import { IGameContext } from '../core/game'; import {CommandRegistry, CommandRunner, registerCommand} from '../utils/command'; import type { Part } from '../core/part'; import {createGameCommand} from "../core/game"; export type TicTacToeState = { currentPlayer: 'X' | 'O'; winner: 'X' | 'O' | 'draw' | null; moveCount: number; }; type TurnResult = { winner: 'X' | 'O' | 'draw' | null; }; export function getBoardRegion(host: IGameContext) { return host.regions.get('board'); } export function isCellOccupied(host: IGameContext, row: number, col: number): boolean { const board = getBoardRegion(host); return board.value.children.some( (child: { value: { position: number[] } }) => child.value.position[0] === row && child.value.position[1] === col ); } export function hasWinningLine(positions: number[][]): boolean { const lines = [ [[0, 0], [0, 1], [0, 2]], [[1, 0], [1, 1], [1, 2]], [[2, 0], [2, 1], [2, 2]], [[0, 0], [1, 0], [2, 0]], [[0, 1], [1, 1], [2, 1]], [[0, 2], [1, 2], [2, 2]], [[0, 0], [1, 1], [2, 2]], [[0, 2], [1, 1], [2, 0]], ]; return lines.some(line => line.every(([r, c]) => positions.some(([pr, pc]) => pr === r && pc === c) ) ); } export function checkWinner(host: IGameContext): 'X' | 'O' | 'draw' | null { const parts = Object.values(host.parts.collection.value).map((s: { value: Part }) => s.value); const xPositions = parts.filter((_: Part, i: number) => i % 2 === 0).map((p: Part) => p.position); const oPositions = parts.filter((_: Part, i: number) => i % 2 === 1).map((p: Part) => p.position); if (hasWinningLine(xPositions)) return 'X'; if (hasWinningLine(oPositions)) return 'O'; return null; } export function placePiece(host: IGameContext, row: number, col: number, moveCount: number) { const board = getBoardRegion(host); const piece: Part = { id: `piece-${moveCount}`, sides: 1, side: 0, region: board, position: [row, col], }; host.parts.add(piece); board.value.children.push(host.parts.get(piece.id)); } const setup = createGameCommand( 'setup', async function() { this.context.regions.add({ id: 'board', axes: [ { name: 'x', min: 0, max: 2 }, { name: 'y', min: 0, max: 2 }, ], children: [], }); let currentPlayer: 'X' | 'O' = 'X'; let turnResult: TurnResult | undefined; let turn = 1; while (true) { const turnOutput = await this.run(`turn ${currentPlayer} ${turn++}`); if (!turnOutput.success) throw new Error(turnOutput.error); turnResult = turnOutput?.result.winner; if (turnResult) break; currentPlayer = currentPlayer === 'X' ? 'O' : 'X'; } return { winner: turnResult }; } ) const turn = createGameCommand( 'turn ', async function(cmd) { const [turnPlayer, turnNumber] = cmd.params as [string, number]; while (true) { const playCmd = await this.prompt('play '); const [player, row, col] = playCmd.params as [string, number, number]; if(turnPlayer !== player) continue; if (isNaN(row) || isNaN(col) || row < 0 || row > 2 || col < 0 || col > 2) continue; if (isCellOccupied(this.context, row, col)) continue; placePiece(this.context, row, col, turnNumber); const winner = checkWinner(this.context); if (winner) return { winner }; if (turnNumber >= 9) return { winner: 'draw' as const }; } } ); export function registerTicTacToeCommands(registry: CommandRegistry) { registerCommand(registry, setup); registerCommand(registry, turn); }