title: "Hex Curler: A Minimalist Webgame" date: August 29, 2019 ---
I just published Hex Curler, a tiny dungeon crawler, based on Jeff Moore's Hex.
You can play it by running the following in a bash shell:
c=x; while [ $c ]; do clear; curl -c k -b k hex.m-chrzan.xyz/$c; read c; done
This was an exercise in minimalism. The game server is implemented in less than
a thousand lines of Ruby code. It is completely stateless, requiring no
database. The front end "client" is a single line of bash, less than 80
characters long. The only dependency is curl
, a CLI tool already
available on most Unix-like systems.
The source code is available on my GitLab.
The whole concept arose from two ideas I had:
curl
and a simple web server could be used to create a
simple remote CLI program.
Engine
and a
skeleton Sinatra app.
To create a new "curling" system, you extend Engine
and implement
four methods:
step
: performs a single step of the state-transition
function.
message
: outputs a message related to the most recent
step
.
hash_to_state
and state_to_hash
: these are
just overhead glue methods. They should deserialize and serialize
between your engine's internal state and a Ruby Hash
.
secret
which is a string that is used to
validate that a submitted cookie represents a valid state. More on this later.
The Sinatra skeleton instantiates an engine with the received cookie, runs a step, sends back the new state in the returned cookie, and responds with the engine's message. The code for it fits in half of a browser window:
require 'sinatra' require 'sinatra/cookies' # exposes `Hex`, which extends `Engine`, implementing a simple dungeon crawler require './hex_engine' def secret # get secret from environment ENV['HEX_SECRET'] end get '/:command' do |command| # `new` uses `hash_to_state` to initialize the engine's state engine = Hex.new cookies.to_h engine.step command # `state_h` uses `state_to_hash` to serialize the engine's new state engine.state_h.each_pair do |key, value| cookies[key] = value end engine.message end
Hex Curler is hosted online but has no session management, no database. It's an O(1) space webapp.
As mentioned before, the game's state is stored in a cookie. The server need only know the contents of that cookie to return a new state back to the user.
To prevent a user from tampering with the cookie, for example by increasing
their health to a ridiculous number, becoming invulnerable to enemies in Hex,
the cookie also contains a checksum
field. This checksum is the
hash of the state together with an appended secret only known by the game
server. The server will refuse to respond to requests whose cookie does not have
a valid checksum.
This introduces some interesting possibilities. For example, let's say Alice wants to boast to her friends about how she just beat Hex, ended with 100 HP remaining, and had upgraded her magic armor to level 5. Her friend Bob doesn't just have to take her word for it, or trust a screenshot that could have easily been photoshopped.
Alice can send Bob her state cookie. If a request to the game server with it succeeds, Bob can be assured that he has cryptographic proof of Alice's claims.