From dc7c0c1c6e5b9bd925444158c149d05f07422bb4 Mon Sep 17 00:00:00 2001 From: pb-coding <71174645+pb-coding@users.noreply.github.com> Date: Wed, 20 Sep 2023 10:25:39 +0200 Subject: [PATCH] wip --- package-lock.json | 509 ++++++++++++++++--------------------- src/game/events.ts | 22 -- src/game/game.ts | 621 +++++++++++++++++++++++++++++++-------------- src/game/player.ts | 48 ++++ 4 files changed, 691 insertions(+), 509 deletions(-) diff --git a/package-lock.json b/package-lock.json index 504931e..05ec71b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,9 +62,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.2.tgz", - "integrity": "sha512-tM8yLeYVe7pRyAu9VMi/Q7aunpLwD139EY1S99xbQkT4/q2qa6eA4ige/WJQYdJ8GBL1K33pPFhPfPdJ/WzT8Q==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.3.tgz", + "integrity": "sha512-Lemgw4io4VZl9GHJmjiBGzQ7ONXRfRPHcUEerndjwiSkbxzrpq0Uggku5MxxrXdwJ+pTj1qyw4jwTu7hkPsgIA==", "cpu": [ "arm" ], @@ -79,9 +79,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.2.tgz", - "integrity": "sha512-lsB65vAbe90I/Qe10OjkmrdxSX4UJDjosDgb8sZUKcg3oefEuW2OT2Vozz8ef7wrJbMcmhvCC+hciF8jY/uAkw==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.3.tgz", + "integrity": "sha512-w+Akc0vv5leog550kjJV9Ru+MXMR2VuMrui3C61mnysim0gkFCPOUTAfzTP0qX+HpN9Syu3YA3p1hf3EPqObRw==", "cpu": [ "arm64" ], @@ -96,9 +96,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.2.tgz", - "integrity": "sha512-qK/TpmHt2M/Hg82WXHRc/W/2SGo/l1thtDHZWqFq7oi24AjZ4O/CpPSu6ZuYKFkEgmZlFoa7CooAyYmuvnaG8w==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.3.tgz", + "integrity": "sha512-FKQJKkK5MXcBHoNZMDNUAg1+WcZlV/cuXrWCoGF/TvdRiYS4znA0m5Il5idUwfxrE20bG/vU1Cr5e1AD6IEIjQ==", "cpu": [ "x64" ], @@ -113,9 +113,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.2.tgz", - "integrity": "sha512-Ora8JokrvrzEPEpZO18ZYXkH4asCdc1DLdcVy8TGf5eWtPO1Ie4WroEJzwI52ZGtpODy3+m0a2yEX9l+KUn0tA==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.3.tgz", + "integrity": "sha512-kw7e3FXU+VsJSSSl2nMKvACYlwtvZB8RUIeVShIEY6PVnuZ3c9+L9lWB2nWeeKWNNYDdtL19foCQ0ZyUL7nqGw==", "cpu": [ "arm64" ], @@ -130,9 +130,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.2.tgz", - "integrity": "sha512-tP+B5UuIbbFMj2hQaUr6EALlHOIOmlLM2FK7jeFBobPy2ERdohI4Ka6ZFjZ1ZYsrHE/hZimGuU90jusRE0pwDw==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.3.tgz", + "integrity": "sha512-tPfZiwF9rO0jW6Jh9ipi58N5ZLoSjdxXeSrAYypy4psA2Yl1dAMhM71KxVfmjZhJmxRjSnb29YlRXXhh3GqzYw==", "cpu": [ "x64" ], @@ -147,9 +147,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.2.tgz", - "integrity": "sha512-YbPY2kc0acfzL1VPVK6EnAlig4f+l8xmq36OZkU0jzBVHcOTyQDhnKQaLzZudNJQyymd9OqQezeaBgkTGdTGeQ==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.3.tgz", + "integrity": "sha512-ERDyjOgYeKe0Vrlr1iLrqTByB026YLPzTytDTz1DRCYM+JI92Dw2dbpRHYmdqn6VBnQ9Bor6J8ZlNwdZdxjlSg==", "cpu": [ "arm64" ], @@ -164,9 +164,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.2.tgz", - "integrity": "sha512-nSO5uZT2clM6hosjWHAsS15hLrwCvIWx+b2e3lZ3MwbYSaXwvfO528OF+dLjas1g3bZonciivI8qKR/Hm7IWGw==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.3.tgz", + "integrity": "sha512-nXesBZ2Ad1qL+Rm3crN7NmEVJ5uvfLFPLJev3x1j3feCQXfAhoYrojC681RhpdOph8NsvKBBwpYZHR7W0ifTTA==", "cpu": [ "x64" ], @@ -181,9 +181,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.2.tgz", - "integrity": "sha512-Odalh8hICg7SOD7XCj0YLpYCEc+6mkoq63UnExDCiRA2wXEmGlK5JVrW50vZR9Qz4qkvqnHcpH+OFEggO3PgTg==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.3.tgz", + "integrity": "sha512-zr48Cg/8zkzZCzDHNxXO/89bf9e+r4HtzNUPoz4GmgAkF1gFAFmfgOdCbR8zMbzFDGb1FqBBhdXUpcTQRYS1cQ==", "cpu": [ "arm" ], @@ -198,9 +198,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.2.tgz", - "integrity": "sha512-ig2P7GeG//zWlU0AggA3pV1h5gdix0MA3wgB+NsnBXViwiGgY77fuN9Wr5uoCrs2YzaYfogXgsWZbm+HGr09xg==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.3.tgz", + "integrity": "sha512-qXvYKmXj8GcJgWq3aGvxL/JG1ZM3UR272SdPU4QSTzD0eymrM7leiZH77pvY3UetCy0k1xuXZ+VPvoJNdtrsWQ==", "cpu": [ "arm64" ], @@ -215,9 +215,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.2.tgz", - "integrity": "sha512-mLfp0ziRPOLSTek0Gd9T5B8AtzKAkoZE70fneiiyPlSnUKKI4lp+mGEnQXcQEHLJAcIYDPSyBvsUbKUG2ri/XQ==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.3.tgz", + "integrity": "sha512-7XlCKCA0nWcbvYpusARWkFjRQNWNGlt45S+Q18UeS///K6Aw8bB2FKYe9mhVWy/XLShvCweOLZPrnMswIaDXQA==", "cpu": [ "ia32" ], @@ -232,9 +232,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.2.tgz", - "integrity": "sha512-hn28+JNDTxxCpnYjdDYVMNTR3SKavyLlCHHkufHV91fkewpIyQchS1d8wSbmXhs1fiYDpNww8KTFlJ1dHsxeSw==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.3.tgz", + "integrity": "sha512-qGTgjweER5xqweiWtUIDl9OKz338EQqCwbS9c2Bh5jgEH19xQ1yhgGPNesugmDFq+UUSDtWgZ264st26b3de8A==", "cpu": [ "loong64" ], @@ -249,9 +249,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.2.tgz", - "integrity": "sha512-KbXaC0Sejt7vD2fEgPoIKb6nxkfYW9OmFUK9XQE4//PvGIxNIfPk1NmlHmMg6f25x57rpmEFrn1OotASYIAaTg==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.3.tgz", + "integrity": "sha512-gy1bFskwEyxVMFRNYSvBauDIWNggD6pyxUksc0MV9UOBD138dKTzr8XnM2R4mBsHwVzeuIH8X5JhmNs2Pzrx+A==", "cpu": [ "mips64el" ], @@ -266,9 +266,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.2.tgz", - "integrity": "sha512-dJ0kE8KTqbiHtA3Fc/zn7lCd7pqVr4JcT0JqOnbj4LLzYnp+7h8Qi4yjfq42ZlHfhOCM42rBh0EwHYLL6LEzcw==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.3.tgz", + "integrity": "sha512-UrYLFu62x1MmmIe85rpR3qou92wB9lEXluwMB/STDzPF9k8mi/9UvNsG07Tt9AqwPQXluMQ6bZbTzYt01+Ue5g==", "cpu": [ "ppc64" ], @@ -283,9 +283,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.2.tgz", - "integrity": "sha512-7Z/jKNFufZ/bbu4INqqCN6DDlrmOTmdw6D0gH+6Y7auok2r02Ur661qPuXidPOJ+FSgbEeQnnAGgsVynfLuOEw==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.3.tgz", + "integrity": "sha512-9E73TfyMCbE+1AwFOg3glnzZ5fBAFK4aawssvuMgCRqCYzE0ylVxxzjEfut8xjmKkR320BEoMui4o/t9KA96gA==", "cpu": [ "riscv64" ], @@ -300,9 +300,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.2.tgz", - "integrity": "sha512-U+RinR6aXXABFCcAY4gSlv4CL1oOVvSSCdseQmGO66H+XyuQGZIUdhG56SZaDJQcLmrSfRmx5XZOWyCJPRqS7g==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.3.tgz", + "integrity": "sha512-LlmsbuBdm1/D66TJ3HW6URY8wO6IlYHf+ChOUz8SUAjVTuaisfuwCOAgcxo3Zsu3BZGxmI7yt//yGOxV+lHcEA==", "cpu": [ "s390x" ], @@ -317,9 +317,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.2.tgz", - "integrity": "sha512-oxzHTEv6VPm3XXNaHPyUTTte+3wGv7qVQtqaZCrgstI16gCuhNOtBXLEBkBREP57YTd68P0VgDgG73jSD8bwXQ==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.3.tgz", + "integrity": "sha512-ogV0+GwEmvwg/8ZbsyfkYGaLACBQWDvO0Kkh8LKBGKj9Ru8VM39zssrnu9Sxn1wbapA2qNS6BiLdwJZGouyCwQ==", "cpu": [ "x64" ], @@ -334,9 +334,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.2.tgz", - "integrity": "sha512-WNa5zZk1XpTTwMDompZmvQLHszDDDN7lYjEHCUmAGB83Bgs20EMs7ICD+oKeT6xt4phV4NDdSi/8OfjPbSbZfQ==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.3.tgz", + "integrity": "sha512-o1jLNe4uzQv2DKXMlmEzf66Wd8MoIhLNO2nlQBHLtWyh2MitDG7sMpfCO3NTcoTMuqHjfufgUQDFRI5C+xsXQw==", "cpu": [ "x64" ], @@ -351,9 +351,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.2.tgz", - "integrity": "sha512-S6kI1aT3S++Dedb7vxIuUOb3oAxqxk2Rh5rOXOTYnzN8JzW1VzBd+IqPiSpgitu45042SYD3HCoEyhLKQcDFDw==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.3.tgz", + "integrity": "sha512-AZJCnr5CZgZOdhouLcfRdnk9Zv6HbaBxjcyhq0StNcvAdVZJSKIdOiPB9az2zc06ywl0ePYJz60CjdKsQacp5Q==", "cpu": [ "x64" ], @@ -368,9 +368,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.2.tgz", - "integrity": "sha512-VXSSMsmb+Z8LbsQGcBMiM+fYObDNRm8p7tkUDMPG/g4fhFX5DEFmjxIEa3N8Zr96SjsJ1woAhF0DUnS3MF3ARw==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.3.tgz", + "integrity": "sha512-Acsujgeqg9InR4glTRvLKGZ+1HMtDm94ehTIHKhJjFpgVzZG9/pIcWW/HA/DoMfEyXmANLDuDZ2sNrWcjq1lxw==", "cpu": [ "x64" ], @@ -385,9 +385,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.2.tgz", - "integrity": "sha512-5NayUlSAyb5PQYFAU9x3bHdsqB88RC3aM9lKDAz4X1mo/EchMIT1Q+pSeBXNgkfNmRecLXA0O8xP+x8V+g/LKg==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.3.tgz", + "integrity": "sha512-FSrAfjVVy7TifFgYgliiJOyYynhQmqgPj15pzLyJk8BUsnlWNwP/IAy6GAiB1LqtoivowRgidZsfpoYLZH586A==", "cpu": [ "arm64" ], @@ -402,9 +402,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.2.tgz", - "integrity": "sha512-47gL/ek1v36iN0wL9L4Q2MFdujR0poLZMJwhO2/N3gA89jgHp4MR8DKCmwYtGNksbfJb9JoTtbkoe6sDhg2QTA==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.3.tgz", + "integrity": "sha512-xTScXYi12xLOWZ/sc5RBmMN99BcXp/eEf7scUC0oeiRoiT5Vvo9AycuqCp+xdpDyAU+LkrCqEpUS9fCSZF8J3Q==", "cpu": [ "ia32" ], @@ -419,9 +419,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.2.tgz", - "integrity": "sha512-tcuhV7ncXBqbt/Ybf0IyrMcwVOAPDckMK9rXNHtF17UTK18OKLpg08glminN06pt2WCoALhXdLfSPbVvK/6fxw==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.3.tgz", + "integrity": "sha512-FbUN+0ZRXsypPyWE2IwIkVjDkDnJoMJARWOcFZn4KPPli+QnKqF0z1anvfaYe3ev5HFCpRDLLBDHyOALLppWHw==", "cpu": [ "x64" ], @@ -511,9 +511,9 @@ } }, "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "version": "1.19.3", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.3.tgz", + "integrity": "sha512-oyl4jvAfTGX9Bt6Or4H9ni1Z447/tQuxnZsytsCaExKlmJiU8sFgnIBRzJUpKwB5eWn9HuBYlUlVA74q/yN0eQ==", "dev": true, "dependencies": { "@types/connect": "*", @@ -592,15 +592,15 @@ } }, "node_modules/@types/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.2.tgz", + "integrity": "sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg==", "dev": true }, "node_modules/@types/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-b0jGiOgHtZ2jqdPgPnP6WLCXZk1T8p06A/vPGzUvxpFGgKMbjXJDjC5m52ErqBnIuWZFgGoIJyRdeG5AyreJjA==", "dev": true, "dependencies": { "@types/node": "*" @@ -613,9 +613,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.6.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.0.tgz", - "integrity": "sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg==" + "version": "20.6.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.2.tgz", + "integrity": "sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==" }, "node_modules/@types/node-cron": { "version": "3.0.8", @@ -647,20 +647,6 @@ "form-data": "^2.5.0" } }, - "node_modules/@types/request/node_modules/form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/@types/send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", @@ -683,15 +669,15 @@ } }, "node_modules/@types/tough-cookie": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", - "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.3.tgz", + "integrity": "sha512-THo502dA5PzG/sfQH+42Lw3fvmYkceefOspdCwpHRul8ik2Jv1K8I5OZz1AT3/rs46kwgMCe9bSBmDLYkkOMGg==", "dev": true }, "node_modules/@types/uuid": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.3.tgz", - "integrity": "sha512-taHQQH/3ZyI3zP8M/puluDEIEvtQHVYcC6y3N8ijFtAd28+Ey/G4sg1u2gB01S8MwybLOKAp9/yCMu/uR5l3Ug==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.4.tgz", + "integrity": "sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA==", "dev": true }, "node_modules/abbrev": { @@ -741,27 +727,6 @@ "node": ">= 6.0.0" } }, - "node_modules/agent-base/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/agent-base/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -856,6 +821,19 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -913,6 +891,19 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1009,9 +1000,9 @@ } }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", "engines": { "node": ">= 0.6" } @@ -1028,14 +1019,6 @@ "node": ">= 0.8.0" } }, - "node_modules/cookie-parser/node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -1090,11 +1073,19 @@ } }, "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { - "ms": "2.0.0" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/delayed-stream": { @@ -1217,39 +1208,10 @@ "node": ">=10.0.0" } }, - "node_modules/engine.io/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/engine.io/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/engine.io/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/esbuild": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.2.tgz", - "integrity": "sha512-G6hPax8UbFakEj3hWO0Vs52LQ8k3lnBhxZWomUJDxfz3rZTLqF5k/FCzuNdLx2RbpBiQQF9H9onlDDH1lZsnjg==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.3.tgz", + "integrity": "sha512-UlJ1qUUA2jL2nNib1JTSkifQTcYTroFqRjwCFW4QYEKEsixXD5Tik9xML7zh2gTxkYTBKGHNH9y7txMwVyPbjw==", "dev": true, "hasInstallScript": true, "peer": true, @@ -1260,34 +1222,34 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.19.2", - "@esbuild/android-arm64": "0.19.2", - "@esbuild/android-x64": "0.19.2", - "@esbuild/darwin-arm64": "0.19.2", - "@esbuild/darwin-x64": "0.19.2", - "@esbuild/freebsd-arm64": "0.19.2", - "@esbuild/freebsd-x64": "0.19.2", - "@esbuild/linux-arm": "0.19.2", - "@esbuild/linux-arm64": "0.19.2", - "@esbuild/linux-ia32": "0.19.2", - "@esbuild/linux-loong64": "0.19.2", - "@esbuild/linux-mips64el": "0.19.2", - "@esbuild/linux-ppc64": "0.19.2", - "@esbuild/linux-riscv64": "0.19.2", - "@esbuild/linux-s390x": "0.19.2", - "@esbuild/linux-x64": "0.19.2", - "@esbuild/netbsd-x64": "0.19.2", - "@esbuild/openbsd-x64": "0.19.2", - "@esbuild/sunos-x64": "0.19.2", - "@esbuild/win32-arm64": "0.19.2", - "@esbuild/win32-ia32": "0.19.2", - "@esbuild/win32-x64": "0.19.2" + "@esbuild/android-arm": "0.19.3", + "@esbuild/android-arm64": "0.19.3", + "@esbuild/android-x64": "0.19.3", + "@esbuild/darwin-arm64": "0.19.3", + "@esbuild/darwin-x64": "0.19.3", + "@esbuild/freebsd-arm64": "0.19.3", + "@esbuild/freebsd-x64": "0.19.3", + "@esbuild/linux-arm": "0.19.3", + "@esbuild/linux-arm64": "0.19.3", + "@esbuild/linux-ia32": "0.19.3", + "@esbuild/linux-loong64": "0.19.3", + "@esbuild/linux-mips64el": "0.19.3", + "@esbuild/linux-ppc64": "0.19.3", + "@esbuild/linux-riscv64": "0.19.3", + "@esbuild/linux-s390x": "0.19.3", + "@esbuild/linux-x64": "0.19.3", + "@esbuild/netbsd-x64": "0.19.3", + "@esbuild/openbsd-x64": "0.19.3", + "@esbuild/sunos-x64": "0.19.3", + "@esbuild/win32-arm64": "0.19.3", + "@esbuild/win32-ia32": "0.19.3", + "@esbuild/win32-x64": "0.19.3" } }, "node_modules/esbuild-register": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.4.2.tgz", - "integrity": "sha512-kG/XyTDyz6+YDuyfB9ZoSIOOmgyFCH+xPRtsCa8W85HLRV5Csp+o3jWVbOSHgSLfyLc5DmP+KFDNwty4mEjC+Q==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.5.0.tgz", + "integrity": "sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==", "dev": true, "dependencies": { "debug": "^4.3.4" @@ -1296,29 +1258,6 @@ "esbuild": ">=0.12 <1" } }, - "node_modules/esbuild-register/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/esbuild-register/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1385,6 +1324,27 @@ "node": ">= 8.0.0" } }, + "node_modules/express/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -1425,6 +1385,19 @@ "node": ">= 0.8" } }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/follow-redirects": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", @@ -1453,16 +1426,17 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dev": true, "dependencies": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", + "combined-stream": "^1.0.6", "mime-types": "^2.1.12" }, "engines": { - "node": ">= 6" + "node": ">= 0.12" } }, "node_modules/forwarded": { @@ -1673,27 +1647,6 @@ "node": ">= 6" } }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -1786,11 +1739,6 @@ "npm": ">=6" } }, - "node_modules/jsonwebtoken/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, "node_modules/jsprim": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", @@ -2007,9 +1955,9 @@ } }, "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/negotiator": { "version": "0.6.3", @@ -2376,6 +2324,19 @@ "node": ">= 0.8.0" } }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2460,48 +2421,6 @@ "node": ">=10.0.0" } }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-parser/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/socket.io/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/sshpk": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", @@ -2719,9 +2638,13 @@ } }, "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } diff --git a/src/game/events.ts b/src/game/events.ts index e08fa98..f7162cf 100644 --- a/src/game/events.ts +++ b/src/game/events.ts @@ -1,8 +1,6 @@ import { Socket } from "socket.io"; import { io } from "../server"; import { Game } from "./game"; -import { Player, ObfuscatedPlayer } from "./player"; -import { Card } from "./card"; export const handleJoinSession = (socket: Socket, sessionId: string) => { socket.join(sessionId); @@ -30,23 +28,3 @@ export const handleNewGame = ( console.log("Not enough players to start a game"); } }; - -const obfuscatePlayerCards = (player: Player): ObfuscatedPlayer => { - return { - id: player.id, - socketId: player.socketId, - name: player.name, - cards: player.cards.map((card: Card, index: number) => { - return { - id: card.id, - value: player.knownCardPositions[index] ? card.value : 0, // unknown cards are obfuscated to 0 - name: card.name, - color: card.color, - matchColorToCardValue: card.matchColorToCardValue, - }; - }), - knownCardPositions: player.knownCardPositions, - playersTurn: player.playersTurn, - cardCache: player.cardCache, - }; -}; diff --git a/src/game/game.ts b/src/game/game.ts index 2bb28bd..761be8c 100644 --- a/src/game/game.ts +++ b/src/game/game.ts @@ -6,10 +6,20 @@ import { io } from "../server"; type PlayerSocketSet = Set; -type PlayerActions = Array< - [string, (playerSocketId: string, data: any) => void] +type PlayerActionEventName = string; +type PlayerActionCallback = (playerSocketId: string, data: any) => void; + +type ExpectedPlayerActions = Array< + [PlayerActionEventName, PlayerActionCallback] >; +type PlayerAction = { + playerSocketId: string; + data: ActionDataType; +}; + +type CardPosition = number; + // obfuscated types are used to send only necessary data to the client export type ObfuscatedGame = { sessionId: string; @@ -18,9 +28,11 @@ export type ObfuscatedGame = { cardStack: ObfuscatedCardStack; discardPile: Card[]; phase: string; + round: number; }; const gamePhase = { + newRound: "new round", revealTwoCards: "reveal two cards", pickUpCard: "pick up card", placeCard: "place card", @@ -37,6 +49,7 @@ export class Game { cardStack: CardStack; discardPile: Card[]; phase: string; + round: number; constructor(socket: Socket, sessionId: string, playerIds: PlayerSocketSet) { this.socket = socket; @@ -51,6 +64,7 @@ export class Game { // get the first card from the cardStack and put it in the discard pile this.discardPile = [this.cardStack.cards.pop()!]; this.phase = gamePhase.revealTwoCards; + this.round = 1; console.log(`New Game created with ${this.playerCount} players!`); this.gameLoop(); @@ -80,36 +94,52 @@ export class Game { return players; } + initializeNewRound(startOver: boolean = false) { + this.round = startOver ? 1 : this.round + 1; + this.cardStack = new CardStack(); + this.cardStack.shuffleCards(); + this.players.forEach((player) => { + player.cards = this.cardStack.cards.splice(0, 12); + player.knownCardPositions = new Array(12).fill(false); + player.playersTurn = false; + player.cardCache = null; + player.tookDispiledCard = false; + player.roundPoints = 0; + player.totalPoints = startOver ? 0 : player.totalPoints; + player.closedRound = false; + }); + this.discardPile = [this.cardStack.cards.pop()!]; + this.phase = gamePhase.revealTwoCards; + } + async gameLoop() { console.log("Game started!"); while (this.phase !== gamePhase.gameEnded) { + this.checkForFullRevealedCards(); switch (this.phase) { case gamePhase.revealTwoCards: - console.log("Game phase: revealTwoCards"); - this.listPlayerSocketListeners(); + console.log("\nGame phase: revealTwoCards"); await this.revealInitialCards(); - this.phase = gamePhase.pickUpCard; break; case gamePhase.pickUpCard: - console.log("Game phase: pickUpCard"); - this.listPlayerSocketListeners(); + console.log("\nGame phase: pickUpCard"); await this.pickUpCard(); - this.phase = gamePhase.placeCard; break; case gamePhase.placeCard: - console.log("Game phase: placeCard"); - this.listPlayerSocketListeners(); + console.log("\nGame phase: placeCard"); await this.placeCard(); break; case gamePhase.revealCard: - console.log("Game phase: revealCard"); - this.listPlayerSocketListeners(); + console.log("\nGame phase: revealCard"); await this.revealCard(); - this.phase = gamePhase.pickUpCard; + break; case gamePhase.revealedLastCard: - console.log("Game phase: revealedLastCard"); - // add logic of last round - this.phase = gamePhase.gameEnded; + console.log("\nGame phase: revealedLastCard"); + await this.revealedLastCard(); + break; + case gamePhase.newRound: + console.log("\nGame phase: newRound"); + await this.nextRound(); break; default: throw new Error(`Invalid game phase: ${this.phase}`); @@ -117,6 +147,364 @@ export class Game { } } + // Game Phases + + async revealInitialCards() { + this.sendMessageToAllPlayers("Reveal two cards"); + while (!this.allPlayersRevealedInitialCards()) { + const playersWithRevealedInitialCards = + this.getPlayersWithRevealedInitialCards(); + const playersWithUnrevealedInitialCards = this.players.filter( + (player) => !playersWithRevealedInitialCards.includes(player) + ); + const playersSocketIds = playersWithUnrevealedInitialCards.map( + (player) => player.socketId + ); + await this.waitForPlayerActions( + [["click-card", this.revealCardAction.bind(this)]], + playersSocketIds + ); + } + this.setInitialPlayersTurn(); + this.phase = gamePhase.pickUpCard; + this.sendObfuscatedGameUpdate(); + } + + async pickUpCard() { + const playerOnTurn = this.getPlayersTurn(); + if (playerOnTurn.closedRound) { + this.phase = gamePhase.revealedLastCard; + this.sendObfuscatedGameUpdate(); + return; + } + this.sendMessageToAllPlayers( + `Waiting for ${playerOnTurn.name} to pick up card` + ); + await this.waitForPlayerActions( + [ + ["draw-from-card-stack", this.drawCardAction.bind(this)], + ["click-discard-pile", this.takeDiscardPileAction.bind(this)], + ], + [playerOnTurn.socketId] + ); + this.phase = gamePhase.placeCard; + this.sendObfuscatedGameUpdate(); + } + + async placeCard() { + const playerOnTurn = this.getPlayersTurn(); + this.sendMessageToAllPlayers( + `Waiting for ${playerOnTurn.name} to place card` + ); + + const expectedActions: ExpectedPlayerActions = [ + ["click-card", this.placeCardAction.bind(this)], + ]; + if (!playerOnTurn.tookDispiledCard) { + expectedActions.push([ + "click-discard-pile", + this.discardCardToPileAction.bind(this), + ]); + } + playerOnTurn.tookDispiledCard = false; + await this.waitForPlayerActions(expectedActions, [playerOnTurn.socketId]); + this.sendObfuscatedGameUpdate(); + } + + async revealCard() { + const playerOnTurn = this.getPlayersTurn(); + this.sendMessageToAllPlayers( + `Waiting for ${playerOnTurn.name} to reveal a card` + ); + + const numberOfRevealedCards = playerOnTurn.getRevealedCardCount(); + // ensures that the player does not select an already revealed card + while (playerOnTurn.getRevealedCardCount() <= numberOfRevealedCards) { + await this.waitForPlayerActions( + [["click-card", this.revealCardAction.bind(this)]], + [playerOnTurn.socketId] + ); + } + this.nextPlayersTurn(); + this.phase = gamePhase.pickUpCard; + this.sendObfuscatedGameUpdate(); + } + + async revealedLastCard() { + this.revealAllCards(); + this.evaluateAndSavePoints(); + this.phase = gamePhase.newRound; + this.sendObfuscatedGameUpdate(); + this.sendMessageToAllPlayers("Waiting for next round"); + } + + async nextRound() { + const playerSocketIds = this.players.map((player) => player.socketId); + await this.waitForPlayerActions( + [["next-round", this.nextRoundAction.bind(this)]], + playerSocketIds + ); + } + + // Player Action Callbacks + + revealCardAction(playerSocketId: string, cardPosition: number) { + const player = this.getPlayerBySocketId(playerSocketId); + const revealedCard = player.cards[cardPosition]; + console.log(`Revealed card ${revealedCard} at position ${cardPosition}`); + const playerIndex = this.players.indexOf(player!); + this.players[playerIndex].knownCardPositions[cardPosition] = true; + this.sendObfuscatedGameUpdate(); + } + + drawCardAction(playerSocketId: string, data: any) { + const player = this.getPlayerBySocketId(playerSocketId); + console.log(`Player ${player.name} drawed a card.`); + const drawnCard = this.cardStack.cards.pop()!; + player.cardCache = drawnCard; + this.sendObfuscatedGameUpdate(); + } + + takeDiscardPileAction(playerSocketId: string, data: any) { + const player = this.getPlayerBySocketId(playerSocketId); + console.log(`Player ${player.name} took the card from discard pile.`); + const discardPileCard = this.discardPile.pop()!; + player.cardCache = discardPileCard; + player.tookDispiledCard = true; + this.sendObfuscatedGameUpdate(); + } + + discardCardToPileAction(playerSocketId: string, data: any) { + const player = this.getPlayerBySocketId(playerSocketId); + console.log(`Player ${player.name} discarded a card to the pile.`); + const discardedCard = player.cardCache!; + this.discardPile.push(discardedCard); + player.cardCache = null; + this.phase = gamePhase.revealCard; + this.sendObfuscatedGameUpdate(); + } + + placeCardAction(playerSocketId: string, cardPosition: number) { + const player = this.getPlayerBySocketId(playerSocketId); + console.log(`Player ${player.name} placed a card.`); + const placedCard = player.cardCache!; + player.cardCache = null; + const replacedCard = player.cards[cardPosition]; + this.discardPile.push(replacedCard); + player.cards[cardPosition] = placedCard; + player.knownCardPositions[cardPosition] = true; + this.nextPlayersTurn(); + this.phase = gamePhase.pickUpCard; + this.sendObfuscatedGameUpdate(); + } + + nextRoundAction(playerSocketId: string, data: any) { + this.initializeNewRound(); + this.sendObfuscatedGameUpdate(); + } + + /** + * This function waits for a player to perform one of the expected actions. + * When a player performs one of the expected actions, the corresponding callback is called and further processes the player data. + * All event listeners are removed after every player defined in expectedFrom performed the expected action. + * The function also returns a promise that resolves with the data sent by the player. + * @param expectedActions + * @param expectedFrom + * @returns playerSocketId and data sent by the player + */ + waitForPlayerActions( + expectedActions: ExpectedPlayerActions, + expectedFrom: Player["socketId"][] + ): Promise> { + const eventListeners = new Map void>(); + + const addPlayerActionListeners = ( + resolve: ( + value: + | PlayerAction + | PromiseLike> + ) => void + ) => { + expectedFrom.forEach((playerSocketId) => { + const playerSocket = io.sockets.sockets.get(playerSocketId); + if (playerSocket) { + expectedActions.forEach((expectedAction) => { + const [actionName, processAction] = expectedAction; + const eventListener = (data: ActionDataType) => { + console.log(`Received ${actionName} from ${playerSocketId}`); + processAction(playerSocketId, data); + // remove current and event listeners of alternative expected actions + removePlayerActionListeners(); + const playerResponse = { playerSocketId, data }; + resolve(playerResponse); + }; + playerSocket.on(actionName, eventListener); + eventListeners.set( + `${playerSocketId}-${actionName}`, + eventListener + ); + }); + } + }); + }; + + const removePlayerActionListeners = () => { + expectedFrom.forEach((playerSocketId) => { + const playerSocket = io.sockets.sockets.get(playerSocketId); + if (playerSocket) { + expectedActions.forEach((expectedAction) => { + const [actionName] = expectedAction; + + const eventListener = eventListeners.get( + `${playerSocketId}-${actionName}` + ); + if (eventListener) { + playerSocket.off(actionName, eventListener); + eventListeners.delete(`${playerSocketId}-${actionName}`); + } + }); + } + }); + }; + + return new Promise>((resolve) => { + addPlayerActionListeners(resolve); + }); + } + + sendObfuscatedGameUpdate() { + // console.trace("sendObfuscatedGameUpdate"); + this.updatePlayerRoundPoints(); + const obfuscatedGame: ObfuscatedGame = { + sessionId: this.sessionId, + playerCount: this.playerCount, + phase: this.phase, + round: this.round, + discardPile: this.discardPile, + players: this.players.map(({ cards, ...player }) => { + return { + id: player.id, + socketId: player.socketId, + name: player.name, + playersTurn: player.playersTurn, + cardCache: player.cardCache, + tookDispiledCard: player.tookDispiledCard, + knownCardPositions: player.knownCardPositions, + roundPoints: player.roundPoints, + totalPoints: player.totalPoints, + closedRound: player.closedRound, + cards: cards.map((card: Card, index: number) => { + // unknown cards are obfuscated to 0 + return { + id: player.knownCardPositions[index] ? card.id : 0, + value: player.knownCardPositions[index] ? card.value : "X", + name: player.knownCardPositions[index] + ? card.name + : "Facedown Card", + color: player.knownCardPositions[index] ? card.color : "black", + matchColorToCardValue: card.matchColorToCardValue, + }; + }), + }; + }), + cardStack: { + cards: this.cardStack.cards.map((card: Card) => { + // player may not see the value of the facedown cards in the cardStack + return { + id: 0, + value: "X", + name: "Facedown Card", + color: "black", + }; + }), + }, + }; + console.log("Sending game update"); + io.to(this.sessionId).emit("game-update", obfuscatedGame); + } + + updatePlayerRoundPoints() { + this.players.forEach((player) => { + const revealedCardValuesSum = player.getRevealedCardsValueSum(); + const threeOfAKinds = player.getThreeOfAKinds(); + const threeOfAKindPoints = threeOfAKinds.reduce( + (points, threeOfAKind) => points + threeOfAKind.value * 3, + 0 + ); + + player.roundPoints = revealedCardValuesSum - threeOfAKindPoints; + }); + } + + getPlayersWithLowestPoints(): Player[] { + this.updatePlayerRoundPoints(); + const lowestScore = Math.min( + ...this.players.map((player) => player.roundPoints) + ); + const playersWithLowestPoints = this.players.filter( + (player) => player.roundPoints === lowestScore + ); + return playersWithLowestPoints; + } + + evaluateAndSavePoints() { + const playersWithLowestPoints = this.getPlayersWithLowestPoints(); + const playerClosedRound = this.getPlayerThatClosedRound(); + if ( + playersWithLowestPoints.includes(playerClosedRound) && + playersWithLowestPoints.length === 1 + ) { + this.sendMessageToAllPlayers(`${playerClosedRound.name} won the round!`); + this.players.forEach((player) => { + player.totalPoints += player.roundPoints; + }); + return; + } else if (playersWithLowestPoints.length === 1) { + this.sendMessageToAllPlayers( + `${playersWithLowestPoints[0].name} won the round!` + ); + } else if (playersWithLowestPoints.length > 1) { + this.sendMessageToAllPlayers( + `${playersWithLowestPoints + .map((player) => player.name) + .join(", ")} scored equally the lowest points!` + ); + } + this.players.forEach((player) => { + if (player.closedRound) player.totalPoints += player.roundPoints * 2; + else player.totalPoints += player.roundPoints; + }); + this.sendMessageToAllPlayers( + `${playerClosedRound.name} points are doubled!` + ); + } + + checkForFullRevealedCards() { + const alreadyClosedPlayers = this.players.filter( + (player) => player.closedRound + ); + if (alreadyClosedPlayers) return; + + const playerWithAllCardsRevealed = this.players.find((player) => + player.knownCardPositions.every( + (knownCardPosition) => knownCardPosition === true + ) + ); + if (playerWithAllCardsRevealed) { + playerWithAllCardsRevealed.closedRound = true; + } + } + + revealAllCards() { + this.players.forEach((player) => { + player.knownCardPositions = player.knownCardPositions.map( + (knownCardPosition) => true + ); + }); + } + + // Helpers + getPlayersWithRevealedInitialCards(): Player[] { return this.players.filter((player) => { return player.hasInitialCardsRevealed(); @@ -175,195 +563,35 @@ export class Game { } nextPlayersTurn() { - const playersTurn = this.players.find((player) => player.playersTurn); - const playersTurnIndex = this.players.indexOf(playersTurn!); + const playerOnTurn = this.getPlayersTurn(); + const playersTurnIndex = this.players.indexOf(playerOnTurn); const nextPlayersTurnIndex = (playersTurnIndex + 1) % this.playerCount; this.players[playersTurnIndex].playersTurn = false; this.players[nextPlayersTurnIndex].playersTurn = true; } - getPlayerBySocketId(playerSocketId: string): Player | undefined { - return this.players.find((player) => player.socketId === playerSocketId); - } - - revealCardAction(playerSocketId: string, cardPosition: number) { - const player = this.getPlayerBySocketId(playerSocketId)!; // TODO: handle player not found - const revealedCard = player.cards[cardPosition]; - console.log(`Revealed card ${revealedCard} at position ${cardPosition}`); - const playerIndex = this.players.indexOf(player!); - this.players[playerIndex].knownCardPositions[cardPosition] = true; - this.sendObfuscatedGameUpdate(); - } - - async revealInitialCards() { - while (!this.allPlayersRevealedInitialCards()) { - const playersWithRevealedInitialCards = - this.getPlayersWithRevealedInitialCards(); - const playersWithUnrevealedInitialCards = this.players.filter( - (player) => !playersWithRevealedInitialCards.includes(player) - ); - const playersSocketIds = playersWithUnrevealedInitialCards.map( - (player) => player.socketId - ); - await this.waitForPlayerActions( - [["click-card", this.revealCardAction.bind(this)]], - playersSocketIds - ); - } - this.setInitialPlayersTurn(); - } - - drawCardAction(playerSocketId: string, data: any) { - const player = this.getPlayerBySocketId(playerSocketId)!; // TODO: handle player not found - console.log(`Player ${player.name} drawed a card.`); - const drawnCard = this.cardStack.cards.pop()!; - player.cardCache = drawnCard; - this.sendObfuscatedGameUpdate(); - } - - takeDiscardPileAction(playerSocketId: string, data: any) { - const player = this.getPlayerBySocketId(playerSocketId)!; // TODO: handle player not found - console.log(`Player ${player.name} took the card from discard pile.`); - const discardPileCard = this.discardPile.pop()!; - player.cardCache = discardPileCard; - this.sendObfuscatedGameUpdate(); - } - - discardCardToPileAction(playerSocketId: string, data: any) { - const player = this.getPlayerBySocketId(playerSocketId)!; // TODO: handle player not found - console.log(`Player ${player.name} discarded a card to the pile.`); - const discardedCard = player.cardCache!; - this.discardPile.push(discardedCard); - player.cardCache = null; - this.sendObfuscatedGameUpdate(); - this.phase = gamePhase.revealCard; - } - - placeCardAction(playerSocketId: string, cardPosition: number) { - const player = this.getPlayerBySocketId(playerSocketId)!; // TODO: handle player not found - console.log(`Player ${player.name} placed a card.`); - const placedCard = player.cardCache!; - player.cardCache = null; - const replacedCard = player.cards[cardPosition]; - this.discardPile.push(replacedCard); - player.cards[cardPosition] = placedCard; - player.knownCardPositions[cardPosition] = true; - this.nextPlayersTurn(); - this.phase = gamePhase.pickUpCard; - this.sendObfuscatedGameUpdate(); - } - - async pickUpCard() { - const playersTurn = this.players.find((player) => player.playersTurn); - console.log(`Waiting for ${playersTurn?.name} to pick up card`); - await this.waitForPlayerActions( - [ - ["draw-from-card-stack", this.drawCardAction.bind(this)], - ["click-discard-pile", this.takeDiscardPileAction.bind(this)], - ], - [playersTurn!.socketId] + getPlayersTurn(): Player { + const playerOnTurn = this.players.find( + (player) => player.playersTurn === true ); + if (playerOnTurn) return playerOnTurn; + else throw new Error("No player on turn found!"); } - async placeCard(tookDiscardPileCard: boolean = false) { - console.log("Waiting for player to place card"); - const playersTurn = this.players.find((player) => player.playersTurn); - const allowedActions: PlayerActions = [ - ["click-card", this.placeCardAction.bind(this)], - ]; - if (!tookDiscardPileCard) { - allowedActions.push([ - "click-discard-pile", - this.discardCardToPileAction.bind(this), - ]); - } - await this.waitForPlayerActions(allowedActions, [playersTurn!.socketId]); - } - - async revealCard() { - console.log("Waiting for player to reveal a card"); - const playersTurn = this.players.find((player) => player.playersTurn); - await this.waitForPlayerActions( - [["click-card", this.revealCardAction.bind(this)]], - [playersTurn!.socketId] + getPlayerThatClosedRound(): Player { + const playerThatClosedRound = this.players.find( + (player) => player.closedRound === true ); - this.nextPlayersTurn(); + if (playerThatClosedRound) return playerThatClosedRound; + else throw new Error("No player that closed the round found!"); } - waitForPlayerActions( - expectedActions: PlayerActions, - expectedFrom: Player["socketId"][] - ): Promise { - return new Promise((resolve) => { - const eventListeners: Array<(playerSocketId: string, data: any) => void> = - []; - expectedFrom.forEach((playerSocketId) => { - const playerSocket = io.sockets.sockets.get(playerSocketId); - if (playerSocket) { - expectedActions.forEach((expectedAction) => { - const [actionName, processAction] = expectedAction; - const eventListener = (data: any) => { - console.log(`Received ${actionName} from ${playerSocketId}`); - processAction(playerSocketId, data); - // remove current and event listeners of alternative expected actions - eventListeners.forEach((eventListener) => { - playerSocket.off(actionName, eventListener); - }); - resolve(data); - }; - playerSocket.on(actionName, eventListener); - eventListeners.push(eventListener); - }); - } else { - // TODO: handle error - } - }); - }); - } - - sendObfuscatedGameUpdate() { - // console.trace("sendObfuscatedGameUpdate"); - const obfuscatedGame: ObfuscatedGame = { - sessionId: this.sessionId, - playerCount: this.playerCount, - players: this.players.map((player: Player) => { - return { - id: player.id, - socketId: player.socketId, - name: player.name, - cards: player.cards.map((card: Card, index: number) => { - // unknown cards are obfuscated to 0 - return { - id: player.knownCardPositions[index] ? card.id : 0, - value: player.knownCardPositions[index] ? card.value : "X", - name: player.knownCardPositions[index] - ? card.name - : "Facedown Card", - color: player.knownCardPositions[index] ? card.color : "black", - matchColorToCardValue: card.matchColorToCardValue, - }; - }), - knownCardPositions: player.knownCardPositions, - playersTurn: player.playersTurn, - cardCache: player.cardCache, - }; - }), - cardStack: { - cards: this.cardStack.cards.map((card: Card) => { - // player may not see the value of the facedown cards in the cardStack - return { - id: 0, - value: "X", - name: "Facedown Card", - color: "black", - }; - }), - }, - discardPile: this.discardPile, - phase: this.phase, - }; - console.log("Sending game update"); - io.to(this.sessionId).emit("game-update", obfuscatedGame); + getPlayerBySocketId(playerSocketId: string): Player { + const player = this.players.find( + (player) => player.socketId === playerSocketId + ); + if (player) return player; + else throw new Error(`No player with socketId ${playerSocketId} found!`); } listPlayerSocketListeners() { @@ -375,4 +603,9 @@ export class Game { }); }); } + + sendMessageToAllPlayers(message: string) { + io.to(this.sessionId).emit("message", message); + console.log(`Sent Message (Session): ${message}`); + } } diff --git a/src/game/player.ts b/src/game/player.ts index 62117e5..a6f54cf 100644 --- a/src/game/player.ts +++ b/src/game/player.ts @@ -8,8 +8,26 @@ export type ObfuscatedPlayer = { knownCardPositions: boolean[]; playersTurn: boolean; cardCache: Card | null; + tookDispiledCard: boolean; + roundPoints: number; + totalPoints: number; + closedRound: boolean; }; +type ColumnPosition = [number, number, number]; + +type ThreeOfAKind = { + position: ColumnPosition; + value: number; +}; + +const COLUMN_POSITIONS: ColumnPosition[] = [ + [0, 4, 8], + [1, 5, 9], + [2, 6, 10], + [3, 7, 11], +]; + export class Player { id: number; socketId: string; @@ -18,6 +36,10 @@ export class Player { knownCardPositions: boolean[]; playersTurn: boolean; cardCache: Card | null; // this is where the card is temporarily stored when a player draws a card + tookDispiledCard: boolean; // this is used to check if a player took a dispiled card in the current turn + roundPoints: number; + totalPoints: number; + closedRound: boolean; constructor(id: number, socketId: string, name: string, cards: Card[]) { this.id = id; this.socketId = socketId; @@ -26,6 +48,10 @@ export class Player { this.knownCardPositions = new Array(12).fill(false); this.playersTurn = true; this.cardCache = null; + this.tookDispiledCard = false; + this.roundPoints = 0; + this.totalPoints = 0; + this.closedRound = false; } hasInitialCardsRevealed(): boolean { @@ -40,6 +66,28 @@ export class Player { return this.knownCardPositions.filter((position) => position).length; } + getThreeOfAKinds(): ThreeOfAKind[] { + const revealedCardPositions = this.getRevealedCardPositions(); + const columns: ColumnPosition[] = COLUMN_POSITIONS; + const threeOfAKinds: ThreeOfAKind[] = []; + columns.forEach((column) => { + const columnValues = column.map((position) => this.cards[position].value); + const columnHasSameValues = columnValues.every( + (value) => value === columnValues[0] + ); + const columnIsRevealed = column.every((position) => + revealedCardPositions.includes(position) + ); + if (columnHasSameValues && columnIsRevealed) { + threeOfAKinds.push({ + position: column, + value: columnValues[0], + }); + } + }); + return threeOfAKinds; + } + getRevealedCardPositions(): number[] { const revealedCardPositions: number[] = []; this.knownCardPositions.forEach((position, index) => {