Skip to content

Instantly share code, notes, and snippets.

@jpadams
Created February 2, 2026 06:13
Show Gist options
  • Select an option

  • Save jpadams/a1afd771410655d29e8d78f609268c1e to your computer and use it in GitHub Desktop.

Select an option

Save jpadams/a1afd771410655d29e8d78f609268c1e to your computer and use it in GitHub Desktop.
Cypher script to generate a Dobble / Spot it! deck (full 57 cards, not just 55. not exact correspondence with commercial version)
// =======================================================
// Dobble / Spot It! (order 7 projective plane) generator
// cypher-shell friendly: each semicolon ends a statement,
// and NO variables are expected to persist across statements.
// =======================================================
// ---------- OPTIONAL CLEAN SLATE (uncomment if desired) ----------
// MATCH (n) WHERE n:Point OR n:Line OR n:Symbol OR n:Card DETACH DELETE n;
// =======================================================
// 1) Create 57 Points/Symbols (pointId 0..56)
// =======================================================
WITH [
"Anchor",
"Apple",
"Baby bottle",
"Bomb",
"Cactus",
"Candle",
"Taxi car",
"Carrot",
"Chess knight",
"Clock",
"Clown",
"Daisy flower",
"Dinosaur",
"Dolphin",
"Dragon",
"Exclamation point",
"Eye",
"Fire",
"Four leaf clover",
"Ghost",
"Green splats",
"Hammer",
"Heart",
"Ice cube",
"Igloo",
"Key",
"Ladybird (Ladybug)",
"Light bulb",
"Lightning bolt",
"Lock",
"Maple leaf",
"Moon",
"No Entry sign",
"Orange scarecrow man",
"Pencil",
"Purple bird",
"Purple cat",
"Purple dobble hand man",
"Red lips",
"Scissors",
"Skull and crossbones",
"Snowflake",
"Snowman",
"Spider",
"Spider web",
"Sun",
"Sunglasses",
"Target/crosshairs",
"Tortoise",
"Treble clef",
"Tree",
"Water drop",
"Dog",
"Yin and Yang",
"Zebra",
"Question mark",
"Cheese"
] AS symbols
UNWIND range(0,56) AS pid
WITH pid,
symbols[pid] AS name,
CASE WHEN pid < 49 THEN "affine" ELSE "infinity" END AS kind,
CASE WHEN pid < 49 THEN toInteger(floor(pid / 7.0)) ELSE null END AS x,
CASE WHEN pid < 49 THEN (pid % 7) ELSE null END AS y,
CASE
WHEN pid = 56 THEN "vertical"
WHEN pid >= 49 AND pid <= 55 THEN (pid - 49)
ELSE null
END AS slope
MERGE (p:Point:Symbol {pointId: pid})
SET p.name = name,
p.kind = kind,
p.x = x,
p.y = y,
p.slope = slope;
// =======================================================
// 2) Create 57 Lines/Cards (cardId 0..56)
// 0..48 : y = m*x + b where m,b in 0..6 (cid = m*7 + b)
// 49..55 : x = c where c in 0..6 (cid = 49 + c)
// 56 : line_at_infinity
// =======================================================
UNWIND range(0,56) AS cid
WITH cid,
CASE
WHEN cid < 49 THEN "affine"
WHEN cid < 56 THEN "vertical"
ELSE "infinity"
END AS kind,
CASE WHEN cid < 49 THEN toInteger(floor(cid / 7.0)) ELSE null END AS m,
CASE WHEN cid < 49 THEN (cid % 7) ELSE null END AS b,
CASE WHEN cid >= 49 AND cid <= 55 THEN (cid - 49) ELSE null END AS x,
CASE
WHEN cid < 49 THEN ("y=" + toString(toInteger(floor(cid / 7.0))) + "x+" + toString(cid % 7))
WHEN cid < 56 THEN ("x=" + toString(cid - 49))
ELSE "line_at_infinity"
END AS label
MERGE (l:Line:Card {cardId: cid})
SET l.kind = kind,
l.m = m,
l.b = b,
l.x = x,
l.label = label;
// =======================================================
// 3) Incidence edges (:Point)-[:ON]->(:Line)
// =======================================================
// 3a) Affine lines contain 7 affine points
MATCH (l:Line:Card {kind:"affine"})
WITH l, l.m AS m, l.b AS b
UNWIND range(0,6) AS x
WITH l, m, b, x, ((m*x + b) % 7) AS y
MATCH (p:Point:Symbol {kind:"affine", x:x, y:y})
MERGE (p)-[:ON]->(l);
// 3b) Each affine line also contains its slope infinity point
MATCH (l:Line:Card {kind:"affine"})
WITH l, l.m AS m
MATCH (inf:Point:Symbol {kind:"infinity", slope:m})
MERGE (inf)-[:ON]->(l);
// 3c) Vertical lines contain 7 affine points
MATCH (l:Line:Card {kind:"vertical"})
WITH l, l.x AS c
UNWIND range(0,6) AS y
MATCH (p:Point:Symbol {kind:"affine", x:c, y:y})
MERGE (p)-[:ON]->(l);
// 3d) Vertical lines also contain the vertical infinity point
MATCH (l:Line:Card {kind:"vertical"})
MATCH (inf:Point:Symbol {kind:"infinity", slope:"vertical"})
MERGE (inf)-[:ON]->(l);
// 3e) The line at infinity contains all 8 infinity points
MATCH (l:Line:Card {kind:"infinity"})
MATCH (inf:Point:Symbol {kind:"infinity"})
MERGE (inf)-[:ON]->(l);
// =======================================================
// 4) Checks
// =======================================================
// Each card has 8 symbols
MATCH (c:Card)<-[:ON]-(s:Symbol)
WITH c, count(s) AS k
RETURN min(k) AS minSymbolsPerCard, max(k) AS maxSymbolsPerCard;
// Each symbol appears on 8 cards
MATCH (s:Symbol)-[:ON]->(c:Card)
WITH s, count(c) AS k
RETURN min(k) AS minCardsPerSymbol, max(k) AS maxCardsPerSymbol;
// Any two cards share exactly 1 symbol
MATCH (c1:Card)<-[:ON]-(s:Symbol)-[:ON]->(c2:Card)
WHERE c1.cardId < c2.cardId
WITH c1, c2, count(DISTINCT s) AS shared
WITH shared, count(*) AS pairCount
RETURN shared, pairCount
ORDER BY shared;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment