8bitcn UI
blocknot-found-crate-pushersource: ok · refreshed
8-bit Crate Pusher 404
A randomized crate-pushing 404 page with 20 curated, solver-verified puzzles.
npx shadcn add "https://www.8bitcn.com/r/not-found-crate-pusher.json"free
Where it goes
error-state
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lightRegistry dependencies
badgebuttoncard
Files8 · source cached 10/6/2026, 11:38:47 PM
components/ui/8bit/blocks/not-found-crate-pusher.tsx
/** biome-ignore-all lint/a11y/noNoninteractiveElementInteractions: The focus-scoped game fieldset intentionally owns keyboard input. */
/** biome-ignore-all lint/a11y/noNoninteractiveTabindex: The game fieldset must receive focus without a global listener. */
"use client";
import Link from "next/link";
import {
type ComponentPropsWithoutRef,
type KeyboardEvent as ReactKeyboardEvent,
useId,
useMemo,
useReducer,
useRef,
useState,
} from "react";
import { Badge } from "@/components/ui/8bit/badge";
import { Button } from "@/components/ui/8bit/button";
import {
Card,
CardContent,
CardFooter,
CardHeader,
} from "@/components/ui/8bit/card";
import { Kbd, KbdGroup } from "@/components/ui/8bit/kbd";
import {
type CratePusherState,
createCratePusherState,
type Direction,
moveCratePusher,
type ParsedCratePusherLevel,
type PositionKey,
parseCratePusherLevel,
resetCratePusher,
startCratePusher,
undoCratePusher,
} from "@/lib/games/crate-pusher";
import {
CRATE_PUSHER_LEVELS,
type CratePusherLevelDefinition,
type CratePusherShuffleBag,
createCratePusherShuffleBag,
drawCratePusherShuffleBag,
} from "@/lib/games/crate-pusher-levels";
import { cn } from "@/lib/utils";
import "@/components/ui/8bit/styles/retro.css";
export interface NotFoundCratePusherProps
extends ComponentPropsWithoutRef<"section"> {
badge?: string;
cta?: string;
description?: string;
href?: string;
level?: readonly string[];
seed?: number;
title?: string;
}
interface CratePusherGameProps
extends Omit<NotFoundCratePusherProps, "level" | "seed"> {
readonly catalogMode: boolean;
readonly initialLevel: ParsedCratePusherLevel;
readonly seed?: number;
}
interface BoardTileProps {
readonly column: number;
readonly row: number;
readonly state: CratePusherState;
}
type CratePusherAction =
| {
readonly announcement: string;
readonly level: ParsedCratePusherLevel;
readonly type: "load";
}
| { readonly type: "move"; readonly direction: Direction }
| { readonly type: "restart" }
| { readonly type: "start" }
| { readonly type: "undo" };
const KEY_DIRECTIONS: Readonly<Record<string, Direction | undefined>> = {
a: "left",
arrowdown: "down",
arrowleft: "left",
arrowright: "right",
arrowup: "up",
d: "right",
s: "down",
w: "up",
};
const INTERACTIVE_SELECTOR =
"a, button, input, textarea, select, [contenteditable=''], [contenteditable='true']";
const PARSED_CRATE_PUSHER_LEVELS = Object.freeze(
CRATE_PUSHER_LEVELS.map((level) => parseCratePusherLevel(level.lines))
);
function createPositionKey(row: number, column: number): PositionKey {
return `${row}:${column}`;
}
function cratePusherReducer(
state: CratePusherState,
action: CratePusherAction
): CratePusherState {
switch (action.type) {
case "load": {
const startedState = startCratePusher(resetCratePusher(action.level));
return {
...startedState,
announcement: `${action.announcement} ${startedState.announcement}`,
};
}
case "move":
return moveCratePusher(state, action.direction);
case "restart":
return startCratePusher(resetCratePusher(state.level));
case "start":
return startCratePusher(state);
case "undo":
return undoCratePusher(state);
default:
return state;
}
}
function createInitialGameState({
catalogMode,
initialLevel,
}: {
readonly catalogMode: boolean;
readonly initialLevel: ParsedCratePusherLevel;
}): CratePusherState {
const state = createCratePusherState(initialLevel);
if (!catalogMode) {
return state;
}
return {
...state,
announcement: `${CRATE_PUSHER_LEVELS.length} puzzles ready. Select Start Shift to load a random route.`,
};
}
function createRuntimeSeed(): number {
const randomValue = new Uint32Array(1);
if (typeof globalThis.crypto?.getRandomValues === "function") {
globalThis.crypto.getRandomValues(randomValue);
return randomValue[0] ?? 0;
}
return Math.floor(Math.random() * 4_294_967_296);
}
function getCatalogLevel(levelIndex: number): {
readonly definition: CratePusherLevelDefinition;
readonly parsedLevel: ParsedCratePusherLevel;
} {
const definition = CRATE_PUSHER_LEVELS[levelIndex];
const parsedLevel = PARSED_CRATE_PUSHER_LEVELS[levelIndex];
if (!(definition && parsedLevel)) {
throw new Error(`Crate Pusher puzzle ${levelIndex + 1} was not found.`);
}
return { definition, parsedLevel };
}
function createPuzzleAnnouncement(
definition: CratePusherLevelDefinition,
levelIndex: number
): string {
return `${definition.name}. Puzzle ${levelIndex + 1} of ${CRATE_PUSHER_LEVELS.length}. ${definition.difficulty} route loaded.`;
}
function getPuzzleDisplayMetadata(
catalogMode: boolean,
activeLevelIndex: number | null
): {
readonly boardLabel: string;
readonly difficultyLabel: string;
readonly puzzleLabel: string;
} {
if (!catalogMode) {
return {
boardLabel: "Crate Pusher custom game board",
difficultyLabel: "CUSTOM",
puzzleLabel: "PUZZLE CUSTOM",
};
}
if (activeLevelIndex === null) {
return {
boardLabel: `Crate Pusher random puzzle preview, ${CRATE_PUSHER_LEVELS.length} routes available`,
difficultyLabel: "RANDOM",
puzzleLabel: `PUZZLES ${CRATE_PUSHER_LEVELS.length}`,
};
}
const { definition } = getCatalogLevel(activeLevelIndex);
const puzzleNumber = activeLevelIndex + 1;
return {
boardLabel: `Crate Pusher puzzle ${puzzleNumber} of ${CRATE_PUSHER_LEVELS.length}, ${definition.difficulty} game board`,
difficultyLabel: definition.difficulty.toUpperCase(),
puzzleLabel: `PUZZLE ${puzzleNumber}/${CRATE_PUSHER_LEVELS.length}`,
};
}
function isInteractiveTarget(target: EventTarget | null): boolean {
return (
target instanceof Element && target.closest(INTERACTIVE_SELECTOR) !== null
);
}
function getPhaseLabel(state: CratePusherState): string {
switch (state.phase) {
case "idle":
return "IDLE";
case "playing":
return "PLAYING";
case "won":
return "WON";
default:
return "IDLE";
}
}
function getTerminalStatus(state: CratePusherState): string {
switch (state.phase) {
case "idle":
return "AWAITING SHIFT";
case "playing":
return "SHIFT ACTIVE";
case "won":
return "ROUTE RESTORED";
default:
return "AWAITING SHIFT";
}
}
function countFilledPortals(state: CratePusherState): number {
let filledPortals = 0;
for (const portal of state.level.portals) {
if (state.crates.has(portal)) {
filledPortals += 1;
}
}
return filledPortals;
}
function getBoardTileKind(
isWall: boolean,
isCrate: boolean,
isPortal: boolean
): string {
if (isWall) {
return "wall";
}
if (isCrate && isPortal) {
return "crate-on-portal";
}
if (isCrate) {
return "crate";
}
if (isPortal) {
return "portal";
}
return "floor";
}
function BoardTile({ column, row, state }: BoardTileProps) {
const position = createPositionKey(row, column);
const isWall = state.level.walls.has(position);
const isPortal = state.level.portals.has(position);
const isCrate = state.crates.has(position);
const isPlayer = state.player === position;
const tileKind = getBoardTileKind(isWall, isCrate, isPortal);
return (
<div
className={cn(
"relative aspect-square min-w-0 flex-1 overflow-hidden border border-background/30 bg-muted/60",
isWall && "bg-foreground"
)}
data-player={isPlayer ? "true" : undefined}
data-position={`${row + 1}:${column + 1}`}
data-tile={tileKind}
>
{isWall && (
<span className="absolute inset-1 border border-background/40 bg-background/15" />
)}
{isPortal && !isWall && (
<>
<span className="absolute inset-[18%] border-2 border-primary" />
<span className="absolute inset-[36%] bg-primary" />
</>
)}
{isCrate && !isWall && (
<span
className={cn(
"absolute inset-[14%] border-2 border-foreground bg-secondary",
isPortal && "outline-2 outline-primary outline-offset-1"
)}
>
<span className="absolute inset-x-[18%] top-[18%] h-[12%] bg-foreground" />
<span className="absolute inset-x-[18%] bottom-[18%] h-[12%] bg-foreground" />
<span className="absolute inset-y-[18%] left-[18%] w-[12%] bg-foreground" />
<span className="absolute inset-y-[18%] right-[18%] w-[12%] bg-foreground" />
</span>
)}
{isPlayer && !isWall && !isCrate && (
<span className="absolute inset-0">
<span className="absolute top-[14%] left-[34%] size-[32%] border border-foreground bg-primary" />
<span className="absolute top-[42%] left-[24%] h-[32%] w-[52%] border border-foreground bg-primary" />
<span className="absolute bottom-[10%] left-[24%] h-[18%] w-[18%] bg-foreground" />
<span className="absolute right-[24%] bottom-[10%] h-[18%] w-[18%] bg-foreground" />
<span className="absolute top-[48%] left-[8%] h-[14%] w-[18%] bg-foreground" />
<span className="absolute top-[48%] right-[8%] h-[14%] w-[18%] bg-foreground" />
</span>
)}
</div>
);
}
function CratePusherBoard({ state }: { readonly state: CratePusherState }) {
return (
<div
aria-hidden="true"
className="w-full border-2 border-foreground bg-muted"
>
{Array.from({ length: state.level.rows }, (_, row) => (
<div className="flex w-full" key={createPositionKey(row, 0)}>
{Array.from({ length: state.level.columns }, (__, column) => (
<BoardTile
column={column}
key={createPositionKey(row, column)}
row={row}
state={state}
/>
))}
</div>
))}
</div>
);
}
function CratePusherGame({
badge = "ROUTE LOST",
catalogMode,
className,
cta = "RETURN HOME",
description,
href = "/",
initialLevel,
seed,
title = "404: CRATE RESCUE",
...sectionProps
}: CratePusherGameProps) {
const [state, dispatch] = useReducer(
cratePusherReducer,
{ catalogMode, initialLevel },
createInitialGameState
);
const [activeLevelIndex, setActiveLevelIndex] = useState<number | null>(null);
const boardReference = useRef<HTMLFieldSetElement>(null);
const shuffleBagReference = useRef<CratePusherShuffleBag | null>(null);
const componentId = useId();
const headingId = `${componentId}-heading`;
const instructionsId = `${componentId}-instructions`;
const resolvedDescription =
description ??
(catalogMode
? "Push every lost crate onto a portal across 20 randomized routes, or return home now."
: "Push every lost crate onto a portal, or return home now.");
const filledPortals = countFilledPortals(state);
const movementIsActive = state.phase === "playing";
const { boardLabel, difficultyLabel, puzzleLabel } = getPuzzleDisplayMetadata(
catalogMode,
activeLevelIndex
);
const focusBoard = () => {
boardReference.current?.focus();
};
const loadNextRandomPuzzle = () => {
const shuffleBag =
shuffleBagReference.current ??
createCratePusherShuffleBag(seed ?? createRuntimeSeed());
const draw = drawCratePusherShuffleBag(shuffleBag);
const nextLevel = getCatalogLevel(draw.levelIndex);
shuffleBagReference.current = draw.shuffleBag;
setActiveLevelIndex(draw.levelIndex);
dispatch({
announcement: createPuzzleAnnouncement(
nextLevel.definition,
draw.levelIndex
),
level: nextLevel.parsedLevel,
type: "load",
});
focusBoard();
};
const handleStart = () => {
if (catalogMode && activeLevelIndex === null) {
loadNextRandomPuzzle();
return;
}
dispatch({ type: "start" });
focusBoard();
};
const handleRestart = () => {
dispatch({ type: "restart" });
focusBoard();
};
const handleMove = (direction: Direction) => {
dispatch({ direction, type: "move" });
};
const handleKeyDown = (event: ReactKeyboardEvent<HTMLFieldSetElement>) => {
if (
event.altKey ||
event.ctrlKey ||
event.metaKey ||
event.shiftKey ||
event.repeat ||
event.currentTarget !== event.target ||
isInteractiveTarget(event.target)
) {
return;
}
const key = event.key.toLowerCase();
const direction = KEY_DIRECTIONS[key];
if (direction) {
if (!movementIsActive) {
return;
}
event.preventDefault();
handleMove(direction);
return;
}
if (key === "u" && state.phase !== "idle") {
event.preventDefault();
dispatch({ type: "undo" });
return;
}
if (key === "r" && state.phase !== "idle") {
event.preventDefault();
handleRestart();
}
};
return (
<section
{...sectionProps}
aria-labelledby={headingId}
className={cn(
"retro w-full bg-background px-4 py-12 text-foreground sm:px-6 sm:py-16",
className
)}
>
<div className="mx-auto flex w-full max-w-4xl flex-col gap-8">
<header className="flex flex-col gap-5">
<div className="flex flex-wrap items-center justify-between gap-5">
<Badge>{badge}</Badge>
<Button asChild variant="outline">
<Link href={href}>{cta}</Link>
</Button>
</div>
<div className="space-y-3">
<p aria-hidden="true" className="font-bold text-6xl sm:text-8xl">
404
</p>
<h1 className="font-bold text-2xl sm:text-4xl" id={headingId}>
{title}
</h1>
<p className="max-w-2xl text-muted-foreground text-xs">
{resolvedDescription}
</p>
</div>
</header>
<Card className="w-full">
<CardHeader className="gap-4">
<div className="flex flex-wrap gap-3">
<Badge variant="secondary">MOVES {state.moves}</Badge>
<Badge variant="secondary">PUSHES {state.pushes}</Badge>
<Badge variant="secondary">
PORTALS {filledPortals}/{state.level.portals.size}
</Badge>
<Badge variant="secondary">{puzzleLabel}</Badge>
<Badge variant="outline">LEVEL {difficultyLabel}</Badge>
<Badge variant="outline">PHASE {getPhaseLabel(state)}</Badge>
</div>
</CardHeader>
<CardContent className="flex flex-col gap-5">
<fieldset
aria-describedby={instructionsId}
aria-keyshortcuts="ArrowUp ArrowRight ArrowDown ArrowLeft W A S D U R"
aria-label={boardLabel}
className="mx-auto w-full min-w-0 max-w-2xl border-0 p-0 outline-none focus-visible:ring-4 focus-visible:ring-primary focus-visible:ring-offset-4 focus-visible:ring-offset-background"
data-phase={state.phase}
onKeyDown={handleKeyDown}
ref={boardReference}
tabIndex={0}
>
<CratePusherBoard state={state} />
</fieldset>
<p className="text-muted-foreground text-xs" id={instructionsId}>
Start the shift, then use Arrow keys or WASD to move. Push every
crate onto a portal. Press U to undo or R to restart. Random
routes can be skipped without losing the rest of the queue.
</p>
<div className="hidden flex-wrap items-center gap-4 md:flex">
<KbdGroup>
<Kbd>ARROWS</Kbd>
<span className="text-muted-foreground text-xs">OR</span>
<Kbd>WASD</Kbd>
<span className="text-muted-foreground text-xs">MOVE</span>
</KbdGroup>
<KbdGroup>
<Kbd>U</Kbd>
<span className="text-muted-foreground text-xs">UNDO</span>
</KbdGroup>
<KbdGroup>
<Kbd>R</Kbd>
<span className="text-muted-foreground text-xs">RESTART</span>
</KbdGroup>
</div>
<fieldset
aria-label="Movement controls"
className="mx-auto grid w-full max-w-64 grid-cols-3 gap-3 border-0 p-0"
>
<Button
aria-label="Move up"
className="col-start-2 min-h-11"
disabled={!movementIsActive}
onClick={() => handleMove("up")}
type="button"
>
UP
</Button>
<Button
aria-label="Move left"
className="col-start-1 min-h-11"
disabled={!movementIsActive}
onClick={() => handleMove("left")}
type="button"
>
LEFT
</Button>
<Button
aria-label="Move down"
className="min-h-11"
disabled={!movementIsActive}
onClick={() => handleMove("down")}
type="button"
>
DOWN
</Button>
<Button
aria-label="Move right"
className="min-h-11"
disabled={!movementIsActive}
onClick={() => handleMove("right")}
type="button"
>
RIGHT
</Button>
</fieldset>
<div className="border-2 border-foreground bg-muted p-4">
<div className="flex flex-wrap items-center justify-between gap-2">
<span className="font-bold text-xs">ROUTE TERMINAL</span>
<span className="text-primary text-xs">
{getTerminalStatus(state)}
</span>
</div>
<p
aria-atomic="true"
aria-live="polite"
className="mt-2 text-muted-foreground text-xs"
role="status"
>
{state.announcement}
</p>
</div>
</CardContent>
<CardFooter className="flex flex-wrap gap-4">
{state.phase === "idle" && (
<Button onClick={handleStart} type="button">
START SHIFT
</Button>
)}
{state.phase === "playing" && (
<>
<Button
disabled={state.history.length === 0}
onClick={() => dispatch({ type: "undo" })}
type="button"
variant="secondary"
>
UNDO
</Button>
<Button onClick={handleRestart} type="button" variant="outline">
RESTART
</Button>
{catalogMode && (
<Button
onClick={loadNextRandomPuzzle}
type="button"
variant="outline"
>
SKIP PUZZLE
</Button>
)}
</>
)}
{state.phase === "won" && (
<>
<Button
disabled={state.history.length === 0}
onClick={() => dispatch({ type: "undo" })}
type="button"
variant="secondary"
>
UNDO
</Button>
{catalogMode && (
<Button onClick={loadNextRandomPuzzle} type="button">
NEXT RANDOM PUZZLE
</Button>
)}
<Button
onClick={handleRestart}
type="button"
variant={catalogMode ? "outline" : "default"}
>
PLAY AGAIN
</Button>
<Button asChild variant="outline">
<Link href={href}>{cta}</Link>
</Button>
</>
)}
</CardFooter>
</Card>
</div>
</section>
);
}
export function NotFoundCratePusher({
level,
seed,
...props
}: NotFoundCratePusherProps) {
const levelSignature = level?.join("\n") ?? null;
const parsedLevel = useMemo(
() =>
levelSignature === null
? getCatalogLevel(0).parsedLevel
: parseLevelSignature(levelSignature),
[levelSignature]
);
const componentKey =
levelSignature === null
? `catalog:${seed ?? "random"}`
: `custom:${levelSignature}`;
return (
<CratePusherGame
catalogMode={levelSignature === null}
initialLevel={parsedLevel}
key={componentKey}
seed={seed}
{...props}
/>
);
}
function parseLevelSignature(levelSignature: string): ParsedCratePusherLevel {
return parseCratePusherLevel(levelSignature.split("\n"));
}
export default NotFoundCratePusher;
lib/games/crate-pusher.ts
export type Direction = "up" | "down" | "left" | "right";
export type PositionKey = `${number}:${number}`;
export type CratePusherPhase = "idle" | "playing" | "won";
export interface ParsedCratePusherLevel {
readonly columns: number;
readonly initialCrates: ReadonlySet<PositionKey>;
readonly initialPlayer: PositionKey;
readonly portals: ReadonlySet<PositionKey>;
readonly rows: number;
readonly walls: ReadonlySet<PositionKey>;
}
export interface CratePusherSnapshot {
readonly crates: ReadonlySet<PositionKey>;
readonly moves: number;
readonly player: PositionKey;
readonly pushes: number;
}
export interface CratePusherState extends CratePusherSnapshot {
readonly announcement: string;
readonly history: readonly CratePusherSnapshot[];
readonly level: ParsedCratePusherLevel;
readonly phase: CratePusherPhase;
}
const MAX_BOARD_SIZE = 16;
const MAX_HISTORY_LENGTH = 128;
const MIN_BOARD_SIZE = 4;
const POSITION_PATTERN = /^(-?\d+):(-?\d+)$/;
const SUPPORTED_TILES: ReadonlySet<string> = new Set([
" ",
"#",
"$",
"*",
"+",
".",
"@",
]);
const DIRECTION_DELTAS: Readonly<
Record<Direction, readonly [row: number, column: number]>
> = {
down: [1, 0],
left: [0, -1],
right: [0, 1],
up: [-1, 0],
};
interface Coordinates {
readonly column: number;
readonly row: number;
}
function createPositionKey(row: number, column: number): PositionKey {
return `${row}:${column}`;
}
function parsePositionKey(position: PositionKey): Coordinates {
const match = POSITION_PATTERN.exec(position);
if (!match) {
throw new Error(`Invalid crate pusher position: "${position}".`);
}
return {
column: Number(match[2]),
row: Number(match[1]),
};
}
function isPerimeterPosition(
row: number,
column: number,
rows: number,
columns: number
): boolean {
return (
row === 0 || column === 0 || row === rows - 1 || column === columns - 1
);
}
function validateLevelPerimeter(
lines: readonly string[],
columns: number
): void {
for (const [row, line] of lines.entries()) {
for (const [column, tile] of [...line].entries()) {
if (
isPerimeterPosition(row, column, lines.length, columns) &&
tile !== "#"
) {
throw new Error(
`Crate pusher level perimeter must be walls; found "${tile}" at row ${row + 1}, column ${column + 1}.`
);
}
}
}
}
function validateSupportedTiles(lines: readonly string[]): void {
for (const [row, line] of lines.entries()) {
for (const [column, tile] of [...line].entries()) {
if (!SUPPORTED_TILES.has(tile)) {
throw new Error(
`Unsupported crate pusher tile "${tile}" at row ${row + 1}, column ${column + 1}.`
);
}
}
}
}
function isInsideLevel(
position: Coordinates,
level: ParsedCratePusherLevel
): boolean {
return (
position.row >= 0 &&
position.row < level.rows &&
position.column >= 0 &&
position.column < level.columns
);
}
function countFilledPortals(
crates: ReadonlySet<PositionKey>,
level: ParsedCratePusherLevel
): number {
let filledPortals = 0;
for (const portal of level.portals) {
if (crates.has(portal)) {
filledPortals += 1;
}
}
return filledPortals;
}
function createSnapshot(state: CratePusherState): CratePusherSnapshot {
return Object.freeze({
crates: state.crates,
moves: state.moves,
player: state.player,
pushes: state.pushes,
});
}
function appendHistory(
history: readonly CratePusherSnapshot[],
snapshot: CratePusherSnapshot
): readonly CratePusherSnapshot[] {
const nextHistory = [...history, snapshot];
if (nextHistory.length <= MAX_HISTORY_LENGTH) {
return Object.freeze(nextHistory);
}
return Object.freeze(nextHistory.slice(-MAX_HISTORY_LENGTH));
}
function createMoveAnnouncement(
direction: Direction,
player: PositionKey,
crates: ReadonlySet<PositionKey>,
level: ParsedCratePusherLevel,
pushedCrate: boolean
): string {
const action = pushedCrate ? "Pushed a crate" : "Moved";
const filledPortals = countFilledPortals(crates, level);
return `${action} ${direction}. Player ${formatCratePusherPosition(player)}. Portals ${filledPortals} of ${level.portals.size}.`;
}
export function parseCratePusherLevel(
lines: readonly string[]
): ParsedCratePusherLevel {
if (lines.length < MIN_BOARD_SIZE || lines.length > MAX_BOARD_SIZE) {
throw new Error(
`Crate pusher levels must have between ${MIN_BOARD_SIZE} and ${MAX_BOARD_SIZE} rows.`
);
}
const columns = lines[0]?.length ?? 0;
if (columns < MIN_BOARD_SIZE || columns > MAX_BOARD_SIZE) {
throw new Error(
`Crate pusher levels must have between ${MIN_BOARD_SIZE} and ${MAX_BOARD_SIZE} columns.`
);
}
if (lines.some((line) => line.length !== columns)) {
throw new Error("Every crate pusher level row must have the same length.");
}
validateSupportedTiles(lines);
validateLevelPerimeter(lines, columns);
const walls = new Set<PositionKey>();
const portals = new Set<PositionKey>();
const crates = new Set<PositionKey>();
let player: PositionKey | undefined;
let playerCount = 0;
for (const [row, line] of lines.entries()) {
for (const [column, tile] of [...line].entries()) {
const position = createPositionKey(row, column);
switch (tile) {
case " ":
break;
case "#":
walls.add(position);
break;
case ".":
portals.add(position);
break;
case "$":
crates.add(position);
break;
case "*":
crates.add(position);
portals.add(position);
break;
case "+":
player = position;
playerCount += 1;
portals.add(position);
break;
case "@":
player = position;
playerCount += 1;
break;
default:
throw new Error(
`Unsupported crate pusher tile "${tile}" at row ${row + 1}, column ${column + 1}.`
);
}
}
}
if (playerCount === 0 || !player) {
throw new Error("Crate pusher levels must contain exactly one player.");
}
if (playerCount > 1) {
throw new Error("Crate pusher levels must contain exactly one player.");
}
if (crates.size === 0) {
throw new Error("Crate pusher levels must contain at least one crate.");
}
if (crates.size !== portals.size) {
throw new Error(
`Crate pusher levels must contain equal crate and portal counts; found ${crates.size} crates and ${portals.size} portals.`
);
}
return Object.freeze({
columns,
initialCrates: crates,
initialPlayer: player,
portals,
rows: lines.length,
walls,
});
}
export function createCratePusherState(
level: ParsedCratePusherLevel
): CratePusherState {
return {
announcement: "Ready. Select Start Shift to move.",
crates: new Set(level.initialCrates),
history: Object.freeze([]),
level,
moves: 0,
phase: "idle",
player: level.initialPlayer,
pushes: 0,
};
}
export function startCratePusher(state: CratePusherState): CratePusherState {
if (state.phase !== "idle") {
return state;
}
if (isCratePusherSolved(state, state.level)) {
return {
...state,
announcement: "Route restored in 0 moves and 0 pushes.",
phase: "won",
};
}
return {
...state,
announcement: `Shift started. Player ${formatCratePusherPosition(state.player)}.`,
phase: "playing",
};
}
export function moveCratePusher(
state: CratePusherState,
direction: Direction
): CratePusherState {
if (state.phase !== "playing") {
return state;
}
const current = parsePositionKey(state.player);
const [rowDelta, columnDelta] = DIRECTION_DELTAS[direction];
const targetCoordinates = {
column: current.column + columnDelta,
row: current.row + rowDelta,
};
if (!isInsideLevel(targetCoordinates, state.level)) {
return {
...state,
announcement: "Blocked by the edge of the board.",
};
}
const target = createPositionKey(
targetCoordinates.row,
targetCoordinates.column
);
if (state.level.walls.has(target)) {
return {
...state,
announcement: "Blocked by a wall.",
};
}
let nextCrates = state.crates;
let pushedCrate = false;
if (state.crates.has(target)) {
const crateDestinationCoordinates = {
column: targetCoordinates.column + columnDelta,
row: targetCoordinates.row + rowDelta,
};
if (!isInsideLevel(crateDestinationCoordinates, state.level)) {
return {
...state,
announcement: "That crate cannot be pushed.",
};
}
const crateDestination = createPositionKey(
crateDestinationCoordinates.row,
crateDestinationCoordinates.column
);
const crateIsBlocked =
state.level.walls.has(crateDestination) ||
state.crates.has(crateDestination);
if (crateIsBlocked) {
return {
...state,
announcement: "That crate cannot be pushed.",
};
}
const movedCrates = new Set(state.crates);
movedCrates.delete(target);
movedCrates.add(crateDestination);
nextCrates = movedCrates;
pushedCrate = true;
}
const nextState: CratePusherState = {
...state,
announcement: "",
crates: nextCrates,
history: appendHistory(state.history, createSnapshot(state)),
moves: state.moves + 1,
phase: "playing",
player: target,
pushes: state.pushes + (pushedCrate ? 1 : 0),
};
if (isCratePusherSolved(nextState, state.level)) {
return {
...nextState,
announcement: `Route restored in ${nextState.moves} moves and ${nextState.pushes} pushes.`,
phase: "won",
};
}
return {
...nextState,
announcement: createMoveAnnouncement(
direction,
nextState.player,
nextState.crates,
state.level,
pushedCrate
),
};
}
export function undoCratePusher(state: CratePusherState): CratePusherState {
const previous = state.history.at(-1);
if (!previous) {
return {
...state,
announcement: "Nothing to undo.",
};
}
return {
...state,
announcement: "Move undone.",
crates: new Set(previous.crates),
history: Object.freeze(state.history.slice(0, -1)),
moves: previous.moves,
phase: "playing",
player: previous.player,
pushes: previous.pushes,
};
}
export function resetCratePusher(
level: ParsedCratePusherLevel
): CratePusherState {
return createCratePusherState(level);
}
export function isCratePusherSolved(
state: Pick<CratePusherSnapshot, "crates">,
level: ParsedCratePusherLevel
): boolean {
for (const portal of level.portals) {
if (!state.crates.has(portal)) {
return false;
}
}
return true;
}
export function formatCratePusherPosition(position: PositionKey): string {
const { column, row } = parsePositionKey(position);
return `row ${row + 1} column ${column + 1}`;
}
lib/games/crate-pusher-levels.ts
export type CratePusherDifficulty = "intermediate" | "advanced" | "expert";
export interface CratePusherLevelDefinition {
readonly difficulty: CratePusherDifficulty;
readonly id: string;
readonly lines: readonly string[];
readonly name: string;
}
export interface CratePusherShuffleBag {
readonly cursor: number;
readonly lastLevelIndex: number | null;
readonly levelCount: number;
readonly order: readonly number[];
readonly randomState: number;
}
export interface CratePusherShuffleDraw {
readonly levelIndex: number;
readonly shuffleBag: CratePusherShuffleBag;
}
const UINT32_RANGE = 4_294_967_296;
function defineLevel(
definition: CratePusherLevelDefinition
): CratePusherLevelDefinition {
return Object.freeze({
...definition,
lines: Object.freeze([...definition.lines]),
});
}
export const CRATE_PUSHER_LEVELS = Object.freeze([
defineLevel({
difficulty: "intermediate",
id: "warm-circuit",
lines: [
"#######",
"# # #",
"# .#",
"# $$ #",
"# #@. #",
"# # #",
"#######",
],
name: "Warm Circuit",
}),
defineLevel({
difficulty: "intermediate",
id: "side-step",
lines: [
"#######",
"# #",
"# # $@#",
"# $ # #",
"#. . #",
"# # # #",
"#######",
],
name: "Side Step",
}),
defineLevel({
difficulty: "intermediate",
id: "relay-room",
lines: [
"########",
"# .. #",
"# # #",
"# $ #",
"#@$ ##",
"# # #",
"########",
],
name: "Relay Room",
}),
defineLevel({
difficulty: "intermediate",
id: "corner-signal",
lines: [
"########",
"# # #",
"# $ #@ #",
"# # $ #",
"# .# #",
"# #",
"# . # #",
"########",
],
name: "Corner Signal",
}),
defineLevel({
difficulty: "advanced",
id: "split-shift",
lines: [
"########",
"## ##",
"# #$ #",
"# # .#",
"##@$ # #",
"# # $ #",
"# .. #",
"########",
],
name: "Split Shift",
}),
defineLevel({
difficulty: "advanced",
id: "long-detour",
lines: [
"########",
"# #",
"# .# $ #",
"# # $ #",
"# # .#",
"# $ #.#",
"# @# #",
"########",
],
name: "Long Detour",
}),
defineLevel({
difficulty: "advanced",
id: "cross-current",
lines: [
"#########",
"# # #",
"# @$ #",
"## #",
"# .# # #",
"# .$* #",
"# #",
"#########",
],
name: "Cross Current",
}),
defineLevel({
difficulty: "advanced",
id: "crate-relay",
lines: [
"#########",
"# ## + #",
"# $$ #",
"# $ #",
"## #",
"## .#.#",
"# # # #",
"## #",
"#########",
],
name: "Crate Relay",
}),
defineLevel({
difficulty: "advanced",
id: "switchback",
lines: [
"#########",
"# . #",
"# # .#",
"# $ #",
"# #@.#",
"# $ #",
"# # $ #",
"# # #",
"#########",
],
name: "Switchback",
}),
defineLevel({
difficulty: "advanced",
id: "deep-storage",
lines: [
"#########",
"# # #",
"# # #",
"## # #",
"# .#$ * #",
"# .#@ $ #",
"# # # #",
"# # #",
"#########",
],
name: "Deep Storage",
}),
defineLevel({
difficulty: "advanced",
id: "backtrack-bay",
lines: [
"########",
"# #",
"# # $ #",
"# $@#",
"#. #",
"# $ #",
"# . . #",
"########",
],
name: "Backtrack Bay",
}),
defineLevel({
difficulty: "advanced",
id: "narrow-orbit",
lines: [
"########",
"## #",
"# #",
"#. $##",
"# # $@ #",
"#.. $ #",
"# # ##",
"########",
],
name: "Narrow Orbit",
}),
defineLevel({
difficulty: "advanced",
id: "forklift-loop",
lines: [
"#########",
"# @ #",
"# *$ ###",
"# # # #",
"# $ # #",
"# ## #",
"# . . #",
"#########",
],
name: "Forklift Loop",
}),
defineLevel({
difficulty: "advanced",
id: "wall-circuit",
lines: [
"#########",
"# . #",
"# .#",
"# ## #",
"#. @$ #",
"# $# $ #",
"# #",
"#########",
],
name: "Wall Circuit",
}),
defineLevel({
difficulty: "advanced",
id: "long-haul",
lines: [
"#########",
"# # # ##",
"# $ * #",
"# # #",
"#. # .##",
"# # #",
"# $@#",
"# # #",
"#########",
],
name: "Long Haul",
}),
defineLevel({
difficulty: "expert",
id: "fourfold-gate",
lines: [
"########",
"# .. #",
"# $.#",
"## # #",
"##$ $. #",
"#@$ #",
"# # # #",
"########",
],
name: "Fourfold Gate",
}),
defineLevel({
difficulty: "expert",
id: "packed-route",
lines: [
"########",
"# #",
"# .* ###",
"# . $@#",
"# $ #",
"# $ # #",
"### . #",
"########",
],
name: "Packed Route",
}),
defineLevel({
difficulty: "expert",
id: "signal-stack",
lines: [
"#########",
"# # #",
"# $ #",
"# $ $ #",
"#.# . #",
"# .$@#",
"# # . ##",
"#########",
],
name: "Signal Stack",
}),
defineLevel({
difficulty: "expert",
id: "cargo-spiral",
lines: [
"#########",
"# # @ #",
"# . $ $ #",
"## $## ##",
"# . #",
"# . $ #",
"# . #",
"#########",
],
name: "Cargo Spiral",
}),
defineLevel({
difficulty: "expert",
id: "final-dispatch",
lines: [
"########",
"# #",
"## $@$.#",
"### *# #",
"# # $ #",
"# .. #",
"# ## #",
"########",
],
name: "Final Dispatch",
}),
]);
export const DEFAULT_CRATE_PUSHER_LEVEL = CRATE_PUSHER_LEVELS[0].lines;
function normalizeSeed(seed: number): number {
if (!Number.isSafeInteger(seed)) {
throw new Error("Crate Pusher shuffle seeds must be safe integers.");
}
return wrapUint32(seed);
}
function validateLevelCount(levelCount: number): void {
if (!Number.isInteger(levelCount) || levelCount < 1) {
throw new Error("Crate Pusher shuffle bags require at least one level.");
}
}
function nextRandom(randomState: number): {
readonly randomState: number;
readonly value: number;
} {
const nextState = wrapUint32(
Math.imul(randomState, 1_664_525) + 1_013_904_223
);
return {
randomState: nextState,
value: nextState / UINT32_RANGE,
};
}
function wrapUint32(value: number): number {
return ((value % UINT32_RANGE) + UINT32_RANGE) % UINT32_RANGE;
}
function shuffledOrder(
levelCount: number,
randomState: number,
excludedFirstIndex: number | null
): {
readonly order: readonly number[];
readonly randomState: number;
} {
const order = Array.from({ length: levelCount }, (_, index) => index);
let nextRandomState = randomState;
for (let index = order.length - 1; index > 0; index -= 1) {
const random = nextRandom(nextRandomState);
const swapIndex = Math.floor(random.value * (index + 1));
nextRandomState = random.randomState;
const currentValue = order[index];
order[index] = order[swapIndex] as number;
order[swapIndex] = currentValue as number;
}
if (
excludedFirstIndex !== null &&
order.length > 1 &&
order[0] === excludedFirstIndex
) {
[order[0], order[1]] = [order[1] as number, order[0] as number];
}
return {
order: Object.freeze(order),
randomState: nextRandomState,
};
}
function createFrozenShuffleBag(
shuffleBag: CratePusherShuffleBag
): CratePusherShuffleBag {
return Object.freeze(shuffleBag);
}
export function createCratePusherShuffleBag(
seed: number,
levelCount: number = CRATE_PUSHER_LEVELS.length
): CratePusherShuffleBag {
validateLevelCount(levelCount);
const shuffled = shuffledOrder(levelCount, normalizeSeed(seed), null);
return createFrozenShuffleBag({
cursor: 0,
lastLevelIndex: null,
levelCount,
order: shuffled.order,
randomState: shuffled.randomState,
});
}
export function drawCratePusherShuffleBag(
shuffleBag: CratePusherShuffleBag
): CratePusherShuffleDraw {
let activeBag = shuffleBag;
if (shuffleBag.cursor >= shuffleBag.order.length) {
const shuffled = shuffledOrder(
shuffleBag.levelCount,
shuffleBag.randomState,
shuffleBag.lastLevelIndex
);
activeBag = createFrozenShuffleBag({
cursor: 0,
lastLevelIndex: shuffleBag.lastLevelIndex,
levelCount: shuffleBag.levelCount,
order: shuffled.order,
randomState: shuffled.randomState,
});
}
const levelIndex = activeBag.order[activeBag.cursor];
if (levelIndex === undefined) {
throw new Error("Crate Pusher shuffle bag could not draw a level.");
}
return Object.freeze({
levelIndex,
shuffleBag: createFrozenShuffleBag({
...activeBag,
cursor: activeBag.cursor + 1,
lastLevelIndex: levelIndex,
}),
});
}
components/ui/8bit/styles/retro.css
@import url("https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap");
.retro {
font-family:
"Press Start 2P",
system-ui,
-apple-system,
sans-serif;
line-height: 1.5;
letter-spacing: 0.5px;
}
.pixelated {
image-rendering: pixelated;
image-rendering: crisp-edges;
}components/ui/8bit/badge.tsx
import { type VariantProps, cva } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { Badge as ShadcnBadge } from "@/components/ui/badge";
export const badgeVariants = cva("", {
variants: {
font: {
normal: "",
retro: "retro",
},
variant: {
default: "border-primary bg-primary",
destructive: "border-destructive bg-destructive",
outline: "border-background bg-background",
secondary: "border-secondary bg-secondary",
},
},
defaultVariants: {
variant: "default",
},
});
export interface BitButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof badgeVariants> {
asChild?: boolean;
}
function Badge({
children,
className = "",
font,
variant,
...props
}: BitButtonProps) {
const color = badgeVariants({ variant, font });
const classes = className.split(" ");
// visual classes for badge and sidebars
const visualClasses = classes.filter(
(c) =>
c.startsWith("bg-") ||
c.startsWith("border-") ||
c.startsWith("text-") ||
c.startsWith("rounded-")
);
// Container should accept all non-visual utility classes (e.g., size, spacing, layout)
const containerClasses = classes.filter(
(c) =>
!(
c.startsWith("bg-") ||
c.startsWith("border-") ||
c.startsWith("text-") ||
c.startsWith("rounded-")
)
);
return (
<div className={cn("relative inline-flex items-stretch", containerClasses)}>
<ShadcnBadge
{...props}
className={cn(
"h-full",
"rounded-none",
"w-full",
font !== "normal" && "retro",
visualClasses
)}
variant={variant}
>
{children}
</ShadcnBadge>
{/* Left pixel bar */}
<div
className={cn(
"-left-1.5 absolute inset-y-[4px] w-1.5",
color,
visualClasses
)}
/>
{/* Right pixel bar */}
<div
className={cn(
"-right-1.5 absolute inset-y-[4px] w-1.5",
color,
visualClasses
)}
/>
</div>
);
}
export { Badge };
components/ui/8bit/button.tsx
import { type VariantProps, cva } from "class-variance-authority";
import { Slot } from "radix-ui";
import type { ButtonHTMLAttributes, Ref } from "react";
import { Button as ShadcnButton } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import "@/components/ui/8bit/styles/retro.css";
export const buttonVariants = cva("", {
variants: {
font: {
normal: "",
retro: "retro",
},
variant: {
default: "bg-foreground",
destructive: "bg-foreground",
outline: "bg-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "",
sm: "",
lg: "",
icon: "",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
});
export interface BitButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
ref?: Ref<HTMLButtonElement>;
}
interface ButtonDecorationsProps {
size: BitButtonProps["size"];
variant: BitButtonProps["variant"];
}
function ButtonDecorations({ size, variant }: ButtonDecorationsProps) {
return (
<span
aria-hidden="true"
className="pointer-events-none contents"
data-slot="button-decorations"
>
{variant !== "ghost" && variant !== "link" && size !== "icon" && (
<>
{/* Pixelated border */}
<span className="absolute -top-1.5 left-1.5 h-1.5 w-1/2 bg-foreground dark:bg-ring" />
<span className="absolute -top-1.5 right-1.5 h-1.5 w-1/2 bg-foreground dark:bg-ring" />
<span className="absolute -bottom-1.5 left-1.5 h-1.5 w-1/2 bg-foreground dark:bg-ring" />
<span className="absolute -bottom-1.5 right-1.5 h-1.5 w-1/2 bg-foreground dark:bg-ring" />
<span className="absolute top-0 left-0 size-1.5 bg-foreground dark:bg-ring" />
<span className="absolute top-0 right-0 size-1.5 bg-foreground dark:bg-ring" />
<span className="absolute bottom-0 left-0 size-1.5 bg-foreground dark:bg-ring" />
<span className="absolute right-0 bottom-0 size-1.5 bg-foreground dark:bg-ring" />
<span className="absolute top-1.5 -left-1.5 h-[calc(100%-12px)] w-1.5 bg-foreground dark:bg-ring" />
<span className="absolute top-1.5 -right-1.5 h-[calc(100%-12px)] w-1.5 bg-foreground dark:bg-ring" />
{variant !== "outline" && (
<>
{/* Top shadow */}
<span className="absolute top-0 left-0 h-1.5 w-full bg-foreground/20" />
<span className="absolute top-1.5 left-0 h-1.5 w-3 bg-foreground/20" />
{/* Bottom shadow */}
<span className="absolute bottom-0 left-0 h-1.5 w-full bg-foreground/20" />
<span className="absolute right-0 bottom-1.5 h-1.5 w-3 bg-foreground/20" />
</>
)}
</>
)}
{size === "icon" && (
<>
<span className="absolute top-0 left-0 h-[5px] w-full bg-foreground md:h-1.5 dark:bg-ring" />
<span className="absolute bottom-0 h-[5px] w-full bg-foreground md:h-1.5 dark:bg-ring" />
<span className="absolute top-1 -left-1 h-1/2 w-[5px] bg-foreground md:w-1.5 dark:bg-ring" />
<span className="absolute bottom-1 -left-1 h-1/2 w-[5px] bg-foreground md:w-1.5 dark:bg-ring" />
<span className="absolute top-1 -right-1 h-1/2 w-[5px] bg-foreground md:w-1.5 dark:bg-ring" />
<span className="absolute -right-1 bottom-1 h-1/2 w-[5px] bg-foreground md:w-1.5 dark:bg-ring" />
</>
)}
</span>
);
}
function Button({
asChild = false,
children,
className,
font,
size,
variant,
...props
}: BitButtonProps) {
const decorations = <ButtonDecorations size={size} variant={variant} />;
return (
<ShadcnButton
{...props}
className={cn(
"rounded-none active:translate-y-1 transition-transform relative inline-flex items-center justify-center gap-1.5 border-none",
size === "icon" && "mx-1 my-0",
font !== "normal" && "retro",
className
)}
size={size}
variant={variant}
asChild={asChild}
>
{asChild ? <Slot.Slottable>{children}</Slot.Slottable> : children}
{decorations}
</ShadcnButton>
);
}
export { Button };
components/ui/8bit/card.tsx
import { type VariantProps, cva } from "class-variance-authority";
import { cn } from "@/lib/utils";
import {
Card as ShadcnCard,
CardAction as ShadcnCardAction,
CardContent as ShadcnCardContent,
CardDescription as ShadcnCardDescription,
CardFooter as ShadcnCardFooter,
CardHeader as ShadcnCardHeader,
CardTitle as ShadcnCardTitle,
} from "@/components/ui/card";
import "@/components/ui/8bit/styles/retro.css";
export const cardVariants = cva("", {
variants: {
font: {
normal: "",
retro: "retro",
},
},
defaultVariants: {
font: "retro",
},
});
export interface BitCardProps
extends React.ComponentProps<"div">,
VariantProps<typeof cardVariants> {
asChild?: boolean;
}
function Card({ className, font, ...props }: BitCardProps) {
return (
<div
className={cn(
"relative bg-card text-card-foreground border-y-6 border-foreground dark:border-ring p-0!",
className
)}
>
<ShadcnCard
{...props}
className={cn(
"rounded-none border-0 w-full! h-full flex flex-col bg-card text-card-foreground shadow-none",
font !== "normal" && "retro",
className
)}
/>
<div
className={cn("absolute inset-0 border-x-6 -mx-1.5 border-inherit pointer-events-none")}
aria-hidden="true"
/>
</div>
);
}
function CardHeader({ ...props }: BitCardProps) {
const { className, font } = props;
return (
<ShadcnCardHeader
className={cn(font !== "normal" && "retro", className)}
{...props}
/>
);
}
function CardTitle({ ...props }: BitCardProps) {
const { className, font } = props;
return (
<ShadcnCardTitle
className={cn(font !== "normal" && "retro", className)}
{...props}
/>
);
}
function CardDescription({ ...props }: BitCardProps) {
const { className, font } = props;
return (
<ShadcnCardDescription
className={cn(font !== "normal" && "retro", className)}
{...props}
/>
);
}
function CardAction({ ...props }: BitCardProps) {
const { className, font } = props;
return (
<ShadcnCardAction
className={cn(font !== "normal" && "retro", className)}
{...props}
/>
);
}
function CardContent({ ...props }: BitCardProps) {
const { className, font } = props;
return (
<ShadcnCardContent
className={cn("flex-1", font !== "normal" && "retro", className)}
{...props}
/>
);
}
function CardFooter({ ...props }: BitCardProps) {
const { className, font } = props;
return (
<ShadcnCardFooter
data-slot="card-footer"
className={cn(font !== "normal" && "retro", className)}
{...props}
/>
);
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
};
components/ui/8bit/kbd.tsx
import { cn } from "@/lib/utils";
import "@/components/ui/8bit/styles/retro.css";
function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
return (
<kbd
data-slot="kbd"
className={cn(
"retro bg-muted text-muted-foreground pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium select-none",
"[&_svg:not([class*='size-'])]:size-3",
"[[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10",
className
)}
{...props}
/>
);
}
function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<kbd
data-slot="kbd-group"
className={cn("inline-flex items-center gap-1", className)}
{...props}
/>
);
}
export { Kbd, KbdGroup };