@nayan-ui/games 1.20.0 → 1.21.0

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.
Files changed (552) hide show
  1. package/lib/module/games/arrows/Arrows.js +1 -1
  2. package/lib/module/games/arrows/ArrowsScene.js +1 -1
  3. package/lib/module/games/balloon-blaster/BalloonBlaster.js +1 -1
  4. package/lib/module/games/balloon-blaster/BalloonBlasterScene.js +1 -1
  5. package/lib/module/games/bike-racing/BikeMeshes.js +1 -1
  6. package/lib/module/games/bike-racing/BikeRacing.js +1 -1
  7. package/lib/module/games/bike-racing/BikeRacingConstants.js +1 -1
  8. package/lib/module/games/bike-racing/BikeRacingScene.js +1 -1
  9. package/lib/module/games/block-blast/BlockBlast.js +1 -1
  10. package/lib/module/games/block-blast/BlockBlastScene.js +1 -1
  11. package/lib/module/games/block-breaker/BlockBreaker.js +1 -1
  12. package/lib/module/games/block-breaker/BlockBreakerScene.js +1 -1
  13. package/lib/module/games/bluk/Bluk.js +1 -1
  14. package/lib/module/games/bluk/BlukScene.js +1 -1
  15. package/lib/module/games/bubble-shooter/BubbleShooter.js +1 -1
  16. package/lib/module/games/bubble-shooter/BubbleShooterScene.js +1 -1
  17. package/lib/module/games/candy-crush/CandyCrush.js +1 -1
  18. package/lib/module/games/candy-crush/CandyCrushScene.js +1 -1
  19. package/lib/module/games/car-racing/CarMeshes.js +1 -1
  20. package/lib/module/games/car-racing/CarRacing.js +1 -1
  21. package/lib/module/games/car-racing/CarRacingScene.js +1 -1
  22. package/lib/module/games/chameleon-run/ChameleonRun.js +1 -1
  23. package/lib/module/games/chameleon-run/ChameleonRunBackdrop.js +1 -1
  24. package/lib/module/games/chameleon-run/ChameleonRunMeshes.js +1 -1
  25. package/lib/module/games/checkers/Checkers.js +1 -1
  26. package/lib/module/games/checkers/CheckersScene.js +1 -1
  27. package/lib/module/games/color-switch/ColorSwitch.js +1 -1
  28. package/lib/module/games/color-switch/ColorSwitchScene.js +1 -1
  29. package/lib/module/games/colors-sort/ColorsSort.js +1 -1
  30. package/lib/module/games/colors-sort/ColorsSortScene.js +1 -1
  31. package/lib/module/games/connect-em-all/ConnectEmAll.js +1 -1
  32. package/lib/module/games/connect-em-all/ConnectEmAllScene.js +1 -1
  33. package/lib/module/games/crossy-road/CrossyRoad.js +1 -1
  34. package/lib/module/games/crossy-road/CrossyRoadMeshes.js +1 -1
  35. package/lib/module/games/crossy-road/CrossyRoadPerson.js +1 -1
  36. package/lib/module/games/dino-jump/DinoJump.js +1 -1
  37. package/lib/module/games/dino-jump/DinoJumpScene.js +1 -1
  38. package/lib/module/games/dots-and-boxes/DotsAndBoxes.js +1 -1
  39. package/lib/module/games/dots-and-boxes/DotsAndBoxesScene.js +1 -1
  40. package/lib/module/games/falling-sticks/FallingSticks.js +1 -1
  41. package/lib/module/games/falling-sticks/FallingSticksScene.js +1 -1
  42. package/lib/module/games/find-different-number/FindDifferentNumber.js +1 -1
  43. package/lib/module/games/find-different-number/FindDifferentNumberScene.js +1 -1
  44. package/lib/module/games/flappy-bird/FlappyBird.js +1 -1
  45. package/lib/module/games/flappy-bird/FlappyBirdScene.js +1 -1
  46. package/lib/module/games/fruit-merger/FruitMerger.js +1 -1
  47. package/lib/module/games/fruit-merger/FruitMergerScene.js +1 -1
  48. package/lib/module/games/fruit-ninja/FruitNinja.js +1 -1
  49. package/lib/module/games/fruit-ninja/FruitNinjaScene.js +1 -1
  50. package/lib/module/games/fruit-ninja/FruitNinjaWorld.js +1 -1
  51. package/lib/module/games/game-2048/Game2048.js +1 -1
  52. package/lib/module/games/game-2048/Game2048Scene.js +1 -1
  53. package/lib/module/games/knife-hit/KnifeHit.js +1 -1
  54. package/lib/module/games/knife-hit/KnifeHitScene.js +1 -1
  55. package/lib/module/games/ludo-king/LudoKing.js +1 -1
  56. package/lib/module/games/ludo-king/LudoKingScene.js +1 -1
  57. package/lib/module/games/maze-runner/MazeRunner.js +1 -1
  58. package/lib/module/games/maze-runner/MazeRunnerScene.js +1 -1
  59. package/lib/module/games/memory-match/MemoryMatch.js +1 -1
  60. package/lib/module/games/memory-match/MemoryMatchScene.js +1 -1
  61. package/lib/module/games/mine-sweeper/MineSweeper.js +1 -1
  62. package/lib/module/games/mine-sweeper/MineSweeperScene.js +1 -1
  63. package/lib/module/games/mob-control/MobControl.js +1 -1
  64. package/lib/module/games/mob-control/MobControlGame.js +1 -1
  65. package/lib/module/games/mob-control/MobControlMeshes.js +1 -1
  66. package/lib/module/games/mob-control/MobControlRenderer.js +1 -1
  67. package/lib/module/games/mob-control/MobControlRules.js +1 -1
  68. package/lib/module/games/mob-control/MobControlScene.js +1 -1
  69. package/lib/module/games/nuts-and-bolts/NutsAndBolts.js +1 -1
  70. package/lib/module/games/nuts-and-bolts/NutsAndBoltsScene.js +1 -1
  71. package/lib/module/games/pac-man/PacMan.js +1 -1
  72. package/lib/module/games/pac-man/PacManGame.js +1 -1
  73. package/lib/module/games/pac-man/PacManScene.js +1 -1
  74. package/lib/module/games/paper-io/PaperIo.js +1 -1
  75. package/lib/module/games/paper-io/PaperIoMeshes.js +1 -1
  76. package/lib/module/games/paper-io/PaperIoScene.js +1 -1
  77. package/lib/module/games/penguin-run/PenguinMeshes.js +1 -1
  78. package/lib/module/games/penguin-run/PenguinRun.js +1 -1
  79. package/lib/module/games/penguin-run/PenguinRunScene.js +1 -1
  80. package/lib/module/games/perfect-circle/PerfectCircle.js +1 -1
  81. package/lib/module/games/perfect-circle/PerfectCircleScene.js +1 -1
  82. package/lib/module/games/pipe-connect/PipeConnect.js +1 -1
  83. package/lib/module/games/pipe-connect/PipeConnectScene.js +1 -1
  84. package/lib/module/games/play-with-tua/PlayWithTua.js +1 -1
  85. package/lib/module/games/popit-fidget/PopitFidget.js +1 -1
  86. package/lib/module/games/popit-fidget/PopitFidgetScene.js +1 -1
  87. package/lib/module/games/reversi/Reversi.js +1 -1
  88. package/lib/module/games/reversi/ReversiScene.js +1 -1
  89. package/lib/module/games/search-grid/SearchGrid.js +1 -1
  90. package/lib/module/games/search-grid/SearchGridScene.js +1 -1
  91. package/lib/module/games/shared/BackdropCard.js +1 -1
  92. package/lib/module/games/shared/EngineBackdrop.js +1 -1
  93. package/lib/module/games/shared/EngineBlasts.js +1 -1
  94. package/lib/module/games/shared/EngineBlocks.js +1 -1
  95. package/lib/module/games/shared/EngineDice.js +1 -1
  96. package/lib/module/games/shared/EngineFruits.js +1 -1
  97. package/lib/module/games/shared/EngineGameLoader.js +1 -1
  98. package/lib/module/games/shared/EngineGameScreen.js +1 -1
  99. package/lib/module/games/shared/EngineGlyphs.js +1 -1
  100. package/lib/module/games/shared/EngineHud.js +1 -1
  101. package/lib/module/games/shared/EngineMeadow.js +1 -1
  102. package/lib/module/games/shared/EngineTouchSurface.js +1 -1
  103. package/lib/module/games/shared/GameActivity.js +1 -1
  104. package/lib/module/games/shared/GameLoading.js +1 -1
  105. package/lib/module/games/shared/GameLoadingTransition.js +1 -1
  106. package/lib/module/games/sliding-numbers/SlidingNumbers.js +1 -1
  107. package/lib/module/games/sliding-numbers/SlidingNumbersScene.js +1 -1
  108. package/lib/module/games/snake-3d/Snake3D.js +1 -1
  109. package/lib/module/games/snake-3d/Snake3DScene.js +1 -1
  110. package/lib/module/games/snakes-and-ladders/SnakesAndLadders.js +1 -1
  111. package/lib/module/games/snakes-and-ladders/SnakesAndLaddersScene.js +1 -1
  112. package/lib/module/games/space-fighter/SpaceFighter.js +1 -1
  113. package/lib/module/games/space-fighter/SpaceFighterScene.js +1 -1
  114. package/lib/module/games/spider-solitaire/SpiderSolitaire.js +1 -1
  115. package/lib/module/games/spider-solitaire/SpiderSolitaireScene.js +1 -1
  116. package/lib/module/games/stack-tower/StackTower.js +1 -1
  117. package/lib/module/games/stack-tower/StackTowerScene.js +1 -1
  118. package/lib/module/games/sudoku/Sudoku.js +1 -1
  119. package/lib/module/games/sudoku/SudokuScene.js +1 -1
  120. package/lib/module/games/tangram/Tangram.js +1 -1
  121. package/lib/module/games/tangram/TangramScene.js +1 -1
  122. package/lib/module/games/tank-1990/Tank1990.js +1 -1
  123. package/lib/module/games/tank-1990/Tank1990Scene.js +1 -1
  124. package/lib/module/games/tic-tac-toe/TicTacToe.js +1 -1
  125. package/lib/module/games/tic-tac-toe/TicTacToeScene.js +1 -1
  126. package/lib/module/games/tile-home/TileHome.js +1 -1
  127. package/lib/module/games/tile-home/TileHomeScene.js +1 -1
  128. package/lib/module/games/treasure-hunt/TreasureHunt.js +1 -1
  129. package/lib/module/games/treasure-hunt/TreasureHuntConstants.js +1 -1
  130. package/lib/module/games/treasure-hunt/TreasureHuntModel.js +1 -1
  131. package/lib/module/games/treasure-hunt/TreasureHuntScene.js +1 -1
  132. package/lib/module/games/whack-a-mole/WhackAMole.js +1 -1
  133. package/lib/module/games/whack-a-mole/WhackAMoleScene.js +1 -1
  134. package/lib/module/helpers/GameSettingsModal.js +1 -1
  135. package/lib/module/helpers/GameTheme.js +1 -1
  136. package/package.json +1 -2
  137. package/src/assets/fonts/Inter-SemiBold.ttf +0 -0
  138. package/src/assets/models/explorer/explorer.glb +0 -0
  139. package/src/assets/models/explorer/params.json +0 -51
  140. package/src/assets/models/explorer/source.json +0 -222
  141. package/src/assets/models/play-with-tua/play-with-tua.glb +0 -0
  142. package/src/assets/models/play-with-tua/sources.json +0 -141
  143. package/src/assets/sfx/balloon_pop.wav +0 -0
  144. package/src/assets/sfx/blocked.wav +0 -0
  145. package/src/assets/sfx/boing.wav +0 -0
  146. package/src/assets/sfx/bonus.wav +0 -0
  147. package/src/assets/sfx/boom.wav +0 -0
  148. package/src/assets/sfx/bump.wav +0 -0
  149. package/src/assets/sfx/cannon.wav +0 -0
  150. package/src/assets/sfx/capture.wav +0 -0
  151. package/src/assets/sfx/card_flip.wav +0 -0
  152. package/src/assets/sfx/card_place.wav +0 -0
  153. package/src/assets/sfx/catch.wav +0 -0
  154. package/src/assets/sfx/chime.wav +0 -0
  155. package/src/assets/sfx/chomp.wav +0 -0
  156. package/src/assets/sfx/chomp_alt.wav +0 -0
  157. package/src/assets/sfx/clack.wav +0 -0
  158. package/src/assets/sfx/clang.wav +0 -0
  159. package/src/assets/sfx/clash.wav +0 -0
  160. package/src/assets/sfx/click.wav +0 -0
  161. package/src/assets/sfx/coin.wav +0 -0
  162. package/src/assets/sfx/combo.wav +0 -0
  163. package/src/assets/sfx/crash.wav +0 -0
  164. package/src/assets/sfx/crunch.wav +0 -0
  165. package/src/assets/sfx/dance.wav +0 -0
  166. package/src/assets/sfx/deselect.wav +0 -0
  167. package/src/assets/sfx/dice_roll.wav +0 -0
  168. package/src/assets/sfx/die.wav +0 -0
  169. package/src/assets/sfx/draw.wav +0 -0
  170. package/src/assets/sfx/drop.wav +0 -0
  171. package/src/assets/sfx/eat.wav +0 -0
  172. package/src/assets/sfx/engine.wav +0 -0
  173. package/src/assets/sfx/error.wav +0 -0
  174. package/src/assets/sfx/flag.wav +0 -0
  175. package/src/assets/sfx/flap.wav +0 -0
  176. package/src/assets/sfx/flip_disc.wav +0 -0
  177. package/src/assets/sfx/footstep_run.wav +0 -0
  178. package/src/assets/sfx/footstep_walk.wav +0 -0
  179. package/src/assets/sfx/game_over.wav +0 -0
  180. package/src/assets/sfx/gate.wav +0 -0
  181. package/src/assets/sfx/hit.wav +0 -0
  182. package/src/assets/sfx/hop.wav +0 -0
  183. package/src/assets/sfx/hurt.wav +0 -0
  184. package/src/assets/sfx/invalid.wav +0 -0
  185. package/src/assets/sfx/jump.wav +0 -0
  186. package/src/assets/sfx/ladder.wav +0 -0
  187. package/src/assets/sfx/land.wav +0 -0
  188. package/src/assets/sfx/laugh.wav +0 -0
  189. package/src/assets/sfx/lose.wav +0 -0
  190. package/src/assets/sfx/merge.wav +0 -0
  191. package/src/assets/sfx/miss.wav +0 -0
  192. package/src/assets/sfx/nitro.wav +0 -0
  193. package/src/assets/sfx/pew.wav +0 -0
  194. package/src/assets/sfx/place.wav +0 -0
  195. package/src/assets/sfx/pling.wav +0 -0
  196. package/src/assets/sfx/point.wav +0 -0
  197. package/src/assets/sfx/pop.wav +0 -0
  198. package/src/assets/sfx/pop_soft.wav +0 -0
  199. package/src/assets/sfx/power_up.wav +0 -0
  200. package/src/assets/sfx/reveal.wav +0 -0
  201. package/src/assets/sfx/robot.wav +0 -0
  202. package/src/assets/sfx/rotate.wav +0 -0
  203. package/src/assets/sfx/run_steps.wav +0 -0
  204. package/src/assets/sfx/select.wav +0 -0
  205. package/src/assets/sfx/shuffle.wav +0 -0
  206. package/src/assets/sfx/six.wav +0 -0
  207. package/src/assets/sfx/slice.wav +0 -0
  208. package/src/assets/sfx/slide.wav +0 -0
  209. package/src/assets/sfx/snake.wav +0 -0
  210. package/src/assets/sfx/snap.wav +0 -0
  211. package/src/assets/sfx/splash.wav +0 -0
  212. package/src/assets/sfx/star.wav +0 -0
  213. package/src/assets/sfx/start.wav +0 -0
  214. package/src/assets/sfx/swoosh.wav +0 -0
  215. package/src/assets/sfx/tap.wav +0 -0
  216. package/src/assets/sfx/thud.wav +0 -0
  217. package/src/assets/sfx/thunk.wav +0 -0
  218. package/src/assets/sfx/tick.wav +0 -0
  219. package/src/assets/sfx/time_up.wav +0 -0
  220. package/src/assets/sfx/tock.wav +0 -0
  221. package/src/assets/sfx/walk_steps.wav +0 -0
  222. package/src/assets/sfx/warning.wav +0 -0
  223. package/src/assets/sfx/whack.wav +0 -0
  224. package/src/assets/sfx/whoosh.wav +0 -0
  225. package/src/assets/sfx/win.wav +0 -0
  226. package/src/assets/sfx/zap.wav +0 -0
  227. package/src/assets/textures/ad_bolt.png +0 -0
  228. package/src/assets/textures/ad_bubbles.png +0 -0
  229. package/src/assets/textures/ad_chevrons.png +0 -0
  230. package/src/assets/textures/asphalt.png +0 -0
  231. package/src/assets/textures/bark.jpg +0 -0
  232. package/src/assets/textures/explorer-atlas.png +0 -0
  233. package/src/assets/textures/facade.png +0 -0
  234. package/src/assets/textures/grass.png +0 -0
  235. package/src/assets/textures/leaves.png +0 -0
  236. package/src/assets/textures/limestone.jpg +0 -0
  237. package/src/assets/textures/meadow.jpg +0 -0
  238. package/src/assets/textures/nature-sources.json +0 -87
  239. package/src/assets/textures/pine.png +0 -0
  240. package/src/assets/textures/river.jpg +0 -0
  241. package/src/assets/textures/soft_glow.png +0 -0
  242. package/src/games/arrows/Arrows.tsx +0 -69
  243. package/src/games/arrows/ArrowsConstants.ts +0 -107
  244. package/src/games/arrows/ArrowsGame.ts +0 -735
  245. package/src/games/arrows/ArrowsScene.ts +0 -395
  246. package/src/games/balloon-blaster/BalloonBlaster.tsx +0 -75
  247. package/src/games/balloon-blaster/BalloonBlasterConstants.ts +0 -79
  248. package/src/games/balloon-blaster/BalloonBlasterGame.ts +0 -287
  249. package/src/games/balloon-blaster/BalloonBlasterPhysics.ts +0 -95
  250. package/src/games/balloon-blaster/BalloonBlasterScene.ts +0 -225
  251. package/src/games/bike-racing/BikeMeshes.ts +0 -273
  252. package/src/games/bike-racing/BikeRacing.tsx +0 -211
  253. package/src/games/bike-racing/BikeRacingConstants.ts +0 -103
  254. package/src/games/bike-racing/BikeRacingGame.ts +0 -557
  255. package/src/games/bike-racing/BikeRacingScene.ts +0 -119
  256. package/src/games/block-blast/BlockBlast.tsx +0 -73
  257. package/src/games/block-blast/BlockBlastConstants.ts +0 -179
  258. package/src/games/block-blast/BlockBlastGame.ts +0 -471
  259. package/src/games/block-blast/BlockBlastScene.ts +0 -156
  260. package/src/games/block-breaker/BlockBreaker.tsx +0 -79
  261. package/src/games/block-breaker/BlockBreakerConstants.ts +0 -139
  262. package/src/games/block-breaker/BlockBreakerGame.ts +0 -459
  263. package/src/games/block-breaker/BlockBreakerScene.ts +0 -257
  264. package/src/games/bluk/Bluk.tsx +0 -82
  265. package/src/games/bluk/BlukConstants.ts +0 -121
  266. package/src/games/bluk/BlukGame.ts +0 -323
  267. package/src/games/bluk/BlukRules.ts +0 -154
  268. package/src/games/bluk/BlukScene.ts +0 -514
  269. package/src/games/bubble-shooter/BubbleShooter.tsx +0 -76
  270. package/src/games/bubble-shooter/BubbleShooterConstants.ts +0 -103
  271. package/src/games/bubble-shooter/BubbleShooterGame.ts +0 -459
  272. package/src/games/bubble-shooter/BubbleShooterScene.ts +0 -158
  273. package/src/games/candy-crush/CandyCrush.tsx +0 -77
  274. package/src/games/candy-crush/CandyCrushConstants.ts +0 -94
  275. package/src/games/candy-crush/CandyCrushGame.ts +0 -477
  276. package/src/games/candy-crush/CandyCrushScene.ts +0 -209
  277. package/src/games/car-racing/CarMeshes.ts +0 -440
  278. package/src/games/car-racing/CarRacing.tsx +0 -211
  279. package/src/games/car-racing/CarRacingConstants.ts +0 -167
  280. package/src/games/car-racing/CarRacingFacade.ts +0 -40
  281. package/src/games/car-racing/CarRacingGame.ts +0 -539
  282. package/src/games/car-racing/CarRacingScene.ts +0 -1026
  283. package/src/games/car-racing/CarRacingScenery.ts +0 -265
  284. package/src/games/chameleon-run/ChameleonRun.tsx +0 -96
  285. package/src/games/chameleon-run/ChameleonRunBackdrop.ts +0 -186
  286. package/src/games/chameleon-run/ChameleonRunConstants.ts +0 -47
  287. package/src/games/chameleon-run/ChameleonRunGame.ts +0 -309
  288. package/src/games/chameleon-run/ChameleonRunMeshes.ts +0 -305
  289. package/src/games/chameleon-run/ChameleonRunRules.ts +0 -252
  290. package/src/games/chameleon-run/ChameleonRunScene.ts +0 -310
  291. package/src/games/checkers/Checkers.tsx +0 -83
  292. package/src/games/checkers/CheckersAI.ts +0 -255
  293. package/src/games/checkers/CheckersConstants.ts +0 -68
  294. package/src/games/checkers/CheckersGame.ts +0 -322
  295. package/src/games/checkers/CheckersRules.ts +0 -167
  296. package/src/games/checkers/CheckersScene.ts +0 -308
  297. package/src/games/color-switch/ColorSwitch.tsx +0 -75
  298. package/src/games/color-switch/ColorSwitchConstants.ts +0 -61
  299. package/src/games/color-switch/ColorSwitchGame.ts +0 -329
  300. package/src/games/color-switch/ColorSwitchScene.ts +0 -220
  301. package/src/games/colors-sort/ColorsSort.tsx +0 -82
  302. package/src/games/colors-sort/ColorsSortConstants.ts +0 -119
  303. package/src/games/colors-sort/ColorsSortGame.ts +0 -246
  304. package/src/games/colors-sort/ColorsSortScene.ts +0 -315
  305. package/src/games/connect-em-all/ConnectEmAll.tsx +0 -70
  306. package/src/games/connect-em-all/ConnectEmAllConstants.ts +0 -77
  307. package/src/games/connect-em-all/ConnectEmAllGame.ts +0 -401
  308. package/src/games/connect-em-all/ConnectEmAllScene.ts +0 -235
  309. package/src/games/crossy-road/CrossyRoad.tsx +0 -74
  310. package/src/games/crossy-road/CrossyRoadCamera.ts +0 -59
  311. package/src/games/crossy-road/CrossyRoadConstants.ts +0 -132
  312. package/src/games/crossy-road/CrossyRoadGame.ts +0 -293
  313. package/src/games/crossy-road/CrossyRoadMeshes.ts +0 -195
  314. package/src/games/crossy-road/CrossyRoadPerson.ts +0 -198
  315. package/src/games/crossy-road/CrossyRoadRules.ts +0 -99
  316. package/src/games/crossy-road/CrossyRoadScene.ts +0 -355
  317. package/src/games/dino-jump/DinoJump.tsx +0 -77
  318. package/src/games/dino-jump/DinoJumpConstants.ts +0 -80
  319. package/src/games/dino-jump/DinoJumpGame.ts +0 -344
  320. package/src/games/dino-jump/DinoJumpScene.ts +0 -686
  321. package/src/games/dots-and-boxes/DotsAndBoxes.tsx +0 -83
  322. package/src/games/dots-and-boxes/DotsAndBoxesConstants.ts +0 -77
  323. package/src/games/dots-and-boxes/DotsAndBoxesGame.ts +0 -271
  324. package/src/games/dots-and-boxes/DotsAndBoxesScene.ts +0 -249
  325. package/src/games/falling-sticks/FallingSticks.tsx +0 -72
  326. package/src/games/falling-sticks/FallingSticksConstants.ts +0 -62
  327. package/src/games/falling-sticks/FallingSticksGame.ts +0 -220
  328. package/src/games/falling-sticks/FallingSticksRules.ts +0 -145
  329. package/src/games/falling-sticks/FallingSticksScene.ts +0 -245
  330. package/src/games/find-different-number/FindDifferentNumber.tsx +0 -74
  331. package/src/games/find-different-number/FindDifferentNumberConstants.ts +0 -74
  332. package/src/games/find-different-number/FindDifferentNumberGame.ts +0 -174
  333. package/src/games/find-different-number/FindDifferentNumberRules.ts +0 -43
  334. package/src/games/find-different-number/FindDifferentNumberScene.ts +0 -193
  335. package/src/games/flappy-bird/FlappyBird.tsx +0 -73
  336. package/src/games/flappy-bird/FlappyBirdConstants.ts +0 -95
  337. package/src/games/flappy-bird/FlappyBirdGame.ts +0 -375
  338. package/src/games/flappy-bird/FlappyBirdScene.ts +0 -630
  339. package/src/games/fruit-merger/FruitMerger.tsx +0 -73
  340. package/src/games/fruit-merger/FruitMergerConstants.ts +0 -102
  341. package/src/games/fruit-merger/FruitMergerGame.ts +0 -285
  342. package/src/games/fruit-merger/FruitMergerRules.ts +0 -286
  343. package/src/games/fruit-merger/FruitMergerScene.ts +0 -498
  344. package/src/games/fruit-ninja/FruitNinja.tsx +0 -129
  345. package/src/games/fruit-ninja/FruitNinjaConstants.ts +0 -107
  346. package/src/games/fruit-ninja/FruitNinjaGame.ts +0 -391
  347. package/src/games/fruit-ninja/FruitNinjaScene.ts +0 -319
  348. package/src/games/fruit-ninja/FruitNinjaWorld.ts +0 -17
  349. package/src/games/game-2048/Game2048.tsx +0 -84
  350. package/src/games/game-2048/Game2048Constants.ts +0 -68
  351. package/src/games/game-2048/Game2048Game.ts +0 -181
  352. package/src/games/game-2048/Game2048Rules.ts +0 -101
  353. package/src/games/game-2048/Game2048Scene.ts +0 -206
  354. package/src/games/knife-hit/KnifeHit.tsx +0 -78
  355. package/src/games/knife-hit/KnifeHitConstants.ts +0 -89
  356. package/src/games/knife-hit/KnifeHitGame.ts +0 -264
  357. package/src/games/knife-hit/KnifeHitScene.ts +0 -360
  358. package/src/games/ludo-king/LudoKing.tsx +0 -86
  359. package/src/games/ludo-king/LudoKingConstants.ts +0 -212
  360. package/src/games/ludo-king/LudoKingGame.ts +0 -326
  361. package/src/games/ludo-king/LudoKingRules.ts +0 -143
  362. package/src/games/ludo-king/LudoKingScene.ts +0 -397
  363. package/src/games/maze-runner/MazeRunner.tsx +0 -88
  364. package/src/games/maze-runner/MazeRunnerConstants.ts +0 -65
  365. package/src/games/maze-runner/MazeRunnerGame.ts +0 -176
  366. package/src/games/maze-runner/MazeRunnerRules.ts +0 -161
  367. package/src/games/maze-runner/MazeRunnerScene.ts +0 -245
  368. package/src/games/memory-match/MemoryMatch.tsx +0 -77
  369. package/src/games/memory-match/MemoryMatchConstants.ts +0 -105
  370. package/src/games/memory-match/MemoryMatchGame.ts +0 -222
  371. package/src/games/memory-match/MemoryMatchRules.ts +0 -38
  372. package/src/games/memory-match/MemoryMatchScene.ts +0 -268
  373. package/src/games/mine-sweeper/MineSweeper.tsx +0 -77
  374. package/src/games/mine-sweeper/MineSweeperConstants.ts +0 -94
  375. package/src/games/mine-sweeper/MineSweeperGame.ts +0 -234
  376. package/src/games/mine-sweeper/MineSweeperRules.ts +0 -67
  377. package/src/games/mine-sweeper/MineSweeperScene.ts +0 -262
  378. package/src/games/mob-control/MobControl.tsx +0 -73
  379. package/src/games/mob-control/MobControlConstants.ts +0 -106
  380. package/src/games/mob-control/MobControlGame.ts +0 -176
  381. package/src/games/mob-control/MobControlMeshes.ts +0 -85
  382. package/src/games/mob-control/MobControlRenderer.ts +0 -108
  383. package/src/games/mob-control/MobControlRules.ts +0 -522
  384. package/src/games/mob-control/MobControlScene.ts +0 -399
  385. package/src/games/number-search/NumberSearch.tsx +0 -18
  386. package/src/games/number-search/NumberSearchNumbers.ts +0 -623
  387. package/src/games/nuts-and-bolts/NutsAndBolts.tsx +0 -85
  388. package/src/games/nuts-and-bolts/NutsAndBoltsConstants.ts +0 -75
  389. package/src/games/nuts-and-bolts/NutsAndBoltsGame.ts +0 -298
  390. package/src/games/nuts-and-bolts/NutsAndBoltsRules.ts +0 -474
  391. package/src/games/nuts-and-bolts/NutsAndBoltsScene.ts +0 -498
  392. package/src/games/pac-man/PacMan.tsx +0 -86
  393. package/src/games/pac-man/PacManConstants.ts +0 -169
  394. package/src/games/pac-man/PacManGame.ts +0 -406
  395. package/src/games/pac-man/PacManRules.ts +0 -255
  396. package/src/games/pac-man/PacManScene.ts +0 -387
  397. package/src/games/paper-io/PaperIo.tsx +0 -91
  398. package/src/games/paper-io/PaperIoConstants.ts +0 -86
  399. package/src/games/paper-io/PaperIoGame.ts +0 -193
  400. package/src/games/paper-io/PaperIoMeshes.ts +0 -233
  401. package/src/games/paper-io/PaperIoRules.ts +0 -315
  402. package/src/games/paper-io/PaperIoScene.ts +0 -310
  403. package/src/games/penguin-run/PenguinMeshes.ts +0 -269
  404. package/src/games/penguin-run/PenguinModel.ts +0 -712
  405. package/src/games/penguin-run/PenguinRun.tsx +0 -77
  406. package/src/games/penguin-run/PenguinRunConstants.ts +0 -69
  407. package/src/games/penguin-run/PenguinRunGame.ts +0 -406
  408. package/src/games/penguin-run/PenguinRunScene.ts +0 -949
  409. package/src/games/perfect-circle/PerfectCircle.tsx +0 -74
  410. package/src/games/perfect-circle/PerfectCircleConstants.ts +0 -52
  411. package/src/games/perfect-circle/PerfectCircleGame.ts +0 -153
  412. package/src/games/perfect-circle/PerfectCircleRules.ts +0 -111
  413. package/src/games/perfect-circle/PerfectCircleScene.ts +0 -176
  414. package/src/games/pipe-connect/PipeConnect.tsx +0 -69
  415. package/src/games/pipe-connect/PipeConnectConstants.ts +0 -101
  416. package/src/games/pipe-connect/PipeConnectGame.ts +0 -179
  417. package/src/games/pipe-connect/PipeConnectRules.ts +0 -326
  418. package/src/games/pipe-connect/PipeConnectScene.ts +0 -271
  419. package/src/games/play-with-tua/PlayWithTua.tsx +0 -123
  420. package/src/games/play-with-tua/PlayWithTuaConstants.ts +0 -70
  421. package/src/games/play-with-tua/PlayWithTuaGame.ts +0 -167
  422. package/src/games/play-with-tua/PlayWithTuaModel.ts +0 -42
  423. package/src/games/play-with-tua/PlayWithTuaScene.ts +0 -36
  424. package/src/games/popit-fidget/PopitFidget.tsx +0 -72
  425. package/src/games/popit-fidget/PopitFidgetConstants.ts +0 -71
  426. package/src/games/popit-fidget/PopitFidgetGame.ts +0 -148
  427. package/src/games/popit-fidget/PopitFidgetScene.ts +0 -242
  428. package/src/games/reversi/Reversi.tsx +0 -94
  429. package/src/games/reversi/ReversiConstants.ts +0 -67
  430. package/src/games/reversi/ReversiGame.ts +0 -266
  431. package/src/games/reversi/ReversiRules.ts +0 -176
  432. package/src/games/reversi/ReversiScene.ts +0 -240
  433. package/src/games/search-grid/SearchGrid.tsx +0 -92
  434. package/src/games/search-grid/SearchGridConstants.ts +0 -178
  435. package/src/games/search-grid/SearchGridGame.ts +0 -209
  436. package/src/games/search-grid/SearchGridScene.ts +0 -301
  437. package/src/games/shared/BackdropCard.tsx +0 -54
  438. package/src/games/shared/EngineBackdrop.ts +0 -300
  439. package/src/games/shared/EngineBlasts.ts +0 -136
  440. package/src/games/shared/EngineBlocks.ts +0 -91
  441. package/src/games/shared/EngineControlText.ts +0 -17
  442. package/src/games/shared/EngineDice.ts +0 -250
  443. package/src/games/shared/EngineFootprint.ts +0 -35
  444. package/src/games/shared/EngineFruits.ts +0 -1435
  445. package/src/games/shared/EngineGameLoader.tsx +0 -88
  446. package/src/games/shared/EngineGameScreen.tsx +0 -325
  447. package/src/games/shared/EngineGameWidth.ts +0 -3
  448. package/src/games/shared/EngineGameWidth.web.ts +0 -3
  449. package/src/games/shared/EngineGlyphs.ts +0 -178
  450. package/src/games/shared/EngineHud.ts +0 -815
  451. package/src/games/shared/EngineLoading.ts +0 -477
  452. package/src/games/shared/EngineMeadow.ts +0 -725
  453. package/src/games/shared/EngineMeshCache.ts +0 -15
  454. package/src/games/shared/EngineMeshes.ts +0 -10
  455. package/src/games/shared/EngineRacingChase.ts +0 -21
  456. package/src/games/shared/EngineRacingHandling.ts +0 -48
  457. package/src/games/shared/EngineReadyContext.ts +0 -4
  458. package/src/games/shared/EngineTilt.ts +0 -44
  459. package/src/games/shared/EngineTouchSurface.tsx +0 -103
  460. package/src/games/shared/EngineUtils.ts +0 -52
  461. package/src/games/shared/GameActivity.ts +0 -13
  462. package/src/games/shared/GameLoading.tsx +0 -202
  463. package/src/games/shared/GameLoadingTransition.tsx +0 -61
  464. package/src/games/shared/ProceduralCacheVersion.ts +0 -2
  465. package/src/games/shared/useEngineGame.ts +0 -240
  466. package/src/games/shared/useGameResource.ts +0 -21
  467. package/src/games/shared/useLiveState.ts +0 -58
  468. package/src/games/sliding-numbers/SlidingNumbers.tsx +0 -71
  469. package/src/games/sliding-numbers/SlidingNumbersConstants.ts +0 -65
  470. package/src/games/sliding-numbers/SlidingNumbersGame.ts +0 -192
  471. package/src/games/sliding-numbers/SlidingNumbersRules.ts +0 -79
  472. package/src/games/sliding-numbers/SlidingNumbersScene.ts +0 -193
  473. package/src/games/snake-3d/Snake3D.tsx +0 -76
  474. package/src/games/snake-3d/Snake3DConstants.ts +0 -61
  475. package/src/games/snake-3d/Snake3DGame.ts +0 -192
  476. package/src/games/snake-3d/Snake3DMotion.ts +0 -22
  477. package/src/games/snake-3d/Snake3DRules.ts +0 -116
  478. package/src/games/snake-3d/Snake3DScene.ts +0 -322
  479. package/src/games/snakes-and-ladders/SnakesAndLadders.tsx +0 -87
  480. package/src/games/snakes-and-ladders/SnakesAndLaddersConstants.ts +0 -136
  481. package/src/games/snakes-and-ladders/SnakesAndLaddersGame.ts +0 -310
  482. package/src/games/snakes-and-ladders/SnakesAndLaddersScene.ts +0 -515
  483. package/src/games/space-fighter/SpaceFighter.tsx +0 -74
  484. package/src/games/space-fighter/SpaceFighterConstants.ts +0 -86
  485. package/src/games/space-fighter/SpaceFighterGame.ts +0 -292
  486. package/src/games/space-fighter/SpaceFighterScene.ts +0 -550
  487. package/src/games/spider-solitaire/SpiderSolitaire.tsx +0 -80
  488. package/src/games/spider-solitaire/SpiderSolitaireConstants.ts +0 -107
  489. package/src/games/spider-solitaire/SpiderSolitaireGame.ts +0 -387
  490. package/src/games/spider-solitaire/SpiderSolitaireRules.ts +0 -123
  491. package/src/games/spider-solitaire/SpiderSolitaireScene.ts +0 -352
  492. package/src/games/stack-tower/StackTower.tsx +0 -76
  493. package/src/games/stack-tower/StackTowerConstants.ts +0 -74
  494. package/src/games/stack-tower/StackTowerGame.ts +0 -236
  495. package/src/games/stack-tower/StackTowerScene.ts +0 -186
  496. package/src/games/sudoku/Sudoku.tsx +0 -73
  497. package/src/games/sudoku/SudokuConstants.ts +0 -127
  498. package/src/games/sudoku/SudokuGame.ts +0 -250
  499. package/src/games/sudoku/SudokuRules.ts +0 -125
  500. package/src/games/sudoku/SudokuScene.ts +0 -304
  501. package/src/games/tangram/Tangram.tsx +0 -79
  502. package/src/games/tangram/TangramConstants.ts +0 -64
  503. package/src/games/tangram/TangramGame.ts +0 -356
  504. package/src/games/tangram/TangramRules.ts +0 -409
  505. package/src/games/tangram/TangramScene.ts +0 -203
  506. package/src/games/tank-1990/Tank1990.tsx +0 -78
  507. package/src/games/tank-1990/Tank1990Constants.ts +0 -81
  508. package/src/games/tank-1990/Tank1990Game.ts +0 -331
  509. package/src/games/tank-1990/Tank1990Rules.ts +0 -390
  510. package/src/games/tank-1990/Tank1990Scene.ts +0 -434
  511. package/src/games/tic-tac-toe/TicTacToe.tsx +0 -98
  512. package/src/games/tic-tac-toe/TicTacToeConstants.ts +0 -69
  513. package/src/games/tic-tac-toe/TicTacToeGame.ts +0 -200
  514. package/src/games/tic-tac-toe/TicTacToeRules.ts +0 -103
  515. package/src/games/tic-tac-toe/TicTacToeScene.ts +0 -245
  516. package/src/games/tile-home/TileHome.tsx +0 -92
  517. package/src/games/tile-home/TileHomeConstants.ts +0 -139
  518. package/src/games/tile-home/TileHomeGame.ts +0 -435
  519. package/src/games/tile-home/TileHomeScene.ts +0 -154
  520. package/src/games/treasure-hunt/TreasureHunt.tsx +0 -206
  521. package/src/games/treasure-hunt/TreasureHuntConstants.ts +0 -266
  522. package/src/games/treasure-hunt/TreasureHuntControls.ts +0 -280
  523. package/src/games/treasure-hunt/TreasureHuntGame.ts +0 -402
  524. package/src/games/treasure-hunt/TreasureHuntMeshes.ts +0 -422
  525. package/src/games/treasure-hunt/TreasureHuntModel.ts +0 -506
  526. package/src/games/treasure-hunt/TreasureHuntRules.ts +0 -1210
  527. package/src/games/treasure-hunt/TreasureHuntScene.ts +0 -586
  528. package/src/games/whack-a-mole/WhackAMole.tsx +0 -79
  529. package/src/games/whack-a-mole/WhackAMoleConstants.ts +0 -94
  530. package/src/games/whack-a-mole/WhackAMoleGame.ts +0 -233
  531. package/src/games/whack-a-mole/WhackAMoleScene.ts +0 -349
  532. package/src/games/word-search/WordSearch.tsx +0 -18
  533. package/src/games/word-search/WordSearchWords.ts +0 -1642
  534. package/src/helpers/GamePatternData.ts +0 -120
  535. package/src/helpers/GameSettingsModal.tsx +0 -384
  536. package/src/helpers/GameTheme.tsx +0 -37
  537. package/src/helpers/IconPaths.ts +0 -6
  538. package/src/helpers/index.ts +0 -2
  539. package/src/index.tsx +0 -60
  540. package/src/services/GameAssetLoader.ts +0 -111
  541. package/src/services/GameAssets.ts +0 -149
  542. package/src/services/GameLoaders.ts +0 -93
  543. package/src/services/GamesConstants.ts +0 -193
  544. package/src/services/GamesMetadata.ts +0 -711
  545. package/src/services/GamesService.ts +0 -116
  546. package/src/services/HapticsService.ts +0 -48
  547. package/src/services/ScoringService.ts +0 -19
  548. package/src/services/SfxSources.ts +0 -81
  549. package/src/services/SoundsService.ts +0 -582
  550. package/src/services/ThemeService.ts +0 -281
  551. package/src/services/UtilsService.ts +0 -134
  552. package/src/services/index.ts +0 -6
