Last active
February 21, 2025 12:54
-
-
Save michael-simons/837577004840d6ee53085e1e2aa53d82 to your computer and use it in GitHub Desktop.
Cypher based Game of Life implementation meets Java meets the terminal. xkcd 356 applies.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
///usr/bin/env jbang "$0" "$@" ; exit $? | |
//JAVA 24 | |
//PREVIEW | |
//DEPS org.neo4j:neo4j-jdbc:6.1.3 | |
import java.sql.DriverManager; | |
import org.neo4j.jdbc.Neo4jPreparedStatement; | |
/** | |
* See <a href="https://neo4j.com/blog/developer/cypher-game-of-life/">A Cypher Game of Life</a>. | |
* | |
* Run with | |
* <pre> | |
* jbang https://gist.github.com/michael-simons/837577004840d6ee53085e1e2aa53d82 | |
* </pre> | |
* | |
* @throws Exception we just throw things around here | |
*/ | |
void main() throws Exception { | |
var height = 30; | |
var width = 30; | |
var sleep = Duration.ofMillis(500); | |
var random = ThreadLocalRandom.current(); | |
var numAlive = (int) (height * width * 0.1); | |
var cells = random.ints(numAlive * 2, 0, height).boxed().gather(Gatherers.windowSliding(2)).toList(); | |
try (var connection = DriverManager.getConnection("jdbc:neo4j://localhost:7687", "neo4j", "verysecret")) { | |
try (var stmt = connection.prepareStatement("MATCH (c:Cell) DETACH DELETE c")) { | |
stmt.executeUpdate(); | |
} | |
try (var stmt = connection.prepareStatement(""" | |
UNWIND range(0,$height-1) AS row | |
WITH row | |
UNWIND range(0,$width-1) AS column | |
WITH row, column | |
CREATE (cell:Cell {coordinate: Point({x:column,y:row}), alive:false}) | |
WITH row, column, cell | |
UNWIND [[row-1, column-1],[row-1, column],[row-1, column+1],[row, column-1]] AS neighbor | |
MATCH (other:Cell {coordinate: Point({x:neighbor[1],y:neighbor[0]})}) | |
MERGE (other)-[:NEIGHBOUR_OF]->(cell) | |
""").unwrap(Neo4jPreparedStatement.class)) { | |
stmt.setInt("height", height); | |
stmt.setInt("width", width); | |
stmt.executeUpdate(); | |
} | |
try (var stmt = connection.prepareStatement(""" | |
UNWIND $cells AS cell | |
MATCH (c:Cell {coordinate: Point({x:cell[0],y:cell[1]})}) | |
SET c.alive = true | |
""" | |
).unwrap(Neo4jPreparedStatement.class)) { | |
stmt.setObject("cells", cells); | |
stmt.executeUpdate(); | |
} | |
try (var stmt = connection.prepareStatement(""" | |
MATCH (cell:Cell) | |
OPTIONAL MATCH (cell)-[:NEIGHBOUR_OF]-(:Cell {alive: true }) | |
WITH cell, COUNT(*) AS liveNeighbours | |
SET cell.alive = | |
CASE liveNeighbours | |
WHEN <= 1 THEN FALSE | |
WHEN >= 4 THEN FALSE | |
WHEN = 3 THEN TRUE | |
ELSE cell.alive | |
END | |
WITH cell ORDER BY cell.coordinate.y ASC, cell.coordinate.x ASC | |
WITH collect(cell) AS cells | |
RETURN reduce(res = "", cell IN cells | | |
res + | |
CASE cell.coordinate.x = 0 | |
WHEN true THEN "\\n" | |
ELSE "" | |
END + | |
CASE cell.alive | |
WHEN true THEN '\u001b[1;38;5;0;48;5;253m ■ \u001b[0m' | |
ELSE '\u001b[1;38;5;214;48;5;253m ■ \u001b[0m' | |
END) AS result | |
""")) { | |
var previous = ""; | |
while (true) { | |
var rs = stmt.executeQuery(); | |
rs.next(); | |
var result = rs.getString("result"); | |
System.out.print("\033[H\033[2J"); | |
System.out.println(result); | |
System.out.flush(); | |
Thread.sleep(sleep); | |
if (result.equals(previous)) { | |
break; | |
} | |
previous = result; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment