]> git.cworth.org Git - empires-server/log
empires-server
3 years agoAdd new failing test case for a reactivated player
Carl Worth [Sat, 27 Jun 2020 16:35:59 +0000 (09:35 -0700)]
Add new failing test case for a reactivated player

I noticed that when a player is reactivated, (that is, by starting a
new event stream by means of a cookie associated with a pre-existing
cookie for a player that had been deactivated), the server was not
properly activating the player.

This commit adds a new failing test case that demonstrates the bug. We
reactivate dale for a 4-player game, but then the server advances the
game out of the answering phase when only 3 players have submitted an
answer. This demonstrates that dale isn't being considered active.

3 years agoempathy: Don't count inactive players to advance from answering phase
Carl Worth [Sat, 27 Jun 2020 16:25:20 +0000 (09:25 -0700)]
empathy: Don't count inactive players to advance from answering phase

This commit implements the desired behavior described in the previous
commit message. The test case that was added in that commit now
passes, (as does the entire test suite).

In the implementation here, we need a count of all player that have
the active property set to true. Instead of looping over the array and
counting this, we have the Game class track this count as players are
added and removed.

3 years agoAdd new failing test case: Inactive players shouldn't impede phase advance
Carl Worth [Sat, 27 Jun 2020 16:11:42 +0000 (09:11 -0700)]
Add new failing test case: Inactive players shouldn't impede phase advance

The current implementation is waiting for all registered players to
have submitted answers before auto-advance (prior to a majority vote
to move on without anyone).

That seems like reasonable logic, except for a player that the server
has already noticed is no longer active (event stream connection has
dropped and server has indicated the player is gone from the game). We
don't want such an inactive player impeding the progress of the game.

This commit adds a test case capturing the behavior we actually want,
(so this test is currently failing).

3 years agoAdd clean handling of Control-C to the test suite
Carl Worth [Sat, 27 Jun 2020 15:59:25 +0000 (08:59 -0700)]
Add clean handling of Control-C to the test suite

Interrupting the test suite will noe run all cleanup necessary, (such
as terminating the child processes that are still actively listening
to empathy event streams), and will give a (partial) test report of
all tests that have been completed.

This is convenient for when a user doesn't want to wait for the slow
tests to complete, but can still get some good information from
sumarizing the results of all of the fast tests that have already been
performed.

3 years agotest: Maintain a list of activated players for automated cleanup
Carl Worth [Sat, 27 Jun 2020 15:43:28 +0000 (08:43 -0700)]
test: Maintain a list of activated players for automated cleanup

The list of deactivate calls at the end of the test sutie was getting
rather messy, (since it required awareness of which players had been
activated in specific tests and then also which had _not_ been
deactivated in other tests).

Instead, we now store an array of each activated player, and loop over
that deactivating all players at the end. We also make deactivate
idempotent, safely doing nothing if we ask to deactivate a plyer which
was already deactivated.

3 years agoOmit inactive players when reporting scores
Carl Worth [Sat, 27 Jun 2020 15:29:49 +0000 (08:29 -0700)]
Omit inactive players when reporting scores

If they have left the game, then we don't need to report a score for
them in this round, since they obviously didn't play in it.

With this commit, the new test added in the previous commit now
passes, and the entire test suite is back to passing again.

3 years agoAdd new failing test case demonstrating zombies sticking around
Carl Worth [Sat, 27 Jun 2020 15:27:19 +0000 (08:27 -0700)]
Add new failing test case demonstrating zombies sticking around

If a player has entirely dropped from the game, then we shouldn't be
reporting a score for them as if they are still active.

This commit adds a new (slow!) test which currently fails to expose
this undesired behavior.

3 years agoAdd a new empathy_player_deactivate function
Carl Worth [Sat, 27 Jun 2020 15:18:26 +0000 (08:18 -0700)]
Add a new empathy_player_deactivate function

To pair with empathy_player_activate and abstract away the goofiness
of pkill and the player_pid=${player}_pid; ${!player_pid} magic.

3 years agoOnly count players who participated in judging when determining quorum
Carl Worth [Sat, 27 Jun 2020 15:00:20 +0000 (08:00 -0700)]
Only count players who participated in judging when determining quorum

Players who are inactive, (whether they registered and then never
submitted answers, or submitted answers but then didn't submit any
judging results), should not be able to sway the results of the
judging.

This commit fixes the failing test case of the previous commit so that
the entire test suite is passing once again.

3 years agoAdd failing test case for a major match-voting bug
Carl Worth [Sat, 27 Jun 2020 14:55:09 +0000 (07:55 -0700)]
Add failing test case for a major match-voting bug

Specifically, the majority requirement for a word match to be
considered succesful has been counting all registered players rather
than only the players actually in the current game.

In the degenerate case, having more inactive registered players than
active would mean that no matches would ever be judged as valid even
with unanimous voting. Obviously, this is a disastrous bug.

The test case added here currently fails, (where one person voting
should be considered a (non-strict) majority in a 2-person game).

3 years agoAdd all players who have answered as "judging" when judging phase starts
Carl Worth [Sat, 27 Jun 2020 14:44:31 +0000 (07:44 -0700)]
Add all players who have answered as "judging" when judging phase starts

The rationale for this change is explained more fully in the commit
message of the previous commit.

This change fixes the failing test case added in the previous commit
so the test suite now entirely passes again.

3 years agoAdd failing test for list of judging players when judging phase begins
Carl Worth [Sat, 27 Jun 2020 14:39:53 +0000 (07:39 -0700)]
Add failing test for list of judging players when judging phase begins

In the answering phase, the list of answering players (the ones we are
waiting for) initially starts out as empty and then gets populated
only as people start typing (that is, submit a post to the
"/answering" endpoint).

That's appropriate since we don't know who among the players
registered are really actively "playing".

But that's not the right logic for the judging phase. The judging
phase starts immediately after the answering phase has completed, so
at that point we know the precise list of active players and we should
display all of them in the "waiting" list until each has submitted.

This commit adds a failing test case demonstrating that this list is
empty when the judging phase starts, but it should include the names
of each player that submitted an answer, (but not the name of a player
that is registered but did not submit).

3 years agoFix bug described in the previous commit
Carl Worth [Sat, 27 Jun 2020 14:31:52 +0000 (07:31 -0700)]
Fix bug described in the previous commit

With this fix, we now advance to the scoring phase as soon as the same
number of people have completed judging as submitted answers in the
first place. (That is, don't wait for a registered player that didn't
participate in the answering phase to now participate in the judging
phase.)

With this fix, the failing test added in the previous commit now
passes, so the test suite is now fully passing.

3 years agotest: Add failing test case for unnecessary delay for end-judging voting
Carl Worth [Sat, 27 Jun 2020 14:27:41 +0000 (07:27 -0700)]
test: Add failing test case for unnecessary delay for end-judging voting

If all of the players that submitted answers have also all completed
judging, then there's nobody left to wait for and it's pointless to
wait for the idle timer to fire and to force everyone to vote to end
judging. Yet, that's what the implementation currently does.

This commit adds a new test case, which fails, showing the point at
which the game should automatically advance to scoring, (once every
player who submitted an answer has also voted).

3 years agoWrap curl_get_cookie and curl_post_cookie in empathy_get/empathy_post
Carl Worth [Sat, 27 Jun 2020 14:16:40 +0000 (07:16 -0700)]
Wrap curl_get_cookie and curl_post_cookie in empathy_get/empathy_post

This allows for simpler calls tat don't need to each keep repeating te
empathy_game_path variable over and over.

While here we add usage documentation to each of the empathy utility
functions as well.

3 years agotest: Keep player event streams open
Carl Worth [Sat, 27 Jun 2020 13:50:00 +0000 (06:50 -0700)]
test: Keep player event streams open

We're about to fix the server to properly notice when a player drops
their connection and then consider them inactive. To prepare for this
in the test suite, here we put the curl calls getting the event stream
into the background so they stick around, and finally kill them at the
end of the test suite.

We use an ugly `eval` based approach so that emapthy_player_activate
with a name like "alice" sets both an $alice_cookie and an $alice_pid
variable. (Sure would be nice if we had real data structures. Oh
well.)

3 years agoStart the judging_idle timer whenever a judgment is received
Carl Worth [Fri, 26 Jun 2020 14:45:10 +0000 (07:45 -0700)]
Start the judging_idle timer whenever a judgment is received

Previously, we were starting the judging_idle timer only when a user
reported they were in the process of judging. This caused a bug in the
following scenario:

  * No players group any words

  * Majority of players submit the word groups unchanged

  * Minority of players drop from the game

  * The PHASE_IDLE_TIMEOUT passes

At this point, the game is supposed to consider the judging phase as
idle, but it wasn't doing this because the idle timer never got
started, (because no players actually grouped any words).

This bug came up in some (admittedly artificial) manual
testing. There's no chane to the test suite here because the current
test suite isn't yet exercising the timing related aspects of the
game, (the phase idle timeouts, etc.).

3 years agoFix some typos in some code comments
Carl Worth [Fri, 26 Jun 2020 14:42:37 +0000 (07:42 -0700)]
Fix some typos in some code comments

Mostly some confusion of "this", "the", and "that".

3 years agoRefactor admin page a bit
Carl Worth [Thu, 25 Jun 2020 03:19:18 +0000 (20:19 -0700)]
Refactor admin page a bit

Using a new filter to map an array to a new array of just a single
property value from each object of the original array.

This lets us avoid using the "for" directive in the template, as well
as use the "join" filter to get commas between each item in the list,
(which is hard to do with the "for" directive in the template).

3 years agoadmin: Fix admin page to correctly show active/idle games and players
Carl Worth [Thu, 25 Jun 2020 01:32:54 +0000 (18:32 -0700)]
admin: Fix admin page to correctly show active/idle games and players

A while ago we changed the storage for players. Previously we had a
_players array and a separate clients array for their
connections. Then at some point in the past we changed to an array
named "players" and instead of separate "clients" each player in
"players" now has its own list of connections.

Ever since that change the admin view has been broken since it wasn't
updated to track that change. Here we bring it up to date, (including
the addition of two nunjucks filters, "active" and "idle" to help with
this).

3 years agoMark players as active:false when they drop all connections
Carl Worth [Thu, 25 Jun 2020 01:29:45 +0000 (18:29 -0700)]
Mark players as active:false when they drop all connections

Prior to this commit, the intent had been to delete players entirely
when they had no remaining connections. But this code was broken for
the same reason as the bug fixed in the previous commit (filter
returns a new array).

But instead of fixing that bug, here we're actually changing the
semantics so that once a player has so remaining connections they are
simply marked as active:false. This is still broadcast out to all
active players as a "player-exit" event but it means the server is
holding onto the data so that a player can reclaim their spot (and
their score) by rejoining later.

3 years agogame: Fix to actually drop connections that get closed
Carl Worth [Thu, 25 Jun 2020 01:28:34 +0000 (18:28 -0700)]
game: Fix to actually drop connections that get closed

The bug here is that filter() returns a new array, so I have to assign
the return value to the original reference for this code to have any
effect.

3 years agoSet judging_start_time_ms before anyone actually does any judging
Carl Worth [Thu, 18 Jun 2020 16:04:29 +0000 (09:04 -0700)]
Set judging_start_time_ms before anyone actually does any judging

The judging phase is different than answering in that judging is
optional. So we can't rely on clients actually sending a "judging"
notification, (since they might just submit without having clicked on
any of the words). So instead, we set the start time for the judging
phase as soon as we enter that phase.

3 years agoAdapt the idle treatment from the answering phase to the judging phase
Carl Worth [Thu, 18 Jun 2020 15:37:03 +0000 (08:37 -0700)]
Adapt the idle treatment from the answering phase to the judging phase

Big copy/paste here, (obviously it would be better to capture the
common code in a "phase" module of some sort).

Note that this is currently untested, only because we haven't gotten
the client listening for judging-idle yet.

3 years agoReset timer handle after clearTimeout
Carl Worth [Thu, 18 Jun 2020 15:15:39 +0000 (08:15 -0700)]
Reset timer handle after clearTimeout

This helps make clear that the timer has been cleared, (so that we
don't accidentally call clearTimeout again in the future or anything
like that).

