dao-game / commit
Lots of changes:
.vscode/launch.json | 18 ++++-
public/index.html | 25 ++++--
public/local.html | 27 ++++---
public/{online.html => remote.html} | 30 +++++---
public/share.png | Bin 0 -> 322 bytes
public/styles.css | 147 ++++++++++++++++++++++++------------
src/game-board.ts | 67 ++++++++++++++++
src/game.ts | 3 +-
src/local-game.ts | 81 ++++++++++++--------
src/modal.ts | 59 +++++++++++++++
src/remote-game.ts | 26 +++----
src/types.ts | 62 ---------------
12 files changed, 360 insertions(+), 185 deletions(-)
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 1b3c68b..98ef16e 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -7,10 +7,26 @@
{
"type": "pwa-chrome",
"request": "launch",
- "name": "Launch Chrome against localhost",
+ "name": "Debug Landing Page",
"url": "http://localhost:3000/",
"webRoot": "${workspaceFolder}/src",
// "preLaunchTask": "task: npm: start"
+ },
+ {
+ "type": "pwa-chrome",
+ "request": "launch",
+ "name": "Debug Local Game",
+ "url": "http://localhost:3000/local.html",
+ "webRoot": "${workspaceFolder}/src",
+ // "preLaunchTask": "task: npm: start"
+ },
+ {
+ "type": "pwa-chrome",
+ "request": "launch",
+ "name": "Debug Remote Game",
+ "url": "http://localhost:3000/remote.html",
+ "webRoot": "${workspaceFolder}/src",
+ // "preLaunchTask": "task: npm: start"
}
]
}
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index 5271b1e..defd512 100644
--- a/public/index.html
+++ b/public/index.html
@@ -15,19 +15,28 @@
</div>
<div class="info">
- The game consists of only one simple rule and three ways to win. Pieces move in a straight line, including
- diagonally, as far as
- possible with no jumps. The three ways to win are arranging your pieces in a straight line on the board (not
- diagonally), occupying
- the four corners, or forming a 2x2 square anywhere on the board.
+ The game consists of only one simple rule and three ways to win.
+
+ Rules:
+ <ul>
+ <li>
+ Pieces move in a straight line, including diagonally, as far as possible with no jumps.
+ </li>
+ </ul>
+ Ways to win:
+ <ol>
+ <li>Arrange your pieces in a straight line on the board (not diagonally)</li>
+ <li>Occupy four corners of the board</li>
+ <li>Form a 2x2 square anywhere on the board</li>
+ </ol>
</div>
<hr />
- <div>
+ <div class="info">
<div>
- <a href='local.html'>Play local (pass-and-play) game of Dao.</a>
+ Play local (pass-and-play) game of Dao <a href='local.html'>here</a>.
</div>
<div>
- <a href='online.html'>Play online game of Dao.</a>
+ Play online game of Dao <a href='remote.html'>here</a>.
</div>
</div>
diff --git a/public/local.html b/public/local.html
index f48f8c6..c3fc3cf 100644
--- a/public/local.html
+++ b/public/local.html
@@ -17,9 +17,8 @@
</div>
<main>
<div class="controls">
- <span class="p1-turn-text"> Red's turn </span>
- <span class="divider">|</span>
- <span class="p2-turn-text"> Blue's turn </span>
+ <span class="p1-turn-text">Red</span>
+ <span class="p2-turn-text">Blue</span>
</div>
<table id="table"></table>
@@ -28,7 +27,6 @@
<button type="button" class="button" id="undo-button">
Undo
</button>
- <span class="divider">|</span>
<button type="button" class="button" id="reset-button">
Reset
</button>
@@ -40,12 +38,22 @@
</div>
<div class="info">
- The game consists of only one simple rule and three ways to win. Pieces move in a straight line, including
- diagonally, as far as
- possible with no jumps. The three ways to win are arranging your pieces in a straight line on the board (not
- diagonally), occupying
- the four corners, or forming a 2x2 square anywhere on the board.
+ The game consists of only one simple rule and three ways to win.
+
+ Rules:
+ <ul>
+ <li>
+ Pieces move in a straight line, including diagonally, as far as possible with no jumps.
+ </li>
+ </ul>
+ Ways to win:
+ <ol>
+ <li>Arrange your pieces in a straight line on the board (not diagonally)</li>
+ <li>Occupy four corners of the board</li>
+ <li>Form a 2x2 square anywhere on the board</li>
+ </ol>
</div>
+
<hr />
<div class="info">
@@ -56,6 +64,7 @@
</div>
<div id="snackbar"></div>
+
</body>
</html>
\ No newline at end of file
diff --git a/public/online.html b/public/remote.html
similarity index 64%
rename from public/online.html
rename to public/remote.html
index f7fefe2..ff94ed7 100644
--- a/public/online.html
+++ b/public/remote.html
@@ -17,16 +17,18 @@
</div>
<main class="invisible">
<div class="controls">
- <span class="p1-turn-text"> Red's turn </span>
- <span class="divider">|</span>
- <span class="p2-turn-text"> Blue's turn </span>
+ <span class="p1-turn-text">Red</span>
+ <span class="p2-turn-text">Blue</span>
</div>
<table id="table"></table>
</main>
<hr />
- <div id="shareLink">Share this link to play with an opponent.</div>
+ <div id="share-container" class="invisible info">
+ Press this button to share a link with your opponent.
+ <button id="share-link">Share <img src="share.png" /></button>
+ </div>
<hr />
<div class="title">
@@ -34,12 +36,22 @@
</div>
<div class="info">
- The game consists of only one simple rule and three ways to win. Pieces move in a straight line, including
- diagonally, as far as
- possible with no jumps. The three ways to win are arranging your pieces in a straight line on the board (not
- diagonally), occupying
- the four corners, or forming a 2x2 square anywhere on the board.
+ The game consists of only one simple rule and three ways to win.
+
+ Rules:
+ <ul>
+ <li>
+ Pieces move in a straight line, including diagonally, as far as possible with no jumps.
+ </li>
+ </ul>
+ Ways to win:
+ <ol>
+ <li>Arrange your pieces in a straight line on the board (not diagonally)</li>
+ <li>Occupy four corners of the board</li>
+ <li>Form a 2x2 square anywhere on the board</li>
+ </ol>
</div>
+
<hr />
<div class="info">
diff --git a/public/share.png b/public/share.png
new file mode 100644
index 0000000..921f933
Binary files /dev/null and b/public/share.png differ
diff --git a/public/styles.css b/public/styles.css
index 93b9ebe..79f8c94 100644
--- a/public/styles.css
+++ b/public/styles.css
@@ -1,54 +1,67 @@
:root {
--default-size: 5px;
--active-size: 40px;
+ --active-pulse-size: 30px;
--cell-size: 80px;
- /* --p1-color: crimson;
- --p2-color: mediumblue; */
+ --max-width: 500px;
+ --font-family: Arial, sans-serif;
}
-* {
+#other-mouse {
+ position: absolute;
+ z-index: 1000;
+ pointer-events: none;
+ display: none;
+}
+
+#table {
margin: auto;
}
-main {
- /* display: flex; */
- flex-direction: column;
- justify-content: space-between;
- align-items: center;
- /* height: 75vh; */
+#undo-button {
+ float: left;
+}
+#reset-button {
+ float: right;
}
-body {
- /* overflow: hidden; */
- margin: 0px;
- background-color: white;
+#share-container {
+ align-content: center;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
}
-div {
+#share-link {
+ background-color: rgb(106, 170, 100);
+ border: black 1px solid;
+ max-width: var(--max-width);
+ margin: auto;
+ border-radius: 5px;
+ cursor: pointer;
display: flex;
- justify-content: space-between;
-}
+
+ align-content: center;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
-#other-mouse {
- position: absolute;
- z-index: 1000;
- pointer-events: none;
- display: none;
+ text-transform: uppercase;
}
.invisible {
- /* visibility: hidden; */
display: none;
}
.title {
text-align: center;
- font-family: 'open sans';
+ font-family: var(--font-family);
font-size: 40px;
+ max-width: var(--max-width);
+ margin: auto;
}
-/* tile */
-td {
+.tile {
width: var(--cell-size);
height: var(--cell-size);
text-align: center;
@@ -57,18 +70,13 @@ td {
.controls {
font-size: 90px;
- margin-left: 15px;
- margin-right: 15px;
- font-family: 'open sans';
+ font-family: var(--font-family);
font-size: 55px;
border-radius: 10px;
padding: 10px;
-}
-
-.divider {
- font-size: 90px;
- margin-left: 15px;
- margin-right: 15px;
+ display: flex;
+ max-width: var(--max-width);
+ margin: auto;
}
.p1-turn-text,
@@ -81,6 +89,7 @@ td {
-ms-user-select: none;
-webkit-user-select: none;
user-select: none;
+ margin: auto;
}
.p1-turn-text.selected-player-text {
@@ -102,12 +111,11 @@ td {
font-size: 60px;
border-color: transparent;
transition: 0.2s all;
+ margin: auto;
}
.button:active {
transform: scale(0.98);
- /* Scaling button to 0.98 to its original size */
box-shadow: 3px 2px 22px 1px rgba(0, 0, 0, 0.24);
- /* Lowering the shadow */
}
.white {
@@ -122,33 +130,33 @@ td {
height: var(--default-size);
padding: 10px;
border-radius: 50px;
- animation: activeToDisabled 1s 1;
- /* background-image: url(yin-yang.svg); */
+ animation: activeToDisabled .5s 1;
+ margin: auto;
}
.p1-piece {
background-color: crimson;
- /* background-image: url(yin-yang.svg); */
}
.p2-piece {
background-color: mediumblue;
- /* background-image: url(yin-yang.svg); */
}
.active-player-piece {
width: var(--active-size);
height: var(--active-size);
- animation: disabledToActive 1s 1;
+ animation: disabledToActive .5s 1;
}
.selected-piece {
- animation: activePulsing 1s infinite;
+ animation: activePulsing .5s infinite;
}
.info {
- text-align: center;
- font-family: 'open sans';
+ /* text-align: center; */
+ font-family: var(--font-family);
font-size: 20px;
+ max-width: var(--max-width);
+ margin: auto;
}
.possible-move {
@@ -203,16 +211,13 @@ td {
@keyframes activePulsing {
from {
- width: var(--active-size);
- height: var(--active-size);
+ opacity: 1;
}
50% {
- width: 25px;
- height: 25px;
+ opacity: .5;
}
to {
- width: var(--active-size);
- height: var(--active-size);
+ opacity: 1;
}
}
@@ -287,3 +292,45 @@ td {
opacity: 0;
}
}
+
+/* Modal from https://www.w3schools.com/howto/howto_css_modals.asp */
+ /* The Modal (background) */
+.modal {
+ display: none; /* Hidden by default */
+ position: fixed; /* Stay in place */
+ z-index: 1; /* Sit on top */
+ left: 0;
+ top: 0;
+ width: 100%; /* Full width */
+ height: 100%; /* Full height */
+ overflow: auto; /* Enable scroll if needed */
+ background-color: rgb(0,0,0); /* Fallback color */
+ background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
+ font-family: var(--font-family);
+ font-size: 30px;
+}
+
+/* Modal Content/Box */
+.modal-content {
+ background-color: #fefefe;
+ margin: 100px auto; /* 15% from the top and centered */
+ padding: 20px;
+ border: 1px solid #888;
+ /* width: 80%; /* Could be more or less, depending on screen size */
+ max-width: var(--max-width);
+}
+
+/* The Close Button */
+.close {
+ color: #aaa;
+ float: right;
+ font-size: 28px;
+ font-weight: bold;
+}
+
+.close:hover,
+.close:focus {
+ color: black;
+ text-decoration: none;
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/src/game-board.ts b/src/game-board.ts
new file mode 100644
index 0000000..f9f54e9
--- /dev/null
+++ b/src/game-board.ts
@@ -0,0 +1,67 @@
+import { Coord } from "./types";
+
+
+export class GameBoard {
+ private board: (string | null)[];
+ public readonly size: Coord;
+ public readonly player1Id: string;
+ public readonly player2Id: string;
+
+ public constructor(player1Id: string, player2Id: string) {
+ // default board
+ const defaultBoard = []
+ .concat([player1Id, null, null, player2Id])
+ .concat([null, player1Id, player2Id, null])
+ .concat([null, player2Id, player1Id, null])
+ .concat([player2Id, null, null, player1Id]);
+ const testBoard = []
+ .concat([player1Id, null, player2Id, player2Id])
+ .concat([player1Id, null, player2Id, null])
+ .concat([player1Id, null, null, null])
+ .concat([null, player1Id, null, player2Id]);
+ this.board = defaultBoard;
+ this.size = new Coord(4, 4);
+ this.player1Id = player1Id;
+ this.player2Id = player2Id;
+ }
+ public move(source: Coord, dest: Coord): void {
+ const isP1 = this.get(source);
+ if (isP1 === null)
+ throw `Source ${source} points to an empty space`;
+ this.set(source, null);
+ this.set(dest, isP1);
+ }
+ public get(coord: Coord): boolean | null {
+ const index = this.coordToBoardIndex(coord);
+ if (!Number.isSafeInteger(index) || index < 0 || index >= this.board.length)
+ throw `Index ${index} is out of bounds`;
+ const id = this.board[index];
+ const isP1 = id === null ? null : id === this.player1Id;
+ return isP1;
+ }
+ public getCoords(id: string): Coord[] {
+ const coords = this.board
+ .map((pieceId, index) => {
+ const isTarget = pieceId === id;
+ return isTarget ? this.boardIndexToCoord(index) : null;
+ })
+ .filter((coord) => coord !== null);
+ return coords;
+ }
+ private set(coord: Coord, isP1: boolean | null): void {
+ const index = this.coordToBoardIndex(coord);
+ if (!Number.isSafeInteger(index) || index < 0 || index >= this.board.length)
+ throw `Index ${index} is out of bounds`;
+ this.board[index] = isP1 === null ? null : isP1 ? this.player1Id : this.player2Id;
+ }
+ public coordToBoardIndex(coord: Coord): number {
+ const { row, col } = coord;
+ const index = row * this.size.col + col;
+ return index;
+ }
+ private boardIndexToCoord(index: number): Coord {
+ const col = index % this.size.col;
+ const row = Math.floor(index / this.size.col);
+ return new Coord(row, col);
+ }
+}
diff --git a/src/game.ts b/src/game.ts
index 55a2b96..2cfd4ee 100644
--- a/src/game.ts
+++ b/src/game.ts
@@ -1,4 +1,5 @@
-import { Coord, GameBoard, Move, MoveResult } from './types';
+import { Coord, Move, MoveResult } from './types';
+import { GameBoard } from "./game-board";
export interface IGame {
size: Coord;
diff --git a/src/local-game.ts b/src/local-game.ts
index 48a317f..ec5a19b 100644
--- a/src/local-game.ts
+++ b/src/local-game.ts
@@ -1,6 +1,7 @@
import { Game, IGame } from './game';
import { Coord, MoveResult } from './types';
import { ListenerManager } from './listeners';
+import { showModal } from './modal';
const classNames = {
selectedPiece: 'selected-piece',
@@ -59,7 +60,8 @@ export class LocalGame extends Game {
dom.cells()[destIndex].appendChild(destPiece);
// start animations
- const destAnimation = dom.cells()[destIndex].animate([{ opacity: 0 }, { opacity: 1 }], { duration: 1000, iterations: 1 });
+ const animDurationMs = 500;
+ const destAnimation = dom.cells()[destIndex].animate([{ opacity: 0 }, { opacity: 1 }], { duration: animDurationMs, iterations: 1 });
const sourceAnimation = piece.animate(
[
{
@@ -73,7 +75,7 @@ export class LocalGame extends Game {
height: '5px',
},
],
- { duration: 1000, iterations: 1 }
+ { duration: animDurationMs, iterations: 1 }
);
// await animations
@@ -97,8 +99,9 @@ export class LocalGame extends Game {
}
public async handleMoveResult(result: MoveResult) {
- if (result.isTerminal) {
- alert(result.message);
+ const { isTerminal, message } = result;
+ if (isTerminal) {
+ await showModal(message);
document.location.reload();
} else {
this.resetUi();
@@ -144,34 +147,47 @@ export class LocalGame extends Game {
listenerManager.removeListeners();
}
public setActiveUi() {
- const { dom, currentPlayerId, listenerManager, selectedPiece } = this;
-
- // mark active pieces
- const piecesAndPositions = this.getPiecesAndCoords(currentPlayerId);
- piecesAndPositions.forEach(({ coord: source, piece }) => {
- piece.classList.add(classNames.activePlayerPiece);
- listenerManager.addListener(piece, 'click', () => {
- // clear current selection
- if (selectedPiece !== null) {
- selectedPiece.classList.remove(classNames.selectedPiece);
- dom.cells().forEach((cell) => cell.classList.remove(classNames.possibleMove));
- }
+ const { dom, currentPlayerId, listenerManager } = this;
+
+ const clearPossibleMovesBlink = () =>
+ dom.cells().forEach((cell) => cell.classList.remove(classNames.possibleMove));
+ const activePieceListener = (piece: Element, source: Coord) => {
+ // clear current selection
+ if (this.selectedPiece !== null) {
+ this.selectedPiece.classList.remove(classNames.selectedPiece);
+ clearPossibleMovesBlink();
+ listenerManager.removeListeners();
+ hookupActivePieces();
+ }
- // set as selected
- piece.classList.add(classNames.selectedPiece);
- this.selectedPiece = piece;
-
- // hook up allowed moves for:
- // 1. blink
- // 2. complete the move
- const allowedMoves = this.getValidMoves(source);
- allowedMoves.forEach((dest: Coord) => {
- const cell = dom.cells()[this.coordToBoardIndex(dest)];
- cell.classList.add(classNames.possibleMove);
- listenerManager.addListener(cell, 'click', async () => await this.makeMove(source, dest));
+ // set as selected
+ this.selectedPiece = piece;
+ piece.classList.add(classNames.selectedPiece);
+
+ // hook up allowed moves for:
+ // 1. blink
+ // 2. complete the move
+ const allowedMoves = this.getValidMoves(source);
+ allowedMoves.forEach((dest: Coord) => {
+ const cell = dom.cells()[this.coordToBoardIndex(dest)];
+ cell.classList.add(classNames.possibleMove);
+ listenerManager.addListener(cell, 'click', async () => {
+ clearPossibleMovesBlink();
+ await this.makeMove(source, dest);
});
});
- });
+ };
+
+ const hookupActivePieces = () => {
+ // mark active pieces
+ const piecesAndPositions = this.getPiecesAndCoords(currentPlayerId);
+ piecesAndPositions.forEach(({ coord: source, piece }) => {
+ piece.classList.add(classNames.activePlayerPiece);
+ listenerManager.addListener(piece, 'click', () => activePieceListener(piece, source));
+ });
+ }
+
+ hookupActivePieces();
}
private init() {
@@ -186,7 +202,8 @@ export class LocalGame extends Game {
for (let c = 0; c < size.col; c++) {
const td = document.createElement('td');
tr.appendChild(td);
- td.className = isWhite ? 'white' : 'black';
+ td.classList.add('tile');
+ td.classList.add(isWhite ? 'white' : 'black');
isWhite = !isWhite;
}
isWhite = !isWhite;
@@ -201,8 +218,8 @@ export class LocalGame extends Game {
this.getPlayerPositions(player1Id).forEach((coord) => createPiece(true, player1Id, coord));
this.getPlayerPositions(player2Id).forEach((coord) => createPiece(false, player2Id, coord));
- dom.p1TurnText().textContent = `${player1Name}'s turn`
- dom.p2TurnText().textContent = `${player2Name}'s turn`
+ dom.p1TurnText().textContent = `${player1Name}`
+ dom.p2TurnText().textContent = `${player2Name}`
// hook up buttons
let blockActions = false;
diff --git a/src/modal.ts b/src/modal.ts
new file mode 100644
index 0000000..92770d6
--- /dev/null
+++ b/src/modal.ts
@@ -0,0 +1,59 @@
+/*
+ <div class="modal">
+ <div class="modal-content">
+ <span class="close">Close</span>
+ <p></p>
+ </div>
+ </div>
+*/
+export const showModal = (text): Promise<void> => {
+ const modal = document.createElement('div');
+ modal.classList.add('modal');
+
+ const modalContentDiv = document.createElement('div');
+ modalContentDiv.classList.add('modal-content');
+ modal.appendChild(modalContentDiv);
+
+ const closeSpan = document.createElement('span');
+ closeSpan.classList.add('close');
+ closeSpan.innerText = 'x';
+ modalContentDiv.appendChild(closeSpan);
+
+ const modalContentPara = document.createElement('p');
+ modalContentPara.id = 'modal-content';
+ modalContentPara.innerText = text;
+ modalContentDiv.appendChild(modalContentPara);
+
+ document.body.appendChild(modal);
+
+ // Get the <span> element that closes the modal
+ var span = document.getElementsByClassName("close")[0] as HTMLSpanElement;
+
+ var content = document.getElementById('modal-content') as HTMLParagraphElement;
+ content.innerText = text;
+
+ const promise = new Promise<void>((resolve, reject) => {
+ let isModalOpen = true;
+ const closeModal = () => {
+ modal.style.display = "none";
+ isModalOpen = false;
+ modal.parentNode.removeChild(modal);
+ resolve();
+ }
+
+ // When the user clicks on <span> (x), close the modal
+ span.onclick = () => closeModal();
+
+ // When the user clicks anywhere outside of the modal, close it
+ window.onclick = (event: MouseEvent) => {
+ if (event.target == modal) {
+ closeModal();
+ }
+ };
+ });
+
+ // show modal
+ modal.style.display = "block";
+
+ return promise;
+}
\ No newline at end of file
diff --git a/src/remote-game.ts b/src/remote-game.ts
index 11ec495..8d642b3 100644
--- a/src/remote-game.ts
+++ b/src/remote-game.ts
@@ -1,5 +1,6 @@
import Peer, { PeerJSOption } from 'peerjs';
import { LocalGame } from './local-game';
+import { showModal } from './modal';
import { useSessionStorage } from './storage';
import { Coord, Move, MoveResult } from './types';
import { delay, generateHash, showSnackbar } from './utils';
@@ -19,7 +20,8 @@ interface PeerData {
const peerOptions: PeerJSOption = { debug: 0 };
const { getter: getData, setter: setData } = useSessionStorage<LocalData>('dao-game', { id: undefined, peerId: undefined });
const otherMouseElement = document.getElementById('other-mouse');
-const shareLinkElement = document.getElementById('shareLink');
+const shareContainer = document.getElementById('share-container');
+const shareLinkButton = document.getElementById('share-link');
const openInNewTab = (url: string) => {
const openInSystemDefault = false;
if (openInSystemDefault) {
@@ -75,8 +77,10 @@ export class PeerGame extends LocalGame {
return result;
}
public override async handleMoveResult(result: MoveResult): Promise<void> {
+ const { isTerminal, message } = result;
if (result.isTerminal) {
- alert(result.message);
+ await showModal(message);
+ sessionStorage.clear();
const { protocol, host, pathname } = location;
const dir = dropFileExtension(pathname);
const newUrl = `${protocol}//${host}${dir}`
@@ -98,7 +102,7 @@ export class PeerGame extends LocalGame {
return;
}
- shareLinkElement.classList.add('invisible');
+ shareContainer.classList.add('invisible');
id = peer.id;
peerId = conn.peer;
@@ -185,16 +189,12 @@ export class PeerGame extends LocalGame {
} else {
queryParams.set('peerId', id);
const newSearch = queryParams.toString();
- const newUrl = `microsoft-edge:${protocol}//${host}${pathname}?${newSearch}`;
- const link = document.createElement('a');
- link.innerText = newUrl;
- link.href = newUrl;
- link.target = '_blank';
- // link.onclick = () => {
- // navigator.clipboard.writeText(newUrl);
- // showSnackbar('Url copied to clipboard');
- // };
- shareLinkElement.appendChild(link);
+ const newUrl = `${protocol}//${host}${pathname}?${newSearch}`;
+ shareLinkButton.onclick = () => {
+ navigator.clipboard.writeText(newUrl);
+ showSnackbar('Url copied to clipboard');
+ };
+ shareContainer.classList.remove('invisible');
// openInNewTab(newUrl);
}
});
diff --git a/src/types.ts b/src/types.ts
index 2af1ea2..9dfa8e0 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -62,65 +62,3 @@ export interface MoveResult {
isTerminal: boolean;
message?: string;
}
-
-export class GameBoard {
- private board: (string | null)[];
- public readonly size: Coord;
- public readonly player1Id: string;
- public readonly player2Id: string;
-
- public constructor(player1Id: string, player2Id: string) {
- // default board
- const defaultBoard = []
- .concat([player1Id, null, null, player2Id])
- .concat([null, player1Id, player2Id, null])
- .concat([null, player2Id, player1Id, null])
- .concat([player2Id, null, null, player1Id]);
- const testBoard = []
- .concat([player1Id, null, player2Id, player2Id])
- .concat([player1Id, null, player2Id, null])
- .concat([player1Id, null, null, null])
- .concat([null, player1Id, null, player2Id]);
- this.board = testBoard;
- this.size = new Coord(4, 4);
- this.player1Id = player1Id;
- this.player2Id = player2Id;
- }
- public move(source: Coord, dest: Coord): void {
- const isP1 = this.get(source);
- if (isP1 === null) throw `Source ${source} points to an empty space`;
- this.set(source, null);
- this.set(dest, isP1);
- }
- public get(coord: Coord): boolean | null {
- const index = this.coordToBoardIndex(coord);
- if (!Number.isSafeInteger(index) || index < 0 || index >= this.board.length) throw `Index ${index} is out of bounds`;
- const id = this.board[index];
- const isP1 = id === null ? null : id === this.player1Id;
- return isP1;
- }
- public getCoords(id: string): Coord[] {
- const coords = this.board
- .map((pieceId, index) => {
- const isTarget = pieceId === id;
- return isTarget ? this.boardIndexToCoord(index) : null;
- })
- .filter((coord) => coord !== null);
- return coords;
- }
- private set(coord: Coord, isP1: boolean | null): void {
- const index = this.coordToBoardIndex(coord);
- if (!Number.isSafeInteger(index) || index < 0 || index >= this.board.length) throw `Index ${index} is out of bounds`;
- this.board[index] = isP1 === null ? null : isP1 ? this.player1Id : this.player2Id;
- }
- public coordToBoardIndex(coord: Coord): number {
- const { row, col } = coord;
- const index = row * this.size.col + col;
- return index;
- }
- private boardIndexToCoord(index: number): Coord {
- const col = index % this.size.col;
- const row = Math.floor(index / this.size.col);
- return new Coord(row, col);
- }
-}