Carl Worth [Mon, 1 Jun 2020 14:34:33 +0000 (07:34 -0700)]
tictactoe: Move all state-related properties into a new "state" property
Consolidating this into a single property is a step toward
standardizing game classes to treat state consistently, (which should
let us reduce some code duplication).
Carl Worth [Mon, 1 Jun 2020 14:30:06 +0000 (07:30 -0700)]
tictactoe: Actually toggle between X and O as the current player
It is kind of hilarious that I hadn't even noticed that up to now
every move committed to the board was an "X". I hadn't noticed this
because the server state is currently only used to determine if a new
move is legal, (in that the board is unoccupied). The code currently
doesn't test for a win, for example.
And meanwhile, the client has been tracking toggling X and O on its
own just fine.
But this will certainly be cleaner going forward, and is strictly
necessary before we serve an event that exposes the entire board
state.
Carl Worth [Mon, 1 Jun 2020 01:04:38 +0000 (18:04 -0700)]
Drop the meta property from the game exports object.
The game's Game class is already a property on the exports object, and
that Game class already has this meta data. So there's no need to copy
it to the exports object as well.
Not to mention, no code is actually referencing it from here.
Carl Worth [Mon, 1 Jun 2020 01:01:00 +0000 (18:01 -0700)]
Return the actual game object from create_game, not just the ID
It was confusing for "create_game" to not return the obvious thing,
(the game that it just created). And now that a game has an "id"
property, it's easy enough to use that when needed.
Carl Worth [Mon, 1 Jun 2020 00:53:50 +0000 (17:53 -0700)]
Drop a gratuitous anonymous object for storing an array of games
Recent commits have trimmed down this object until it has a single
property, so we can drop the object and store the value of that
property directly.
This makes a bunch of code much easier to read by replacing all
occurences of "game.game" with just "game". The old code was really
confusing because it was so unclear what the top-level game container
even was.
Carl Worth [Mon, 1 Jun 2020 00:10:46 +0000 (17:10 -0700)]
Eliminate code duplication for root path
The desired behavior for both existing games is identical for the root
path: If there's no nickname set in the session, we want to query for
one, otherwise we server the game-specific template.
And in the last few commits, we actually made the implementation
identical for this function in both games.
So in this commit we replace the two copied functions with a single
function at the top-level. Hurrah for deletion of duplicated code!
This means less boilerplate is required for all future games.
Carl Worth [Sun, 31 May 2020 23:40:22 +0000 (16:40 -0700)]
Add first use of Game.meta to both the Empires and TicTacToe classes
So far, we just set a name field, then we access this name field from
a game instance in each of the implementations of the route handler
for the root path. In each case this is to provide the name of the
game as context for the rendering of the choose-nickname template.
The reason for all of this is to enable convergence of the code
handling that route, (since as soon as the code is precisely common
between both games the code can move up into the base class).
It's close here, but will also need to be able to select the proper
template in the case where a nickname is already chosen.
Carl Worth [Sun, 31 May 2020 23:34:44 +0000 (16:34 -0700)]
game: Add support for a static "meta" field to hold game metadata
We use a static getter/setter function for this since JavaScript
doesn't yet have wide support for static fields.
Also, we duplicate the data on both a field within the class itself
(Game._meta) as well as in the prototype (Game.prototype). The purpose
of this is so that we can access this meta-data as either a class
static (Game.meta) or via an instance (some_game.meta).
Carl Worth [Sun, 31 May 2020 23:12:10 +0000 (16:12 -0700)]
test: Add simple testing for tictactoe as well
This just verifies we can move to a square, (and can't then move to
the same square again).
To do something more interesting here, I'll have to think about how to
test with multiple clients connecting to the same game and how to
verify the results of the streaming from the /events API.
Carl Worth [Sun, 31 May 2020 23:00:39 +0000 (16:00 -0700)]
test: Update the test suite to work with game creation
Previously the test script only worked when given an API endpoint for
a single game of Empires.
In this commit we bring it up to something more modern by making it
aware of the top-level APIs for creating a new game ID and then using
that game ID to talk to the Empires API.
We also revamp the test script so that it does some actual
testing. Previously, it was just a simple script for exercising
several endpoints. Now, it actually inspects responses and ensures
that things are working correctly.
Carl Worth [Sat, 30 May 2020 03:39:56 +0000 (20:39 -0700)]
Add common handle_events code to the Game class
Again, putting code into the parent class to reduce duplication that
is in the Empires and TicTacToe classes. In Empires, there's a little
extra work to be done when a client connects, so we shadow the parent
function, call super.handle_events, and then do the rest that's
needed.
Carl Worth [Sat, 30 May 2020 00:16:33 +0000 (17:16 -0700)]
Add new game.js with a new parent class Game
This new parent is now extended by our two game classes: Empires and
TicTacToe. There's not yet any real functionality in the Game class
yet, (just storage of the name of the engine). But this will give us a
place to lodge some common functionality as we aim to reduce code
duplication.
Carl Worth [Wed, 27 May 2020 17:06:23 +0000 (10:06 -0700)]
Use an express Router for each of the game-engine-specific sub-apps
This eliminates the redundant code that was otherwise required by
using an entire app at the level of each game engine, (setting up
cors, body-parser, nunjucks configuration, etc.).
So this is much more pleasant, with less boilerplate and less code
duplication.
Carl Worth [Wed, 27 May 2020 04:01:13 +0000 (21:01 -0700)]
Add a minimal implementation of the TicTacToe game engine
This implements a /move endpoint to allow clients to send moves to the
server and an /events endpoint for the server to broadcast the moves
to all connected clients.
This TicTacToe class has some duplicated code from the empires Game
class. We should refactor this to have a common parent class to reduce
duplication.
Carl Worth [Wed, 27 May 2020 03:57:27 +0000 (20:57 -0700)]
lmno: Generalize the support for multiple game engines
Rather than having repeated lists of the engines, we have one list at
the top of the file and then use that list to iterate over all engines
to mount their sub-apps at a path that is named by engine.name.
We also fix the construction of the Game class to use an imported
constructor (so that each game engine has objects of its own class
instead of only having games constructed via empires.Game()
exclusively).
Carl Worth [Wed, 27 May 2020 03:43:58 +0000 (20:43 -0700)]
Ensure path ending with game ID always has a trailing slash
The trailing slash is essential here so that when JavaScript code
executing in the user agent accesses a relative resource, it is a
child of the game ID rather than a peer.
That is, from https://lmno.games/empires/WLWV/ a reference to the
"players" resource is https://lmno.games/empires/WLVW/players
(But if we started with https://lmno.games/empires/WLWV we would
end up referencing https://lmno.games/empires/players which will
not work.)
Here, we were already doing a redirect in the case of needing to
canonize the game ID, so we simply need to also do that redirect even
if the game ID was already canonical but the trailing slash was
missing.
Carl Worth [Wed, 27 May 2020 03:39:20 +0000 (20:39 -0700)]
generate_id: Use Array.fill(null) to initialize an array of null values
When I first wrote this code I reached for a construct of:
Array(4).map(...)
but I found that map doesn't work on an array of empty items like
this. To get things to work I instead used:
[null, null, null, null].map(...)
which did the trick, but only because I happened to be using a
sufficiently small size that it was reasonable to type the complete
literal. For this commit I've found a cleaner approach of:
Carl Worth [Tue, 26 May 2020 03:36:09 +0000 (20:36 -0700)]
Add the barest template of an implementation of a tictactoe game
This supports only the ability to select a nickname (which is provided
by lmno.js), and then the serving of minimal HTML into which the
tictactoe React cient can render itself.
There is not yet here any implementation of API endpoints specific to
the tictactoe game. Those will have to come later. (So, for now, each
player that joins a game of Tic Tac Toe will have an independent game
without any communication between them.)
Carl Worth [Sun, 24 May 2020 20:39:01 +0000 (13:39 -0700)]
lmno: Generalize middleware to not be specific to empires
We're planning to extend LMNO past the original single game of Empires
soon. So, we adjust this middleware here to work for any game engine
prior to the game ID in the path, (and simply preserve that engine
string in the result of the redirect).
Carl Worth [Sat, 23 May 2020 23:34:20 +0000 (16:34 -0700)]
empires: Bring in game client code as a template
So far, this is identical to the static HTML file we were using
before, but the idea is that, here as a template, we can add some
dynamic elements, (such as the game ID).
Carl Worth [Sat, 23 May 2020 17:25:44 +0000 (10:25 -0700)]
empires: Add an initial "choose nickname" step before joining a game
This lodges the selected nickname in the current session, (using a new
/profile API also added in this commit).
Having the nickname stored in the session has the advantage that when
playing multiple games in a row, a player won't need to keep re-typing
their own name each time.
Carl Worth [Sat, 23 May 2020 02:33:40 +0000 (19:33 -0700)]
Rename the /stats page to /admin
We're about to change this to not only report statistics, but to also
allow the admin to perform administrative functions (such as deleting
games). So give the page a better name for that.
Carl Worth [Sat, 23 May 2020 02:29:33 +0000 (19:29 -0700)]
Convert rendering of login.html to use a nunjucks template
This new base.html template will allow us to avoid duplicating a bunch
of boilerplate as we start adding additional HTML pages.
Also, we pull the undisplay, add_message, lmno_login, and
lmno_login_loaded functions in instead of including the static
JavaScript file from /lmno.js.
This makes things much more independent here, (rather than relying on
JavaScript functions defined in a script file that is maintained in a
separate git repository: lmno.games).
Carl Worth [Thu, 21 May 2020 16:13:11 +0000 (09:13 -0700)]
Add a simple /stats endpoint to get a count of current games in progress
This view is particularly spartan so far, (just two lines of next, not
even HTML).
Most of the work in this commit is actually setting up the
authentication mechanism, since /stats is the first page we have that
requires a user to be authenticated (and to also have the "admin"
role).
We have a nice-looking "/login" page with proper styling and clean
messages for login failure. If an unauthenticated user goes to /stats
they will be sent to /login?next=/stats and after successfully
authenticating, will be sent back to /stats (this time getting the
spartan view of the game statistics).
There is another set of pages that is more minmal than we really
want. This is all in the area of user that successfully authenticates
but doesn't have the "admin" role. I'm ignoring all of these issues
for now because I'm not going to actually configure any such
users. But here are the issues:
* If a user without the admin role hits /stats they they will get a
correct 401 status, but a very spartan page (just the word
"Unauthorized" as plain text).
* In that case, if the user wants to logout there are no links
provided to do that.
* There _is_ a page at /logout which does do a correct logout, but
again returns a very spartan, plain-text message that you are
logged out.
Carl Worth [Wed, 20 May 2020 23:56:43 +0000 (16:56 -0700)]
Add simple session tracking
By taking advantage of the express-session module.
Note that we're not actually using the session for anything yet, so we
haven't configured anything such as the expiration time or even which
backend should be used for storing the session data (meaning the
session data will just be stored in memory for now).
We do add the very beginnings of a configuration file here, which for
now simply has a "secret_session" key with the random data for the
session manager to use.
Also, we configure both the "resave" and the "saveUninitialized"
properties on the session object to avoid the warnings about the
deprecated values for these two properties.
Carl Worth [Wed, 20 May 2020 02:10:27 +0000 (19:10 -0700)]
lmno: Be forgiving in game ID values received from the user
Most significantly, we now accept a lowercase version of the ID.
But also, game ID values are intentionally chosen not to have
ambiguous character in them, (such as "M"/"N" or "S"/"F"), which might
sound similar when someone says a game ID aloud. So on the input side
we accept either form and canonicalize to the only letter which can
possibly exist in a game ID.
Carl Worth [Sun, 17 May 2020 21:45:34 +0000 (14:45 -0700)]
empires: Serve the actual game file from the root of the app
This game.html file is actually totally static, but it lives at a
dynamic path (which includes the game ID in the path), so within the
dynamic app we need to serve the static file. To arrange for this we
symlink to the static file. Note that we're not committing the symlink
to git since the destination location can differ on each system.
Carl Worth [Sun, 17 May 2020 19:40:44 +0000 (12:40 -0700)]
Incorporate empires.js as a sub-app within lmno.js
Now, instead of calling listen() the empires.js simply exports its app
object so that the higher level lmno server can mount all of its
available paths with app.use('/empires/<GAMEID>/', empires.app).
At the same time, empires.js no longer constructs a Game object, but
instead the higher-level /new API within lmno.js calls into
empires.Game to create a game for a specific ID. Then, there's also a
new middleware in lmno.js to lookup this game object by the ID from
the path and add it as a property of the request object. So all of the
empires.js entry points now fetch that game object from the request as
their first statement.
Carl Worth [Sun, 17 May 2020 19:28:35 +0000 (12:28 -0700)]
empires: Return immediately from reveal_next if we aren't in the REVEAL state
I think this fix came about after Kevin did some testing by mashing on
all of the host buttons as fast as possible.
It's definitely a correct fix as far as it's true we shouldn't be
revealing anything if we're not in the REVEAL state. But I think we're
due for a better audit of what's happening to various pieces of state
on state transitions, (such as, shouldn't the interval timer get
cleared before we _leave_ the REVEAL state).
Carl Worth [Sun, 17 May 2020 19:26:42 +0000 (12:26 -0700)]
Rename server.js to empires.js
We have multiple express-based server apps implemented here now, so
the name "server.js" is now ambiguous. We rename it "empires.js" to
make clear that its an implementation only of the empires game.
Carl Worth [Sun, 17 May 2020 19:19:53 +0000 (12:19 -0700)]
Add a new server: lmno.js
This is the new top-level server for all dynamic content and games
that we plan to host at https://lmno.games. The previous server
implementation (in server.js) will likely be subsumed as a
sub-application within this new server soon.
Carl Worth [Mon, 11 May 2020 23:03:54 +0000 (16:03 -0700)]
Implement a more correct response to /reset
Specifically, we fix things here to clear some internal state that was
previously being missed (the "characters_to_reveal" field) and we also
fix things to report to the clients the new game state and the emptty
list of players.
Carl Worth [Mon, 11 May 2020 21:52:09 +0000 (14:52 -0700)]
Send multiple game-state events (if needed) when a client connects
This implements the latest change as of version 0.6 of the API protocol.
This change fixes a bug in the current empires-html client where
reloading the browser when in the middle of the game would break the
client, (by displaying some content that's inappropriate for the
current state). By sending each step-wise transition through the game
states, the client does not get confused.
Carl Worth [Sun, 10 May 2020 22:45:46 +0000 (15:45 -0700)]
Implement the /reveal,/start endpoints as well as the "game-state" event
This brings us up to protocol version 0.5 and should be a pretty
playable server at this point. Upon receiving the "/reveal" API
request the server will transition to the "reveal" game-state and from
there will broadcast the names of each character to all clients, one
name every three seconds, (and sending an empty name at the end to
clear off the last name). The "/reveal" API request can be sent
additional times if the players want to see the names again.
Carl Worth [Sun, 10 May 2020 21:21:19 +0000 (14:21 -0700)]
Rename a variable from "player_string" to "player_data"
The name of "player_string" made me think this is just a string
representation of a player's name. But instead, this variable is
holding the JSON representation of the data block of a
"player-register" event. So a name of "player_data" is much more
clear.
Carl Worth [Sun, 10 May 2020 15:40:14 +0000 (08:40 -0700)]
Send a comment to every connected client every 15 seconds
This prevents timeouts when the client receives nothing from the
server. For example, firefox appeares to give up on the server when it
receives nothing for two minutes.
Carl Worth [Sun, 10 May 2020 15:35:21 +0000 (08:35 -0700)]
Rename broadcast() to broadcast_event() supported by broadcast_string()
The lower-level broadcast_string() simply sends a string to all clients.
The higher-level broadcast_event() sends a structured event (with both an
event type and a separate data block, as well as ending with two newlines),
exactly as clients will be expecting.
Carl Worth [Sun, 3 May 2020 22:03:56 +0000 (15:03 -0700)]
Add some linting fixes
Specifically some missing semicolons and a missing const.
It's funny that I didn't realize that emacs was doing live linting of
JavaScript all along here, (and highlighting all problems with various
colors and underlining).
Carl Worth [Sun, 3 May 2020 02:24:34 +0000 (19:24 -0700)]
Add simple testing for the entire API
This is more "exercising" than testing, (it doesn't currently verify
the results), but I've verified the server isn't hitting any syntax
errors (with fairly decent code coverage I think) and I've manually
inspected the results to ensure they look correct.
Carl Worth [Sat, 2 May 2020 23:53:13 +0000 (16:53 -0700)]
Add an ID value to each player
This is a first baby step toward implementing the more complete API
for the game which we defined today, and which can be found here:
https://git.cworth.org/git/empires-api
In this commit we also introduce a 'Game' class to encapsulate all
data necessary for the game, (so the 'players' array we had before,
but also the 'next_player_id' which we need now).