refactor: update usage pattern for tic tac toe and boop
This commit is contained in:
parent
5f812a3478
commit
15122defcc
|
|
@ -17,6 +17,7 @@ type PlayerSupply = {
|
||||||
cat: PieceSupply;
|
cat: PieceSupply;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO refactor this into an Entity
|
||||||
function createPlayerSupply(): PlayerSupply {
|
function createPlayerSupply(): PlayerSupply {
|
||||||
return {
|
return {
|
||||||
kitten: { supply: MAX_PIECES_PER_PLAYER, placed: 0 },
|
kitten: { supply: MAX_PIECES_PER_PLAYER, placed: 0 },
|
||||||
|
|
@ -67,21 +68,32 @@ registration.add('setup', async function() {
|
||||||
|
|
||||||
registration.add('turn <player>', async function(cmd) {
|
registration.add('turn <player>', async function(cmd) {
|
||||||
const [turnPlayer] = cmd.params as [PlayerType];
|
const [turnPlayer] = cmd.params as [PlayerType];
|
||||||
const maxRetries = 50;
|
|
||||||
let retries = 0;
|
|
||||||
|
|
||||||
while (retries < maxRetries) {
|
const playCmd = await this.prompt(
|
||||||
retries++;
|
'play <player> <row:number> <col:number> [type:string]',
|
||||||
const playCmd = await this.prompt('play <player> <row:number> <col:number> [type:string]');
|
(command) => {
|
||||||
const [player, row, col, type] = playCmd.params as [PlayerType, number, number, PieceType?];
|
const [player, row, col, type] = command.params as [PlayerType, number, number, PieceType?];
|
||||||
const pieceType = type === 'cat' ? 'cat' : 'kitten';
|
const pieceType = type === 'cat' ? 'cat' : 'kitten';
|
||||||
|
|
||||||
if (player !== turnPlayer) continue;
|
if (player !== turnPlayer) {
|
||||||
if (!isValidMove(row, col)) continue;
|
return `Invalid player: ${player}. Expected ${turnPlayer}.`;
|
||||||
if (isCellOccupied(this.context, row, col)) continue;
|
}
|
||||||
|
if (!isValidMove(row, col)) {
|
||||||
|
return `Invalid position: (${row}, ${col}). Must be between 0 and ${BOARD_SIZE - 1}.`;
|
||||||
|
}
|
||||||
|
if (isCellOccupied(this.context, row, col)) {
|
||||||
|
return `Cell (${row}, ${col}) is already occupied.`;
|
||||||
|
}
|
||||||
|
|
||||||
const supply = this.context.value.players[player][pieceType].supply;
|
const supply = this.context.value.players[player][pieceType].supply;
|
||||||
if (supply <= 0) continue;
|
if (supply <= 0) {
|
||||||
|
return `No ${pieceType}s left in ${player}'s supply.`;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const [player, row, col, type] = playCmd.params as [PlayerType, number, number, PieceType?];
|
||||||
|
const pieceType = type === 'cat' ? 'cat' : 'kitten';
|
||||||
|
|
||||||
placePiece(this.context, row, col, turnPlayer, pieceType);
|
placePiece(this.context, row, col, turnPlayer, pieceType);
|
||||||
applyBoops(this.context, row, col, pieceType);
|
applyBoops(this.context, row, col, pieceType);
|
||||||
|
|
@ -95,9 +107,6 @@ registration.add('turn <player>', async function(cmd) {
|
||||||
if (winner) return { winner };
|
if (winner) return { winner };
|
||||||
|
|
||||||
return { winner: null };
|
return { winner: null };
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('Too many invalid attempts');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function isValidMove(row: number, col: number): boolean {
|
function isValidMove(row: number, col: number): boolean {
|
||||||
|
|
|
||||||
|
|
@ -61,18 +61,26 @@ registration.add('setup', async function() {
|
||||||
|
|
||||||
registration.add('turn <player> <turn:number>', async function(cmd) {
|
registration.add('turn <player> <turn:number>', async function(cmd) {
|
||||||
const [turnPlayer, turnNumber] = cmd.params as [PlayerType, number];
|
const [turnPlayer, turnNumber] = cmd.params as [PlayerType, number];
|
||||||
const maxRetries = MAX_TURNS * 2;
|
|
||||||
let retries = 0;
|
|
||||||
|
|
||||||
while (retries < maxRetries) {
|
const playCmd = await this.prompt(
|
||||||
retries++;
|
'play <player> <row:number> <col:number>',
|
||||||
const playCmd = await this.prompt('play <player> <row:number> <col:number>');
|
(command) => {
|
||||||
|
const [player, row, col] = command.params as [PlayerType, number, number];
|
||||||
|
|
||||||
|
if (player !== turnPlayer) {
|
||||||
|
return `Invalid player: ${player}. Expected ${turnPlayer}.`;
|
||||||
|
}
|
||||||
|
if (!isValidMove(row, col)) {
|
||||||
|
return `Invalid position: (${row}, ${col}). Must be between 0 and ${BOARD_SIZE - 1}.`;
|
||||||
|
}
|
||||||
|
if (isCellOccupied(this.context, row, col)) {
|
||||||
|
return `Cell (${row}, ${col}) is already occupied.`;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
const [player, row, col] = playCmd.params as [PlayerType, number, number];
|
const [player, row, col] = playCmd.params as [PlayerType, number, number];
|
||||||
|
|
||||||
if (player !== turnPlayer) continue;
|
|
||||||
if (!isValidMove(row, col)) continue;
|
|
||||||
if (isCellOccupied(this.context, row, col)) continue;
|
|
||||||
|
|
||||||
placePiece(this.context, row, col, turnPlayer);
|
placePiece(this.context, row, col, turnPlayer);
|
||||||
|
|
||||||
const winner = checkWinner(this.context);
|
const winner = checkWinner(this.context);
|
||||||
|
|
@ -80,9 +88,6 @@ registration.add('turn <player> <turn:number>', async function(cmd) {
|
||||||
if (turnNumber >= MAX_TURNS) return { winner: 'draw' as WinnerType };
|
if (turnNumber >= MAX_TURNS) return { winner: 'draw' as WinnerType };
|
||||||
|
|
||||||
return { winner: null };
|
return { winner: null };
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('Too many invalid attempts');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function isValidMove(row: number, col: number): boolean {
|
function isValidMove(row: number, col: number): boolean {
|
||||||
|
|
|
||||||
|
|
@ -498,14 +498,12 @@ describe('Boop - game flow', () => {
|
||||||
const runPromise = ctx.commands.run<{winner: WinnerType}>('turn white');
|
const runPromise = ctx.commands.run<{winner: WinnerType}>('turn white');
|
||||||
|
|
||||||
const promptEvent1 = await promptPromise;
|
const promptEvent1 = await promptPromise;
|
||||||
// 没有验证器,tryCommit 返回 null,但游戏逻辑会 continue 并重新 prompt
|
// 验证器会拒绝错误的玩家
|
||||||
const error1 = promptEvent1.tryCommit({ name: 'play', params: ['black', 2, 2], options: {}, flags: {} });
|
const error1 = promptEvent1.tryCommit({ name: 'play', params: ['black', 2, 2], options: {}, flags: {} });
|
||||||
expect(error1).toBeNull();
|
expect(error1).toContain('Invalid player');
|
||||||
|
|
||||||
const promptEvent2 = await waitForPrompt(ctx);
|
// 验证失败后,再次尝试有效输入
|
||||||
expect(promptEvent2).not.toBeNull();
|
const error2 = promptEvent1.tryCommit({ name: 'play', params: ['white', 2, 2], options: {}, flags: {} });
|
||||||
|
|
||||||
const error2 = promptEvent2.tryCommit({ name: 'play', params: ['white', 2, 2], options: {}, flags: {} });
|
|
||||||
expect(error2).toBeNull();
|
expect(error2).toBeNull();
|
||||||
|
|
||||||
const result = await runPromise;
|
const result = await runPromise;
|
||||||
|
|
@ -524,12 +522,10 @@ describe('Boop - game flow', () => {
|
||||||
|
|
||||||
const promptEvent1 = await promptPromise;
|
const promptEvent1 = await promptPromise;
|
||||||
const error1 = promptEvent1.tryCommit({ name: 'play', params: ['white', 2, 2], options: {}, flags: {} });
|
const error1 = promptEvent1.tryCommit({ name: 'play', params: ['white', 2, 2], options: {}, flags: {} });
|
||||||
expect(error1).toBeNull();
|
expect(error1).toContain('occupied');
|
||||||
|
|
||||||
const promptEvent2 = await waitForPrompt(ctx);
|
// 验证失败后,再次尝试有效输入
|
||||||
expect(promptEvent2).not.toBeNull();
|
const error2 = promptEvent1.tryCommit({ name: 'play', params: ['white', 0, 0], options: {}, flags: {} });
|
||||||
|
|
||||||
const error2 = promptEvent2.tryCommit({ name: 'play', params: ['white', 0, 0], options: {}, flags: {} });
|
|
||||||
expect(error2).toBeNull();
|
expect(error2).toBeNull();
|
||||||
|
|
||||||
const result = await runPromise;
|
const result = await runPromise;
|
||||||
|
|
@ -550,12 +546,10 @@ describe('Boop - game flow', () => {
|
||||||
|
|
||||||
const promptEvent1 = await promptPromise;
|
const promptEvent1 = await promptPromise;
|
||||||
const error1 = promptEvent1.tryCommit({ name: 'play', params: ['white', 0, 0], options: {}, flags: {} });
|
const error1 = promptEvent1.tryCommit({ name: 'play', params: ['white', 0, 0], options: {}, flags: {} });
|
||||||
expect(error1).toBeNull();
|
expect(error1).toContain('No kittens');
|
||||||
|
|
||||||
const promptEvent2 = await waitForPrompt(ctx);
|
// 验证失败后,取消
|
||||||
expect(promptEvent2).not.toBeNull();
|
promptEvent1.cancel('test end');
|
||||||
|
|
||||||
promptEvent2.cancel('test end');
|
|
||||||
|
|
||||||
const result = await runPromise;
|
const result = await runPromise;
|
||||||
expect(result.success).toBe(false);
|
expect(result.success).toBe(false);
|
||||||
|
|
@ -643,12 +637,10 @@ describe('Boop - game flow', () => {
|
||||||
|
|
||||||
const promptEvent1 = await promptPromise;
|
const promptEvent1 = await promptPromise;
|
||||||
const error1 = promptEvent1.tryCommit({ name: 'play', params: ['white', 0, 0, 'cat'], options: {}, flags: {} });
|
const error1 = promptEvent1.tryCommit({ name: 'play', params: ['white', 0, 0, 'cat'], options: {}, flags: {} });
|
||||||
expect(error1).toBeNull();
|
expect(error1).toContain('No cats');
|
||||||
|
|
||||||
const promptEvent2 = await waitForPrompt(ctx);
|
// 验证失败后,取消
|
||||||
expect(promptEvent2).not.toBeNull();
|
promptEvent1.cancel('test end');
|
||||||
|
|
||||||
promptEvent2.cancel('test end');
|
|
||||||
|
|
||||||
const result = await runPromise;
|
const result = await runPromise;
|
||||||
expect(result.success).toBe(false);
|
expect(result.success).toBe(false);
|
||||||
|
|
|
||||||
|
|
@ -240,14 +240,12 @@ describe('TicTacToe - game flow', () => {
|
||||||
const runPromise = ctx.commands.run<{winner: WinnerType}>('turn X 1');
|
const runPromise = ctx.commands.run<{winner: WinnerType}>('turn X 1');
|
||||||
|
|
||||||
const promptEvent1 = await promptPromise;
|
const promptEvent1 = await promptPromise;
|
||||||
// 没有验证器,tryCommit 返回 null,但游戏逻辑会 continue 并重新 prompt
|
// 验证器会拒绝错误的玩家
|
||||||
const error1 = promptEvent1.tryCommit({ name: 'play', params: ['O', 1, 1], options: {}, flags: {} });
|
const error1 = promptEvent1.tryCommit({ name: 'play', params: ['O', 1, 1], options: {}, flags: {} });
|
||||||
expect(error1).toBeNull();
|
expect(error1).toContain('Invalid player');
|
||||||
|
|
||||||
const promptEvent2 = await waitForPrompt(ctx);
|
// 验证失败后,再次尝试有效输入
|
||||||
expect(promptEvent2).not.toBeNull();
|
const error2 = promptEvent1.tryCommit({ name: 'play', params: ['X', 1, 1], options: {}, flags: {} });
|
||||||
|
|
||||||
const error2 = promptEvent2.tryCommit({ name: 'play', params: ['X', 1, 1], options: {}, flags: {} });
|
|
||||||
expect(error2).toBeNull();
|
expect(error2).toBeNull();
|
||||||
|
|
||||||
const result = await runPromise;
|
const result = await runPromise;
|
||||||
|
|
@ -266,12 +264,10 @@ describe('TicTacToe - game flow', () => {
|
||||||
|
|
||||||
const promptEvent1 = await promptPromise;
|
const promptEvent1 = await promptPromise;
|
||||||
const error1 = promptEvent1.tryCommit({ name: 'play', params: ['X', 1, 1], options: {}, flags: {} });
|
const error1 = promptEvent1.tryCommit({ name: 'play', params: ['X', 1, 1], options: {}, flags: {} });
|
||||||
expect(error1).toBeNull();
|
expect(error1).toContain('occupied');
|
||||||
|
|
||||||
const promptEvent2 = await waitForPrompt(ctx);
|
// 验证失败后,再次尝试有效输入
|
||||||
expect(promptEvent2).not.toBeNull();
|
const error2 = promptEvent1.tryCommit({ name: 'play', params: ['X', 0, 0], options: {}, flags: {} });
|
||||||
|
|
||||||
const error2 = promptEvent2.tryCommit({ name: 'play', params: ['X', 0, 0], options: {}, flags: {} });
|
|
||||||
expect(error2).toBeNull();
|
expect(error2).toBeNull();
|
||||||
|
|
||||||
const result = await runPromise;
|
const result = await runPromise;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue