fuzzyworld

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

Branch react · default branch · all branches

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;
417dc266 2022-03-23 Pavel Safronov29 getPiece(pos: Coord): string | null;
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 }
417dc266 2022-03-23 Pavel Safronov51 getPiece(pos: Coord): string | null {
52 const id = this.gameBoard.getPiece(pos);
53 if (id === null) return null;
47dfe48a 2022-03-20 Pavel Safronov54 return id;
55 }
56 public currentPlayerId: string;
57 public get size(): Coord {
58 return this.gameBoard.size;
59 }
60 public get moveHistory(): Move[] {
61 return this._moveHistory;
62 }
63 public getPlayerPositions(playerId: string): Coord[] {
64 return this.gameBoard.getCoords(playerId);
65 }
66 public coordToBoardIndex(coord: Coord): number {
67 return this.gameBoard.coordToBoardIndex(coord);
68 }
69 public async makeMove(source: Coord, dest: Coord): Promise<MoveResult> {
70 const checkForWin = (isP1Turn: boolean): MoveResult => {
71 const doCoordsFormSquare = (coords: Coord[]): boolean => {
72 const allRows = coords.map((c) => c.row);
73 const allCols = coords.map((c) => c.col);
74 const min = new Coord(Math.min(...allRows), Math.min(...allCols));
75 const max = new Coord(Math.max(...allRows), Math.max(...allCols));
76 const diff = max.sub(min);
77 const isSquare = diff.row === 1 && diff.col === 1;
78 return isSquare;
79 };
5805825c 2022-03-21 Pavel Safronov80 const { gameBoard, player1Id, player2Id, player1Name, player2Name } = this;
47dfe48a 2022-03-20 Pavel Safronov81 const playerId = isP1Turn ? player1Id : player2Id;
2909d324 2022-03-16 Pavel Safronov82
47dfe48a 2022-03-20 Pavel Safronov83 const playerCoords = gameBoard.getCoords(playerId);
84 const squareCoords: Coord[] = [new Coord(0, 0), new Coord(3, 0), new Coord(3, 3), new Coord(0, 3)];
2909d324 2022-03-16 Pavel Safronov85
47dfe48a 2022-03-20 Pavel Safronov86 // winning conditions:
87 // 1. all in one row
88 // 2. all in one column
89 // 3. all in corners
90 // 4. all in square
91 // losing conditions:
92 // 1. on three sides of an opponent
2909d324 2022-03-16 Pavel Safronov93
47dfe48a 2022-03-20 Pavel Safronov94 const firstPiece = playerCoords[0];
95 const allInRow = playerCoords.every((pc) => pc.row === firstPiece.row);
96 const allInCol = playerCoords.every((pc) => pc.col === firstPiece.col);
97 const allInCorners = playerCoords.every((pc) => squareCoords.findIndex((sc) => pc.equals(sc)) >= 0);
98 const allInSquare = doCoordsFormSquare(playerCoords);
8f71d3db 2022-03-01 Pavel Safronov99
5805825c 2022-03-21 Pavel Safronov100 const winnerName = isP1Turn ? player1Name : player2Name;
47dfe48a 2022-03-20 Pavel Safronov101 let winningMessage = undefined;
102 if (allInRow) {
103 winningMessage = `${winnerName} wins by forming a row`;
104 }
105 if (allInCol) {
106 winningMessage = `${winnerName} wins by forming a column`;
107 }
108 if (allInCorners) {
109 winningMessage = `${winnerName} wins by putting pieces in corners`;
110 }
111 if (allInSquare) {
112 winningMessage = `${winnerName} wins by forming a square`;
113 }
114 if (winningMessage) {
115 return {
116 isTerminal: true,
117 message: winningMessage,
118 };
119 } else {
120 return {
121 isTerminal: false,
cda3e92d 2022-03-11 Pavel Safronov122 };
47dfe48a 2022-03-20 Pavel Safronov123 }
124 };
0b6fe828 2022-02-26 Pavel Safronov125
47dfe48a 2022-03-20 Pavel Safronov126 const { gameBoard, player1Id, player2Id } = this;
417dc266 2022-03-23 Pavel Safronov127
47dfe48a 2022-03-20 Pavel Safronov128 const isP1Turn = this.currentPlayerId === player1Id;
129 const playerId = isP1Turn ? player1Id : player2Id;
0b6fe828 2022-02-26 Pavel Safronov130
5648264d 2022-03-22 Pavel Safronov131 this._moveHistory.push({ playerId, source, dest });
47dfe48a 2022-03-20 Pavel Safronov132 gameBoard.move(source, dest);
0b6fe828 2022-02-26 Pavel Safronov133
47dfe48a 2022-03-20 Pavel Safronov134 const result = checkForWin(isP1Turn);
135 if (result.isTerminal) {
136 this.isGameOver = true;
137 } else {
138 this.currentPlayerId = isP1Turn ? this.player2Id : this.player1Id;
f73c95d8 2022-03-01 Pavel Safronov139 }
47dfe48a 2022-03-20 Pavel Safronov140 return result;
141 }
142 public getValidMoves(start: Coord): Coord[] {
417dc266 2022-03-23 Pavel Safronov143 const getFarthestCoord = (startCoord: Coord, direction: Coord): Coord | undefined => {
47dfe48a 2022-03-20 Pavel Safronov144 const { gameBoard } = this;
145 let farthestPos = undefined;
146 let testPos = startCoord;
147 while (true) {
148 const pos = new Coord(testPos.row + direction.row, testPos.col + direction.col);
2909d324 2022-03-16 Pavel Safronov149
47dfe48a 2022-03-20 Pavel Safronov150 const withinBounds = gameBoard.size.contains(pos);
151 if (!withinBounds) {
152 break;
153 }
417dc266 2022-03-23 Pavel Safronov154 const isOccupied = gameBoard.getPiece(pos) !== null;
47dfe48a 2022-03-20 Pavel Safronov155 if (isOccupied) {
156 break;
157 }
4918da41 2022-03-17 Pavel Safronov158
47dfe48a 2022-03-20 Pavel Safronov159 farthestPos = pos;
160 testPos = pos;
161 }
162 return farthestPos;
163 };
8f71d3db 2022-03-01 Pavel Safronov164
47dfe48a 2022-03-20 Pavel Safronov165 const farthestCoords = Coord.allDirections
166 .map((direction) => getFarthestCoord(start, direction))
417dc266 2022-03-23 Pavel Safronov167 .filter((farCoord) => farCoord !== undefined)
168 .map(fc => fc!);
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;
417dc266 2022-03-23 Pavel Safronov177 const move = this._moveHistory.pop()!;
47dfe48a 2022-03-20 Pavel Safronov178 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}