fix: api change for bop
This commit is contained in:
parent
61857b8256
commit
de4e83e4ea
|
|
@ -1,14 +1,15 @@
|
|||
import {
|
||||
BOARD_SIZE,
|
||||
BoopState,
|
||||
BoopPart,
|
||||
PieceType,
|
||||
PlayerType,
|
||||
WinnerType,
|
||||
WIN_LENGTH,
|
||||
MAX_PIECES_PER_PLAYER, BoopGame
|
||||
MAX_PIECES_PER_PLAYER,
|
||||
BoopGame,
|
||||
} from "./data";
|
||||
import {createGameCommandRegistry} from "@/core/game";
|
||||
import {moveToRegion} from "@/core/region";
|
||||
import {createGameCommandRegistry, Command, moveToRegion} from "boardgame-core";
|
||||
import {
|
||||
findPartAtPosition,
|
||||
findPartInRegion,
|
||||
|
|
@ -34,7 +35,7 @@ async function place(game: BoopGame, row: number, col: number, player: PlayerTyp
|
|||
|
||||
const partId = part.id;
|
||||
|
||||
await game.produceAsync(state => {
|
||||
await game.produceAsync((state: BoopState) => {
|
||||
// 将棋子从supply移动到棋盘
|
||||
const part = state.pieces[partId];
|
||||
moveToRegion(part, state.regions[player], state.regions.board, [row, col]);
|
||||
|
|
@ -50,7 +51,7 @@ const placeCommand = registry.register( 'place <row:number> <col:number> <player
|
|||
async function boop(game: BoopGame, row: number, col: number, type: PieceType) {
|
||||
const booped: string[] = [];
|
||||
|
||||
await game.produceAsync(state => {
|
||||
await game.produceAsync((state: BoopState) => {
|
||||
// 按照远离放置位置的方向推动
|
||||
for (const [dr, dc] of getNeighborPositions()) {
|
||||
const nr = row + dr;
|
||||
|
|
@ -133,7 +134,7 @@ async function checkGraduates(game: BoopGame){
|
|||
}
|
||||
}
|
||||
|
||||
await game.produceAsync(state => {
|
||||
await game.produceAsync((state: BoopState) => {
|
||||
for(const partId of toUpgrade){
|
||||
const part = state.pieces[partId];
|
||||
const [row, col] = part.position;
|
||||
|
|
@ -153,7 +154,7 @@ async function setup(game: BoopGame) {
|
|||
const turnOutput = await turnCommand(game, currentPlayer);
|
||||
if (!turnOutput.success) throw new Error(turnOutput.error);
|
||||
|
||||
await game.produceAsync(state => {
|
||||
await game.produceAsync((state: BoopState) => {
|
||||
state.winner = turnOutput.result.winner;
|
||||
if (!state.winner) {
|
||||
state.currentPlayer = state.currentPlayer === 'white' ? 'black' : 'white';
|
||||
|
|
@ -169,15 +170,15 @@ registry.register('setup', setup);
|
|||
async function checkFullBoard(game: BoopGame, turnPlayer: PlayerType){
|
||||
// 检查8-piece规则: 如果玩家所有8个棋子都在棋盘上且没有获胜,强制升级一个小猫
|
||||
const playerPieces = Object.values(game.value.pieces).filter(
|
||||
p => p.player === turnPlayer && p.regionId === 'board'
|
||||
(p: BoopPart) => p.player === turnPlayer && p.regionId === 'board'
|
||||
);
|
||||
if(playerPieces.length < MAX_PIECES_PER_PLAYER || game.value.winner !== null){
|
||||
return;
|
||||
}
|
||||
|
||||
const partId = await game.prompt(
|
||||
'choose <player> <row:number> <col:number>',
|
||||
(command) => {
|
||||
'play <player> <row:number> <col:number> [type:string]',
|
||||
(command: Command) => {
|
||||
const [player, row, col] = command.params as [PlayerType, number, number];
|
||||
if (player !== turnPlayer) {
|
||||
throw `Invalid player: ${player}. Expected ${turnPlayer}.`;
|
||||
|
|
@ -195,7 +196,7 @@ async function checkFullBoard(game: BoopGame, turnPlayer: PlayerType){
|
|||
}
|
||||
);
|
||||
|
||||
await game.produceAsync(state => {
|
||||
await game.produceAsync((state: BoopState) => {
|
||||
const part = state.pieces[partId];
|
||||
moveToRegion(part, state.regions.board, null);
|
||||
const cat = findPartInRegion(state, '', 'cat');
|
||||
|
|
@ -206,7 +207,7 @@ async function checkFullBoard(game: BoopGame, turnPlayer: PlayerType){
|
|||
async function turn(game: BoopGame, turnPlayer: PlayerType) {
|
||||
const {row, col, type} = await game.prompt(
|
||||
'play <player> <row:number> <col:number> [type:string]',
|
||||
(command) => {
|
||||
(command: Command) => {
|
||||
const [player, row, col, type] = command.params as [PlayerType, number, number, PieceType?];
|
||||
const pieceType = type === 'cat' ? 'cat' : 'kitten';
|
||||
|
||||
|
|
@ -240,3 +241,8 @@ async function turn(game: BoopGame, turnPlayer: PlayerType) {
|
|||
return { winner: null };
|
||||
}
|
||||
const turnCommand = registry.register('turn <player>', turn);
|
||||
export const commands = {
|
||||
play: (player: PlayerType, row: number, col: number, type: PieceType) => {
|
||||
return `play ${player} ${row} ${col} ${type}`;
|
||||
}
|
||||
};
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import parts from './parts.csv';
|
||||
import {createRegion, moveToRegion, Region} from "@/core/region";
|
||||
import {createPartsFromTable} from "@/core/part-factory";
|
||||
import {Part} from "@/core/part";
|
||||
import {IGameContext} from "@/core/game";
|
||||
import {createRegion, moveToRegion, Region} from "boardgame-core";
|
||||
import {createPartsFromTable} from "boardgame-core";
|
||||
import {Part} from "boardgame-core";
|
||||
import {IGameContext} from "boardgame-core";
|
||||
|
||||
export const BOARD_SIZE = 6;
|
||||
export const MAX_PIECES_PER_PLAYER = 8;
|
||||
|
|
@ -18,8 +18,8 @@ export type BoopPart = Part<BoopPartMeta>;
|
|||
export function createInitialState() {
|
||||
const pieces = createPartsFromTable(
|
||||
parts,
|
||||
(item, index) => `${item.player}-${item.type}-${index + 1}`,
|
||||
(item) => item.count
|
||||
(item: {player: string, type: string}, index: number) => `${item.player}-${item.type}-${index + 1}`,
|
||||
(item: {count: number}) => item.count
|
||||
) as Record<string, BoopPart>;
|
||||
|
||||
// Initialize region childIds
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
PlayerType,
|
||||
RegionType,
|
||||
WIN_LENGTH
|
||||
} from "@/samples/boop/data";
|
||||
} from "./data";
|
||||
|
||||
const DIRS = [
|
||||
[0, 1],
|
||||
|
|
@ -55,7 +55,7 @@ export function findPartInRegion(ctx: BoopGame | BoopState, regionId: keyof Boop
|
|||
if(!regionId){
|
||||
return Object.values(state.pieces).find(part => match(regionId, part, type, player)) || null;
|
||||
}
|
||||
const id = state.regions[regionId].childIds.find(id => match(regionId, state.pieces[id], type, player));
|
||||
const id = state.regions[regionId].childIds.find((id: string) => match(regionId, state.pieces[id], type, player));
|
||||
return id ? state.pieces[id] || null : null;
|
||||
}
|
||||
function match(regionId: RegionType, part: BoopPart, type: PieceType, player?: PlayerType){
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import Phaser from 'phaser';
|
||||
import type { BoopState, PlayerType } from '@/game';
|
||||
import type { ReadonlySignal } from '@preact/signals-core';
|
||||
import type { BoopState, PlayerType, BoopPart } from '@/game';
|
||||
|
||||
const BOARD_SIZE = 6;
|
||||
const CELL_SIZE = 80;
|
||||
|
|
@ -70,18 +69,29 @@ export class BoardRenderer {
|
|||
}).setOrigin(0.5);
|
||||
}
|
||||
|
||||
private countPieces(state: BoopState, player: PlayerType) {
|
||||
const pieces = Object.values(state.pieces);
|
||||
const playerPieces = pieces.filter((p: BoopPart) => p.player === player);
|
||||
|
||||
const kittensInSupply = playerPieces.filter((p: BoopPart) => p.type === 'kitten' && p.regionId === player).length;
|
||||
const catsInSupply = playerPieces.filter((p: BoopPart) => p.type === 'cat' && p.regionId === player).length;
|
||||
const piecesOnBoard = playerPieces.filter((p: BoopPart) => p.regionId === 'board').length;
|
||||
|
||||
return { kittensInSupply, catsInSupply, piecesOnBoard };
|
||||
}
|
||||
|
||||
updateTurnText(player: PlayerType, state: BoopState): void {
|
||||
const current = player === 'white' ? state.players.white : state.players.black;
|
||||
const catsAvailable = current.catPool.remaining() + current.graduatedCount;
|
||||
const { kittensInSupply, catsInSupply } = this.countPieces(state, player);
|
||||
|
||||
this.turnText.setText(
|
||||
`${player.toUpperCase()}'s turn | Kittens: ${current.kittenPool.remaining()} | Cats: ${catsAvailable}`
|
||||
`${player.toUpperCase()}'s turn | Kittens: ${kittensInSupply} | Cats: ${catsInSupply}`
|
||||
);
|
||||
}
|
||||
|
||||
setupInput(
|
||||
state: ReadonlySignal<BoopState>,
|
||||
onCellClick: (row: number, col: number) => void
|
||||
getState: () => BoopState,
|
||||
onCellClick: (row: number, col: number) => void,
|
||||
checkWinner: () => boolean
|
||||
): void {
|
||||
for (let row = 0; row < BOARD_SIZE; row++) {
|
||||
for (let col = 0; col < BOARD_SIZE; col++) {
|
||||
|
|
@ -91,8 +101,9 @@ export class BoardRenderer {
|
|||
const zone = this.scene.add.zone(x, y, CELL_SIZE, CELL_SIZE).setInteractive();
|
||||
|
||||
zone.on('pointerdown', () => {
|
||||
const isOccupied = !!state.value.board.partMap[`${row},${col}`];
|
||||
if (!isOccupied && !state.value.winner) {
|
||||
const state = getState();
|
||||
const isOccupied = !!state.regions.board.partMap[`${row},${col}`];
|
||||
if (!isOccupied && !checkWinner()) {
|
||||
onCellClick(row, col);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -30,9 +30,11 @@ export class GameScene extends GameHostScene<BoopState> {
|
|||
this.disposables.add(createPieceSpawner(this));
|
||||
|
||||
// 设置输入处理
|
||||
this.boardRenderer.setupInput(this.gameHost.state, (row, col) => {
|
||||
this.handleCellClick(row, col);
|
||||
});
|
||||
this.boardRenderer.setupInput(
|
||||
() => this.state,
|
||||
(row, col) => this.handleCellClick(row, col),
|
||||
() => !!this.state.winner
|
||||
);
|
||||
|
||||
// 监听状态变化
|
||||
this.addEffect(() => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Phaser from 'phaser';
|
||||
import type { BoopState, BoopPart } from '@/game/boop';
|
||||
import type { BoopState, BoopPart } from '@/game';
|
||||
import { GameHostScene, spawnEffect, type Spawner } from 'boardgame-phaser';
|
||||
import { BOARD_OFFSET, CELL_SIZE } from './BoardRenderer';
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ class BoopPartSpawner implements Spawner<BoopPart, Phaser.GameObjects.Container>
|
|||
|
||||
const container = this.scene.add.container(x, y);
|
||||
|
||||
const isCat = part.pieceType === 'cat';
|
||||
const isCat = part.type === 'cat';
|
||||
const baseColor = part.player === 'white' ? 0xffffff : 0x333333;
|
||||
const strokeColor = part.player === 'white' ? 0x000000 : 0xffffff;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Phaser from 'phaser';
|
||||
import type { BoopState, PlayerType, PieceType } from '@/game/boop';
|
||||
import type { BoopState, PlayerType, PieceType, BoopPart } from '@/game';
|
||||
import { BOARD_OFFSET, CELL_SIZE, BOARD_SIZE } from './BoardRenderer';
|
||||
|
||||
export class PieceTypeSelector {
|
||||
|
|
@ -58,23 +58,36 @@ export class PieceTypeSelector {
|
|||
return this.selectedType;
|
||||
}
|
||||
|
||||
private countPieces(state: BoopState, player: PlayerType) {
|
||||
const pieces = Object.values(state.pieces);
|
||||
const playerPieces = pieces.filter((p: BoopPart) => p.player === player);
|
||||
|
||||
const kittensInSupply = playerPieces.filter((p: BoopPart) => p.type === 'kitten' && p.regionId === player).length;
|
||||
const catsInSupply = playerPieces.filter((p: BoopPart) => p.type === 'cat' && p.regionId === player).length;
|
||||
const catsOnBoard = playerPieces.filter((p: BoopPart) => p.type === 'cat' && p.regionId === 'board').length;
|
||||
|
||||
return { kittensInSupply, catsInSupply, catsOnBoard };
|
||||
}
|
||||
|
||||
update(state: BoopState): void {
|
||||
const currentPlayer = state.players[state.currentPlayer];
|
||||
const kittenAvailable = currentPlayer.kittenPool.remaining() > 0;
|
||||
const catsAvailable = currentPlayer.catPool.remaining() + currentPlayer.graduatedCount > 0;
|
||||
const currentPlayer = state.currentPlayer;
|
||||
const { kittensInSupply, catsInSupply, catsOnBoard } = this.countPieces(state, currentPlayer);
|
||||
|
||||
const kittenAvailable = kittensInSupply > 0;
|
||||
const catsAvailable = catsInSupply + catsOnBoard > 0;
|
||||
|
||||
this.updateButton(
|
||||
this.kittenButton,
|
||||
kittenAvailable,
|
||||
this.selectedType === 'kitten',
|
||||
`🐾 小猫 (${currentPlayer.kittenPool.remaining()})`
|
||||
`🐾 小猫 (${kittensInSupply})`
|
||||
);
|
||||
|
||||
this.updateButton(
|
||||
this.catButton,
|
||||
catsAvailable,
|
||||
this.selectedType === 'cat',
|
||||
`🐱 大猫 (${currentPlayer.catPool.remaining() + currentPlayer.graduatedCount})`
|
||||
`🐱 大猫 (${catsInSupply + catsOnBoard})`
|
||||
);
|
||||
|
||||
// 自动切换到可用类型
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Phaser from 'phaser';
|
||||
import type { BoopState, PlayerType } from '@/game/boop';
|
||||
import type { BoopState, PlayerType, BoopPart } from '@/game';
|
||||
import { BOARD_OFFSET, CELL_SIZE, BOARD_SIZE } from './BoardRenderer';
|
||||
|
||||
export class SupplyUI {
|
||||
|
|
@ -39,19 +39,30 @@ export class SupplyUI {
|
|||
this.blackContainer.setDepth(100);
|
||||
}
|
||||
|
||||
update(state: BoopState): void {
|
||||
const white = state.players.white;
|
||||
const black = state.players.black;
|
||||
private countPieces(state: BoopState, player: PlayerType) {
|
||||
const pieces = Object.values(state.pieces);
|
||||
const playerPieces = pieces.filter((p: BoopPart) => p.player === player);
|
||||
|
||||
const whiteCatsAvailable = white.catPool.remaining() + white.graduatedCount;
|
||||
const blackCatsAvailable = black.catPool.remaining() + black.graduatedCount;
|
||||
const kittensInSupply = playerPieces.filter((p: BoopPart) => p.type === 'kitten' && p.regionId === player).length;
|
||||
const catsInSupply = playerPieces.filter((p: BoopPart) => p.type === 'cat' && p.regionId === player).length;
|
||||
const catsOnBoard = playerPieces.filter((p: BoopPart) => p.type === 'cat' && p.regionId === 'board').length;
|
||||
|
||||
return { kittensInSupply, catsInSupply, catsOnBoard };
|
||||
}
|
||||
|
||||
update(state: BoopState): void {
|
||||
const white = this.countPieces(state, 'white');
|
||||
const black = this.countPieces(state, 'black');
|
||||
|
||||
const whiteCatsAvailable = white.catsInSupply + white.catsOnBoard;
|
||||
const blackCatsAvailable = black.catsInSupply + black.catsOnBoard;
|
||||
|
||||
this.whiteText.setText(
|
||||
`⚪ WHITE\n🐾 ${white.kittenPool.remaining()} | 🐱 ${whiteCatsAvailable}`
|
||||
`⚪ WHITE\n🐾 ${white.kittensInSupply} | 🐱 ${whiteCatsAvailable}`
|
||||
);
|
||||
|
||||
this.blackText.setText(
|
||||
`⚫ BLACK\n🐾 ${black.kittenPool.remaining()} | 🐱 ${blackCatsAvailable}`
|
||||
`⚫ BLACK\n🐾 ${black.kittensInSupply} | 🐱 ${blackCatsAvailable}`
|
||||
);
|
||||
|
||||
this.updateHighlight(state.currentPlayer);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Phaser from 'phaser';
|
||||
import type { BoopState, WinnerType } from '@/game/boop';
|
||||
import type { BoopState, WinnerType } from '@/game';
|
||||
import { BOARD_OFFSET, CELL_SIZE, BOARD_SIZE } from './BoardRenderer';
|
||||
|
||||
export class WinnerOverlay {
|
||||
|
|
|
|||
Loading…
Reference in New Issue