]> git.cworth.org Git - lmno-server/log
lmno-server
3 years agoEmpathy: Prune the list of proposed categories on game reset
Carl Worth [Wed, 10 Jun 2020 14:55:16 +0000 (07:55 -0700)]
Empathy: Prune the list of proposed categories on game reset

Obviously, we drop the prompt that we just played. We also drop any
prompts that got no votes at all, (giving players a way to prune
prompts from the list from one round to the next).

3 years agoFix the /reset endpoint
Carl Worth [Wed, 10 Jun 2020 14:25:13 +0000 (07:25 -0700)]
Fix the /reset endpoint

The fix here was pretty simple. I'd only missed this because I had
forgotten to add an explicit setting of state.scores to null in the
constructor, (so then forgot to also do the same in reset()).

3 years agoAdd scoring once all players have submitted
Carl Worth [Wed, 10 Jun 2020 04:28:23 +0000 (21:28 -0700)]
Add scoring once all players have submitted

And a not-quite-yet-fully-functional /reset, (or maybe it works but
the client is broken). This was a 90-minute flurry of coding, so I
haven't looked closely yet at where the bugs are.

3 years agoempathy: Tell clients how many players have responded
Carl Worth [Wed, 10 Jun 2020 03:01:56 +0000 (20:01 -0700)]
empathy: Tell clients how many players have responded

That's probably not quite the information the client _really_ wants,
(since it probably wants to at least show the names of those who
haven't submitted yet), but it at least gives the client _some_
information that's not static.

3 years agoEmpathy: Add a new /answer route to accept answers from a user
Carl Worth [Wed, 10 Jun 2020 02:41:23 +0000 (19:41 -0700)]
Empathy: Add a new /answer route to accept answers from a user

This is the bare minimum, simply storing the answers. Going forward
we'll want to expand this to inform players when another player has
submitted, etc.

3 years agoFix buggy regular expression
Carl Worth [Wed, 10 Jun 2020 02:38:08 +0000 (19:38 -0700)]
Fix buggy regular expression

I mean, it's unlikely that anyone would ever submit a URL with a
square bracket just before the prompt number, so this probably never
would have mattered all that much.

3 years agoUse spread syntax rather than fill(nill) in generate_id
Carl Worth [Wed, 10 Jun 2020 01:14:29 +0000 (18:14 -0700)]
Use spread syntax rather than fill(nill) in generate_id

We don't actually need the array filled with null values specifically,
we just need something that map() will act on (since it doesn't act on
the empty entries we get from Array(4)). Using [...Array(4)] gives us
an array of undefined values which does the trick, and is a bit more
compact than .fill(null).

Of course, I blow away any savings in compactness with the giant
comment I've added here!

3 years agoDon't let a new prompt get started when another is active
Carl Worth [Tue, 9 Jun 2020 15:10:11 +0000 (08:10 -0700)]
Don't let a new prompt get started when another is active

This avoids any negative side effect from a race condition where two
clients request a game to start within a short time window of each
other. When one prompt is active, the server will ignore any further
incoming requests to start a new prompt, (until the active one is
completed).

3 years agoEmpathy: Add support for starting the actual game
Carl Worth [Tue, 9 Jun 2020 15:06:37 +0000 (08:06 -0700)]
Empathy: Add support for starting the actual game

When a request is received at "/start" to start a particular prompt,
the server responds by broadcasting a "start" event to all clients
telling them to start the game.

The prompt is also set as the active prompt in the game state so that
if any new client joins while a prompt is active they will become
aware of that.

3 years agoempathy: Better separation of concerns between routes and game class
Carl Worth [Tue, 9 Jun 2020 15:03:58 +0000 (08:03 -0700)]
empathy: Better separation of concerns between routes and game class

In this commit we make the route handlers much simpler: Their job is
to parse data from the received request and provide a response to the
client, (such as an appropriate status code), but otherwise, the meat
of the functionality is provided by methods on the Empathy class).

This separation should lead to more maintainable code as the
implementations on both sides are smaller and more focused on a single
job.

3 years agoEmpathy: Make a vote for a prompt toggle the vote
Carl Worth [Tue, 9 Jun 2020 02:05:22 +0000 (19:05 -0700)]
Empathy: Make a vote for a prompt toggle the vote

Since what else might it mean for a user to vote where they had
already voted?

3 years agoEmpathy: Add routes to receive prompts and votes on prompts
Carl Worth [Mon, 8 Jun 2020 13:59:43 +0000 (06:59 -0700)]
Empathy: Add routes to receive prompts and votes on prompts

The received votes are added to the prompt objects themselves, so we
don't need to send anything beyond the prompts, and the votes will
just ride along "for free". Also, because prompts are a part of the
Empathy game's "state" property, they will always be sent as part of
the "game-state" event sent when a new client joins, so we don't need
any explicit code in the Empathy class to take care of that either.

3 years agoAdd a new "broadcast_event_object" method to accept an obejct not a string
Carl Worth [Mon, 8 Jun 2020 13:57:22 +0000 (06:57 -0700)]
Add a new "broadcast_event_object" method to accept an obejct not a string

I probably want this to be the default, (replacing broadcast_event
entirely, or perhaps renaming it to broadcast_event_string if there
are any users that do need to stick around).

With cases like this, I do wish I was using a type safe language to
avoid the confusion that comes with "Does this function want an object
or a string representation of that object?".

3 years agoAdd initial shell of a game for Empathy
Carl Worth [Sun, 7 Jun 2020 18:55:32 +0000 (11:55 -0700)]
Add initial shell of a game for Empathy

No real functionality here yet.

3 years agoScribe: Small tweaks to the page layout
Carl Worth [Sat, 6 Jun 2020 19:26:35 +0000 (12:26 -0700)]
Scribe: Small tweaks to the page layout

Adding a link to the game-title header, and moving attribution below
the game instead of above it.

3 years agoAdd 'trust proxy' option since we trust our proxy
Carl Worth [Sat, 6 Jun 2020 16:34:29 +0000 (09:34 -0700)]
Add 'trust proxy' option since we trust our proxy

