fuzzyworld

dao-game / commit

Lots of changes:

b3c7b964104011e15976efbee6368ad38b07bda4 · Pavel Safronov · 2022-03-21 18:23:39 -0700 · parents 5805825cf3b7

Lots of changes:
- added debug configs for 3 pages: landing, local game, online game
- standardized some styles
- prettified pages and added clear instructions
- removed all non-class/non-id styles (no more tag-based styles
- using modals for reporting terminal conditions, instead of alerts
 .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);
-  }
-}