3 years agoFix bug attempting to set "answering_idle" state
Carl Worth [Thu, 18 Jun 2020 15:11:00 +0000 (08:11 -0700)]
Fix bug attempting to set "answering_idle" state

So annoying to have no static compiler checks for assigning to a
mistyped property like this.

Anyway, the impact of the bug here would have been fairly minimal: If
a user reloaded during idle, they would not have gotten the "Move On"
button like expected.

3 years agoExtend PHASE_IDLE_TIMEOUT from 10 seconds to 30 seconds
Carl Worth [Thu, 18 Jun 2020 14:59:17 +0000 (07:59 -0700)]
Extend PHASE_IDLE_TIMEOUT from 10 seconds to 30 seconds

The 10 second value was always known to be aggressively short. I had
this in there with such a short value because we didn't have the
feature to auto-advance a phase after every single player was in, nor
to auto-idle the phase after the players-waiting list is emptied. So
without those features implemented, players were forced to wait for
the idle timeout in situations where there was nothing to wait for. So
to keep that from being too annoying, I had this parameter dialed
down.

But now that those two missing features were implemented in recent
commits, we take this timeout up to a higher value. With this,
hopefully the Move On button won't appear too soon so that people
inadvertently skip an active player, but instead only appears once
people really are stuck waiting.

3 years agoAdd named parameters for the two idle timer controls
Carl Worth [Thu, 18 Jun 2020 14:57:27 +0000 (07:57 -0700)]
Add named parameters for the two idle timer controls

These controls may need some tweaking, so it's better to have some
defined names for them along with some carefully specified semantics.

Also, we plan to extend the current idle detection from the answering
phased to the judging phase as well, so it will be nice to be able to
reuse these parameters there too.

3 years agoBroadcast "answering-idle" as soon as the waiting list becomes empty
Carl Worth [Thu, 18 Jun 2020 14:49:16 +0000 (07:49 -0700)]
Broadcast "answering-idle" as soon as the waiting list becomes empty

That is, as long as the answering phase has been going on for a "while".

This avoids an awkward pause when all active players are done
answering, everybody wants to move on, but the "Move On" button hasn't
appeared yet, (because clients haven't been told yet that the
answering phase is idle).

3 years agoInclude still-answering players when ruling on end-asnwering majority
Carl Worth [Fri, 26 Jun 2020 14:33:04 +0000 (07:33 -0700)]
Include still-answering players when ruling on end-asnwering majority

The test suite exposed a logical bug here. Imagine a 10-player game
where two players have submitted and 8 are still answering. Those two
players should not have the ability to move on without the answers
from the larger set of players still waiting. But that's what the old
logic would have allowed here.

In this commit we count up all players who have already submitted
answers as well as all players who have at least indicated that they
are answering in order to decide how many we need for a majority.

Note: This does mean that if something happens, (like a major network
outage), that prevents a majority of the players from being able to
submit their answers, the game cannot proceed with the minority who
succesfully submitted. I think I'm OK with that. (If the minority
really wants to proceed in a case as dramatic as that then it would be
reasonable for them to start a new game.)

With this commit the test suite is now fully passing again, (for the
first time since the recent addition of new tests).

3 years agoConsistently use null value to reset the ambiguities list
Carl Worth [Fri, 26 Jun 2020 14:31:43 +0000 (07:31 -0700)]
Consistently use null value to reset the ambiguities list

The constructor has always been setting ambiguities to null, but on
game reset it was getting set to 0 instead. The test suite noticed
this inconsistency, which we fix here to make a recently-added test
start passing.

3 years agoSend a response in the implementation of the '/reset' endpoint
Carl Worth [Fri, 26 Jun 2020 14:30:38 +0000 (07:30 -0700)]
Send a response in the implementation of the '/reset' endpoint

Without this, a client could hang forever waiting for the server to
say something, (which is exactly what the test suite was doing without
this fix).

3 years agoExpand test suite to include testing of auto-phase advancement
Carl Worth [Fri, 26 Jun 2020 14:26:05 +0000 (07:26 -0700)]
Expand test suite to include testing of auto-phase advancement

The previous commit makes it so that when all involved players are
ready to move on, (such as, everyone has submitted their answers), the
game automatically advances to the next phase.

This breaks some existing tests, which had been expecting the game to
wait for everyone to vote to advance (the old, clunky behavior).

In this commit, we fix the test suite to expect the auto advancing in
the first ame (while also adding a check to ensure it doesn't
auto-advance until the last player is ready).

We also add a second game where auto advancing doesn't happen, (one
player is never ready) but a majority of the remaining players vote to
advance without them.

These tests do exhibit a couple of bugs in the current implementation,
(so some failures are expected here). These failures will be fixed in
the immediately following commits.

3 years agoEmpathy: Eliminate delay when there's nobody to wait for
Carl Worth [Thu, 18 Jun 2020 14:31:32 +0000 (07:31 -0700)]
Empathy: Eliminate delay when there's nobody to wait for

We don't even need to see the "Move On" button appear if every single
registered player has completed the answering or judging task.

3 years agoTweak idle timer down from 30 seconds to 10
Carl Worth [Sun, 14 Jun 2020 23:59:33 +0000 (16:59 -0700)]
Tweak idle timer down from 30 seconds to 10

I'm nervos that otherwise the game will impose a long delay after the
final player submits their answers.

3 years agoTrim leading/trailing whitespace when canonizing words
Carl Worth [Sun, 14 Jun 2020 23:48:56 +0000 (16:48 -0700)]
Trim leading/trailing whitespace when canonizing words

We have a theory that this is what was causing otherwise
identical-looking values to appear next to each other in the judging
lists of recent games.

3 years agoImplement an idle event for when nobody has been typing for a while
Carl Worth [Sun, 14 Jun 2020 23:42:33 +0000 (16:42 -0700)]
Implement an idle event for when nobody has been typing for a while

During the answering phase, if no player has been active, (that is, by
submitting an "answering" event), for 30 seconds, then submit an
"answering-idle" event to all players.

3 years agoTo rule on the judging "Move On" button require a maority of answered players
Carl Worth [Sun, 14 Jun 2020 23:40:35 +0000 (16:40 -0700)]
To rule on the judging "Move On" button require a maority of answered players

At this point in the game we know a reliable number of players that
are actually "in" this round, so use that when determining the
majority required.

3 years agoRemove player names from answering and judging when they submit
Carl Worth [Sun, 14 Jun 2020 21:57:10 +0000 (14:57 -0700)]
Remove player names from answering and judging when they submit

Without this fix, reloading the "still waiting" view would make player
names that had previously submitted erroneously appear in the "still
waiting" list once again.

3 years agoFix game-state object to include players judging and answering
Carl Worth [Sun, 14 Jun 2020 21:55:12 +0000 (14:55 -0700)]
Fix game-state object to include players judging and answering

Previously, these were always appearing as empty objects in the JSON
results that we streamed from the server in the game-state object
because JSON.stringify doesn't natively know how to serialize a Set
object.

Here, we provide a replacer function that serializes a Set object as
an array.

3 years agoAdd "/answering" and "/judging" endpoints
Carl Worth [Sun, 14 Jun 2020 18:18:18 +0000 (11:18 -0700)]
Add "/answering" and "/judging" endpoints

These are set up so that clients can report when they are doing any
answering or judging. The server then passes this along so that other
clients can be aware of what's going on.

3 years agoRename "/judging" endpoint to "/judged"
Carl Worth [Sun, 14 Jun 2020 17:18:12 +0000 (10:18 -0700)]
Rename "/judging" endpoint to "/judged"

This is to make room for a new "/judging" endpoint to capture the
notification that a player is in the process of performing judging,
(that they've started clicking on some of the words in the list).

This commit also fixes the test suite to track this change.

Note: "judged" isn't really a perfect name here. By analogy with
"answer" what we want here is a noun, which would imply "judgment" but
that suggests an entirely different meaning to my ears. So, for now,
we're going with the slightly inconsistent "judged".

3 years agoTweak game-ID generation toward more frequently used letters
Carl Worth [Sun, 14 Jun 2020 16:37:27 +0000 (09:37 -0700)]
Tweak game-ID generation toward more frequently used letters

I'd noticed recently that game IDs seemed to have more Qs, Zs, and Js
than it felt like they should. In fact, all letters were being given
an equal probability of appearing, but that doesn't match our
expectation that some letters should be "rare".

In this commit we tweak the selection of letters toward more "common"
letters. This should hopefully give game ID strings that are slightly
more "friendly" looking in general.

The set of available game ID values isn't reduced here (at least
because of the above, but it is reduced because of part of what is
described below), but when many game IDs are allocated it can now take
longer for the random-selection algorithm to find an available one.