This fixes the URL that the server gives to clients in the game-info
event so that it will be a working URL (https://lmno.games/WXYZ)
instead of a URL from the node point of view (http://localhost/WXYZ)
which won't do any good for users to share.

3 years agoInitial implementation of Scribe
Carl Worth [Sat, 6 Jun 2020 15:51:59 +0000 (08:51 -0700)]
Initial implementation of Scribe

This is not at all sophisticated yet. The biggest shortcoming is that
it doesn't yet reject moves that don't follow the movement
restrictions (where a player must play in the super-grid corresponding
to their last moves mini-grid).

3 years agoProperly JSON-stingify move before broadcasting it
Carl Worth [Sat, 6 Jun 2020 15:47:27 +0000 (08:47 -0700)]
Properly JSON-stingify move before broadcasting it

With the Tic Tac Toe game, this bug was hidden, (since the move data
is just an integer and there's no difference in strignifying that or
not). But an upcoming game (Scribe) uses a pair of integers, and
there's a lot of difference between "1,2" and "[1,2]" since only the
latter is proper JSON.

3 years agoAllow for games to opt in to allow "Guest" users
Carl Worth [Sat, 6 Jun 2020 12:58:53 +0000 (05:58 -0700)]
Allow for games to opt in to allow "Guest" users

Here we allow a new object to the game metadata called "options" and a
new optional property in that called "allow_guest".

If this option is present and true, then the choose-nickname form does
not require the user to enter a name, but will use "Guest" by default.

This is convenient in a 2-player game, for example, where the two
players are the only ones present so there's no ambiguity over who is
who.

But this could be very confusing for a party game like Empires.

That is why, in this commit, tictactoe.js sets allow_guest to true
while empires.js does not.

Also, now that it's even more likely for there to be name collisions,
(such as multiple people entering the game as "Guest"), when adding a
player we not automatically append a numeric suffix to their name to
make it unique within the game. (Note: The name in the session is left
untouched so if a user with the same session joins a different game
and the session name is unique there, it won't get any suffix.)

3 years agoempires: Rename add_player to register_player
Carl Worth [Sat, 6 Jun 2020 12:54:35 +0000 (05:54 -0700)]
empires: Rename add_player to register_player

We still haven't yet ported the Empires implementation to play nicely
with all of the new functionality in the base Game class. In
particular, Game now has a base "add_player" method with different
semantics than the older "add_player" method in Empires.

To avoid Empires.add_player from being inadvertently called, we rename
it here to register_player.

This fixes, for the moment, a failure when trying to create a new
Empires game of:

    TypeError: player.remove_connection is not a function

3 years agoFix admin interface to work once again
Carl Worth [Sat, 6 Jun 2020 12:10:36 +0000 (05:10 -0700)]
Fix admin interface to work once again

This has been broken since commit 4c810ccb16ea6cfcdcb7f507aea2affc2c36f163
where we renamed "clients" to "players" but missed this reference.

How I miss static checking for things like this...

3 years agoAssign new players to the first team with no players
Carl Worth [Sat, 6 Jun 2020 00:07:10 +0000 (17:07 -0700)]
Assign new players to the first team with no players

But only if the game has already started, (since with an unstarted
game, all players are intentionally unassigned so that any player can
go first).

If there is no team that has no players, the new player will be
unassigned, (and will instead be a spectator until they join a team).

This logic is designed so that a game in which the precise number of
people were invited to play, (most notabl, a two-player game), these
players will be automatically assigned to teams as soon as one player
makes a move, (regardless of whether the players joined the game
before or after that move).

3 years agoSwitch team property from being a string to being an object
Carl Worth [Fri, 5 Jun 2020 23:55:55 +0000 (16:55 -0700)]
Switch team property from being a string to being an object

This should be cleaner as it avoids a bunch of bare string literals
all around the place that happen to match. Now, the player's team
property is a pointer into the game's array of teams. And for the case
when a player is not on a team, the game file maintains a "no_game"
sentinel object (with an empty-string name).

3 years agoStream player-enter events for existing players to a new player
Carl Worth [Fri, 5 Jun 2020 23:35:12 +0000 (16:35 -0700)]
Stream player-enter events for existing players to a new player

So they can be informed of all players that are already in the game.

3 years agogame: Allow either player to play the first move
Carl Worth [Fri, 5 Jun 2020 23:18:57 +0000 (16:18 -0700)]
game: Allow either player to play the first move

When players originally join the game they do not belong on any
team. And while we could require the players to divide themselves up
and join separate teams, it's much easier for them if they aren't
required to coordinate on that.

So, with this commit we allow any player, (not yet in a team), to make
the first move. At that point, we assign that player the appropriate
team for playing first, and assign at least one player (if there are
others in the game) to each successive team value in the order they
originally joined the game.

This means that in the common case of a 2-player game, if both players
are in the game, and one player makes a move, then the two players
will be assigned to separate teams.

3 years agogame: Store players in both an array _and_ a session-indexed object
Carl Worth [Fri, 5 Jun 2020 23:15:47 +0000 (16:15 -0700)]
game: Store players in both an array _and_ a session-indexed object

We originally wrote the code storing palyers in an array, and then we
changed to using an object (indexed by session ID) for convenience in
looking up players by the session ID value.

Here, we add the array back so that we can access players either way:

1. Indexing into players_by_session to lookup a player by session ID

2. Iterating over players when we want to access players in order

The second usage, (in order), will be convenient for a subsequent
commit where we assign teams out to players in the order that they
joined the game.

3 years agotictactoe: Simplify code by not reusing the expression "this.state"
Carl Worth [Fri, 5 Jun 2020 23:14:04 +0000 (16:14 -0700)]
tictactoe: Simplify code by not reusing the expression "this.state"

Instead, just call this once with "const state = this.state" and then
just use "state" for all subsequent uses.

3 years agoMove some checks from TicTacToe.add_move to Game.add_move
Carl Worth [Fri, 5 Jun 2020 20:56:45 +0000 (13:56 -0700)]
Move some checks from TicTacToe.add_move to Game.add_move

Specifically, the checks for whether the player who submitted a move
belongs on a team and belongs to the team that has the next move.

These checks belong in the generic Game class so that future games
won't have to maintain their own copies of these implementations.

Previously, we were using the presence of the "add_move" property on a
game class to determine whether to add the "/move" route. Now that we
are adding add_move to the base class, we have to check specifically
whether the child class has its own add_move (with
hasOwnProperty('add_move')) to know whether the "/move" route should
be added.

3 years agoRename "next_player" property to "team_to_play"
Carl Worth [Fri, 5 Jun 2020 20:54:28 +0000 (13:54 -0700)]
Rename "next_player" property to "team_to_play"

The game object already has two distinct notions of players and
teams. And this property will only be compared to team values, not
player values, so this name is more accurate and less confusing.

3 years agotictactoe: Enforce only legal moves with regard to team membership
Carl Worth [Fri, 5 Jun 2020 14:15:33 +0000 (07:15 -0700)]
tictactoe: Enforce only legal moves with regard to team membership

Rejecting any attempt to move if a player cannot be found from the
current session, or if the player is not on the right team, etc.

Commit includes testing.

3 years agogame: Index players directly by session ID
Carl Worth [Fri, 5 Jun 2020 13:57:04 +0000 (06:57 -0700)]
game: Index players directly by session ID

Previously we were just storing the players in an unordered array, and
doing an O(n) walk through the array to find a player with a session
ID that matched the ID of interest.

Instead, we now index directly with the session ID which is already
guaranteed yo be unique and is convenient in JavaScript where we can
use the session ID as a property name, and even use a convenient
subscript syntax for that.

With this change we don't even need to store the session_id property
on the player since it is now not used at all.

3 years agogame/tictactoe: Expand player to include a team property
Carl Worth [Fri, 5 Jun 2020 13:36:41 +0000 (06:36 -0700)]
game/tictactoe: Expand player to include a team property

The existing /player endpoint is expanded to allow a player to choose
which team to join. The team must be either an empty string, (to leave
all teams and be just a spectator), or must be one of the defined
teams of the current game. As expected, TicTacToe defines its teams as
"X" and "O".

Commit includes testing.

3 years agogame: Add a new /player endpoint to allow a player to change their name
Carl Worth [Fri, 5 Jun 2020 13:18:19 +0000 (06:18 -0700)]
game: Add a new /player endpoint to allow a player to change their name

This sets the name both in the current game's player object as well as
in the session, (so it's there for subsequent games as well).

We have a simple test for this new endpoint as well.

3 years agoAdd a new player-info event to the stream
Carl Worth [Fri, 5 Jun 2020 12:41:40 +0000 (05:41 -0700)]
Add a new player-info event to the stream

This event informs the player what their ID and their name is. It's
significant to tell them their own name because it may be provided
from the server session (obtained by a cookie from the client).

This commit includes a test case which uses the existing /profile
endpoint to set a name, and then verifies that that name is returned
in the player-info event.

3 years agogame: Rename Game.clients to Game.players, combining multiple connections
Carl Worth [Fri, 5 Jun 2020 12:31:15 +0000 (05:31 -0700)]
game: Rename Game.clients to Game.players, combining multiple connections

Previously we were simply storing an array of "clients", one for every
request to the /events endpoint.

Now, the array of "players" is similar, but there is only one item in
the array for each unique session ID, but where each one may have
multiple "connections", (for a case where a player connects multiple
times with the same session ID).

In this commit we're not making large changes to the Empires class to
take advantage of this new functionality, (for example, it already has
unique session identification as part of its "spectators"
notion). Instead, we make a minimal change Empires so that it doesn't
step on the base "players" property.

In the future, we'll be able to port Empires forward to use this
base-class functionality and in the process delete some code from the
Empires class.

3 years agogame: Send a game-info event when a client connects
Carl Worth [Fri, 5 Jun 2020 01:17:48 +0000 (18:17 -0700)]
game: Send a game-info event when a client connects

This is in the generic Game class so all games will get this
functionality. This will allow clients to be able to display the
game's ID and URL so players can invite friends.

3 years agoMove the keepalive functionality from Empires up to Game
Carl Worth [Thu, 4 Jun 2020 01:06:39 +0000 (18:06 -0700)]
Move the keepalive functionality from Empires up to Game

Since TacTacToe, (and any future game), need this too.

This keepalive prevents Firefox (at least) from timing out on the
server-sent-events stream after the user is idle for two minutes.

3 years agoAdd message string to the return value of add_move
Carl Worth [Wed, 3 Jun 2020 22:20:12 +0000 (15:20 -0700)]
Add message string to the return value of add_move

Now, instead of just returning a Boolean indicating whether the move
was legal, add_move returns an object with both the Boolean "legal"
property as well as a string "message" indicating what made the move
illegal.

3 years agotictactoe: Reword the comment describing the add_move method
Carl Worth [Mon, 1 Jun 2020 21:32:50 +0000 (14:32 -0700)]
tictactoe: Reword the comment describing the add_move method

The former wording was just a little vague and potentially
ambiguous. It's now more clear about when a true value is returned,
and when false is returned.

3 years agoStore only the the .Game property when importing a game engine
Carl Worth [Mon, 1 Jun 2020 21:29:53 +0000 (14:29 -0700)]
Store only the the .Game property when importing a game engine

Previously, we were referencing engine.Game separately from
engine.router, etc. But now that engine.Game is the only property
that's part of the interface, let's dereference that immediately at
the time of require() so that later on we can just use "engine" in
place of "engine.Game".

3 years agoAdd some documentation about the Game import interface
Carl Worth [Mon, 1 Jun 2020 21:24:29 +0000 (14:24 -0700)]
Add some documentation about the Game import interface

Having some notes here will simplify the task of making a new Game by
copying an existing game, (since the game logic itself may obscure
some of the details of this interface).

3 years agoMove our router objects from exports.router to exports.Game.router
Carl Worth [Mon, 1 Jun 2020 21:22:26 +0000 (14:22 -0700)]
Move our router objects from exports.router to exports.Game.router

It feels cleaner to have a single object to import and hang everything
we need off of it.

3 years agoGeneralize the handling of the /move request
Carl Worth [Mon, 1 Jun 2020 21:16:04 +0000 (14:16 -0700)]
Generalize the handling of the /move request

Implementing it at the "lmno" level instead of in "tictactoe".

This means that future game implementations that involve clients
sending moves to the server (which, is obviously a common thing), can
simply implement an "add_move" method in the Game class at not need to
implement the route handler piece of things.

3 years agoRename identifiers from square to move
Carl Worth [Mon, 1 Jun 2020 18:35:43 +0000 (11:35 -0700)]
Rename identifiers from square to move

There is no functional change here, but the code makes more sense this
way given the code change of the previous commit. The body property is
now named .move not .square so use identifiers that match.

3 years agotictactoe: Track API change for the /move request: "square" -> "move"
Carl Worth [Mon, 1 Jun 2020 18:33:36 +0000 (11:33 -0700)]
tictactoe: Track API change for the /move request: "square" -> "move"

The API was recently changed to be more generic. We track that here by
looking for a .move property in the body object, instead of .square.

And we conform to that by sending events with data named "move" nota
"square" in the corresponding test cases.

3 years agoGive the "/events" route a common implementation
Carl Worth [Mon, 1 Jun 2020 14:51:59 +0000 (07:51 -0700)]
Give the "/events" route a common implementation

At this point, both the empires and the tictactoe implementation have
an identical implementation of the "/events" route so we push this up
to the lmno.js file.

3 years agoStandardize transmission of game "state" when a client joins
Carl Worth [Mon, 1 Jun 2020 14:44:37 +0000 (07:44 -0700)]
Standardize transmission of game "state" when a client joins

With this addition to the Game class, the TicTacToe class drops its
custom implementation of the handle_events method.

Similarly, future game implementations can simply have a "state"
property and not need to do anything further for new clients to be
provided with the current state when they join.

3 years agotictactoe: Use a "game-state" event instead of a series of "move" events
Carl Worth [Mon, 1 Jun 2020 14:38:45 +0000 (07:38 -0700)]
tictactoe: Use a "game-state" event instead of a series of "move" events

With this, when a client joins they will get a snapshot of the current
game state as well as a history of all previous moves.

3 years agotictactoe: Move all state-related properties into a new "state" property
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).

3 years agotictactoe: Actually toggle between X and O as the current player
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.

3 years agotictactoe: Use empty strings instead of null objects for empty squares
Carl Worth [Mon, 1 Jun 2020 14:25:53 +0000 (07:25 -0700)]
tictactoe: Use empty strings instead of null objects for empty squares

So that the data in the board array is always of a consistent type.

3 years agoempires: Rename GameState to GamePhase
Carl Worth [Mon, 1 Jun 2020 05:45:10 +0000 (22:45 -0700)]
empires: Rename GameState to GamePhase

In order to free up "state" as a more generic term for capturing the
totality of the state needed for a game.

3 years agoDrop the meta property from the game exports object.
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.

3 years agoReturn the actual game object from create_game, not just the ID
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.

3 years agoDrop a gratuitous anonymous object for storing an array of games
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.

3 years agoLMNO: Simplify storage of the games array by not storing ID next to game
Carl Worth [Mon, 1 Jun 2020 00:49:47 +0000 (17:49 -0700)]
LMNO: Simplify storage of the games array by not storing ID next to game

In the previous commit, we ensured that a game object always knows its
own ID, so it's unnecessary to store it alongside.

Delete this field from this anonymous object and change all references
to instead access game.id.

3 years agoPlumb a game's ID value down into the base class
Carl Worth [Mon, 1 Jun 2020 00:47:28 +0000 (17:47 -0700)]
Plumb a game's ID value down into the base class

Since, whenever anyone has a reference to a game, it can be handy to
be able to query its ID value.

3 years agoDrop the name field from the Game class
Carl Worth [Mon, 1 Jun 2020 00:44:14 +0000 (17:44 -0700)]
Drop the name field from the Game class

This name is already not used anywhere, (the code has since changed to
using Game.meta.identifier instead), so kill it.

And this lets us frop the global "engine_name" variable from each
game's JavaScript file (which had a value redundant with
meta.identifier anyway).

3 years agoDrop the "name" entry from the game-engine exports
Carl Worth [Mon, 1 Jun 2020 00:35:17 +0000 (17:35 -0700)]
Drop the "name" entry from the game-engine exports

This is now available as "identifier" within the game metadata, so use
that consistently instead.

3 years agotest: Extend test case to include the LMNO redirects
Carl Worth [Mon, 1 Jun 2020 00:27:18 +0000 (17:27 -0700)]
test: Extend test case to include the LMNO redirects

Obviously, this is functionality we don't want to break in any current
(or future) code refactoring.

3 years agoEliminate code duplication for root path
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.

3 years agoAdd a new "identifier" field to the metadata for each game
Carl Worth [Sun, 31 May 2020 23:46:50 +0000 (16:46 -0700)]
Add a new "identifier" field to the metadata for each game

And use this in the handling of the root path to select the
appropriate HTML template to render.

3 years agoAdd first use of Game.meta to both the Empires and TicTacToe classes
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.

3 years agogame: Add support for a static "meta" field to hold game metadata
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).

3 years agotest: Add simple testing for tictactoe as well
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.

3 years agotest: Update the test suite to work with game creation
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.

3 years agoempires: Add a response of the player ID from /register
Carl Worth [Sun, 31 May 2020 22:59:44 +0000 (15:59 -0700)]
empires: Add a response of the player ID from /register

This tracks the change in Empires API version 0.8.

3 years agoRename LMNO.ids to LMNO.games
Carl Worth [Sun, 31 May 2020 15:30:01 +0000 (08:30 -0700)]
Rename LMNO.ids to LMNO.games

This is really a set of games (indexed by ID) not a set of IDs
themselves, so this is a much more accurate name.

3 years agotictactoe: Replay previous moves when a new client connects
Carl Worth [Sat, 30 May 2020 03:55:30 +0000 (20:55 -0700)]
tictactoe: Replay previous moves when a new client connects

This fixes the bug where a client would join a game in progress but
would see only the blank board.

3 years agoAdd common handle_events code to the Game class
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.

3 years agoPut add_client/remove_client and the various broadcast functions into Game
Carl Worth [Sat, 30 May 2020 03:22:17 +0000 (20:22 -0700)]
Put add_client/remove_client and the various broadcast functions into Game

This eliminates the duplication of these functions that we previously
had in both the Empires and TicTacToe classes.

3 years agoAdd new game.js with a new parent class Game
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.

3 years agoRename "app" to "router" within each game engine
Carl Worth [Wed, 27 May 2020 17:10:02 +0000 (10:10 -0700)]
Rename "app" to "router" within each game engine

This is a more suitable name now that each of these objects is
actually an express router and not a full-fledged express app.

3 years agoUse an express Router for each of the game-engine-specific sub-apps
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.

3 years agoDrop misleading word "active" from list of idle games
Carl Worth [Wed, 27 May 2020 16:54:52 +0000 (09:54 -0700)]
Drop misleading word "active" from list of idle games

By definition, an idle game has no active players, so this word was
misleading, (just a leftover copy/paste bug from the list of active
games).

3 years agoRemove debugging log message
Carl Worth [Wed, 27 May 2020 16:53:53 +0000 (09:53 -0700)]
Remove debugging log message

This was left around from some recent work to fixup the redirect.

3 years agoAdd a minimal implementation of the TicTacToe game engine
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.

3 years agolmno: Generalize the support for multiple game engines
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).

