refactor: transition to non child entity format
This commit is contained in:
+81
-111
@@ -1,4 +1,4 @@
|
||||
import {createGameCommandRegistry, Part, Entity, entity, RegionEntity} from '@/index';
|
||||
import {createGameCommandRegistry, Part, Entity, createRegion} from '@/index';
|
||||
|
||||
const BOARD_SIZE = 6;
|
||||
const MAX_PIECES_PER_PLAYER = 8;
|
||||
@@ -18,26 +18,15 @@ type Player = {
|
||||
cat: PieceSupply;
|
||||
};
|
||||
|
||||
type PlayerEntity = Entity<Player>;
|
||||
|
||||
function createPlayer(id: PlayerType): PlayerEntity {
|
||||
return entity<Player>(id, {
|
||||
id,
|
||||
kitten: { supply: MAX_PIECES_PER_PLAYER, placed: 0 },
|
||||
cat: { supply: 0, placed: 0 },
|
||||
});
|
||||
}
|
||||
type PlayerData = Record<PlayerType, Player>;
|
||||
|
||||
export function createInitialState() {
|
||||
return {
|
||||
board: new RegionEntity('board', {
|
||||
id: 'board',
|
||||
axes: [
|
||||
{ name: 'x', min: 0, max: BOARD_SIZE - 1 },
|
||||
{ name: 'y', min: 0, max: BOARD_SIZE - 1 },
|
||||
],
|
||||
children: [],
|
||||
}),
|
||||
board: createRegion('board', [
|
||||
{ name: 'x', min: 0, max: BOARD_SIZE - 1 },
|
||||
{ name: 'y', min: 0, max: BOARD_SIZE - 1 },
|
||||
]),
|
||||
pieces: [] as BoopPart[],
|
||||
currentPlayer: 'white' as PlayerType,
|
||||
winner: null as WinnerType,
|
||||
players: {
|
||||
@@ -46,26 +35,30 @@ export function createInitialState() {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createPlayer(id: PlayerType): Player {
|
||||
return {
|
||||
id,
|
||||
kitten: { supply: MAX_PIECES_PER_PLAYER, placed: 0 },
|
||||
cat: { supply: 0, placed: 0 },
|
||||
};
|
||||
}
|
||||
|
||||
export type BoopState = ReturnType<typeof createInitialState>;
|
||||
const registration = createGameCommandRegistry<BoopState>();
|
||||
export const registry = registration.registry;
|
||||
|
||||
// Player Entity helper functions
|
||||
export function getPlayer(host: Entity<BoopState>, player: PlayerType): PlayerEntity {
|
||||
export function getPlayer(host: Entity<BoopState>, player: PlayerType): Player {
|
||||
return host.value.players[player];
|
||||
}
|
||||
|
||||
export function decrementSupply(player: PlayerEntity, pieceType: PieceType) {
|
||||
player.produce(p => {
|
||||
p[pieceType].supply--;
|
||||
p[pieceType].placed++;
|
||||
});
|
||||
export function decrementSupply(player: Player, pieceType: PieceType) {
|
||||
player[pieceType].supply--;
|
||||
player[pieceType].placed++;
|
||||
}
|
||||
|
||||
export function incrementSupply(player: PlayerEntity, pieceType: PieceType, count?: number) {
|
||||
player.produce(p => {
|
||||
p[pieceType].supply += count ?? 1;
|
||||
});
|
||||
export function incrementSupply(player: Player, pieceType: PieceType, count?: number) {
|
||||
player[pieceType].supply += count ?? 1;
|
||||
}
|
||||
|
||||
registration.add('setup', async function() {
|
||||
@@ -106,8 +99,8 @@ registration.add('turn <player>', async function(cmd) {
|
||||
return `Cell (${row}, ${col}) is already occupied.`;
|
||||
}
|
||||
|
||||
const playerEntity = getPlayer(this.context, player);
|
||||
const supply = playerEntity.value[pieceType].supply;
|
||||
const playerData = getPlayer(this.context, player);
|
||||
const supply = playerData[pieceType].supply;
|
||||
if (supply <= 0) {
|
||||
return `No ${pieceType}s left in ${player}'s supply.`;
|
||||
}
|
||||
@@ -126,15 +119,9 @@ registration.add('turn <player>', async function(cmd) {
|
||||
}
|
||||
|
||||
if (countPiecesOnBoard(this.context, turnPlayer) >= MAX_PIECES_PER_PLAYER) {
|
||||
const board = getBoardRegion(this.context);
|
||||
const partsMap = board.partsMap.value;
|
||||
const availableKittens: Entity<BoopPart>[] = [];
|
||||
for (const key in partsMap) {
|
||||
const part = partsMap[key] as Entity<BoopPart>;
|
||||
if (part.value.player === turnPlayer && part.value.pieceType === 'kitten') {
|
||||
availableKittens.push(part);
|
||||
}
|
||||
}
|
||||
const availableKittens = this.context.value.pieces.filter(
|
||||
p => p.player === turnPlayer && p.pieceType === 'kitten'
|
||||
);
|
||||
|
||||
if (availableKittens.length > 0) {
|
||||
const graduateCmd = await this.prompt(
|
||||
@@ -142,16 +129,16 @@ registration.add('turn <player>', async function(cmd) {
|
||||
(command) => {
|
||||
const [row, col] = command.params as [number, number];
|
||||
const posKey = `${row},${col}`;
|
||||
const part = availableKittens.find(p => `${p.value.position[0]},${p.value.position[1]}` === posKey);
|
||||
const part = availableKittens.find(p => `${p.position[0]},${p.position[1]}` === posKey);
|
||||
if (!part) return `No kitten at (${row}, ${col}).`;
|
||||
return null;
|
||||
}
|
||||
);
|
||||
const [row, col] = graduateCmd.params as [number, number];
|
||||
const part = availableKittens.find(p => p.value.position[0] === row && p.value.position[1] === col)!;
|
||||
const part = availableKittens.find(p => p.position[0] === row && p.position[1] === col)!;
|
||||
removePieceFromBoard(this.context, part);
|
||||
const playerEntity = getPlayer(this.context, turnPlayer);
|
||||
incrementSupply(playerEntity, 'cat', 1);
|
||||
const playerData = getPlayer(this.context, turnPlayer);
|
||||
incrementSupply(playerData, 'cat', 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,44 +158,44 @@ export function getBoardRegion(host: Entity<BoopState>) {
|
||||
|
||||
export function isCellOccupied(host: Entity<BoopState>, row: number, col: number): boolean {
|
||||
const board = getBoardRegion(host);
|
||||
return board.partsMap.value[`${row},${col}`] !== undefined;
|
||||
return board.partMap[`${row},${col}`] !== undefined;
|
||||
}
|
||||
|
||||
export function getPartAt(host: Entity<BoopState>, row: number, col: number): Entity<BoopPart> | null {
|
||||
export function getPartAt(host: Entity<BoopState>, row: number, col: number): BoopPart | null {
|
||||
const board = getBoardRegion(host);
|
||||
return (board.partsMap.value[`${row},${col}`] as Entity<BoopPart> | undefined) || null;
|
||||
const partId = board.partMap[`${row},${col}`];
|
||||
if (!partId) return null;
|
||||
return host.value.pieces.find(p => p.id === partId) || null;
|
||||
}
|
||||
|
||||
export function placePiece(host: Entity<BoopState>, row: number, col: number, player: PlayerType, pieceType: PieceType) {
|
||||
const board = getBoardRegion(host);
|
||||
const playerEntity = getPlayer(host, player);
|
||||
const count = playerEntity.value[pieceType].placed + 1;
|
||||
const playerData = getPlayer(host, player);
|
||||
const count = playerData[pieceType].placed + 1;
|
||||
|
||||
const piece: BoopPart = {
|
||||
id: `${player}-${pieceType}-${count}`,
|
||||
region: board,
|
||||
regionId: 'board',
|
||||
position: [row, col],
|
||||
player,
|
||||
pieceType,
|
||||
};
|
||||
host.produce(s => {
|
||||
const e = entity(piece.id, piece);
|
||||
board.produce(draft => {
|
||||
draft.children.push(e);
|
||||
});
|
||||
s.pieces.push(piece);
|
||||
board.childIds.push(piece.id);
|
||||
board.partMap[`${row},${col}`] = piece.id;
|
||||
});
|
||||
decrementSupply(playerEntity, pieceType);
|
||||
decrementSupply(playerData, pieceType);
|
||||
}
|
||||
|
||||
export function applyBoops(host: Entity<BoopState>, placedRow: number, placedCol: number, placedType: PieceType) {
|
||||
const board = getBoardRegion(host);
|
||||
const partsMap = board.partsMap.value;
|
||||
const pieces = host.value.pieces;
|
||||
|
||||
const piecesToBoop: { part: Entity<BoopPart>; dr: number; dc: number }[] = [];
|
||||
const piecesToBoop: { part: BoopPart; dr: number; dc: number }[] = [];
|
||||
|
||||
for (const key in partsMap) {
|
||||
const part = partsMap[key] as Entity<BoopPart>;
|
||||
const [r, c] = part.value.position;
|
||||
for (const part of pieces) {
|
||||
const [r, c] = part.position;
|
||||
if (r === placedRow && c === placedCol) continue;
|
||||
|
||||
const dr = Math.sign(r - placedRow);
|
||||
@@ -216,7 +203,7 @@ export function applyBoops(host: Entity<BoopState>, placedRow: number, placedCol
|
||||
|
||||
if (Math.abs(r - placedRow) <= 1 && Math.abs(c - placedCol) <= 1) {
|
||||
const booperIsKitten = placedType === 'kitten';
|
||||
const targetIsCat = part.value.pieceType === 'cat';
|
||||
const targetIsCat = part.pieceType === 'cat';
|
||||
|
||||
if (booperIsKitten && targetIsCat) continue;
|
||||
|
||||
@@ -225,36 +212,38 @@ export function applyBoops(host: Entity<BoopState>, placedRow: number, placedCol
|
||||
}
|
||||
|
||||
for (const { part, dr, dc } of piecesToBoop) {
|
||||
const [r, c] = part.value.position;
|
||||
const [r, c] = part.position;
|
||||
const newRow = r + dr;
|
||||
const newCol = c + dc;
|
||||
|
||||
if (newRow < 0 || newRow >= BOARD_SIZE || newCol < 0 || newCol >= BOARD_SIZE) {
|
||||
const pt = part.value.pieceType;
|
||||
const pl = part.value.player;
|
||||
const playerEntity = getPlayer(host, pl);
|
||||
const pt = part.pieceType;
|
||||
const pl = part.player;
|
||||
const playerData = getPlayer(host, pl);
|
||||
removePieceFromBoard(host, part);
|
||||
incrementSupply(playerEntity, pt);
|
||||
incrementSupply(playerData, pt);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isCellOccupied(host, newRow, newCol)) continue;
|
||||
|
||||
part.produce(p => {
|
||||
p.position = [newRow, newCol];
|
||||
});
|
||||
part.position = [newRow, newCol];
|
||||
board.partMap = Object.fromEntries(
|
||||
board.childIds.map(id => {
|
||||
const p = pieces.find(x => x.id === id)!;
|
||||
return [p.position.join(','), id];
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function removePieceFromBoard(host: Entity<BoopState>, part: Entity<BoopPart>) {
|
||||
export function removePieceFromBoard(host: Entity<BoopState>, part: BoopPart) {
|
||||
const board = getBoardRegion(host);
|
||||
const playerEntity = getPlayer(host, part.value.player);
|
||||
board.produce(draft => {
|
||||
draft.children = draft.children.filter(p => p.id !== part.id);
|
||||
});
|
||||
playerEntity.produce(p => {
|
||||
p[part.value.pieceType].placed--;
|
||||
});
|
||||
const playerData = getPlayer(host, part.player);
|
||||
board.childIds = board.childIds.filter(id => id !== part.id);
|
||||
delete board.partMap[part.position.join(',')];
|
||||
host.value.pieces = host.value.pieces.filter(p => p.id !== part.id);
|
||||
playerData[part.pieceType].placed--;
|
||||
}
|
||||
|
||||
const DIRECTIONS: [number, number][] = [
|
||||
@@ -309,14 +298,12 @@ export function hasWinningLine(positions: number[][]): boolean {
|
||||
}
|
||||
|
||||
export function checkGraduation(host: Entity<BoopState>, player: PlayerType): number[][][] {
|
||||
const board = getBoardRegion(host);
|
||||
const partsMap = board.partsMap.value;
|
||||
const pieces = host.value.pieces;
|
||||
const posSet = new Set<string>();
|
||||
|
||||
for (const key in partsMap) {
|
||||
const part = partsMap[key] as Entity<BoopPart>;
|
||||
if (part.value.player === player && part.value.pieceType === 'kitten') {
|
||||
posSet.add(`${part.value.position[0]},${part.value.position[1]}`);
|
||||
for (const part of pieces) {
|
||||
if (part.player === player && part.pieceType === 'kitten') {
|
||||
posSet.add(`${part.position[0]},${part.position[1]}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,48 +325,31 @@ export function processGraduation(host: Entity<BoopState>, player: PlayerType, l
|
||||
}
|
||||
|
||||
const board = getBoardRegion(host);
|
||||
const partsMap = board.partsMap.value;
|
||||
const partsToRemove: Entity<BoopPart>[] = [];
|
||||
|
||||
for (const key in partsMap) {
|
||||
const part = partsMap[key] as Entity<BoopPart>;
|
||||
if (part.value.player === player && part.value.pieceType === 'kitten' && allPositions.has(`${part.value.position[0]},${part.value.position[1]}`)) {
|
||||
partsToRemove.push(part);
|
||||
}
|
||||
}
|
||||
const partsToRemove = host.value.pieces.filter(
|
||||
p => p.player === player && p.pieceType === 'kitten' && allPositions.has(`${p.position[0]},${p.position[1]}`)
|
||||
);
|
||||
|
||||
for (const part of partsToRemove) {
|
||||
removePieceFromBoard(host, part);
|
||||
}
|
||||
|
||||
const count = partsToRemove.length;
|
||||
const playerEntity = getPlayer(host, player);
|
||||
incrementSupply(playerEntity, 'cat', count);
|
||||
const playerData = getPlayer(host, player);
|
||||
incrementSupply(playerData, 'cat', count);
|
||||
}
|
||||
|
||||
export function countPiecesOnBoard(host: Entity<BoopState>, player: PlayerType): number {
|
||||
const board = getBoardRegion(host);
|
||||
const partsMap = board.partsMap.value;
|
||||
let count = 0;
|
||||
for (const key in partsMap) {
|
||||
const part = partsMap[key] as Entity<BoopPart>;
|
||||
if (part.value.player === player) count++;
|
||||
}
|
||||
return count;
|
||||
const pieces = host.value.pieces;
|
||||
return pieces.filter(p => p.player === player).length;
|
||||
}
|
||||
|
||||
export function checkWinner(host: Entity<BoopState>): WinnerType {
|
||||
const board = getBoardRegion(host);
|
||||
const partsMap = board.partsMap.value;
|
||||
const pieces = host.value.pieces;
|
||||
|
||||
for (const player of ['white', 'black'] as PlayerType[]) {
|
||||
const positions: number[][] = [];
|
||||
for (const key in partsMap) {
|
||||
const part = partsMap[key] as Entity<BoopPart>;
|
||||
if (part.value.player === player && part.value.pieceType === 'cat') {
|
||||
positions.push(part.value.position);
|
||||
}
|
||||
}
|
||||
const positions = pieces
|
||||
.filter(p => p.player === player && p.pieceType === 'cat')
|
||||
.map(p => p.position);
|
||||
if (hasWinningLine(positions)) return player;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user