Skip to content

Instantly share code, notes, and snippets.

@michael-simons
Last active February 21, 2025 12:54
Show Gist options
  • Save michael-simons/837577004840d6ee53085e1e2aa53d82 to your computer and use it in GitHub Desktop.
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.
///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