m-chrzan.xyz
aboutsummaryrefslogtreecommitdiff
path: root/src/blog/hex-curler.html
diff options
context:
space:
mode:
Diffstat (limited to 'src/blog/hex-curler.html')
-rw-r--r--src/blog/hex-curler.html136
1 files changed, 136 insertions, 0 deletions
diff --git a/src/blog/hex-curler.html b/src/blog/hex-curler.html
new file mode 100644
index 0000000..8f5f1c6
--- /dev/null
+++ b/src/blog/hex-curler.html
@@ -0,0 +1,136 @@
+title: "Hex Curler: A Minimalist Webgame"
+date: August 29, 2019
+---
+<p>
+I just published Hex Curler, a tiny dungeon crawler, based on Jeff Moore's
+<a href='http://www.1km1kt.net/rpg/hex'>Hex</a>.
+</p>
+
+<p>
+You can play it by running the following in a bash shell:
+
+<pre>
+c=x; while [ $c ]; do clear; curl -c k -b k hex.m-chrzan.xyz/$c; read c; done
+</pre>
+</p>
+
+<p>
+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 <code>curl</code>, a CLI tool already
+available on most Unix-like systems.
+</p>
+
+<p>
+The source code is available <a href='https://gitlab.com/m-chrzan/hex-curler'>on
+my GitLab</a>.
+</p>
+
+<h3>Let's get curling</h3>
+<p>
+The whole concept arose from two ideas I had:
+
+<ul>
+ <li>
+ <code>curl</code> and a simple web server could be used to create a
+ simple remote CLI program.
+ </li>
+ <li>
+ For a webapp that implements a simple state-transition system (like a
+ simple game), one could forget about session management and a database,
+ and just store the state client-side in a cookie.
+ </li>
+</ul>
+
+I abstracted these ideas away into a Ruby class called <code>Engine</code> and a
+skeleton Sinatra app.
+</p>
+
+<p>
+To create a new "curling" system, you extend <code>Engine</code> and implement
+four methods:
+
+<ul>
+ <li>
+ <code>step</code>: performs a single step of the state-transition
+ function.
+ </li>
+ <li>
+ <code>message</code>: outputs a message related to the most recent
+ <code>step</code>.
+ </li>
+ <li>
+ <code>hash_to_state</code> and <code>state_to_hash</code>: these are
+ just overhead glue methods. They should deserialize and serialize
+ between your engine's internal state and a Ruby <code>Hash</code>.
+ </li>
+</ul>
+
+You also need to define a <code>secret</code> which is a string that is used to
+validate that a submitted cookie represents a valid state. More on this later.
+</p>
+
+<p>
+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:
+
+<pre>
+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
+</pre>
+</p>
+
+<h3><em>O</em>(1) space webapp</h3>
+<p>
+Hex Curler is hosted online but has no session management, no database. It's an
+<em>O</em>(1) space webapp.
+</p>
+
+<p>
+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.
+</p>
+
+<p>
+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 <code>checksum</code> 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.
+</p>
+
+<p>
+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.
+</p>
+
+<p>
+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.
+</p>