fuzzyworld

dao-game / commit

Mouse position transmission works, but ATM we're always starting anew.

c3c52dafd09abee220bc21d923ab3c2719d017c4 · Pavel Safronov · 2022-03-18 14:45:51 -0700 · parents 7e7f049021f7

Mouse position transmission works, but ATM we're always starting anew.
Starting to use Prettier.
 .prettierrc        |   7 ++
 public/index.html  | 111 ++++++++++++------------
 public/online.html |  10 +--
 public/styles.css  | 250 +++++++++++++++++++++++++++++------------------------
 src/remote-game.ts | 136 +++++++++++++++++++++--------
 src/storage.ts     |  41 +++++----
 6 files changed, 329 insertions(+), 226 deletions(-)

diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..91ef31e
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,7 @@
+{
+  "tabWidth": 2,
+  "useTabs": false,
+  "semi": true,
+  "trailingComma": "es5",
+  "singleQuote": true
+}
diff --git a/public/index.html b/public/index.html
index 766b639..2bcc389 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1,62 +1,67 @@
 <!DOCTYPE html>
 <html lang="en">
 
-<head>
-    <meta charset="utf-8" />
-    <title>Dao Game</title>
-    <meta name="viewport" content="width=device-width, initial-scale=1" />
-    <link rel="icon" href="yin-yang.svg" type="image/svg+xml" />
-    <link rel="stylesheet" href="styles.css" />
-    <script defer src="bundle.js"></script>
-</head>
-
-<body>
-    <div class="invisible">
-        <p class="piece p1-piece" id="default-p1-piece"></p>
-        <p class="piece p2-piece" id="default-p2-piece"></p>
-    </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>
+    <head>
+        <meta charset="utf-8" />
+        <title>Dao Game</title>
+        <meta name="viewport" content="width=device-width, initial-scale=1" />
+        <link rel="icon" href="yin-yang.svg" type="image/svg+xml" />
+        <link rel="stylesheet" href="styles.css" />
+        <script defer src="bundle.js"></script>
+    </head>
+
+    <body>
+        <div class="invisible">
+            <p class="piece p1-piece" id="default-p1-piece"></p>
+            <p class="piece p2-piece" id="default-p2-piece"></p>
+        </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>
+            </div>
+
+            <table id="table"></table>
+
+            <div class="controls">
+                <button type="button" class="button" id="undo-button">
+                    Undo
+                </button>
+                <span class="divider">|</span>
+                <button type="button" class="button" id="reset-button">
+                    Reset
+                </button>
+            </div>
+        </main>
+        <hr />
+
+        <div class="title">
+            <p>Board game
+                <a href="https://en.wikipedia.org/wiki/Dao_(game)"
+                    target="_blank" rel="noopener">Dao</a>.
+            </p>
         </div>
 
-        <table id="table"></table>
+        <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.
+        </div>
+        <hr />
 
-        <div class="controls">
-            <button type="button" class="button" id="undo-button">
-                Undo
-            </button>
-            <span class="divider">|</span>
-            <button type="button" class="button" id="reset-button">
-                Reset
-            </button>
+        <div class="info">
+            <p>
+                Here is the
+                <a href="https://gitlab.com/fuzzy-rp/dao-game" target="_blank"
+                    rel="noopener">source code</a>
+            </p>
         </div>
-    </main>
-    <hr />
-
-    <div class="title">
-        <p>Board game
-            <a href="https://en.wikipedia.org/wiki/Dao_(game)" target="_blank" rel="noopener">Dao</a>.
-        </p>
-    </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.
-    </div>
-    <hr />
-
-    <div class="info">
-        <p>
-            Here is the
-            <a href="https://gitlab.com/fuzzy-rp/dao-game" target="_blank" rel="noopener">source code</a>
-        </p>
-    </div>
-</body>
+
+        <canvas id="back-canvas"></canvas>
+
+    </body>
 
 </html>
\ No newline at end of file
diff --git a/public/online.html b/public/online.html
index 1b5fc5d..ad554d7 100644
--- a/public/online.html
+++ b/public/online.html
@@ -36,9 +36,7 @@
                 <a
                     href="https://en.wikipedia.org/wiki/Dao_(game)"
                     target="_blank"
-                    rel="noopener"
-                    >Dao</a
-                >.
+                    rel="noopener">Dao</a>.
             </p>
         </div>
 
@@ -57,10 +55,10 @@
                 <a
                     href="https://gitlab.com/fuzzy-rp/dao-game"
                     target="_blank"
-                    rel="noopener"
-                    >source code</a
-                >
+                    rel="noopener">source code</a>
             </p>
         </div>
+
+        <canvas id="back-canvas"></canvas>
     </body>
 </html>
