Created
November 28, 2013 16:13
-
-
Save lgtml/7694310 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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