We also switch from using 'B' and 'F'/'X' to instead using 'P' and
'S', again favoring letters that English speakers expect to be
"common".

This actually fixes a bug which I hadn't noticed before, in that the
code has always been replacing a '5' in the input with 'S' but doing
that after 'S' was replaced with 'F', so any input with '5' would not
be mapped as intended. Now an input of '5' will work in the place of
'S'.

3 years agoAdapt test suite to add explicit voting to advance phases
Carl Worth [Thu, 25 Jun 2020 16:08:53 +0000 (09:08 -0700)]
Adapt test suite to add explicit voting to advance phases

As of the previous commit, it's now required for players to explicit
vote to advance from one phase to the next. That commit broke the test
suite which didn't have this voting added.

In this commit, we fix the test suite by adding that voting (and
ensuring the state game state transitions from before to after the
vote).

3 years agoAccept votes to advance game at /end-answers and /end-judging endpoints
Carl Worth [Sat, 13 Jun 2020 21:38:28 +0000 (14:38 -0700)]
Accept votes to advance game at /end-answers and /end-judging endpoints

The previous logic was far too strict, where the game would advance
only after every registered player had submitted. That made it
possible for the game to become totally stuck with just a single
player that left the game, (or even that accidentally reloaded the
game in a way that lost their previous session).

Now there are endpoints for players to vote that they would like to
advance past either the "answers" or "judging" phases of the game, and
the game will advance as soon as a majority of the registered players
have voted.

This does still make it possible for the game to get stuck if a
sufficient number of players stop playing, but it's at least less
likely to happen. And we've got plans to improve this situation
further soon as well.

3 years agoForce all submitted judging words to lowercase before mapping
Carl Worth [Fri, 12 Jun 2020 17:24:41 +0000 (10:24 -0700)]
Force all submitted judging words to lowercase before mapping

This fixes the bug revealed in the previous commit so that the test
suite now passes once again.

The server now doesn't apy attention to the capitalization that a
client happens to use when providing a judging submission, but will
treat things as equivalent regardless of case differences.

3 years agotest: Stress the implementation a bit with a mixed-case judging report
Carl Worth [Fri, 12 Jun 2020 17:18:00 +0000 (10:18 -0700)]
test: Stress the implementation a bit with a mixed-case judging report

With this change to the test, we now have a user submitting a judging
report with "grains of sand" in a list after having received "Grains
of Sand" from the server.

The server currently misscores in this case, so this test case now
fails, (revealing a bug in the server).

3 years agoFix bug with incorrect scoring based on capitalization
Carl Worth [Fri, 12 Jun 2020 17:10:06 +0000 (10:10 -0700)]
Fix bug with incorrect scoring based on capitalization

As exposed in the previous commit which extended testing.

After we construct what we call "word maps", (where a group of words
are hased to by each word in the group individually), we collapse them
down to just the array of word groups themselves. This step was being
carried out by looking for the mapping to the word group where the
mapped word matched the first word in the list.

However, if that first word happened to differ in case from the
mapping word (which was already made canonically lowercase) then the
word group wouldn't be included in the final array and all submissions
of words from that group would incorrectly be scored as singletons.

The fix for this is to simply force the word being compared to as in
this commit.

This commit causes the test suite to pass again.

3 years agotest: Empathy: Extend test to submit judging results with proper capitalization
Carl Worth [Fri, 12 Jun 2020 16:56:35 +0000 (09:56 -0700)]
test: Empathy: Extend test to submit judging results with proper capitalization

In commit abea9915939bd7f5b6e70f12d724f19dc6694ec9 we extended the
test suite to cover the case of players submitting words with mixed
capitalization and we ensured that the server didn't send out any
duplicate words for judging, (that it ignored case when determining
which words were unique).

