fuzzyworld

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

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

local-game.ts

8.4 KB · history · blame

1import { Game, IGame } from './game';
2import { Coord, MoveResult } from './types';
3import { ListenerManager } from './listeners';
4import { showModal } from './modal';
5import { poly_animate } from './animate';
6
7const classNames = {
8 selectedPiece: 'selected-piece',
9 selectedPlayerText: 'selected-player-text',
10 activePlayerPiece: 'active-player-piece',
11 possibleMove: 'possible-move',
12};
13
14export class LocalGame extends Game {
15 selectedPiece: Element | null = null;
16 listenerManager: ListenerManager = new ListenerManager();
17
18 // DOM references
19 readonly dom = {
20 defaultP1Piece: document.getElementById('default-p1-piece'),
21 defaultP2Piece: document.getElementById('default-p2-piece'),
22 resetButton: document.getElementById('reset-button') as HTMLInputElement,
23 undoButton: document.getElementById('undo-button') as HTMLInputElement,
24 cells: () => Array.from(document.querySelectorAll('td')),
25 allPieces: () => Array.from(document.querySelectorAll('.piece')),
26 p1TurnText: () => Array.from(document.querySelectorAll('.p1-turn-text'))[0],
27 p2TurnText: () => Array.from(document.querySelectorAll('.p2-turn-text'))[0],
28 };
29
30 private getPiecesAndCoords(playerId: string) {
31 const allCells = this.dom.cells();
32 const playerPositions = this.getPlayerPositions(playerId);
33 const piecesAndPositions = playerPositions.map((coord) => {
34 const index = this.coordToBoardIndex(coord);
35 const cell = allCells[index];
36 const piece = cell.children[0];
37 return { coord, piece };
38 });
39 return piecesAndPositions;
40 }
41
42 /**
43 * Animates the motion of piece moving from source to dest
44 * @param source Starting Coord
45 * @param dest Ending Coord
46 * @param playerId Player id
47 */
48 private async movePiece(source: Coord, dest: Coord) {
49 const { dom } = this;
50 const sourceIndex = this.coordToBoardIndex(source);
51 const destIndex = this.coordToBoardIndex(dest);
52
53 // find current piece
54 const piece = dom.cells()[sourceIndex].children[0];
55 // unselect piece
56 piece.classList.remove(classNames.selectedPiece);
57
58 // create clone for new position
59 const destPiece = piece.cloneNode(true);
60 // add to destination cell
61 dom.cells()[destIndex].appendChild(destPiece);
62
63 // start animations
64 const animDurationMs = 500;
65 const destElement = dom.cells()[destIndex];
66 const destAnimation = poly_animate(destElement,[{ opacity: 0 }, { opacity: 1 }], { duration: animDurationMs, iterations: 1 });
67 const sourceAnimation = poly_animate(piece,
68 [
69 {
70 opacity: 1,
71 width: '45px',
72 height: '45px',
73 },
74 {
75 opacity: 0,
76 width: '5px',
77 height: '5px',
78 },
79 ],
80 { duration: animDurationMs, iterations: 1 }
81 );
82
83 // await animations
84 await Promise.all([sourceAnimation.finished, destAnimation.finished]);
85
86 // remove old piece
87 piece.parentNode.removeChild(piece);
88 }
89
90 public constructor(player1Id: string, player1Name: string, player2Id: string, player2Name: string) {
91 super(player1Id, player1Name, player2Id, player2Name);
92 this.init();
93 }
94
95 public override async makeMove(source: Coord, dest: Coord): Promise<MoveResult> {
96 this.listenerManager.removeListeners();
97 await this.movePiece(source, dest);
98 const result = await super.makeMove(source, dest);
99 this.handleMoveResult(result);
100 return result;
101 }
102
103 public async handleMoveResult(result: MoveResult) {
104 const { isTerminal, message } = result;
105 if (isTerminal) {
106 await showModal({ text: message});
107 document.location.reload();
108 } else {
109 this.resetUi();
110 this.setActiveUi();
111 }
112 }
113
114 public resetUi() {
115 const { dom, currentPlayerId, player1Id, canUndoMove, moveHistory, listenerManager, selectedPiece } = this;
116
117 // reset text
118 dom.p1TurnText().classList.remove(classNames.selectedPlayerText);
119 dom.p2TurnText().classList.remove(classNames.selectedPlayerText);
120
121 const isP1Turn = currentPlayerId === player1Id;
122 if (isP1Turn) {
123 dom.p1TurnText().classList.add(classNames.selectedPlayerText);
124 } else {
125 dom.p2TurnText().classList.add(classNames.selectedPlayerText);
126 }
127
128 if (dom.undoButton || dom.resetButton) {
129 dom.undoButton.disabled = canUndoMove;
130 if (moveHistory.length === 0) {
131 dom.undoButton.disabled = true;
132 dom.resetButton.disabled = true;
133 } else {
134 dom.undoButton.disabled = false;
135 dom.resetButton.disabled = false;
136 }
137 }
138
139 this.selectedPiece = null;
140
141 // reset Cells
142 dom.cells().forEach((cell) => cell.classList.remove(classNames.possibleMove));
143 // reset pieces
144 dom.allPieces().forEach((piece) => {
145 piece.classList.remove(classNames.activePlayerPiece);
146 piece.classList.remove(classNames.selectedPiece);
147 });
148 // remove all listeners
149 listenerManager.removeListeners();
150 }
151 public setActiveUi() {
152 const { dom, currentPlayerId, listenerManager } = this;
153
154 const clearPossibleMovesBlink = () =>
155 dom.cells().forEach((cell) => cell.classList.remove(classNames.possibleMove));
156 const activePieceListener = (piece: Element, source: Coord) => {
157 // clear current selection
158 if (this.selectedPiece !== null) {
159 this.selectedPiece.classList.remove(classNames.selectedPiece);
160 clearPossibleMovesBlink();
161 listenerManager.removeListeners();
162 hookupActivePieces();
163 }
164
165 // set as selected
166 this.selectedPiece = piece;
167 piece.classList.add(classNames.selectedPiece);
168
169 // hook up allowed moves for:
170 // 1. blink
171 // 2. complete the move
172 const allowedMoves = this.getValidMoves(source);
173 allowedMoves.forEach((dest: Coord) => {
174 const cell = dom.cells()[this.coordToBoardIndex(dest)];
175 cell.classList.add(classNames.possibleMove);
176 listenerManager.addListener(cell, 'click', async () => {
177 clearPossibleMovesBlink();
178 await this.makeMove(source, dest);
179 });
180 });
181 };
182
183 const hookupActivePieces = () => {
184 // mark active pieces
185 const piecesAndPositions = this.getPiecesAndCoords(currentPlayerId);
186 piecesAndPositions.forEach(({ coord: source, piece }) => {
187 piece.classList.add(classNames.activePlayerPiece);
188 listenerManager.addListener(piece, 'click', () => activePieceListener(piece, source));
189 });
190 }
191
192 hookupActivePieces();
193 }
194
195 private init() {
196 const { dom, player1Id, player1Name, player2Id, player2Name, size } = this;
197
198 // create board and pieces
199 let isWhite = true;
200 const table = document.getElementById('table');
201 for (let r = 0; r < size.row; r++) {
202 const tr = document.createElement('tr');
203 table.appendChild(tr);
204 for (let c = 0; c < size.col; c++) {
205 const td = document.createElement('td');
206 tr.appendChild(td);
207 td.classList.add('tile');
208 td.classList.add(isWhite ? 'white' : 'black');
209 isWhite = !isWhite;
210 }
211 isWhite = !isWhite;
212 }
213 const createPiece = (isP1Turn: boolean, pieceId: string, coord: Coord) => {
214 const defaultPiece = isP1Turn ? dom.defaultP1Piece : dom.defaultP2Piece;
215 const newPiece = defaultPiece.cloneNode(true) as HTMLParagraphElement;
216 newPiece.id = pieceId;
217 const index = this.coordToBoardIndex(coord);
218 dom.cells()[index].appendChild(newPiece);
219 };
220 this.getPlayerPositions(player1Id).forEach((coord) => createPiece(true, player1Id, coord));
221 this.getPlayerPositions(player2Id).forEach((coord) => createPiece(false, player2Id, coord));
222
223 dom.p1TurnText().textContent = `${player1Name}`
224 dom.p2TurnText().textContent = `${player2Name}`
225
226 // hook up buttons
227 let blockActions = false;
228 if (dom.resetButton) {
229 dom.resetButton.addEventListener('click', () => {
230 if (blockActions) {
231 return;
232 }
233
234 blockActions = true;
235 if (confirm('Reset?')) {
236 window.location.reload();
237 }
238 blockActions = false;
239 });
240 }
241
242 if (dom.undoButton) {
243 dom.undoButton.addEventListener('click', async () => {
244 if (blockActions) {
245 return;
246 }
247
248 blockActions = true;
249
250 const move = this.undoMove();
251 await this.movePiece(move.dest, move.source);
252 this.resetUi();
253 this.setActiveUi();
254
255 blockActions = false;
256 });
257 }
258 }
259}
260
261// start!
262export const playGame = () => {
263 const lg = new LocalGame('red-player', 'Red', 'blue-player', 'Blue');
264 lg.resetUi();
265 lg.setActiveUi();
266};