v0.7.0 — four game types, a lobby you can read and leave, and a multiplayer game that makes a sound
The multiplayer set-up, the lobby, the start of a game, and four signals a remote client had never been sent. Reasoning, the preset table and what was verified how: CHANGELOG.md. - Co-op, Competitive, Cutthroat, Solitaire and Custom, on both screens, from one shared block — they had drifted, and each was missing a question the other asked. - A player reads the whole rule set before taking a seat, may leave a lobby or a running game, and keeps a seat across a reload. The host may clear a chair. The browser remembers every game it is in, not just the last one. - The start of a game is drawn: a handoff beat, an announcement, the code and type in the header. - Sound, the timetable flash, announcements and the just-drawn badge now reach a remote client; justDrawn goes to the seat that drew it and nobody else. - Played on StartOS, which found the rest: an Extra belongs to the player who played it, the board never named the Superintendent, bot seats were reported as absent players, and rule section numbers are out of every string a player reads. Also carries the previous session's Heavy Grade documentation work — asked again, answer unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016JczK5i33ZNSf2PtzZqdhS
This commit is contained in:
co-authored by
Claude Opus 5
parent
42adfda390
commit
06db36e5b5
+18
-3
@@ -266,8 +266,14 @@ function newTrainPhase(s: GameState, events: GameEvent[]): AdvanceResult {
|
||||
* chooser is the acting player; §7 says the player who PLAYED the card, which is the same person
|
||||
* in solitaire and needs `pendingExtras` to carry a player before it is not.
|
||||
*/
|
||||
if (s.pendingExtras.length > 0 && s.freeTrays.length > 0) {
|
||||
s.clock.currentActor = actorAt(s, s.clock.actorOffset % s.players.length);
|
||||
const extra = s.pendingExtras[0];
|
||||
if (extra !== undefined && s.freeTrays.length > 0) {
|
||||
// §7 — the player who PLAYED the card places it. `pendingExtras` carries them (2026-08-23);
|
||||
// before that it was a bare train number and this asked whoever the acting order was on, which
|
||||
// is the same person in solitaire and the wrong one at every table. Reported by Jesse from a
|
||||
// two-player game.
|
||||
if (s.clock.currentActor !== extra.player) events.push({ type: 'actorChanged', player: extra.player });
|
||||
s.clock.currentActor = extra.player;
|
||||
return { events, needsInput: true };
|
||||
}
|
||||
|
||||
@@ -285,7 +291,16 @@ function newTrainPhase(s: GameState, events: GameEvent[]): AdvanceResult {
|
||||
const filling = trainNeedingCars(s);
|
||||
if (filling) {
|
||||
const tray = s.trays.get(filling)!;
|
||||
const nextActor = actorAt(s, tray.consist.length % s.players.length);
|
||||
/**
|
||||
* TWO DIFFERENT RULES, and §7 states them a paragraph apart. A TIMETABLED train's consist is
|
||||
* built by the table — "starting with the Superintendent and working left, each player may place
|
||||
* ONE car" — while an EXTRA is loaded by the player who played it, "as he chooses". So an Extra
|
||||
* does not enter the round at all; it belongs to `builtBy` until it is full.
|
||||
*/
|
||||
const nextActor =
|
||||
tray.trainIsExtra && tray.builtBy !== undefined
|
||||
? tray.builtBy
|
||||
: actorAt(s, tray.consist.length % s.players.length);
|
||||
if (s.clock.currentActor !== nextActor) events.push({ type: 'actorChanged', player: nextActor });
|
||||
s.clock.currentActor = nextActor;
|
||||
return { events, needsInput: true };
|
||||
|
||||
Reference in New Issue
Block a user