refactor: update api
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
createGameCommandRegistry, Part, createRegion, createPart, isCellOccupied as isCellOccupiedUtil,
|
||||
IGameContext, Command
|
||||
createGameCommandRegistry, Part, createRegion,
|
||||
IGameContext, createRegionAxis, GameModule
|
||||
} from 'boardgame-core';
|
||||
|
||||
const BOARD_SIZE = 3;
|
||||
@@ -18,14 +18,15 @@ const WINNING_LINES: number[][][] = [
|
||||
|
||||
export type PlayerType = 'X' | 'O';
|
||||
export type WinnerType = PlayerType | 'draw' | null;
|
||||
|
||||
export type TicTacToePart = Part<{ player: PlayerType }>;
|
||||
export type TicTacToeState = ReturnType<typeof createInitialState>;
|
||||
export type TicTacToeGame = IGameContext<TicTacToeState>;
|
||||
|
||||
export function createInitialState() {
|
||||
return {
|
||||
board: createRegion('board', [
|
||||
{ name: 'x', min: 0, max: BOARD_SIZE - 1 },
|
||||
{ name: 'y', min: 0, max: BOARD_SIZE - 1 },
|
||||
createRegionAxis('x', 0, BOARD_SIZE - 1),
|
||||
createRegionAxis('y', 0, BOARD_SIZE - 1),
|
||||
]),
|
||||
parts: {} as Record<string, TicTacToePart>,
|
||||
currentPlayer: 'X' as PlayerType,
|
||||
@@ -33,19 +34,16 @@ export function createInitialState() {
|
||||
turn: 0,
|
||||
};
|
||||
}
|
||||
export type TicTacToeState = ReturnType<typeof createInitialState>;
|
||||
export type TicTacToeGame = IGameContext<TicTacToeState>;
|
||||
export const registry = createGameCommandRegistry<TicTacToeState>();
|
||||
|
||||
async function setup(game: TicTacToeGame) {
|
||||
export async function start(game: TicTacToeGame) {
|
||||
while (true) {
|
||||
const currentPlayer = game.value.currentPlayer;
|
||||
const turnNumber = game.value.turn + 1;
|
||||
const turnOutput = await turnCommand(game, currentPlayer, turnNumber);
|
||||
if (!turnOutput.success) throw new Error(turnOutput.error);
|
||||
const turnOutput = await turn(game, currentPlayer, turnNumber);
|
||||
|
||||
game.produce((state: TicTacToeState) => {
|
||||
state.winner = turnOutput.result.winner;
|
||||
state.winner = turnOutput.winner;
|
||||
if (!state.winner) {
|
||||
state.currentPlayer = state.currentPlayer === 'X' ? 'O' : 'X';
|
||||
state.turn = turnNumber;
|
||||
@@ -56,14 +54,11 @@ async function setup(game: TicTacToeGame) {
|
||||
|
||||
return game.value;
|
||||
}
|
||||
registry.register('setup', setup);
|
||||
|
||||
async function turn(game: TicTacToeGame, turnPlayer: PlayerType, turnNumber: number) {
|
||||
async function handleTurn(game: TicTacToeGame, turnPlayer: PlayerType, turnNumber: number) {
|
||||
const {player, row, col} = await game.prompt(
|
||||
'play <player> <row:number> <col:number>',
|
||||
(command: Command) => {
|
||||
const [player, row, col] = command.params as [PlayerType, number, number];
|
||||
|
||||
(player: PlayerType, row: number, col: number) => {
|
||||
if (player !== turnPlayer) {
|
||||
throw `Invalid player: ${player}. Expected ${turnPlayer}.`;
|
||||
} else if (!isValidMove(row, col)) {
|
||||
@@ -85,14 +80,14 @@ async function turn(game: TicTacToeGame, turnPlayer: PlayerType, turnNumber: num
|
||||
|
||||
return { winner: null };
|
||||
}
|
||||
const turnCommand = registry.register('turn <player:string> <turnNumber:int>', turn);
|
||||
const turn = registry.register('turn <player:string> <turnNumber:int>', handleTurn);
|
||||
|
||||
function isValidMove(row: number, col: number): boolean {
|
||||
return !isNaN(row) && !isNaN(col) && row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE;
|
||||
}
|
||||
|
||||
export function isCellOccupied(host: TicTacToeGame, row: number, col: number): boolean {
|
||||
return isCellOccupiedUtil(host.value.parts, 'board', [row, col]);
|
||||
return host.value.board.partMap[`${row},${col}`] !== undefined;
|
||||
}
|
||||
|
||||
export function hasWinningLine(positions: number[][]): boolean {
|
||||
@@ -120,10 +115,10 @@ export function checkWinner(host: TicTacToeGame): WinnerType {
|
||||
export function placePiece(host: TicTacToeGame, row: number, col: number, player: PlayerType) {
|
||||
const board = host.value.board;
|
||||
const moveNumber = Object.keys(host.value.parts).length + 1;
|
||||
const piece = createPart<{ player: PlayerType }>(
|
||||
{ regionId: 'board', position: [row, col], player },
|
||||
`piece-${player}-${moveNumber}`
|
||||
);
|
||||
const piece = {
|
||||
regionId: 'board', position: [row, col], player,
|
||||
id: `piece-${player}-${moveNumber}`
|
||||
};
|
||||
host.produce((state: TicTacToeState) => {
|
||||
state.parts[piece.id] = piece;
|
||||
board.childIds.push(piece.id);
|
||||
@@ -131,13 +126,14 @@ export function placePiece(host: TicTacToeGame, row: number, col: number, player
|
||||
});
|
||||
}
|
||||
|
||||
export const gameModule = {
|
||||
export const gameModule: GameModule<TicTacToeState> = {
|
||||
registry,
|
||||
createInitialState,
|
||||
start
|
||||
};
|
||||
|
||||
export const commands = {
|
||||
export const prompts = {
|
||||
play: (player: PlayerType, row: number, col: number) => {
|
||||
return `play ${player} ${row} ${col}`;
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import Phaser from 'phaser';
|
||||
import type {TicTacToeState, TicTacToePart} from '@/game/tic-tac-toe';
|
||||
import { GameHostScene } from 'boardgame-phaser';
|
||||
import { spawnEffect, type Spawner } from 'boardgame-phaser';
|
||||
import {commands} from "@/game/tic-tac-toe";
|
||||
import {prompts} from "@/game/tic-tac-toe";
|
||||
|
||||
const CELL_SIZE = 120;
|
||||
const BOARD_OFFSET = { x: 100, y: 100 };
|
||||
@@ -61,7 +61,7 @@ export class GameScene extends GameHostScene<TicTacToeState> {
|
||||
if (this.state.winner) return;
|
||||
if (this.isCellOccupied(row, col)) return;
|
||||
|
||||
const cmd = commands.play(this.state.currentPlayer, row, col);
|
||||
const cmd = prompts.play(this.state.currentPlayer, row, col);
|
||||
const error = this.gameHost.onInput(cmd);
|
||||
if (error) {
|
||||
console.warn('Invalid move:', error);
|
||||
@@ -133,7 +133,7 @@ export class GameScene extends GameHostScene<TicTacToeState> {
|
||||
).setInteractive({ useHandCursor: true });
|
||||
|
||||
bg.on('pointerdown', () => {
|
||||
this.gameHost.setup('setup');
|
||||
this.gameHost.start();
|
||||
});
|
||||
|
||||
this.winnerOverlay.add(bg);
|
||||
|
||||
@@ -13,24 +13,13 @@ export default function App<TState extends Record<string, unknown>>(props: { gam
|
||||
|
||||
const scene = useComputed(() => new props.gameScene());
|
||||
|
||||
const handleReset = async () => {
|
||||
gameHost.value.gameHost.setup('setup').then(result => {
|
||||
if(!result.success) {
|
||||
console.error(result.error);
|
||||
}else{
|
||||
console.log('Game finished!', result.result);
|
||||
}
|
||||
});
|
||||
const handleReset = () => {
|
||||
gameHost.value.gameHost.start();
|
||||
};
|
||||
const label = useComputed(() => gameHost.value.gameHost.status.value === 'running' ? 'Restart' : 'Start');
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen">
|
||||
<div className="flex-1 relative">
|
||||
<PhaserGame>
|
||||
<PhaserScene sceneKey="GameScene" scene={scene.value} autoStart data={gameHost.value} />
|
||||
</PhaserGame>
|
||||
</div>
|
||||
<div className="p-4 bg-gray-100 border-t border-gray-200">
|
||||
<button
|
||||
onClick={handleReset}
|
||||
@@ -39,6 +28,11 @@ export default function App<TState extends Record<string, unknown>>(props: { gam
|
||||
{label}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 flex relative justify-center items-center">
|
||||
<PhaserGame>
|
||||
<PhaserScene sceneKey="GameScene" scene={scene.value} autoStart data={gameHost.value} />
|
||||
</PhaserGame>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user