diff --git a/src/core/game.ts b/src/core/game.ts index c8a9945..60c39f0 100644 --- a/src/core/game.ts +++ b/src/core/game.ts @@ -4,10 +4,10 @@ import {Region} from "./region"; import { Command, CommandRegistry, - type CommandRunner, CommandRunnerContext, + CommandRunnerContext, CommandRunnerContextExport, CommandSchema, createCommandRegistry, createCommandRunnerContext, parseCommandSchema, - PromptEvent + PromptEvent, registerCommand } from "../utils/command"; import {AsyncQueue} from "../utils/async-queue"; @@ -60,11 +60,12 @@ export function createGameCommandRegistry } export function createGameCommand = {} , TResult = unknown>( + registry: CommandRegistry>, schema: CommandSchema | string, run: (this: CommandRunnerContext>, command: Command) => Promise -): CommandRunner, TResult> { - return { +) { + registerCommand(registry, { schema: typeof schema === 'string' ? parseCommandSchema(schema) : schema, run, - }; -} + }); +} \ No newline at end of file diff --git a/src/samples/tic-tac-toe.ts b/src/samples/tic-tac-toe.ts index 20174d0..9f8cb3c 100644 --- a/src/samples/tic-tac-toe.ts +++ b/src/samples/tic-tac-toe.ts @@ -1,19 +1,66 @@ import {createGameCommand, createGameCommandRegistry, IGameContext} from '../core/game'; -import { registerCommand } from '../utils/command'; import type { Part } from '../core/part'; -type TurnResult = { - winner: 'X' | 'O' | 'draw' | null; -}; - +type PlayerType = 'X' | 'O'; +type WinnerType = 'X' | 'O' | 'draw' | null; export function createInitialState() { return { - currentPlayer: 'X' as 'X' | 'O', - winner: null as 'X' | 'O' | 'draw' | null, - moveCount: 0, + currentPlayer: 'O' as PlayerType, + winner: null as WinnerType, + turn: 0, }; } export type TicTacToeState = ReturnType; +export const registry = createGameCommandRegistry(); + +createGameCommand(registry, 'setup', async function() { + const {regions, state} = this.context; + regions.add({ + id: 'board', + axes: [ + { name: 'x', min: 0, max: 2 }, + { name: 'y', min: 0, max: 2 }, + ], + children: [], + }); + + while (true) { + let player = 'X' as PlayerType; + let turn = 0; + state.produce(state => { + player = state.currentPlayer = state.currentPlayer === 'X' ? 'O' : 'X'; + turn = ++state.turn; + }); + const turnOutput = await this.run<{winner: WinnerType}>(`turn ${player} ${turn}`); + if (!turnOutput.success) throw new Error(turnOutput.error); + + state.produce(state => { + state.winner = turnOutput.result.winner; + }); + if (state.value.winner) break; + } + + return state.value; +}); + +createGameCommand(registry, '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 : winner as WinnerType }; + + if (turnNumber >= 9) return { winner: 'draw' as WinnerType}; + } +}); export function getBoardRegion(host: IGameContext) { return host.regions.get('board'); @@ -70,59 +117,4 @@ export function placePiece(host: IGameContext, row: number, col: board.produce(draft => { draft.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 winner: 'X' | 'O' | 'draw' | null = null; - let turn = 1; - - while (true) { - const turnOutput = await this.run(`turn ${currentPlayer} ${turn++}`); - if (!turnOutput.success) throw new Error(turnOutput.error); - winner = turnOutput.result.winner; - if (winner) break; - - currentPlayer = currentPlayer === 'X' ? 'O' : 'X'; - } - - return { winner }; - } -) - -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 const registry = createGameCommandRegistry(); -registerCommand(registry, setup); -registerCommand(registry, turn); \ No newline at end of file +} \ No newline at end of file