commit 733ac3891f6aeef9ff79c9806177c3469fc4cc8d Author: Jose Conde Date: Fri Jul 5 01:19:43 2024 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..26683c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# Node.js +node_modules/ +npm-debug.log +yarn-error.log + +# Logs +logs/ +*.log + +# Build output +dist/ +build/ + +# Environment variables +.env + +# IDE files +.vscode/ +.idea/ + +# Miscellaneous +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/.hmrc b/.hmrc new file mode 100644 index 0000000..31678dd --- /dev/null +++ b/.hmrc @@ -0,0 +1,34 @@ +{ + "path": "G:\\Other\\Development\\Projects\\[ideas]\\domino", + "name": "domino", + "initialVersion": "1.0.0", + "version": "1.0.0", + "docker": { + "repository": "arhuako/domino" + }, + "repository": { + "type": "github", + "user": "jmconde", + "name": "domino", + "manage": true, + "createOnInit": true + }, + "changelog": { + "create": true, + "managed": true, + "createHTML": true, + "htmlPath": "public" + }, + "_backupInitial": { + "name": "domino", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e8cfef1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog +All notable changes to this project will be documented in this file. + +## Unreleased +Initial commit diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..64bee94 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1638 @@ +{ + "name": "domino", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "domino", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "chalk": "^4.1.2", + "cors": "^2.8.5", + "express": "^4.19.2", + "pino": "^9.2.0", + "pino-pretty": "^11.2.1", + "seedrandom": "^3.0.5", + "socket.io": "^4.7.5" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/node": "^20.14.8", + "@types/seedrandom": "^3.0.8", + "ts-node": "^10.9.2", + "typescript": "^5.5.2" + }, + "engines": { + "node": ">=20.6.0" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.5", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", + "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.14.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.8.tgz", + "integrity": "sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/qs": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/seedrandom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-3.0.8.tgz", + "integrity": "sha512-TY1eezMU2zH2ozQoAFAQFOPpvP15g+ZgSfTZt31AUUH/Rxtnz3H+A/Sv1Snw2/amp//omibc+AEkTaA8KUeOLQ==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", + "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "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", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "engines": { + "node": "*" + } + }, + "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/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "engines": { + "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.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "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/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==" + }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/pino": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.2.0.tgz", + "integrity": "sha512-g3/hpwfujK5a4oVbaefoJxezLzsDgLcNJeITvC6yrfwYeT9la+edCK42j5QpEQSQCZgTKapXvnQIdgZwvRaZug==", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.2.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^3.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-pretty": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.2.1.tgz", + "integrity": "sha512-O05NuD9tkRasFRWVaF/uHLOvoRDFD7tb5VMertr78rbsYFjYp48Vg3477EshVAF5eZaEw+OpDl/tu+B0R5o+7g==", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.2", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==" + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/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/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/socket.io": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-adapter/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-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "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.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "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/sonic-boom": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.0.1.tgz", + "integrity": "sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", + "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2817591 --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "name": "domino", + "version": "1.0.0", + "description": "", + "main": "index.js", + "engines": { + "node": ">=20.6.0" + }, + "scripts": { + "build": "tsc", + "dev": "node --env-file=.env --watch -r ts-node/register src/server/index.ts", + "test": "node --env-file=.env -r ts-node/register src/test.ts", + "test:watch": "node --env-file=.env --watch -r ts-node/register src/test.ts", + "docker-build": "docker build -t arhuako/domino:latest .", + "docker-tag": "docker tag arhuako/domino:latest arhuako/domino:1.0.0", + "docker-push": "docker push arhuako/domino:latest && docker push arhuako/domino:1.0.0", + "publish": "npm run docker-build && npm run docker-tag && npm run docker-push" + }, + "keywords": [], + "author": "arhuako", + "license": "ISC", + "type": "commonjs", + "reposityory": "github:jmconde/domino", + "dependencies": { + "chalk": "^4.1.2", + "cors": "^2.8.5", + "express": "^4.19.2", + "pino": "^9.2.0", + "pino-pretty": "^11.2.1", + "seedrandom": "^3.0.5", + "socket.io": "^4.7.5" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/node": "^20.14.8", + "@types/seedrandom": "^3.0.8", + "ts-node": "^10.9.2", + "typescript": "^5.5.2" + } +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..7b7a371 --- /dev/null +++ b/public/index.html @@ -0,0 +1,22 @@ + + + + + + Domino Tiles + + + +
+
+
+
+ +
+
+
+
+ + + + diff --git a/public/script.js b/public/script.js new file mode 100644 index 0000000..0da70e6 --- /dev/null +++ b/public/script.js @@ -0,0 +1,35 @@ +document.querySelectorAll('.domino').forEach(domino => { + const topValue = domino.getAttribute('data-top'); + const bottomValue = domino.getAttribute('data-bottom'); + + addPips(domino.querySelector('.top'), topValue); + addPips(domino.querySelector('.bottom'), bottomValue); +}); + +function addPips(half, value) { + const pipPositions = getPipPositions(value); + pipPositions.forEach(pos => { + const pip = document.createElement('div'); + pip.className = 'pip'; + pip.style.left = pos[0]; + pip.style.top = pos[1]; + half.appendChild(pip); + }); +} + +function getPipPositions(value) { + const p1 = '17%'; + const p2 = '42%'; + const p3 = '67%'; + + const positions = { + 0: [], + 1: [[p2, p2]], + 2: [[p1, p1], [p3, p3]], + 3: [[p1, p1], [p2, p2], [p3, p3]], + 4: [[p1, p1], [p1, p3], [p3, p1], [p3, p3]], + 5: [[p1, p1], [p1, p3], [p2, p2], [p3, p1], [p3, p3]], + 6: [[p1, p1], [p1, p3], [p2, p1], [p2, p3], [p3, p1], [p3, p3]], + }; + return positions[value]; +} diff --git a/public/styles.css b/public/styles.css new file mode 100644 index 0000000..8ec5a31 --- /dev/null +++ b/public/styles.css @@ -0,0 +1,77 @@ +body { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + background: #f0f0f0; + font-family: Arial, sans-serif; +} + +.domino { + width: 100px; + height: 200px; + background: white; + border: 2px solid black; + border-radius: 10px; + position: relative; + display: flex; + flex-direction: column; +} + +.half { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + position: relative; +} + +.top { + border-bottom: 2px solid black; +} + +.pip { + width: 15px; + height: 15px; + background: black; + border-radius: 50%; + position: absolute; +} + +.pip[data-number="1"] { + left: 50%; + top: 50%; + transform: translate(-50%, -50%); +} + +.pip[data-number="2"] { + left: 25%; + top: 25%; +} + +.pip[data-number="3"] { + right: 25%; + top: 25%; +} + +.pip[data-number="4"] { + left: 25%; + bottom: 25%; +} + +.pip[data-number="5"] { + right: 25%; + bottom: 25%; +} + +.pip[data-number="6"] { + left: 50%; + top: 50%; + transform: translate(-50%, -50%); +} + +.pip[data-number="7"] { + right: 50%; + top: 50%; + transform: translate(50%, -50%); +} diff --git a/src/common/LoggingService.ts b/src/common/LoggingService.ts new file mode 100644 index 0000000..04d318b --- /dev/null +++ b/src/common/LoggingService.ts @@ -0,0 +1,102 @@ +import pino, { BaseLogger } from 'pino'; +import path from 'path'; + +export class LoggingService { + static instance: LoggingService; + logsPath: string = path.join(process.cwd(), 'app', 'server', 'logs'); + logger!: BaseLogger; + level: string = process.env.LOG_LEVEL || 'info'; + + /* + * ogger.fatal('fatal'); + logger.error('error'); + logger.warn('warn'); + logger.info('info'); + logger.debug('debug'); + logger.trace('trace'); + */ + constructor() { + if ((!LoggingService.instance)) { + LoggingService.instance = this; + this.logger = pino({ + level: this.level, + timestamp: pino.stdTimeFunctions.isoTime, + }, this.transports); + } + return LoggingService.instance; + } + + get commonRorationOptions() : any { + return { + interval: '1d', + maxFiles: 10, + path: this.logsPath, + size: '10M', + maxSize: '100M', + }; + } + + get transports() { + return pino.transport({ + targets: [ + // { + // target: 'pino-rotating-file-stream', + // level: this.level, + // options: { + // filename: 'app.log', + // ...this.commonRorationOptions + // }, + // }, + { + target: 'pino-pretty', + level: this.level, + options: { + sync: true, + colorized: true, + } + }, + ] + }); + } + + debug(message: string, data?: any) { + this.logger.debug(this._getMessageWidthObject(message, data)); + } + + info(message: string, data?: any) { + this.logger.info(this._getMessageWidthObject(message, data)); + } + + warn(message: string, data?: any) { + this.logger.warn(this._getMessageWidthObject(message, data)); + } + + error(error: any, message?: string) { + this.logger.error(error, message); + } + + fatal(message: string, data?: any) { + this.logger.fatal(this._getMessageWidthObject(message, data)); + } + + + trace(message: string, data?: any) { + this.logger.trace(this._getMessageWidthObject(message, data)); + } + + object(message: any) { + this.logger.info(JSON.stringify(message, null, 2)); + } + + _getMessageWidthObject(message: string, data?: any) { + if (!data) { + return message; + } + return `${message}\n${this._getStringObject(data)}`; + } + + _getStringObject(data: any) { + return JSON.stringify(data, null, 2); + } + +} \ No newline at end of file diff --git a/src/common/exceptions/ErrorBase.ts b/src/common/exceptions/ErrorBase.ts new file mode 100644 index 0000000..a9d7364 --- /dev/null +++ b/src/common/exceptions/ErrorBase.ts @@ -0,0 +1,8 @@ +export class ErrorBase extends Error { + constructor(message: string) { + super(message); + console.log('this.constructor.name :>> ', this.constructor.name); + this.name = this.constructor.name; + this.stack = (new Error()).stack; + } +} \ No newline at end of file diff --git a/src/common/exceptions/SocketDisconnectedError.ts b/src/common/exceptions/SocketDisconnectedError.ts new file mode 100644 index 0000000..5da76ab --- /dev/null +++ b/src/common/exceptions/SocketDisconnectedError.ts @@ -0,0 +1,7 @@ +import { ErrorBase } from "./ErrorBase"; + +export class SocketDisconnectedError extends ErrorBase { + constructor() { + super('Socket disconnected'); + } +} \ No newline at end of file diff --git a/src/common/utilities.ts b/src/common/utilities.ts new file mode 100644 index 0000000..7f84961 --- /dev/null +++ b/src/common/utilities.ts @@ -0,0 +1,63 @@ + +import { randomBytes, randomUUID } from 'crypto'; +import * as readline from 'readline'; +import { Tile } from '../game/entities/Tile'; +import chalk from 'chalk'; +import { Board } from '../game/entities/Board'; + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +export async function wait(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +export function askQuestion(question: string): Promise { + return new Promise((resolve) => { + // console.log(chalk.yellow(question)); + rl.question(`${chalk.yellow(question + ' > ')}`, (answer) => { + resolve(answer); + }); + }); +} + +export function getRandomSeed(): string { + const timestamp = Date.now(); + const randomPart = Math.random().toString(36).substring(2); + const securePart = randomBytes(4).toString('hex'); + return `${timestamp}-${randomPart}-${securePart}`; +} + +export function printTiles(prefix:string, tiles: Tile[]): void { + console.log(`${prefix}${tiles.join(' ')}`); +} + +export function printSelection(prefix:string, tiles: Tile[]): void { + const line: string = tiles.map((t,i) => { + const index = i + 1; + return `(${index > 9 ? `${index})`: `${index}) `} ` + }).join(' '); + printTiles(prefix, tiles); + console.log(`${Array(prefix.length).join((' '))} ${line}`); +} + +export function printBoard(board: Board, highlighted: boolean = false): void { + if (highlighted) + console.log(chalk.cyan(`Board: ${board.tiles.length > 0 ? board.tiles.join(' ') : '--empty--'}`)); + else + console.log(chalk.gray(`Board: ${board.tiles.length > 0 ? board.tiles.join(' ') : '--empty--'}`)); +} + +export function printLine(msg: string): void { + console.log(chalk.grey(msg)); +} + +export function printError(msg: string): void { + console.log(chalk.red(msg)); +} + +export function uuid() { + return randomUUID(); +} \ No newline at end of file diff --git a/src/game/DominoesGame.ts b/src/game/DominoesGame.ts new file mode 100644 index 0000000..59146e9 --- /dev/null +++ b/src/game/DominoesGame.ts @@ -0,0 +1,238 @@ +import { PRNG } from 'seedrandom'; +import { Board } from "./entities/Board"; +import { PlayerMove } from "./entities/PlayerMove"; +import { PlayerInterface } from "./entities/player/PlayerInterface"; +import { Tile } from "./entities/Tile"; +import { LoggingService } from "../common/LoggingService"; +import { printBoard, printLine, uuid, wait } from '../common/utilities'; +import { GameSummary } from './dto/GameSummary'; +import { PlayerNotificationManager } from './PlayerNotificationManager'; +import { GameState } from './dto/GameState'; + +export class DominoesGame { + private id: string; + private seed: string | undefined; + autoDeal: boolean = true; + board: Board; + currentPlayerIndex: number = 0; + gameInProgress: boolean = false; + gameOver: boolean = false; + gameBlocked: boolean = false; + gameTied: boolean = false; + tileSelectionPhase: boolean = true; + logger: LoggingService = new LoggingService(); + blockedCount: number = 0; + winner: PlayerInterface | null = null; + rng: PRNG; + handSize: number = 7; + notificationManager: PlayerNotificationManager = new PlayerNotificationManager(this); + lastMove: PlayerMove | null = null; + + constructor(public players: PlayerInterface[], seed: PRNG) { + this.id = uuid(); + this.logger.info(`Game ID: ${this.id}`); + this.logger.info(`Seed: ${this.seed}`); + this.rng = seed + this.board = new Board(seed); + this.initializeGame(); + } + + async initializeGame() { + this.gameOver = false; + this.gameBlocked = false; + this.gameTied = false; + this.board.boneyard = this.generateTiles(); + } + + generateTiles(): Tile[] { + const tiles: Tile[] = []; + for (let i = 6; i >= 0; i--) { + for (let j = i; j >= 0; j--) { + tiles.push(new Tile([i, j])); + } + } + this.logger.debug('tiles :>> ' + tiles); + return this.shuffle(tiles); + } + + private shuffle(array: Tile[]): Tile[] { + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(this.rng() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } + return array; + } + + nextPlayer() { + this.currentPlayerIndex = (this.currentPlayerIndex + 1) % this.players.length; + } + + isBlocked(): boolean { + return this.blockedCount === this.players.length; + } + + isGameOver(): boolean { + const hasWinner: boolean = this.players.some(player => player.hand.length === 0); + return hasWinner || this.gameBlocked; + } + + getWinner(): PlayerInterface | null { + if (!this.gameOver) { + return null; + } + const winnerNoTiles = this.players.find(player => player.hand.length === 0); + if (winnerNoTiles !== undefined) { + return winnerNoTiles; + } + const winnerMinPipsCount = this.players.reduce((acc, player) => { + return player.pipsCount() < acc.pipsCount() ? player : acc; + }); + return winnerMinPipsCount; + } + + getStartingPlayerIndex(): number { + // Determine starting player + let startingIndex = this.players.findIndex(player => player.hand.some(tile => tile.pips[0] === 6 && tile.pips[1] === 6)); + if (startingIndex === -1) { + startingIndex = this.players.findIndex(player => player.hand.some(tile => tile.pips[0] === 5 && tile.pips[1] === 5)); + if (startingIndex === -1) { + startingIndex = this.players.findIndex(player => player.hand.some(tile => tile.pips[0] === 4 && tile.pips[1] === 4)); + if (startingIndex === -1) { + startingIndex = this.players.findIndex(player => player.hand.some(tile => tile.pips[0] === 3 && tile.pips[1] === 3)); + if (startingIndex === -1) { + startingIndex = this.players.findIndex(player => player.hand.some(tile => tile.pips[0] === 2 && tile.pips[1] === 2)); + if (startingIndex === -1) { + startingIndex = this.players.findIndex(player => player.hand.some(tile => tile.pips[0] === 1 && tile.pips[1] === 1)); + if (startingIndex === -1) { + startingIndex = this.players.findIndex(player => player.hand.some(tile => tile.pips[0] === 0 && tile.pips[1] === 0)); + } + } + } + } + } + } + return startingIndex === -1 ? 0 : startingIndex; + } + + async playTurn(): Promise { + const player = this.players[this.currentPlayerIndex]; + console.log(`${player.name}'s turn (${player.hand.length} tiles)`); + printBoard(this.board); + + // let playerMove: PlayerMove | null = null; + // while(playerMove === null) { + // try { + // playerMove = await player.makeMove(this.board); + // } catch (error) { + // this.logger.error(error, 'Error making move'); + // } + // } + const playerMove = await player.makeMove(this.board); + printBoard(this.board, true); + this.lastMove = playerMove; + if (playerMove === null) { + console.log('Player cannot move'); + this.blockedCount += 1; + this.nextPlayer(); + return; + } + this.blockedCount = 0; + this.board.play(playerMove); + player.hand = player.hand.filter(tile => tile !== playerMove.tile); + this.nextPlayer(); + } + + async start(): Promise { + this.gameInProgress = false; + this.tileSelectionPhase = true; + await this.notificationManager.notifyGameState(); + await this.notificationManager.notifyPlayersState(); + this.logger.debug('clients received boneyard :>> ' + this.board.boneyard); + await wait(1000); + + if (this.autoDeal) { + this.dealTiles(); + await this.notificationManager.notifyGameState(); + await this.notificationManager.notifyPlayersState(); + } else { + await this.tilesSelection(); + } + + this.tileSelectionPhase = false; + this.gameInProgress = true; + this.currentPlayerIndex = this.getStartingPlayerIndex(); + printLine(`${this.players[this.currentPlayerIndex].name} is the starting player:`); + while (!this.gameOver) { + await this.playTurn(); + await this.notificationManager.notifyGameState(); + await this.notificationManager.notifyPlayersState(); + this.gameBlocked = this.isBlocked(); + this.gameOver = this.isGameOver(); + } + this.gameInProgress = false; + this.winner = this.getWinner(); + + return { + gameId: this.id, + isBlocked: this.gameBlocked, + isTied: this.gameTied, + winner: this.winner + }; + } + + dealTiles() { + for (let i = 0; i < this.handSize; i++) { + for (let player of this.players) { + const tile: Tile | undefined = this.board.boneyard.pop(); + if (tile !== undefined) { + tile.revealed = true; + player.hand.push(tile); + } + } + } + } + + async tilesSelection() { + while (this.board.boneyard.length > 0) { + for (let player of this.players) { + const choosen = await player.chooseTile(this.board); + await this.notificationManager.notifyGameState(); + await this.notificationManager.notifyPlayersState(); + if (this.board.boneyard.length === 0) { + break; + } + } + } + } + + getGameState(): GameState { + const currentPlayer = this.players[this.currentPlayerIndex] + return { + id: uuid(), + lastMove: this.lastMove, + gameInProgress: this.gameInProgress, + winner: this.winner, + tileSelectionPhase: this.tileSelectionPhase, + gameBlocked: this.gameBlocked, + gameTied: this.gameTied, + gameId: this.id, + boneyard: this.board.boneyard.map(tile => ({ id: tile.id})), + players: this.players.map(player => ({ + id: player.id, + name: player.name, + score: player.score, + hand: player.hand.map(tile => tile.id), + })), + currentPlayer: { + id: currentPlayer.id, + name: currentPlayer.name + }, + board: this.board.tiles.map(tile => ({ + id: tile.id, + pips: tile.pips + })), + boardFreeEnds: this.board.getFreeEnds(), + } + + } +} \ No newline at end of file diff --git a/src/game/GameSession.ts b/src/game/GameSession.ts new file mode 100644 index 0000000..b851c49 --- /dev/null +++ b/src/game/GameSession.ts @@ -0,0 +1,149 @@ +import { DominoesGame } from "./DominoesGame"; +import { PlayerAI } from "./entities/player/PlayerAI"; +import { PlayerInterface } from "./entities/player/PlayerInterface"; +import { LoggingService } from "../common/LoggingService"; +import { getRandomSeed, uuid, wait } from "../common/utilities"; +import { GameSessionState } from "./dto/GameSessionState"; +import { PlayerNotificationManager } from './PlayerNotificationManager'; +import seedrandom, { PRNG } from "seedrandom"; + +export class GameSession { + private game: DominoesGame | null = null; + private minHumanPlayers: number = 1; + private waitingForPlayers: boolean = true; + private waitingSeconds: number = 0; + private logger: LoggingService = new LoggingService(); + private mode: string = 'classic'; + private pointsToWin: number = 100; + private playerNotificationManager: PlayerNotificationManager; + id: string; + players: PlayerInterface[] = []; + sessionInProgress: boolean = false; + maxPlayers: number = 4; + seed!: string + rng!: PRNG + + constructor(public creator: PlayerInterface, public name?: string) { + this.playerNotificationManager = new PlayerNotificationManager(this); + this.id = uuid(); + this.name = name || `Game ${this.id}`; + this.addPlayer(creator); + this.logger.info(`GameSession created by: ${creator.name}`); + this.creator = creator; + this.playerNotificationManager.notifySessionState(); + } + + get numPlayers() { + return this.players.length; + } + + private async startGame(seed: string) { + this.rng = seedrandom(seed); + const missingPlayers = this.maxPlayers - this.numPlayers; + for (let i = 0; i < missingPlayers; i++) { + this.addPlayer(this.createPlayerAI(i)); + } + this.game = new DominoesGame(this.players, this.rng); + this.sessionInProgress = true; + this.logger.info('Game started'); + this.playerNotificationManager.notifySessionState(); + await this.game.start(); + return this.endGame(); + } + + private endGame(): any { + if (this.game !== null) { + this.sessionInProgress = false; + const { gameBlocked, gameTied, winner } = this.game; + + gameBlocked ? console.log('Game blocked!') : gameTied ? console.log('Game tied!') : console.log('Game over!'); + console.log('Winner: ' + winner?.name + ' with ' + winner?.pipsCount() + ' points'); + this.getScore(this.game); + this.sessionInProgress = false; + this.logger.info('Game ended'); + this.game = null; + this.playerNotificationManager.notifySessionState(); + + return { + gameBlocked, + gameTied, + winner + }; + } + } + + private getScore(game: DominoesGame) { + const pips = game.players + .sort((a,b) => (b.pipsCount() - a.pipsCount())) + .map(player => { + return `${player.name}: ${player.pipsCount()}`; + }); + console.log(`Pips count: ${pips.join(', ')}`); + const totalPoints = game.players.reduce((acc, player) => acc + player.pipsCount(), 0); + if (game.winner !== null) { + game.winner.score += totalPoints; + } + const scores = game.players + .sort((a,b) => (b.score - a.score)) + .map(player => { + return `${player.name}: ${player.score}`; + }); + console.log(`Scores: ${scores.join(', ')}`); + } + + createPlayerAI(i: number) { + const AInames = ["Alice (AI)", "Bob (AI)", "Charlie (AI)", "David (AI)"]; + return new PlayerAI(AInames[i], this.rng); + } + + async start(seed?: string) { + this.seed = seed || getRandomSeed(); + console.log('seed :>> ', this.seed); + if (this.sessionInProgress) { + throw new Error("Game already in progress"); + } + this.waitingForPlayers = true; + this.logger.info('Waiting for players to join'); + while (this.numPlayers < this.maxPlayers) { + this.waitingSeconds += 1; + this.logger.info(`Waiting for players to join: ${this.waitingSeconds}`); + await wait(1000); + } + this.waitingForPlayers = false; + this.logger.info('All players joined'); + this.startGame(this.seed); + } + + addPlayer(player: PlayerInterface) { + if (this.numPlayers >= this.maxPlayers) { + throw new Error("GameSession is full"); + } + this.players.push(player); + this.logger.info(`${player.name} joined the game!`); + } + + toString() { + return `GameSession:(${this.id} ${this.name})`; + } + + getState(): GameSessionState { + return { + id: this.id, + name: this.name!, + creator: this.creator.id, + players: this.players.map(player =>( { + id: player.id, + name: player.name, + })), + sessionInProgress: this.sessionInProgress, + maxPlayers: this.maxPlayers, + numPlayers: this.numPlayers, + waitingForPlayers: this.waitingForPlayers, + waitingSeconds: this.waitingSeconds, + seed: this.seed, + mode: this.mode, + pointsToWin: this.pointsToWin, + status: this.sessionInProgress ? 'in progress' : 'waiting' + }; + } +} \ No newline at end of file diff --git a/src/game/NetworkClientNotifier.ts b/src/game/NetworkClientNotifier.ts new file mode 100644 index 0000000..a973911 --- /dev/null +++ b/src/game/NetworkClientNotifier.ts @@ -0,0 +1,37 @@ +import { NetworkPlayer } from "./entities/player/NetworkPlayer"; +import { LoggingService } from "../common/LoggingService"; + +export class NetworkClientNotifier { + static instance: NetworkClientNotifier; + io: any; + logger: LoggingService = new LoggingService(); + constructor() { + if (!NetworkClientNotifier.instance) { + NetworkClientNotifier.instance = this; + } + + return NetworkClientNotifier.instance + } + + setSocket(io: any) { + this.io = io; + } + + async notifyPlayer(player: NetworkPlayer, event: string, data: any = {}, timeoutSecs: number = 300): Promise { + try { + const response = await this.io.to(player.socketId) + .timeout(timeoutSecs * 1000) + .emitWithAck(event, data); + return response[0] + } catch (error) { + this.logger.error(error); + return false; + } + } + + async broadcast(event: string, data: any) { + const responses = await this.io.emit(event, data); + this.logger.debug('responses :>> ', responses); + return true; + } +} diff --git a/src/game/PlayerInteractionConsole.ts b/src/game/PlayerInteractionConsole.ts new file mode 100644 index 0000000..876fa3d --- /dev/null +++ b/src/game/PlayerInteractionConsole.ts @@ -0,0 +1,70 @@ +import { Board } from "./entities/Board"; +import { PlayerInterface } from "./entities/player/PlayerInterface"; +import { askQuestion, printError, printSelection, printTiles, wait } from "../common/utilities"; +import { PlayerMove } from "./entities/PlayerMove"; +import { Tile } from "./entities/Tile"; +import { PlayerMoveSide, PlayerMoveSideType } from "./constants"; +import { PlayerInteractionInterface } from "./PlayerInteractionInterface"; + +export class PlayerInteractionConsole implements PlayerInteractionInterface { + player: PlayerInterface; + + constructor(player: PlayerInterface) { + this.player = player; + } + + async makeMove(board: Board): Promise { + let move: PlayerMove | null = null; + let tile: Tile; + let side: PlayerMoveSideType | null = null; + const { player } = this; + + printSelection('Hand: ', player.hand); + while (move === null) { + const answer = await askQuestion('Enter your move (tile index side is L or R, e.g. 0L, to pass'); + const char0 = answer.charAt(0); + const char1 = answer.charAt(1); + if (answer === '' || answer === '') { + return null; + } + + if (char1 === 'L' || char1 === 'R' || char1 === 'l' || char1 === 'r') { + side = char1 === 'L' || char1 === 'l' ? PlayerMoveSide.LEFT : PlayerMoveSide.RIGHT; + } + + if (char0 > '0' && char0 <= String(player.hand.length)) { + const tileIndex = parseInt(char0) - 1; + tile = player.hand[tileIndex]; + move = board.isValidMove(tile, side, player); + if (move === null) { + printError('Invalid move'); + } + } + } + return move; + } + + async chooseTile(board: Board): Promise { + const { player: { hand} } = this; + let index: number = -1; + while (index < 0 || index >= board.boneyard.length) { + printTiles('Hand: ', hand); + printSelection('Boneyard: ', board.boneyard); + const answer = await askQuestion('Choose a tile from the boneyard'); + if (answer === '') { + index = 0; + continue; + } + if (answer < '0' || answer > '9') { + printError('Invalid selection'); + continue; + } + index = parseInt(answer) - 1; + } + const tile = board.boneyard.splice(index, 1)[0]; + tile.revealed = true; + hand.push(tile); + return tile; + } +} + diff --git a/src/game/PlayerInteractionInterface.ts b/src/game/PlayerInteractionInterface.ts new file mode 100644 index 0000000..0c3a382 --- /dev/null +++ b/src/game/PlayerInteractionInterface.ts @@ -0,0 +1,11 @@ +import { Board } from "./entities/Board"; +import { PlayerInterface } from "./entities/player/PlayerInterface"; +import { PlayerMove } from "./entities/PlayerMove"; +import { Tile } from "./entities/Tile"; + +export interface PlayerInteractionInterface { + player: PlayerInterface; + + makeMove(board: Board): Promise; + chooseTile(board: Board): Promise +} \ No newline at end of file diff --git a/src/game/PlayerInteractionNetwork.ts b/src/game/PlayerInteractionNetwork.ts new file mode 100644 index 0000000..769e832 --- /dev/null +++ b/src/game/PlayerInteractionNetwork.ts @@ -0,0 +1,49 @@ +import { PlayerInteractionInterface } from './PlayerInteractionInterface'; +import { Board } from './entities/Board'; +import { PlayerInterface } from './entities/player/PlayerInterface'; +import { PlayerMove } from './entities/PlayerMove'; +import { Tile } from './entities/Tile'; +import { NetworkClientNotifier } from './NetworkClientNotifier'; +import { NetworkPlayer } from './entities/player/NetworkPlayer'; +import { PlayerMoveSide, PlayerMoveSideType } from './constants'; +import { SocketDisconnectedError } from '../common/exceptions/SocketDisconnectedError'; + +export class PlayerInteractionNetwork implements PlayerInteractionInterface { + player: PlayerInterface; + clientNotifier = new NetworkClientNotifier(); + + constructor(player: PlayerInterface) { + this.player = player; + } + + async makeMove(board: Board): Promise { + let response = undefined; + try { + response = await this.clientNotifier.notifyPlayer(this.player as NetworkPlayer, 'makeMove', { + freeHands: board.getFreeEnds(), + }); + } catch (error) { + throw new SocketDisconnectedError(); + } + const { tile: tilePlayed, type, direction } = response; + if (type === 'pass') { + return null; + } + const { player: { hand} } = this; + const index: number = hand.findIndex(t => t.id === tilePlayed.id); + const side: PlayerMoveSideType = type === 'left' ? PlayerMoveSide.LEFT : PlayerMoveSide.RIGHT; + const tile = hand.splice(index, 1)[0]; + tile.revealed = true; + return board.isValidMove(tile, side, this.player, direction); + } + + async chooseTile(board: Board): Promise { + const { player: { hand} } = this; + const response: any = await this.clientNotifier.notifyPlayer(this.player as NetworkPlayer, 'chooseTile'); + const index: number = board.boneyard.findIndex(t => t.id === response.tileId); + const tile = board.boneyard.splice(index, 1)[0]; + tile.revealed = true; + hand.push(tile); + return tile; + } +} \ No newline at end of file diff --git a/src/game/PlayerNotificationManager.ts b/src/game/PlayerNotificationManager.ts new file mode 100644 index 0000000..c4f3ce4 --- /dev/null +++ b/src/game/PlayerNotificationManager.ts @@ -0,0 +1,39 @@ +import { DominoesGame } from "./DominoesGame"; +import { GameSession } from "./GameSession"; +import { GameState } from "./dto/GameState"; + +export class PlayerNotificationManager { + game!: DominoesGame; + session!: GameSession; + + constructor(game: DominoesGame | GameSession) { + if (game instanceof GameSession) { + this.session = game; + } else { + this.game = game; + } + } + + async notifyGameState() { + if(!this.game) throw new Error('Game not initialized'); + const gameState: GameState = this.game.getGameState(); + const { players } = this.game; + let promises: Promise[] = players.map(player => player.notifyGameState(gameState)); + return await Promise.all(promises); + } + + async notifyPlayersState() { + if(!this.game) throw new Error('Game not initialized'); + const { players } = this.game; + let promises: Promise[] = players.map(player => player.notifyPlayerState(player.getState())); + return await Promise.all(promises); + } + + + async notifySessionState() { + if(!this.session) throw new Error('Session not initialized'); + const { players } = this.session; + let promises: Promise[] = players.map(player => player.notifySessionState(this.session.getState())); + return await Promise.all(promises); + } +} \ No newline at end of file diff --git a/src/game/SimulatedBoard.ts b/src/game/SimulatedBoard.ts new file mode 100644 index 0000000..101640d --- /dev/null +++ b/src/game/SimulatedBoard.ts @@ -0,0 +1,15 @@ +import { PRNG } from "seedrandom"; +import { Board } from "./entities/Board"; +import { Tile } from "./entities/Tile"; + +export class SimulatedBoard extends Board { + constructor(tiles: Tile[] = [], rng: PRNG) { + super(rng); + this.tiles = tiles; + } + + evaluate(): number { + return this.tiles.length; + //return this.tiles.reduce((acc, tile) => acc + tile.count, 0); + } +} \ No newline at end of file diff --git a/src/game/constants.ts b/src/game/constants.ts new file mode 100644 index 0000000..38b446f --- /dev/null +++ b/src/game/constants.ts @@ -0,0 +1,15 @@ +export type PlayerType = 'AI' | 'Human'; +export type PlayerMoveSideType = 'left' | 'right' | 'both'; +export type JointValueType = 0 | 1 | 2; + +export const PlayerMoveSide: { [key: string]: PlayerMoveSideType } = { + LEFT: 'left', + RIGHT: 'right', + BOTH: 'both' +}; + +export const JointValue: { [key: string]: JointValueType } = { + LEFT: 0, + RIGHT: 1, + NONE: 2 +}; diff --git a/src/game/dto/Game.ts b/src/game/dto/Game.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/game/dto/GameSessionState.ts b/src/game/dto/GameSessionState.ts new file mode 100644 index 0000000..696d43f --- /dev/null +++ b/src/game/dto/GameSessionState.ts @@ -0,0 +1,17 @@ +import { PlayerDto } from "./PlayerDto"; + +export interface GameSessionState { + id: string; + name: string; + creator: string; + players: PlayerDto[]; + seed: string; + waitingForPlayers: boolean; + mode: string; + pointsToWin: number; + sessionInProgress: boolean; + status: string; + maxPlayers: number; + numPlayers: number; + waitingSeconds: number; +} \ No newline at end of file diff --git a/src/game/dto/GameState.ts b/src/game/dto/GameState.ts new file mode 100644 index 0000000..fda4a26 --- /dev/null +++ b/src/game/dto/GameState.ts @@ -0,0 +1,18 @@ +import { PlayerMove } from "../entities/PlayerMove"; +import { PlayerDto } from "./PlayerDto"; + +export interface GameState { + id: string; + players: PlayerDto[]; + boneyard: any[]; + currentPlayer: PlayerDto | null; + board: any[]; + gameInProgress: boolean; + winner?: any; + gameBlocked: boolean; + gameTied: boolean; + gameId: string; + tileSelectionPhase: boolean; + boardFreeEnds: number[]; + lastMove: PlayerMove | null; +} \ No newline at end of file diff --git a/src/game/dto/GameSummary.ts b/src/game/dto/GameSummary.ts new file mode 100644 index 0000000..347054f --- /dev/null +++ b/src/game/dto/GameSummary.ts @@ -0,0 +1,8 @@ +import { PlayerInterface } from "../entities/player/PlayerInterface"; + +export interface GameSummary { + gameId: string; + isBlocked: boolean; + isTied: boolean; + winner: PlayerInterface | null; +} \ No newline at end of file diff --git a/src/game/dto/PlayerDto.ts b/src/game/dto/PlayerDto.ts new file mode 100644 index 0000000..61dce32 --- /dev/null +++ b/src/game/dto/PlayerDto.ts @@ -0,0 +1,6 @@ +export interface PlayerDto { + id: string; + name: string; + score?: number; + hand?: string[]; +} \ No newline at end of file diff --git a/src/game/dto/PlayerState.ts b/src/game/dto/PlayerState.ts new file mode 100644 index 0000000..bb649bc --- /dev/null +++ b/src/game/dto/PlayerState.ts @@ -0,0 +1,7 @@ +export interface PlayerState { + id: string; + name: string; + score: number; + hand: any[]; + teamedWith: string | undefined; +} \ No newline at end of file diff --git a/src/game/entities/Board.ts b/src/game/entities/Board.ts new file mode 100644 index 0000000..584e341 --- /dev/null +++ b/src/game/entities/Board.ts @@ -0,0 +1,125 @@ +import { PRNG } from "seedrandom"; +import { PlayerMoveSideType, PlayerMoveSide, JointValue } from "../constants"; +import { PlayerInterface } from "./player/PlayerInterface"; +import { PlayerMove } from "./PlayerMove"; +import { Tile } from "./Tile"; + +export class Board { + tiles: Tile[] = []; + boneyard: Tile[] = []; + + constructor(private rng: PRNG) {} + + get isGameOver(): boolean { + return this.tiles.length === 0; + } + + get playedPipsCount() { + return this.tiles.reduce((acc, tile) => acc + tile.count, 0); + } + + get count () { + return this.tiles.length; + } + + get leftEnd() { + return this.tiles[0]; + } + + get rightEnd() { + return this.tiles[this.tiles.length - 1]; + } + + get leftFreeEnd() { + return this.leftEnd?.flippedPips[0]; + } + + get rightFreeEnd() { + return this.rightEnd?.flippedPips[1]; + } + + getFreeEnds() { + if(this.count === 0) { + return []; + } + return [this.leftEnd.flippedPips[0], this.rightEnd.flippedPips[1]]; + } + + play(playerMove: PlayerMove): void { + const { type, tile } = playerMove; + tile.revealed = true; + if (type === PlayerMoveSide.LEFT) { + this.playTileLeft(tile); + // printLine(`${tile} -- left`); + } else { + this.playTileRight(tile); + // printLine(`${tile} -- right`); + } + } + + playTileLeft(tile: Tile) { + if (tile.flippedPips[1] !== this.leftFreeEnd) { + tile.flip(); + } + this.tiles.unshift(tile); + } + + playTileRight(tile: Tile) { + if (tile.flippedPips[0] !== this.rightFreeEnd) { + tile.flip(); + } + this.tiles.push(tile); + } + + matchesFreeEnd(tile: Tile, freeEnd: number): boolean { + return tile.pips[0] === freeEnd || tile.pips[1] === freeEnd; + } + + isValidMove(tile: Tile, side: PlayerMoveSideType | null, player: PlayerInterface, direction?: string): PlayerMove | null { + if (this.count === 0) { + return new PlayerMove(tile, PlayerMoveSide.BOTH, player.id, direction); + } + + const freeEnds = this.getFreeEnds(); + const leftEnd = freeEnds[0]; + const rightEnd = freeEnds[1]; + + if (side !== null) { + if (side === PlayerMoveSide.LEFT) { + if (this.matchesFreeEnd(tile, leftEnd)) { + return new PlayerMove(tile, side, player.id, direction); + } + } else { + if (this.matchesFreeEnd(tile, rightEnd)) { + return new PlayerMove(tile, side, player.id, direction); + } + } + return null; + } + + if ((this.matchesFreeEnd(tile, leftEnd) && this.matchesFreeEnd(tile, rightEnd))) { + const side = this.rng() < 0.5 ? PlayerMoveSide.LEFT : PlayerMoveSide.RIGHT; + return new PlayerMove(tile, side, player.id, direction); + } else if (this.matchesFreeEnd(tile, leftEnd)) { + return new PlayerMove(tile, PlayerMoveSide.LEFT, player.id, direction); + } else if (this.matchesFreeEnd(tile, rightEnd)) { + return new PlayerMove(tile, PlayerMoveSide.RIGHT, player.id, direction); + } + return null; + } + + getValidMoves(player: PlayerInterface): PlayerMove[] { + + return player.hand.reduce((acc, tile) => { + const validMove = this.isValidMove(tile, null, player); + if (validMove !== null) { + acc.push(validMove); + } + return acc;4 + }, [] as PlayerMove[]); + } + + toString(): string { + return this.tiles.map(tile => tile.toString()).join(' '); + } +} \ No newline at end of file diff --git a/src/game/entities/PlayerMove.ts b/src/game/entities/PlayerMove.ts new file mode 100644 index 0000000..6604350 --- /dev/null +++ b/src/game/entities/PlayerMove.ts @@ -0,0 +1,12 @@ +import { uuid } from "../../common/utilities"; +import { PlayerMoveSideType } from "../constants"; +import { Tile } from "./Tile"; + +export class PlayerMove { + id: string = uuid(); + constructor(public tile: Tile, public type: PlayerMoveSideType | null, public playerId: string, direction?: string) {} + + toString() { + return `PlayerMove:([${this.tile.pips[0]}|${this.tile.pips[1]}] ${this.type})`; + } +} \ No newline at end of file diff --git a/src/game/entities/Tile.ts b/src/game/entities/Tile.ts new file mode 100644 index 0000000..b6dcf50 --- /dev/null +++ b/src/game/entities/Tile.ts @@ -0,0 +1,37 @@ +import { uuid } from "../../common/utilities"; + +export class Tile { + id: string; + pips: [number, number]; + revealed: boolean = true; + flipped: boolean = false; + + constructor(pips: [number, number]) { + this.id = uuid(); + this.pips = pips; + } + + get count() { + return this.pips[0] + this.pips[1]; + } + + get isPair(): boolean { + return this.pips[0] === this.pips[1]; + } + + get flippedPips(): [number, number] { + return this.flipped ? [this.pips[1], this.pips[0]] : this.pips; + } + + flip() { + this.flipped = !this.flipped; + } + + toString(): string { + if (!this.revealed) { + return '[ | ]'; + } else { + return `[${this.flippedPips[0]}|${this.flippedPips[1]}]`; + } + } +} diff --git a/src/game/entities/player/AbstractPlayer.ts b/src/game/entities/player/AbstractPlayer.ts new file mode 100644 index 0000000..1801beb --- /dev/null +++ b/src/game/entities/player/AbstractPlayer.ts @@ -0,0 +1,70 @@ +import { Board } from "../Board"; +import { PlayerInterface } from "./PlayerInterface"; +import { PlayerMove } from "../PlayerMove"; +import { Tile } from "../Tile"; +import { LoggingService } from "../../../common/LoggingService"; +import { EventEmitter } from "stream"; +import { PlayerInteractionInterface } from "../../PlayerInteractionInterface"; +import { uuid } from "../../../common/utilities"; +import { GameState } from "../../dto/GameState"; +import { PlayerState } from "../../dto/PlayerState"; +import { GameSessionState } from "../../dto/GameSessionState"; + +export abstract class AbstractPlayer extends EventEmitter implements PlayerInterface { + hand: Tile[] = []; + score: number = 0; + logger: LoggingService = new LoggingService(); + teamedWith: PlayerInterface | null = null; + playerInteraction: PlayerInteractionInterface = undefined as any; + id: string = uuid(); + + constructor(public name: string) { + super(); + } + + abstract makeMove(board: Board): Promise; + abstract chooseTile(board: Board): Promise; + + + async notifyGameState(state: GameState): Promise { + } + + async notifyPlayerState(state: PlayerState): Promise { + } + + async notifySessionState(state: GameSessionState): Promise { + } + + pipsCount(): number { + return this.hand.reduce((acc, tile) => acc + tile.pips[0] + tile.pips[1], 0); + } + + getHighestPair(): Tile | null { + if (this.hand.length === 0) { + return null; + } + + let highestPair: Tile | null = null; + const pairs = this.hand.filter(tile => tile.pips[0] === tile.pips[1]); + pairs.forEach(tile => { + if (tile.count > (highestPair?.count ?? 0)) { + highestPair = tile; + } + }); + return highestPair; + } + + getState(): PlayerState { + return { + id: this.id, + name: this.name, + score: this.score, + hand: this.hand.map(tile => ({ + id: tile.id, + pips: tile.pips, + flipped: tile.revealed, + })), + teamedWith: this.teamedWith?.id, + }; + } +} diff --git a/src/game/entities/player/NetworkPlayer.ts b/src/game/entities/player/NetworkPlayer.ts new file mode 100644 index 0000000..a98e966 --- /dev/null +++ b/src/game/entities/player/NetworkPlayer.ts @@ -0,0 +1,50 @@ +import { PlayerInteractionInterface } from "../../PlayerInteractionInterface"; +import { PlayerInteractionNetwork } from "../../PlayerInteractionNetwork"; +import { PlayerHuman } from "./PlayerHuman"; +import { NetworkClientNotifier } from "../../NetworkClientNotifier"; +import { Tile } from "../Tile"; +import { Board } from "../Board"; +import { GameState } from "../../dto/GameState"; +import { PlayerState } from "../../dto/PlayerState"; +import { GameSessionState } from "../../dto/GameSessionState"; +import { SocketDisconnectedError } from "../../../common/exceptions/SocketDisconnectedError"; + +export class NetworkPlayer extends PlayerHuman { + socketId: string; + playerInteraction: PlayerInteractionInterface = new PlayerInteractionNetwork(this); + clientNotifier: NetworkClientNotifier = new NetworkClientNotifier(); + + constructor(name: string, socketId: string) { + super(name); + this.socketId = socketId; + } + + async notifyGameState(state: GameState): Promise { + const response = await this.clientNotifier.notifyPlayer(this, 'gameState', state); + console.log('game state notified :>> ', response); + if (response === undefined || response.status !== 'ok' ) { + throw new SocketDisconnectedError(); + } + } + + async notifyPlayerState(state: PlayerState): Promise { + const response = await this.clientNotifier.notifyPlayer(this, 'playerState', state); + console.log('player state notified :>> ', response); + if (response === undefined || response.status !== 'ok' ) { + throw new SocketDisconnectedError(); + } + } + + async notifySessionState(state: GameSessionState): Promise { + const response = await this.clientNotifier.notifyPlayer(this, 'sessionState', state); + console.log('session state notified :>> ', response); + if (response === undefined || response.status !== 'ok' ) { + throw new SocketDisconnectedError(); + } + } + + + async chooseTile(board: Board): Promise { + return await this.playerInteraction.chooseTile(board); + } +} \ No newline at end of file diff --git a/src/game/entities/player/PlayerAI.ts b/src/game/entities/player/PlayerAI.ts new file mode 100644 index 0000000..32ccd82 --- /dev/null +++ b/src/game/entities/player/PlayerAI.ts @@ -0,0 +1,145 @@ +import { PlayerMoveSide } from "../../constants"; +import { printLine, wait } from "../../../common/utilities"; +import { AbstractPlayer } from "../../entities/player/AbstractPlayer"; +import { Board } from "../Board"; +import { PlayerMove } from "../PlayerMove"; +import { SimulatedBoard } from "../../SimulatedBoard"; +import { Tile } from "../Tile"; +import { PRNG } from "seedrandom"; + +export class PlayerAI extends AbstractPlayer { + constructor(name: string, private rng: PRNG) { + super(name); + } + + async makeMove(board: Board): Promise { + await wait(500); // Simulate thinking time + if (board.tiles.length === 0) { + printLine('playing the first tile'); + const highestPair = this.getHighestPair(); + if (highestPair !== null) { + return new PlayerMove(highestPair, PlayerMoveSide.BOTH, this.id); + } + const maxTile = this.getMaxTile(); + if (maxTile !== null) { + return new PlayerMove(maxTile, PlayerMoveSide.BOTH, this.id); + } + } + // Analyze the game state + // Return the best move based on strategy + return this.chooseTileGreed(board); + } + + async chooseTile(board: Board): Promise { + const randomWait = Math.floor((Math.random() * 1000) + 500); + await wait(randomWait); // Simulate thinking time + const randomIndex = Math.floor(this.rng() * board.boneyard.length); + const tile = board.boneyard.splice(randomIndex, 1)[0]; + this.hand.push(tile); + printLine(`${this.name} has chosen a tile`); + return tile; + } + + getMaxTile(): Tile | null { + if (this.hand.length === 0) { + return null; + } + + let maxTile: Tile | null = null; + this.hand.forEach(tile => { + if (tile.count > (maxTile?.count ?? 0)) { + maxTile = tile; + } + }); + return maxTile; + } + + chooseTileGreed(board: Board): PlayerMove | null { // greed algorithm + let bestMove: PlayerMove |null = null; + let bestTileScore: number = -1; + const validMoves: PlayerMove[] = board.getValidMoves(this); + + validMoves.forEach(move => { + const { tile } = move; + const tileScore = tile.pips[0] + tile.pips[1]; + if (tileScore > bestTileScore) { + bestMove = move; + bestTileScore = tileScore; + } + }); + return bestMove; + } + + chooseTileRandom(board: Board): Tile | null { // random algorithm + const validTiles: Tile[] = this.hand.filter(tile => board.isValidMove(tile, null, this)); + return validTiles[Math.floor(this.rng() * validTiles.length)]; + } + + chooseTileMinMax(board: Board, depth: number = 3): Tile | null { // minmax algorithm + const bestMove: Tile | null = null; + let bestScore: number = -Infinity; + const validMoves: PlayerMove[] = board.getValidMoves(this); + + validMoves.forEach(move => { + const simulatedBoard = new SimulatedBoard([ ...board.tiles ], this.rng); + simulatedBoard.play(move); + const score = this.minmax(simulatedBoard, depth - 1, false); + }); + + return null; + } + + minmax(simulatedBoard: SimulatedBoard, depth: number, isMaximizing: boolean): number { + if (depth === 0 || simulatedBoard.isGameOver) { + return simulatedBoard.evaluate(); + } + + if (isMaximizing) { + let maxEval = -Infinity; + const validMoves = simulatedBoard.getValidMoves(this); + validMoves.forEach((move: PlayerMove) => { + const newSimulatedBoard = new SimulatedBoard([ ...simulatedBoard.tiles ], this.rng); + newSimulatedBoard.play(move); + const evaluation: number = this.minmax(newSimulatedBoard, depth - 1, false); + maxEval = Math.max(maxEval, evaluation); + }); + return maxEval; + } + else { + let minEval = Infinity; + const validMoves = simulatedBoard.getValidMoves(this); + validMoves.forEach((move: PlayerMove) => { + const newSimulatedBoard = new SimulatedBoard([ ...simulatedBoard.tiles ], this.rng); + newSimulatedBoard.play(move); + const evaluation: number = this.minmax(newSimulatedBoard, depth - 1, true); + minEval = Math.min(minEval, evaluation); + }); + return minEval; + } + + // if (isMaximizing) { + // let bestScore = -Infinity; + // boardTiles.forEach(tile => { + // const newBoardTiles = [ ...boardTiles ]; + // newBoardTiles.push(tile); + // const score = this.minmax(newBoardTiles, depth - 1, false); + // bestScore = Math.max(score, bestScore); + // }); + // return bestScore; + // } else { + // let bestScore = Infinity; + // boardTiles.forEach(tile => { + // const newBoardTiles = [ ...boardTiles ]; + // newBoardTiles.push(tile); + // const score = this.minmax(newBoardTiles, depth - 1, true); + // bestScore = Math.min(score, bestScore); + // }); + // return bestScore; + // } + } + + evaluateBoard(board: Board): number { + // Custom heuristic to evaluate the board state + return board.tiles.length; // Simplistic example + } +} \ No newline at end of file diff --git a/src/game/entities/player/PlayerHuman.ts b/src/game/entities/player/PlayerHuman.ts new file mode 100644 index 0000000..14b68b6 --- /dev/null +++ b/src/game/entities/player/PlayerHuman.ts @@ -0,0 +1,22 @@ +import { PlayerMoveSide, PlayerMoveSideType } from '../../constants'; +import { AbstractPlayer } from './AbstractPlayer'; +import { Board } from '../Board'; +import { PlayerMove } from '../PlayerMove'; +import { Tile } from '../Tile'; +import { PlayerInteractionConsole } from '../../PlayerInteractionConsole'; +import { PlayerInteractionInterface } from '../../PlayerInteractionInterface'; + +export class PlayerHuman extends AbstractPlayer { + playerInteraction: PlayerInteractionInterface = new PlayerInteractionConsole(this); + constructor(name: string) { + super(name); + } + + async makeMove(board: Board): Promise { + return await this.playerInteraction.makeMove(board); + } + + async chooseTile(board: Board): Promise { + return this.playerInteraction.chooseTile(board); + } +} \ No newline at end of file diff --git a/src/game/entities/player/PlayerInterface.ts b/src/game/entities/player/PlayerInterface.ts new file mode 100644 index 0000000..fdc0db4 --- /dev/null +++ b/src/game/entities/player/PlayerInterface.ts @@ -0,0 +1,24 @@ +import { PlayerInteractionInterface } from "../../PlayerInteractionInterface"; +import { Board } from "../Board"; +import { GameState } from "../../dto/GameState"; +import { PlayerMove } from "../PlayerMove"; +import { PlayerState } from "../../dto/PlayerState"; +import { Tile } from "../Tile"; +import { GameSessionState } from "../../dto/GameSessionState"; + +export interface PlayerInterface { + id: string; + name: string; + score: number; + hand: Tile[]; + teamedWith: PlayerInterface | null; + playerInteraction: PlayerInteractionInterface; + + makeMove(gameState: Board): Promise; + chooseTile(board: Board): Promise; + pipsCount(): number; + notifyGameState(state: GameState): Promise; + notifyPlayerState(state: PlayerState): Promise; + notifySessionState(state: GameSessionState): Promise; + getState(): PlayerState; +} \ No newline at end of file diff --git a/src/server/controllers/ControllerBase.ts b/src/server/controllers/ControllerBase.ts new file mode 100644 index 0000000..8780817 --- /dev/null +++ b/src/server/controllers/ControllerBase.ts @@ -0,0 +1,5 @@ +import { LoggingService } from "../../common/LoggingService"; + +export class ControllerBase { + protected logger = new LoggingService(); +} \ No newline at end of file diff --git a/src/server/controllers/SessionController.ts b/src/server/controllers/SessionController.ts new file mode 100644 index 0000000..a241d1c --- /dev/null +++ b/src/server/controllers/SessionController.ts @@ -0,0 +1,77 @@ +import { LoggingService } from "../../common/LoggingService"; +import { GameSession } from "../../game/GameSession"; +import { NetworkPlayer } from "../../game/entities/player/NetworkPlayer"; + +import { ControllerBase } from "./ControllerBase"; + +export class SessionController extends ControllerBase{ + private static sessions: any = {}; + + constructor() { + super(); + this.logger.info('SessionController created'); + } + + createSession(data: any, socketId: string): any { + const { user, sessionName } = data; + const player = new NetworkPlayer(user, socketId); + const session = new GameSession(player, sessionName); + SessionController.sessions[session.id] = session; + + return { + status: 'ok', + sessionId: session.id, + playerId: player.id + }; + } + + joinSession(data: any, socketId: string): any { + this.logger.debug('joinSession data :>> ') + this.logger.object(data); + const { user, sessionId } = data; + const session = SessionController.sessions[sessionId]; + const player = new NetworkPlayer(user, socketId); + session.addPlayer(player); + return { + status: 'ok', + sessionId: session.id, + playerId: player.id + }; + } + + startSession(data: any): any { + const sessionId: string = data.sessionId; + const seed: string | undefined = data.seed; + const session = SessionController.sessions[sessionId]; + + if (!session) { + return ({ + status: 'error', + message: 'Session not found' + }); + } else if (session.gameInProgress) { + return { + status: 'error', + message: 'Game already in progress' + }; + } else { + const missingHumans = session.maxPlayers - session.numPlayers; + for (let i = 0; i < missingHumans; i++) { + session.addPlayer(session.createPlayerAI(i)); + } + session.start(seed); + return { + status: 'ok' + }; + } + } + + + getSession(id: string) { + return SessionController.sessions[id]; + } + + deleteSession(id: string) { + delete SessionController.sessions[id]; + } +} \ No newline at end of file diff --git a/src/server/index.html b/src/server/index.html new file mode 100644 index 0000000..bea641b --- /dev/null +++ b/src/server/index.html @@ -0,0 +1,89 @@ + + + + + Socket.IO chat + + + +
    +
    +

    + +

    + +

    +

    +

    +
    + + + + + diff --git a/src/server/index.ts b/src/server/index.ts new file mode 100644 index 0000000..e1360ef --- /dev/null +++ b/src/server/index.ts @@ -0,0 +1,25 @@ +import express from 'express'; +import http from 'http'; +import cors from 'cors'; +import { join } from 'path'; + +import { NetworkClientNotifier } from '../game/NetworkClientNotifier'; +import { SocketIoService } from './services/SocketIoService'; + +const clientNotifier = new NetworkClientNotifier(); +const app = express(); +const httpServer = http.createServer(app); +const socketIoService = new SocketIoService(httpServer); +clientNotifier.setSocket(socketIoService.getServer()); + +const PORT = process.env.PORT || 3000; +console.log('__dirname :>> ', __dirname); + +app.use(cors()); +app.get('/', (req, res) => { + res.sendFile(join(__dirname, 'index.html')); +}); + +httpServer.listen(PORT, () => { + console.log(`listening on *:${PORT}`); +}); diff --git a/src/server/services/ServiceBase.ts b/src/server/services/ServiceBase.ts new file mode 100644 index 0000000..caa45eb --- /dev/null +++ b/src/server/services/ServiceBase.ts @@ -0,0 +1,5 @@ +import { LoggingService } from "../../common/LoggingService"; + +export class ServiceBase { + protected logger = new LoggingService(); +} \ No newline at end of file diff --git a/src/server/services/SocketIoService.ts b/src/server/services/SocketIoService.ts new file mode 100644 index 0000000..bf591cf --- /dev/null +++ b/src/server/services/SocketIoService.ts @@ -0,0 +1,75 @@ +import { Server as HttpServer } from "http"; +import { ServiceBase } from "./ServiceBase"; +import { Server } from "socket.io"; +import { SessionController } from "../controllers/SessionController"; + +export class SocketIoService extends ServiceBase{ + io: Server + constructor(private httpServer: HttpServer) { + super() + this.io = this.socketIo(httpServer); + this.initListeners(); + } + + public getServer(): Server { + return this.io; + } + + private initListeners() { + const sessionController = new SessionController(); + this.io.on('connection', (socket) => { + console.log(`connect ${socket.id}`); + if (socket.recovered) { + // recovery was successful: socket.id, socket.rooms and socket.data were restored + console.log("recovered!"); + console.log("socket.rooms:", socket.rooms); + console.log("socket.data:", socket.data); + } else { + console.log("new connection"); + socket.join('room-general') + socket.data.foo = "bar"; + } + + + socket.on('disconnect', () => { + console.log('user disconnected'); + }); + + socket.on('createSession', (data, callback) => { + const response = sessionController.createSession(data, socket.id); + callback(response); + }); + + socket.on('startSession', (data, callback) => { + const response = sessionController.startSession(data); + callback(response); + }); + + socket.on('joinSession', (data, callback) => { + const response = sessionController.joinSession(data, socket.id); + callback(response); + }); + + // socket.on('chat message', (msg, callback) => { + // io.emit('chat message', msg); + // callback({ + // status: 'ok', + // message: 'Message received', + // }) + // }); + }); + } + + private socketIo(httpServer: HttpServer): Server { + return new Server(httpServer, { + cors: { + origin: '*', + }, + connectionStateRecovery: { + maxDisconnectionDuration: 15 * 60 * 1000, + skipMiddlewares: true, + }, + connectTimeout: 15 * 60 * 1000, + }) + } +} \ No newline at end of file diff --git a/src/test.ts b/src/test.ts new file mode 100644 index 0000000..cd7d73f --- /dev/null +++ b/src/test.ts @@ -0,0 +1,56 @@ +import { PlayerAI } from "./game/entities/player/PlayerAI"; +import { PlayerHuman } from "./game/entities/player/PlayerHuman"; +import {LoggingService} from "./common/LoggingService"; +import { GameSession } from "./game/GameSession"; + +console.log('process.arg :>> ', process.argv); + +// const game = new DominoesGame([ +// new PlayerAI("1", "Player 1"), +// new PlayerAI("2", "Player 2"), +// new PlayerAI("3", "Player 3"), +// new PlayerAI("4", "Player 4"), +// ]); +// const logger = new LoggingService(); + +// async function wait(ms: number) {s +// return new Promise(resolve => setTimeout(resolve, ms)); +// } + +async function playSolo(seed?: string) { + const session = new GameSession(new PlayerHuman( "Jose"), "Test Game"); + console.log(`Session (${session.id}) created by: ${session.creator.name}`); + setTimeout(() => session.addPlayer(new PlayerAI("AI 2")), 1000); + setTimeout(() => session.addPlayer(new PlayerAI("AI 3")), 2000); + setTimeout(() => session.addPlayer(new PlayerAI("AI 4")), 3000); + session.start(seed); +} + +async function playHumans(seed?: string) { + const session = new GameSession(new PlayerHuman("Jose"), "Test Game"); + session.addPlayer(new PlayerHuman("Pepe")); + session.addPlayer(new PlayerHuman("Juan")); + session.addPlayer(new PlayerHuman("Luis")); + session.start(seed); +} + +async function playAIs(seed?: string) { + const session = new GameSession(new PlayerAI("AI 1"), "Test Game"); + session.addPlayer(new PlayerAI("AI 2")); + session.addPlayer(new PlayerAI("AI 3")); + session.addPlayer(new PlayerAI("AI 4")); + session.start(seed); +} + +async function playTeams(seed?: string) { + const session = new GameSession(new PlayerHuman("Jose"), "Test Game"); + session.addPlayer(new PlayerAI("AI 1")); + session.addPlayer(new PlayerHuman("Juan")); + session.addPlayer(new PlayerAI("AI 2")); + session.start(seed); +} + +const blockedSeed = '1719236688462-ytwrwzfzoi-01aad98f'; +const seed2 = '1719237652000-09vddd3hsth7-adbc1842'; + +playSolo('1719248315701-itmcciws3oi-e5dd2024'); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b6fd952 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,110 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "include": ["./src/**/*"], + "exclude": ["node_modules"] +}