Korivash

Work / Browser arcade game

Kitty’s Beachball Fantasy

A 2D pixel-art beach brawler that runs in the browser, written in Python.

Open kittysbeachballfantasy.comHow it works

Role
Game design, development and hosting
Status
Live. Free to play in the browser.
Platform
Web, keyboard and mouse
Stack
Python, pygame, pygbag (WebAssembly), Flask, MariaDB, IIS
Kitty’s Beachball Fantasy gameplay: Kitty balances on a beachball on the sand while more beachballs roll in, with a x6 combo on screen.

The problem

I made this game for my wife, Kitty. It had to open from a link, with nothing to download or install.

The game is written in Python, which does not normally run in a browser, so the build compiles it to WebAssembly and the same code runs in a tab.

What I built

  • An arcade brawler. Pop and squish waves of beachballs before they overrun you: spiked, shielded, charging and elite ones, then a boss.
  • Progression. Combos, critical hits and XP. Each level-up offers three upgrades to choose from, and shells buy random boosts from the beach crate.
  • Three cats to play. Plus achievements and daily challenges.
  • Accounts and saves. Progress is saved to an account, and a shared leaderboard tracks the best scores. Guests save in the browser.
  • Play together. A solo queue and a party queue with invites.

How it works

The Python game never talks to the network itself. The web page around it does, and the two pass state back and forth through a handful of JavaScript functions.

  1. Page

    The web pageHandles sign-in, the queue, the lobby and music, and makes every network call
  2. Game

    Python in WebAssemblypygame at 1280 by 720 with an async main loop. It hands input and scores to the page and takes back saves and multiplayer state
  3. API

    Flask on WaitressAccounts, saves and the leaderboard. Session tokens are random and stored only as hashes
  4. Match server

    A separate Python processRuns each multiplayer match at 60 ticks a second. Browsers send inputs and receive snapshots over a WebSocket
  5. Data

    MariaDB13 tables for accounts, sessions, parties, saves and scores. It stores lasting data only, never the live simulation

Hard problems and the decisions made

The match server froze on the first player

Problem As soon as one player joined over WebSocket, the server stopped. One function took a lock and then called another that asked for the same lock.

Decision Make the lock re-entrant.

Result A one-line fix, found by reading where the thread was stuck.

Multiplayer that jittered

Problem Enemies jumped around and movement stuttered. Several causes were stacked on top of each other.

Decision Apply a snapshot only when its version is new. Match each enemy to its previous position by kind and distance, not by list index. Predict positions between snapshots. Remove a fixed sleep that capped the snapshot rate.

Result Enemies stop jumping when one of them dies, and motion is smooth between snapshots.

Scores that could be made up

Problem A browser game can send any score it likes.

Decision The leaderboard takes score, wave and kills from the server’s own game state and rejects a submitted score that is higher. Sign-in and score submission are rate limited.

pygame in a browser tab

Problem Music played through pygame was unreliable in the browser, the hidden game canvas stole keyboard input from the sign-in fields, and uncompressed sound made the download heavy.

Decision Play music with the page’s own audio, fix the focus handling, and ship compressed audio only.

Result The sound files went from 94 MB to about 7 MB.

The build that shipped the wrong file

Problem Gameplay changes were made in one source file while the web build packaged another, so the live game had none of them.

Decision Change the file the build actually packages, and check the contents of the live archive after every deploy.

The stack, and what each part is for

Python and pygame
The game itself, with one async main loop.
pygbag
Compiles the game to WebAssembly so the same code runs in a browser tab.
Flask on Waitress
The API for sign-in, saves and the leaderboard. Passwords are hashed with PBKDF2 and a per-user salt.
WebSocket match server
Server-run game logic. WebSocket was chosen for browser support.
MariaDB
Lasting data only. The design notes rule it out as a bus for the real-time simulation.
IIS and PM2
IIS serves the game and PM2 keeps the backend running. Restarts are limited to this one site so the others on the server stay up.

Screens

Kitty’s Beachball Fantasy key art: pixel-art Kitty holding a beachball, surrounded by hearts, stars and beachballs.
Key art.

Timeline and what is next

  1. Mar 11, 2026First commit: the game, sign-in and a leaderboard
  2. Mar 13, 2026Hosted, with saved progress
  3. Mar 17, 2026Dedicated match server
  4. Apr 6, 2026Multiplayer finished, more content and a new title screen
  5. Oct 16, 2026Security pass: server-checked scores and rate limits
  6. Oct 19, 2026Discord sign-in on kittysbeachballfantasy.com

Have something to build?

Tell me what it needs to do and where it has to run. I answer email myself.