Commit Graph
2 Commits
Author SHA1 Message Date
tiennm99 ef638e52fd feat(game): leave a dead end to the clock and show what was missed
Closing the position no longer wins the game on the spot. The player handed
a syllable that starts nothing keeps the turn they were given and loses it to
the clock, the way they lose any turn they cannot answer — the win used to
land before they had seen the board at all. A turn lost to a position nobody
could answer is reported as the dead end it was rather than as time spent
thinking.

The bot is the exception, and stays one: it has no clock to spend, so the
room settles its dead end the moment the search comes back empty. NoMove
does that without dressing it up as a resignation the bot never chose.

GameOver now carries a few of the words the position still had, filled for
the losing player only — the winner was not the one who was stuck. An empty
list on a loss is the other half of the message: nothing could have been
played, so the panel says so instead of listing nothing.
2026-09-07 15:51:22 +07:00
tiennm99 ca01145d06 feat(game): add noi tu rules engine and bot opponent
The engine is transport-free: no sockets, no protobuf, and no clock of its
own. Callers pass the current time in and read the deadline back, so every
rule is testable without a timer. One goroutine owns a game.

Validation resolves the word before checking the chain link. Roughly a
third of dictionary aliases move the first syllable, so "sy hai" resolves
to "si hai"; checking the link against what the player typed would reject
legal moves. The used-word set is keyed on the canonical form, which also
stops the same word being played twice under two spellings.

An opening whose last syllable starts nothing is refused. It would hand
the first player a game already lost, with no move and no reason, that
resolves only when the turn timer expires and then reports a timeout.

Three bot difficulties, separated by how far they look ahead rather than
by how willing they are to win: random, one-ply greedy, and depth-limited
negamax with alpha-beta. Strategies see a read-only view of the board, so
a bot cannot bypass the same validation a human's move goes through.

Simulation on the real corpus, alternating sides across 60 games per
pairing: hard beats easy 98%, medium beats easy 87%, hard beats medium
65%. Decisions take at most 19ms against a 150ms budget.

Three defects surfaced only under simulation. Withholding the winning
move from the middle bot, as first designed, made it lose to the random
bot 97% of the time. The search evaluated leaves with an inverted sign,
so it hunted for positions where it was about to be trapped. And the
rate limit on taking an instant win did nothing, because declining the
shortcut let the search rediscover the same move.

Games against the hard bot end after about three moves versus fifteen
for two random bots: with 1,814 dead-end syllables an instant win is
usually available. Tunable, and flagged for playtesting.
2026-09-04 17:29:47 +07:00