Files
station-master-startos/startos/actions/gamesInProgress.ts
T
Jesse 5bd06af932 Bundle Station Master v0.5.6, and count seats from 1 in Games in Progress
Pin moves to v0.5.6, package version to 0.5.6:0.

One packaging change of its own: Games in Progress reported "seat 0" while
the game's own screens now say "Seat 1", so an administrator and a player
would have been describing different chairs. Same +1, same reason.

Verified on phoenix.local as an update from 0.5.5:0. The served client
carries all three of the release's fixes: the lobby's seat numbers go
through seatLabel, .bs-name.bs-turn has lost the font-weight that made the
current player's name unreadable at 11px, and the west-to-east seating line
is gated to Day 1 Stage 1.
2026-08-21 21:01:24 -04:00

93 lines
3.1 KiB
TypeScript

import { i18n } from '../i18n'
import { sdk } from '../sdk'
import { listGames } from '../serverApi'
import type { GameRow } from '../serverApi'
/** "3 Aug 2026, 14:32" — a timestamp a person reads, not an epoch they decode. */
function when(ms: number): string {
return new Date(ms).toLocaleString('en-GB', {
dateStyle: 'medium',
timeStyle: 'short',
})
}
function describe(g: GameRow): string {
const who = g.playerNames.join(', ')
if (g.state === 'lobby') {
return [
i18n('Waiting to start — nobody has played a turn yet.'),
`${i18n('Seats')} (${g.playerCount}): ${who}`,
`${i18n('Created')}: ${when(g.createdAt)}`,
].join('\n')
}
const waiting =
g.status === 'finished'
? i18n('Finished — this game is over and waits on nobody.')
: g.waitingOn
? // +1 for the same reason the game's own screens do it: seats are zero-based internally
// and one-based to a person, and an administrator reading "seat 0" against a player
// reading "Seat 1" is two people describing different chairs.
`${i18n('Waiting on')}: ${g.waitingOn.name} (${i18n('seat')} ${g.waitingOn.seat + 1})`
: `${i18n('Waiting on')}: ${i18n('nobody — the Mainline Phase is running itself')}`
return [
`${i18n('Players')} (${g.playerCount}): ${who}`,
`${i18n('Day')} ${g.day}, ${i18n('Stage')} ${g.stage} — ${g.phase}`,
waiting,
`${i18n('Started')}: ${when(g.createdAt)}`,
`${i18n('Last move')}: ${g.lastMoveAt === undefined ? i18n('unknown') : when(g.lastMoveAt)}`,
].join('\n')
}
/**
* Read-only, and separate from Manage Game on purpose: this is the one you run without thinking,
* so it must not be the one that can delete anything.
*
* `only-running` because none of this exists on disk in readable form. A save is the seed plus the
* intents, so "whose turn is it" is only answerable by replaying the game through the engine —
* which lives in the game repo, not in this package. The running server has already done that work
* and is asked for the answer.
*/
export const gamesInProgress = sdk.Action.withoutInput(
'games-in-progress',
async () => ({
name: i18n('Games in Progress'),
description: i18n(
'List every game and lobby on this server, and where each one has got to',
),
warning: null,
allowedStatuses: 'only-running',
group: null,
visibility: 'enabled',
}),
async ({ effects }) => {
const games = await listGames(effects)
if (games.length === 0) {
return {
version: '1',
title: i18n('Games in Progress'),
message: i18n('There are no games on this server.'),
result: null,
}
}
return {
version: '1',
title: i18n('Games in Progress'),
message: `${games.length} ${i18n('game(s) on this server. Use Manage Game to save one for later, or to end it.')}`,
result: {
type: 'group',
value: games.map((g) => ({
type: 'single' as const,
name: g.gameCode ?? g.gameId,
description: null,
value: describe(g),
masked: false,
copyable: false,
qr: false,
})),
},
}
},
)