{"_id":"@ayshrj/ludo.js","_rev":"10-228cfeeb7f477a7e0e585f8833858774","name":"@ayshrj/ludo.js","dist-tags":{"latest":"1.0.10"},"versions":{"1.0.0":{"name":"@ayshrj/ludo.js","version":"1.0.0","keywords":["ludo","board game","typescript","game","ai"],"author":{"name":"Ayush Raj"},"license":"MIT","_id":"@ayshrj/ludo.js@1.0.0","maintainers":[{"name":"ayshrj","email":"ayshrj10@gmail.com"}],"dist":{"shasum":"0bba638262d46946827251a18ce95f4e6d494e0d","tarball":"https://registry.npmjs.org/@ayshrj/ludo.js/-/ludo.js-1.0.0.tgz","fileCount":12,"integrity":"sha512-XUsOT2UgV2wWt0J2AxZU0UqNe0XikQyjm/6wo+SNvOiysVIoi5IOzNRPyQIuRDcWqXCQ7B7xmTL6xect/hpmcQ==","signatures":[{"sig":"MEQCIBnPs5LvbRLYDJ8KH0sNRK56y8xUu0FM2FzSEIvV7Eo2AiAVNNSMsVF+J7xuousTFdNMIuqhgDyqoqLZ4WjNU7Ke4Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":63224},"main":"dist/index.js","types":"dist/index.d.ts","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build":"tsc","prepare":"npm run build","prepublishOnly":"npm run build"},"_npmUser":{"name":"ayshrj","email":"ayshrj10@gmail.com"},"_npmVersion":"10.5.1","description":"A TypeScript-based Ludo game library","directories":{},"_nodeVersion":"23.6.1","_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.2"},"_npmOperationalInternal":{"tmp":"tmp/ludo.js_1.0.0_1740812617429_0.05482584488339759","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@ayshrj/ludo.js","version":"1.0.1","keywords":["ludo","board game","typescript","game","ai"],"author":{"name":"Ayush Raj"},"license":"MIT","_id":"@ayshrj/ludo.js@1.0.1","maintainers":[{"name":"ayshrj","email":"ayshrj10@gmail.com"}],"dist":{"shasum":"a516c7ceb8eb9972ee2bf88877bad20776e51e84","tarball":"https://registry.npmjs.org/@ayshrj/ludo.js/-/ludo.js-1.0.1.tgz","fileCount":12,"integrity":"sha512-gW7Ph9a0dZk3sxwISKO/X/XEFRlTaVvbLQSF59178SsBSIU9s/A6xQ4RqbM9UvmgYgkjdb2cEiy4LBbyzYZBEA==","signatures":[{"sig":"MEQCIDt+6eFJSIsQwt1AjKDebjI9Qdls7Uz75lfFOmIxWCbtAiAK2qA6WSBLWLiAn0CS15u66sDwFeBF2Tvw+o9Gkylrrw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":63232},"main":"dist/index.js","types":"dist/index.d.ts","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build":"tsc","prepare":"npm run build","prepublishOnly":"npm run build"},"_npmUser":{"name":"ayshrj","email":"ayshrj10@gmail.com"},"_npmVersion":"10.5.1","description":"A TypeScript-based Ludo game library","directories":{},"_nodeVersion":"23.6.1","_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.2"},"_npmOperationalInternal":{"tmp":"tmp/ludo.js_1.0.1_1740812907294_0.8898682427282893","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@ayshrj/ludo.js","version":"1.0.2","keywords":["ludo","board game","typescript","game","ai"],"author":{"name":"Ayush Raj"},"license":"MIT","_id":"@ayshrj/ludo.js@1.0.2","maintainers":[{"name":"ayshrj","email":"ayshrj10@gmail.com"}],"dist":{"shasum":"8c84381f26567edd34d3d6994692c5601c393461","tarball":"https://registry.npmjs.org/@ayshrj/ludo.js/-/ludo.js-1.0.2.tgz","fileCount":12,"integrity":"sha512-c6qQFaXP6nRLLnY7vrmxmi7uChJTsEN11YXSwgGDb4Cpt8ksFQzWTIzuKlsiDYPSXKdZpZ4j23nzCEF3Ce2dng==","signatures":[{"sig":"MEYCIQDCQTpC4bE/DrLSMEAJwb6kilLhztxOmx0SAFqx+yOo1QIhAIdUY5w8VS5JDf2hSIbIx75cTWIG7UN5VexTWY9sXjhj","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":63272},"main":"dist/index.js","types":"dist/index.d.ts","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build":"tsc","prepare":"npm run build","prepublishOnly":"npm run build"},"_npmUser":{"name":"ayshrj","email":"ayshrj10@gmail.com"},"_npmVersion":"10.5.1","description":"A TypeScript-based Ludo game library","directories":{},"_nodeVersion":"23.6.1","_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.2"},"_npmOperationalInternal":{"tmp":"tmp/ludo.js_1.0.2_1740813177940_0.16668479289391192","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@ayshrj/ludo.js","version":"1.0.3","keywords":["ludo","board game","typescript","game","ai"],"author":{"name":"Ayush Raj"},"license":"MIT","_id":"@ayshrj/ludo.js@1.0.3","maintainers":[{"name":"ayshrj","email":"ayshrj10@gmail.com"}],"homepage":"https://github.com/ayshrj/ludo.js#readme","bugs":{"url":"https://github.com/ayshrj/ludo.js/issues"},"dist":{"shasum":"f38419080ae4e063f70686d07b372bab2e421538","tarball":"https://registry.npmjs.org/@ayshrj/ludo.js/-/ludo.js-1.0.3.tgz","fileCount":12,"integrity":"sha512-G5IDwih4DJMgrk6EftBzr/so7fdf1r/CK9f1vidtIChEoARpQynY8p25c3r8olHRfUQuXKL5Gt4HWt92/ueS6w==","signatures":[{"sig":"MEUCIQCQPf8WxfP3P/vK0+tPBndgVC2g2a2jTliVmkBL8eBU2wIgTfLAb20ctIlvK0rHlA7R7e0bKsYA3BeymK7jNYj3/8s=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":63423},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"0d51d751aad9a6d8c55d379c34a4f851b65f9faf","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build":"tsc","prepare":"npm run build","prepublishOnly":"npm run build"},"_npmUser":{"name":"ayshrj","email":"ayshrj10@gmail.com"},"repository":{"url":"git+https://github.com/ayshrj/ludo.js.git","type":"git"},"_npmVersion":"10.5.1","description":"A TypeScript-based Ludo game library","directories":{},"_nodeVersion":"23.6.1","_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.2"},"_npmOperationalInternal":{"tmp":"tmp/ludo.js_1.0.3_1740813647988_0.06564382924784273","host":"s3://npm-registry-packages-npm-production"}},"1.0.4":{"name":"@ayshrj/ludo.js","version":"1.0.4","keywords":["ludo","board game","typescript","game","ai"],"author":{"name":"Ayush Raj"},"license":"MIT","_id":"@ayshrj/ludo.js@1.0.4","maintainers":[{"name":"ayshrj","email":"ayshrj10@gmail.com"}],"homepage":"https://github.com/ayshrj/ludo.js#readme","bugs":{"url":"https://github.com/ayshrj/ludo.js/issues"},"dist":{"shasum":"31d581683443961272814b9aa93ba411e7fedfb7","tarball":"https://registry.npmjs.org/@ayshrj/ludo.js/-/ludo.js-1.0.4.tgz","fileCount":12,"integrity":"sha512-CHEhwU+f+XcLf8haBspWEuksyQsnDDZCbFoZNRtn7gkmysrIaF9lKN0KQw0tf+u9NVb9UZltqUuc9cPcY5WjUw==","signatures":[{"sig":"MEUCIQDtBNWtuIQcZc4PhYI45ps8npBDBTghuZfxPjvAJBgKGAIgcfk+eT3DnZmDjk5Y4h1+gVPQneEwfyyQVuujdu8EFKo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":73378},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"a5b042e9585e5a925418eb8d273d54a013e6c56b","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build":"tsc","prepare":"npm run build","prepublishOnly":"npm run build"},"_npmUser":{"name":"ayshrj","email":"ayshrj10@gmail.com"},"repository":{"url":"git+https://github.com/ayshrj/ludo.js.git","type":"git"},"_npmVersion":"10.5.1","description":"A TypeScript-based headless Ludo game engine for simulating game logic, AI moves, and game state management.","directories":{},"_nodeVersion":"23.6.1","_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.2"},"_npmOperationalInternal":{"tmp":"tmp/ludo.js_1.0.4_1740814644884_0.7480129115739809","host":"s3://npm-registry-packages-npm-production"}},"1.0.5":{"name":"@ayshrj/ludo.js","version":"1.0.5","keywords":["ludo","board game","typescript","game","ai"],"author":{"name":"Ayush Raj"},"license":"MIT","_id":"@ayshrj/ludo.js@1.0.5","maintainers":[{"name":"ayshrj","email":"ayshrj10@gmail.com"}],"homepage":"https://github.com/ayshrj/ludo.js#readme","bugs":{"url":"https://github.com/ayshrj/ludo.js/issues"},"dist":{"shasum":"47a3884d7325e51ff9a28c73e35dffe8d5a03326","tarball":"https://registry.npmjs.org/@ayshrj/ludo.js/-/ludo.js-1.0.5.tgz","fileCount":12,"integrity":"sha512-nakL+LzX39D9LN5OeYfB2DxzkJIlJ/gmk+Q8sRn/D2+j0SXY4OPUxbuBuNkkBLPjkWwKa1LEWlaqoYQA/PeQHQ==","signatures":[{"sig":"MEUCIQD+N9xf1/MntnoXvWUsUhZYgsgXXcw2t6zgIMnsGdreWwIgdTmGqFNA9lebu4iP9dUfedFCbpkm1tMxwZsri5WHBj4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":64065},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"46b07d9b8cb3c75bbded98fea508d538a5e2c4e7","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build":"tsc","prepare":"npm run build","prepublishOnly":"npm run build"},"_npmUser":{"name":"ayshrj","email":"ayshrj10@gmail.com"},"repository":{"url":"git+https://github.com/ayshrj/ludo.js.git","type":"git"},"_npmVersion":"10.5.1","description":"A TypeScript-based headless Ludo game engine for simulating game logic, AI moves, and game state management.","directories":{},"_nodeVersion":"23.6.1","dependencies":{"events":"^3.3.0"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.2","@types/events":"^3.0.3"},"_npmOperationalInternal":{"tmp":"tmp/ludo.js_1.0.5_1740820300449_0.44281108110034273","host":"s3://npm-registry-packages-npm-production"}},"1.0.6":{"name":"@ayshrj/ludo.js","version":"1.0.6","keywords":["ludo","board game","typescript","game","ai"],"author":{"name":"Ayush Raj"},"license":"MIT","_id":"@ayshrj/ludo.js@1.0.6","maintainers":[{"name":"ayshrj","email":"ayshrj10@gmail.com"}],"homepage":"https://github.com/ayshrj/ludo.js#readme","bugs":{"url":"https://github.com/ayshrj/ludo.js/issues"},"dist":{"shasum":"5b57a89ad8c7c63f5e11626ec1cd7227adc30e3b","tarball":"https://registry.npmjs.org/@ayshrj/ludo.js/-/ludo.js-1.0.6.tgz","fileCount":12,"integrity":"sha512-vnSwGTSfC8h+YRrlZi2ZB2HPYhjA8erDGTnu4ePyhzjQ4VmUtzk3KkzBwjKrbjuIU25Vlp/wZcgkWuzkrqeKzA==","signatures":[{"sig":"MEUCIQDwTuWTJJ+4EnXcXsZwX8+HicnOQw+5EpvCip8bFp4+YQIgVPKVloIaysPJYcmH1Q/v3XW0oqZwuiGZm8TzEMdDug0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":65149},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"578269159e6287a7677795abf2911601a6fe3a53","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build":"tsc","prepare":"npm run build","prepublishOnly":"npm run build"},"_npmUser":{"name":"ayshrj","email":"ayshrj10@gmail.com"},"repository":{"url":"git+https://github.com/ayshrj/ludo.js.git","type":"git"},"_npmVersion":"10.5.1","description":"A TypeScript-based headless Ludo game engine for simulating game logic, AI moves, and game state management.","directories":{},"_nodeVersion":"23.6.1","dependencies":{"events":"^3.3.0"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.2","@types/events":"^3.0.3"},"_npmOperationalInternal":{"tmp":"tmp/ludo.js_1.0.6_1740821306295_0.7266614770760305","host":"s3://npm-registry-packages-npm-production"}},"1.0.7":{"name":"@ayshrj/ludo.js","version":"1.0.7","keywords":["ludo","board game","typescript","game","ai"],"author":{"name":"Ayush Raj"},"license":"MIT","_id":"@ayshrj/ludo.js@1.0.7","maintainers":[{"name":"ayshrj","email":"ayshrj10@gmail.com"}],"homepage":"https://github.com/ayshrj/ludo.js#readme","bugs":{"url":"https://github.com/ayshrj/ludo.js/issues"},"dist":{"shasum":"ff711bdc407176ad35aed502740cba4b7017e3c7","tarball":"https://registry.npmjs.org/@ayshrj/ludo.js/-/ludo.js-1.0.7.tgz","fileCount":12,"integrity":"sha512-vV87igUIBRuWH28XZ1O5JaTyFfZwC5iat2FJPn0rbY+RxWbl/wrbP4WdhNPRYmbT0V1b5kEO796rMNtq5SA2QQ==","signatures":[{"sig":"MEYCIQCUoiJU0VarE09RcNlMrek7GyJrTQMDdxRmOo6/o3xACAIhAMJWXW+CIlztDfJrtUVGfYoRKhZ/AosyycmkX9zG0p7u","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":65375},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"b2c907125c2563f0b7ee9394a515da1d46a244a0","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build":"tsc","prepare":"npm run build","prepublishOnly":"npm run build"},"_npmUser":{"name":"ayshrj","email":"ayshrj10@gmail.com"},"repository":{"url":"git+https://github.com/ayshrj/ludo.js.git","type":"git"},"_npmVersion":"10.5.1","description":"A TypeScript-based headless Ludo game engine for simulating game logic, AI moves, and game state management.","directories":{},"_nodeVersion":"23.6.1","dependencies":{"events":"^3.3.0"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.2","@types/events":"^3.0.3"},"_npmOperationalInternal":{"tmp":"tmp/ludo.js_1.0.7_1740821477496_0.7808651132727007","host":"s3://npm-registry-packages-npm-production"}},"1.0.8":{"name":"@ayshrj/ludo.js","version":"1.0.8","keywords":["ludo","board game","typescript","game","ai"],"author":{"name":"Ayush Raj"},"license":"MIT","_id":"@ayshrj/ludo.js@1.0.8","maintainers":[{"name":"ayshrj","email":"ayshrj10@gmail.com"}],"homepage":"https://github.com/ayshrj/ludo.js#readme","bugs":{"url":"https://github.com/ayshrj/ludo.js/issues"},"dist":{"shasum":"8284d13f2b61fcea70e3f6e9646afb38acd0631f","tarball":"https://registry.npmjs.org/@ayshrj/ludo.js/-/ludo.js-1.0.8.tgz","fileCount":12,"integrity":"sha512-EQxbkp/DsQdiFqOYgxqE2FL9heSD5JyrCVc2pyXC1urOPB6r1GHr8N5yLWdmemEzXWIvsXk4cimSa8f5U3prfw==","signatures":[{"sig":"MEUCIQCKXCrTEX9ro4MeLgdNMgdNRsMjtbsk+/RAmPBGvwZaUgIgEOj0tlOQTQ+c1GMMtmb8G8wGpBtuQyaeXeumAA4hNbo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":65470},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"787a5f101915914004c7119bdba6372a87b64484","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build":"tsc","prepare":"npm run build","prepublishOnly":"npm run build"},"_npmUser":{"name":"ayshrj","email":"ayshrj10@gmail.com"},"repository":{"url":"git+https://github.com/ayshrj/ludo.js.git","type":"git"},"_npmVersion":"10.5.1","description":"A TypeScript-based headless Ludo game engine for simulating game logic, AI moves, and game state management.","directories":{},"_nodeVersion":"23.6.1","dependencies":{"events":"^3.3.0"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.2","@types/events":"^3.0.3"},"_npmOperationalInternal":{"tmp":"tmp/ludo.js_1.0.8_1740821630857_0.8938084870048497","host":"s3://npm-registry-packages-npm-production"}},"1.0.9":{"name":"@ayshrj/ludo.js","version":"1.0.9","keywords":["ludo","board game","typescript","game","ai"],"author":{"name":"Ayush Raj"},"license":"MIT","_id":"@ayshrj/ludo.js@1.0.9","maintainers":[{"name":"ayshrj","email":"ayshrj10@gmail.com"}],"homepage":"https://github.com/ayshrj/ludo.js#readme","bugs":{"url":"https://github.com/ayshrj/ludo.js/issues"},"dist":{"shasum":"30c7afc1fba3457f0e46198821ba2c4dbf35e74c","tarball":"https://registry.npmjs.org/@ayshrj/ludo.js/-/ludo.js-1.0.9.tgz","fileCount":12,"integrity":"sha512-5GGeEI7bczxITgNbghCNEeCLEE8Kd+h+ws0wISW6hA9g/8Ut96fy9cNQ3wL6xSol0yKkPj/oc8GLKw7onywcJw==","signatures":[{"sig":"MEQCIACsNmTAYgyRhMsSXY3YxLiNu1l4nI+UXF+C1oHEpDb1AiBWMuqDcTXL5FVTE6h/fImA4cpEVBLERBhZkfyzHA2rZA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":83012},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"9d984a749c01ce354c4346103bb7471fd80a53c1","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build":"tsc","prepare":"npm run build","prepublishOnly":"npm run build"},"_npmUser":{"name":"ayshrj","email":"ayshrj10@gmail.com"},"repository":{"url":"git+https://github.com/ayshrj/ludo.js.git","type":"git"},"_npmVersion":"10.5.1","description":"A TypeScript-based headless Ludo game engine for simulating game logic, AI moves, and game state management.","directories":{},"_nodeVersion":"23.6.1","dependencies":{"events":"^3.3.0"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.2","@types/events":"^3.0.3"},"_npmOperationalInternal":{"tmp":"tmp/ludo.js_1.0.9_1740823000628_0.6278606744824342","host":"s3://npm-registry-packages-npm-production"}},"1.0.10":{"name":"@ayshrj/ludo.js","version":"1.0.10","description":"A TypeScript-based headless Ludo game engine for simulating game logic, AI moves, and game state management.","main":"dist/index.js","types":"dist/index.d.ts","repository":{"type":"git","url":"git+https://github.com/ayshrj/ludo.js.git"},"homepage":"https://github.com/ayshrj/ludo.js#readme","scripts":{"build":"tsup","prepare":"npm run build","test":"echo \"Error: no test specified\" && exit 1","prepublishOnly":"npm run build"},"keywords":["ludo","board game","typescript","game","ai"],"author":{"name":"Ayush Raj"},"license":"MIT","devDependencies":{"@types/events":"^3.0.3","tsup":"^8.4.0","typescript":"^5.8.2"},"dependencies":{"events":"^3.3.0"},"_id":"@ayshrj/ludo.js@1.0.10","gitHead":"bc4cecfec8f0d0dc8f0f4dfddab4edb0c2ddcb5a","bugs":{"url":"https://github.com/ayshrj/ludo.js/issues"},"_nodeVersion":"23.6.1","_npmVersion":"10.5.1","dist":{"integrity":"sha512-hehigQmL6TwOcgep2IKTPeOQ8cbGqj6is1mqohIJ9HD5Mw33mGL5imbb8UqM4GTgCnx200w0CoHkptZnMfZpig==","shasum":"ba9725a1d09206b8fa33ae09f11f55ea06506997","tarball":"https://registry.npmjs.org/@ayshrj/ludo.js/-/ludo.js-1.0.10.tgz","fileCount":11,"unpackedSize":83152,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCgwUhZR9XaDigGyaY4IXoA17AOUtnfjxPY2gpBjH+BHQIhANU2mXFlOEhIj1zWKulPjoeYAVz2Z+kQXSSZudJPaUi1"}]},"_npmUser":{"name":"ayshrj","email":"ayshrj10@gmail.com"},"directories":{},"maintainers":[{"name":"ayshrj","email":"ayshrj10@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ludo.js_1.0.10_1740898439560_0.18651644477438278"},"_hasShrinkwrap":false}},"time":{"created":"2025-03-01T07:03:37.343Z","modified":"2025-03-02T06:53:59.902Z","1.0.0":"2025-03-01T07:03:37.620Z","1.0.1":"2025-03-01T07:08:27.554Z","1.0.2":"2025-03-01T07:12:58.112Z","1.0.3":"2025-03-01T07:20:48.218Z","1.0.4":"2025-03-01T07:37:25.068Z","1.0.5":"2025-03-01T09:11:40.666Z","1.0.6":"2025-03-01T09:28:26.484Z","1.0.7":"2025-03-01T09:31:17.719Z","1.0.8":"2025-03-01T09:33:51.081Z","1.0.9":"2025-03-01T09:56:40.877Z","1.0.10":"2025-03-02T06:53:59.743Z"},"bugs":{"url":"https://github.com/ayshrj/ludo.js/issues"},"author":{"name":"Ayush Raj"},"license":"MIT","homepage":"https://github.com/ayshrj/ludo.js#readme","keywords":["ludo","board game","typescript","game","ai"],"repository":{"type":"git","url":"git+https://github.com/ayshrj/ludo.js.git"},"description":"A TypeScript-based headless Ludo game engine for simulating game logic, AI moves, and game state management.","maintainers":[{"name":"ayshrj","email":"ayshrj10@gmail.com"}],"readme":"# @ayshrj/ludo.js\r\n\r\n[![npm](https://img.shields.io/npm/v/@ayshrj/ludo.js?color=blue)](https://www.npmjs.com/package/@ayshrj/ludo.js)\r\n[![npm](https://img.shields.io/npm/dm/@ayshrj/ludo.js)](https://www.npmjs.com/package/@ayshrj/ludo.js)\r\n\r\n**@ayshrj/ludo.js** is a TypeScript library for simulating the classic Ludo board\r\ngame. It handles:\r\n\r\n- **Board Setup** (2, 3, or 4 players)\r\n- **Dice Rolling** (automatic skip after three consecutive sixes)\r\n- **Token Movement** (including leaving home on a 6)\r\n- **Capturing Opponents** on non-safe squares\r\n- **Safe Zones & Final Squares**\r\n- **Ranking** (tracks order in which players finish)\r\n- **Lightweight AI** with `bestMove()`\r\n- **EventEmitter-based Updates** (automatically emit `\"stateChange\"` with the latest state)\r\n- **Comprehensive State Tracking** for easy integration into any UI\r\n\r\n---\r\n\r\n## Table of Contents\r\n\r\n- [@ayshrj/ludo.js](#ayshrjludojs)\r\n  - [Table of Contents](#table-of-contents)\r\n  - [Installation](#installation)\r\n  - [Importing](#importing)\r\n    - [Import (as ESM)](#import-as-esm)\r\n    - [Import (as CommonJS)](#import-as-commonjs)\r\n  - [Quick Start Example](#quick-start-example)\r\n  - [Features](#features)\r\n  - [API](#api)\r\n    - [Constructor: `new Ludo(numberOfPlayers)`](#constructor-new-ludonumberofplayers)\r\n    - [`.rollDiceForCurrentPiece()`](#rolldiceforcurrentpiece)\r\n    - [`.selectToken(tokenIndex)`](#selecttokentokenindex)\r\n    - [`.bestMove()`](#bestmove)\r\n    - [`.reset()`](#reset)\r\n    - [`.getCurrentState()`](#getcurrentstate)\r\n    - [`.tokenPositions`](#tokenpositions)\r\n    - [`.validTokenIndices`](#validtokenindices)\r\n    - [`.ranking`](#ranking)\r\n    - [`.gameState`](#gamestate)\r\n    - [`.currentPiece`](#currentpiece)\r\n    - [Event Emitter: `\"stateChange\"`](#event-emitter-statechange)\r\n    - [`.players`](#players)\r\n    - [`initializeTokenPosition()`](#initializetokenposition)\r\n  - [Example: React Integration](#example-react-integration)\r\n  - [Advanced Usage Notes](#advanced-usage-notes)\r\n  - [License](#license)\r\n\r\n---\r\n\r\n## Installation\r\n\r\n```bash\r\nnpm install @ayshrj/ludo.js\r\n```\r\nor\r\n```bash\r\nyarn add @ayshrj/ludo.js\r\n```\r\n\r\n---\r\n\r\n## Importing\r\n\r\n### Import (as ESM)\r\n\r\n```js\r\nimport { Ludo } from '@ayshrj/ludo.js'\r\n```\r\n\r\n### Import (as CommonJS)\r\n\r\n```js\r\nconst { Ludo } = require('@ayshrj/ludo.js')\r\n```\r\n\r\n---\r\n\r\n## Quick Start Example\r\n\r\n```ts\r\nimport { Ludo } from '@ayshrj/ludo.js'\r\n\r\n// Create a Ludo game with 4 players\r\nconst game = new Ludo(4)\r\n\r\n// Subscribe to state changes (optional)\r\ngame.on(\"stateChange\", (state) => {\r\n  console.log(\"STATE CHANGE:\", state)\r\n})\r\n\r\n// Roll the dice\r\nconst diceValue = game.rollDiceForCurrentPiece()\r\nconsole.log(`Dice: ${diceValue}`)\r\n\r\n// If there's a valid token, select it\r\nif (game.validTokenIndices.length > 0) {\r\n  game.selectToken(game.validTokenIndices[0])\r\n}\r\n\r\n// Or use the basic AI\r\nconst best = game.bestMove()\r\nif (best >= 0) game.selectToken(best)\r\n```\r\n\r\n---\r\n\r\n## Features\r\n\r\n1. **2, 3, or 4 Players**  \r\n   Colors are chosen from `[\"blue\",\"red\",\"green\",\"yellow\"]` based on the\r\n   requested number of players.\r\n\r\n2. **Dice Rolling**  \r\n   Rolls `1..6`. Three consecutive sixes skip your turn.\r\n\r\n3. **Movement & Safe Zones**  \r\n   Leave home only on a 6. Safe squares cannot be captured.\r\n\r\n4. **Capturing**  \r\n   If you land on an opponent in a non-safe zone, that opponent’s token goes\r\n   home (`-1`).\r\n\r\n5. **Final & Ranking**  \r\n   Index `56` is the final square; finishing all 4 tokens places you in `ranking`.\r\n\r\n6. **Simple AI**  \r\n   `bestMove()` returns a recommended token index.\r\n\r\n7. **Event Emitter**  \r\n   The `Ludo` class extends Node.js’s `EventEmitter`. Each time the internal\r\n   state changes (after rolls, moves, etc.), it emits `\"stateChange\"` with the\r\n   updated `LudoGameState`.\r\n\r\n---\r\n\r\n## API\r\n\r\n### Constructor: `new Ludo(numberOfPlayers)`\r\n\r\n```ts\r\n// 2 players => uses [\"blue\",\"green\"]\r\n// 3 players => uses [\"blue\",\"red\",\"green\"]\r\n// 4 players => uses [\"blue\",\"red\",\"green\",\"yellow\"]\r\nconst game = new Ludo(4)\r\n```\r\n\r\n---\r\n\r\n### `.rollDiceForCurrentPiece()`\r\nRolls a 6-sided die and updates `currentDiceRoll`, `lastDiceRoll`, and\r\n`validTokenIndices`. Automatically skips turn on three consecutive sixes or if\r\nthere are no valid moves.\r\n\r\n---\r\n\r\n### `.selectToken(tokenIndex)`\r\nMoves the chosen token for the current color using the last dice roll. Handles\r\ncaptures, final squares, turn passing, etc.\r\n\r\n---\r\n\r\n### `.bestMove()`\r\nA basic heuristic that returns the “best” token index or -1 if none.\r\n\r\n---\r\n\r\n### `.reset()`\r\nCompletely reinitializes the board and picks a new starting player randomly.\r\n\r\n---\r\n\r\n### `.getCurrentState()`\r\nReturns a snapshot of the entire state:\r\n\r\n```ts\r\ninterface LudoGameState {\r\n  turn: Color;\r\n  tokenPositions: TokenPositions;\r\n  ranking: Color[];\r\n  boardStatus: string;\r\n  diceRoll: number | null;\r\n  lastDiceRoll: number | null;\r\n  gameState: GameState;\r\n  players: Color[];\r\n}\r\n```\r\n\r\n---\r\n\r\n### `.tokenPositions`\r\nA record of each color’s four token positions (`-1` for home, `0..56` on track).\r\n\r\n---\r\n\r\n### `.validTokenIndices`\r\nWhich tokens (0..3) the current color can move on their turn.  \r\nUpdated when `.rollDiceForCurrentPiece()` is called.\r\n\r\n---\r\n\r\n### `.ranking`\r\nColors finish in order. If `ranking.length` equals number of players, the game is\r\ncomplete.\r\n\r\n---\r\n\r\n### `.gameState`\r\nOne of:\r\n- `\"playerHasToRollADice\"`\r\n- `\"playerHasToSelectAPosition\"`\r\n- `\"gameFinished\"`\r\n\r\n---\r\n\r\n### `.currentPiece`\r\nWhich color’s turn it is right now.\r\n\r\n---\r\n\r\n### Event Emitter: `\"stateChange\"`\r\nEvery time the game state changes (e.g., after a roll or move), the library\r\ncalls: \r\n```ts\r\nthis.emit(\"stateChange\", this.getCurrentState())\r\n```\r\nSo you can do:\r\n```ts\r\ngame.on(\"stateChange\", (state) => {\r\n  // React or update UI automatically with the new state\r\n})\r\n```\r\n\r\n---\r\n\r\n### `.players`\r\nThe array of active colors, in turn order. For example, with 3 players it might\r\nbe `[\"blue\",\"red\",\"green\"]`.\r\n\r\n---\r\n\r\n### `initializeTokenPosition()`\r\nA utility function that initializes the token positions for all colors. Each token starts at `-1` (home).\r\n\r\n```ts\r\n/**\r\n * Initializes the token positions for all colors.\r\n * @returns {TokenPositions} An object with token positions for each color.\r\n * @example\r\n * const tokenPositions = initializeTokenPosition();\r\n * // Returns:\r\n * // {\r\n * //   red: [-1, -1, -1, -1],\r\n * //   green: [-1, -1, -1, -1],\r\n * //   yellow: [-1, -1, -1, -1],\r\n * //   blue: [-1, -1, -1, -1]\r\n * // }\r\n */\r\nfunction initializeTokenPosition(): TokenPositions;\r\n```\r\n\r\n---\r\n\r\n## Example: React Integration\r\n\r\n```tsx\r\nimport React, { useState, useEffect, useCallback, useRef } from \"react\";\r\nimport {\r\n  Ludo,\r\n  Color,\r\n  LudoGameState,\r\n  initializeTokenPosition,\r\n} from \"@ayshrj/ludo.js\";\r\n\r\n// Available color sets for 2/3/4 players:\r\nconst COLOR_SETS: Record<2 | 3 | 4, Color[]> = {\r\n  2: [\"blue\", \"green\"],\r\n  3: [\"blue\", \"red\", \"green\"],\r\n  4: [\"blue\", \"red\", \"green\", \"yellow\"],\r\n};\r\n\r\nconst LudoPage: React.FC = () => {\r\n  const [playerCount, setPlayerCount] = useState<2 | 3 | 4>(4);\r\n  const [playerTypes, setPlayerTypes] = useState<\r\n    Record<Color, \"human\" | \"bot\">\r\n  >({\r\n    blue: \"human\",\r\n    red: \"human\",\r\n    green: \"human\",\r\n    yellow: \"human\",\r\n  });\r\n  const [ludo, setLudo] = useState<Ludo | null>(null);\r\n  const [gameState, setGameState] = useState<LudoGameState>({\r\n    turn: \"blue\",\r\n    ranking: [],\r\n    tokenPositions: initializeTokenPosition(),\r\n    boardStatus: \"\",\r\n    diceRoll: null,\r\n    lastDiceRoll: null,\r\n    gameState: \"playerHasToRollADice\",\r\n    players: [],\r\n  });\r\n  const [showSetupModal, setShowSetupModal] = useState<boolean>(true);\r\n  const [showResetModal, setShowResetModal] = useState<boolean>(false);\r\n  const [botThinking, setBotThinking] = useState<boolean>(false);\r\n  const botTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n\r\n  useEffect(() => {\r\n    if (!ludo) return;\r\n    const handleStateChange = (newState: LudoGameState) => {\r\n      setGameState(newState);\r\n    };\r\n    ludo.on(\"stateChange\", handleStateChange);\r\n    return () => {\r\n      ludo.off(\"stateChange\", handleStateChange);\r\n    };\r\n  }, [ludo]);\r\n\r\n  const s = gameState;\r\n  const isGameFinished =\r\n    s.ranking.length >= s.players.length && s.players.length > 0;\r\n\r\n  const startNewGame = useCallback(() => {\r\n    const chosenColors = COLOR_SETS[playerCount];\r\n    const newLudo = new Ludo(playerCount);\r\n    newLudo.players = chosenColors;\r\n    newLudo.reset();\r\n    setGameState(newLudo.getCurrentState());\r\n    setLudo(newLudo);\r\n    setShowSetupModal(false);\r\n    setShowResetModal(false);\r\n  }, [playerCount]);\r\n\r\n  useEffect(() => {\r\n    if (!ludo || isGameFinished) return;\r\n    if (s.gameState !== \"playerHasToRollADice\") return;\r\n\r\n    const botTurn = playerTypes[s.turn] === \"bot\";\r\n    if (!botTurn || botThinking) return;\r\n\r\n    setBotThinking(true);\r\n    botTimeoutRef.current = setTimeout(() => {\r\n      ludo.rollDiceForCurrentPiece();\r\n      setBotThinking(false);\r\n    }, 1200);\r\n  }, [\r\n    ludo,\r\n    s.gameState,\r\n    s.turn,\r\n    s.players,\r\n    botThinking,\r\n    isGameFinished,\r\n    playerTypes,\r\n  ]);\r\n\r\n  useEffect(() => {\r\n    if (!ludo || isGameFinished) return;\r\n    if (s.gameState !== \"playerHasToSelectAPosition\" || s.diceRoll === null)\r\n      return;\r\n\r\n    const botTurn = playerTypes[s.turn] === \"bot\";\r\n    if (!botTurn || botThinking) return;\r\n\r\n    setBotThinking(true);\r\n    botTimeoutRef.current = setTimeout(() => {\r\n      const best = ludo.bestMove();\r\n      if (best >= 0) {\r\n        ludo.selectToken(best);\r\n      }\r\n      setBotThinking(false);\r\n    }, 800);\r\n  }, [\r\n    ludo,\r\n    s.gameState,\r\n    s.turn,\r\n    s.players,\r\n    s.diceRoll,\r\n    botThinking,\r\n    isGameFinished,\r\n    playerTypes,\r\n  ]);\r\n\r\n  const handleRollDice = (): void => {\r\n    if (!ludo || isGameFinished) return;\r\n    const { turn } = ludo.getCurrentState();\r\n    if (playerTypes[turn] === \"bot\") return;\r\n    setTimeout(() => {\r\n      ludo.rollDiceForCurrentPiece();\r\n    }, 1000);\r\n  };\r\n\r\n  const handleTokenClick = (color: Color, tokenIndex: number): void => {\r\n    if (!ludo || isGameFinished) return;\r\n    const st = ludo.getCurrentState();\r\n    if (color !== st.turn) return;\r\n    if (playerTypes[color] === \"bot\") return;\r\n    if (!ludo.validTokenIndices.includes(tokenIndex)) return;\r\n    ludo.selectToken(tokenIndex);\r\n  };\r\n\r\n  const colorMap: Record<Color, string> = {\r\n    red: \"#FF0000\",\r\n    green: \"#00FF00\",\r\n    yellow: \"#FFFF00\",\r\n    blue: \"#0000FF\",\r\n  };\r\n\r\n  function getColorOffset(index: number, totalColors: number) {\r\n    const offset = 10;\r\n    if (totalColors === 1) return { x: 0, y: 0 };\r\n    if (totalColors === 2)\r\n      return index === 0\r\n        ? { x: -offset, y: -offset }\r\n        : { x: offset, y: offset };\r\n    if (totalColors === 3) {\r\n      if (index === 0) return { x: -offset, y: -offset };\r\n      if (index === 1) return { x: offset, y: -offset };\r\n      if (index === 2) return { x: offset, y: offset };\r\n    }\r\n    if (totalColors === 4) {\r\n      if (index === 0) return { x: -offset, y: -offset };\r\n      if (index === 1) return { x: offset, y: -offset };\r\n      if (index === 2) return { x: -offset, y: offset };\r\n      if (index === 3) return { x: offset, y: offset };\r\n    }\r\n    return { x: 0, y: 0 };\r\n  }\r\n\r\n  const startingPositions: Record<Color, [number, number][]> = {\r\n    red: [\r\n      [1, 1],\r\n      [1, 4],\r\n      [4, 1],\r\n      [4, 4],\r\n    ],\r\n    green: [\r\n      [1, 10],\r\n      [1, 13],\r\n      [4, 10],\r\n      [4, 13],\r\n    ],\r\n    blue: [\r\n      [10, 1],\r\n      [10, 4],\r\n      [13, 1],\r\n      [13, 4],\r\n    ],\r\n    yellow: [\r\n      [10, 10],\r\n      [10, 13],\r\n      [13, 10],\r\n      [13, 13],\r\n    ],\r\n  };\r\n\r\n  const getTokensAtSquare = (\r\n    rowIndex: number,\r\n    colIndex: number\r\n  ): { token: Color; index: number }[] => {\r\n    if (!ludo) return [];\r\n    const tokens: { token: Color; index: number }[] = [];\r\n    s.players.forEach((color) => {\r\n      s.tokenPositions[color].forEach((pos, i) => {\r\n        if (pos !== -1) {\r\n          const [r2, c2] = ludo.colorPaths[color][pos];\r\n          if (r2 === rowIndex && c2 === colIndex) {\r\n            tokens.push({ token: color, index: i });\r\n          }\r\n        } else {\r\n          const [hr, hc] = startingPositions[color][i];\r\n          if (hr === rowIndex && hc === colIndex) {\r\n            tokens.push({ token: color, index: i });\r\n          }\r\n        }\r\n      });\r\n    });\r\n    return tokens;\r\n  };\r\n\r\n  const groupTokensByColor = (\r\n    tokens: { token: Color; index: number }[]\r\n  ): { token: Color; total: number }[] => {\r\n    const counts: Record<Color, number> = {\r\n      red: 0,\r\n      green: 0,\r\n      blue: 0,\r\n      yellow: 0,\r\n    };\r\n    tokens.forEach(({ token }) => {\r\n      counts[token]++;\r\n    });\r\n    return Object.entries(counts)\r\n      .filter(([, cnt]) => cnt > 0)\r\n      .map(([color, total]) => ({ token: color as Color, total }));\r\n  };\r\n\r\n  let statusMessage = \"No game in progress.\";\r\n  if (ludo) {\r\n    if (isGameFinished) {\r\n      statusMessage = `Game finished! Final ranking: ${s.ranking.join(\" -> \")}`;\r\n    } else {\r\n      statusMessage = `${s.turn.toUpperCase()}'s turn.`;\r\n      if (s.lastDiceRoll !== null) {\r\n        statusMessage += ` (Last roll: ${s.lastDiceRoll})`;\r\n      }\r\n      if (s.boardStatus) {\r\n        statusMessage += ` — ${s.boardStatus}`;\r\n      }\r\n    }\r\n  }\r\n\r\n  const isDiceDisabled =\r\n    !ludo ||\r\n    isGameFinished ||\r\n    s.gameState !== \"playerHasToRollADice\" ||\r\n    playerTypes[s.turn] === \"bot\" ||\r\n    botThinking;\r\n\r\n  const modalStyle: React.CSSProperties = {\r\n    position: \"fixed\",\r\n    top: \"50%\",\r\n    left: \"50%\",\r\n    transform: \"translate(-50%, -50%)\",\r\n    backgroundColor: \"white\",\r\n    padding: \"20px\",\r\n    borderRadius: \"8px\",\r\n    boxShadow: \"0 4px 6px rgba(0, 0, 0, 0.1)\",\r\n    zIndex: 1000,\r\n  };\r\n\r\n  const buttonStyle: React.CSSProperties = {\r\n    padding: \"10px 20px\",\r\n    borderRadius: \"4px\",\r\n    border: \"none\",\r\n    cursor: \"pointer\",\r\n    fontSize: \"16px\",\r\n  };\r\n\r\n  const selectStyle: React.CSSProperties = {\r\n    padding: \"5px\",\r\n    borderRadius: \"4px\",\r\n    border: \"1px solid #ccc\",\r\n    fontSize: \"16px\",\r\n  };\r\n\r\n  return (\r\n    <div\r\n      style={{\r\n        display: \"flex\",\r\n        flexDirection: \"column\",\r\n        alignItems: \"center\",\r\n        padding: \"16px\",\r\n        height: \"100vh\",\r\n      }}\r\n    >\r\n      {/* Game Status */}\r\n      <div\r\n        style={{\r\n          width: \"100%\",\r\n          textAlign: \"center\",\r\n          fontSize: \"18px\",\r\n          fontWeight: \"500\",\r\n        }}\r\n      >\r\n        {statusMessage}\r\n      </div>\r\n\r\n      {/* Board */}\r\n      {ludo ? (\r\n        <div\r\n          style={{\r\n            width: \"100%\",\r\n            overflow: \"hidden\",\r\n            aspectRatio: \"1/1\",\r\n            backgroundColor: \"#f0f0f0\",\r\n            borderRadius: \"8px\",\r\n          }}\r\n        >\r\n          <div\r\n            style={{\r\n              display: \"grid\",\r\n              width: \"100%\",\r\n              height: \"100%\",\r\n              gridTemplateColumns: \"repeat(15, 1fr)\",\r\n              gridTemplateRows: \"repeat(15, 1fr)\",\r\n            }}\r\n          >\r\n            {ludo.board.map((row, rowIndex) =>\r\n              row.map((col, colIndex) => {\r\n                const rawTokens = getTokensAtSquare(rowIndex, colIndex);\r\n                const groupedTokens = groupTokensByColor(rawTokens);\r\n                return (\r\n                  <div\r\n                    key={`${rowIndex}-${colIndex}`}\r\n                    style={{\r\n                      position: \"relative\",\r\n                      display: \"flex\",\r\n                      alignItems: \"center\",\r\n                      justifyContent: \"center\",\r\n                      borderBottom: \"1px solid #ccc\",\r\n                      borderRight: \"1px solid #ccc\",\r\n                      backgroundColor: col === null ? \"#f8f8f8\" : \"\",\r\n                      ...(col?.isOnPathToFinalPosition\r\n                        ? {\r\n                            backgroundColor:\r\n                              colorMap[col.isOnPathToFinalPosition],\r\n                          }\r\n                        : {}),\r\n                      ...(col?.isStartingPosition\r\n                        ? { backgroundColor: colorMap[col.isStartingPosition] }\r\n                        : {}),\r\n                      ...(col?.isFinalPosition\r\n                        ? { backgroundColor: colorMap[col.isFinalPosition] }\r\n                        : {}),\r\n                    }}\r\n                  >\r\n                    {groupedTokens.map(({ token, total }, i) => {\r\n                      const isCurrentPlayerStack =\r\n                        token === s.turn &&\r\n                        rawTokens.some(\r\n                          (t) =>\r\n                            t.token === token &&\r\n                            ludo.validTokenIndices.includes(t.index)\r\n                        );\r\n                      const onCircleClick = () => {\r\n                        const validToken = rawTokens.find(\r\n                          (t) =>\r\n                            t.token === token &&\r\n                            ludo.validTokenIndices.includes(t.index)\r\n                        );\r\n                        if (validToken) {\r\n                          handleTokenClick(validToken.token, validToken.index);\r\n                        }\r\n                      };\r\n                      const offset = getColorOffset(i, groupedTokens.length);\r\n                      return (\r\n                        <div\r\n                          key={`${token}-${i}`}\r\n                          onClick={onCircleClick}\r\n                          style={{\r\n                            position: \"absolute\",\r\n                            transform: `translate(${offset.x}px, ${offset.y}px)`,\r\n                            width: `${40 + total * 10}%`,\r\n                            height: `${40 + total * 10}%`,\r\n                            backgroundColor: colorMap[token],\r\n                            borderRadius: \"50%\",\r\n                            display: \"flex\",\r\n                            alignItems: \"center\",\r\n                            justifyContent: \"center\",\r\n                            color: \"white\",\r\n                            fontSize: \"12px\",\r\n                            fontWeight: \"bold\",\r\n                            cursor: \"pointer\",\r\n                            border:\r\n                              isCurrentPlayerStack && !isGameFinished\r\n                                ? \"2px solid white\"\r\n                                : \"none\",\r\n                          }}\r\n                        >\r\n                          {total > 1 ? total : \"\"}\r\n                        </div>\r\n                      );\r\n                    })}\r\n                    {col?.isSafeZone &&\r\n                      !col?.isStartingPosition &&\r\n                      !col?.isFinalPosition && (\r\n                        <div\r\n                          style={{\r\n                            position: \"absolute\",\r\n                            left: \"50%\",\r\n                            top: \"50%\",\r\n                            transform: \"translate(-50%, -50%)\",\r\n                            zIndex: 1,\r\n                            display: \"flex\",\r\n                            alignItems: \"center\",\r\n                            justifyContent: \"center\",\r\n                            height: \"100%\",\r\n                            width: \"100%\",\r\n                          }}\r\n                        >\r\n                          <div\r\n                            style={{\r\n                              width: \"50%\",\r\n                              height: \"50%\",\r\n                              border: \"1px dashed #800080\",\r\n                              borderRadius: \"50%\",\r\n                              backgroundColor: \"#e0e0ff\",\r\n                            }}\r\n                          />\r\n                        </div>\r\n                      )}\r\n                  </div>\r\n                );\r\n              })\r\n            )}\r\n          </div>\r\n        </div>\r\n      ) : (\r\n        <div\r\n          style={{\r\n            width: \"100%\",\r\n            aspectRatio: \"1/1\",\r\n            display: \"flex\",\r\n            alignItems: \"center\",\r\n            justifyContent: \"center\",\r\n            backgroundColor: \"#f0f0f0\",\r\n            borderRadius: \"8px\",\r\n          }}\r\n        >\r\n          No Game Yet\r\n        </div>\r\n      )}\r\n\r\n      {/* Ranking/Turn Info & Controls */}\r\n      <div\r\n        style={{\r\n          width: \"100%\",\r\n          display: \"flex\",\r\n          flexDirection: \"column\",\r\n          gap: \"8px\",\r\n        }}\r\n      >\r\n        {s.ranking.length > 0 && !isGameFinished && (\r\n          <div style={{ textAlign: \"center\" }}>\r\n            Current Ranking: {s.ranking.join(\" -> \")}\r\n          </div>\r\n        )}\r\n        {isGameFinished && (\r\n          <div style={{ textAlign: \"center\" }}>\r\n            Final Ranking: {s.ranking.join(\" -> \")}\r\n          </div>\r\n        )}\r\n        {!isGameFinished && ludo && (\r\n          <button\r\n            style={{\r\n              ...buttonStyle,\r\n              backgroundColor: \"#0000FF\",\r\n              color: \"white\",\r\n              width: \"100%\",\r\n            }}\r\n            onClick={handleRollDice}\r\n            disabled={isDiceDisabled}\r\n          >\r\n            Roll Dice\r\n          </button>\r\n        )}\r\n        <button\r\n          style={{\r\n            ...buttonStyle,\r\n            backgroundColor: \"#00FF00\",\r\n            color: \"white\",\r\n            width: \"100%\",\r\n          }}\r\n          onClick={() => setShowResetModal(true)}\r\n        >\r\n          Reset\r\n        </button>\r\n      </div>\r\n\r\n      {/* Setup Modal */}\r\n      {showSetupModal && !ludo && (\r\n        <div style={modalStyle}>\r\n          <h2 style={{ marginBottom: \"16px\" }}>Ludo Setup</h2>\r\n          <div\r\n            style={{ display: \"flex\", flexDirection: \"column\", gap: \"16px\" }}\r\n          >\r\n            <div\r\n              style={{\r\n                display: \"grid\",\r\n                gridTemplateColumns: \"repeat(5, 1fr)\",\r\n                alignItems: \"center\",\r\n                gap: \"8px\",\r\n              }}\r\n            >\r\n              <label style={{ gridColumn: \"span 3\" }} htmlFor=\"playerCount\">\r\n                Number of Players:\r\n              </label>\r\n              <select\r\n                id=\"playerCount\"\r\n                value={playerCount}\r\n                onChange={(e) => setPlayerCount(+e.target.value as 2 | 3 | 4)}\r\n                style={selectStyle}\r\n              >\r\n                <option value={2}>2</option>\r\n                <option value={3}>3</option>\r\n                <option value={4}>4</option>\r\n              </select>\r\n            </div>\r\n            {COLOR_SETS[playerCount].map((color) => (\r\n              <div\r\n                style={{\r\n                  display: \"grid\",\r\n                  gridTemplateColumns: \"repeat(5, 1fr)\",\r\n                  alignItems: \"center\",\r\n                  gap: \"8px\",\r\n                }}\r\n                key={color}\r\n              >\r\n                <label\r\n                  style={{ gridColumn: \"span 3\" }}\r\n                  htmlFor={`sel-${color}`}\r\n                >\r\n                  {color}:\r\n                </label>\r\n                <select\r\n                  id={`sel-${color}`}\r\n                  value={playerTypes[color]}\r\n                  onChange={(e) =>\r\n                    setPlayerTypes((prev) => ({\r\n                      ...prev,\r\n                      [color]: e.target.value as \"human\" | \"bot\",\r\n                    }))\r\n                  }\r\n                  style={selectStyle}\r\n                >\r\n                  <option value=\"human\">Human</option>\r\n                  <option value=\"bot\">Bot</option>\r\n                </select>\r\n              </div>\r\n            ))}\r\n            <button\r\n              style={{\r\n                ...buttonStyle,\r\n                backgroundColor: \"#00FF00\",\r\n                color: \"white\",\r\n                width: \"100%\",\r\n              }}\r\n              onClick={startNewGame}\r\n            >\r\n              Start\r\n            </button>\r\n          </div>\r\n        </div>\r\n      )}\r\n\r\n      {/* Reset Confirmation Modal */}\r\n      {showResetModal && (\r\n        <div style={modalStyle}>\r\n          <h2 style={{ marginBottom: \"16px\" }}>Reset Game</h2>\r\n          <div\r\n            style={{ display: \"flex\", flexDirection: \"column\", gap: \"16px\" }}\r\n          >\r\n            <p>Are you sure you want to start a new Ludo game?</p>\r\n            <div style={{ display: \"flex\", gap: \"8px\" }}>\r\n              <button\r\n                style={{\r\n                  ...buttonStyle,\r\n                  backgroundColor: \"#ccc\",\r\n                  color: \"black\",\r\n                  width: \"50%\",\r\n                }}\r\n                onClick={() => setShowResetModal(false)}\r\n              >\r\n                Cancel\r\n              </button>\r\n              <button\r\n                style={{\r\n                  ...buttonStyle,\r\n                  backgroundColor: \"#00FF00\",\r\n                  color: \"white\",\r\n                  width: \"50%\",\r\n                }}\r\n                onClick={() => {\r\n                  setShowResetModal(false);\r\n                  setLudo(null);\r\n                  setShowSetupModal(true);\r\n                }}\r\n              >\r\n                Start\r\n              </button>\r\n            </div>\r\n          </div>\r\n        </div>\r\n      )}\r\n    </div>\r\n  );\r\n};\r\n\r\nexport default LudoPage;\r\n```\r\n\r\n---\r\n\r\n## Advanced Usage Notes\r\n\r\n1. **Skipping / Passing**  \r\n   If `.rollDiceForCurrentPiece()` yields no valid moves, it auto-passes to the\r\n   next player.\r\n2. **Consecutive Sixes**  \r\n   Rolling three in a row ends your turn immediately.\r\n3. **Event-Driven Updates**  \r\n   Subscribing to `\"stateChange\"` means you can react to changes\r\n   without manually querying the state after every call.\r\n\r\n---\r\n\r\n## License\r\n\r\nMIT License. Feel free to modify or contribute!\r\n\r\n---","readmeFilename":"README.md"}