fuzzyworld

dao-game / commit

Starting experimenting with multiplayer.

cda3e92d1219edf6f7637647bbdd4dffec110204 · Pavel Safronov · 2022-03-11 00:01:30 -0800 · parents ed1a45fd541f

Starting experimenting with multiplayer.
 .vscode/launch.json |   2 +-
 package-lock.json   | 686 ++++------------------------------------------------
 package.json        |   3 +-
 public/index.html   |  82 +++----
 public/online.html  |  66 +++++
 readme.md           |   5 +
 src/game.ts         | 261 ++++++++++++++------
 src/peering.ts      |  51 ++++
 src/piano.ts        |  36 +++
 src/types.ts        |   8 +-
 10 files changed, 433 insertions(+), 767 deletions(-)

diff --git a/.vscode/launch.json b/.vscode/launch.json
index b9fd332..1b3c68b 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -10,7 +10,7 @@
             "name": "Launch Chrome against localhost",
             "url": "http://localhost:3000/",
             "webRoot": "${workspaceFolder}/src",
-            "preLaunchTask": "task: npm: start"
+            // "preLaunchTask": "task: npm: start"
         }
     ]
 }
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index bb7d6c6..edb445e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,642 +1,8 @@
 {
   "name": "dao-game",
   "version": "1.0.0",
-  "lockfileVersion": 2,
+  "lockfileVersion": 1,
   "requires": true,
-  "packages": {
-    "": {
-      "name": "dao-game",
-      "version": "1.0.0",
-      "license": "ISC",
-      "dependencies": {
-        "esbuild": "^0.14.25"
-      },
-      "devDependencies": {
-        "@types/node": "^17.0.21",
-        "rimraf": "^3.0.2",
-        "ts-node": "^10.6.0",
-        "typescript": "^4.6.2"
-      }
-    },
-    "node_modules/@cspotcode/source-map-consumer": {
-      "version": "0.8.0",
-      "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz",
-      "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==",
-      "dev": true,
-      "engines": {
-        "node": ">= 12"
-      }
-    },
-    "node_modules/@cspotcode/source-map-support": {
-      "version": "0.7.0",
-      "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz",
-      "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==",
-      "dev": true,
-      "dependencies": {
-        "@cspotcode/source-map-consumer": "0.8.0"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@tsconfig/node10": {
-      "version": "1.0.8",
-      "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz",
-      "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==",
-      "dev": true
-    },
-    "node_modules/@tsconfig/node12": {
-      "version": "1.0.9",
-      "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz",
-      "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==",
-      "dev": true
-    },
-    "node_modules/@tsconfig/node14": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz",
-      "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==",
-      "dev": true
-    },
-    "node_modules/@tsconfig/node16": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz",
-      "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==",
-      "dev": true
-    },
-    "node_modules/@types/node": {
-      "version": "17.0.21",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz",
-      "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==",
-      "dev": true
-    },
-    "node_modules/acorn": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
-      "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
-      "dev": true,
-      "bin": {
-        "acorn": "bin/acorn"
-      },
-      "engines": {
-        "node": ">=0.4.0"
-      }
-    },
-    "node_modules/acorn-walk": {
-      "version": "8.2.0",
-      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
-      "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.4.0"
-      }
-    },
-    "node_modules/arg": {
-      "version": "4.1.3",
-      "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
-      "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
-      "dev": true
-    },
-    "node_modules/balanced-match": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
-      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
-      "dev": true
-    },
-    "node_modules/brace-expansion": {
-      "version": "1.1.11",
-      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
-      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
-      "dev": true,
-      "dependencies": {
-        "balanced-match": "^1.0.0",
-        "concat-map": "0.0.1"
-      }
-    },
-    "node_modules/concat-map": {
-      "version": "0.0.1",
-      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
-      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
-      "dev": true
-    },
-    "node_modules/create-require": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
-      "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
-      "dev": true
-    },
-    "node_modules/diff": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
-      "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.3.1"
-      }
-    },
-    "node_modules/esbuild": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.25.tgz",
-      "integrity": "sha512-4JHEIOMNFvK09ziiL+iVmldIhLbn49V4NAVo888tcGFKedEZY/Y8YapfStJ6zSE23tzYPKxqKwQBnQoIO0BI/Q==",
-      "hasInstallScript": true,
-      "bin": {
-        "esbuild": "bin/esbuild"
-      },
-      "engines": {
-        "node": ">=12"
-      },
-      "optionalDependencies": {
-        "esbuild-android-64": "0.14.25",
-        "esbuild-android-arm64": "0.14.25",
-        "esbuild-darwin-64": "0.14.25",
-        "esbuild-darwin-arm64": "0.14.25",
-        "esbuild-freebsd-64": "0.14.25",
-        "esbuild-freebsd-arm64": "0.14.25",
-        "esbuild-linux-32": "0.14.25",
-        "esbuild-linux-64": "0.14.25",
-        "esbuild-linux-arm": "0.14.25",
-        "esbuild-linux-arm64": "0.14.25",
-        "esbuild-linux-mips64le": "0.14.25",
-        "esbuild-linux-ppc64le": "0.14.25",
-        "esbuild-linux-riscv64": "0.14.25",
-        "esbuild-linux-s390x": "0.14.25",
-        "esbuild-netbsd-64": "0.14.25",
-        "esbuild-openbsd-64": "0.14.25",
-        "esbuild-sunos-64": "0.14.25",
-        "esbuild-windows-32": "0.14.25",
-        "esbuild-windows-64": "0.14.25",
-        "esbuild-windows-arm64": "0.14.25"
-      }
-    },
-    "node_modules/esbuild-android-64": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.25.tgz",
-      "integrity": "sha512-L5vCUk7TzFbBnoESNoXjU3x9+/+7TDIE/1mTfy/erAfvZAqC+S3sp/Qa9wkypFMcFvN9FzvESkTlpeQDolREtQ==",
-      "cpu": [
-        "x64"
-      ],
-      "optional": true,
-      "os": [
-        "android"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-android-arm64": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.25.tgz",
-      "integrity": "sha512-4jv5xPjM/qNm27T5j3ZEck0PvjgQtoMHnz4FzwF5zNP56PvY2CT0WStcAIl6jNlsuDdN63rk2HRBIsO6xFbcFw==",
-      "cpu": [
-        "arm64"
-      ],
-      "optional": true,
-      "os": [
-        "android"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-darwin-64": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.25.tgz",
-      "integrity": "sha512-TGp8tuudIxOyWd1+8aYPxQmC1ZQyvij/AfNBa35RubixD0zJ1vkKHVAzo0Zao1zcG6pNqiSyzfPto8vmg0s7oA==",
-      "cpu": [
-        "x64"
-      ],
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-darwin-arm64": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.25.tgz",
-      "integrity": "sha512-oTcDgdm0MDVEmw2DWu8BV68pYuImpFgvWREPErBZmNA4MYKGuBRaCiJqq6jZmBR1x+3y1DWCjez+5uLtuAm6mw==",
-      "cpu": [
-        "arm64"
-      ],
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-freebsd-64": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.25.tgz",
-      "integrity": "sha512-ueAqbnMZ8arnuLH8tHwTCQYeptnHOUV7vA6px6j4zjjQwDx7TdP7kACPf3TLZLdJQ3CAD1XCvQ2sPhX+8tacvQ==",
-      "cpu": [
-        "x64"
-      ],
-      "optional": true,
-      "os": [
-        "freebsd"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-freebsd-arm64": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.25.tgz",
-      "integrity": "sha512-+ZVWud2HKh+Ob6k/qiJWjBtUg4KmJGGmbvEXXW1SNKS7hW7HU+Zq2ZCcE1akFxOPkVB+EhOty/sSek30tkCYug==",
-      "cpu": [
-        "arm64"
-      ],
-      "optional": true,
-      "os": [
-        "freebsd"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-linux-32": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.25.tgz",
-      "integrity": "sha512-3OP/lwV3kCzEz45tobH9nj+uE4ubhGsfx+tn0L26WAGtUbmmcRpqy7XRG/qK7h1mClZ+eguIANcQntYMdYklfw==",
-      "cpu": [
-        "ia32"
-      ],
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-linux-64": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.25.tgz",
-      "integrity": "sha512-+aKHdHZmX9qwVlQmu5xYXh7GsBFf4TWrePgeJTalhXHOG7NNuUwoHmketGiZEoNsWyyqwH9rE5BC+iwcLY30Ug==",
-      "cpu": [
-        "x64"
-      ],
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-linux-arm": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.25.tgz",
-      "integrity": "sha512-aTLcE2VBoLydL943REcAcgnDi3bHtmULSXWLbjtBdtykRatJVSxKMjK9YlBXUZC4/YcNQfH7AxwVeQr9fNxPhw==",
-      "cpu": [
-        "arm"
-      ],
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-linux-arm64": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.25.tgz",
-      "integrity": "sha512-UxfenPx/wSZx55gScCImPtXekvZQLI2GW3qe5dtlmU7luiqhp5GWPzGeQEbD3yN3xg/pHc671m5bma5Ns7lBHw==",
-      "cpu": [
-        "arm64"
-      ],
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-linux-mips64le": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.25.tgz",
-      "integrity": "sha512-wLWYyqVfYx9Ur6eU5RT92yJVsaBGi5RdkoWqRHOqcJ38Kn60QMlcghsKeWfe9jcYut8LangYZ98xO1LxIoSXrQ==",
-      "cpu": [
-        "mips64el"
-      ],
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-linux-ppc64le": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.25.tgz",
-      "integrity": "sha512-0dR6Csl6Zas3g4p9ULckEl8Mo8IInJh33VCJ3eaV1hj9+MHGdmDOakYMN8MZP9/5nl+NU/0ygpd14cWgy8uqRw==",
-      "cpu": [
-        "ppc64"
-      ],
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-linux-riscv64": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.25.tgz",
-      "integrity": "sha512-J4d20HDmTrgvhR0bdkDhvvJGaikH3LzXQnNaseo8rcw9Yqby9A90gKUmWpfwqLVNRILvNnAmKLfBjCKU9ajg8w==",
-      "cpu": [
-        "riscv64"
-      ],
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-linux-s390x": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.25.tgz",
-      "integrity": "sha512-YI2d5V6nTE73ZnhEKQD7MtsPs1EtUZJ3obS21oxQxGbbRw1G+PtJKjNyur+3t6nzHP9oTg6GHQ3S3hOLLmbDIQ==",
-      "cpu": [
-        "s390x"
-      ],
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-netbsd-64": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.25.tgz",
-      "integrity": "sha512-TKIVgNWLUOkr+Exrye70XTEE1lJjdQXdM4tAXRzfHE9iBA7LXWcNtVIuSnphTqpanPzTDFarF0yqq4kpbC6miA==",
-      "cpu": [
-        "x64"
-      ],
-      "optional": true,
-      "os": [
-        "netbsd"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-openbsd-64": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.25.tgz",
-      "integrity": "sha512-QgFJ37A15D7NIXBTYEqz29+uw3nNBOIyog+3kFidANn6kjw0GHZ0lEYQn+cwjyzu94WobR+fes7cTl/ZYlHb1A==",
-      "cpu": [
-        "x64"
-      ],
-      "optional": true,
-      "os": [
-        "openbsd"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-sunos-64": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.25.tgz",
-      "integrity": "sha512-rmWfjUItYIVlqr5EnTH1+GCxXiBOC42WBZ3w++qh7n2cS9Xo0lO5pGSG2N+huOU2fX5L+6YUuJ78/vOYvefeFw==",
-      "cpu": [
-        "x64"
-      ],
-      "optional": true,
-      "os": [
-        "sunos"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-windows-32": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.25.tgz",
-      "integrity": "sha512-HGAxVUofl3iUIz9W10Y9XKtD0bNsK9fBXv1D55N/ljNvkrAYcGB8YCm0v7DjlwtyS6ws3dkdQyXadbxkbzaKOA==",
-      "cpu": [
-        "ia32"
-      ],
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-windows-64": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.25.tgz",
-      "integrity": "sha512-TirEohRkfWU9hXLgoDxzhMQD1g8I2mOqvdQF2RS9E/wbkORTAqJHyh7wqGRCQAwNzdNXdg3JAyhQ9/177AadWA==",
-      "cpu": [
-        "x64"
-      ],
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-windows-arm64": {
-      "version": "0.14.25",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.25.tgz",
-      "integrity": "sha512-4ype9ERiI45rSh+R8qUoBtaj6kJvUOI7oVLhKqPEpcF4Pa5PpT3hm/mXAyotJHREkHpM87PAJcA442mLnbtlNA==",
-      "cpu": [
-        "arm64"
-      ],
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/fs.realpath": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
-      "dev": true
-    },
-    "node_modules/glob": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
-      "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
-      "dev": true,
-      "dependencies": {
-        "fs.realpath": "^1.0.0",
-        "inflight": "^1.0.4",
-        "inherits": "2",
-        "minimatch": "^3.0.4",
-        "once": "^1.3.0",
-        "path-is-absolute": "^1.0.0"
-      },
-      "engines": {
-        "node": "*"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/isaacs"
-      }
-    },
-    "node_modules/inflight": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
-      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
-      "dev": true,
-      "dependencies": {
-        "once": "^1.3.0",
-        "wrappy": "1"
-      }
-    },
-    "node_modules/inherits": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
-      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
-      "dev": true
-    },
-    "node_modules/make-error": {
-      "version": "1.3.6",
-      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
-      "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
-      "dev": true
-    },
-    "node_modules/minimatch": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
-      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
-      "dev": true,
-      "dependencies": {
-        "brace-expansion": "^1.1.7"
-      },
-      "engines": {
-        "node": "*"
-      }
-    },
-    "node_modules/once": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
-      "dev": true,
-      "dependencies": {
-        "wrappy": "1"
-      }
-    },
-    "node_modules/path-is-absolute": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/rimraf": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
-      "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
-      "dev": true,
-      "dependencies": {
-        "glob": "^7.1.3"
-      },
-      "bin": {
-        "rimraf": "bin.js"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/isaacs"
-      }
-    },
-    "node_modules/ts-node": {
-      "version": "10.6.0",
-      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.6.0.tgz",
-      "integrity": "sha512-CJen6+dfOXolxudBQXnVjRVvYTmTWbyz7cn+xq2XTsvnaXbHqr4gXSCNbS2Jj8yTZMuGwUoBESLaOkLascVVvg==",
-      "dev": true,
-      "dependencies": {
-        "@cspotcode/source-map-support": "0.7.0",
-        "@tsconfig/node10": "^1.0.7",
-        "@tsconfig/node12": "^1.0.7",
-        "@tsconfig/node14": "^1.0.0",
-        "@tsconfig/node16": "^1.0.2",
-        "acorn": "^8.4.1",
-        "acorn-walk": "^8.1.1",
-        "arg": "^4.1.0",
-        "create-require": "^1.1.0",
-        "diff": "^4.0.1",
-        "make-error": "^1.1.1",
-        "v8-compile-cache-lib": "^3.0.0",
-        "yn": "3.1.1"
-      },
-      "bin": {
-        "ts-node": "dist/bin.js",
-        "ts-node-cwd": "dist/bin-cwd.js",
-        "ts-node-script": "dist/bin-script.js",
-        "ts-node-transpile-only": "dist/bin-transpile.js",
-        "ts-script": "dist/bin-script-deprecated.js"
-      },
-      "peerDependencies": {
-        "@swc/core": ">=1.2.50",
-        "@swc/wasm": ">=1.2.50",
-        "@types/node": "*",
-        "typescript": ">=2.7"
-      },
-      "peerDependenciesMeta": {
-        "@swc/core": {
-          "optional": true
-        },
-        "@swc/wasm": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/typescript": {
-      "version": "4.6.2",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.2.tgz",
-      "integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==",
-      "dev": true,
-      "bin": {
-        "tsc": "bin/tsc",
-        "tsserver": "bin/tsserver"
-      },
-      "engines": {
-        "node": ">=4.2.0"
-      }
-    },
-    "node_modules/v8-compile-cache-lib": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz",
-      "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==",
-      "dev": true
-    },
-    "node_modules/wrappy": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
-      "dev": true
-    },
-    "node_modules/yn": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
-      "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
-      "dev": true,
-      "engines": {
-        "node": ">=6"
-      }
-    }
-  },
   "dependencies": {
     "@cspotcode/source-map-consumer": {
       "version": "0.8.0",
@@ -882,6 +248,11 @@
       "integrity": "sha512-4ype9ERiI45rSh+R8qUoBtaj6kJvUOI7oVLhKqPEpcF4Pa5PpT3hm/mXAyotJHREkHpM87PAJcA442mLnbtlNA==",
       "optional": true
     },
+    "eventemitter3": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
+      "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="
+    },
     "fs.realpath": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -948,6 +319,29 @@
       "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
       "dev": true
     },
+    "peerjs": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/peerjs/-/peerjs-1.3.2.tgz",
+      "integrity": "sha512-+PHfmsC7QGUU8Ye3OLi6tKQZGPCNy7QatUVNw4JtE8alkguF3+DdO5W0bzepqP2OtE9FqH/ltXt37qyvHw2CqA==",
+      "requires": {
+        "@types/node": "^10.14.33",
+        "eventemitter3": "^3.1.2",
+        "peerjs-js-binarypack": "1.0.1",
+        "webrtc-adapter": "^7.7.1"
+      },
+      "dependencies": {
+        "@types/node": {
+          "version": "10.17.60",
+          "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz",
+          "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw=="
+        }
+      }
+    },
+    "peerjs-js-binarypack": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/peerjs-js-binarypack/-/peerjs-js-binarypack-1.0.1.tgz",
+      "integrity": "sha512-N6aeia3NhdpV7kiGxJV5xQiZZCVEEVjRz2T2C6UZQiBkHWHzUv/oWA4myQLcwBwO8LUoR1KWW5oStvwVesmfCg=="
+    },
     "rimraf": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -957,6 +351,19 @@
         "glob": "^7.1.3"
       }
     },
+    "rtcpeerconnection-shim": {
+      "version": "1.2.15",
+      "resolved": "https://registry.npmjs.org/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz",
+      "integrity": "sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw==",
+      "requires": {
+        "sdp": "^2.6.0"
+      }
+    },
+    "sdp": {
+      "version": "2.12.0",
+      "resolved": "https://registry.npmjs.org/sdp/-/sdp-2.12.0.tgz",
+      "integrity": "sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw=="
+    },
     "ts-node": {
       "version": "10.6.0",
       "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.6.0.tgz",
@@ -990,6 +397,15 @@
       "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==",
       "dev": true
     },
