New templateType "snake" alongside quiz/clicker/maze — additive, not a redesign, so it's wired through every existing per-template enumeration point (schema, types unions, scene factory, builder dispatch on create/edit/AI-review pages, template icon maps) following the pattern each already established. games.templateType is a plain SQLite text column with a TypeScript- only enum (no SQL DDL behind it) — confirmed via drizzle-kit generate that adding "snake" produces zero migration, same as every in-place template redesign this session. Bounded arena (no maze-style wall authoring — keeps the builder trivial: width/height/startLength/targetLength/wrapAround, nothing else). Movement is a self-rescheduling delayedCall loop rather than a fixed interval, since the step interval itself shrinks as the snake grows; speed curve sanity-checked standalone before wiring it in. Discrete per-cell snapping (not tweened, unlike the maze's glide) — that's the authentic blocky Snake feel, not a shortcut. Standard collision rule: the tail cell is excluded from the self-collision check unless this step eats food, since the tail is vacating that cell. Progress resumes the exact live state (body/direction/food/score) — Snake has no natural level-boundary checkpoint the way the other three templates do, so unlike their coarse-grained resume this restores precisely. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
31 lines
1.5 KiB
TypeScript
31 lines
1.5 KiB
TypeScript
import { redirect, notFound } from "next/navigation";
|
|
import { getCurrentSession } from "@/lib/auth/session";
|
|
import { TEMPLATE_TYPES, TEMPLATE_LABELS, type TemplateType } from "@/lib/games/types";
|
|
import { QuizBuilder } from "@/components/builders/quiz-builder";
|
|
import { ClickerBuilder } from "@/components/builders/clicker-builder";
|
|
import { MazeBuilder } from "@/components/builders/maze-builder";
|
|
import { SnakeBuilder } from "@/components/builders/snake-builder";
|
|
|
|
export default async function ManualCreatePage({ params }: { params: Promise<{ template: string }> }) {
|
|
const { template } = await params;
|
|
if (!TEMPLATE_TYPES.includes(template as TemplateType)) notFound();
|
|
const templateType = template as TemplateType;
|
|
|
|
const session = await getCurrentSession();
|
|
if (!session) redirect("/login");
|
|
|
|
return (
|
|
<div className="mx-auto w-full max-w-2xl flex-1 px-4 py-8 sm:px-6">
|
|
<h1 className="mb-1 font-display text-2xl font-bold tracking-tight">
|
|
Новая игра — {TEMPLATE_LABELS[templateType]}
|
|
</h1>
|
|
<p className="mb-6 text-sm text-text-muted">Сохранится как черновик — опубликуете, когда будете готовы.</p>
|
|
|
|
{templateType === "quiz" && <QuizBuilder mode="create" />}
|
|
{templateType === "clicker" && <ClickerBuilder mode="create" />}
|
|
{templateType === "maze" && <MazeBuilder mode="create" />}
|
|
{templateType === "snake" && <SnakeBuilder mode="create" />}
|
|
</div>
|
|
);
|
|
}
|