testspace / frontend /src /lib /puzzles.ts
Mayug Maniparambil
Add admin leaderboard, LLM comparison, random puzzles, clearer rules
c52f47a
Raw
History Blame Contribute Delete
3.9 kB
import type { PuzzleType } from "./api";
export type PuzzleFamily = {
value: PuzzleType;
label: string;
blurb: string;
helpText: string;
tutorialUrl: string;
};
// To add a tutorial: paste a URL into the matching `tutorialUrl`.
// Empty string -> the "Watch tutorial" link is hidden.
export const PUZZLE_FAMILIES: PuzzleFamily[] = [
{
value: "bridges",
label: "Bridges",
blurb: "Connect islands with single or double links.",
helpText:
"Connect every numbered island into one network of bridges. The number on each island is exactly how many bridge-ends must touch it. Bridges run horizontally or vertically, never cross, and you may use at most two between any pair of islands. Click between two islands to cycle no bridge → single → double.",
tutorialUrl: "https://www.youtube.com/watch?v=i9RL_3cn4mo",
},
{
value: "flow_free",
label: "Flow Free",
blurb: "Fill the board with non-crossing color paths.",
helpText:
"Connect each pair of matching colored endpoints with a continuous path, leaving no empty cell. Paths cannot cross or share a cell. Click an endpoint to pick its color, then click adjacent empty cells to extend the path; clicking a filled cell with that same color erases it. The board is solved when every color is paired and every cell is covered.",
tutorialUrl: "https://www.youtube.com/shorts/mli8IdECi-g",
},
{
value: "galaxies",
label: "Galaxies",
blurb: "Draw region boundaries around rotationally symmetric clusters.",
helpText:
"Partition the board with interior walls so each region contains exactly one dot and is 180°-rotationally symmetric around that dot. For every cell in a galaxy, the cell on the opposite side of the dot must also belong to it. Click an interior segment to add or remove a wall. Submit when every cell is assigned to exactly one valid galaxy.",
tutorialUrl: "https://www.youtube.com/watch?v=legK2UDyHlg",
},
{
value: "loopy",
label: "Loopy",
blurb: "Toggle loop edges around numeric clues.",
helpText:
"Draw exactly one closed loop along the grid edges. The loop cannot branch or cross itself, and the number in each cell counts how many of its four sides are part of the loop. Click any segment (including the outer border) to toggle it between part of the loop and not part of it. The board is solved when every numeric clue is satisfied by a single continuous loop.",
tutorialUrl: "https://www.youtube.com/watch?v=nSLdxmHefts&t=22s",
},
{
value: "pattern",
label: "Pattern",
blurb: "Fill cells to satisfy run clues for every row and column.",
helpText:
"Each row and column clue lists the lengths of consecutive black runs, in order, separated by at least one white cell. Click a cell to toggle it between white and filled black. Solve by filling cells so every row and column simultaneously matches its clues — only the run lengths matter, no other connectivity rule applies.",
tutorialUrl: "https://www.youtube.com/watch?v=CRVEAxP-UUk",
},
{
value: "undead",
label: "Undead",
blurb: "Place monsters around mirrors and sightline clues.",
helpText:
"Place ghosts (G), vampires (V), and zombies (Z) so each edge clue equals the number of monsters seen looking down that row or column from that side. Vampires are visible only along direct line of sight; ghosts are visible only when at least one mirror redirects the sight; zombies are visible either way. The / and \\ symbols are fixed mirrors that bend the sightline by 90°. Click any empty cell to cycle blank → G → V → Z.",
tutorialUrl: "https://www.youtube.com/watch?v=L72V9O_anbg&t=187s",
},
];
export const PUZZLE_FAMILY_BY_TYPE: Record<PuzzleType, PuzzleFamily> = Object.fromEntries(
PUZZLE_FAMILIES.map((family) => [family.value, family]),
) as Record<PuzzleType, PuzzleFamily>;