3 years agoEnsure path ending with game ID always has a trailing slash
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.

3 years agogenerate_id: Use Array.fill(null) to initialize an array of null values
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:

Array(4).fill(null).map(...)

3 years agoAdd the barest template of an implementation of a tictactoe game
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.)

3 years agochoose-nickname: Make this page parameterizable
Carl Worth [Tue, 26 May 2020 03:34:02 +0000 (20:34 -0700)]
choose-nickname: Make this page parameterizable

Accepting in the template context the name of a game to be rendered.

This allows for the same choose-nickname template to be used for
several different games.

3 years agolmno: Generalize middleware to not be specific to empires
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).

3 years agoempires: Implement support for spectators
Carl Worth [Sun, 24 May 2020 16:19:50 +0000 (09:19 -0700)]
empires: Implement support for spectators

These are much like players, but without a selected character, and
they are displayed separately in the game UI.

This brings us up to version 0.7 of the empires protocol in
lmno-api:empires.txt.

3 years agoFix viewport meta tag to use proper separators
Carl Worth [Sun, 24 May 2020 15:53:31 +0000 (08:53 -0700)]
Fix viewport meta tag to use proper separators

I have no idea where these semicolons came from. Thanks to the
chromium console for reporting that they should be commas instead.

3 years agoempires: Bring in game client code as a template
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).

