Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

46 Commits
 
 
 
 

Repository files navigation

Summary

Warning

This write-up is an active work in progress. Some benchmarks are changing as I continue to optimise the engine and game. The write-up will be updated as I make progress. This is the public write-up for a private repository, so some of the content is intentionally vague or incomplete. If you have questions, please reach out to me directly.

Link to the Game

Note

Snibble started its life as a multiplyer word game combining Snake, Scrabble, and adversarial stealing mechanics.

Over time, it has evolved into a game-engine and simulator.

Collect a Letter Form a Word
Collect a Letter Form a Word
Submit a Word Steal a Word
Submit a Word Perform a Steal

The engine features:

  • Headless simulation
  • Discrete movement and actions, yet continuous fluid rendering and animation
  • Exact game replays with a scrubbable timeline (like a video player)
  • Tiny QR-encodable replays
  • Procuedral level generation (walls) and sound generation
  • Configurable bot "genes" for bot behaviour, with imperfect information and perception levels
  • A custom binary wire protocol for multiplayer games and replay compression
  • An infinite toroidal board with zones and flow fields
  • Fully separated logic and rendering

Architecture

Procedural Wall Generation

Walls are generated based on the seed and a custom shape grammar of 8x8 shape primitives. The shape primitives can be scaled up to different powers of 2, allowing for a wide variety of wall configurations.

Procedural Wall Generation

Wall Shape Primitives

Each shape primitive is an 8x8 "grid" of wall segments and "nodes". How it works is 2x8-byte masks that get combined

  • Mask 1: Define a 8x8 shape primtive of nodes (think of them as line end-caps)
  • Mask 2:Define a second 8x8 shape primitive of walls (think of them as line segments)
  • Each bit in the mask is a boolean value, where 1 = wall/node and 0 = empty space. The two masks are combined to form a single 8x8 shape primitive.
Wall Primitive Diamond Example Zoomed out board with Nodes coloured in
Wall Primitive Sample Zoomed out with Nodes

The node-wall stroke algorithm is a simple custom algorithm that draws a line between two nodes via line of sight, and then fills in the line with wall segments. The algorithm is designed to be fast and efficient, and it can handle a wide variety of wall configurations. In the above images, the nodes are pink, the walls are black, and the empty space is white. The walls are generated procedurally based on the seed, and they can be scaled up to different powers of 2. Walls can be connected via a solid or dotted "stroke" which is seed derived.

Bot "Gene" System

Bots are driven by "genes", compact byte-encoded behavioural parameters that is used to make decisions. Each bot makes decisions by scoring candidate options and taking the highest-utility one. In previous gifs you can see bots hunting (!), thinking (...), or moving - if you look closely.

Gene Variants

A gene belongs to one of seven categories, each of which can be tuned to a value between 0 and 255:

  • wordAmbition — commit to short/fast words vs hold out for long, high-value ones
  • wordDiscipline — strict valid-word play vs eats anything (garbage words, board chaos)
  • wordSense — sticks to simple/common words (BOAT, CAT) vs form rare, obscure words (TSADES, QI)
  • aggression — passive collector vs hunts other players tails
  • perception — stops frequently to "think" and forgets board information vs stops infrequently and retains board information (my favourite gene - see note below)
  • caution — reckless vs danger-averse
  • impulsiveness — hoards letters and barely ejects them vs ejects them freely to escape or hunt or form a different word

Perception Gene, or, Imperfect Information

Note

Bots are not oracles, they don't see the whole board. The board is segmented into zones, and a bot can only see its current zone and its 8 adjacent neighbouring zones.

Just like how humans have imperfect information and need to stop to "scan" the board, bots also have imperfect information and need to stop to "scan" the board. The perception gene controls how often a bot stops to scan, and how long it remembers what it saw.

Imperfect Humans Imperfect Bots

Zones have bitmasks over them which "hide" information from bots. So as bots play the game, they have imperfect information - just like a human. And just like a human, if they pause movement to stop and "scan" the board, the information is revelaed to them. But also just like a human, they forget what they saw after a while. The perception gene controls how often a bot stops to scan, and how long it remembers what it saw.

Why did I implement this? Becuase the game carries heavy processing overhead for humans, I first decided to implement non-continuous movement and actions so players can pause and think - unlike most snake games. Then I realised through playtesting that bots had perfect information so didn't have any need to pause, which caused an asymmetry in the game. I also did not want to introduce random pauses for bot movement, I wanted a more elegant solution akin to what the human player experiences. So partial information masking was the obvious solution given that the game was already segmented into power-of-2 zones.

QR Code Replays

Replay with camera subject switching Scan this QR code to watch the EXACT replay from the gif
Scan this to open the exact replay this gif was captured from

Every single played game can be replayed, and the replays are so tiny that they fit in a QR code. There is no server, no database, no url-shortener. This is the entire game state required to replay the exact game. The replay can be watched and scrubbed like a video, and the camera can be dynamically changed to follow any player. You can even play against your own "ghost" from a previous game.

And just for fun, I made the QR code look like an actual live game of Snibble with letter tiles and snakes.

Compression Story and Custom Event Protocol

Player actions are a 1 byte event - the only actions a player can take are to stay still, or move in a cardinal direction (up, down, left, right) or to eject a letter. Behind the scenes actions are discrete step-based events tied to the grid structure positionally, even though visually the game appears to be continuous and movement is fluid between grid cells.

What is a player event-byte made of exactly?

  • 2 bits encode 4 possible directions (up, down, left, right)
  • 1 bit encodes whether the player is ejecting a letter or not
  • 1 bit encodes whether the player is moving or not
  • And the remaining 4 bits encode the "tick-delta" since the last event *

* The major compression insight here was that the engine timestep / resolution is not 1-tick, it is actually 16-ticks. So each state of the tick-delta represents 16-ticks

Performance and Stress Testing

Note

All benchmarks were run on a 2021 Macbook Pro 16GB RAM.

The game was run in a chrome browser with measurements using the chrome performance profiler.

2048 enemies in play (constant 60 FPS) All space occupied by letters (constant 60fps)

Challenges

Game Loop

Discrete Movement and Actions, Deterministic Simulation, and Continuous Fluid Rendering and Animation

Visual Density and Hierarchy

Word Forming Ability and Hand-Holding

No hand-holding (harder) Hand-holding (easier)

Still to Discuss:

  • Engineering Highlights 3x3 gif grid of "insane" highlights of the engine and game.
  • Biggest struggles (visual density and hierarchy, game loop, compression (esp multiplayer))
  • Written canvas
  • Camera system
  • Pseudocode
  • Seed system
  • Rift mechanic
  • Hero stats section
  • Extreme compression and example replays and exact encoding structure
  • Extreme performance and flame graphs
  • Custom wire protocol
  • Procedural sound generation
  • Discrete movement and actions
  • Continuous fluid rendering and animation
  • Fully separated logic and rendering
  • Ability to run the game headless
  • Infinite toroidal board
  • Game replays
    • Spectate your own game
    • Spectate an enemies game
    • Change the camera subject dynamically
    • Play against your “ghost”
    • QR code that encodes the entire game replay
  • Everything as a power of 2
    • Board dimensions
    • Inner board zones
    • Bot count
    • Bot gene parameters
    • Sound generation
  • Word Dictionary
    • DAWG
    • Prefix / suffix complexity analysis
  • Global state versus individual state
    • Flow fields
    • Zones