diff --git a/public/styles.css b/public/styles.css
index 021b0df..6f25d87 100644
--- a/public/styles.css
+++ b/public/styles.css
@@ -1,205 +1,225 @@
 :root {
-    --default-size: 5px;
-    --active-size: 40px;
-    --cell-size: 80px;
-    /* --p1-color: crimson;
+  --default-size: 5px;
+  --active-size: 40px;
+  --cell-size: 80px;
+  /* --p1-color: crimson;
     --p2-color: mediumblue; */
 }
 
 * {
-    margin: auto;
+  margin: auto;
 }
 
 main {
-    display: flex;
-    flex-direction: column;
-    justify-content: space-between;
-    align-items: center;
-    /* height: 75vh; */
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  align-items: center;
+  /* height: 75vh; */
 }
 
 body {
-    /* overflow: hidden; */
-    margin: 0px;
-    background-color: white;
+  /* overflow: hidden; */
+  margin: 0px;
+  background-color: white;
 }
 
 div {
-    display: flex;
-    justify-content: space-between;
-    /* display: inline;
+  display: flex;
+  justify-content: space-between;
+  /* display: inline;
     flex: 1; */
 }
 
+#back-canvas {
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  z-index: -1000;
+  pointer-events: none;
+}
+
 .invisible {
-    display: none;
+  display: none;
 }
 
 .title {
-    text-align: center;
-    font-family: "open sans";
-    font-size: 40px;
+  text-align: center;
+  font-family: 'open sans';
+  font-size: 40px;
 }
 
 /* tile */
 td {
-    width: var(--cell-size);
-    height: var(--cell-size);
-    text-align: center;
-    border: 2px transparent solid;
+  width: var(--cell-size);
+  height: var(--cell-size);
+  text-align: center;
+  border: 2px transparent solid;
 }
 
 .controls {
-    font-size: 90px;
-    margin-left: 15px;
-    margin-right: 15px;
-    font-family: "open sans";
-    font-size: 55px;
-    border-radius: 10px;
-    padding: 10px;
+  font-size: 90px;
+  margin-left: 15px;
+  margin-right: 15px;
+  font-family: 'open sans';
+  font-size: 55px;
+  border-radius: 10px;
+  padding: 10px;
 }
 
 .divider {
-    font-size: 90px;
-    margin-left: 15px;
-    margin-right: 15px;
+  font-size: 90px;
+  margin-left: 15px;
+  margin-right: 15px;
 }
 
