fuzzyworld

dao-game / commit

Got local fully working.

1e41e9b4e9650c477975d1cdfc08b75cd1c8e33a · Pavel Safronov · 2022-03-17 18:04:14 -0700 · parents c3e44de21c70

Got local fully working.
 src/game.ts        |   4 +-
 src/local-game.ts  | 414 ++++++++++++++++++++++++++++-------------------------
 src/remote-game.ts |  24 ++++
 src/storage.ts     |  18 +++
 4 files changed, 260 insertions(+), 200 deletions(-)

diff --git a/src/game.ts b/src/game.ts
index 5c5a68d..4b58f6d 100644
--- a/src/game.ts
+++ b/src/game.ts
@@ -7,7 +7,7 @@ export interface IGame {
     player2Id: string;
     currentPlayerId: string;
     canUndoMove: boolean;
-    getPlayerPositions: (isP1: boolean) => Coord[];
+    getPlayerPositions: (playerId: string) => Coord[];
     coordToBoardIndex: (coord: Coord) => number;
     getValidMoves: (start: Coord) => Coord[];
     makeMove: (source: Coord, dest: Coord) => MoveResult;
@@ -34,7 +34,7 @@ export class Game implements IGame {
     public get player1Id(): string { return this._player1Id; }
     public get player2Id(): string { return this._player2Id; }
     public get moveHistory(): Move[] { return this._moveHistory; }
-    public getPlayerPositions(isP1: boolean): Coord[] { return this.gameBoard.getCoords(isP1 ? this._player1Id : this.player2Id); }
+    public getPlayerPositions(playerId: string): Coord[] { return this.gameBoard.getCoords(playerId); }
     public coordToBoardIndex(coord: Coord): number { return this.gameBoard.coordToBoardIndex(coord); }
     public makeMove(source: Coord, dest: Coord): MoveResult {
         const checkForWin = (isP1Turn: boolean): MoveResult => {
diff --git a/src/local-game.ts b/src/local-game.ts
index 1b2dcda..a8d9ffa 100644
--- a/src/local-game.ts
+++ b/src/local-game.ts
@@ -9,222 +9,240 @@ const classNames = {
     possibleMove: "possible-move",
 };
 
-let selectedPiece: Element | null = null;
-const game: IGame = new Game('Red', 'Blue');
-const listenerManager: ListenerManager = new ListenerManager();
-
-// DOM references
-const dom = {
-    defaultP1Piece: document.getElementById("default-p1-piece"),
-    defaultP2Piece: document.getElementById("default-p2-piece"),
-    resetButton: document.getElementById("reset-button") as HTMLInputElement,
-    undoButton: document.getElementById("undo-button") as HTMLInputElement,
-    cells: () => Array.from(document.querySelectorAll("td")),
-    allPieces: () => Array.from(document.querySelectorAll(".piece")),
-    p1TurnText: () => Array.from(document.querySelectorAll(".p1-turn-text")),
-    p2TurnText: () => Array.from(document.querySelectorAll(".p2-turn-text")),
-};
-
-const resetBoard = () => {
-    // reset Cells
-    dom.cells().forEach(cell => cell.classList.remove(classNames.possibleMove));
-    // reset pieces
-    dom.allPieces().forEach(piece => {
-        piece.classList.remove(classNames.activePlayerPiece)
-        piece.classList.remove(classNames.selectedPiece)
-    });
-    // remove all listeners
-    listenerManager.removeListeners();
-};
+export class LocalGame extends Game {
+    selectedPiece: Element | null = null;
+    listenerManager: ListenerManager = new ListenerManager();
+
+    // DOM references
+    private readonly dom = {
+        defaultP1Piece: document.getElementById("default-p1-piece"),
+        defaultP2Piece: document.getElementById("default-p2-piece"),
+        resetButton: document.getElementById("reset-button") as HTMLInputElement,
+        undoButton: document.getElementById("undo-button") as HTMLInputElement,
+        cells: () => Array.from(document.querySelectorAll("td")),
+        allPieces: () => Array.from(document.querySelectorAll(".piece")),
+        p1TurnText: () => Array.from(document.querySelectorAll(".p1-turn-text")),
+        p2TurnText: () => Array.from(document.querySelectorAll(".p2-turn-text")),
+    }
 
-/**
- * Animates the motion of piece moving from source to dest
- * @param source Starting Coord
- * @param dest Ending Coord
- * @param playerId Player id
- */
-const movePiece = async (source: Coord, dest: Coord) => {
-    const sourceIndex = game.coordToBoardIndex(source);
-    const destIndex = game.coordToBoardIndex(dest);
-
-    // find current piece
-    const piece = dom.cells()[sourceIndex].children[0];
-    // unselect piece
-    piece.classList.remove(classNames.selectedPiece);
-
-    // create clone for new position
-    const destPiece = piece.cloneNode(true);
-    // add to destination cell
-    dom.cells()[destIndex].appendChild(destPiece);
-
-    // start animations
-    const destAnimation = dom.cells()[destIndex].animate(
-        [{ opacity: 0 }, { opacity: 1 }],
-        { duration: 1000, iterations: 1, });
-    const sourceAnimation = piece.animate(
-        [{
-            opacity: 1,
-            width: "45px",
-            height: "45px",
-        }, {
-            opacity: 0,
-            width: "5px",
-            height: "5px",
-        }],
-        { duration: 1000, iterations: 1 }
-    );
-
-    // await animations
-    await Promise.all([sourceAnimation.finished, destAnimation.finished]);
-
-    // remove old piece
-    piece.parentNode.removeChild(piece);
-};
+    private resetBoard() {
+        const { dom, listenerManager } = this;
+        // reset Cells
+        dom.cells().forEach(cell => cell.classList.remove(classNames.possibleMove));
+        // reset pieces
+        dom.allPieces().forEach(piece => {
+            piece.classList.remove(classNames.activePlayerPiece)
+            piece.classList.remove(classNames.selectedPiece)
+        });
+        // remove all listeners
+        listenerManager.removeListeners();
+    }
 
-const nextMove = () => {
-    const setActivePlayerPieces = () => {
-        const selectPiece = (piece: Element, source: Coord) => {
-            // clear current selection
-            if (selectedPiece !== null) {
-                selectedPiece.classList.remove(classNames.selectedPiece);
-                dom.cells().forEach(cell => cell.classList.remove(classNames.possibleMove));
-            }
+    /**
+     * Animates the motion of piece moving from source to dest
+     * @param source Starting Coord
+     * @param dest Ending Coord
+     * @param playerId Player id
+     */
+    private async movePiece(source: Coord, dest: Coord) {
+        const { dom } = this;
+        const sourceIndex = this.coordToBoardIndex(source);
+        const destIndex = this.coordToBoardIndex(dest);
+
+        // find current piece
+        const piece = dom.cells()[sourceIndex].children[0];
+        // unselect piece
+        piece.classList.remove(classNames.selectedPiece);
+
+        // create clone for new position
+        const destPiece = piece.cloneNode(true);
+        // add to destination cell
+        dom.cells()[destIndex].appendChild(destPiece);
+
+        // start animations
+        const destAnimation = dom.cells()[destIndex].animate(
+            [{ opacity: 0 }, { opacity: 1 }],
+            { duration: 1000, iterations: 1, });
+        const sourceAnimation = piece.animate(
+            [{
+                opacity: 1,
+                width: "45px",
+                height: "45px",
+            }, {
+                opacity: 0,
+                width: "5px",
+                height: "5px",
+            }],
+            { duration: 1000, iterations: 1 }
+        );
+
+        // await animations
+        await Promise.all([sourceAnimation.finished, destAnimation.finished]);
+
+        // remove old piece
+        piece.parentNode.removeChild(piece);
+    }
 
-            // set as selected
-            piece.classList.add(classNames.selectedPiece);
-            selectedPiece = piece;
-
-            // hook up allowed moves for:
-            // 1. blink
-            // 2. complete the move
-            const allowedMoves = game.getValidMoves(source);
-            allowedMoves.map((dest: Coord) => {
-                const cell = dom.cells()[game.coordToBoardIndex(dest)];
-                cell.classList.add(classNames.possibleMove);
-                listenerManager.addListener(cell, "click", async () => await makeMove(source, dest));
+    public nextMove() {
+        const setActivePlayerPieces = () => {
+            const {
+                dom,
+                listenerManager,
+                selectedPiece,
+                currentPlayerId, player1Id, player2Id,
+            } = this;
+            const selectPiece = (piece: Element, source: Coord) => {
+                // clear current selection
+                if (selectedPiece !== null) {
+                    selectedPiece.classList.remove(classNames.selectedPiece);
+                    dom.cells().forEach(cell => cell.classList.remove(classNames.possibleMove));
+                }
+
+                // 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.map((dest: Coord) => {
+                    const cell = dom.cells()[this.coordToBoardIndex(dest)];
+                    cell.classList.add(classNames.possibleMove);
+                    listenerManager.addListener(cell, "click", async () => await finishMove(source, dest));
+                });
+            };
+
+            // complete the move from source to dest
+            const finishMove = async (source: Coord, dest: Coord) => {
+                listenerManager.removeListeners();
+
+                const result = this.makeMove(source, dest);
+                await this.movePiece(source, dest);
+
+                if (result.isTerminal) {
+                    alert(result.message);
+                    document.location.reload();
+                } else {
+                    this.nextMove();
+                }
+            };
+
+            // mark active pieces
+            const allCells = dom.cells();
+            const playerPositions = this.getPlayerPositions(currentPlayerId);
+            const piecesAndPositions = playerPositions.map(coord => {
+                const index = this.coordToBoardIndex(coord);
+                const cell = allCells[index];
+                const piece = cell.children[0];
+                return { coord, piece };
             });
+            piecesAndPositions
+                .forEach(({ coord, piece }) => {
+                    piece.classList.add(classNames.activePlayerPiece);
+                    listenerManager.addListener(piece, "click", event => selectPiece(piece, coord));
+                });
         };
 
-        // complete the move from source to dest
-        const makeMove = async (source: Coord, dest: Coord) => {
-            listenerManager.removeListeners();
-
-            const result = game.makeMove(source, dest);
-            await movePiece(source, dest);
+        const {
+            dom,
+            currentPlayerId, player1Id, player2Id,
+            canUndoMove, moveHistory,
+        } = this;
+
+        // reset text
+        dom.p1TurnText().forEach((t) => t.classList.remove(classNames.selectedPlayerText));
+        dom.p2TurnText().forEach((t) => t.classList.remove(classNames.selectedPlayerText));
+
+        // changePlayer
+        // 1. flip flag
+        // 2. set text
+        // 3. set click callbacks
+        const isP1Turn = currentPlayerId === player1Id;
+        if (isP1Turn) {
+            dom.p1TurnText().forEach((t) => t.classList.add(classNames.selectedPlayerText));
+        } else {
+            dom.p2TurnText().forEach((t) => t.classList.add(classNames.selectedPlayerText));
+        }
 
-            if (result.isTerminal) {
-                alert(result.message);
-                document.location.reload();
-            } else {
-                nextMove();
-            }
-        };
+        dom.undoButton.disabled = canUndoMove;
+        if (moveHistory.length === 0) {
+            dom.undoButton.disabled = true;
+            dom.resetButton.disabled = true;
+        } else {
+            dom.undoButton.disabled = false;
+            dom.resetButton.disabled = false;
+        }
 
-        // mark active pieces
-        const allCells = dom.cells();
-        const playerPositions = game.getPlayerPositions(game.currentPlayerId === game.player1Id);
-        const piecesAndPositions = playerPositions.map(coord => {
-            const index = game.coordToBoardIndex(coord);
-            const cell = allCells[index];
-            const piece = cell.children[0];
-            return { coord, piece };
-        });
-        piecesAndPositions
-            .forEach(({ coord, piece }) => {
-                piece.classList.add(classNames.activePlayerPiece);
-                listenerManager.addListener(piece, "click", event => selectPiece(piece, coord));
-            });
-    };
-
-    // reset text
-    dom.p1TurnText().forEach((t) => t.classList.remove(classNames.selectedPlayerText));
-    dom.p2TurnText().forEach((t) => t.classList.remove(classNames.selectedPlayerText));
-
-    // changePlayer
-    // 1. flip flag
-    // 2. set text
-    // 3. set click callbacks
-    const isP1Turn = game.currentPlayerId === game.player1Id;
-    if (isP1Turn) {
-        dom.p1TurnText().forEach((t) => t.classList.add(classNames.selectedPlayerText));
-    } else {
-        dom.p2TurnText().forEach((t) => t.classList.add(classNames.selectedPlayerText));
-    }
+        this.selectedPiece = null;
 
-    dom.undoButton.disabled = game.canUndoMove;
-    if (game.moveHistory.length === 0) {
-        dom.undoButton.disabled = true;
-        dom.resetButton.disabled = true;
-    } else {
-        dom.undoButton.disabled = false;
-        dom.resetButton.disabled = false;
+        this.resetBoard();
+        setActivePlayerPieces();
     }
 
-    selectedPiece = null;
-
-    resetBoard();
-    setActivePlayerPieces();
-};
-
-const init = () => {
-    // create board and pieces
-    let isWhite = true;
-    const table = document.getElementById("table");
-    for (let r = 0; r < game.size.row; r++) {
-        const tr = document.createElement("tr");
-        table.appendChild(tr);
-        for (let c = 0; c < game.size.col; c++) {
-            const td = document.createElement("td");
-            tr.appendChild(td);
-            td.className = isWhite ? "white" : "black";
+    public init() {
+        const { dom, player1Id, player2Id, size } = this;
+
+        // create board and pieces
+        let isWhite = true;
+        const table = document.getElementById("table");
+        for (let r = 0; r < size.row; r++) {
+            const tr = document.createElement("tr");
+            table.appendChild(tr);
+            for (let c = 0; c < size.col; c++) {
+                const td = document.createElement("td");
+                tr.appendChild(td);
+                td.className = isWhite ? "white" : "black";
+                isWhite = !isWhite;
+            }
             isWhite = !isWhite;
         }
-        isWhite = !isWhite;
-    }
-    const createPiece = (isP1Turn: boolean, pieceId: string, coord: Coord) => {
-        const defaultPiece = isP1Turn
-            ? dom.defaultP1Piece
-            : dom.defaultP2Piece;
-        const newPiece = defaultPiece.cloneNode(
-            true
-        ) as HTMLParagraphElement;
-        newPiece.id = pieceId;
-        const index = game.coordToBoardIndex(coord);
-        dom.cells()[index].appendChild(newPiece);
-    };
-    game.getPlayerPositions(true).forEach(coord => createPiece(true, game.player1Id, coord));
-    game.getPlayerPositions(false).forEach(coord => createPiece(false, game.player2Id, coord));
-
-    // hook up buttons
-    let blockActions = false;
-    dom.resetButton.addEventListener("click", () => {
-        if (blockActions) {
-            return;
-        }
+        const createPiece = (isP1Turn: boolean, pieceId: string, coord: Coord) => {
+            const defaultPiece = isP1Turn
+                ? dom.defaultP1Piece
+                : dom.defaultP2Piece;
+            const newPiece = defaultPiece.cloneNode(
+                true
+            ) as HTMLParagraphElement;
+            newPiece.id = pieceId;
+            const index = this.coordToBoardIndex(coord);
+            dom.cells()[index].appendChild(newPiece);
+        };
+        this.getPlayerPositions(player1Id).forEach(coord => createPiece(true, player1Id, coord));
+        this.getPlayerPositions(player2Id).forEach(coord => createPiece(false, player2Id, coord));
+
+        // hook up buttons
+        let blockActions = false;
+        dom.resetButton.addEventListener("click", () => {
+            if (blockActions) {
+                return;
+            }
 
-        blockActions = true;
-        if (confirm("Reset?")) {
-            window.location.reload();
-        }
-        blockActions = false;
-    });
-    dom.undoButton.addEventListener("click", async () => {
-        if (blockActions) {
-            return;
-        }
+            blockActions = true;
+            if (confirm("Reset?")) {
+                window.location.reload();
+            }
+            blockActions = false;
+        });
+        dom.undoButton.addEventListener("click", async () => {
+            if (blockActions) {
+                return;
+            }
 
-        blockActions = true;
+            blockActions = true;
 
-        const move = game.undoMove();
-        await movePiece(move.dest, move.source);
-        nextMove();
+            const move = this.undoMove();
+            await this.movePiece(move.dest, move.source);
+            this.nextMove();
 
-        blockActions = false;
-    });
-};
+            blockActions = false;
+        });
+    }
+}
 
 // start!
 export const playGame = () => {
-    init();
-    nextMove();
+    const lg = new LocalGame('Red', 'Blue');
+    lg.init();
+    lg.nextMove();
 };
diff --git a/src/remote-game.ts b/src/remote-game.ts
new file mode 100644
index 0000000..4dd362f
--- /dev/null
+++ b/src/remote-game.ts
@@ -0,0 +1,24 @@
+import Peer from 'peerjs'
+import { LocalGame } from './local-game';
+import { useLocalStorage } from './storage';
+
+interface LocalData {
+    selfPeerId: string;
+    otherPeerIds: string[]
+}
+const { getter: getData, setter: setData } = useLocalStorage<LocalData>('dao-game',
+    { selfPeerId: `dao-game-${prompt('Name?', 'Red') ?? 'Red'}`, otherPeerIds: ['dao-game-Blue'] });
+
+export class PeerGame extends LocalGame {
+    public constructor() {
+        super('Local', 'Remote');
+
+        const { selfPeerId, otherPeerIds } = getData();
+        const peer = new Peer(selfPeerId);
+        if (otherPeerIds.length > 0) {
+            const otherPeer = otherPeerIds[0];
+            peer.connect(otherPeer);
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/src/storage.ts b/src/storage.ts
new file mode 100644
index 0000000..c45bea2
--- /dev/null
+++ b/src/storage.ts
@@ -0,0 +1,18 @@
+export interface StorageInterface<T> {
+    getter: () => T,
+    setter: (data: T) => void
+}
+export const useLocalStorage = <T>(key: string, defaultData?: T): StorageInterface<T> => {
+    return {
+        getter: () => {
+            let item = localStorage.getItem(key);
+            if (item === null && defaultData !== undefined) {
+                item = JSON.stringify(defaultData);
+                localStorage.setItem(key, item);
+            }
+            const data = JSON.parse(item) as T;
+            return data;
+        },
+        setter: (data: T) => localStorage.setItem(key, JSON.stringify(data as T))
+    };
+}
\ No newline at end of file