hbengine 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/engine-cli/connect.mjs +73 -0
- package/engine-cli/hbengine.mjs +565 -0
- package/engine-cli/package.json +1 -0
- package/engine-cli/templates.mjs +14 -0
- package/engine-conformance/analyse-worker.mjs +12 -0
- package/engine-conformance/analyse.mjs +4458 -0
- package/engine-conformance/bundle.mjs +208 -0
- package/engine-conformance/clock.mjs +294 -0
- package/engine-conformance/csp.go +56 -0
- package/engine-conformance/csp.json +6 -0
- package/engine-conformance/csp.mjs +20 -0
- package/engine-conformance/gzip-sizes.mjs +9 -0
- package/engine-conformance/package.json +1 -0
- package/engine-conformance/picture.mjs +55 -0
- package/engine-conformance/report.go +349 -0
- package/engine-conformance/run.mjs +1217 -0
- package/engine-conformance/scan.mjs +476 -0
- package/engine-conformance/testdata/clock-overlay/bundle/assets.json +1 -0
- package/engine-conformance/testdata/clock-overlay/bundle/game.css +17 -0
- package/engine-conformance/testdata/clock-overlay/bundle/game.js +83 -0
- package/engine-conformance/testdata/clock-overlay/bundle/hbtest.json +4 -0
- package/engine-conformance/testdata/clock-overlay/bundle/index.html +34 -0
- package/engine-conformance/testdata/clock-probe/bundle/assets.json +1 -0
- package/engine-conformance/testdata/clock-probe/bundle/game.css +17 -0
- package/engine-conformance/testdata/clock-probe/bundle/game.js +65 -0
- package/engine-conformance/testdata/clock-probe/bundle/hbtest.json +4 -0
- package/engine-conformance/testdata/clock-probe/bundle/index.html +35 -0
- package/engine-conformance/testdata/clock-probe/bundle/probe-worker.js +46 -0
- package/engine-conformance/testdata/clock-probe/bundle/probe.js +210 -0
- package/engine-conformance/testdata/clock-probe/math/dice.tsv +100 -0
- package/engine-conformance/testdata/clock-probe/math/manifest.json +2925 -0
- package/engine-conformance/testdata/clock-worker/bundle/assets.json +1 -0
- package/engine-conformance/testdata/clock-worker/bundle/clock-worker.js +14 -0
- package/engine-conformance/testdata/clock-worker/bundle/game.css +17 -0
- package/engine-conformance/testdata/clock-worker/bundle/game.js +61 -0
- package/engine-conformance/testdata/clock-worker/bundle/hbtest.json +4 -0
- package/engine-conformance/testdata/clock-worker/bundle/index.html +35 -0
- package/engine-conformance/testdata/clock-worker/math/dice.tsv +100 -0
- package/engine-conformance/testdata/clock-worker/math/manifest.json +2925 -0
- package/engine-conformance/travel.mjs +1209 -0
- package/engine-conformance/vendor/acorn/LICENSE +21 -0
- package/engine-conformance/vendor/acorn/acorn.mjs +6266 -0
- package/engine-conformance/wasm-releases.json +168 -0
- package/engine-examples/README.md +23 -0
- package/engine-examples/coin/AGENTS.md +3 -0
- package/engine-examples/coin/README.md +7 -0
- package/engine-examples/coin/bundle/assets.json +1 -0
- package/engine-examples/coin/bundle/game.css +5 -0
- package/engine-examples/coin/bundle/game.js +19 -0
- package/engine-examples/coin/bundle/hbtest.json +5 -0
- package/engine-examples/coin/bundle/index.html +16 -0
- package/engine-examples/coin/math/coin.tsv +2 -0
- package/engine-examples/coin/math/manifest.json +17 -0
- package/engine-examples/crash/AGENTS.md +3 -0
- package/engine-examples/crash/README.md +14 -0
- package/engine-examples/crash/bundle/assets.json +1 -0
- package/engine-examples/crash/bundle/game.css +8 -0
- package/engine-examples/crash/bundle/game.js +90 -0
- package/engine-examples/crash/bundle/hbtest.json +5 -0
- package/engine-examples/crash/bundle/index.html +20 -0
- package/engine-examples/crash/math/crash-01.tsv +2 -0
- package/engine-examples/crash/math/crash-02.tsv +2 -0
- package/engine-examples/crash/math/crash-03.tsv +2 -0
- package/engine-examples/crash/math/crash-04.tsv +2 -0
- package/engine-examples/crash/math/crash-05.tsv +2 -0
- package/engine-examples/crash/math/crash-06.tsv +2 -0
- package/engine-examples/crash/math/crash-07.tsv +2 -0
- package/engine-examples/crash/math/crash-08.tsv +2 -0
- package/engine-examples/crash/math/crash-09.tsv +2 -0
- package/engine-examples/crash/math/crash-10.tsv +2 -0
- package/engine-examples/crash/math/crash-11.tsv +2 -0
- package/engine-examples/crash/math/crash-12.tsv +2 -0
- package/engine-examples/crash/math/crash-13.tsv +2 -0
- package/engine-examples/crash/math/crash-14.tsv +2 -0
- package/engine-examples/crash/math/crash-15.tsv +2 -0
- package/engine-examples/crash/math/crash-16.tsv +2 -0
- package/engine-examples/crash/math/crash-17.tsv +2 -0
- package/engine-examples/crash/math/crash-18.tsv +2 -0
- package/engine-examples/crash/math/crash-19.tsv +2 -0
- package/engine-examples/crash/math/crash-20.tsv +2 -0
- package/engine-examples/crash/math/crash-21.tsv +2 -0
- package/engine-examples/crash/math/crash-22.tsv +2 -0
- package/engine-examples/crash/math/crash-23.tsv +2 -0
- package/engine-examples/crash/math/crash-24.tsv +2 -0
- package/engine-examples/crash/math/crash-25.tsv +2 -0
- package/engine-examples/crash/math/crash-26.tsv +2 -0
- package/engine-examples/crash/math/crash-27.tsv +2 -0
- package/engine-examples/crash/math/crash-28.tsv +2 -0
- package/engine-examples/crash/math/crash-29.tsv +2 -0
- package/engine-examples/crash/math/crash-30.tsv +2 -0
- package/engine-examples/crash/math/crash-31.tsv +2 -0
- package/engine-examples/crash/math/crash-32.tsv +2 -0
- package/engine-examples/crash/math/crash-33.tsv +2 -0
- package/engine-examples/crash/math/crash-34.tsv +2 -0
- package/engine-examples/crash/math/crash-35.tsv +2 -0
- package/engine-examples/crash/math/crash-36.tsv +2 -0
- package/engine-examples/crash/math/crash-37.tsv +2 -0
- package/engine-examples/crash/math/crash-38.tsv +2 -0
- package/engine-examples/crash/math/crash-39.tsv +2 -0
- package/engine-examples/crash/math/crash-40.tsv +2 -0
- package/engine-examples/crash/math/crash-41.tsv +2 -0
- package/engine-examples/crash/math/crash-42.tsv +2 -0
- package/engine-examples/crash/math/crash-43.tsv +2 -0
- package/engine-examples/crash/math/crash-44.tsv +2 -0
- package/engine-examples/crash/math/crash-45.tsv +2 -0
- package/engine-examples/crash/math/crash-46.tsv +2 -0
- package/engine-examples/crash/math/crash-47.tsv +2 -0
- package/engine-examples/crash/math/crash-48.tsv +2 -0
- package/engine-examples/crash/math/crash-49.tsv +2 -0
- package/engine-examples/crash/math/crash-50.tsv +2 -0
- package/engine-examples/crash/math/crash-51.tsv +2 -0
- package/engine-examples/crash/math/crash-52.tsv +2 -0
- package/engine-examples/crash/math/crash-53.tsv +2 -0
- package/engine-examples/crash/math/crash-54.tsv +2 -0
- package/engine-examples/crash/math/crash-55.tsv +2 -0
- package/engine-examples/crash/math/crash-56.tsv +2 -0
- package/engine-examples/crash/math/crash-57.tsv +2 -0
- package/engine-examples/crash/math/crash-58.tsv +2 -0
- package/engine-examples/crash/math/crash-59.tsv +2 -0
- package/engine-examples/crash/math/crash-60.tsv +2 -0
- package/engine-examples/crash/math/crash-61.tsv +2 -0
- package/engine-examples/crash/math/crash-62.tsv +2 -0
- package/engine-examples/crash/math/crash-63.tsv +2 -0
- package/engine-examples/crash/math/crash-64.tsv +2 -0
- package/engine-examples/crash/math/gen.mjs +31 -0
- package/engine-examples/crash/math/manifest.json +1 -0
- package/engine-examples/dice/AGENTS.md +3 -0
- package/engine-examples/dice/README.md +25 -0
- package/engine-examples/dice/bundle/assets.json +1 -0
- package/engine-examples/dice/bundle/game.css +17 -0
- package/engine-examples/dice/bundle/game.js +61 -0
- package/engine-examples/dice/bundle/hbtest.json +4 -0
- package/engine-examples/dice/bundle/index.html +34 -0
- package/engine-examples/dice/math/dice.tsv +100 -0
- package/engine-examples/dice/math/manifest.json +2925 -0
- package/engine-examples/keno/AGENTS.md +3 -0
- package/engine-examples/keno/README.md +11 -0
- package/engine-examples/keno/bundle/assets.json +1 -0
- package/engine-examples/keno/bundle/game.css +14 -0
- package/engine-examples/keno/bundle/game.js +86 -0
- package/engine-examples/keno/bundle/hbtest.json +4 -0
- package/engine-examples/keno/bundle/i18n.json +342 -0
- package/engine-examples/keno/bundle/index.html +20 -0
- package/engine-examples/keno/bundle/model.json +1 -0
- package/engine-examples/keno/math/gen.mjs +35 -0
- package/engine-examples/keno/math/keno-picks-1.tsv +2 -0
- package/engine-examples/keno/math/keno-picks-10.tsv +11 -0
- package/engine-examples/keno/math/keno-picks-2.tsv +3 -0
- package/engine-examples/keno/math/keno-picks-3.tsv +4 -0
- package/engine-examples/keno/math/keno-picks-4.tsv +5 -0
- package/engine-examples/keno/math/keno-picks-5.tsv +6 -0
- package/engine-examples/keno/math/keno-picks-6.tsv +7 -0
- package/engine-examples/keno/math/keno-picks-7.tsv +8 -0
- package/engine-examples/keno/math/keno-picks-8.tsv +9 -0
- package/engine-examples/keno/math/keno-picks-9.tsv +10 -0
- package/engine-examples/keno/math/manifest.json +1 -0
- package/engine-examples/keno/math-kit/pack.mjs +165 -0
- package/engine-examples/keno/math-kit/package.json +8 -0
- package/engine-examples/keno/math-kit/slot.mjs +94 -0
- package/engine-examples/keno/math-kit/tumble.mjs +94 -0
- package/engine-examples/lines/AGENTS.md +3 -0
- package/engine-examples/lines/README.md +20 -0
- package/engine-examples/lines/bundle/assets.json +1 -0
- package/engine-examples/lines/bundle/game.css +16 -0
- package/engine-examples/lines/bundle/game.js +125 -0
- package/engine-examples/lines/bundle/hbtest.json +4 -0
- package/engine-examples/lines/bundle/i18n.json +242 -0
- package/engine-examples/lines/bundle/index.html +23 -0
- package/engine-examples/lines/bundle/model.json +1 -0
- package/engine-examples/lines/math/gen.mjs +54 -0
- package/engine-examples/lines/math-kit/pack.mjs +165 -0
- package/engine-examples/lines/math-kit/package.json +8 -0
- package/engine-examples/lines/math-kit/slot.mjs +94 -0
- package/engine-examples/lines/math-kit/tumble.mjs +94 -0
- package/engine-examples/mines/AGENTS.md +3 -0
- package/engine-examples/mines/README.md +13 -0
- package/engine-examples/mines/bundle/assets.json +1 -0
- package/engine-examples/mines/bundle/game.css +12 -0
- package/engine-examples/mines/bundle/game.js +58 -0
- package/engine-examples/mines/bundle/hbtest.json +8 -0
- package/engine-examples/mines/bundle/index.html +16 -0
- package/engine-examples/mines/math/gen.mjs +40 -0
- package/engine-examples/mines/math/manifest.json +1 -0
- package/engine-examples/mines/math/mines-1-1.tsv +2 -0
- package/engine-examples/mines/math/mines-1-2.tsv +2 -0
- package/engine-examples/mines/math/mines-1-22.tsv +2 -0
- package/engine-examples/mines/math/mines-1-23.tsv +2 -0
- package/engine-examples/mines/math/mines-1-24.tsv +2 -0
- package/engine-examples/mines/math/mines-1-3.tsv +2 -0
- package/engine-examples/mines/math/mines-10-1.tsv +2 -0
- package/engine-examples/mines/math/mines-10-10.tsv +2 -0
- package/engine-examples/mines/math/mines-10-11.tsv +2 -0
- package/engine-examples/mines/math/mines-10-12.tsv +2 -0
- package/engine-examples/mines/math/mines-10-13.tsv +2 -0
- package/engine-examples/mines/math/mines-10-14.tsv +2 -0
- package/engine-examples/mines/math/mines-10-15.tsv +2 -0
- package/engine-examples/mines/math/mines-10-2.tsv +2 -0
- package/engine-examples/mines/math/mines-10-3.tsv +2 -0
- package/engine-examples/mines/math/mines-10-4.tsv +2 -0
- package/engine-examples/mines/math/mines-10-5.tsv +2 -0
- package/engine-examples/mines/math/mines-10-6.tsv +2 -0
- package/engine-examples/mines/math/mines-10-7.tsv +2 -0
- package/engine-examples/mines/math/mines-10-8.tsv +2 -0
- package/engine-examples/mines/math/mines-10-9.tsv +2 -0
- package/engine-examples/mines/math/mines-11-1.tsv +2 -0
- package/engine-examples/mines/math/mines-11-10.tsv +2 -0
- package/engine-examples/mines/math/mines-11-11.tsv +2 -0
- package/engine-examples/mines/math/mines-11-12.tsv +2 -0
- package/engine-examples/mines/math/mines-11-13.tsv +2 -0
- package/engine-examples/mines/math/mines-11-14.tsv +2 -0
- package/engine-examples/mines/math/mines-11-2.tsv +2 -0
- package/engine-examples/mines/math/mines-11-3.tsv +2 -0
- package/engine-examples/mines/math/mines-11-4.tsv +2 -0
- package/engine-examples/mines/math/mines-11-5.tsv +2 -0
- package/engine-examples/mines/math/mines-11-6.tsv +2 -0
- package/engine-examples/mines/math/mines-11-7.tsv +2 -0
- package/engine-examples/mines/math/mines-11-8.tsv +2 -0
- package/engine-examples/mines/math/mines-11-9.tsv +2 -0
- package/engine-examples/mines/math/mines-12-1.tsv +2 -0
- package/engine-examples/mines/math/mines-12-10.tsv +2 -0
- package/engine-examples/mines/math/mines-12-11.tsv +2 -0
- package/engine-examples/mines/math/mines-12-12.tsv +2 -0
- package/engine-examples/mines/math/mines-12-13.tsv +2 -0
- package/engine-examples/mines/math/mines-12-2.tsv +2 -0
- package/engine-examples/mines/math/mines-12-3.tsv +2 -0
- package/engine-examples/mines/math/mines-12-4.tsv +2 -0
- package/engine-examples/mines/math/mines-12-5.tsv +2 -0
- package/engine-examples/mines/math/mines-12-6.tsv +2 -0
- package/engine-examples/mines/math/mines-12-7.tsv +2 -0
- package/engine-examples/mines/math/mines-12-8.tsv +2 -0
- package/engine-examples/mines/math/mines-12-9.tsv +2 -0
- package/engine-examples/mines/math/mines-13-1.tsv +2 -0
- package/engine-examples/mines/math/mines-13-10.tsv +2 -0
- package/engine-examples/mines/math/mines-13-11.tsv +2 -0
- package/engine-examples/mines/math/mines-13-12.tsv +2 -0
- package/engine-examples/mines/math/mines-13-2.tsv +2 -0
- package/engine-examples/mines/math/mines-13-3.tsv +2 -0
- package/engine-examples/mines/math/mines-13-4.tsv +2 -0
- package/engine-examples/mines/math/mines-13-5.tsv +2 -0
- package/engine-examples/mines/math/mines-13-6.tsv +2 -0
- package/engine-examples/mines/math/mines-13-7.tsv +2 -0
- package/engine-examples/mines/math/mines-13-8.tsv +2 -0
- package/engine-examples/mines/math/mines-13-9.tsv +2 -0
- package/engine-examples/mines/math/mines-14-1.tsv +2 -0
- package/engine-examples/mines/math/mines-14-10.tsv +2 -0
- package/engine-examples/mines/math/mines-14-11.tsv +2 -0
- package/engine-examples/mines/math/mines-14-2.tsv +2 -0
- package/engine-examples/mines/math/mines-14-3.tsv +2 -0
- package/engine-examples/mines/math/mines-14-4.tsv +2 -0
- package/engine-examples/mines/math/mines-14-5.tsv +2 -0
- package/engine-examples/mines/math/mines-14-6.tsv +2 -0
- package/engine-examples/mines/math/mines-14-7.tsv +2 -0
- package/engine-examples/mines/math/mines-14-8.tsv +2 -0
- package/engine-examples/mines/math/mines-14-9.tsv +2 -0
- package/engine-examples/mines/math/mines-15-1.tsv +2 -0
- package/engine-examples/mines/math/mines-15-10.tsv +2 -0
- package/engine-examples/mines/math/mines-15-2.tsv +2 -0
- package/engine-examples/mines/math/mines-15-3.tsv +2 -0
- package/engine-examples/mines/math/mines-15-4.tsv +2 -0
- package/engine-examples/mines/math/mines-15-5.tsv +2 -0
- package/engine-examples/mines/math/mines-15-6.tsv +2 -0
- package/engine-examples/mines/math/mines-15-7.tsv +2 -0
- package/engine-examples/mines/math/mines-15-8.tsv +2 -0
- package/engine-examples/mines/math/mines-15-9.tsv +2 -0
- package/engine-examples/mines/math/mines-16-1.tsv +2 -0
- package/engine-examples/mines/math/mines-16-2.tsv +2 -0
- package/engine-examples/mines/math/mines-16-3.tsv +2 -0
- package/engine-examples/mines/math/mines-16-4.tsv +2 -0
- package/engine-examples/mines/math/mines-16-5.tsv +2 -0
- package/engine-examples/mines/math/mines-16-6.tsv +2 -0
- package/engine-examples/mines/math/mines-16-7.tsv +2 -0
- package/engine-examples/mines/math/mines-16-8.tsv +2 -0
- package/engine-examples/mines/math/mines-16-9.tsv +2 -0
- package/engine-examples/mines/math/mines-17-1.tsv +2 -0
- package/engine-examples/mines/math/mines-17-2.tsv +2 -0
- package/engine-examples/mines/math/mines-17-3.tsv +2 -0
- package/engine-examples/mines/math/mines-17-4.tsv +2 -0
- package/engine-examples/mines/math/mines-17-5.tsv +2 -0
- package/engine-examples/mines/math/mines-17-6.tsv +2 -0
- package/engine-examples/mines/math/mines-17-7.tsv +2 -0
- package/engine-examples/mines/math/mines-17-8.tsv +2 -0
- package/engine-examples/mines/math/mines-18-1.tsv +2 -0
- package/engine-examples/mines/math/mines-18-2.tsv +2 -0
- package/engine-examples/mines/math/mines-18-3.tsv +2 -0
- package/engine-examples/mines/math/mines-18-4.tsv +2 -0
- package/engine-examples/mines/math/mines-18-5.tsv +2 -0
- package/engine-examples/mines/math/mines-18-6.tsv +2 -0
- package/engine-examples/mines/math/mines-18-7.tsv +2 -0
- package/engine-examples/mines/math/mines-19-1.tsv +2 -0
- package/engine-examples/mines/math/mines-19-2.tsv +2 -0
- package/engine-examples/mines/math/mines-19-3.tsv +2 -0
- package/engine-examples/mines/math/mines-19-4.tsv +2 -0
- package/engine-examples/mines/math/mines-19-5.tsv +2 -0
- package/engine-examples/mines/math/mines-19-6.tsv +2 -0
- package/engine-examples/mines/math/mines-2-1.tsv +2 -0
- package/engine-examples/mines/math/mines-2-19.tsv +2 -0
- package/engine-examples/mines/math/mines-2-2.tsv +2 -0
- package/engine-examples/mines/math/mines-2-20.tsv +2 -0
- package/engine-examples/mines/math/mines-2-21.tsv +2 -0
- package/engine-examples/mines/math/mines-2-22.tsv +2 -0
- package/engine-examples/mines/math/mines-2-23.tsv +2 -0
- package/engine-examples/mines/math/mines-2-3.tsv +2 -0
- package/engine-examples/mines/math/mines-2-4.tsv +2 -0
- package/engine-examples/mines/math/mines-2-5.tsv +2 -0
- package/engine-examples/mines/math/mines-20-1.tsv +2 -0
- package/engine-examples/mines/math/mines-20-2.tsv +2 -0
- package/engine-examples/mines/math/mines-20-3.tsv +2 -0
- package/engine-examples/mines/math/mines-20-4.tsv +2 -0
- package/engine-examples/mines/math/mines-20-5.tsv +2 -0
- package/engine-examples/mines/math/mines-21-1.tsv +2 -0
- package/engine-examples/mines/math/mines-21-2.tsv +2 -0
- package/engine-examples/mines/math/mines-21-3.tsv +2 -0
- package/engine-examples/mines/math/mines-21-4.tsv +2 -0
- package/engine-examples/mines/math/mines-22-1.tsv +2 -0
- package/engine-examples/mines/math/mines-22-2.tsv +2 -0
- package/engine-examples/mines/math/mines-22-3.tsv +2 -0
- package/engine-examples/mines/math/mines-23-1.tsv +2 -0
- package/engine-examples/mines/math/mines-23-2.tsv +2 -0
- package/engine-examples/mines/math/mines-24-1.tsv +2 -0
- package/engine-examples/mines/math/mines-3-1.tsv +2 -0
- package/engine-examples/mines/math/mines-3-17.tsv +2 -0
- package/engine-examples/mines/math/mines-3-18.tsv +2 -0
- package/engine-examples/mines/math/mines-3-19.tsv +2 -0
- package/engine-examples/mines/math/mines-3-2.tsv +2 -0
- package/engine-examples/mines/math/mines-3-20.tsv +2 -0
- package/engine-examples/mines/math/mines-3-21.tsv +2 -0
- package/engine-examples/mines/math/mines-3-22.tsv +2 -0
- package/engine-examples/mines/math/mines-3-3.tsv +2 -0
- package/engine-examples/mines/math/mines-3-4.tsv +2 -0
- package/engine-examples/mines/math/mines-3-5.tsv +2 -0
- package/engine-examples/mines/math/mines-3-6.tsv +2 -0
- package/engine-examples/mines/math/mines-4-1.tsv +2 -0
- package/engine-examples/mines/math/mines-4-13.tsv +2 -0
- package/engine-examples/mines/math/mines-4-14.tsv +2 -0
- package/engine-examples/mines/math/mines-4-15.tsv +2 -0
- package/engine-examples/mines/math/mines-4-16.tsv +2 -0
- package/engine-examples/mines/math/mines-4-17.tsv +2 -0
- package/engine-examples/mines/math/mines-4-18.tsv +2 -0
- package/engine-examples/mines/math/mines-4-19.tsv +2 -0
- package/engine-examples/mines/math/mines-4-2.tsv +2 -0
- package/engine-examples/mines/math/mines-4-20.tsv +2 -0
- package/engine-examples/mines/math/mines-4-21.tsv +2 -0
- package/engine-examples/mines/math/mines-4-3.tsv +2 -0
- package/engine-examples/mines/math/mines-4-4.tsv +2 -0
- package/engine-examples/mines/math/mines-4-5.tsv +2 -0
- package/engine-examples/mines/math/mines-4-6.tsv +2 -0
- package/engine-examples/mines/math/mines-4-7.tsv +2 -0
- package/engine-examples/mines/math/mines-4-8.tsv +2 -0
- package/engine-examples/mines/math/mines-4-9.tsv +2 -0
- package/engine-examples/mines/math/mines-5-1.tsv +2 -0
- package/engine-examples/mines/math/mines-5-10.tsv +2 -0
- package/engine-examples/mines/math/mines-5-11.tsv +2 -0
- package/engine-examples/mines/math/mines-5-12.tsv +2 -0
- package/engine-examples/mines/math/mines-5-13.tsv +2 -0
- package/engine-examples/mines/math/mines-5-14.tsv +2 -0
- package/engine-examples/mines/math/mines-5-15.tsv +2 -0
- package/engine-examples/mines/math/mines-5-16.tsv +2 -0
- package/engine-examples/mines/math/mines-5-17.tsv +2 -0
- package/engine-examples/mines/math/mines-5-18.tsv +2 -0
- package/engine-examples/mines/math/mines-5-19.tsv +2 -0
- package/engine-examples/mines/math/mines-5-2.tsv +2 -0
- package/engine-examples/mines/math/mines-5-20.tsv +2 -0
- package/engine-examples/mines/math/mines-5-3.tsv +2 -0
- package/engine-examples/mines/math/mines-5-4.tsv +2 -0
- package/engine-examples/mines/math/mines-5-5.tsv +2 -0
- package/engine-examples/mines/math/mines-5-6.tsv +2 -0
- package/engine-examples/mines/math/mines-5-7.tsv +2 -0
- package/engine-examples/mines/math/mines-5-8.tsv +2 -0
- package/engine-examples/mines/math/mines-5-9.tsv +2 -0
- package/engine-examples/mines/math/mines-6-1.tsv +2 -0
- package/engine-examples/mines/math/mines-6-10.tsv +2 -0
- package/engine-examples/mines/math/mines-6-11.tsv +2 -0
- package/engine-examples/mines/math/mines-6-12.tsv +2 -0
- package/engine-examples/mines/math/mines-6-13.tsv +2 -0
- package/engine-examples/mines/math/mines-6-14.tsv +2 -0
- package/engine-examples/mines/math/mines-6-15.tsv +2 -0
- package/engine-examples/mines/math/mines-6-16.tsv +2 -0
- package/engine-examples/mines/math/mines-6-17.tsv +2 -0
- package/engine-examples/mines/math/mines-6-18.tsv +2 -0
- package/engine-examples/mines/math/mines-6-19.tsv +2 -0
- package/engine-examples/mines/math/mines-6-2.tsv +2 -0
- package/engine-examples/mines/math/mines-6-3.tsv +2 -0
- package/engine-examples/mines/math/mines-6-4.tsv +2 -0
- package/engine-examples/mines/math/mines-6-5.tsv +2 -0
- package/engine-examples/mines/math/mines-6-6.tsv +2 -0
- package/engine-examples/mines/math/mines-6-7.tsv +2 -0
- package/engine-examples/mines/math/mines-6-8.tsv +2 -0
- package/engine-examples/mines/math/mines-6-9.tsv +2 -0
- package/engine-examples/mines/math/mines-7-1.tsv +2 -0
- package/engine-examples/mines/math/mines-7-10.tsv +2 -0
- package/engine-examples/mines/math/mines-7-11.tsv +2 -0
- package/engine-examples/mines/math/mines-7-12.tsv +2 -0
- package/engine-examples/mines/math/mines-7-13.tsv +2 -0
- package/engine-examples/mines/math/mines-7-14.tsv +2 -0
- package/engine-examples/mines/math/mines-7-15.tsv +2 -0
- package/engine-examples/mines/math/mines-7-16.tsv +2 -0
- package/engine-examples/mines/math/mines-7-17.tsv +2 -0
- package/engine-examples/mines/math/mines-7-18.tsv +2 -0
- package/engine-examples/mines/math/mines-7-2.tsv +2 -0
- package/engine-examples/mines/math/mines-7-3.tsv +2 -0
- package/engine-examples/mines/math/mines-7-4.tsv +2 -0
- package/engine-examples/mines/math/mines-7-5.tsv +2 -0
- package/engine-examples/mines/math/mines-7-6.tsv +2 -0
- package/engine-examples/mines/math/mines-7-7.tsv +2 -0
- package/engine-examples/mines/math/mines-7-8.tsv +2 -0
- package/engine-examples/mines/math/mines-7-9.tsv +2 -0
- package/engine-examples/mines/math/mines-8-1.tsv +2 -0
- package/engine-examples/mines/math/mines-8-10.tsv +2 -0
- package/engine-examples/mines/math/mines-8-11.tsv +2 -0
- package/engine-examples/mines/math/mines-8-12.tsv +2 -0
- package/engine-examples/mines/math/mines-8-13.tsv +2 -0
- package/engine-examples/mines/math/mines-8-14.tsv +2 -0
- package/engine-examples/mines/math/mines-8-15.tsv +2 -0
- package/engine-examples/mines/math/mines-8-16.tsv +2 -0
- package/engine-examples/mines/math/mines-8-17.tsv +2 -0
- package/engine-examples/mines/math/mines-8-2.tsv +2 -0
- package/engine-examples/mines/math/mines-8-3.tsv +2 -0
- package/engine-examples/mines/math/mines-8-4.tsv +2 -0
- package/engine-examples/mines/math/mines-8-5.tsv +2 -0
- package/engine-examples/mines/math/mines-8-6.tsv +2 -0
- package/engine-examples/mines/math/mines-8-7.tsv +2 -0
- package/engine-examples/mines/math/mines-8-8.tsv +2 -0
- package/engine-examples/mines/math/mines-8-9.tsv +2 -0
- package/engine-examples/mines/math/mines-9-1.tsv +2 -0
- package/engine-examples/mines/math/mines-9-10.tsv +2 -0
- package/engine-examples/mines/math/mines-9-11.tsv +2 -0
- package/engine-examples/mines/math/mines-9-12.tsv +2 -0
- package/engine-examples/mines/math/mines-9-13.tsv +2 -0
- package/engine-examples/mines/math/mines-9-14.tsv +2 -0
- package/engine-examples/mines/math/mines-9-15.tsv +2 -0
- package/engine-examples/mines/math/mines-9-16.tsv +2 -0
- package/engine-examples/mines/math/mines-9-2.tsv +2 -0
- package/engine-examples/mines/math/mines-9-3.tsv +2 -0
- package/engine-examples/mines/math/mines-9-4.tsv +2 -0
- package/engine-examples/mines/math/mines-9-5.tsv +2 -0
- package/engine-examples/mines/math/mines-9-6.tsv +2 -0
- package/engine-examples/mines/math/mines-9-7.tsv +2 -0
- package/engine-examples/mines/math/mines-9-8.tsv +2 -0
- package/engine-examples/mines/math/mines-9-9.tsv +2 -0
- package/engine-examples/plinko/AGENTS.md +3 -0
- package/engine-examples/plinko/README.md +9 -0
- package/engine-examples/plinko/bundle/assets.json +1 -0
- package/engine-examples/plinko/bundle/game.css +5 -0
- package/engine-examples/plinko/bundle/game.js +92 -0
- package/engine-examples/plinko/bundle/hbtest.json +4 -0
- package/engine-examples/plinko/bundle/index.html +16 -0
- package/engine-examples/plinko/bundle/model.json +1 -0
- package/engine-examples/plinko/math/gen.mjs +40 -0
- package/engine-examples/plinko/math/manifest.json +1 -0
- package/engine-examples/plinko/math/plinko-12-high.tsv +13 -0
- package/engine-examples/plinko/math/plinko-12-low.tsv +13 -0
- package/engine-examples/plinko/math/plinko-12-medium.tsv +13 -0
- package/engine-examples/plinko/math/plinko-16-high.tsv +17 -0
- package/engine-examples/plinko/math/plinko-16-low.tsv +17 -0
- package/engine-examples/plinko/math/plinko-16-medium.tsv +17 -0
- package/engine-examples/plinko/math/plinko-8-high.tsv +9 -0
- package/engine-examples/plinko/math/plinko-8-low.tsv +9 -0
- package/engine-examples/plinko/math/plinko-8-medium.tsv +9 -0
- package/engine-examples/plinko/math-kit/pack.mjs +165 -0
- package/engine-examples/plinko/math-kit/package.json +8 -0
- package/engine-examples/plinko/math-kit/slot.mjs +94 -0
- package/engine-examples/plinko/math-kit/tumble.mjs +94 -0
- package/engine-examples/slot/AGENTS.md +3 -0
- package/engine-examples/slot/README.md +26 -0
- package/engine-examples/slot/bundle/assets.json +1 -0
- package/engine-examples/slot/bundle/game.css +19 -0
- package/engine-examples/slot/bundle/game.js +42 -0
- package/engine-examples/slot/bundle/hbtest.json +4 -0
- package/engine-examples/slot/bundle/index.html +23 -0
- package/engine-examples/slot/math/gen.mjs +79 -0
- package/engine-examples/slot/math/manifest.json +1 -0
- package/engine-examples/slot/math/slot.tsv +110592 -0
- package/engine-examples/tower/AGENTS.md +3 -0
- package/engine-examples/tower/README.md +21 -0
- package/engine-examples/tower/bundle/assets.json +1 -0
- package/engine-examples/tower/bundle/game.css +8 -0
- package/engine-examples/tower/bundle/game.js +37 -0
- package/engine-examples/tower/bundle/hbtest.json +5 -0
- package/engine-examples/tower/bundle/index.html +16 -0
- package/engine-examples/tower/math/floor.tsv +4 -0
- package/engine-examples/tower/math/manifest.json +61 -0
- package/engine-examples/tumble/AGENTS.md +3 -0
- package/engine-examples/tumble/README.md +18 -0
- package/engine-examples/tumble/bundle/assets.json +1 -0
- package/engine-examples/tumble/bundle/game.css +13 -0
- package/engine-examples/tumble/bundle/game.js +119 -0
- package/engine-examples/tumble/bundle/hbtest.json +4 -0
- package/engine-examples/tumble/bundle/i18n.json +122 -0
- package/engine-examples/tumble/bundle/index.html +20 -0
- package/engine-examples/tumble/bundle/model.json +1 -0
- package/engine-examples/tumble/math/gen.mjs +38 -0
- package/engine-examples/tumble/math-kit/pack.mjs +165 -0
- package/engine-examples/tumble/math-kit/package.json +8 -0
- package/engine-examples/tumble/math-kit/slot.mjs +94 -0
- package/engine-examples/tumble/math-kit/tumble.mjs +94 -0
- package/engine-examples/wheel/README.md +14 -0
- package/engine-examples/wheel/bundle/assets.json +1 -0
- package/engine-examples/wheel/bundle/game.css +7 -0
- package/engine-examples/wheel/bundle/game.js +51 -0
- package/engine-examples/wheel/bundle/hbtest.json +4 -0
- package/engine-examples/wheel/bundle/index.html +19 -0
- package/engine-examples/wheel/math/high.tsv +24 -0
- package/engine-examples/wheel/math/low.tsv +24 -0
- package/engine-examples/wheel/math/manifest.json +48 -0
- package/engine-examples/wheel/math/medium.tsv +24 -0
- package/engine-math/pack.mjs +165 -0
- package/engine-math/package.json +1 -0
- package/engine-math/slot.mjs +94 -0
- package/engine-math/tumble.mjs +94 -0
- package/engine-mcp/package.json +1 -0
- package/engine-mcp/server.mjs +151 -0
- package/engine-sdk/GUIDE.md +855 -0
- package/engine-sdk/assets.js +92 -0
- package/engine-sdk/autoplay.js +82 -0
- package/engine-sdk/bet-field.js +391 -0
- package/engine-sdk/kit/fiat-coins.js +51 -0
- package/engine-sdk/kit/numeric-input.js +116 -0
- package/engine-sdk/kit/wallet-format.js +423 -0
- package/engine-sdk/marks.js +22 -0
- package/engine-sdk/messages.js +289 -0
- package/engine-sdk/money.js +89 -0
- package/engine-sdk/package.json +1 -0
- package/engine-sdk/protocol.js +263 -0
- package/engine-sdk/sdk.d.ts +138 -0
- package/engine-sdk/sdk.js +330 -0
- package/engine-sdk/stake.js +68 -0
- package/engine-sdk/ui.d.ts +36 -0
- package/engine-sdk/ui.js +340 -0
- package/engine-shell/core/faults.js +127 -0
- package/engine-shell/core/rgs.js +193 -0
- package/engine-shell/core/router.js +917 -0
- package/engine-shell/host/boot.js +408 -0
- package/engine-shell/host/engine-v1.js +438 -0
- package/engine-shell/host/fair.js +285 -0
- package/engine-shell/host/fonts/SOURCE.json +11 -0
- package/engine-shell/host/fonts/figtree-400.woff2 +0 -0
- package/engine-shell/host/fonts/figtree-500.woff2 +0 -0
- package/engine-shell/host/fonts/figtree-600.woff2 +0 -0
- package/engine-shell/host/fonts/red-hat-display-700.woff2 +0 -0
- package/engine-shell/host/index.html +230 -0
- package/engine-shell/host/page.js +809 -0
- package/engine-shell/host/sandbox-panel.js +681 -0
- package/engine-shell/host/sandbox-scenarios.js +554 -0
- package/engine-shell/host/site-bridge.js +87 -0
- package/engine-shell/host/strings.js +916 -0
- package/engine-shell/package.json +1 -0
- package/engine-shell/verifiers/badcat/coin.tsv +2 -0
- package/engine-shell/verifiers/badcat/dice-2.tsv +11 -0
- package/engine-shell/verifiers/badcat/dice-5.tsv +26 -0
- package/engine-shell/verifiers/badcat/dice-9.tsv +46 -0
- package/engine-shell/verifiers/badcat/manifest.json +3344 -0
- package/engine-shell/verifiers/badcat/rps.tsv +3 -0
- package/engine-shell/verifiers/badcat/triple.tsv +66 -0
- package/engine-shell/verifiers/engine-verify.js +358 -0
- package/engine-shell/verifiers/package.json +5 -0
- package/engine-shell/verifiers/parity/manifest.json +27 -0
- package/engine-shell/verifiers/parity/wide.tsv +5 -0
- package/engine-shell/verifiers/parity-vectors.json +244 -0
- package/engine-shell/verifiers/vectors.json +100 -0
- package/package.json +10 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Staged loading. A game declares its files in groups (assets.json) and
|
|
3
|
+
* loads them when it needs them: `boot` before the first frame, the rest
|
|
4
|
+
* after `ready()` or on demand. The upload checks hold every group to its
|
|
5
|
+
* byte budget, so the first frame never waits for music it does not play.
|
|
6
|
+
*
|
|
7
|
+
* {
|
|
8
|
+
* "groups": {
|
|
9
|
+
* "boot": { "files": ["index.js", "ui.css", "lobby.webp"] },
|
|
10
|
+
* "music": { "files": ["music/theme.mp3"], "load": "idle" },
|
|
11
|
+
* "skins": { "files": ["skins/tabby.webp"], "load": "demand" }
|
|
12
|
+
* }
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* `load` is when a group is fetched: "boot" (the group named boot, always
|
|
16
|
+
* first), "idle" (after ready, in declaration order) or "demand" (only when
|
|
17
|
+
* the game asks). Files are fetched into the browser cache; the game then
|
|
18
|
+
* uses their ordinary relative URLs.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const LOADS = ['boot', 'idle', 'demand'];
|
|
22
|
+
const GROUP_RE = /^[a-z0-9][a-z0-9-]{0,31}$/;
|
|
23
|
+
const FILE_RE = /^(?!\/)(?!.*\.\.)[A-Za-z0-9._\/-]{1,200}$/;
|
|
24
|
+
|
|
25
|
+
/** Checks an assets manifest's shape. Returns the problems, empty when fine. */
|
|
26
|
+
export function checkAssetManifest(manifest) {
|
|
27
|
+
const problems = [];
|
|
28
|
+
const groups = manifest && manifest.groups;
|
|
29
|
+
if (!groups || typeof groups !== 'object' || Array.isArray(groups)) return ['groups must be an object'];
|
|
30
|
+
if (!groups.boot) problems.push('a boot group is required');
|
|
31
|
+
const seen = new Map();
|
|
32
|
+
for (const [name, g] of Object.entries(groups)) {
|
|
33
|
+
if (!GROUP_RE.test(name)) problems.push(`group name ${JSON.stringify(name)}`);
|
|
34
|
+
if (!g || !Array.isArray(g.files) || g.files.length === 0) {
|
|
35
|
+
problems.push(`group ${name} needs files`);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const load = name === 'boot' ? 'boot' : g.load ?? 'idle';
|
|
39
|
+
if (!LOADS.includes(load) || (load === 'boot') !== (name === 'boot')) problems.push(`group ${name} load ${JSON.stringify(g.load)}`);
|
|
40
|
+
for (const f of g.files) {
|
|
41
|
+
if (typeof f !== 'string' || !FILE_RE.test(f)) problems.push(`group ${name} file ${JSON.stringify(f)}`);
|
|
42
|
+
else if (seen.has(f)) problems.push(`${f} is in both ${seen.get(f)} and ${name}`);
|
|
43
|
+
else seen.set(f, name);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return problems;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates the loader. `onProgress(group, loadedFiles, totalFiles)` reports
|
|
51
|
+
* each group (the SDK forwards it to the shell as `loading`).
|
|
52
|
+
*/
|
|
53
|
+
export function createAssetLoader(manifest, { base = './', fetchFile = (url) => fetch(url), onProgress = () => {}, concurrency = 6 } = {}) {
|
|
54
|
+
const problems = checkAssetManifest(manifest);
|
|
55
|
+
if (problems.length) throw new TypeError(`assets.json: ${problems.join('; ')}`);
|
|
56
|
+
if (!Number.isSafeInteger(concurrency) || concurrency < 1) throw new RangeError('asset concurrency must be a positive integer');
|
|
57
|
+
const groups = manifest.groups;
|
|
58
|
+
const loading = new Map(); // group -> Promise
|
|
59
|
+
|
|
60
|
+
function load(name) {
|
|
61
|
+
if (!Object.hasOwn(groups, name)) return Promise.reject(new TypeError(`unknown asset group ${name}`));
|
|
62
|
+
if (!loading.has(name)) loading.set(name, fetchGroup(name));
|
|
63
|
+
return loading.get(name);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function fetchGroup(name) {
|
|
67
|
+
const files = groups[name].files;
|
|
68
|
+
let done = 0;
|
|
69
|
+
let next = 0;
|
|
70
|
+
onProgress(name, 0, files.length);
|
|
71
|
+
async function worker() {
|
|
72
|
+
while (next < files.length) {
|
|
73
|
+
const file = files[next++];
|
|
74
|
+
// checkAssetManifest admitted only plain relative paths.
|
|
75
|
+
const res = await fetchFile(base + file);
|
|
76
|
+
if (!res || !res.ok) throw new Error(`asset ${file}: ${res ? res.status : 'no response'}`);
|
|
77
|
+
await res.arrayBuffer(); // into the cache, whatever the type
|
|
78
|
+
onProgress(name, ++done, files.length);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, files.length) }, worker));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Loads the idle groups one after another, in declaration order. */
|
|
85
|
+
async function loadIdle() {
|
|
86
|
+
for (const [name, g] of Object.entries(groups)) {
|
|
87
|
+
if (name !== 'boot' && (g.load ?? 'idle') === 'idle') await load(name);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { load, loadBoot: () => load('boot'), loadIdle };
|
|
92
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Originals' autobet rules, as the engine panel (ui.js) turns them into
|
|
3
|
+
* a shell autoplay session. Pure, so the rules are tested without a DOM.
|
|
4
|
+
*
|
|
5
|
+
* The session is the shell's (protocol.js AUTOPLAY_START): it holds at most
|
|
6
|
+
* `rounds` plays of the bet it was asked for, none above `stake`, and ends at
|
|
7
|
+
* the player's stop on profit or loss (the shell shows the player all of it
|
|
8
|
+
* before Start). The panel keeps the Originals' settings — a count (0 = as many as
|
|
9
|
+
* the shell allows), on win / on loss reset the stake or increase it by a
|
|
10
|
+
* percent, stop on profit / stop on loss — and asks for the session those
|
|
11
|
+
* settings can need: the stake it starts with when both rules reset, or the
|
|
12
|
+
* most the player could stake (the coin's maximum, the selected bet's own
|
|
13
|
+
* ceiling under the maximum win and the balance) when a rule grows it. A
|
|
14
|
+
* grown stake is cut to that ceiling, never above it: the shell refuses a
|
|
15
|
+
* play above the session's stake. The request names the bet (`mode`, `bet`),
|
|
16
|
+
* so the shell caps the stake at the bet's own ceiling
|
|
17
|
+
* (`limits.maxStake[mode][bet]`) as well as the coin's maximum and the
|
|
18
|
+
* balance; the panel applies the same ceiling (field.maxStake) so the terms
|
|
19
|
+
* it asks for are the ones the player will see.
|
|
20
|
+
*/
|
|
21
|
+
import { AUTOPLAY_MAX_ROUNDS } from './protocol.js';
|
|
22
|
+
|
|
23
|
+
// Truncated down to dp decimals, with the float noise scrubbed first (as in
|
|
24
|
+
// bet-field.js): a stake never grows past what the rule gives.
|
|
25
|
+
export function trunc(n, dp) {
|
|
26
|
+
if (!Number.isFinite(n) || n <= 0) return 0;
|
|
27
|
+
const f = 10 ** dp;
|
|
28
|
+
return Math.floor(parseFloat((n * f).toPrecision(15))) / f;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The most a stake may reach: the coin's max, the bet's own ceiling
|
|
33
|
+
* (bet-field.js `maxStake`, from protocol.js maxStakeFor) and the balance,
|
|
34
|
+
* whichever is lowest; `amount` when none is known. A bet ceiling of 0 (the
|
|
35
|
+
* bet takes nothing while the coin has no rate) never gets here: the field
|
|
36
|
+
* refuses the stake before a run starts.
|
|
37
|
+
*/
|
|
38
|
+
export function stakeCeiling({ amount, limits = {}, balance = null, maxStake = null }) {
|
|
39
|
+
const bounds = [];
|
|
40
|
+
if (typeof limits.max === 'number' && limits.max > 0) bounds.push(limits.max);
|
|
41
|
+
if (typeof maxStake === 'number' && maxStake > 0) bounds.push(maxStake);
|
|
42
|
+
if (typeof balance === 'number' && balance >= 0) bounds.push(balance);
|
|
43
|
+
if (!bounds.length) return amount;
|
|
44
|
+
return Math.max(amount, Math.min(...bounds));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The session the panel asks the shell for.
|
|
49
|
+
* mode, bet the bet the run is for (the panel's selection; the shell holds every play to it)
|
|
50
|
+
* amount the stake the run starts with (wallet units)
|
|
51
|
+
* count the player's number of bets; 0 is "infinite", which is the shell's cap
|
|
52
|
+
* rule { win: 'reset'|'increase', loss: 'reset'|'increase' }
|
|
53
|
+
* winPct, lossPct the percent a rule increases by
|
|
54
|
+
* stopProfit, stopLoss the player's stops (0 = none)
|
|
55
|
+
* limits, balance, maxStake what bounds a grown stake: the coin's limits, the
|
|
56
|
+
* balance, and the selected bet's own ceiling (null when the session names none)
|
|
57
|
+
*/
|
|
58
|
+
export function autoplayRequest({ mode, bet, amount, count = 0, rule = { win: 'reset', loss: 'reset' }, winPct = 0, lossPct = 0, stopProfit = 0, stopLoss = 0, limits = {}, balance = null, maxStake = null }) {
|
|
59
|
+
const rounds = count > 0 ? Math.min(Math.floor(count), AUTOPLAY_MAX_ROUNDS) : AUTOPLAY_MAX_ROUNDS;
|
|
60
|
+
const grows = (rule.win === 'increase' && winPct > 0) || (rule.loss === 'increase' && lossPct > 0);
|
|
61
|
+
const req = { rounds, stake: grows ? stakeCeiling({ amount, limits, balance, maxStake }) : amount };
|
|
62
|
+
if (typeof mode === 'string') req.mode = mode;
|
|
63
|
+
if (typeof bet === 'string') req.bet = bet;
|
|
64
|
+
if (stopProfit > 0) req.stopOnProfit = stopProfit;
|
|
65
|
+
if (stopLoss > 0) req.stopOnLoss = stopLoss;
|
|
66
|
+
return req;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The stake of the next round after a win or a loss, within the session's stake. */
|
|
70
|
+
export function nextStake({ amount, base, won, rule, winPct = 0, lossPct = 0, cap, decimals = 8 }) {
|
|
71
|
+
const how = won ? rule.win : rule.loss;
|
|
72
|
+
const pct = Math.max(0, won ? winPct : lossPct);
|
|
73
|
+
const next = how === 'increase' ? amount * (1 + pct / 100) : base;
|
|
74
|
+
return trunc(Math.min(next, cap), decimals);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Why the run stops on its result so far: 'profit', 'loss', or null to go on. */
|
|
78
|
+
export function autoplayStop({ profit, stopProfit = 0, stopLoss = 0 }) {
|
|
79
|
+
if (stopProfit > 0 && profit >= stopProfit) return 'profit';
|
|
80
|
+
if (stopLoss > 0 && -profit >= stopLoss) return 'loss';
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Originals' money fields, for any game: drop them in, connect them to
|
|
3
|
+
* the engine, and the player sees their own currency exactly as in Dice or
|
|
4
|
+
* Coin Flip — our coin mark, the fiat sign, the field's precision, the
|
|
5
|
+
* caption in the other unit, ½ · 2× · max — with nothing to build.
|
|
6
|
+
*
|
|
7
|
+
* import './engine-sdk/bet-field.js';
|
|
8
|
+
*
|
|
9
|
+
* <hb-bet-amount></hb-bet-amount> the stake (label="…", max-button)
|
|
10
|
+
* <hb-balance></hb-balance> the balance, as the header shows it
|
|
11
|
+
*
|
|
12
|
+
* field.connect(engine);
|
|
13
|
+
* field.bet = { mode, bet }; // the bet the stake is for (its own ceiling)
|
|
14
|
+
* engine.play({ mode, bet, amount: field.amount }); // wallet units
|
|
15
|
+
* field.disabled = true; // while a round runs
|
|
16
|
+
* field.addEventListener('change', (e) => e.detail); // { amount, reason }
|
|
17
|
+
* field.value; // the same as text, like an <input>
|
|
18
|
+
*
|
|
19
|
+
* The rule is the Originals' (bet field: coinflip-frontend BetAmount.svelte,
|
|
20
|
+
* money: game-kit wallet-format.js through engine.money): a typed amount is in
|
|
21
|
+
* the field's unit and becomes the wallet amount truncated down, never more
|
|
22
|
+
* than typed; an empty field is no stake (`reason` is `min`: an engine round
|
|
23
|
+
* is never played for free, so a play button stays disabled on it); a stake
|
|
24
|
+
* the server would refuse (over the balance or the limits) turns the field
|
|
25
|
+
* red and says why, and the game should not send it. The limits include the
|
|
26
|
+
* maximum win per round: each bet has its own highest stake
|
|
27
|
+
* (engine.config.limits.maxStake[mode][bet], lower the higher the bet's top
|
|
28
|
+
* multiplier), so tell the field which bet the stake is for and it holds the
|
|
29
|
+
* stake to it, says the most the bet takes, and its max chip stops there. A
|
|
30
|
+
* figure of 0 (the server names it while the coin has no USD rate) means
|
|
31
|
+
* nothing can be staked for now: the field says so in the Originals' words
|
|
32
|
+
* and hides its max chip, and a play button on `reason` stays disabled. The
|
|
33
|
+
* server checks everything again anyway.
|
|
34
|
+
*
|
|
35
|
+
* Shadow DOM: the game's styles cannot reach in, and these cannot leak out.
|
|
36
|
+
* The look follows the site's tokens; a game may set --hb-font.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { MESSAGES } from './messages.js';
|
|
40
|
+
import { maxStakeFor } from './protocol.js';
|
|
41
|
+
import { leastStake, stakeCeiling, stakeReason } from './stake.js';
|
|
42
|
+
|
|
43
|
+
const CSS = `
|
|
44
|
+
:host { display: block; font-family: var(--hb-font, 'Figtree', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif); color: #F9F9FB; }
|
|
45
|
+
.row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; min-width: 0; margin-bottom: 8px; }
|
|
46
|
+
.label { font-size: 14px; line-height: 20px; font-weight: 600; }
|
|
47
|
+
.cap { font-size: 14px; line-height: 20px; font-weight: 600; color: #8F96A8; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
48
|
+
.hint { font-size: 12px; line-height: 16px; color: #8F96A8; min-height: 16px; margin-top: 6px; }
|
|
49
|
+
.hint.bad { color: #FA2E5D; }
|
|
50
|
+
.field { position: relative; box-sizing: border-box; display: flex; align-items: center; gap: 8px; height: 48px; width: 100%; padding: 0 4px 0 16px;
|
|
51
|
+
background: #0b1f28; border: 1px solid #0D2A38; border-radius: 8px; transition: border-color 120ms ease, box-shadow 120ms ease; }
|
|
52
|
+
.field:focus-within { box-shadow: 0 0 0 4px #08141a, 0 0 0 6px #28F3F3; }
|
|
53
|
+
.field.bad { border-color: #FA2E5D; box-shadow: 0 0 0 1px #FA2E5D inset; }
|
|
54
|
+
.field.shake { animation: shake 200ms ease; }
|
|
55
|
+
@keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-3px); } 75% { transform: translateX(3px); } }
|
|
56
|
+
.mark { flex: 0 0 auto; width: 16px; height: 16px; display: block; border-radius: 50%; }
|
|
57
|
+
.sign { flex: 0 0 auto; white-space: nowrap; font-size: 16px; font-weight: 500; font-variant-numeric: tabular-nums; margin-right: -8px; }
|
|
58
|
+
.sign.empty { color: #8F96A8; }
|
|
59
|
+
input { flex: 1 1 0; min-width: 0; height: 100%; padding: 0; background: transparent; border: 0; outline: none; color: #F9F9FB;
|
|
60
|
+
font: inherit; font-size: 16px; font-weight: 500; font-variant-numeric: tabular-nums; }
|
|
61
|
+
input::placeholder { color: #8F96A8; }
|
|
62
|
+
input[readonly] { color: #8F96A8; cursor: not-allowed; }
|
|
63
|
+
.chips { display: flex; align-items: center; gap: 4px; flex: 0 0 auto; }
|
|
64
|
+
button { display: inline-flex; align-items: center; justify-content: center; min-width: 34px; height: 32px; padding: 0 8px; border: 0; border-radius: 8px;
|
|
65
|
+
background: rgba(9, 102, 125, 0.15); color: #F9F9FB; font: inherit; font-size: 12px; font-weight: 600; cursor: pointer; user-select: none;
|
|
66
|
+
transition: background 120ms ease, transform 120ms ease; }
|
|
67
|
+
button:hover, button:focus-visible { background: rgba(9, 102, 125, 0.2); outline: none; }
|
|
68
|
+
button:active { transform: scale(0.96); }
|
|
69
|
+
button:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }
|
|
70
|
+
.balance { display: inline-flex; align-items: center; gap: 6px; font-size: 16px; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
71
|
+
`;
|
|
72
|
+
|
|
73
|
+
// The field's words are the Originals' own, in every language the site
|
|
74
|
+
// ships (messages.js, generated from Coin Flip's dictionaries). The language
|
|
75
|
+
// is the player's: the field's `lang`, the shell's, the page's, the browser's.
|
|
76
|
+
export function wordsFor(lang) {
|
|
77
|
+
const v = String(lang || '').toLowerCase().replace('_', '-');
|
|
78
|
+
return MESSAGES[v] || MESSAGES[v.split('-')[0]] || MESSAGES.en;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Truncated down to dp decimals, with the float noise scrubbed first (as in
|
|
82
|
+
// wallet-format.js): a stake never grows past what the player chose.
|
|
83
|
+
function trunc(n, dp) {
|
|
84
|
+
if (!Number.isFinite(n) || n <= 0) return 0;
|
|
85
|
+
const f = 10 ** dp;
|
|
86
|
+
return Math.floor(parseFloat((n * f).toPrecision(15))) / f;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function sheet(root) {
|
|
90
|
+
const style = document.createElement('style');
|
|
91
|
+
style.textContent = CSS;
|
|
92
|
+
root.append(style);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export class BetAmountField extends HTMLElement {
|
|
96
|
+
static observedAttributes = ['label', 'disabled', 'max-button', 'lang'];
|
|
97
|
+
#engine = null;
|
|
98
|
+
#money = null;
|
|
99
|
+
#amount = 0;
|
|
100
|
+
#bet = null; // { mode, bet } the stake is for: its own ceiling under the maximum win
|
|
101
|
+
#typing = false;
|
|
102
|
+
#hintTimer = 0;
|
|
103
|
+
#note = '';
|
|
104
|
+
#els;
|
|
105
|
+
|
|
106
|
+
constructor() {
|
|
107
|
+
super();
|
|
108
|
+
const root = this.attachShadow({ mode: 'open' });
|
|
109
|
+
sheet(root);
|
|
110
|
+
const wrap = document.createElement('div');
|
|
111
|
+
wrap.innerHTML = `<div class="row"><span class="label" part="label"></span><span class="cap" part="caption"></span></div>
|
|
112
|
+
<div class="field" part="field"><img class="mark" alt="" hidden><span class="sign" hidden></span>
|
|
113
|
+
<input inputmode="decimal" autocomplete="off" spellcheck="false" readonly>
|
|
114
|
+
<span class="chips"><button type="button" data-chip="half">½</button><button type="button" data-chip="double">2×</button><button type="button" data-chip="max" hidden>max</button></span></div>
|
|
115
|
+
<div class="hint" part="hint" aria-live="polite"></div>`;
|
|
116
|
+
root.append(wrap);
|
|
117
|
+
const $ = (s) => root.querySelector(s);
|
|
118
|
+
this.#els = { label: $('.label'), cap: $('.cap'), field: $('.field'), mark: $('.mark'), sign: $('.sign'), input: $('input'), hint: $('.hint'), chips: [...root.querySelectorAll('button')] };
|
|
119
|
+
const { input } = this.#els;
|
|
120
|
+
input.addEventListener('focus', () => input.select());
|
|
121
|
+
input.addEventListener('input', () => this.#onInput());
|
|
122
|
+
input.addEventListener('blur', () => this.#onBlur());
|
|
123
|
+
for (const b of this.#els.chips) b.addEventListener('click', () => this.#chip(b.dataset.chip));
|
|
124
|
+
this.#render();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Binds the field to the engine: its currency, limits and balance. */
|
|
128
|
+
connect(engine) {
|
|
129
|
+
this.#engine = engine;
|
|
130
|
+
this.#money = engine.money;
|
|
131
|
+
engine.on('balance', () => this.#render());
|
|
132
|
+
const kept = this.#recall();
|
|
133
|
+
if (kept > 0) this.#amount = kept;
|
|
134
|
+
this.#render();
|
|
135
|
+
return this;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** The stake in wallet units, what engine.play takes. */
|
|
139
|
+
get amount() {
|
|
140
|
+
return this.#amount;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
set amount(v) {
|
|
144
|
+
this.#set(Number(v) || 0);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** The stake as text, like an <input>'s value: for code written against
|
|
148
|
+
* one (a plain decimal in wallet units, never an exponent). */
|
|
149
|
+
get value() {
|
|
150
|
+
if (!this.#money) return '0';
|
|
151
|
+
const text = this.#amount.toFixed(this.#money.walletDecimals);
|
|
152
|
+
return text.includes('.') ? text.replace(/0+$/, '').replace(/\.$/, '') : text;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
set value(v) {
|
|
156
|
+
this.#set(this.#money ? this.#money.parse(String(v)) : 0);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** The bet the stake is for ({ mode, bet }), or null: the field holds the
|
|
160
|
+
* stake to that bet's own highest stake under the maximum win per round
|
|
161
|
+
* (engine.config.limits.maxStake[mode][bet]) once it knows the bet. */
|
|
162
|
+
get bet() {
|
|
163
|
+
return this.#bet;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
set bet(v) {
|
|
167
|
+
const next = v && typeof v.mode === 'string' && typeof v.bet === 'string' ? { mode: v.mode, bet: v.bet } : null;
|
|
168
|
+
if ((next && this.#bet && next.mode === this.#bet.mode && next.bet === this.#bet.bet) || (!next && !this.#bet)) return;
|
|
169
|
+
this.#bet = next;
|
|
170
|
+
this.#render();
|
|
171
|
+
// Another bet may take less: whoever follows `reason` hears it changed.
|
|
172
|
+
this.dispatchEvent(new CustomEvent('change', { detail: { amount: this.#amount, reason: this.reason } }));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** The most the selected bet takes, in wallet units, or null when the
|
|
176
|
+
* session names none for it (protocol.js maxStakeFor). 0 when it names
|
|
177
|
+
* nothing can be staked for now (the coin has no rate). */
|
|
178
|
+
get maxStake() {
|
|
179
|
+
if (!this.#engine || !this.#bet) return null;
|
|
180
|
+
return maxStakeFor(this.#engine.config.limits, this.#bet.mode, this.#bet.bet);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Why the stake cannot be played now ({ code, message }), or null. An
|
|
184
|
+
* empty field is `min`: there is no free round (stake.js); `maxWin` is
|
|
185
|
+
* over the most the selected bet takes under the maximum win per round,
|
|
186
|
+
* and the message names that figure. When that figure is 0 nothing can
|
|
187
|
+
* be staked until the coin's rate is back, and the message is the
|
|
188
|
+
* Originals' "something went wrong": the server would answer every play
|
|
189
|
+
* with `unavailable`. */
|
|
190
|
+
get reason() {
|
|
191
|
+
const m = this.#money;
|
|
192
|
+
if (!m || !this.#engine) return null;
|
|
193
|
+
const limits = this.#engine.config.limits || {};
|
|
194
|
+
const most = this.maxStake;
|
|
195
|
+
const code = stakeReason(this.#amount, limits, this.#engine.balance, most);
|
|
196
|
+
if (code === 'balance') return { code, message: this.#words.blockOverBalance };
|
|
197
|
+
if (code === 'min') return { code, message: this.#say('blockUnderMin', leastStake(limits, m.walletDecimals)) };
|
|
198
|
+
if (code === 'max') return { code, message: this.#say('blockOverMax', limits.max) };
|
|
199
|
+
if (code === 'maxWin') return { code, message: most === 0 ? this.#words.apiInternalError : this.#say('blockOverMax', most) };
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
get disabled() {
|
|
204
|
+
return this.hasAttribute('disabled');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
set disabled(on) {
|
|
208
|
+
this.toggleAttribute('disabled', !!on);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
attributeChangedCallback() {
|
|
212
|
+
this.#render();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
get #words() {
|
|
216
|
+
return wordsFor(this.getAttribute('lang') || (this.#engine && this.#engine.config.locale) || document.documentElement.lang || navigator.language);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#say(key, amount) {
|
|
220
|
+
const m = this.#money;
|
|
221
|
+
return this.#words[key].replace('{amount}', m.format(amount)).replace('{currency}', m.unit);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
#onInput() {
|
|
225
|
+
const { input } = this.#els;
|
|
226
|
+
this.#typing = true;
|
|
227
|
+
let text = this.#money ? this.#money.filter(input) : input.value;
|
|
228
|
+
// No more decimals than the field shows: a stake of 1.239 shown as
|
|
229
|
+
// "1.24" would be a stake the player cannot see.
|
|
230
|
+
const dot = text.indexOf('.');
|
|
231
|
+
if (this.#money && dot >= 0 && text.length - dot - 1 > this.#money.decimals) {
|
|
232
|
+
text = text.slice(0, dot + (this.#money.decimals ? this.#money.decimals + 1 : 0));
|
|
233
|
+
input.value = text;
|
|
234
|
+
}
|
|
235
|
+
if (!this.#money || text.replace(/[\s,]/g, '') === '') return this.#render();
|
|
236
|
+
this.#set(this.#money.parse(text), { typing: true });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Left empty: no stake (the field says the minimum; the panel will not
|
|
240
|
+
// play it). Below the minimum: raised to it, and said so.
|
|
241
|
+
#onBlur() {
|
|
242
|
+
if (!this.#typing) return;
|
|
243
|
+
this.#typing = false;
|
|
244
|
+
if (this.#els.input.value.replace(/[\s,]/g, '') === '') return this.#set(0);
|
|
245
|
+
const min = this.#engine && this.#engine.config.limits && this.#engine.config.limits.min;
|
|
246
|
+
if (typeof min === 'number' && this.#amount > 0 && this.#amount < min) {
|
|
247
|
+
this.#set(min);
|
|
248
|
+
return this.#hint(this.#say('blockUnderMin', min));
|
|
249
|
+
}
|
|
250
|
+
this.#render();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
#chip(kind) {
|
|
254
|
+
const m = this.#money;
|
|
255
|
+
if (!m || this.disabled) return;
|
|
256
|
+
// The most the field offers: the coin's maximum, the selected bet's own
|
|
257
|
+
// ceiling under the maximum win, the balance — whichever is least.
|
|
258
|
+
const cap = stakeCeiling(this.#engine && this.#engine.config.limits, this.#engine.balance, this.maxStake);
|
|
259
|
+
if (kind === 'half') return this.#set(trunc(this.#amount / 2, m.walletDecimals));
|
|
260
|
+
const want = kind === 'double' ? this.#amount * 2 : cap;
|
|
261
|
+
if (Number.isFinite(want)) this.#set(trunc(Math.min(want, cap), m.walletDecimals));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
#set(amount, { typing = false } = {}) {
|
|
265
|
+
const m = this.#money;
|
|
266
|
+
const next = m ? trunc(amount, m.walletDecimals) : 0;
|
|
267
|
+
const before = this.reason;
|
|
268
|
+
this.#amount = next;
|
|
269
|
+
if (!typing) this.#typing = false;
|
|
270
|
+
this.#remember();
|
|
271
|
+
this.#render();
|
|
272
|
+
const reason = this.reason;
|
|
273
|
+
// A wrong stake shakes; an empty field is only not yet a stake.
|
|
274
|
+
if (reason && next > 0 && (!before || before.code !== reason.code)) this.#shake();
|
|
275
|
+
// Typing already sends 'input' out of the shadow root (it is composed);
|
|
276
|
+
// a chip or a script sets the amount without one.
|
|
277
|
+
if (!typing) this.dispatchEvent(new Event('input', { bubbles: true }));
|
|
278
|
+
this.dispatchEvent(new CustomEvent('change', { detail: { amount: next, reason } }));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** A note under the field for a few seconds (a refusal's message). */
|
|
282
|
+
note(text) {
|
|
283
|
+
this.#hint(String(text));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// A note for a few seconds: money never changes without a word.
|
|
287
|
+
#hint(text) {
|
|
288
|
+
this.#note = text;
|
|
289
|
+
clearTimeout(this.#hintTimer);
|
|
290
|
+
this.#hintTimer = setTimeout(() => {
|
|
291
|
+
this.#note = '';
|
|
292
|
+
this.#render();
|
|
293
|
+
}, 4000);
|
|
294
|
+
this.#render();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
#shake() {
|
|
298
|
+
const f = this.#els.field;
|
|
299
|
+
f.classList.remove('shake');
|
|
300
|
+
void f.offsetWidth;
|
|
301
|
+
f.classList.add('shake');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
#render() {
|
|
305
|
+
const e = this.#els;
|
|
306
|
+
if (!e) return;
|
|
307
|
+
const m = this.#money;
|
|
308
|
+
e.label.textContent = this.getAttribute('label') || this.#words.betAmount;
|
|
309
|
+
// No max chip for a bet that takes nothing right now (maxStake 0): there is no most to set.
|
|
310
|
+
e.chips[2].hidden = !this.hasAttribute('max-button') || this.maxStake === 0;
|
|
311
|
+
const off = this.disabled || !m;
|
|
312
|
+
e.input.readOnly = off;
|
|
313
|
+
for (const b of e.chips) b.disabled = off;
|
|
314
|
+
if (!m) return;
|
|
315
|
+
e.mark.src = m.mark;
|
|
316
|
+
e.mark.hidden = false;
|
|
317
|
+
e.sign.textContent = m.sign;
|
|
318
|
+
e.sign.hidden = !m.sign;
|
|
319
|
+
e.input.placeholder = m.format(0);
|
|
320
|
+
if (!this.#typing) e.input.value = this.#amount > 0 ? m.format(this.#amount) : '';
|
|
321
|
+
e.input.setAttribute('aria-label', `${e.label.textContent}, ${m.unit}`);
|
|
322
|
+
e.sign.classList.toggle('empty', e.input.value === '');
|
|
323
|
+
e.cap.textContent = m.caption(this.#amount) || '';
|
|
324
|
+
const reason = this.reason;
|
|
325
|
+
// Red is for a stake the server would refuse; an empty field just
|
|
326
|
+
// says what the least stake is.
|
|
327
|
+
const wrong = !!reason && this.#amount > 0;
|
|
328
|
+
e.field.classList.toggle('bad', wrong);
|
|
329
|
+
e.hint.classList.toggle('bad', wrong);
|
|
330
|
+
e.hint.textContent = reason ? reason.message : this.#note;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// The last stake, per game and coin, on this device: the field comes back
|
|
334
|
+
// with what the player last played, as the Originals' does.
|
|
335
|
+
#key() {
|
|
336
|
+
const c = this.#engine && this.#engine.config;
|
|
337
|
+
return c ? `hb-bet:${c.game}:${this.#money.coin}` : null;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
#remember() {
|
|
341
|
+
const k = this.#key();
|
|
342
|
+
try { if (k) localStorage.setItem(k, String(this.#amount)); } catch { /* storage is a convenience */ }
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
#recall() {
|
|
346
|
+
const k = this.#key();
|
|
347
|
+
let v = 0;
|
|
348
|
+
try { v = k ? Number(localStorage.getItem(k)) : 0; } catch { v = 0; }
|
|
349
|
+
return Number.isFinite(v) && v > 0 ? trunc(v, this.#money.walletDecimals) : 0;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export class BalanceField extends HTMLElement {
|
|
354
|
+
#engine = null;
|
|
355
|
+
#el;
|
|
356
|
+
|
|
357
|
+
constructor() {
|
|
358
|
+
super();
|
|
359
|
+
const root = this.attachShadow({ mode: 'open' });
|
|
360
|
+
sheet(root);
|
|
361
|
+
this.#el = document.createElement('span');
|
|
362
|
+
this.#el.className = 'balance';
|
|
363
|
+
this.#el.setAttribute('part', 'balance');
|
|
364
|
+
root.append(this.#el);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
connect(engine) {
|
|
368
|
+
this.#engine = engine;
|
|
369
|
+
engine.on('balance', () => this.#render());
|
|
370
|
+
this.#render();
|
|
371
|
+
return this;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
#render() {
|
|
375
|
+
const e = this.#engine;
|
|
376
|
+
if (!e) return;
|
|
377
|
+
const m = e.money;
|
|
378
|
+
const img = document.createElement('img');
|
|
379
|
+
img.className = 'mark';
|
|
380
|
+
img.alt = '';
|
|
381
|
+
img.src = m.mark;
|
|
382
|
+
const text = e.balance === null ? '—' : (m.sign || '') + m.format(e.balance);
|
|
383
|
+
this.#el.replaceChildren(img, document.createTextNode(text + (m.sign ? '' : ' ' + m.unit)));
|
|
384
|
+
this.title = m.caption(e.balance ?? 0) || '';
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (globalThis.customElements && !customElements.get('hb-bet-amount')) {
|
|
389
|
+
customElements.define('hb-bet-amount', BetAmountField);
|
|
390
|
+
customElements.define('hb-balance', BalanceField);
|
|
391
|
+
}
|