@@ -1,815 +0,0 @@
1
- // Playtura's scoreboard and game controls drawn by the engine, as a GameView `overlay`: the same panel,
2
- // columns, start/stop button and trophy/share buttons as ScoreBoardContainer and GameControlButton, but
3
- // rendered in Rust with the scene, so React re-renders nothing while a game runs. Overlay space is in
4
- // points from the view's top-left, y up (screen y is negated), z toward the viewer.
5
- import { type Color, type Entity, extrude, type Font, loadFont, measureText, type Model, Path, World } from '@nayan-ui/engine';
6
- import { PLAY_PATH, SHARE_PATH, TROPHY_PATH } from '../../helpers/IconPaths';
7
- import type { GameTheme } from '../../services/ThemeService';
8
- import { formatTime, getGameDimensions } from '../../services/UtilsService';
9
- import { cardBackdropMesh } from './EngineBackdrop';
10
- import { fitControlSubtitle } from './EngineControlText';
11
- import { cacheGameAsset, createGameMesh as createShape } from './EngineMeshes';
12
- import { gameWidthFor, toColor } from './EngineUtils';
13
-
14
- const CAPACITY = 900;
15
- const BOARD_PAD_V = 14;
16
- const BOARD_RADIUS = 18;
17
- // Font sizes and letter spacing in points, as ScoreBoardContainer / GameControlButton / GameOverModal
18
- const LABEL_SIZE = 12;
19
- const VALUE_SIZE = 24;
20
- const LABEL_SPACING = 1.2;
21
- const BOARD_HEIGHT = BOARD_PAD_V * 2 + 12 + 4 + 24;
22
- const ROW_PAD_V = 20;
23
- const ROW_PAD_H = 16;
24
- const ROW_GAP = 20;
25
- const MAIN_HEIGHT = 60;
26
- const ICON_SIZE = 56;
27
- const BUTTON_TEXT = 16;
28
- const BUTTON_SUBTEXT = 10;
29
- const PULSE = 1.05;
30
- const PRESSED = 0.95;
31
- // The game-over card (GameOverModal's layout)
32
- const Z_OVER = 40;
33
- const CARD_PAD = 20;
34
- const TROPHY = 48;
35
- const TITLE_SIZE = 28;
36
- const MESSAGE_SIZE = 14;
37
- const SCORE_LABEL_SIZE = 13;
38
- const SCORE_SIZE = 44;
39
- const PROMPT_SIZE = 15;
40
- const PLAY_SIZE = 17;
41
- const SHARE_SIZE = 16;
42
- const RESULT_BUTTON = 52;
43
- const COUNT_DELAY = 0.2;
44
- const COUNT_TIME = 0.9;
45
- const SHARE_PROMPT = 'Share your goals with your family and friends and challenge them to beat your score!';
46
-
47
- /** A scoreboard column: text (optionally colored), or `lives` drawn as hearts. */
48
- export type HudColumn = { label: string; value: string; color?: string; valueColor?: string; lives?: { count: number; max: number } };
49
-
50
- /** Top edge of the scoreboard; `offset` is the height of the host app's header, 0 when there is none. */
51
- export const getScoreBoardTop = (offset = 0) => (offset > 0 ? offset + 15 : 15);
52
- /** Insets for a React touch surface over the scene, leaving the native HUD's controls reachable. */
53
- export const getSceneInputInsets = (offset = 0) => ({
54
- top: getScoreBoardTop(offset) + BOARD_HEIGHT + 10,
55
- bottom: ROW_PAD_V + MAIN_HEIGHT + 10 + 5
56
- });
57
-
58
- /** The usual columns. */
59
- export const scoreColumn = (score: number, label = 'SCORE', format: (n: number) => string = (n) => n.toLocaleString()): HudColumn => ({
60
- label,
61
- value: format(score)
62
- });
63
- /** Time as m:ss, red at or under `lowAt` seconds (0: only at zero). */
64
- export const timerColumn = (seconds: number, theme: GameTheme, lowAt = 30, label = 'TIMER'): HudColumn => {
65
- const s = Math.max(0, Math.ceil(seconds));
66
- return { label, value: formatTime(s), valueColor: s <= lowAt ? theme.DANGER : undefined };
67
- };
68
- export const livesColumn = (count: number, max: number, label = 'LIVES'): HudColumn => ({ label, value: `${count}/${max}`, lives: { count, max } });
69
- export const textColumn = (label: string, value: string | number, color?: string): HudColumn => ({ label, value: String(value), color });
70
-
71
- const HEART_PATH =
72
- 'M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z';
73
- const HEART_SIZE = 20;
74
- const HEART_GAP = 2;
75
- const HEART_ON = '#E53935';
76
- const HEART_OFF = '#2B2B2B';
77
- export type HudButton = 'main' | 'leaderboard' | 'share' | 'choice';
78
- export type HudActions = { onStart: () => void; onStop: () => void; onShare?: () => void; onShowLeaderboard?: () => void };
79
-
80
- type Rect = { id: HudButton; x: number; y: number; w: number; h: number; root: Entity; action?: () => void };
81
-
82
- export type GameOverCard = {
83
- title: string;
84
- message?: string;
85
- scoreLabel: string;
86
- score: number;
87
- formatter?: (score: number) => string;
88
- buttonText: string;
89
- onPlayAgain: () => void;
90
- onShare?: () => void;
91
- };
92
- export interface HudChoice {
93
- id: string;
94
- label: string;
95
- selected?: boolean;
96
- onPress: () => void;
97
- }
98
-
99
- /** Events coalesce until the next engine frame; consuming them never triggers a React render. */
100
- export interface HudFeedback {
101
- flash?: boolean;
102
- message?: string;
103
- }
104
-
105
- type Over = {
106
- card: GameOverCard;
107
- root: Entity;
108
- scoreParent: Entity;
109
- scoreY: number;
110
- scoreText: string;
111
- scoreEntity: Entity | null;
112
- elapsed: number;
113
- buttons: Rect[];
114
- /** Where the banner ad goes, in points from the view's top. */
115
- bannerTop: number;
116
- bannerWidth: number;
117
- };
118
-
119
- const meshes = new Map<string, Model>();
120
- const cached = (key: string, build: () => Model) => {
121
- let m = meshes.get(key);
122
- if (!m) {
123
- m = cacheGameAsset('shared/EngineHud:' + key, build);
124
- meshes.set(key, m);
125
- }
126
- return m;
127
- };
128
- /** A flat rounded rectangle centred on the origin (sizes are rounded so few meshes are made). */
129
- const panelMesh = (w: number, h: number, r: number) => {
130
- const [rw, rh] = [Math.round(w), Math.round(h)];
131
- return cached(`hud-panel:${rw}:${rh}:${r}`, () =>
132
- createShape(extrude(new Path().addRoundedRect(-rw / 2, -rh / 2, rw, rh, Math.min(r, rw / 2, rh / 2)), { depth: 0 }), { center: false })
133
- );
134
- };
135
- /** The outline of panelMesh(w, h, r), `bw` thick (a border, so translucent faces aren't tinted by it). */
136
- const ringMesh = (w: number, h: number, r: number, bw: number) => {
137
- const [rw, rh] = [Math.round(w), Math.round(h)];
138
- return cached(`hud-ring:${rw}:${rh}:${r}:${bw}`, () => {
139
- const outer = Math.min(r, rw / 2, rh / 2);
140
- const path = new Path()
141
- .addRoundedRect(-rw / 2, -rh / 2, rw, rh, outer)
142
- .addRoundedRect(-rw / 2 + bw, -rh / 2 + bw, rw - bw * 2, rh - bw * 2, Math.max(0, outer - bw));
143
- return createShape(extrude(path, { depth: 0, fillRule: 'evenodd' }), { center: false });
144
- });
145
- };
146
- /** The RN icons' own outlines (Icons.tsx), flat, one unit across at their widest. */
147
- const iconMesh = (key: string, d: string) => cached(`hud-icon:${key}`, () => createShape(extrude(Path.fromSVG(d), { depth: 0 }), { fit: 1 }));
148
- const trophyMesh = () => iconMesh('trophy', TROPHY_PATH);
149
- const shareMesh = () => iconMesh('share', SHARE_PATH);
150
- const playMesh = () => iconMesh('play', PLAY_PATH);
151
- const heartMesh = () => iconMesh('heart', HEART_PATH);
152
- // How much of each icon's 512 box its outline fills, so `size` matches the RN icon's size.
153
- const TROPHY_INK = 448 / 512;
154
- const SHARE_INK = 415 / 512;
155
- const PLAY_INK = 332 / 512;
156
-
157
- type Fonts = { semi: Font; regular: Font };
158
- type Weight = keyof Fonts;
159
- let fonts: Fonts | null = null;
160
- let fontsLoading: Promise<Fonts> | null = null;
161
- // Glyphs share the engine's 4,096 app-wide mesh ids with models. Prewarm common HUD characters in
162
- // one weight; the font builds other Unicode symbols only when a label actually needs them.
163
- const HUD_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.,:!?'%/+-·×÷…";
164
- /** Inter SemiBold for all HUD text, flat (depth 0) like normal text: loaded once per app. */
165
- export function loadHudFonts(): Promise<Fonts> {
166
- fontsLoading ??= loadFont(require('../../assets/fonts/Inter-SemiBold.ttf'), { depth: 0, chars: HUD_CHARS }).then(
167
- (semi) => (fonts = { semi, regular: semi })
168
- );
169
- return fontsLoading;
170
- }
171
-
172
- /** A line's width in points at font size `size`, with `spacing` points between letters. */
173
- function textWidth(text: string, size: number, spacing = 0, weight: Weight = 'semi'): number {
174
- const font = fonts?.[weight];
175
- if (!font) return 0;
176
- return measureText(font, text, { letterSpacing: spacing / size }) * size;
177
- }
178
-
179
- /** Words broken into lines at most `max` points wide. */
180
- function wrap(text: string, size: number, max: number, weight: Weight): string[] {
181
- const lines: string[] = [];
182
- let line = '';
183
- for (const word of text.split(/\s+/)) {
184
- const next = line ? `${line} ${word}` : word;
185
- if (line && textWidth(next, size, 0, weight) > max) {
186
- lines.push(line);
187
- line = word;
188
- } else line = next;
189
- }
190
- if (line) lines.push(line);
191
- return lines;
192
- }
193
-
194
- const columnKey = (c: HudColumn) =>
195
- `${c.value}\u0000${c.color ?? ''}\u0000${c.valueColor ?? ''}\u0000${c.lives ? `${c.lives.count}/${c.lives.max}` : ''}`;
196
-
197
- /** Text centred on (x, y) in its parent at font size `size` (points), `spacing` points between letters. */
198
- function spawnText(
199
- world: World,
200
- parent: Entity,
201
- text: string,
202
- x: number,
203
- y: number,
204
- size: number,
205
- color: Color,
206
- spacing = 0,
207
- weight: Weight = 'semi',
208
- tabular = false
209
- ): Entity {
210
- return world.spawn({
211
- text,
212
- font: fonts![weight],
213
- parent,
214
- position: [x, y, 2],
215
- scale: size,
216
- color,
217
- letterSpacing: spacing / size,
218
- tabular,
219
- pickable: false
220
- });
221
- }
222
-
223
- /** The engine-drawn scoreboard and controls. Feed it state; pass `world` as GameView's `overlay`. */
224
- export class EngineHud {
225
- readonly world: World;
226
- private theme: GameTheme;
227
- private size = { width: 0, height: 0, offset: 0 };
228
- private columns: HudColumn[] = [];
229
- private playing = false;
230
- private subtext = 'Begin playing!';
231
- private actions: HudActions = { onStart: () => {}, onStop: () => {} };
232
- private board: Entity | null = null;
233
- private controls: Entity | null = null;
234
- private values: { entity: Entity; key: string; x: number; y: number }[] = [];
235
- private buttons: Rect[] = [];
236
- private pressed: Rect | null = null;
237
- private over: Over | null = null;
238
- private overReserve = 0;
239
- private disposed = false;
240
- private feedback: { root: Entity; flash: Entity | null; message: Entity | null; text: string } | null = null;
241
-
242
- private waiting: GameOverCard | null = null;
243
- private layers = new Map<string, Entity>();
244
- private choices: HudChoice[] = [];
245
- private choiceRoot: Entity | null = null;
246
- private choiceButtons: Rect[] = [];
247
- private choicePage = 0;
248
-
249
- get ready() {
250
- return !this.disposed && !!fonts && this.size.width > 0;
251
- }
252
- get viewport() {
253
- return this.size;
254
- }
255
- layer(key: string): Entity {
256
- let root = this.layers.get(key);
257
- if (root === undefined) {
258
- root = this.world.spawn({ mesh: 'none', position: [0, 0, 5], pickable: false });
259
- this.layers.set(key, root);
260
- }
261
- return root;
262
- }
263
- clearLayer(key: string) {
264
- const root = this.layers.get(key);
265
- if (root !== undefined) this.world.despawn(root);
266
- this.layers.delete(key);
267
- }
268
- text(parent: Entity, text: string, x: number, y: number, size: number, color: Color): Entity {
269
- return spawnText(this.world, parent, text, x, y, size, color);
270
- }
271
- rectangle(parent: Entity, x: number, y: number, width: number, height: number, color: Color, z = 0): Entity {
272
- return this.world.spawn({ mesh: panelMesh(1, 1, 0), parent, position: [x, y, z], scale: [width, height, 1], color, pickable: false });
273
- }
274
- setChoices(choices: HudChoice[]) {
275
- const changed =
276
- choices.length !== this.choices.length ||
277
- choices.some((item, i) => {
278
- const previous = this.choices[i];
279
- return item.id !== previous?.id || item.label !== previous.label || item.selected !== previous.selected;
280
- });
281
- this.choices = choices;
282
- if (changed) this.buildChoices();
283
- }
284
- private buildChoices() {
285
- if (this.choiceRoot !== null) this.world.despawn(this.choiceRoot);
286
- this.choiceRoot = null;
287
- this.choiceButtons = [];
288
- if (!this.ready || !this.choices.length) return;
289
- const { width, height } = this.size;
290
- const pageSize = 3;
291
- const pages = Math.ceil(this.choices.length / pageSize);
292
- this.choicePage = Math.min(this.choicePage, pages - 1);
293
- const root = this.world.spawn({ mesh: 'none', position: [0, 0, 6], pickable: false });
294
- this.choiceRoot = root;
295
- const w = Math.min(width - 24, 500),
296
- gap = 6,
297
- cell = (w - gap * 4) / 5;
298
- const cy = height - getSceneInputInsets().bottom - 22;
299
- const draw = (label: string, index: number, action: () => void, selected = false) => {
300
- const x = (width - w) / 2 + cell / 2 + index * (cell + gap);
301
- const panel = this.panel(root, x, cy, cell, 36, 12, selected ? this.theme.SOLID : this.theme.SURFACE);
302
- spawnText(
303
- this.world,
304
- panel,
305
- label,
306
- 0,
307
- 0,
308
- Math.min(12, (cell - 8) / Math.max(1, label.length) / 0.65),
309
- toColor(selected ? this.theme.ON_SOLID : this.theme.TEXT)
310
- );
311
- this.choiceButtons.push({ id: 'choice', x: x - cell / 2, y: cy - 18, w: cell, h: 36, root: panel, action });
312
- };
313
- draw('<', 0, () => {
314
- this.choicePage = (this.choicePage + pages - 1) % pages;
315
- this.buildChoices();
316
- });
317
- this.choices
318
- .slice(this.choicePage * pageSize, (this.choicePage + 1) * pageSize)
319
- .forEach((item, i) => draw(item.label, i + 1, () => this.choices.find((choice) => choice.id === item.id)?.onPress(), item.selected));
320
- draw('>', 4, () => {
321
- this.choicePage = (this.choicePage + 1) % pages;
322
- this.buildChoices();
323
- });
324
- }
325
-
326
- constructor(theme: GameTheme, capacity = CAPACITY) {
327
- this.world = new World(capacity);
328
- this.theme = theme;
329
- // Everything is drawn once the fonts are in (immediately after the first visit)
330
- loadHudFonts()
331
- .then(() => {
332
- if (this.disposed) return;
333
- this.buildBoard();
334
- this.buildControls();
335
- this.buildChoices();
336
- if (this.waiting) this.buildOver(this.waiting, true);
337
- this.waiting = null;
338
- })
339
- .catch((error: unknown) => console.error(`EngineHud: ${error instanceof Error ? error.message : String(error)}`));
340
- }
341
-
342
- setActions(actions: HudActions) {
343
- const changed = !!actions.onShare !== !!this.actions.onShare || !!actions.onShowLeaderboard !== !!this.actions.onShowLeaderboard;
344
- this.actions = actions;
345
- if (changed) this.buildControls();
346
- }
347
-
348
- layout(width: number, height: number, offset: number) {
349
- if (width === this.size.width && height === this.size.height && offset === this.size.offset) return;
350
- this.size = { width, height, offset };
351
- this.layoutFeedback();
352
- this.buildBoard();
353
- this.buildControls();
354
- this.buildChoices();
355
- if (this.over) this.buildOver(this.over.card, false);
356
- }
357
-
358
- setTheme(theme: GameTheme) {
359
- if (theme === this.theme) return;
360
- this.theme = theme;
361
- this.clearFeedback();
362
- this.buildBoard();
363
- this.buildControls();
364
- this.buildChoices();
365
- if (this.over) this.buildOver(this.over.card, false);
366
- }
367
-
368
- /** The scoreboard's columns; only values that changed are redrawn. */
369
- setColumns(columns: HudColumn[]) {
370
- const sameLabels = columns.length === this.columns.length && columns.every((c, i) => c.label === this.columns[i]!.label);
371
- this.columns = columns;
372
- if (!sameLabels || this.values.length !== columns.length) return this.buildBoard();
373
- columns.forEach((c, i) => {
374
- const v = this.values[i]!;
375
- const key = columnKey(c);
376
- if (v.key === key || this.disposed || this.board === null) return;
377
- this.world.despawn(v.entity);
378
- v.entity = this.spawnValue(this.board, c, v.x, v.y);
379
- v.key = key;
380
- });
381
- }
382
-
383
- setPlaying(playing: boolean, subtext = this.subtext) {
384
- if (playing === this.playing && subtext === this.subtext) return;
385
- this.playing = playing;
386
- this.subtext = subtext;
387
- this.buildControls();
388
- }
389
-
390
- update(dt: number, feedback?: HudFeedback) {
391
- if (this.disposed) return;
392
- if (feedback) this.showFeedback(feedback);
393
- const o = this.over;
394
- if (o && o.elapsed < COUNT_DELAY + COUNT_TIME) {
395
- o.elapsed += dt;
396
- const t = Math.min(1, Math.max(0, (o.elapsed - COUNT_DELAY) / COUNT_TIME));
397
- this.setOverScore(Math.round(o.card.score * (1 - (1 - t) ** 3)));
398
- }
399
- this.world.update(dt);
400
- }
401
-
402
- private showFeedback(feedback: HudFeedback) {
403
- if (!this.size.width || !fonts) return;
404
- if (!this.over && !this.waiting && (feedback.flash || feedback.message)) {
405
- const effects = (this.feedback ??= {
406
- root: this.world.spawn({ mesh: 'none', position: [0, 0, Z_OVER - 5], pickable: false }),
407
- flash: null,
408
- message: null,
409
- text: ''
410
- });
411
- if (feedback.flash) {
412
- effects.flash ??= this.world.spawn({ mesh: panelMesh(1, 1, 0), parent: effects.root, pickable: false });
413
- const color = toColor(this.theme.DANGER, 0.6);
414
- this.world.stopAnimation(effects.flash);
415
- this.world.set(effects.flash, { color });
416
- this.world.animate(effects.flash, { color: [color[0], color[1], color[2], 0] }, { duration: 0.4 });
417
- }
418
- if (feedback.message) {
419
- if (effects.text !== feedback.message && effects.message !== null) {
420
- this.world.despawn(effects.message);
421
- effects.message = null;
422
- }
423
- const color = toColor(this.theme.SCORE_HIGHLIGHT);
424
- effects.text = feedback.message;
425
- effects.message ??= spawnText(this.world, effects.root, effects.text, 0, 0, 30, color);
426
- this.world.stopAnimation(effects.message);
427
- this.world.set(effects.message, { color });
428
- this.world.animate(effects.message, { color: [color[0], color[1], color[2], 0] }, { duration: 0.9 });
429
- }
430
- this.layoutFeedback();
431
- }
432
- feedback.flash = false;
433
- feedback.message = undefined;
434
- }
435
-
436
- private layoutFeedback() {
437
- const effects = this.feedback;
438
- if (!effects) return;
439
- const { width, height, offset } = this.size;
440
- if (effects.flash !== null) this.world.set(effects.flash, { position: [width / 2, -height / 2, 0], scale: [width, height, 1] });
441
- if (effects.message !== null) this.world.set(effects.message, { position: [width / 2, -(getScoreBoardTop(offset) + BOARD_HEIGHT + 14 + 15), 2] });
442
- }
443
-
444
- private clearFeedback() {
445
- if (this.feedback && !this.disposed) this.world.despawn(this.feedback.root);
446
- this.feedback = null;
447
- }
448
-
449
- /** Shows the game-over card over everything (it takes every touch until hidden). */
450
- showGameOver(card: GameOverCard) {
451
- this.clearFeedback();
452
- const o = this.over;
453
- if (o && o.card.title === card.title && o.card.message === card.message && o.card.score === card.score && !!o.card.onShare === !!card.onShare) {
454
- o.card = card; // fresh callbacks only
455
- return;
456
- }
457
- if (!fonts) this.waiting = card;
458
- else this.buildOver(card, true);
459
- }
460
-
461
- hideGameOver() {
462
- this.waiting = null;
463
- if (!this.over) return;
464
- if (!this.disposed) this.world.despawn(this.over.root);
465
- if (this.pressed && this.over.buttons.includes(this.pressed)) this.pressed = null;
466
- this.over = null;
467
- }
468
-
469
- /** Where an RN banner ad sits under the card's buttons (null: no card). */
470
- get gameOverBanner(): { top: number; width: number } | null {
471
- return this.over ? { top: this.over.bannerTop, width: this.over.bannerWidth } : null;
472
- }
473
-
474
- /** Room left in the card for the banner ad (its measured height), in points. */
475
- setGameOverReserve(height: number) {
476
- if (Math.abs(height - this.overReserve) < 1) return;
477
- this.overReserve = height;
478
- if (this.over) this.buildOver(this.over.card, false);
479
- }
480
-
481
- /** GameView's onOverlayTouch: true when a button took the touch. */
482
- touch = (phase: 'start' | 'move' | 'end' | 'cancel', x: number, y: number): boolean => {
483
- if (phase === 'start') {
484
- const list = this.over ? this.over.buttons : [...this.buttons, ...this.choiceButtons];
485
- const hit = list.find((b) => x >= b.x && x <= b.x + b.w && y >= b.y && y <= b.y + b.h);
486
- if (!hit) return this.over !== null; // the card is modal
487
- this.pressed = hit;
488
- this.world.animate(hit.root, { scale: PRESSED }, { duration: 0.08 });
489
- return true;
490
- }
491
- if (phase === 'move' || !this.pressed) return true;
492
- const b = this.pressed;
493
- this.pressed = null;
494
- const inside = x >= b.x - 10 && x <= b.x + b.w + 10 && y >= b.y - 10 && y <= b.y + b.h + 10;
495
- this.release(b);
496
- if (phase === 'end' && inside) {
497
- if (b.action) b.action();
498
- else this.fire(b.id);
499
- }
500
- return true;
501
- };
502
-
503
- dispose() {
504
- if (this.disposed) return;
505
- this.disposed = true;
506
- this.world.dispose();
507
- }
508
-
509
- private release(b: Rect) {
510
- if (b.id === 'main' && !this.playing && !b.action) this.pulse(b.root);
511
- else this.world.animate(b.root, { scale: 1 }, { duration: 0.12 });
512
- }
513
-
514
- private pulse(root: Entity) {
515
- this.world.set(root, { scale: 1 });
516
- this.world.animate(root, { scale: PULSE }, { duration: 1, repeat: 'forever', yoyo: true });
517
- }
518
-
519
- private fire(id: HudButton) {
520
- if (id === 'main') {
521
- if (this.playing) this.clearFeedback();
522
- (this.playing ? this.actions.onStop : this.actions.onStart)();
523
- } else if (id === 'share') this.actions.onShare?.();
524
- else this.actions.onShowLeaderboard?.();
525
- }
526
-
527
- /** A themed button panel (border behind the face) under a root at its centre. */
528
- private panel(
529
- parent: Entity | null,
530
- cx: number,
531
- cy: number,
532
- w: number,
533
- h: number,
534
- r: number,
535
- face = this.theme.SURFACE,
536
- border = this.theme.BORDER
537
- ): Entity {
538
- const t = this.theme;
539
- const root = this.world.spawn({ mesh: 'none', ...(parent !== null && { parent }), position: [cx, -cy, 0], pickable: false });
540
- const bw = t.BORDER_WIDTH ?? 0;
541
- if (bw > 0) this.world.spawn({ mesh: ringMesh(w, h, r, bw), parent: root, position: [0, 0, 0.5], color: toColor(border), pickable: false });
542
- this.world.spawn({ mesh: panelMesh(w - bw * 2, h - bw * 2, r - bw), parent: root, position: [0, 0, 0], color: toColor(face), pickable: false });
543
- return root;
544
- }
545
-
546
- private setOverScore(value: number) {
547
- const o = this.over;
548
- if (!o) return;
549
- const text = (o.card.formatter ?? ((n: number) => n.toLocaleString()))(value);
550
- if (text === o.scoreText && o.scoreEntity !== null) return;
551
- if (o.scoreEntity !== null) this.world.despawn(o.scoreEntity);
552
- o.scoreText = text;
553
- o.scoreEntity = spawnText(this.world, o.scoreParent, text, 0, o.scoreY, SCORE_SIZE, toColor(this.theme.SCORE_HIGHLIGHT), 0, 'semi', true);
554
- }
555
-
556
- /** The card, laid out top to bottom as GameOverModal; `animate` plays its entrance. */
557
- private buildOver(card: GameOverCard, animate: boolean) {
558
- const counted = this.over && !animate ? this.over.elapsed : 0;
559
- this.hideGameOver();
560
- const { width, height } = this.size;
561
- if (this.disposed || !width || !fonts) return;
562
- const t = this.theme;
563
- const w = Math.min(getGameDimensions().width, width - 30);
564
- const inner = w - CARD_PAD * 2;
565
- const message = card.message ? wrap(card.message, MESSAGE_SIZE, inner, 'regular') : [];
566
- const prompt = card.onShare ? wrap(SHARE_PROMPT, PROMPT_SIZE, inner + 30, 'regular') : [];
567
- // Heights, top to bottom
568
- const titleH = 34;
569
- const scoreH = 16 + 16 + 4 + 52 + 16;
570
- const h =
571
- CARD_PAD +
572
- TROPHY +
573
- 8 +
574
- titleH +
575
- (message.length ? 4 + message.length * 20 + 16 : 20) +
576
- scoreH +
577
- 24 +
578
- (prompt.length ? prompt.length * 25 + 5 + 12 : 0) +
579
- RESULT_BUTTON +
580
- (this.overReserve > 0 ? 12 + this.overReserve : 0) +
581
- CARD_PAD;
582
- const top = Math.max(10, (height - h) / 2);
583
- const cx = width / 2;
584
-
585
- const root = this.world.spawn({ mesh: 'none', position: [0, 0, Z_OVER], pickable: false });
586
- const backdrop = this.world.spawn({
587
- mesh: panelMesh(width, height, 0),
588
- parent: root,
589
- position: [width / 2, -height / 2, 0],
590
- color: animate ? toColor(t.OVERLAY, 0) : toColor(t.OVERLAY),
591
- pickable: false
592
- });
593
- // The card: the game background pattern (as BackdropCard), cut to its rounded corners
594
- const box = this.world.spawn({ mesh: 'none', parent: root, position: [cx, -(top + h / 2), 2], pickable: false });
595
- this.world.spawn({
596
- mesh: cardBackdropMesh(t, w, h, 16),
597
- parent: box,
598
- position: [-Math.round(w) / 2, Math.round(h) / 2, 0],
599
- color: [1, 1, 1, 1],
600
- pickable: false
601
- });
602
- // Children of the card are placed from its centre: screen y below the card's top -> local y
603
- const ly = (y: number) => h / 2 - y;
604
- const group = (y: number) => this.world.spawn({ mesh: 'none', parent: box, position: [0, ly(y), 1], pickable: false });
605
- let y = CARD_PAD;
606
-
607
- const trophy = group(y + TROPHY / 2);
608
- this.world.spawn({ mesh: trophyMesh(), parent: trophy, position: [0, 0, 0], scale: TROPHY * TROPHY_INK, color: [1, 1, 1, 1], pickable: false });
609
- y += TROPHY + 8;
610
- spawnText(this.world, group(y + titleH / 2), card.title, 0, 0, TITLE_SIZE, toColor(t.TEXT), 1);
611
- y += titleH;
612
- if (message.length) {
613
- y += 4;
614
- for (const line of message) {
615
- spawnText(this.world, group(y + 10), line, 0, 0, MESSAGE_SIZE, toColor(t.TEXT_SUBTLE), 0, 'regular');
616
- y += 20;
617
- }
618
- y += 16;
619
- } else y += 20;
620
-
621
- const scoreAt = ly(y + scoreH / 2);
622
- const scoreGroup = group(y + scoreH / 2);
623
- const sw = Math.max(200, Math.min(inner, 260));
624
- const scorePanel = this.panel(scoreGroup, 0, 0, sw, scoreH, 16);
625
- spawnText(this.world, scorePanel, card.scoreLabel.toUpperCase(), 0, scoreH / 2 - 16 - 8, SCORE_LABEL_SIZE, toColor(t.TEXT_SUBTLE), 1, 'semi');
626
- const scoreY = -scoreH / 2 + 16 + 26;
627
- y += scoreH + 24;
628
-
629
- const actionsAt = ly(y);
630
- const actions = group(y);
631
- let ay = 0; // screen points below the actions' top
632
- for (const line of prompt) {
633
- spawnText(this.world, actions, line, 0, -(ay + 12.5), PROMPT_SIZE, toColor(t.TEXT_SUBTLE), 0, 'regular');
634
- ay += 25;
635
- }
636
- if (prompt.length) ay += 5 + 12;
637
- const buttons: Rect[] = [];
638
- const rowY = ay + RESULT_BUTTON / 2;
639
- const left = -inner / 2;
640
- const shareW = card.onShare ? ((inner - 10) * 2) / 5 : 0;
641
- const playW = card.onShare ? inner - 10 - shareW : inner;
642
- const addButton = (
643
- x: number,
644
- bw: number,
645
- face: string,
646
- border: string,
647
- label: string,
648
- size: number,
649
- weight: Weight,
650
- color: Color,
651
- mesh: Model,
652
- ink: number,
653
- action: () => void
654
- ) => {
655
- const b = this.panel(actions, x + bw / 2, rowY, bw, RESULT_BUTTON, 26, face, border);
656
- // Icon (18) + gap (8) + label, centred as a row
657
- const fittedSize = Math.min(size, (size * (bw - 44)) / Math.max(1, textWidth(label, size, 0.5, weight)));
658
- const textW = textWidth(label, fittedSize, 0.5, weight);
659
- const iconX = -(textW + 26) / 2 + 9;
660
- this.world.spawn({ mesh, parent: b, position: [iconX, 0, 2], scale: 18 * ink, color, pickable: false });
661
- spawnText(this.world, b, label, iconX + 9 + 8 + textW / 2, 0, fittedSize, color, 0.5, weight);
662
- // Screen rect: the actions group's top is at screen `top + y`
663
- buttons.push({ id: 'main', x: cx + x, y: top + y + rowY - RESULT_BUTTON / 2, w: bw, h: RESULT_BUTTON, root: b, action });
664
- };
665
- if (card.onShare)
666
- addButton(left, shareW, t.SURFACE, t.BORDER, 'Share', SHARE_SIZE, 'semi', toColor(t.TEXT), shareMesh(), SHARE_INK, () =>
667
- this.over?.card.onShare?.()
668
- );
669
- addButton(
670
- left + (card.onShare ? shareW + 10 : 0),
671
- playW,
672
- t.SOLID,
673
- t.SOLID,
674
- card.buttonText,
675
- PLAY_SIZE,
676
- 'semi',
677
- toColor(t.ON_SOLID),
678
- playMesh(),
679
- PLAY_INK,
680
- () => this.over?.card.onPlayAgain()
681
- );
682
- ay += RESULT_BUTTON;
683
-
684
- this.over = {
685
- card,
686
- root,
687
- scoreParent: scorePanel,
688
- scoreY,
689
- scoreText: '',
690
- scoreEntity: null,
691
- elapsed: counted,
692
- buttons,
693
- bannerTop: top + y + ay + 12,
694
- bannerWidth: inner
695
- };
696
- this.setOverScore(animate ? 0 : card.score);
697
-
698
- if (!animate) return;
699
- // GameOverModal's entrance: fade the backdrop, spring the card, punch the trophy, lift score then actions
700
- this.world.animate(backdrop, { color: toColor(t.OVERLAY) }, { duration: 0.16 });
701
- this.world.set(box, { scale: 0.85 });
702
- this.world.animate(box, { scale: 1 }, { spring: 'bouncy' });
703
- this.world.set(trophy, { scale: 0 });
704
- this.world.animate(trophy, [{ scale: 1.18 }, { scale: 1 }], { delay: 0.09, duration: 0.35, easing: 'back' });
705
- for (const [g, at, delay] of [
706
- [scoreGroup, scoreAt, 0.2],
707
- [actions, actionsAt, 0.33]
708
- ] as const) {
709
- this.world.set(g, { position: [0, at - 18, 1] });
710
- this.world.animate(g, { position: [0, at, 1] }, { delay, spring: true });
711
- }
712
- }
713
-
714
- private buildBoard() {
715
- if (this.disposed) return;
716
- if (this.board !== null) this.world.despawn(this.board);
717
- this.board = null;
718
- this.values = [];
719
- const { width, offset } = this.size;
720
- if (!width || !fonts) return;
721
- const w = gameWidthFor(width);
722
- const top = getScoreBoardTop(offset);
723
- const board = this.panel(null, width / 2, top + BOARD_HEIGHT / 2, w, BOARD_HEIGHT, BOARD_RADIUS);
724
- this.board = board;
725
- const n = this.columns.length;
726
- const labelY = BOARD_HEIGHT / 2 - BOARD_PAD_V - 6;
727
- const valueY = -BOARD_HEIGHT / 2 + BOARD_PAD_V + 12;
728
- const label = toColor(this.theme.LABEL);
729
- this.columns.forEach((c, i) => {
730
- const x = -w / 2 + (w * (i + 0.5)) / n;
731
- spawnText(this.world, board, c.label, x, labelY, LABEL_SIZE, c.color ? toColor(c.color) : label, LABEL_SPACING, 'semi');
732
- this.values.push({ entity: this.spawnValue(board, c, x, valueY), key: columnKey(c), x, y: valueY });
733
- });
734
- }
735
-
736
- /** The same primary control for keyboard input, including the results card. */
737
- activatePrimary() {
738
- if (!this.ready) return;
739
- if (this.over) this.over.card.onPlayAgain();
740
- else this.fire('main');
741
- }
742
-
743
- /** A column's value: its text, or a row of hearts. */
744
- private spawnValue(board: Entity, c: HudColumn, x: number, y: number): Entity {
745
- if (!c.lives)
746
- return spawnText(this.world, board, c.value, x, y, VALUE_SIZE, toColor(c.valueColor ?? c.color ?? this.theme.TEXT), 0, 'semi', true);
747
- const { count, max } = c.lives;
748
- const row = this.world.spawn({ mesh: 'none', parent: board, position: [x, y, 2], pickable: false });
749
- const columnWidth = gameWidthFor(this.size.width) / this.columns.length - 12;
750
- const heartSize = Math.min(HEART_SIZE, Math.max(6, (columnWidth - (max - 1) * HEART_GAP) / Math.max(1, max)));
751
- const step = heartSize + HEART_GAP;
752
- for (let i = 0; i < max; i++) {
753
- const hx = (i - (max - 1) / 2) * step;
754
- this.world.spawn({
755
- mesh: heartMesh(),
756
- parent: row,
757
- position: [hx, 0, 0],
758
- scale: heartSize,
759
- color: toColor(i < count ? HEART_ON : HEART_OFF),
760
- pickable: false
761
- });
762
- }
763
- return row;
764
- }
765
-
766
- private buildControls() {
767
- if (this.disposed) return;
768
- if (this.controls !== null) this.world.despawn(this.controls);
769
- this.controls = null;
770
- this.buttons = [];
771
- this.pressed = null;
772
- const { width, height } = this.size;
773
- if (!width || !fonts) return;
774
- const rowW = Math.min(getGameDimensions().width, width);
775
- const left = (width - rowW) / 2 + ROW_PAD_H;
776
- const right = (width + rowW) / 2 - ROW_PAD_H;
777
- const cy = height - 5 - ROW_PAD_V - MAIN_HEIGHT / 2;
778
- const controls = this.world.spawn({ mesh: 'none', position: [0, 0, 10], pickable: false });
779
- this.controls = controls;
780
- const text = toColor(this.theme.TEXT);
781
- let mainLeft = left;
782
- let mainRight = right;
783
- const icon = (id: HudButton, x: number, mesh: Model, ink: number) => {
784
- const root = this.panel(controls, x, cy, ICON_SIZE, ICON_SIZE, ICON_SIZE / 2);
785
- this.world.spawn({ mesh, parent: root, position: [0, 0, 2], scale: 24 * ink, color: text, pickable: false });
786
- this.buttons.push({ id, x: x - ICON_SIZE / 2, y: cy - ICON_SIZE / 2, w: ICON_SIZE, h: ICON_SIZE, root });
787
- };
788
- if (this.actions.onShowLeaderboard) {
789
- icon('leaderboard', left + ICON_SIZE / 2, trophyMesh(), TROPHY_INK);
790
- mainLeft += ICON_SIZE + ROW_GAP;
791
- }
792
- if (this.actions.onShare) {
793
- icon('share', right - ICON_SIZE / 2, shareMesh(), SHARE_INK);
794
- mainRight -= ICON_SIZE + ROW_GAP;
795
- }
796
- const mw = mainRight - mainLeft;
797
- const mx = (mainLeft + mainRight) / 2;
798
- const main = this.panel(controls, mx, cy, mw, MAIN_HEIGHT, 30);
799
- spawnText(this.world, main, this.playing ? 'STOP GAME' : 'START GAME', 0, 7, BUTTON_TEXT, text, 1);
800
- const subtitle = fitControlSubtitle(this.playing ? 'Stop current game' : this.subtext, Math.max(0, mw - 24), (value) =>
801
- textWidth(value, BUTTON_SUBTEXT, 0.5)
802
- );
803
- spawnText(this.world, main, subtitle, 0, -12, BUTTON_SUBTEXT, toColor(this.theme.LABEL), 0.5, 'semi');
804
- this.buttons.push({ id: 'main', x: mainLeft, y: cy - MAIN_HEIGHT / 2, w: mw, h: MAIN_HEIGHT, root: main });
805
- if (!this.playing) this.pulse(main);
806
- }
807
- }
808
-
809
- /** Reusable assets; the game loader advances one builder per frame. */
810
- export function* warmAssets(): Iterable<() => unknown> {
811
- yield trophyMesh;
812
- yield shareMesh;
813
- yield playMesh;
814
- yield heartMesh;
815
- }