However, in that commit we still had the players returning the results
of their judging in all lowercase. This is not a good test since
actual clients will return words exactly as they received them.

Here we improve the test by submitting judging results with the same
capitalization that the server used when sending out the
ambiguities. And we also expect that same capitalization to appear in
the final word list.

However, this test currently fails in the final word list _and_ also
fails in the scoring. So this test is exposing a bug that currently
exists in the server.

3 years agotest: Empathy: Add test for final words submitted
Carl Worth [Fri, 12 Jun 2020 16:53:10 +0000 (09:53 -0700)]
test: Empathy: Add test for final words submitted

Verifying that we get exactly the results that we expect, (including
all groups and all singletons).

This new test currently passes, (hurrah!).

3 years agoempathy: Alphabetize list before sending it out for judging
Carl Worth [Fri, 12 Jun 2020 02:58:33 +0000 (19:58 -0700)]
empathy: Alphabetize list before sending it out for judging

This should make judging much easier.

Fix is confirmed with the existing test case (which is updated to
match the new behavior).

3 years agoEmpathy: Fix the capitalization bug
Carl Worth [Fri, 12 Jun 2020 02:53:15 +0000 (19:53 -0700)]
Empathy: Fix the capitalization bug

The simple fix here is to always use the canonize function before
passing any word into the word_map.

With this fix, the test implemented in the previous commit now passes.

3 years agotest: Expand empathy test case to include mixed capitalization
Carl Worth [Fri, 12 Jun 2020 02:49:12 +0000 (19:49 -0700)]
test: Expand empathy test case to include mixed capitalization

When actually playing a live game recently, we noticed it normalized
different capitalization when sending words out to be judged, but
didn't normalize it when scoring. So we add a test case for this.

It currently fails here, (showing the bug). When the bug is fixed this
test should start passing without needing any change to the test
itself.

3 years agotest: Add test case for rejection of category that is far too large
Carl Worth [Fri, 12 Jun 2020 00:07:20 +0000 (17:07 -0700)]
test: Add test case for rejection of category that is far too large

As implemented in the previous commit.

3 years agoEmpathy: Reject categories with more than 20 items
Carl Worth [Fri, 12 Jun 2020 00:06:28 +0000 (17:06 -0700)]
Empathy: Reject categories with more than 20 items

Just to avoid any smart aleck trying to get every player's browser to
try to render a million text input fields.

3 years agotest: Exercise the situation coded for in the previous commit
Carl Worth [Thu, 11 Jun 2020 23:41:31 +0000 (16:41 -0700)]
test: Exercise the situation coded for in the previous commit

Specifically, by changing bob's answer of "surfers" to "sand" he
should now receive one less point than before. That's because
"surfers" was worth one point (since only bob submitted it), but now
"sand" is worth 0. And _that's_ because bob is already getting full
credit of 3 points from "sands" for matching everybody, so he doesn't
deserve any additional credit for putting down an additional answer
that the group decided is equivalent to one he already has.

3 years agoEmpathy: Use word groups to assign scores to players, not their lists
Carl Worth [Thu, 11 Jun 2020 23:37:43 +0000 (16:37 -0700)]
Empathy: Use word groups to assign scores to players, not their lists

Previously, we were marching through each players list of words and
looking each word up in the word groups to find a score for that
word. This worked in general, but failed in a specific case:

If a player had two different words that were considered by the group
to be equivalent, that player received the score for that word
twice. To avoid giving a palyer in this situation points that the
group clearly didn't consider they deserved, we instead iterative over
the worud groups themselves to assign points out to players.

This means that when a player ends up having two (or more) words that
end up being equivalent, (based on the team consensus when judging),
all but one of those words will be worth nothing.

3 years agoEmpathy: Change /judging endpoint to expect a top-level word_groups property
Carl Worth [Thu, 11 Jun 2020 22:57:24 +0000 (15:57 -0700)]
Empathy: Change /judging endpoint to expect a top-level word_groups property

It's probably wise, (for future-proofing our APIs), if the server
looks for an explicitly-named property rather than just slurping up
the whole request body.

This also keeps the code more consistent with other endpoints, (which
is going to be particularly important as I start refactoring things to
remove some duplicated code).

