fuzzyworld

dao-game / files / src / game / game.ts

game.ts

Blame: the commit that last changed each line · file · history

b3c7b964 2022-03-22 Pavel Safronov1import { GameBoard } from "./game-board";
38c7e27c 2022-03-22 Pavel Safronov2import { Coord } from "./coord";
3
4export interface Move {
5 source: Coord;
6 dest: Coord;
7 playerId: string;
8}
9
10export interface MoveResult {
11 isTerminal: boolean;
12 message?: string;
13}
4918da41 2022-03-17 Pavel Safronov14
15export interface IGame {
47dfe48a 2022-03-20 Pavel Safronov16 size: Coord;
17 moveHistory: Move[];
18 player1Id: string;
5805825c 2022-03-21 Pavel Safronov19 player1Name: string;
47dfe48a 2022-03-20 Pavel Safronov20 player2Id: string;
5805825c 2022-03-21 Pavel Safronov21 player2Name: string;
47dfe48a 2022-03-20 Pavel Safronov22 currentPlayerId: string;
23 canUndoMove: boolean;
24 getPlayerPositions: (playerId: string) => Coord[];
25 coordToBoardIndex: (coord: Coord) => number;
26 getValidMoves: (start: Coord) => Coord[];
27 makeMove: (source: Coord, dest: Coord) => Promise<MoveResult>;
28 undoMove: () => Move;
29 getPiece(pos: Coord): string | undefined;
4918da41 2022-03-17 Pavel Safronov30}
2909d324 2022-03-16 Pavel Safronov31
32export class Game implements IGame {
47dfe48a 2022-03-20 Pavel Safronov33 private isGameOver: boolean;
34 private readonly gameBoard: GameBoard;
35 private readonly _moveHistory: Move[];
5805825c 2022-03-21 Pavel Safronov36 public readonly player1Id: string;
37 public readonly player2Id: string;
38 public readonly player1Name: string;
39 public readonly player2Name: string;
2909d324 2022-03-16 Pavel Safronov40
5805825c 2022-03-21 Pavel Safronov41 public constructor(player1Id: string, player1Name: string, player2Id: string, player2Name: string) {
47dfe48a 2022-03-20 Pavel Safronov42 this.gameBoard = new GameBoard(player1Id, player2Id);
5805825c 2022-03-21 Pavel Safronov43 this.player1Id = player1Id;
44 this.player2Id = player2Id;
45 this.player1Name = player1Name;
46 this.player2Name = player2Name;
47dfe48a 2022-03-20 Pavel Safronov47 this.isGameOver = false;
48 this._moveHistory = [];
49 this.currentPlayerId = player1Id;
50 }
51 getPiece(pos: Coord): string {
52 const value = this.gameBoard.get(pos);
53 if (value === null) return null;
54 const b = value.valueOf();
55 const id = b ? this.player1Id : this.player2Id;
56 return id;
57 }
58 public currentPlayerId: string;
59 public get size(): Coord {
60 return this.gameBoard.size;
61 }
62 public get moveHistory(): Move[] {
63 return this._moveHistory;
64 }
65 public getPlayerPositions(playerId: string): Coord[] {
66 return this.gameBoard.getCoords(playerId);
67 }
68 public coordToBoardIndex(coord: Coord): number {
69 return this.gameBoard.coordToBoardIndex(coord);
70 }
71 public async makeMove(source: Coord, dest: Coord): Promise<MoveResult> {
72 const checkForWin = (isP1Turn: boolean): MoveResult => {
73 const doCoordsFormSquare = (coords: Coord[]): boolean => {
74 const allRows = coords.map((c) => c.row);
75 const allCols = coords.map((c) => c.col);
76 const min = new Coord(Math.min(...allRows), Math.min(...allCols));
77 const max = new Coord(Math.max(...allRows), Math.max(...allCols));
78 const diff = max.sub(min);
79 const isSquare = diff.row === 1 && diff.col === 1;
80 return isSquare;
81 };
5805825c 2022-03-21 Pavel Safronov82 const { gameBoard, player1Id, player2Id, player1Name, player2Name } = this;
47dfe48a 2022-03-20 Pavel Safronov83 const playerId = isP1Turn ? player1Id : player2Id;
2909d324 2022-03-16 Pavel Safronov84
47dfe48a 2022-03-20 Pavel Safronov85 const playerCoords = gameBoard.getCoords(playerId);
86 const squareCoords: Coord[] = [new Coord(0, 0), new Coord(3, 0), new Coord(3, 3), new Coord(0, 3)];
2909d324 2022-03-16 Pavel Safronov87
47dfe48a 2022-03-20 Pavel Safronov88 // winning conditions:
89 // 1. all in one row
90 // 2. all in one column
91 // 3. all in corners
92 // 4. all in square
93 // losing conditions:
94 // 1. on three sides of an opponent
2909d324 2022-03-16 Pavel Safronov95
47dfe48a 2022-03-20 Pavel Safronov96 const firstPiece = playerCoords[0];
97 const allInRow = playerCoords.every((pc) => pc.row === firstPiece.row);
98 const allInCol = playerCoords.every((pc) => pc.col === firstPiece.col);
99 const allInCorners = playerCoords.every((pc) => squareCoords.findIndex((sc) => pc.equals(sc)) >= 0);
100 const allInSquare = doCoordsFormSquare(playerCoords);
8f71d3db 2022-03-01 Pavel Safronov101
5805825c 2022-03-21 Pavel Safronov102 const winnerName = isP1Turn ? player1Name : player2Name;
47dfe48a 2022-03-20 Pavel Safronov103 let winningMessage = undefined;
104 if (allInRow) {
105 winningMessage = `${winnerName} wins by forming a row`;
106 }
107 if (allInCol) {
108 winningMessage = `${winnerName} wins by forming a column`;
109 }
110 if (allInCorners) {
111 winningMessage = `${winnerName} wins by putting pieces in corners`;
112 }
113 if (allInSquare) {
114 winningMessage = `${winnerName} wins by forming a square`;
115 }
116 if (winningMessage) {
117 return {
118 isTerminal: true,
119 message: winningMessage,
120 };
121 } else {
122 return {
123 isTerminal: false,
cda3e92d 2022-03-11 Pavel Safronov124 };
47dfe48a 2022-03-20 Pavel Safronov125 }
126 };
0b6fe828 2022-02-26 Pavel Safronov127
47dfe48a 2022-03-20 Pavel Safronov128 const { gameBoard, player1Id, player2Id } = this;
129 const isP1Turn = this.currentPlayerId === player1Id;
130 const playerId = isP1Turn ? player1Id : player2Id;
0b6fe828 2022-02-26 Pavel Safronov131
5648264d 2022-03-22 Pavel Safronov132 this._moveHistory.push({ playerId, source, dest });
47dfe48a 2022-03-20 Pavel Safronov133 gameBoard.move(source, dest);
0b6fe828 2022-02-26 Pavel Safronov134
47dfe48a 2022-03-20 Pavel Safronov135 const result = checkForWin(isP1Turn);
136 if (result.isTerminal) {
137 this.isGameOver = true;
138 } else {
139 this.currentPlayerId = isP1Turn ? this.player2Id : this.player1Id;
f73c95d8 2022-03-01 Pavel Safronov140 }
47dfe48a 2022-03-20 Pavel Safronov141 return result;
142 }
143 public getValidMoves(start: Coord): Coord[] {
144 const getFarthestCoord = (startCoord: Coord, direction: Coord): Coord => {
145 const { gameBoard } = this;
146 let farthestPos = undefined;
147 let testPos = startCoord;
148 while (true) {
149 const pos = new Coord(testPos.row + direction.row, testPos.col + direction.col);
2909d324 2022-03-16 Pavel Safronov150
47dfe48a 2022-03-20 Pavel Safronov151 const withinBounds = gameBoard.size.contains(pos);
152 if (!withinBounds) {
153 break;
154 }
155 const isOccupied = gameBoard.get(pos) !== null;
156 if (isOccupied) {
157 break;
158 }
4918da41 2022-03-17 Pavel Safronov159
47dfe48a 2022-03-20 Pavel Safronov160 farthestPos = pos;
161 testPos = pos;
162 }
163 return farthestPos;
164 };
8f71d3db 2022-03-01 Pavel Safronov165
47dfe48a 2022-03-20 Pavel Safronov166 const farthestCoords = Coord.allDirections
167 .map((direction) => getFarthestCoord(start, direction))
168 .filter((farCoord) => farCoord !== undefined);
481788e8 2022-02-27 Pavel Safronov169
47dfe48a 2022-03-20 Pavel Safronov170 return farthestCoords;
171 }
172 public undoMove(): Move {
173 if (!this.canUndoMove) {
174 throw "Newp! Can't undo!";
481788e8 2022-02-27 Pavel Safronov175 }
47dfe48a 2022-03-20 Pavel Safronov176 const { gameBoard } = this;
177 const move = this._moveHistory.pop();
178 const { source, dest, playerId } = move;
179 this.currentPlayerId = playerId;
180 gameBoard.move(dest, source);
181 return move;
182 }
183 public get canUndoMove(): boolean {
184 if (this._moveHistory.length === 0 || this.isGameOver) {
185 return false;
cda3e92d 2022-03-11 Pavel Safronov186 }
47dfe48a 2022-03-20 Pavel Safronov187 return true;
188 }
cda3e92d 2022-03-11 Pavel Safronov189}