3 years ago/register: Optionally use the session profile nickname for the player's name
Carl Worth [Sat, 23 May 2020 17:40:54 +0000 (10:40 -0700)]
/register: Optionally use the session profile nickname for the player's name

This allows a UI to set a nickname once in the profile and then send
only the character name in the /register action.

It's still supported to set the name in the /register action as well.

3 years agoempires: Add an initial "choose nickname" step before joining a game
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.

3 years ago/logout: Destroy the session object on logout
Carl Worth [Sat, 23 May 2020 17:24:16 +0000 (10:24 -0700)]
/logout: Destroy the session object on logout

This allows for using the /logout API to clear a session, (even if the
user hasn't authenticated at all).

3 years ago/admin: Switch to rendering via a template
Carl Worth [Sat, 23 May 2020 12:47:36 +0000 (05:47 -0700)]
/admin: Switch to rendering via a template

Thanks to the common code of base.html, this looks lovely and styled
(compared to the previous text/plain output) while not even being that
much code.

We also now display the IDs of all games as well as the names of each
player.

3 years agoRename the /stats page to /admin
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.

3 years agoConvert rendering of login.html to use a nunjucks template
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).

3 years agoAdd dependency for nunjucks
Carl Worth [Sat, 23 May 2020 02:29:22 +0000 (19:29 -0700)]
Add dependency for nunjucks