-.p1-turn-text, .p2-turn-text {
-    text-align: center;
-    color: lightgray;
-    background-color: white;
-    /* border: 1px solid black; */
-    border-radius: 10px;
-    padding: 10px;
-    -ms-user-select: none;
-    -webkit-user-select: none;
-    user-select: none;
+.p1-turn-text,
+.p2-turn-text {
+  text-align: center;
+  color: lightgray;
+  background-color: white;
+  /* border: 1px solid black; */
+  border-radius: 10px;
+  padding: 10px;
+  -ms-user-select: none;
+  -webkit-user-select: none;
+  user-select: none;
 }
 
 .p1-turn-text.selected-player-text {
-    background-color: red;
+  background-color: red;
 }
 
 .p2-turn-text.selected-player-text {
-    background-color: blue;
+  background-color: blue;
 }
 
 .selected-player-text {
-    color: white;
+  color: white;
 }
 
 .button {
-    border-radius: 10px;
-    padding: 10px;
-    background-color: orange;
-    font-size: 60px;
-    border-color: transparent;
-    transition: .2s all;
+  border-radius: 10px;
+  padding: 10px;
+  background-color: orange;
+  font-size: 60px;
+  border-color: transparent;
+  transition: 0.2s all;
 }
 .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 */
+  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 {
-    background-color: orange;
+  background-color: orange;
 }
 .black {
-    background-color: wheat;
+  background-color: wheat;
 }
 
 .piece {
-    width: var(--default-size);
-    height: var(--default-size);
-    padding: 10px;
-    border-radius: 50px;
-    animation: activeToDisabled 1s 1;    
-    /* background-image: url(yin-yang.svg); */
+  width: var(--default-size);
+  height: var(--default-size);
+  padding: 10px;
+  border-radius: 50px;
+  animation: activeToDisabled 1s 1;
+  /* background-image: url(yin-yang.svg); */
 }
 
 .p1-piece {
-    background-color: crimson;
-    /* background-image: url(yin-yang.svg); */
+  background-color: crimson;
+  /* background-image: url(yin-yang.svg); */
 }
 .p2-piece {
-    background-color: mediumblue;
-    /* background-image: url(yin-yang.svg); */
+  background-color: mediumblue;
+  /* background-image: url(yin-yang.svg); */
 }
 
 .active-player-piece {
-    width: var(--active-size);
-    height: var(--active-size);
-    animation: disabledToActive 1s 1;
+  width: var(--active-size);
+  height: var(--active-size);
+  animation: disabledToActive 1s 1;
 }
 
 .selected-piece {
-    animation: activePulsing 1s infinite;
+  animation: activePulsing 1s infinite;
 }
 
 .info {
-    text-align: center;
-    font-family: "open sans";
-    font-size: 20px;
+  text-align: center;
+  font-family: 'open sans';
+  font-size: 20px;
 }
 
 .possible-move {
-    animation: blinkingText 1s infinite;
-    border-color: black;
-    filter: drop-shadow(0px 0px 5px black);
+  animation: blinkingText 1s infinite;
+  border-color: black;
+  filter: drop-shadow(0px 0px 5px black);
 }
 @keyframes blinkingText {
-    0%{opacity: 1;}
-    50%{opacity: .6;}
-    100%{opacity: 1;}
+  0% {
+    opacity: 1;
+  }
+  50% {
+    opacity: 0.6;
+  }
+  100% {
+    opacity: 1;
+  }
 }
 
 @keyframes fade-in {
-    0%{opacity: 0;}
-    50%{opacity: .5;}
-    100%{opacity: 1;}    
+  0% {
+    opacity: 0;
+  }
+  50% {
+    opacity: 0.5;
+  }
+  100% {
+    opacity: 1;
+  }
 }
 
 @keyframes disabledToActive {
-    from {
-        width: var(--default-size);
-        height: var(--default-size);
-    }
-    to {
-        width: var(--active-size);
-        height: var(--active-size);
-    }
+  from {
+    width: var(--default-size);
+    height: var(--default-size);
+  }
+  to {
+    width: var(--active-size);
+    height: var(--active-size);
+  }
 }
 @keyframes activeToDisabled {
-    from {
-        width: var(--active-size);
-        height: var(--active-size);
-    }
-    to {
-        width: var(--default-size);
-        height: var(--default-size);
-    }
+  from {
+    width: var(--active-size);
+    height: var(--active-size);
+  }
+  to {
+    width: var(--default-size);
+    height: var(--default-size);
+  }
 }
 
-
 /* .pulsing {
     animation: pulsingSize 1.5s infinite;
 } */
 @keyframes activePulsing {
-    from {
-        width: var(--active-size);
-        height: var(--active-size);
-    }
-    50% {
-        width: 25px;
-        height: 25px;
-    }
-    to {
-        width: var(--active-size);
-        height: var(--active-size);
-    }
+  from {
+    width: var(--active-size);
+    height: var(--active-size);
+  }
+  50% {
+    width: 25px;
+    height: 25px;
+  }
+  to {
+    width: var(--active-size);
+    height: var(--active-size);
+  }
 }
 
 /* breaks */
@@ -291,4 +311,4 @@ td {
     .black-turn-text {
         font-size: 35px;
     }
-} */
\ No newline at end of file
+} */
diff --git a/src/remote-game.ts b/src/remote-game.ts
index 90f579e..fdb631d 100644
--- a/src/remote-game.ts
+++ b/src/remote-game.ts
@@ -1,56 +1,120 @@
-import Peer from 'peerjs'
+import Peer, { PeerJSOption } from 'peerjs';
 import { LocalGame } from './local-game';
 import { useLocalStorage } from './storage';
 
 interface LocalData {
-    selfPeerId: string;
-    otherPeerIds: string[]
+  id: string;
+  peerId: string;
 }
 interface Pos {
-    x: number;
-    y: number;
+  x: number;
+  y: number;
 }
 interface PeerData {
-    mousePos: Pos;
+  mousePos: Pos;
 }
 
 // {
 //     selfPeerId: `dao-game-${prompt('Name?', 'Red') ?? 'Red'}`,
 //     otherPeerIds: []
 // }