+    "webrtc-adapter": {
+      "version": "7.7.1",
+      "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-7.7.1.tgz",
+      "integrity": "sha512-TbrbBmiQBL9n0/5bvDdORc6ZfRY/Z7JnEj+EYOD1ghseZdpJ+nF2yx14k3LgQKc7JZnG7HAcL+zHnY25So9d7A==",
+      "requires": {
+        "rtcpeerconnection-shim": "^1.2.15",
+        "sdp": "^2.12.0"
+      }
+    },
     "wrappy": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
diff --git a/package.json b/package.json
index 7569d29..e9cc180 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,8 @@
   },
   "homepage": "https://gitlab.com/fuzzy-rp/dao-game#readme",
   "dependencies": {
-    "esbuild": "^0.14.25"
+    "esbuild": "^0.14.25",
+    "peerjs": "^1.3.2"
   },
   "devDependencies": {
     "@types/node": "^17.0.21",
diff --git a/public/index.html b/public/index.html
index 033ea5a..ae1c898 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1,57 +1,36 @@
 <!DOCTYPE html>
 <html lang="en">
-
     <head>
-        <meta charset="utf-8">
+        <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>
+        <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 red-piece'
-               id='default-red-piece'></p>
-            <p class='piece blue-piece'
-               id='default-blue-piece'></p>
+            <p class="piece red-piece" id="default-red-piece"></p>
+            <p class="piece blue-piece" id="default-blue-piece"></p>
         </div>
         <main>
-
             <div class="controls">
-
-                <span class="red-turn-text">
-                    Red's turn!
-                </span>
+                <span class="red-turn-text"> Red's turn! </span>
 
                 <span class="divider">|</span>
 
-                <span class="blue-turn-text">
-                    Blue's turn
-                </span>
+                <span class="blue-turn-text"> Blue's turn </span>
             </div>
 
-            <table id="table">
-            </table>
+            <table id="table"></table>
 
             <div class="controls">
-                <button type="button"
-                        class="button"
-                        id="undo-button">
+                <button type="button" class="button" id="undo-button">
                     Undo
                 </button>
-
                 <span class="divider">|</span>
-                <button type="button"
-                        class="button"
-                        id="reset-button">
+                <button type="button" class="button" id="reset-button">
                     Reset
                 </button>
             </div>
@@ -60,36 +39,35 @@
 
         <div class="title">
             <p>
-                Board game <a href='https://en.wikipedia.org/wiki/Dao_(game)'
-                   target='_blank'
-                   rel="noopener">Dao</a>.
+                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.
+            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>
+                <a
+                    href="https://gitlab.com/fuzzy-rp/dao-game"
+                    target="_blank"
+                    rel="noopener"
+                    >source code</a
+                >
             </p>
         </div>
     </body>
-
 </html>
diff --git a/public/online.html b/public/online.html
new file mode 100644
index 0000000..1d4f8ac
--- /dev/null
+++ b/public/online.html
@@ -0,0 +1,66 @@
+<!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>
+            window.daoIsOnline = true;
+        </script>
+        <script defer src="bundle.js"></script>
+    </head>
+
+    <body>
+        <div class="invisible">
+            <p class="piece red-piece" id="default-red-piece"></p>
+            <p class="piece blue-piece" id="default-blue-piece"></p>
+        </div>
+        <main>
+            <div class="controls">
+                <span class="red-turn-text"> Red's turn! </span>
+
+                <span class="divider">|</span>
+
+                <span class="blue-turn-text"> Blue's turn </span>
+            </div>
+
+            <table id="table"></table>
+        </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>
+</html>
diff --git a/readme.md b/readme.md
index a5af0f1..17faa1b 100644
--- a/readme.md
+++ b/readme.md
@@ -4,6 +4,11 @@
 
 1. `npm run start`
 
+## Debugging
+
+1. `npm run start`
+2. Run `Launch Chrome against localhost` debug task in VSCode
+
 ## Gitlab Pages
 
 [Location](https://fuzzy-rp.gitlab.io/dao-game/)
diff --git a/src/game.ts b/src/game.ts
index 60fa73c..19371a7 100644
--- a/src/game.ts
+++ b/src/game.ts
@@ -1,44 +1,59 @@
-import { Coord, GameBoard, ListenerManager, Move } from './types'
+import { PeerComm } from "./peering";
+import { Coord, GameBoard, ListenerManager, Move } from "./types";
 
 const constants = {
-    selectedPieceClassName: 'selected-piece',
-    selectedPlayerTextClassName: 'selected-player-text',
-    activePlayerPieceClassName: 'active-player-piece',
-    possibleMoveClassName: 'possible-move',
-}
+    selectedPieceClassName: "selected-piece",
+    selectedPlayerTextClassName: "selected-player-text",
+    activePlayerPieceClassName: "active-player-piece",
+    possibleMoveClassName: "possible-move",
+};
 
 const gameBoard = new GameBoard();
 const listenerManager: ListenerManager = new ListenerManager();
 
-const player: { isRedTurn: boolean, selectedPieceId: number | undefined, moveHistory: Move[] } = {
+const player: {
+    isPlayerRed: boolean;
+    isRedTurn: boolean;
+    selectedPieceId: number | undefined;
+    moveHistory: Move[];
+} = {
+    isPlayerRed: false,
     isRedTurn: false,
     selectedPieceId: undefined,
     moveHistory: [],
-}
+};
 
 // DOM references
 const dom = {
-    defaultRedPiece: document.getElementById('default-red-piece'),
-    defaultBluePiece: document.getElementById('default-blue-piece'),
+    defaultRedPiece: document.getElementById("default-red-piece"),
+    defaultBluePiece: document.getElementById("default-blue-piece"),
     cells: () => Array.from(document.querySelectorAll("td")),
-    allPieces: () => Array.from(document.querySelectorAll('.piece')),
-    playerPieces: (isRedTurn: boolean) => isRedTurn ?
-        Array.from(document.querySelectorAll('.red-piece:not(#default-red-piece')) :
-        Array.from(document.querySelectorAll('.blue-piece:not(#default-blue-piece')),
+    allPieces: () => Array.from(document.querySelectorAll(".piece")),
+    playerPieces: (isRedTurn: boolean) =>
+        isRedTurn
+            ? Array.from(
+                document.querySelectorAll(".red-piece:not(#default-red-piece")
+            )
+            : Array.from(
+                document.querySelectorAll(
+                    ".blue-piece:not(#default-blue-piece"
+                )
+            ),
     redTurnText: () => Array.from(document.querySelectorAll(".red-turn-text")),
-    blueTurnText: () => Array.from(document.querySelectorAll(".blue-turn-text")),
-    resetButton: document.getElementById('reset-button') as HTMLInputElement,
-    undoButton: document.getElementById('undo-button') as HTMLInputElement,
-}
+    blueTurnText: () =>
+        Array.from(document.querySelectorAll(".blue-turn-text")),
+    resetButton: document.getElementById("reset-button") as HTMLInputElement,
+    undoButton: document.getElementById("undo-button") as HTMLInputElement,
+};
 
 const resetBoard = () => {
     // resetCells
-    dom.cells().forEach(cell => {
+    dom.cells().forEach((cell) => {
         cell.classList.remove(constants.possibleMoveClassName);
     });
 
     // reset pieces
-    dom.allPieces().forEach(piece => {
+    dom.allPieces().forEach((piece) => {
         if (piece.classList.contains(constants.activePlayerPieceClassName)) {
             piece.classList.toggle(constants.activePlayerPieceClassName);
         }
@@ -64,16 +79,27 @@ const movePiece = async (sourceIndex: number, destIndex: number) => {
     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 });
+    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]);
@@ -88,17 +114,29 @@ const movePiece = async (sourceIndex: number, destIndex: number) => {
 const nextMove = () => {
     const setActivePlayerPieces = () => {
         const selectPiece = (event: Event) => {
-            const getAllowedMoveIndicesForIndex = (boardIndex: number): number[] => {
-                const getFarthestCoord = (startCoord: Coord, direction: Coord): Coord => {
+            const getAllowedMoveIndicesForIndex = (
+                boardIndex: number
+            ): number[] => {
+                const getFarthestCoord = (
+                    startCoord: Coord,
+                    direction: Coord
+                ): Coord => {
                     let farthestPos = undefined;
                     let testPos = startCoord;
                     while (true) {
-                        const pos = new Coord(testPos.row + direction.row, testPos.col + direction.col);
+                        const pos = new Coord(
+                            testPos.row + direction.row,
+                            testPos.col + direction.col
+                        );
                         const index = gameBoard.coordToBoardIndex(pos);
                         const withinBounds =
-                            pos.row >= 0 && pos.row < gameBoard.rows &&
-                            pos.col >= 0 && pos.col < gameBoard.cols;
-                        const isOccupied = Number.isInteger(gameBoard.board[index]);
+                            pos.row >= 0 &&
+                            pos.row < gameBoard.rows &&
+                            pos.col >= 0 &&
+                            pos.col < gameBoard.cols;
+                        const isOccupied = Number.isInteger(
+                            gameBoard.board[index]
+                        );
                         if (isOccupied || !withinBounds) {
                             break;
                         } else {
@@ -112,18 +150,24 @@ const nextMove = () => {
                 const coord = gameBoard.boardIndexToCoord(boardIndex);
 
                 const farthestCoords = Coord.allDirections
-                    .map(direction => getFarthestCoord(coord, direction))
-                    .filter(farCoord => farCoord !== undefined);
+                    .map((direction) => getFarthestCoord(coord, direction))
+                    .filter((farCoord) => farCoord !== undefined);
 
-                const farthestIndices = farthestCoords.map(c => gameBoard.coordToBoardIndex(c));
+                const farthestIndices = farthestCoords.map((c) =>
+                    gameBoard.coordToBoardIndex(c)
+                );
                 return farthestIndices;
             };
 
             const previousSelectedPieceId = player.selectedPieceId;
             if (previousSelectedPieceId !== undefined) {
-                const previousPiece = document.getElementById(previousSelectedPieceId.toString());
-                previousPiece.classList.remove(constants.selectedPieceClassName);
-                dom.cells().forEach(cell => {
+                const previousPiece = document.getElementById(
+                    previousSelectedPieceId.toString()
+                );
+                previousPiece.classList.remove(
+                    constants.selectedPieceClassName
+                );
+                dom.cells().forEach((cell) => {
                     cell.classList.remove(constants.possibleMoveClassName);
                 });
             }
@@ -140,10 +184,15 @@ const nextMove = () => {
             // 1. blink
             // 2. complete the move
             const pieceIndex = gameBoard.board.indexOf(pieceId);
-            const allowedMoveIndices = getAllowedMoveIndicesForIndex(pieceIndex);
-            allowedMoveIndices.forEach(i => {
+            const allowedMoveIndices =
+                getAllowedMoveIndicesForIndex(pieceIndex);
+            allowedMoveIndices.forEach((i) => {
                 const cell = dom.cells()[i];
-                listenerManager.addListener(cell, 'click', async () => await makeMove(i));
+                listenerManager.addListener(
+                    cell,
+                    "click",
+                    async () => await makeMove(i)
+                );
                 cell.classList.add(constants.possibleMoveClassName);
             });
         };
@@ -158,19 +207,23 @@ const nextMove = () => {
 
             checkForWin();
             nextMove();
-        }
+        };
 
         // mark active pieces
         const playerPieces = dom.playerPieces(player.isRedTurn);
-        playerPieces.forEach(pp => {
+        playerPieces.forEach((pp) => {
             pp.classList.add(constants.activePlayerPieceClassName);
-            listenerManager.addListener(pp, 'click', selectPiece);
+            listenerManager.addListener(pp, "click", selectPiece);
         });
     };
 
     // reset text
-    dom.redTurnText().forEach(t => t.classList.remove(constants.selectedPlayerTextClassName));
-    dom.blueTurnText().forEach(t => t.classList.remove(constants.selectedPlayerTextClassName));
+    dom.redTurnText().forEach((t) =>
+        t.classList.remove(constants.selectedPlayerTextClassName)
+    );
+    dom.blueTurnText().forEach((t) =>
+        t.classList.remove(constants.selectedPlayerTextClassName)
+    );
 
     // changePlayer
     // 1. flip flag
@@ -178,11 +231,14 @@ const nextMove = () => {
     // 3. set click callbacks
     if (player.isRedTurn) {
         player.isRedTurn = false;
-        dom.blueTurnText().forEach(t => t.classList.add(constants.selectedPlayerTextClassName));
-    }
-    else {
+        dom.blueTurnText().forEach((t) =>
+            t.classList.add(constants.selectedPlayerTextClassName)
+        );
+    } else {
         player.isRedTurn = true;
-        dom.redTurnText().forEach(t => t.classList.add(constants.selectedPlayerTextClassName));
+        dom.redTurnText().forEach((t) =>
+            t.classList.add(constants.selectedPlayerTextClassName)
+        );
     }
 
     if (player.moveHistory.length === 0) {
@@ -201,7 +257,7 @@ const nextMove = () => {
 const checkForWin = () => {
     const elementsToCoords = (elements: Element[]): Coord[] => {
         const elementsToBoardIndexes = (elements: Element[]): number[] => {
-            const indices = elements.map(pp => {
+            const indices = elements.map((pp) => {
                 const pieceId = Number.parseInt(pp.id);
                 const pieceIndex = gameBoard.board.indexOf(pieceId);
                 return pieceIndex;
@@ -210,20 +266,22 @@ const checkForWin = () => {
         };
 
         const indices = elementsToBoardIndexes(elements);
-        const coords = indices.map(pieceIndex => gameBoard.boardIndexToCoord(pieceIndex));
+        const coords = indices.map((pieceIndex) =>
+            gameBoard.boardIndexToCoord(pieceIndex)
+        );
         return coords;
     };
     const doCoordsFormSquare = (coords: Coord[]): boolean => {
-        const allRows = coords.map(c => c.row);
-        const allCols = coords.map(c => c.col);
+        const allRows = coords.map((c) => c.row);
+        const allCols = coords.map((c) => c.col);
         const min = new Coord(Math.min(...allRows), Math.min(...allCols));
         const max = new Coord(Math.max(...allRows), Math.max(...allCols));
         const diff = max.sub(min);
-        const isSquare = (diff.row === 1 && diff.col === 1);
+        const isSquare = diff.row === 1 && diff.col === 1;
         return isSquare;
     };
 
-    const playerCoords = elementsToCoords(dom.playerPieces(player.isRedTurn))
+    const playerCoords = elementsToCoords(dom.playerPieces(player.isRedTurn));
     const otherCoords = elementsToCoords(dom.playerPieces(!player.isRedTurn));
     const squareCoords: Coord[] = [
         new Coord(0, 0),
@@ -241,12 +299,14 @@ const checkForWin = () => {
     // 1. on three sides of an opponent
 
     const firstPiece = playerCoords[0];
-    const allInRow = playerCoords.every(pc => pc.row === firstPiece.row)
-    const allInCol = playerCoords.every(pc => pc.col === firstPiece.col)
-    const allInCorners = playerCoords.every(pc => squareCoords.findIndex(sc => pc.equals(sc)) >= 0);
+    const allInRow = playerCoords.every((pc) => pc.row === firstPiece.row);
+    const allInCol = playerCoords.every((pc) => pc.col === firstPiece.col);
+    const allInCorners = playerCoords.every(
+        (pc) => squareCoords.findIndex((sc) => pc.equals(sc)) >= 0
+    );
     const allInSquare = doCoordsFormSquare(playerCoords);
 
-    const winnerName = player.isRedTurn ? 'Red' : 'Blue';
+    const winnerName = player.isRedTurn ? "Red" : "Blue";
     let winningMessage = undefined;
     if (allInRow) {
         winningMessage = `${winnerName} wins by forming a row`;
@@ -270,14 +330,14 @@ const checkForWin = () => {
 const init = () => {
     // create board and pieces
     let isWhite = true;
-    const table = document.getElementById('table');
+    const table = document.getElementById("table");
     for (let r = 0; r < gameBoard.rows; r++) {
-        const tr = document.createElement('tr');
+        const tr = document.createElement("tr");
         table.appendChild(tr);
         for (let c = 0; c < gameBoard.cols; c++) {
-            const td = document.createElement('td');
+            const td = document.createElement("td");
             tr.appendChild(td);
-            td.className = isWhite ? 'white' : 'black';
+            td.className = isWhite ? "white" : "black";
             isWhite = !isWhite;
         }
         isWhite = !isWhite;
@@ -285,8 +345,12 @@ const init = () => {
     gameBoard.board.forEach((value, index) => {
         if (value !== null) {
             const isRed = value < 4;
-            const defaultPiece = isRed ? dom.defaultRedPiece : dom.defaultBluePiece;
-            const newPiece = defaultPiece.cloneNode(true) as HTMLParagraphElement;
+            const defaultPiece = isRed
+                ? dom.defaultRedPiece
+                : dom.defaultBluePiece;
+            const newPiece = defaultPiece.cloneNode(
+                true
+            ) as HTMLParagraphElement;
             newPiece.id = value.toString();
             dom.cells()[index].appendChild(newPiece);
         }
@@ -294,7 +358,7 @@ const init = () => {
 
     // hook up buttons
     let blockActions = false;
-    dom.resetButton.addEventListener('click', () => {
+    dom.resetButton.addEventListener("click", () => {
         if (blockActions) {
             return;
         }
@@ -303,12 +367,12 @@ const init = () => {
         }
 
         blockActions = true;
-        if (confirm('Reset?')) {
+        if (confirm("Reset?")) {
             window.location.reload();
         }
         blockActions = false;
     });
-    dom.undoButton.addEventListener('click', async () => {
+    dom.undoButton.addEventListener("click", async () => {
         if (blockActions) {
             return;
         }
@@ -329,5 +393,52 @@ const init = () => {
 };
 
 // start!
-init();
-nextMove();
\ No newline at end of file
+const playGame = () => {
+    init();
+    nextMove();
+};
+
+// const isOnlineGame = (window as any).daoIsOnline;
+// if (isOnlineGame) {
+//     const receiver = (data) => 
+//     const peer = new PeerComm()
+//     } else {
+//         playGame();
+
+// }
+
+playGame();
+
+function delay(ms: number) {
+    return new Promise(resolve => setTimeout(resolve, ms));
+}
+const doStuff = async () => {
+    const peer = new PeerComm((data) => {
+        console.log(`TO:    ${peer.id})`);
+        console.log(`FROM:  ?)`);
+        console.log(`DATA:  ${data}`);
+    });
+    prompt('Self id:', peer.id);
+    const otherId = prompt('Peer id to connect to:', '');
+    if (!otherId) {
+        while (!peer.isConnected) {
+            await delay(500);
+        }
+    } else {
+        peer.connect(otherId);
+    }
+
+    alert('Connected!');
+    alert('Starting messaging server');
+    alert('Respond with empty text to stop');
+
+    while (true) {
+        const message = prompt('Message to send:', '');
+        if (!message) {
+            break;
+        }
+        peer.send(message);
+        delay(1000);
+    }
+}
+// doStuff();
diff --git a/src/peering.ts b/src/peering.ts
new file mode 100644
index 0000000..ed11a3f
--- /dev/null
+++ b/src/peering.ts
@@ -0,0 +1,51 @@
+import Peer from "peerjs";
+
+const daoPeerIdKey = "dao-game-peer-id";
+export class PeerComm<T> {
+    public constructor(receiver: (data: T) => void) {
+        this.receiver = receiver;
+
+        // const peerId = localStorage.getItem(daoPeerIdKey) ?? `dao-game-peer-id-${Date.now()}`;
+        const peerId = `dao-game-peer-id-${Date.now()}`;
+        const peer = new Peer(peerId);
+        // localStorage.setItem(daoPeerIdKey, peerId);
+        this.id = peerId;
+        this.peer = peer;
+
+        peer.on('connection', (connection) => {
+            connection.on('data', data => {
+                receiver(JSON.parse(data));
+            });
+            connection.on('open', () => {
+                connection.send('hello');
+                this.conn = connection;
+            })
+        });
+    }
+
+    public get isConnected() {
+        return this.conn !== null;
+    }
+    public connect(otherPeerId: string): void {
+        if (this.conn !== null) {
+            throw "Already connected";
+        }
+        const conn = this.peer.connect(otherPeerId);
+        conn.on('open', () => {
+            conn.send('hi');
+            this.conn = conn;
+        });
+    }
+
+    public readonly id: string;
+    public receiver: (data: T) => void;
+    public send(data: T): void {
+        if (!this.isConnected) {
+            throw "Not connected";
+        }
+        this.conn.send(JSON.stringify(data));
+    }
+
+    private peer: Peer;
+    private conn: Peer.DataConnection | null = null;
+}
diff --git a/src/piano.ts b/src/piano.ts
new file mode 100644
index 0000000..d9072ec
--- /dev/null
+++ b/src/piano.ts
@@ -0,0 +1,36 @@
+import { PeerComm } from "./peering";
+import { Move } from "./types";
+
+export interface Game {
+  moveHistory: Move[];
+  isRedTurn: boolean;
+  isLocalRed: boolean;
+  isMoveLegal: (sourceIndex: number, destIndex: number) => boolean;
+  requestMove: () => ((sourceIndex: number, destIndex: number) => void);
+}
+enum State {
+  Starting,
+  RedTurn,
+  BlueTurn
+}
+interface PeerData {
+  senderId: string;
+  receiverId: string;
+  redId: string;
+  state: State;
+  move: Move | null;
+}
+
+export class Piano {
+  public game: Game;
+  private peerComm: PeerComm<PeerData>;
+  private receiver: (data: PeerData) => void;
+  public constructor(game: Game) {
+    this.game = game;
+    this.receiver = (data: PeerData) => {
+      const isGameStarting = game.moveHistory.length === 0;
+      const isLocalRed = data.redId === this.peerComm.id;
+    };
+    this.peerComm = new PeerComm<PeerData>(this.receiver)
+  }
+}
\ No newline at end of file
diff --git a/src/types.ts b/src/types.ts
index 9ba103b..18cf62d 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -38,11 +38,13 @@ export class Coord {
 }
 
 export class Move {
-    public sourceIndex: number;
-    public destIndex: number;
-    constructor(sourceIndex: number, destIndex: number) {
+    public readonly sourceIndex: number;
+    public readonly destIndex: number;
+    public readonly isPieceRed: boolean;
+    constructor(isPieceRed: boolean, sourceIndex: number, destIndex: number) {
         this.sourceIndex = sourceIndex;
         this.destIndex = destIndex;
+        this.isPieceRed = isPieceRed;
     }
 }