This was with:

npm install nunjucks

The nunjucks module gives us templating along the lines of jinja2
(along with inherited templates that overwrite only selected blocks).

3 years agoempires: Pull the body_parser use statements up to the top of the file
Carl Worth [Thu, 21 May 2020 16:23:40 +0000 (09:23 -0700)]
empires: Pull the body_parser use statements up to the top of the file

Just keeping things organized by enabling all of our dependencies up
at the top of the file.

3 years agoAdd a simple /stats endpoint to get a count of current games in progress
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.

3 years agoAdd a utility lmno-passwd.py
Carl Worth [Thu, 21 May 2020 14:11:53 +0000 (07:11 -0700)]
Add a utility lmno-passwd.py

This is a convenience to help the admin generate user entries for the
configuration that include correctly hashed passwords.

3 years agoAdd dependencies for the prompt-sync module
Carl Worth [Thu, 21 May 2020 14:11:07 +0000 (07:11 -0700)]
Add dependencies for the prompt-sync module

Generated with "npm install prompt-sync"

This is for getting user input for a password-hashing utility.

3 years agoAdd dependencies for bcrypt
Carl Worth [Thu, 21 May 2020 14:04:37 +0000 (07:04 -0700)]
Add dependencies for bcrypt

The contents of this commit were created by "npm install bcrypt"

3 years agoAvoid an undefined reference when handed an unknwown game ID
Carl Worth [Thu, 21 May 2020 03:33:27 +0000 (20:33 -0700)]
Avoid an undefined reference when handed an unknwown game ID

We're on our way to a clean 404 error, but we don't want to
hit a JavaScript error along the way.

3 years agoAdd simple session tracking
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.