Skip to content

Instantly share code, notes, and snippets.

@jexp
Last active April 13, 2022 10:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jexp/9db80d92bf260693075077ab41fd794d to your computer and use it in GitHub Desktop.
Save jexp/9db80d92bf260693075077ab41fd794d to your computer and use it in GitHub Desktop.
game of life in neo4j

Conways Game of Life in Cypher

John Conway (RIP) created the amazing game of life automaton, which is based on a simple rule.

Every cell that has either exactly 3 neighbours or was alive and has 2 neighbours is alive in the next generation.

Setup Grid

// constraint for lookup
create constraint if not exists on (p:Point) assert p.loc is unique;

// create points
unwind range(0,99) as x
unwind range(0,99) as y
create (:Point {loc:point({x:x, y:y})});


// with diagonals (8 neighbours each)
:auto unwind range(0,99) as x
unwind range(0,99) as y
call { with x,y
match (p:Point {loc:point({x:x, y:y})})
unwind range(-1,1) as dx
unwind range(-1,1) as dy
match (o:Point {loc:point({x:x+dx, y:y+dy})})
where o<>p
merge (p)-[:NB]-(o)
} in transactions of 1000 rows;
Show a few nodes
// show a bunch starting from 0,0
MATCH p=(:Point {loc:point({x:0,y:0})})-[*3]-() RETURN p LIMIT 100

Starting Patterns

We are using an R-Pentomino the smallest 5 element starting point. Could also use a glider or such.

// set smallest r-pentomino
// .OO
// OO.
// .O.
unwind [[1,0],[2,0],[0,1],[1,1],[1,2]] as pento
match (p:Point {loc:point({x: 5+pento[0], y:5+pento[1]})})
SET p:Alive;

Iterate Generations based on the Rule

// iterate generations

match (p:Point)
with p, case size((p)--(:Alive)) when 2 then p:Alive when 3 then true else false end as alive
call { with p, alive
    WITH * WHERE alive SET p:Alive
}
call { with p, alive
    WITH * WHERE not alive REMOVE p:Alive
};

Display Results

In Neo4j Browser it’s a bit of dragging involved, in Neo4j Bloom it should be automatically updated

// display results
unwind range(0,9) as x
unwind range(0,9) as y
match (p:Point {loc:point({x:x,y:y})}) return p
162638740 2c48c7b1 a05e 45d7 9cf3 34fb0d2f1905
Figure 1. Starting Point
162638745 d1596452 6035 4093 a1a6 8a8b41355368
Figure 2. First Generation
162638744 aa7fafe1 31e7 4497 a995 7d9cfdd260d4
Figure 3. Second Generation
{
"name": "Game of Life",
"id": "dc0d5540-ba64-11ec-a125-eb9a0fd2a20c",
"categories": [{
"id": 1,
"name": "Alive",
"labels": ["Alive"],
"properties": [{
"name": "loc",
"exclude": false,
"isCaption": true,
"dataType": "Point"
}, {
"name": "x",
"exclude": false,
"isCaption": false,
"dataType": "number"
}, {
"name": "y",
"exclude": false,
"isCaption": false,
"dataType": "number"
}],
"caption": [""],
"createdAt": 1649770289321,
"lastEditedAt": 1649770289321,
"color": "#FFE081",
"size": 1,
"icon": "no-icon"
}, {
"id": 2,
"name": "Point",
"labels": ["Point"],
"properties": [{
"name": "loc",
"exclude": false,
"isCaption": true,
"dataType": "Point"
}, {
"name": "x",
"exclude": false,
"isCaption": false,
"dataType": "number"
}, {
"name": "y",
"exclude": false,
"isCaption": false,
"dataType": "number"
}],
"caption": [""],
"createdAt": 1649770295174,
"lastEditedAt": 1649770295177,
"color": "#C990C0",
"size": 1,
"icon": "no-icon"
}],
"labels": {
"Alive": [{
"propertyKey": "loc",
"type": "Alive",
"dataType": "Point"
}, {
"propertyKey": "x",
"type": "Alive",
"dataType": "number"
}, {
"propertyKey": "y",
"type": "Alive",
"dataType": "number"
}],
"Point": [{
"propertyKey": "loc",
"type": "Point",
"dataType": "Point"
}, {
"propertyKey": "x",
"type": "Point",
"dataType": "number"
}, {
"propertyKey": "y",
"type": "Point",
"dataType": "number"
}]
},
"relationshipTypes": [{
"properties": [],
"name": "NB",
"id": "NB"
}],
"palette": {
"colors": ["#FFE081", "#C990C0", "#F79767", "#57C7E3", "#F16667", "#D9C8AE", "#8DCC93", "#ECB5C9", "#4C8EDA", "#FFC454", "#DA7194", "#569480", "#848484", "#D9D9D9"],
"currentIndex": 2
},
"createdAt": 1649770289300,
"lastEditedAt": 1649770289300,
"templates": [{
"name": "grid",
"id": "tmpl:1649770986918",
"createdAt": 1649770986918,
"text": "grid",
"cypher": "unwind range(0,15) as x\nunwind range(0,15) as y\nmatch (p:Point {loc:point({x:x,y:y})}) return p",
"isUpdateQuery": null,
"params": [],
"hasCypherErrors": false
}, {
"name": "Next Generation",
"id": "tmpl:1649770530334",
"createdAt": 1649770530334,
"text": "next",
"cypher": "match (p:Point) WHERE p:Alive OR exists { (p)-[:NB]-(:Alive) }\nwith p, case size((p)--(:Alive)) when 2 then p:Alive when 3 then true else false end as alive\ncall { with p, alive\n WITH * WHERE alive SET p:Alive\n}\ncall { with p, alive\n WITH * WHERE not alive REMOVE p:Alive\n}\nRETURN p",
"isUpdateQuery": null,
"params": [],
"hasCypherErrors": false
}],
"sceneActions": [{
"name": "Toggle",
"id": "8c9ad4a0-bb14-11ec-a125-eb9a0fd2a20c",
"createdAt": 1649845747435,
"cypher": "MATCH (n:Alive) WHERE id(n) in $nodes REMOVE n:Alive return n\nunion all \nMATCH (n:Point) WHERE id(n) in $nodes AND not n:Alive SET n:Alive return n",
"isUpdateQuery": null,
"categories": null,
"relationshipTypes": null,
"hasCypherErrors": false
}],
"hiddenRelationshipTypes": [],
"hiddenCategories": [],
"hideUncategorisedData": false,
"isAuto": false,
"parentPerspectiveId": null,
"metadata": {
"pathSegments": [{
"source": "Point",
"relationshipType": "NB",
"target": "Alive"
}, {
"source": "Point",
"relationshipType": "NB",
"target": "Point"
}, {
"source": "Alive",
"relationshipType": "NB",
"target": "Point"
}, {
"source": "Alive",
"relationshipType": "NB",
"target": "Alive"
}],
"indexes": [{
"label": "Alive",
"type": "native",
"isMetadataPropIndex": true,
"propertyKeys": ["loc", "x", "y"]
}, {
"label": "Point",
"type": "native",
"propertyKeys": ["loc"]
}],
"stats": {
"labels": {},
"relationshipTypes": {
"NB": 39402
}
}
},
"version": "2.2.0"
}
@jexp
Copy link
Author

jexp commented Apr 13, 2022

game-of-life-bloom.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment