UNPKG

labyrinth-game-logic

Version:

A libary that implements the boardgame labyrinth and exposes the functionality to create and interact with games

245 lines (224 loc) 7.75 kB
/** * @author Timo lehnertz */ import { AllPlayerStates } from "./AllPlayerStates"; import { Board } from "./Board"; import { BoardPosition } from "./BoardPosition"; import { ShiftPosition } from "./ShiftPosition"; import { Move } from "./Move"; import { PlayerState } from "./PlayerState"; import { Treasure } from "./Treasure"; import { Cyrb64 } from "./Cyrb64"; /** * GameState class representing the state of a game * This class exposes all information about the game that a player could legally know by looking at the board */ export class GameState { public readonly board: Board; public readonly allPlayerStates: AllPlayerStates; public readonly historyMoves: Move[]; private hashCache: Cyrb64 | null = null; public constructor( board: Board, allPlayerStates: AllPlayerStates, historyMoves: Move[] ) { this.board = board; this.allPlayerStates = allPlayerStates; this.historyMoves = historyMoves; } /** * @throws Error in case the move is not valid */ public validateMove(move: Move) { if ( !this.board.isShiftPositionValid(move.fromShiftPosition) || !this.board.isShiftPositionValid(move.toShiftPosition) ) { throw new Error("Invalid shift position"); } if (!this.board.shiftPosition.equals(move.fromShiftPosition)) { throw new Error("Invalid starting shiftPosition"); } const gameStatedAfterSlide = this.setShiftPosition(move.toShiftPosition) .rotateLooseTile(move.rotateBeforeShift) .insertLooseTile(); const playerState = gameStatedAfterSlide.allPlayerStates.getPlayerState( move.playerIndex ); if (playerState === null) { throw new Error("Invalid playerIndex"); } if (!move.from.equals(playerState.position)) { console.log(move.from, playerState.position); throw new Error("Invalid starting position"); } if (!gameStatedAfterSlide.board.isReachable(move.from, move.to)) { throw new Error("Destination is not in reach"); } let expectedTreasure: Treasure | null = null; const treasureAtTo = gameStatedAfterSlide.board.getTile(move.to).treasure; if (treasureAtTo !== null) { if (playerState.currentTreasure?.equals(treasureAtTo)) { expectedTreasure = treasureAtTo; } } if (!Treasure.compare(expectedTreasure, move.collectedTreasure)) { console.log(expectedTreasure, move.collectedTreasure); console.log(JSON.stringify(move)); throw new Error("Invalid collected treasure"); } } /** * @throws Error in case the move is not valid */ public move(move: Move): GameState { this.validateMove(move); return this.rotateLooseTile(move.rotateBeforeShift) .setShiftPosition(move.toShiftPosition) .insertLooseTile() .movePlayer(move.playerIndex, move.to) .collectTreasure(move.playerIndex, move.collectedTreasure) .nextPlayer() .addMoveToHistory(move); } public undoMove(): { newGameState: GameState; undoneMove: Move } { if (this.historyMoves.length === 0) { throw new Error("no moves to undo"); } const move = this.historyMoves[this.historyMoves.length - 1]; return { newGameState: this.removeLastHistoryMove() .prevPlayer() .removeLastTreasure(move.playerIndex, move.collectedTreasure) .movePlayer(move.playerIndex, move.from) .insertLooseTile() .setShiftPosition(move.fromShiftPosition) .rotateLooseTile(-move.rotateBeforeShift), undoneMove: move, }; } public rotateLooseTile(rotateBeforeShift: number): GameState { return new GameState( this.board.rotateLooseTile(rotateBeforeShift), this.allPlayerStates, this.historyMoves ); } public setShiftPosition(shiftPosition: ShiftPosition): GameState { const newBoard = this.board.setShiftPosition(shiftPosition); return new GameState(newBoard, this.allPlayerStates, this.historyMoves); } public insertLooseTile(): GameState { const newAllPlayerStates = this.allPlayerStates.mutateAll( (playerState: PlayerState): PlayerState => { return playerState.setPosition( this.board.shiftPosition.shiftPlayer( playerState.position, this.board.width, this.board.height ) ); } ); const newBoard = this.board.insertLooseTile(); return new GameState(newBoard, newAllPlayerStates, this.historyMoves); } private movePlayer(playerIndex: number, to: BoardPosition) { const newAllPlayerStates = this.allPlayerStates.movePlayer(playerIndex, to); return new GameState(this.board, newAllPlayerStates, this.historyMoves); } private collectTreasure( playerIndex: number, foundTreasure: Treasure | null ): GameState { if (foundTreasure === null) { return this; } const newAllPlayerStates = this.allPlayerStates.collectTreasure( playerIndex, foundTreasure ); return new GameState(this.board, newAllPlayerStates, this.historyMoves); } public getWinnerIndex(): number | null { const playerStates = this.allPlayerStates.getPlayerStatesWithAllTreasures(); for (const player of playerStates) { const playerHomePoint = Board.getPlayerHomePosition( player.playerIndex, this.board.width, this.board.height ); if (player.playerState.position.equals(playerHomePoint)) { return player.playerIndex; } } return null; } private removeLastTreasure( playerIndex: number, foundTreasure: Treasure | null ): GameState { if (foundTreasure === null) { return this; } const newAllPlayerStates = this.allPlayerStates.removeLastTreasure(playerIndex); return new GameState(this.board, newAllPlayerStates, this.historyMoves); } private nextPlayer(): GameState { return new GameState( this.board, this.allPlayerStates.nextPlayer(), this.historyMoves ); } private prevPlayer(): GameState { return new GameState( this.board, this.allPlayerStates.prevPlayer(), this.historyMoves ); } private copyHistory(): Move[] { const newHistory: Move[] = []; for (const historyMove of this.historyMoves) { newHistory.push(historyMove); } return newHistory; } private addMoveToHistory(move: Move): GameState { const newHistory = this.copyHistory(); newHistory.push(move); return new GameState(this.board, this.allPlayerStates, newHistory); } private removeLastHistoryMove(): GameState { const newHistory = this.copyHistory(); newHistory.pop(); return new GameState(this.board, this.allPlayerStates, newHistory); } public equals(other: GameState): boolean { // if (!other.hash().equals(this.hash())) { // console.log(this.board.looseTile); // console.log(other.board.looseTile); // } return other.hash().equals(this.hash()); } public static create(instance: GameState): GameState { const historyMoves: Move[] = []; for (const historyMove of instance.historyMoves) { historyMoves.push(Move.create(historyMove)); } return new GameState( Board.create(instance.board), AllPlayerStates.create(instance.allPlayerStates), historyMoves ); } public hash(): Cyrb64 { if (this.hashCache === null) { this.hashCache = Cyrb64.hashString(JSON.stringify(this), 0); } return this.hashCache; } }