Disclaimer: this project was mostly generated by AI (GPT-5.1-Codex-Max).
Stanghetta is a turn-based Roman numerals challenge. Players take turns entering one letter at a time (I, V, X, L, C, D, M) to assemble the correct numeral for an ever-increasing number. Miss a letter or run out of time and the round ends.
Why play
- Sharpens attention: you must track the correct sequence while others play.
- Trains working memory: in hard mode messages vanish quickly, forcing recall.
- Reinforces number sense: you continuously translate numbers into Roman notation.
- Builds focus under pressure: each turn has a short timer.
- Encourages collaboration and friendly competition through chat and shared turns.
Modes
- Online multiplayer: create or join a room with a 6-character code. Turns, scores, and chat sync via Supabase. A 10 s timer is enforced when at least two humans are present.
- Offline (same device): play locally without any backend. Progress is saved per room code so you can resume later.
- Offline with bots: add up to eight total players by inviting built-in bots. They auto-play optimal letters.
Turn rules
- Create or join a room and pick a nickname. If you create, choose a difficulty.
- On your turn, tap a Roman key to extend the shared numeral for the current number (starting from 1 and climbing).
- Finish the correct numeral to earn 1 point; the target number increments and the next player starts.
- Play passes in order; if the 10 s timer expires, your turn is skipped.
- If anyone plays an invalid sequence, the game ends and the correct numeral is shown. Start again to reset scores and pick a new starting player.
Difficulty levels
- Facile (Easy): shows the correct Roman target as a hint.
- Normale (Normal): no hints; messages stay in chat.
- Difficile (Hard): no hints and chat messages fade after a few seconds, so you must remember what was played.
npm install
npm run devThen open the printed local URL. Use npm run build for production bundles or npm run preview to serve the build.
- Requires
VITE_SUPABASE_URLandVITE_SUPABASE_ANON_KEYin a.envfile to enable multiplayer (the backend is Supabase). - Supabase Realtime must be enabled on the game tables below; the anon key is used client-side (sessions are not persisted).
- Without these variables, offline and bot modes still work.
- Share the room code with friends; they can join from any device.
- Frontend: React + TypeScript + Vite SPA. Client state manages turns, timers, and offline storage. Roman numeral logic lives in the browser.
- Backend: Supabase Postgres + Realtime. The client subscribes to
rooms,room_players, androom_messagestables for live updates. All writes go directly to Supabase via the anon key. - Networking: WebSocket channels (Supabase Realtime) push player, message, and room state changes. REST/RPC reads are used for room hydration and scoreboard updates.
- Persistence: Offline mode stores snapshots in localStorage by room code; online mode stores a lightweight session (roomId, playerId, username) locally to reconnect.
Run these SQL statements in Supabase (or adapt if tables already exist). Enable Realtime on all three tables. Enforce Row Level Security and policies as needed for your project.
-- Rooms
create table if not exists public.rooms (
id uuid primary key default gen_random_uuid(),
code varchar(6) not null unique,
difficulty text not null check (difficulty in ('easy','normal','hard')),
show_hints boolean not null default false,
status text not null default 'active' check (status in ('active','finished')),
current_number integer not null default 1,
current_player_index integer not null default 0,
created_at timestamptz not null default now()
);
-- Players
create table if not exists public.room_players (
id uuid primary key default gen_random_uuid(),
room_id uuid not null references public.rooms(id) on delete cascade,
name text not null,
is_bot boolean not null default false,
points integer not null default 0,
turn_order integer not null,
is_owner boolean not null default false,
created_at timestamptz not null default now()
);
create index if not exists room_players_room_order_idx on public.room_players (room_id, turn_order);
-- Messages (chat + plays)
create table if not exists public.room_messages (
id bigserial primary key,
room_id uuid not null references public.rooms(id) on delete cascade,
player_id uuid references public.room_players(id) on delete set null,
type text not null check (type in ('system','play','error')),
text text not null,
number integer,
correct_roman text,
created_at timestamptz not null default now()
);
create index if not exists room_messages_room_created_idx on public.room_messages (room_id, created_at);