diff --git a/src/apps/chess/__tests__/rules.test.ts b/src/apps/chess/__tests__/rules.test.ts index 3d4987fd..45a0810a 100644 --- a/src/apps/chess/__tests__/rules.test.ts +++ b/src/apps/chess/__tests__/rules.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest' import { + nameOf, glyph, describeResult, formatClock, @@ -126,3 +127,18 @@ describe('describeResult', () => { expect(describeResult({ ending: 'timeout' }, 'alice')).toBe('Draw: time ran out, and a bare king cannot win') }) }) + +// A bot's seat is named for its engine and strength; a player's id is +// already their name. +describe('nameOf', () => { + it('reads a bot’s seat as its engine and strength', () => { + expect(nameOf('stockfish@1500')).toBe('Stockfish 1500') + expect(nameOf('bouncy-coral-quokka-x9k2')).toBe('bouncy-coral-quokka-x9k2') + }) + + it('names a bot that won', () => { + expect(describeResult({ winner: 'stockfish@1500', winnerColor: 'white', ending: 'checkmate' }, 'alice')).toBe( + 'Stockfish 1500 won by checkmate' + ) + }) +}) diff --git a/src/apps/chess/components/ChessTable.tsx b/src/apps/chess/components/ChessTable.tsx index e4291a2a..2f1d6465 100644 --- a/src/apps/chess/components/ChessTable.tsx +++ b/src/apps/chess/components/ChessTable.tsx @@ -4,7 +4,7 @@ import { createPortal } from 'react-dom' import type { ChessTableActions } from '@/hooks/useChessTable' import felt from '@/apps/castle/components/CastleTable.module.css' import type { ChessColor, ChessView } from '../wire' -import { describeMove, describeResult, formatClock, glyph, lastMoveSquares, movesTo, pieceName, readBoard, squaresFor, targetsFrom } from '../rules' +import { describeMove, describeResult, formatClock, glyph, lastMoveSquares, movesTo, nameOf, pieceName, readBoard, squaresFor, targetsFrom } from '../rules' import styles from './ChessTable.module.css' import ScoreSheet from './ScoreSheet' @@ -18,7 +18,7 @@ export interface ChessTableProps { playerId: string connected: boolean view: ChessView - table: ChessTableActions & { opening: boolean } + table: ChessTableActions & { opening: boolean; seating: boolean } } // The clocks a starter can pick, as minutes + increment seconds. @@ -29,6 +29,15 @@ const CLOCKS: Record = [ + { elo: 1320, name: 'Beginner' }, + { elo: 1600, name: 'Casual' }, + { elo: 1900, name: 'Club' }, + { elo: 2300, name: 'Strong' }, + { elo: 3190, name: 'Full strength' } +] + const PROMOTIONS: Array<{ letter: string; name: string }> = [ { letter: 'q', name: 'Queen' }, { letter: 'r', name: 'Rook' }, @@ -64,9 +73,9 @@ const ClockRow = ({ view, seatId, color, you }: { view: ChessView; seatId: strin return (
- {you ? `${seatId} (you)` : seatId} · {color} + {you ? `${nameOf(seatId)} (you)` : nameOf(seatId)} · {color} - + {formatClock(ms)}
@@ -107,6 +116,7 @@ const ChessTable = ({ playerId, connected, view, table }: ChessTableProps) => { const [promoting, setPromoting] = useState<{ view: ChessView; from: string; to: string; moves: string[] } | null>(null) const [confirmResign, setConfirmResign] = useState(null) const [clockChoice, setClockChoice] = useState('3+2') + const [botElo, setBotElo] = useState(BOT_STRENGTHS[1].elo) const me = view.players.find(player => player.playerId === playerId) const myColor: ChessColor = me?.color ?? 'white' @@ -215,15 +225,15 @@ const ChessTable = ({ playerId, connected, view, table }: ChessTableProps) => { // A leave mid-game is that game's result; between games, the news. if (view.result?.ending === 'abandoned') return describeResult(view.result, playerId) const left = view.players.find(player => player.playerId !== playerId) - return `${left?.playerId ?? 'Your opponent'} left the table.` + return `${left === undefined ? 'Your opponent' : nameOf(left.playerId)} left the table.` } if (pendingPromotion !== null) return 'Choose a piece to promote to.' - if (!myTurn) return `${view.currentPlayerId ?? ''} to move.` + if (!myTurn) return `${nameOf(view.currentPlayerId ?? '')} to move.` // The move just made is the opponent's: say it, since the board only // shows it. const lastMove = view.moves[view.moves.length - 1] const lastMover = view.sideToMove === undefined ? undefined : view.players.find(player => player.color === other(view.sideToMove!)) - const played = lastMove !== undefined && lastMover !== undefined ? `${lastMover.playerId} played ${describeMove(lastMove)}. ` : '' + const played = lastMove !== undefined && lastMover !== undefined ? `${nameOf(lastMover.playerId)} played ${describeMove(lastMove)}. ` : '' return `${played}${view.inCheck ? 'Check. ' : ''}Your move.` })() @@ -255,9 +265,24 @@ const ChessTable = ({ playerId, connected, view, table }: ChessTableProps) => {
    {view.players.map(player => ( -
  • {player.playerId === playerId ? `${player.playerId} (you)` : player.playerId}
  • +
  • {player.playerId === playerId ? `${player.playerId} (you)` : nameOf(player.playerId)}
  • ))}
+ {/* Alone at the table, a bot can take the other chair. */} + {view.players.length === 1 && ( +
+ + +
+ )}