fuzzyworld

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

Branch react · default branch · all branches

local-game.ts

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

38c7e27c 2022-03-22 Pavel Safronov1import { Game, IGame, MoveResult } from './game';
2import { ListenerManager } from '../utils/listeners';
3import { showModal } from '../utils/modal';
4import { Coord } from './coord';
4918da41 2022-03-17 Pavel Safronov5
6const classNames = {
47dfe48a 2022-03-20 Pavel Safronov7 selectedPiece: 'selected-piece',
8 selectedPlayerText: 'selected-player-text',
9 activePlayerPiece: 'active-player-piece',
10 possibleMove: 'possible-move',
2909d324 2022-03-16 Pavel Safronov11};
12
1e41e9b4 2022-03-18 Pavel Safronov13export class LocalGame extends Game {
47dfe48a 2022-03-20 Pavel Safronov14 selectedPiece: Element | null = null;
15 listenerManager: ListenerManager = new ListenerManager();
16
17 // DOM references
9e157243 2022-03-21 Pavel Safronov18 readonly dom = {
417dc266 2022-03-23 Pavel Safronov19 defaultP1Piece: document.getElementById('default-p1-piece')!,
20 defaultP2Piece: document.getElementById('default-p2-piece')!,
47dfe48a 2022-03-20 Pavel Safronov21 resetButton: document.getElementById('reset-button') as HTMLInputElement,
22 undoButton: document.getElementById('undo-button') as HTMLInputElement,
23 cells: () => Array.from(document.querySelectorAll('td')),
24 allPieces: () => Array.from(document.querySelectorAll('.piece')),
9e157243 2022-03-21 Pavel Safronov25 p1TurnText: () => Array.from(document.querySelectorAll('.p1-turn-text'))[0],
26 p2TurnText: () => Array.from(document.querySelectorAll('.p2-turn-text'))[0],
47dfe48a 2022-03-20 Pavel Safronov27 };
28
29 private getPiecesAndCoords(playerId: string) {
30 const allCells = this.dom.cells();
31 const playerPositions = this.getPlayerPositions(playerId);
32 const piecesAndPositions = playerPositions.map((coord) => {
33 const index = this.coordToBoardIndex(coord);
34 const cell = allCells[index];
35 const piece = cell.children[0];
36 return { coord, piece };
37 });
38 return piecesAndPositions;
39 }
40
41 /**
42 * Animates the motion of piece moving from source to dest
43 * @param source Starting Coord
44 * @param dest Ending Coord
45 * @param playerId Player id
46 */
47 private async movePiece(source: Coord, dest: Coord) {
48 const { dom } = this;
49 const sourceIndex = this.coordToBoardIndex(source);
50 const destIndex = this.coordToBoardIndex(dest);
51
52 // find current piece
53 const piece = dom.cells()[sourceIndex].children[0];
54 // unselect piece
55 piece.classList.remove(classNames.selectedPiece);
56
57 // create clone for new position
58 const destPiece = piece.cloneNode(true);
59 // add to destination cell
60 dom.cells()[destIndex].appendChild(destPiece);
61
62 // start animations
b3c7b964 2022-03-22 Pavel Safronov63 const animDurationMs = 500;
e46456c2 2022-03-22 Pavel Safronov64 const destElement = dom.cells()[destIndex];
38c7e27c 2022-03-22 Pavel Safronov65 const destAnimation = destElement.animate([{ opacity: 0 }, { opacity: 1 }], { duration: animDurationMs, iterations: 1 });
66 const sourceAnimation = piece.animate(
47dfe48a 2022-03-20 Pavel Safronov67 [
68 {
69 opacity: 1,
70 width: '45px',
71 height: '45px',
72 },
73 {
74 opacity: 0,
75 width: '5px',
76 height: '5px',
77 },
78 ],
b3c7b964 2022-03-22 Pavel Safronov79 { duration: animDurationMs, iterations: 1 }
47dfe48a 2022-03-20 Pavel Safronov80 );
81
82 // await animations
83 await Promise.all([sourceAnimation.finished, destAnimation.finished]);
84
85 // remove old piece
417dc266 2022-03-23 Pavel Safronov86 piece.parentNode!.removeChild(piece);
47dfe48a 2022-03-20 Pavel Safronov87 }
88
5805825c 2022-03-21 Pavel Safronov89 public constructor(player1Id: string, player1Name: string, player2Id: string, player2Name: string) {
90 super(player1Id, player1Name, player2Id, player2Name);
47dfe48a 2022-03-20 Pavel Safronov91 this.init();
92 }
93
94 public override async makeMove(source: Coord, dest: Coord): Promise<MoveResult> {
95 this.listenerManager.removeListeners();
96 await this.movePiece(source, dest);
97 const result = await super.makeMove(source, dest);
98 this.handleMoveResult(result);
99 return result;
100 }
101
102 public async handleMoveResult(result: MoveResult) {
b3c7b964 2022-03-22 Pavel Safronov103 const { isTerminal, message } = result;
104 if (isTerminal) {
38c7e27c 2022-03-22 Pavel Safronov105 await showModal({ text: message });
47dfe48a 2022-03-20 Pavel Safronov106 document.location.reload();
107 } else {
108 this.resetUi();
109 this.setActiveUi();
1e41e9b4 2022-03-18 Pavel Safronov110 }
47dfe48a 2022-03-20 Pavel Safronov111 }
2909d324 2022-03-16 Pavel Safronov112
47dfe48a 2022-03-20 Pavel Safronov113 public resetUi() {
114 const { dom, currentPlayerId, player1Id, canUndoMove, moveHistory, listenerManager, selectedPiece } = this;
2909d324 2022-03-16 Pavel Safronov115
47dfe48a 2022-03-20 Pavel Safronov116 // reset text
9e157243 2022-03-21 Pavel Safronov117 dom.p1TurnText().classList.remove(classNames.selectedPlayerText);
118 dom.p2TurnText().classList.remove(classNames.selectedPlayerText);
2909d324 2022-03-16 Pavel Safronov119
47dfe48a 2022-03-20 Pavel Safronov120 const isP1Turn = currentPlayerId === player1Id;
121 if (isP1Turn) {
9e157243 2022-03-21 Pavel Safronov122 dom.p1TurnText().classList.add(classNames.selectedPlayerText);
47dfe48a 2022-03-20 Pavel Safronov123 } else {
9e157243 2022-03-21 Pavel Safronov124 dom.p2TurnText().classList.add(classNames.selectedPlayerText);
47dfe48a 2022-03-20 Pavel Safronov125 }
2909d324 2022-03-16 Pavel Safronov126
f2c94061 2022-03-21 Pavel Safronov127 if (dom.undoButton || dom.resetButton) {
128 dom.undoButton.disabled = canUndoMove;
129 if (moveHistory.length === 0) {
130 dom.undoButton.disabled = true;
131 dom.resetButton.disabled = true;
132 } else {
133 dom.undoButton.disabled = false;
134 dom.resetButton.disabled = false;
135 }
2909d324 2022-03-16 Pavel Safronov136 }
137
47dfe48a 2022-03-20 Pavel Safronov138 this.selectedPiece = null;
139
140 // reset Cells
141 dom.cells().forEach((cell) => cell.classList.remove(classNames.possibleMove));
142 // reset pieces
143 dom.allPieces().forEach((piece) => {
144 piece.classList.remove(classNames.activePlayerPiece);
145 piece.classList.remove(classNames.selectedPiece);
146 });
147 // remove all listeners
148 listenerManager.removeListeners();
149 }
150 public setActiveUi() {
b3c7b964 2022-03-22 Pavel Safronov151 const { dom, currentPlayerId, listenerManager } = this;
152
153 const clearPossibleMovesBlink = () =>
154 dom.cells().forEach((cell) => cell.classList.remove(classNames.possibleMove));
155 const activePieceListener = (piece: Element, source: Coord) => {
156 // clear current selection
157 if (this.selectedPiece !== null) {
158 this.selectedPiece.classList.remove(classNames.selectedPiece);
159 clearPossibleMovesBlink();
160 listenerManager.removeListeners();
161 hookupActivePieces();
162 }
4918da41 2022-03-17 Pavel Safronov163
b3c7b964 2022-03-22 Pavel Safronov164 // set as selected
165 this.selectedPiece = piece;
166 piece.classList.add(classNames.selectedPiece);
167
168 // hook up allowed moves for:
169 // 1. blink
170 // 2. complete the move
171 const allowedMoves = this.getValidMoves(source);
172 allowedMoves.forEach((dest: Coord) => {
173 const cell = dom.cells()[this.coordToBoardIndex(dest)];
174 cell.classList.add(classNames.possibleMove);
175 listenerManager.addListener(cell, 'click', async () => {
176 clearPossibleMovesBlink();
177 await this.makeMove(source, dest);
1e41e9b4 2022-03-18 Pavel Safronov178 });
47dfe48a 2022-03-20 Pavel Safronov179 });
b3c7b964 2022-03-22 Pavel Safronov180 };
181
182 const hookupActivePieces = () => {
183 // mark active pieces
184 const piecesAndPositions = this.getPiecesAndCoords(currentPlayerId);
185 piecesAndPositions.forEach(({ coord: source, piece }) => {
186 piece.classList.add(classNames.activePlayerPiece);
187 listenerManager.addListener(piece, 'click', () => activePieceListener(piece, source));
188 });
189 }
190
191 hookupActivePieces();
47dfe48a 2022-03-20 Pavel Safronov192 }
193
194 private init() {
5805825c 2022-03-21 Pavel Safronov195 const { dom, player1Id, player1Name, player2Id, player2Name, size } = this;
47dfe48a 2022-03-20 Pavel Safronov196
197 // create board and pieces
198 let isWhite = true;
417dc266 2022-03-23 Pavel Safronov199 const table = document.getElementById('table')!;
47dfe48a 2022-03-20 Pavel Safronov200 for (let r = 0; r < size.row; r++) {
201 const tr = document.createElement('tr');
202 table.appendChild(tr);
203 for (let c = 0; c < size.col; c++) {
204 const td = document.createElement('td');
205 tr.appendChild(td);
b3c7b964 2022-03-22 Pavel Safronov206 td.classList.add('tile');
207 td.classList.add(isWhite ? 'white' : 'black');
47dfe48a 2022-03-20 Pavel Safronov208 isWhite = !isWhite;
209 }
210 isWhite = !isWhite;
1e41e9b4 2022-03-18 Pavel Safronov211 }
47dfe48a 2022-03-20 Pavel Safronov212 const createPiece = (isP1Turn: boolean, pieceId: string, coord: Coord) => {
213 const defaultPiece = isP1Turn ? dom.defaultP1Piece : dom.defaultP2Piece;
214 const newPiece = defaultPiece.cloneNode(true) as HTMLParagraphElement;
215 newPiece.id = pieceId;
216 const index = this.coordToBoardIndex(coord);
217 dom.cells()[index].appendChild(newPiece);
218 };
219 this.getPlayerPositions(player1Id).forEach((coord) => createPiece(true, player1Id, coord));
220 this.getPlayerPositions(player2Id).forEach((coord) => createPiece(false, player2Id, coord));
221
b3c7b964 2022-03-22 Pavel Safronov222 dom.p1TurnText().textContent = `${player1Name}`
223 dom.p2TurnText().textContent = `${player2Name}`
5805825c 2022-03-21 Pavel Safronov224
47dfe48a 2022-03-20 Pavel Safronov225 // hook up buttons
226 let blockActions = false;
f2c94061 2022-03-21 Pavel Safronov227 if (dom.resetButton) {
228 dom.resetButton.addEventListener('click', () => {
229 if (blockActions) {
230 return;
231 }
47dfe48a 2022-03-20 Pavel Safronov232
f2c94061 2022-03-21 Pavel Safronov233 blockActions = true;
234 if (confirm('Reset?')) {
235 window.location.reload();
236 }
237 blockActions = false;
238 });
239 }
47dfe48a 2022-03-20 Pavel Safronov240
f2c94061 2022-03-21 Pavel Safronov241 if (dom.undoButton) {
242 dom.undoButton.addEventListener('click', async () => {
243 if (blockActions) {
244 return;
245 }
47dfe48a 2022-03-20 Pavel Safronov246
f2c94061 2022-03-21 Pavel Safronov247 blockActions = true;
47dfe48a 2022-03-20 Pavel Safronov248
f2c94061 2022-03-21 Pavel Safronov249 const move = this.undoMove();
250 await this.movePiece(move.dest, move.source);
251 this.resetUi();
252 this.setActiveUi();
253
254 blockActions = false;
255 });
256 }
47dfe48a 2022-03-20 Pavel Safronov257 }
1e41e9b4 2022-03-18 Pavel Safronov258}
2909d324 2022-03-16 Pavel Safronov259
260// start!
4918da41 2022-03-17 Pavel Safronov261export const playGame = () => {
5805825c 2022-03-21 Pavel Safronov262 const lg = new LocalGame('red-player', 'Red', 'blue-player', 'Blue');
47dfe48a 2022-03-20 Pavel Safronov263 lg.resetUi();
264 lg.setActiveUi();
2909d324 2022-03-16 Pavel Safronov265};