This commit is contained in:
pb-coding 2023-09-20 10:25:39 +02:00
parent 512e5e8b5d
commit dc7c0c1c6e
4 changed files with 691 additions and 509 deletions

509
package-lock.json generated
View file

@ -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"
}

View file

@ -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,
};
};

View file

@ -6,10 +6,20 @@ import { io } from "../server";
type PlayerSocketSet = Set<string>;
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<ActionDataType> = {
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<CardPosition>(
[["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<ActionDataType>(
expectedActions: ExpectedPlayerActions,
expectedFrom: Player["socketId"][]
): Promise<PlayerAction<ActionDataType>> {
const eventListeners = new Map<string, (...args: any[]) => void>();
const addPlayerActionListeners = (
resolve: (
value:
| PlayerAction<ActionDataType>
| PromiseLike<PlayerAction<ActionDataType>>
) => 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<PlayerAction<ActionDataType>>((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<void> {
return new Promise<void>((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}`);
}
}

View file

@ -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) => {