-const { getter: getData, setter: setData } = useLocalStorage<LocalData>('dao-game');
+const { getter: getData, setter: setData } = useLocalStorage<LocalData>(
+  'dao-game-v2022-03-18-1412',
+  { id: undefined, peerId: undefined }
+);
 
 export class PeerGame extends LocalGame {
-    peerMousePos: Pos;
-
-    public constructor() {
-        super('Local', 'Remote');
-
-        const { selfPeerId, otherPeerIds } = getData();
-        const peer = new Peer(selfPeerId);
-        peer.on('connection',)
-
-        if (otherPeerIds.length > 0) {
-            const otherPeer = otherPeerIds[0];
-            const conn = peer.connect(otherPeer);
-            conn.on('data', (data: string) => {
-                const peerData = JSON.parse(data) as PeerData;
-                if (peerData && peerData.mousePos) {
-                    this.peerMousePos = peerData.mousePos;
-                }
-            });
-            const main = document.getElementsByTagName('main')[0];
-            main.addEventListener('mousemove', (ev: MouseEvent) => {
-                const peerData: PeerData = { mousePos: { x: ev.x, y: ev.y } };
-                const text = JSON.stringify(peerData);
-                conn.send(text);
-            });
+  peerMousePos: Pos;
+
+  public constructor() {
+    super('Local', 'Remote');
+
+    let { id, peerId } = getData();
+    const main = document.getElementsByTagName('body')[0];
+    const backCanvas = document.getElementById(
+      'back-canvas'
+    ) as HTMLCanvasElement;
+
+    const handleConn = (conn: Peer.DataConnection) => {
+      id = peer.id;
+      peerId = conn.peer;
+      setData({ id, peerId });
+
+      console.log('Adding event listener to main');
+      main.addEventListener('mousemove', (ev: MouseEvent) => {
+        const peerData: PeerData = { mousePos: { x: ev.x, y: ev.y } };
+        const text = JSON.stringify(peerData);
+        // console.log(`Sending data to ${conn.peer}: ${text}`);
+        conn.send(text);
+      });
+
+      conn.on('open', () => {
+        console.log(`Connection to ${conn.peer} opened`);
+        conn.send('{}');
+      });
+      conn.on('data', (data: string) => {
+        // console.log(`Receiving data from ${conn.peer}: ${data}`);
+        const peerData = JSON.parse(data) as PeerData;
+        if (peerData && peerData.mousePos) {
+          this.peerMousePos = peerData.mousePos;
         }
-    }
+      });
+      conn.send('hi!');
+    };
+
+    const { host, protocol, pathname, search } = location;
+    const queryParams = new URLSearchParams(search);
+
+    console.log(`Creating peer connection for ${id}`);
+    const peerOptions: PeerJSOption = { debug: 0 };
+    const peer =
+      id === undefined ? new Peer(peerOptions) : new Peer(id, peerOptions);
+    id = peer.id;
+    window.onunload = () => peer.destroy();
+    // document.addEventListener('close', () => peer.destroy());
+    peer.on('connection', (conn) => {
+      console.log(`Connection established to ${conn.peer}`);
+      handleConn(conn);
+    });
+    peer.on('open', (trueId) => {
+      id = trueId;
+      peerId = queryParams.get('peerId') ?? peerId;
+      setData({ id, peerId });
+      if (peerId) {
+        console.log(`${id} connecting to peer "${peerId}"`);
+        const conn = peer.connect(peerId);
+        handleConn(conn);
+      } else {
+        queryParams.set('peerId', id);
+        const newSearch = queryParams.toString();
+        const newUrl = `microsoft-edge:${protocol}//${host}${pathname}?${newSearch}`;
+        const nw = window.open(newUrl, '_blank');
+      }
+    });
+    peer.on('error', (err) => console.error({ err }));
+
+    const animate = () => {
+      const { peerMousePos } = this;
+      if (peerMousePos !== undefined) {
+        // console.log({ peerMousePos: this.peerMousePos });
+        // this.peerMousePos = undefined;
+        backCanvas.width = window.innerWidth;
+        backCanvas.height = window.innerHeight;
+        const ctx = backCanvas.getContext('2d');
+        ctx.fillStyle = 'green';
+        ctx.beginPath();
+        ctx.ellipse(peerMousePos.x, peerMousePos.y, 50, 50, 0, 0, Math.PI * 2);
+        ctx.closePath();
+        ctx.fill();
+      }
+      requestAnimationFrame(() => animate());
+    };
+    requestAnimationFrame(() => animate());
+  }
 }
 
 export const playGame = () => {
-    const pg = new PeerGame();
-    pg.init();
-    pg.nextMove();
-}
\ No newline at end of file
+  const pg = new PeerGame();
+  pg.init();
+  pg.nextMove();
+};
diff --git a/src/storage.ts b/src/storage.ts
index c45bea2..7e48952 100644
--- a/src/storage.ts
+++ b/src/storage.ts
@@ -1,18 +1,27 @@
 export interface StorageInterface<T> {
-    getter: () => T,
-    setter: (data: T) => void
+  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
+export const useLocalStorage = <T>(
+  key: string,
+  defaultData?: T
+): StorageInterface<T> => {
+  return {
+    getter: () => {
+      let item = sessionStorage.getItem(key);
+      if (item === null && defaultData !== undefined) {
+        item = JSON.stringify(defaultData);
+        sessionStorage.setItem(key, item);
+      }
+      const data = JSON.parse(item) as T;
+      return data;
+    },
+    setter: (data: T) => {
+      const text = JSON.stringify(data as T);
+      console.log(`Saving data to local storage`);
+      console.log(`Key=${key}`);
+      console.log(`Text=${text}`);
+      sessionStorage.setItem(key, text);
+    },
+  };
+};