fuzzyworld

dao-game / peerjs-browser-bug / files / src / game.ts

Branch peerjs-browser-bug · default branch · all branches

game.ts

5.7 KB · history · blame

1import { Coord, Move, MoveResult } from './types';
2import { GameBoard } from "./game-board";
3
4export interface IGame {
5 size: Coord;
6 moveHistory: Move[];
7 player1Id: string;
8 player1Name: string;
9 player2Id: string;
10 player2Name: string;
11 currentPlayerId: string;
12 canUndoMove: boolean;
13 getPlayerPositions: (playerId: string) => Coord[];
14 coordToBoardIndex: (coord: Coord) => number;
15 getValidMoves: (start: Coord) => Coord[];
16 makeMove: (source: Coord, dest: Coord) => Promise<MoveResult>;
17 undoMove: () => Move;
18 getPiece(pos: Coord): string | undefined;
19}
20
21export class Game implements IGame {
22 private isGameOver: boolean;
23 private readonly gameBoard: GameBoard;
24 private readonly _moveHistory: Move[];
25 public readonly player1Id: string;
26 public readonly player2Id: string;
27 public readonly player1Name: string;
28 public readonly player2Name: string;
29
30 public constructor(player1Id: string, player1Name: string, player2Id: string, player2Name: string) {
31 this.gameBoard = new GameBoard(player1Id, player2Id);
32 this.player1Id = player1Id;
33 this.player2Id = player2Id;
34 this.player1Name = player1Name;
35 this.player2Name = player2Name;
36 this.isGameOver = false;
37 this._moveHistory = [];
38 this.currentPlayerId = player1Id;
39 }
40 getPiece(pos: Coord): string {
41 const value = this.gameBoard.get(pos);
42 if (value === null) return null;
43 const b = value.valueOf();
44 const id = b ? this.player1Id : this.player2Id;
45 return id;
46 }
47 public currentPlayerId: string;
48 public get size(): Coord {
49 return this.gameBoard.size;
50 }
51 public get moveHistory(): Move[] {
52 return this._moveHistory;
53 }
54 public getPlayerPositions(playerId: string): Coord[] {
55 return this.gameBoard.getCoords(playerId);
56 }
57 public coordToBoardIndex(coord: Coord): number {
58 return this.gameBoard.coordToBoardIndex(coord);
59 }
60 public async makeMove(source: Coord, dest: Coord): Promise<MoveResult> {
61 const checkForWin = (isP1Turn: boolean): MoveResult => {
62 const doCoordsFormSquare = (coords: Coord[]): boolean => {
63 const allRows = coords.map((c) => c.row);
64 const allCols = coords.map((c) => c.col);
65 const min = new Coord(Math.min(...allRows), Math.min(...allCols));
66 const max = new Coord(Math.max(...allRows), Math.max(...allCols));
67 const diff = max.sub(min);
68 const isSquare = diff.row === 1 && diff.col === 1;
69 return isSquare;
70 };
71 const { gameBoard, player1Id, player2Id, player1Name, player2Name } = this;
72 const playerId = isP1Turn ? player1Id : player2Id;
73
74 const playerCoords = gameBoard.getCoords(playerId);
75 const squareCoords: Coord[] = [new Coord(0, 0), new Coord(3, 0), new Coord(3, 3), new Coord(0, 3)];
76
77 // winning conditions:
78 // 1. all in one row
79 // 2. all in one column
80 // 3. all in corners
81 // 4. all in square
82 // losing conditions:
83 // 1. on three sides of an opponent
84
85 const firstPiece = playerCoords[0];
86 const allInRow = playerCoords.every((pc) => pc.row === firstPiece.row);
87 const allInCol = playerCoords.every((pc) => pc.col === firstPiece.col);
88 const allInCorners = playerCoords.every((pc) => squareCoords.findIndex((sc) => pc.equals(sc)) >= 0);
89 const allInSquare = doCoordsFormSquare(playerCoords);
90
91 const winnerName = isP1Turn ? player1Name : player2Name;
92 let winningMessage = undefined;
93 if (allInRow) {
94 winningMessage = `${winnerName} wins by forming a row`;
95 }
96 if (allInCol) {
97 winningMessage = `${winnerName} wins by forming a column`;
98 }
99 if (allInCorners) {
100 winningMessage = `${winnerName} wins by putting pieces in corners`;
101 }
102 if (allInSquare) {
103 winningMessage = `${winnerName} wins by forming a square`;
104 }
105 if (winningMessage) {
106 return {
107 isTerminal: true,
108 message: winningMessage,
109 };
110 } else {
111 return {
112 isTerminal: false,
113 };
114 }
115 };
116
117 const { gameBoard, player1Id, player2Id } = this;
118 const isP1Turn = this.currentPlayerId === player1Id;
119 const playerId = isP1Turn ? player1Id : player2Id;
120
121 this._moveHistory.push({ playerId, source, dest });
122 gameBoard.move(source, dest);
123
124 const result = checkForWin(isP1Turn);
125 if (result.isTerminal) {
126 this.isGameOver = true;
127 } else {
128 this.currentPlayerId = isP1Turn ? this.player2Id : this.player1Id;
129 }
130 return result;
131 }
132 public getValidMoves(start: Coord): Coord[] {
133 const getFarthestCoord = (startCoord: Coord, direction: Coord): Coord => {
134 const { gameBoard } = this;
135 let farthestPos = undefined;
136 let testPos = startCoord;
137 while (true) {
138 const pos = new Coord(testPos.row + direction.row, testPos.col + direction.col);
139
140 const withinBounds = gameBoard.size.contains(pos);
141 if (!withinBounds) {
142 break;
143 }
144 const isOccupied = gameBoard.get(pos) !== null;
145 if (isOccupied) {
146 break;
147 }
148
149 farthestPos = pos;
150 testPos = pos;
151 }
152 return farthestPos;
153 };
154
155 const farthestCoords = Coord.allDirections
156 .map((direction) => getFarthestCoord(start, direction))
157 .filter((farCoord) => farCoord !== undefined);
158
159 return farthestCoords;
160 }
161 public undoMove(): Move {
162 if (!this.canUndoMove) {
163 throw "Newp! Can't undo!";
164 }
165 const { gameBoard } = this;
166 const move = this._moveHistory.pop();
167 const { source, dest, playerId } = move;
168 this.currentPlayerId = playerId;
169 gameBoard.move(dest, source);
170 return move;
171 }
172 public get canUndoMove(): boolean {
173 if (this._moveHistory.length === 0 || this.isGameOver) {
174 return false;
175 }
176 return true;
177 }
178}