178 lines
4.8 KiB
TypeScript
178 lines
4.8 KiB
TypeScript
|
|
import { Board, PitType } from './Board';
|
||
|
|
import { GameRule } from './GameRule';
|
||
|
|
import { Player } from './Player';
|
||
|
|
|
||
|
|
export type GameState = 'initial' | 'playing' | 'ended';
|
||
|
|
|
||
|
|
export class MancalaGame {
|
||
|
|
board: Board;
|
||
|
|
player1: Player;
|
||
|
|
player2: Player;
|
||
|
|
turnPlayerId: string;
|
||
|
|
state: GameState;
|
||
|
|
gameRules: GameRule[];
|
||
|
|
|
||
|
|
constructor(
|
||
|
|
board: Board,
|
||
|
|
player1: Player,
|
||
|
|
player2: Player,
|
||
|
|
turnPlayerId: string,
|
||
|
|
gameRules: GameRule[]
|
||
|
|
) {
|
||
|
|
this.board = board;
|
||
|
|
this.player1 = player1;
|
||
|
|
this.player2 = player2;
|
||
|
|
this.turnPlayerId = turnPlayerId;
|
||
|
|
this.state = 'initial';
|
||
|
|
this.gameRules = gameRules;
|
||
|
|
this.listenBoardMoveEvents();
|
||
|
|
}
|
||
|
|
|
||
|
|
listenBoardMoveEvents() {
|
||
|
|
this.board.onGameMoveStart = (index: number) => {
|
||
|
|
this.gameRules.forEach((gameRule) => {
|
||
|
|
gameRule.onGameMoveStart(this, index);
|
||
|
|
});
|
||
|
|
};
|
||
|
|
this.board.onGameMove = (index: number) => {
|
||
|
|
this.gameRules.forEach((gameRule) => {
|
||
|
|
gameRule.onGameMove(this, index);
|
||
|
|
});
|
||
|
|
};
|
||
|
|
this.board.onGameMoveEnd = (index: number) => {
|
||
|
|
this.gameRules.forEach((gameRule) => {
|
||
|
|
gameRule.onGameMoveEnd(this, index);
|
||
|
|
});
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
changePlayerTurn() {
|
||
|
|
if (this.turnPlayerId === this.player1.id) {
|
||
|
|
this.turnPlayerId = this.player2.id;
|
||
|
|
} else {
|
||
|
|
this.turnPlayerId = this.player1.id;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
isTurnPlayer1() {
|
||
|
|
return this.player1.id === this.turnPlayerId;
|
||
|
|
}
|
||
|
|
|
||
|
|
isTurnPlayer2() {
|
||
|
|
return this.player2.id === this.turnPlayerId;
|
||
|
|
}
|
||
|
|
|
||
|
|
getPlayerByPitType(pitType: PitType): Player {
|
||
|
|
if (pitType === 'player1Pit' || pitType === 'player1Bank') {
|
||
|
|
return this.player1;
|
||
|
|
} else if (pitType === 'player2Pit' || pitType === 'player2Bank') {
|
||
|
|
return this.player2;
|
||
|
|
} else {
|
||
|
|
throw new Error('Unknown pit type : ' + pitType);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
getPlayerByIndex(index: number): Player {
|
||
|
|
const pitType = this.board.getPitTypeByIndex(index);
|
||
|
|
return this.getPlayerByPitType(pitType);
|
||
|
|
}
|
||
|
|
|
||
|
|
checkIsPlayerTurnByIndex(index: number): boolean {
|
||
|
|
const player = this.getPlayerByIndex(index);
|
||
|
|
return this.checkIsPlayerTurn(player);
|
||
|
|
}
|
||
|
|
|
||
|
|
getBoardIndexByPlayer(player: Player, pitIndex: number) {
|
||
|
|
if (this.player1.id === player.id) {
|
||
|
|
return this.board.player1PitStartIndex() + pitIndex;
|
||
|
|
} else if (this.player2.id === player.id) {
|
||
|
|
return this.board.player2PitStartIndex() + pitIndex;
|
||
|
|
} else {
|
||
|
|
return -1; // throwing an error might be better
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public checkIsPlayerTurn(player: Player) {
|
||
|
|
return player.id === this.turnPlayerId;
|
||
|
|
}
|
||
|
|
|
||
|
|
public checkPitIndexForPlayer(player: Player, pitIndex: number) {
|
||
|
|
const foundPlayer = this.getPlayerByIndex(
|
||
|
|
this.getBoardIndexByPlayer(player, pitIndex)
|
||
|
|
);
|
||
|
|
return player.id === foundPlayer.id;
|
||
|
|
}
|
||
|
|
|
||
|
|
public checkIsPitIndexBank(player: Player, pitIndex: number) {
|
||
|
|
const pitType = this.board.getPitTypeByIndex(
|
||
|
|
this.getBoardIndexByPlayer(player, pitIndex)
|
||
|
|
);
|
||
|
|
return pitType === 'player1Bank' || pitType === 'player2Bank';
|
||
|
|
}
|
||
|
|
|
||
|
|
public canPlayerMove(player: Player, pitIndex: number) {
|
||
|
|
const isPitIndexCorrect = this.checkPitIndexForPlayer(player, pitIndex);
|
||
|
|
const isPitIndexBank = this.checkIsPitIndexBank(player, pitIndex);
|
||
|
|
const isPlayerTurn = this.checkIsPlayerTurn(player);
|
||
|
|
return isPitIndexCorrect && !isPitIndexBank && isPlayerTurn;
|
||
|
|
}
|
||
|
|
|
||
|
|
public moveByPlayerPit(player: Player, pitIndex: number) {
|
||
|
|
if (this.state === 'ended') return;
|
||
|
|
if (this.state === 'initial') {
|
||
|
|
this.state = 'playing';
|
||
|
|
}
|
||
|
|
if (this.canPlayerMove(player, pitIndex)) {
|
||
|
|
this.board.move(this.getBoardIndexByPlayer(player, pitIndex));
|
||
|
|
if (this.checkGameIsEnded()) {
|
||
|
|
this.state = 'ended';
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
const isPitIndexCorrect = this.checkPitIndexForPlayer(player, pitIndex);
|
||
|
|
const isPlayerTurn = this.checkIsPlayerTurn(player);
|
||
|
|
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
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
checkGameIsEnded(): boolean {
|
||
|
|
if (
|
||
|
|
this.getPlayer1StoneCountInPits() === 0 ||
|
||
|
|
this.getPlayer2StoneCountInPits() === 0
|
||
|
|
) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|