713 字
4 分钟
POPUCOM Match-3 Chess: A Multiplayer Web Game
Try it online (Vercel) Repository (GitHub)
NOTEA remake of yj’s mini-game, now a lightweight board game for the browser. Simple rules. Plenty of decision space. Supports room battles and local practice.
Gameplay Overview
- Board: 9×9.
- Placing pieces: you may only place on neutral tiles or your own territory. You cannot place on the opponent’s territory.
- Match-3: horizontally, vertically, and on both diagonals. Whenever 3 consecutive pieces appear, those three pieces are cleared and the tiles become empty.
- Territory: the line of the match-3 belongs to the current player. Extending outward from both ends of that line, the territory stops when it hits an opponent’s piece or when the next tile holds an opponent’s piece. Territory can be overwritten.
- Turns and victory: the game settles after a total of 50 moves. Compare the number of territory tiles each side holds. The side with more wins; a tie is a draw.
Screenshots and recordings are in the repository README.
Flavors
- Serverless (recommended): front-end + API fully hosted on Vercel. Data lives in Supabase. Supports rate-limiting middleware.
- Python (Legacy): Flask REST service + Pygame client. Can be run locally or on a self-hosted server.
Architecture and Core Files
- Front-end and rule engine
- Pages and styles: index.html, styles.css.
- Interaction and local rule engine: main.js.
- Key class: BaseGame. The core algorithm lives in BaseGame.calculateNextState. It handles match-3 elimination and territory expansion, and advances turns and win/loss.
- Modes:
- LocalGame: local single-player.
- OnlineGame: syncs room state based on polling.
- Serverless API (Vercel)
- Entry point: api/game.js, exports a handler.
- Routes:
- GET /api/game?roomId=… query room state.
- POST /api/game create or ensure a room exists.
- PUT /api/game write the game state (board, territory, current player, winner, last move).
- Data and operations (Supabase / PostgreSQL)
- Table initialization and Realtime publication: database-init.sql.
- Scheduled cleanup of rooms inactive for 48 hours (pg_cron): cron-cleandata.sql.
- Rate limiting (Edge Middleware)
- File: middleware.js.
- Dependencies: @upstash/redis, @upstash/ratelimit, @vercel/edge.
Rule Implementation Highlights
- Match-3 detection: starting from each placed piece, check four directions at once to see if 3 consecutive pieces of the same color form.
- Elimination and reset: tiles that hit a match-3 are reset to zero. Multiple lines can be hit concurrently.
- Territory expansion: after recording which line a match-3 belongs to, scan outward from both ends of the line. Stop when hitting an opponent’s piece or when the “next tile is an opponent’s piece”. Overwritten territory uses the latest ownership.
- Settlement: when the total number of moves reaches , count the territory tiles on each side. More wins; a tie is a draw.
Corresponding implementations:
- JS: BaseGame.calculateNextState (place piece -> scan -> clear -> territory expansion -> advance turn -> win/loss).
- Python: process_eliminations, remove_elimination_tiles, claim_line, all driven by GameEngine.
Serverless Quick Start
- Set up Supabase
- Create a new project. Run database-init.sql to initialize tables and policies.
- Optional: run cron-cleandata.sql to enable pg_cron for cleaning up expired rooms.
- Configure Vercel environment variables
- SUPABASE_URL: the project REST URL.
- SUPABASE_KEY: using a Legacy API Key is recommended.
- Optional rate limiting: configure Upstash Redis (URL, TOKEN).
- Deploy
- Import the repository into Vercel. After it’s done, visit the root path to play.
Data Tables (Supabase / PostgreSQL)
- Table games (JSON-persisted game state + metadata):
- room_id (PK)
- board (int[][])
- territory (int[][])
- current_player (int)
- winner (int)
- last_move_pos (int[2])
- updated_at (timestamptz)
Example (excerpt):
create table if not exists public.games ( room_id text primary key, board jsonb not null, territory jsonb not null, current_player int not null default 1, winner int not null default 0, last_move_pos jsonb, updated_at timestamptz not null default now());
create index if not exists idx_games_updated_at on public.games(updated_at desc);Python Version (Legacy)
- Server (Flask)
- GameEngine: maintains the board, turns, and win/loss.
- process_eliminations / remove_elimination_tiles / claim_line: the three-stage rule implementation.
- MatchState: match scheduling and concurrency safety.
- server.py: REST routes for joining, querying, placing pieces, and resetting.
- Client (Pygame)
- LocalGame: local matches.
- RemoteGame: polls the Flask service.
- client.py: rendering and the event loop.
Startup steps:
# enter the python subdirectory (if any)pip install -r python/requirements.txtpython python/server.pypython python/client.py # choose local or online modeRate Limiting and Stability
- middleware.js implements throttling based on Upstash Redis.
- Rate limits by IP and path dimensions. Runs at the edge with low overhead.
- Can be combined with Supabase Realtime to reduce polling pressure.
Extension Directions
- Real-time sync: migrate from polling to Supabase Realtime or WebSocket / Edge Functions.
- Spectating and replays: provide a read-only view for non-players. Persist the historical move list and allow playback.
- Anti-cheat: combine IP/device fingerprint/throttling strategies with server-side validation.
- UX improvements: room sharing cards. One-click screenshots (html2canvas already integrated in main.js). Mobile touch adaptation (styles.css already adapts to basic sizes).
License and Acknowledgments
- See the repository LICENSE.
- Thanks to Vercel, Supabase, and Upstash for the cloud infrastructure.
- Thanks to the community for suggestions on rules and usability.
TIPThe service is ready; you can play directly. Issues and PRs are welcome.
POPUCOM Match-3 Chess: A Multiplayer Web Game
https://tski.uk/blog/en/popucom-chess/