Skip to content

Instantly share code, notes, and snippets.

@lgtml
Created November 28, 2013 16:13
Show Gist options
  • Save lgtml/7694310 to your computer and use it in GitHub Desktop.
Save lgtml/7694310 to your computer and use it in GitHub Desktop.
Goal: multiplayer [hangman](https://en.wikipedia.org/wiki/Hangman_%28game%29)
Picking an incorrect letter advances the hangman state. The hangman has 6 body state: head, body, left leg, right leg, left arm, right arm. Once all body parts are drawn, the game is over.
For GoInstant, this means that clients will be submitting letter choices. Once the game is finished, the room cannot be updated (enforced by the trigger).
# Phase 0: client
listens to a bunch of keys:
- `/body` integer from 0 to 6. 6 is YOU LOSE.
- `/win` when == 1, YOU WIN.
- `/try/*` - a through z. Clients set these to "1" or whatever truthy value.
- `/guesses/*` - a through z, each value is a sequence 1..N where `-N` is an incorrect guess, `+N` is a correct guess.
- `/hint` - The current "hint", e.g. `____` (four underscores) for the hidden word "doge", `___ ____` for "the doge", etc. When correct guesses come in, the trigger will update this to `__e ___e`.
- `/answer` - the actual an
ACL:
- clients can't read `/answer`
- clients can't write to anything _except_ `/try/*`
- clients can read everything else (state, win, try, guesses, and hint)
- trigger will bypass the ACL.
# Phase 1: PoC trigger
no contextify, just write a hard-coded trigger that reacts to the room data
being set, implementing the game logic. We need to make sure that the game
works and that we can "broadcast" change events made by the storage server
itself.
- the "context" of the event should have some kind of special ID for the trigger (i.e. not a user ID).
- use async.queue to line up events destined for a trigger
- when a guess comes in, assign it a number (redis INCR or just a counter)
- do the rest of the game logic
# Phase 2: Likely API
Without introducing contextify just yet, refine the API that the trigger uses some global `goinstant` object. Here's a sketch of what I think a trigger looks like:
```js
var goinstant = require('./the/api');
var async = require('async');
exports.trigger = function (event, cb) {
// 1. Validate
var guess = event.key.basename();
if (!guess.match(/^[a-z]$/)) {
return cb('invalid guess');
}
var n = event.sequence;
// 2. Isolate
go.getState({
answer: '/answer', // string
'try': '/try', // object
guesses: '/guesses', // object
body: '/body', // int
hint: '/hint' // string
}, function(err, state) {
var output = {};
// 3. Compute
if (state.answer.indexOf(guess) >= 0) {
// YAY
var updatedHint = compute(answer,hint);
output['/hint'] = updatedHint;
output['/try/'+guess] = '+'+n;
} else {
// WRONG
output['/body'] = state.body + 1;
output['/try/'+guess] = '-'+n;
}
output['/guesses/'+guess] = 1;
// 4. Release
go.setState(output);
cb(null);
});
}
```
The 1-4 points above are something I recently discussed with my PhD friend
who's thesis is on data scheduling in concurrent systems. This is a common flow
in RDBMSes like Postgres too. Turns out you can optimize the crap out of
programs that work like this.
Expose `async` to the trigger, because callback-hell. Just use `require()` for
now since when we use contextify we can just eval async.js into the Context.
- design an API that allows the trigger to run by just querying the key store
- When the trigger calls `cb(null, value)`, this is the value that actually gets set.
The triggers are stateless, but are associated with a room (in order to resolve the keys properly)
# Phase 3: contextify!
Run the trigger in the context, have a global `go` object that bridges to redis commands. Nuke the Context after every invocation.
# Phase 4: stored triggers!
- `GET /v1/app/:appId/triggers`
- `PUT /v1/app/:appId/triggers/:event/:key...`
- `GET /v1/app/:appId/triggers/:event/:key...`
- `DELETE /v1/app/:appId/triggers/:event/:key...`
Those store somewhere permanent (storage redis for now, just to KISS, maybe Pg later)
The source code for these triggers gets loaded into the storage server whenever they're changed (just to make hacking easy).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment