mancala/packages/mancala.js/src/core/MancalaGame.ts

240 lines
6.7 KiB
TypeScript
Raw Normal View History

2022-05-02 00:42:59 +03:00
import { Board, PitType } from './Board';
import { GameRule } from './GameRule';
2022-05-08 16:59:35 +03:00
import { HistoryItem, MoveHistoryItem, GameStep } from './HistoryItem';
2022-05-02 00:42:59 +03:00
export type GameState = 'initial' | 'playing' | 'ended';
2022-05-08 16:59:35 +03:00
export const GAME_STEP_GAME_MOVE = 'GAME_STEP_GAME_MOVE';
2022-05-02 00:42:59 +03:00
export class MancalaGame {
2022-05-05 23:04:10 +03:00
id: string;
2022-05-02 00:42:59 +03:00
board: Board;
2022-05-04 15:04:41 +03:00
player1Id: string;
player2Id: string;
2022-05-02 00:42:59 +03:00
turnPlayerId: string;
state: GameState;
gameRules: GameRule[];
2022-05-05 23:50:26 +03:00
history: HistoryItem[];
2022-05-08 16:59:35 +03:00
currentHistoryItem: HistoryItem | null = null;
2022-05-02 00:42:59 +03:00
constructor(
2022-05-05 23:04:10 +03:00
id: string,
2022-05-02 00:42:59 +03:00
board: Board,
2022-05-04 15:04:41 +03:00
player1Id: string,
player2Id: string,
2022-05-02 00:42:59 +03:00
turnPlayerId: string,
2022-05-04 15:04:41 +03:00
gameRules: GameRule[],
2022-05-05 23:50:26 +03:00
history: HistoryItem[],
2022-05-04 15:04:41 +03:00
state: GameState = 'initial'
2022-05-02 00:42:59 +03:00
) {
2022-05-05 23:04:10 +03:00
this.id = id;
2022-05-02 00:42:59 +03:00
this.board = board;
2022-05-04 15:04:41 +03:00
this.player1Id = player1Id;
this.player2Id = player2Id;
2022-05-02 00:42:59 +03:00
this.turnPlayerId = turnPlayerId;
2022-05-04 15:04:41 +03:00
this.state = state;
2022-05-02 00:42:59 +03:00
this.gameRules = gameRules;
2022-05-05 23:50:26 +03:00
this.history = history;
2022-05-02 00:42:59 +03:00
this.listenBoardMoveEvents();
}
listenBoardMoveEvents() {
this.board.onGameMoveStart = (index: number) => {
this.gameRules.forEach((gameRule) => {
gameRule.onGameMoveStart(this, index);
});
};
this.board.onGameMove = (index: number) => {
2022-05-08 16:59:35 +03:00
this.addGameStep(new GameStep(index, GAME_STEP_GAME_MOVE));
2022-05-02 00:42:59 +03:00
this.gameRules.forEach((gameRule) => {
gameRule.onGameMove(this, index);
});
};
this.board.onGameMoveEnd = (index: number) => {
this.gameRules.forEach((gameRule) => {
gameRule.onGameMoveEnd(this, index);
});
};
}
changePlayerTurn() {
2022-05-04 15:04:41 +03:00
if (this.turnPlayerId === this.player1Id) {
this.turnPlayerId = this.player2Id;
2022-05-02 00:42:59 +03:00
} else {
2022-05-04 15:04:41 +03:00
this.turnPlayerId = this.player1Id;
2022-05-02 00:42:59 +03:00
}
}
isTurnPlayer1() {
2022-05-04 15:04:41 +03:00
return this.player1Id === this.turnPlayerId;
2022-05-02 00:42:59 +03:00
}
isTurnPlayer2() {
2022-05-04 15:04:41 +03:00
return this.player2Id === this.turnPlayerId;
2022-05-02 00:42:59 +03:00
}
2022-05-04 15:04:41 +03:00
getPlayerIdByPitType(pitType: PitType): string {
2022-05-02 00:42:59 +03:00
if (pitType === 'player1Pit' || pitType === 'player1Bank') {
2022-05-04 15:04:41 +03:00
return this.player1Id;
2022-05-02 00:42:59 +03:00
} else if (pitType === 'player2Pit' || pitType === 'player2Bank') {
2022-05-04 15:04:41 +03:00
return this.player2Id;
2022-05-02 00:42:59 +03:00
} else {
throw new Error('Unknown pit type : ' + pitType);
}
}
2022-05-04 15:04:41 +03:00
getPlayerIdByIndex(index: number): string {
2022-05-02 00:42:59 +03:00
const pitType = this.board.getPitTypeByIndex(index);
2022-05-04 15:04:41 +03:00
return this.getPlayerIdByPitType(pitType);
2022-05-02 00:42:59 +03:00
}
checkIsPlayerTurnByIndex(index: number): boolean {
2022-05-04 15:04:41 +03:00
const playerId = this.getPlayerIdByIndex(index);
return this.checkIsPlayerTurn(playerId);
2022-05-02 00:42:59 +03:00
}
2022-05-04 15:04:41 +03:00
getBoardIndexByPlayerId(playerId: string, pitIndex: number) {
if (this.player1Id === playerId) {
2022-05-02 00:42:59 +03:00
return this.board.player1PitStartIndex() + pitIndex;
2022-05-04 15:04:41 +03:00
} else if (this.player2Id === playerId) {
2022-05-02 00:42:59 +03:00
return this.board.player2PitStartIndex() + pitIndex;
} else {
return -1; // throwing an error might be better
}
}
2022-05-04 15:04:41 +03:00
public checkIsPlayerTurn(playerId: string) {
return playerId === this.turnPlayerId;
2022-05-02 00:42:59 +03:00
}
2022-05-04 15:04:41 +03:00
public checkPitIndexForPlayerId(playerId: string, pitIndex: number) {
const foundPlayerId = this.getPlayerIdByIndex(
this.getBoardIndexByPlayerId(playerId, pitIndex)
2022-05-02 00:42:59 +03:00
);
2022-05-04 15:04:41 +03:00
return playerId === foundPlayerId;
2022-05-02 00:42:59 +03:00
}
2022-05-04 15:04:41 +03:00
public checkIsPitIndexBank(playerId: string, pitIndex: number) {
2022-05-02 00:42:59 +03:00
const pitType = this.board.getPitTypeByIndex(
2022-05-04 15:04:41 +03:00
this.getBoardIndexByPlayerId(playerId, pitIndex)
2022-05-02 00:42:59 +03:00
);
return pitType === 'player1Bank' || pitType === 'player2Bank';
}
2022-05-04 15:04:41 +03:00
public canPlayerMove(playerId: string, pitIndex: number) {
const isPitIndexCorrect = this.checkPitIndexForPlayerId(playerId, pitIndex);
const isPitIndexBank = this.checkIsPitIndexBank(playerId, pitIndex);
const isPlayerTurn = this.checkIsPlayerTurn(playerId);
2022-05-02 00:42:59 +03:00
return isPitIndexCorrect && !isPitIndexBank && isPlayerTurn;
}
2022-05-04 15:04:41 +03:00
public moveByPlayerPit(playerId: string, pitIndex: number) {
2022-05-02 00:42:59 +03:00
if (this.state === 'ended') return;
if (this.state === 'initial') {
this.state = 'playing';
}
2022-05-04 15:04:41 +03:00
if (this.canPlayerMove(playerId, pitIndex)) {
2022-05-05 23:50:26 +03:00
const moveIndex = this.getBoardIndexByPlayerId(playerId, pitIndex);
2022-05-08 16:59:35 +03:00
this.currentHistoryItem = new MoveHistoryItem(
playerId,
moveIndex,
[],
[]
2022-05-05 23:50:26 +03:00
);
2022-05-08 16:59:35 +03:00
this.board.move(moveIndex);
this.currentHistoryItem.boardSnapshot = this.board.getStoneArray();
this.history.push(this.currentHistoryItem);
2022-05-02 00:42:59 +03:00
if (this.checkGameIsEnded()) {
this.state = 'ended';
}
} else {
2022-05-04 15:04:41 +03:00
const isPitIndexCorrect = this.checkPitIndexForPlayerId(
playerId,
pitIndex
);
const isPlayerTurn = this.checkIsPlayerTurn(playerId);
2022-05-02 00:42:59 +03:00
throw new Error(
`Player cannot move reason : isPitIndexCorrect = ${isPitIndexCorrect} isPlayerTurn = ${isPlayerTurn}`
);
}
}
getPlayer1StoneCountInPits(): number {
return this.getPlayerStoneCountInPits(
this.board.player1PitStartIndex(),
this.board.player1BankIndex()
);
}
getPlayer2StoneCountInPits(): number {
return this.getPlayerStoneCountInPits(
this.board.player2PitStartIndex(),
this.board.player2BankIndex()
);
}
getPlayerStoneCountInPits(
playerPitStartIndex: number,
playerBankIndex: number
): number {
const player2Pits = this.board.pits.slice(
playerPitStartIndex,
playerBankIndex
);
return player2Pits
.map((pit) => pit.stoneCount)
.reduce(
(previousStoneCount, stoneCount) => previousStoneCount + stoneCount
);
}
2022-05-04 15:04:41 +03:00
public checkGameIsEnded(): boolean {
2022-05-02 00:42:59 +03:00
if (
this.getPlayer1StoneCountInPits() === 0 ||
this.getPlayer2StoneCountInPits() === 0
) {
return true;
}
return false;
}
2022-05-04 15:04:41 +03:00
2022-07-09 12:46:05 +03:00
public getWonPlayerId(): string | undefined {
const player1StoneCount = this.board.player1Bank.stoneCount;
const player2StoneCount = this.board.player2Bank.stoneCount;
2022-05-04 15:04:41 +03:00
if (this.checkGameIsEnded()) {
2022-07-09 12:46:05 +03:00
if (player1StoneCount === player2StoneCount) {
return undefined;
} else if (player1StoneCount > player2StoneCount) {
2022-05-04 15:04:41 +03:00
return this.player1Id;
} else {
return this.player2Id;
}
}
}
public static createFromMancalaGame(mancalaGame: MancalaGame): MancalaGame {
return new MancalaGame(
2022-05-05 23:04:10 +03:00
mancalaGame.id,
2022-05-04 15:04:41 +03:00
new Board(
mancalaGame.board.playerPitCount,
mancalaGame.board.initialStoneCountInPits,
mancalaGame.board.pits
),
mancalaGame.player1Id,
mancalaGame.player2Id,
mancalaGame.turnPlayerId,
mancalaGame.gameRules,
2022-05-05 23:50:26 +03:00
mancalaGame.history,
2022-05-04 15:04:41 +03:00
mancalaGame.state
);
}
2022-05-08 16:59:35 +03:00
public getCurrentHistoryItem(): HistoryItem | null {
return this.currentHistoryItem;
}
public addGameStep(gameStep: GameStep) {
this.getCurrentHistoryItem()?.gameSteps.push(gameStep);
}
2022-05-02 00:42:59 +03:00
}