3 years agoEmpathy: test: Including final score in testing
Carl Worth [Thu, 11 Jun 2020 22:52:28 +0000 (15:52 -0700)]
Empathy: test: Including final score in testing

And expand the answers a bit to eliminate duplicate scores between
alice and bob. This test verifies that the game is properly scored
according to the word groups that the players submitted.

3 years agoEmpathy: Initial implementation of scoring that considers players' judging
Carl Worth [Thu, 11 Jun 2020 22:26:17 +0000 (15:26 -0700)]
Empathy: Initial implementation of scoring that considers players' judging

I'm slowly learning more about JavaScript, (such as using
Objects.entries instead of open-coding it, using Set(), etc.), which
is helping as I write progressively more complex code.

But I don't feel _really_ confident that I've written this code in the
best form possible.

3 years agoDon't throw out punctuation when "canonizing" words
Carl Worth [Thu, 11 Jun 2020 22:23:32 +0000 (15:23 -0700)]
Don't throw out punctuation when "canonizing" words

This caused a pretty disastrous failure of the game when the team
chose "Programming languages"; people submitted C, C++, and C#; and
the word list they were presented for voting had only C# in it.

3 years agotest: Add initial testing for the Empathy game
Carl Worth [Thu, 11 Jun 2020 19:11:45 +0000 (12:11 -0700)]
test: Add initial testing for the Empathy game

Two general techniques that are introduced here that will be useful
for testing of other games:

  * Registration of multiple user and events sent from each (see
    multiple cookies and a player-cookie parameter for many empathy_*
    functions)

  * Fishing events out of the event stream in response to client
    requests (see curl_get_event)

This testing gets as far as is currently implemented in the server:
Multiple player join, a category is submitted and voted one, the game
starts, all players submit answers, players receive words to judge,
and players submit word groups to capture the results of their
judging.

What's not yet tested is the next phase that's also not implemented
yet: The server using the submitted word groups to affect the scoring.

3 years agoProvide an explicit (empty) return body for some responses
Carl Worth [Thu, 11 Jun 2020 18:53:43 +0000 (11:53 -0700)]
Provide an explicit (empty) return body for some responses

If we just do the numeric sendStatus(200) then node goes and sends a
response with a body of "OK" rather than something empty.

I don't necessarily want that, (and I notice that when I have spurious
"OK" strings cluttering up my test-suite output).

3 years agoempathy: Fix /prompts endpoint to return the ID of the new prompt
Carl Worth [Thu, 11 Jun 2020 14:52:26 +0000 (07:52 -0700)]
empathy: Fix /prompts endpoint to return the ID of the new prompt

It never ceases to amaze me the bugs that can only be found when
writing tests. (My manual client testing missed this bug because they
never looked for this ID, but instead picked up the ID from the
broadcast to all clients when a new prompt is added.)

3 years agotest: Rename cookie file from .test-cookie to .cookie-tictactoe
Carl Worth [Thu, 11 Jun 2020 14:36:56 +0000 (07:36 -0700)]
test: Rename cookie file from .test-cookie to .cookie-tictactoe

In order to have a little independence between test sections, let's
move to having independent cookie files for each.

3 years agoAdd a game phase to perform judging
Carl Worth [Thu, 11 Jun 2020 14:22:29 +0000 (07:22 -0700)]
Add a game phase to perform judging

This is after all answers are submitted, and before scores are computed.

The list of unique words are sent to each player so that they can
group them into sets of equivalent words.

The server isn't yet _doing_ anything with the word groups that are
submitted from judging, but that will be the next step.

3 years agoEmpathy: Delay the updating of total scores until a new round
Carl Worth [Thu, 11 Jun 2020 02:26:51 +0000 (19:26 -0700)]
Empathy: Delay the updating of total scores until a new round

Instead of doing this in the compute_scores function (for the current
round) as we may end up having that function called multiple times.

3 years agoAccumulate running scores for a multi-round game
Carl Worth [Wed, 10 Jun 2020 16:15:36 +0000 (09:15 -0700)]
Accumulate running scores for a multi-round game

Obviously, if players stick around for more than one round, they are
going to want to see hwo their score accumulates.

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.