setiastrosuitepro 1.6.2.post1__py3-none-any.whl

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.

Potentially problematic release.


This version of setiastrosuitepro might be problematic. Click here for more details.

Files changed (367) hide show
  1. setiastro/__init__.py +2 -0
  2. setiastro/data/SASP_data.fits +0 -0
  3. setiastro/data/catalogs/List_of_Galaxies_with_Distances_Gly.csv +488 -0
  4. setiastro/data/catalogs/astrobin_filters.csv +890 -0
  5. setiastro/data/catalogs/astrobin_filters_page1_local.csv +51 -0
  6. setiastro/data/catalogs/cali2.csv +63 -0
  7. setiastro/data/catalogs/cali2color.csv +65 -0
  8. setiastro/data/catalogs/celestial_catalog - original.csv +16471 -0
  9. setiastro/data/catalogs/celestial_catalog.csv +24031 -0
  10. setiastro/data/catalogs/detected_stars.csv +24784 -0
  11. setiastro/data/catalogs/fits_header_data.csv +46 -0
  12. setiastro/data/catalogs/test.csv +8 -0
  13. setiastro/data/catalogs/updated_celestial_catalog.csv +16471 -0
  14. setiastro/images/Astro_Spikes.png +0 -0
  15. setiastro/images/Background_startup.jpg +0 -0
  16. setiastro/images/HRDiagram.png +0 -0
  17. setiastro/images/LExtract.png +0 -0
  18. setiastro/images/LInsert.png +0 -0
  19. setiastro/images/Oxygenation-atm-2.svg.png +0 -0
  20. setiastro/images/RGB080604.png +0 -0
  21. setiastro/images/abeicon.png +0 -0
  22. setiastro/images/aberration.png +0 -0
  23. setiastro/images/andromedatry.png +0 -0
  24. setiastro/images/andromedatry_satellited.png +0 -0
  25. setiastro/images/annotated.png +0 -0
  26. setiastro/images/aperture.png +0 -0
  27. setiastro/images/astrosuite.ico +0 -0
  28. setiastro/images/astrosuite.png +0 -0
  29. setiastro/images/astrosuitepro.icns +0 -0
  30. setiastro/images/astrosuitepro.ico +0 -0
  31. setiastro/images/astrosuitepro.png +0 -0
  32. setiastro/images/background.png +0 -0
  33. setiastro/images/background2.png +0 -0
  34. setiastro/images/benchmark.png +0 -0
  35. setiastro/images/big_moon_stabilizer_timeline.png +0 -0
  36. setiastro/images/big_moon_stabilizer_timeline_clean.png +0 -0
  37. setiastro/images/blaster.png +0 -0
  38. setiastro/images/blink.png +0 -0
  39. setiastro/images/clahe.png +0 -0
  40. setiastro/images/collage.png +0 -0
  41. setiastro/images/colorwheel.png +0 -0
  42. setiastro/images/contsub.png +0 -0
  43. setiastro/images/convo.png +0 -0
  44. setiastro/images/copyslot.png +0 -0
  45. setiastro/images/cosmic.png +0 -0
  46. setiastro/images/cosmicsat.png +0 -0
  47. setiastro/images/crop1.png +0 -0
  48. setiastro/images/cropicon.png +0 -0
  49. setiastro/images/curves.png +0 -0
  50. setiastro/images/cvs.png +0 -0
  51. setiastro/images/debayer.png +0 -0
  52. setiastro/images/denoise_cnn_custom.png +0 -0
  53. setiastro/images/denoise_cnn_graph.png +0 -0
  54. setiastro/images/disk.png +0 -0
  55. setiastro/images/dse.png +0 -0
  56. setiastro/images/exoicon.png +0 -0
  57. setiastro/images/eye.png +0 -0
  58. setiastro/images/fliphorizontal.png +0 -0
  59. setiastro/images/flipvertical.png +0 -0
  60. setiastro/images/font.png +0 -0
  61. setiastro/images/freqsep.png +0 -0
  62. setiastro/images/functionbundle.png +0 -0
  63. setiastro/images/graxpert.png +0 -0
  64. setiastro/images/green.png +0 -0
  65. setiastro/images/gridicon.png +0 -0
  66. setiastro/images/halo.png +0 -0
  67. setiastro/images/hdr.png +0 -0
  68. setiastro/images/histogram.png +0 -0
  69. setiastro/images/hubble.png +0 -0
  70. setiastro/images/imagecombine.png +0 -0
  71. setiastro/images/invert.png +0 -0
  72. setiastro/images/isophote.png +0 -0
  73. setiastro/images/isophote_demo_figure.png +0 -0
  74. setiastro/images/isophote_demo_image.png +0 -0
  75. setiastro/images/isophote_demo_model.png +0 -0
  76. setiastro/images/isophote_demo_residual.png +0 -0
  77. setiastro/images/jwstpupil.png +0 -0
  78. setiastro/images/linearfit.png +0 -0
  79. setiastro/images/livestacking.png +0 -0
  80. setiastro/images/mask.png +0 -0
  81. setiastro/images/maskapply.png +0 -0
  82. setiastro/images/maskcreate.png +0 -0
  83. setiastro/images/maskremove.png +0 -0
  84. setiastro/images/morpho.png +0 -0
  85. setiastro/images/mosaic.png +0 -0
  86. setiastro/images/multiscale_decomp.png +0 -0
  87. setiastro/images/nbtorgb.png +0 -0
  88. setiastro/images/neutral.png +0 -0
  89. setiastro/images/nuke.png +0 -0
  90. setiastro/images/openfile.png +0 -0
  91. setiastro/images/pedestal.png +0 -0
  92. setiastro/images/pen.png +0 -0
  93. setiastro/images/pixelmath.png +0 -0
  94. setiastro/images/platesolve.png +0 -0
  95. setiastro/images/ppp.png +0 -0
  96. setiastro/images/pro.png +0 -0
  97. setiastro/images/project.png +0 -0
  98. setiastro/images/psf.png +0 -0
  99. setiastro/images/redo.png +0 -0
  100. setiastro/images/redoicon.png +0 -0
  101. setiastro/images/rescale.png +0 -0
  102. setiastro/images/rgbalign.png +0 -0
  103. setiastro/images/rgbcombo.png +0 -0
  104. setiastro/images/rgbextract.png +0 -0
  105. setiastro/images/rotate180.png +0 -0
  106. setiastro/images/rotateclockwise.png +0 -0
  107. setiastro/images/rotatecounterclockwise.png +0 -0
  108. setiastro/images/satellite.png +0 -0
  109. setiastro/images/script.png +0 -0
  110. setiastro/images/selectivecolor.png +0 -0
  111. setiastro/images/simbad.png +0 -0
  112. setiastro/images/slot0.png +0 -0
  113. setiastro/images/slot1.png +0 -0
  114. setiastro/images/slot2.png +0 -0
  115. setiastro/images/slot3.png +0 -0
  116. setiastro/images/slot4.png +0 -0
  117. setiastro/images/slot5.png +0 -0
  118. setiastro/images/slot6.png +0 -0
  119. setiastro/images/slot7.png +0 -0
  120. setiastro/images/slot8.png +0 -0
  121. setiastro/images/slot9.png +0 -0
  122. setiastro/images/spcc.png +0 -0
  123. setiastro/images/spin_precession_vs_lunar_distance.png +0 -0
  124. setiastro/images/spinner.gif +0 -0
  125. setiastro/images/stacking.png +0 -0
  126. setiastro/images/staradd.png +0 -0
  127. setiastro/images/staralign.png +0 -0
  128. setiastro/images/starnet.png +0 -0
  129. setiastro/images/starregistration.png +0 -0
  130. setiastro/images/starspike.png +0 -0
  131. setiastro/images/starstretch.png +0 -0
  132. setiastro/images/statstretch.png +0 -0
  133. setiastro/images/supernova.png +0 -0
  134. setiastro/images/uhs.png +0 -0
  135. setiastro/images/undoicon.png +0 -0
  136. setiastro/images/upscale.png +0 -0
  137. setiastro/images/viewbundle.png +0 -0
  138. setiastro/images/whitebalance.png +0 -0
  139. setiastro/images/wimi_icon_256x256.png +0 -0
  140. setiastro/images/wimilogo.png +0 -0
  141. setiastro/images/wims.png +0 -0
  142. setiastro/images/wrench_icon.png +0 -0
  143. setiastro/images/xisfliberator.png +0 -0
  144. setiastro/qml/ResourceMonitor.qml +126 -0
  145. setiastro/saspro/__init__.py +20 -0
  146. setiastro/saspro/__main__.py +945 -0
  147. setiastro/saspro/_generated/__init__.py +7 -0
  148. setiastro/saspro/_generated/build_info.py +3 -0
  149. setiastro/saspro/abe.py +1346 -0
  150. setiastro/saspro/abe_preset.py +196 -0
  151. setiastro/saspro/aberration_ai.py +694 -0
  152. setiastro/saspro/aberration_ai_preset.py +224 -0
  153. setiastro/saspro/accel_installer.py +218 -0
  154. setiastro/saspro/accel_workers.py +30 -0
  155. setiastro/saspro/add_stars.py +624 -0
  156. setiastro/saspro/astrobin_exporter.py +1010 -0
  157. setiastro/saspro/astrospike.py +153 -0
  158. setiastro/saspro/astrospike_python.py +1841 -0
  159. setiastro/saspro/autostretch.py +198 -0
  160. setiastro/saspro/backgroundneutral.py +602 -0
  161. setiastro/saspro/batch_convert.py +328 -0
  162. setiastro/saspro/batch_renamer.py +522 -0
  163. setiastro/saspro/blemish_blaster.py +491 -0
  164. setiastro/saspro/blink_comparator_pro.py +2926 -0
  165. setiastro/saspro/bundles.py +61 -0
  166. setiastro/saspro/bundles_dock.py +114 -0
  167. setiastro/saspro/cheat_sheet.py +213 -0
  168. setiastro/saspro/clahe.py +368 -0
  169. setiastro/saspro/comet_stacking.py +1442 -0
  170. setiastro/saspro/common_tr.py +107 -0
  171. setiastro/saspro/config.py +38 -0
  172. setiastro/saspro/config_bootstrap.py +40 -0
  173. setiastro/saspro/config_manager.py +316 -0
  174. setiastro/saspro/continuum_subtract.py +1617 -0
  175. setiastro/saspro/convo.py +1400 -0
  176. setiastro/saspro/convo_preset.py +414 -0
  177. setiastro/saspro/copyastro.py +190 -0
  178. setiastro/saspro/cosmicclarity.py +1589 -0
  179. setiastro/saspro/cosmicclarity_preset.py +407 -0
  180. setiastro/saspro/crop_dialog_pro.py +973 -0
  181. setiastro/saspro/crop_preset.py +189 -0
  182. setiastro/saspro/curve_editor_pro.py +2562 -0
  183. setiastro/saspro/curves_preset.py +375 -0
  184. setiastro/saspro/debayer.py +673 -0
  185. setiastro/saspro/debug_utils.py +29 -0
  186. setiastro/saspro/dnd_mime.py +35 -0
  187. setiastro/saspro/doc_manager.py +2664 -0
  188. setiastro/saspro/exoplanet_detector.py +2166 -0
  189. setiastro/saspro/file_utils.py +284 -0
  190. setiastro/saspro/fitsmodifier.py +748 -0
  191. setiastro/saspro/fix_bom.py +32 -0
  192. setiastro/saspro/free_torch_memory.py +48 -0
  193. setiastro/saspro/frequency_separation.py +1349 -0
  194. setiastro/saspro/function_bundle.py +1596 -0
  195. setiastro/saspro/generate_translations.py +3092 -0
  196. setiastro/saspro/ghs_dialog_pro.py +663 -0
  197. setiastro/saspro/ghs_preset.py +284 -0
  198. setiastro/saspro/graxpert.py +637 -0
  199. setiastro/saspro/graxpert_preset.py +287 -0
  200. setiastro/saspro/gui/__init__.py +0 -0
  201. setiastro/saspro/gui/main_window.py +8810 -0
  202. setiastro/saspro/gui/mixins/__init__.py +33 -0
  203. setiastro/saspro/gui/mixins/dock_mixin.py +362 -0
  204. setiastro/saspro/gui/mixins/file_mixin.py +450 -0
  205. setiastro/saspro/gui/mixins/geometry_mixin.py +403 -0
  206. setiastro/saspro/gui/mixins/header_mixin.py +441 -0
  207. setiastro/saspro/gui/mixins/mask_mixin.py +421 -0
  208. setiastro/saspro/gui/mixins/menu_mixin.py +389 -0
  209. setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
  210. setiastro/saspro/gui/mixins/toolbar_mixin.py +1457 -0
  211. setiastro/saspro/gui/mixins/update_mixin.py +309 -0
  212. setiastro/saspro/gui/mixins/view_mixin.py +435 -0
  213. setiastro/saspro/gui/statistics_dialog.py +47 -0
  214. setiastro/saspro/halobgon.py +488 -0
  215. setiastro/saspro/header_viewer.py +448 -0
  216. setiastro/saspro/headless_utils.py +88 -0
  217. setiastro/saspro/histogram.py +756 -0
  218. setiastro/saspro/history_explorer.py +941 -0
  219. setiastro/saspro/i18n.py +168 -0
  220. setiastro/saspro/image_combine.py +417 -0
  221. setiastro/saspro/image_peeker_pro.py +1604 -0
  222. setiastro/saspro/imageops/__init__.py +37 -0
  223. setiastro/saspro/imageops/mdi_snap.py +292 -0
  224. setiastro/saspro/imageops/scnr.py +36 -0
  225. setiastro/saspro/imageops/starbasedwhitebalance.py +210 -0
  226. setiastro/saspro/imageops/stretch.py +236 -0
  227. setiastro/saspro/isophote.py +1182 -0
  228. setiastro/saspro/layers.py +208 -0
  229. setiastro/saspro/layers_dock.py +714 -0
  230. setiastro/saspro/lazy_imports.py +193 -0
  231. setiastro/saspro/legacy/__init__.py +2 -0
  232. setiastro/saspro/legacy/image_manager.py +2226 -0
  233. setiastro/saspro/legacy/numba_utils.py +3676 -0
  234. setiastro/saspro/legacy/xisf.py +1071 -0
  235. setiastro/saspro/linear_fit.py +537 -0
  236. setiastro/saspro/live_stacking.py +1841 -0
  237. setiastro/saspro/log_bus.py +5 -0
  238. setiastro/saspro/logging_config.py +460 -0
  239. setiastro/saspro/luminancerecombine.py +309 -0
  240. setiastro/saspro/main_helpers.py +201 -0
  241. setiastro/saspro/mask_creation.py +931 -0
  242. setiastro/saspro/masks_core.py +56 -0
  243. setiastro/saspro/mdi_widgets.py +353 -0
  244. setiastro/saspro/memory_utils.py +666 -0
  245. setiastro/saspro/metadata_patcher.py +75 -0
  246. setiastro/saspro/mfdeconv.py +3831 -0
  247. setiastro/saspro/mfdeconv_earlystop.py +71 -0
  248. setiastro/saspro/mfdeconvcudnn.py +3263 -0
  249. setiastro/saspro/mfdeconvsport.py +2382 -0
  250. setiastro/saspro/minorbodycatalog.py +567 -0
  251. setiastro/saspro/morphology.py +407 -0
  252. setiastro/saspro/multiscale_decomp.py +1293 -0
  253. setiastro/saspro/nbtorgb_stars.py +541 -0
  254. setiastro/saspro/numba_utils.py +3145 -0
  255. setiastro/saspro/numba_warmup.py +141 -0
  256. setiastro/saspro/ops/__init__.py +9 -0
  257. setiastro/saspro/ops/command_help_dialog.py +623 -0
  258. setiastro/saspro/ops/command_runner.py +217 -0
  259. setiastro/saspro/ops/commands.py +1594 -0
  260. setiastro/saspro/ops/script_editor.py +1102 -0
  261. setiastro/saspro/ops/scripts.py +1473 -0
  262. setiastro/saspro/ops/settings.py +637 -0
  263. setiastro/saspro/parallel_utils.py +554 -0
  264. setiastro/saspro/pedestal.py +121 -0
  265. setiastro/saspro/perfect_palette_picker.py +1071 -0
  266. setiastro/saspro/pipeline.py +110 -0
  267. setiastro/saspro/pixelmath.py +1604 -0
  268. setiastro/saspro/plate_solver.py +2445 -0
  269. setiastro/saspro/project_io.py +797 -0
  270. setiastro/saspro/psf_utils.py +136 -0
  271. setiastro/saspro/psf_viewer.py +549 -0
  272. setiastro/saspro/pyi_rthook_astroquery.py +95 -0
  273. setiastro/saspro/remove_green.py +331 -0
  274. setiastro/saspro/remove_stars.py +1599 -0
  275. setiastro/saspro/remove_stars_preset.py +404 -0
  276. setiastro/saspro/resources.py +501 -0
  277. setiastro/saspro/rgb_combination.py +208 -0
  278. setiastro/saspro/rgb_extract.py +19 -0
  279. setiastro/saspro/rgbalign.py +723 -0
  280. setiastro/saspro/runtime_imports.py +7 -0
  281. setiastro/saspro/runtime_torch.py +754 -0
  282. setiastro/saspro/save_options.py +73 -0
  283. setiastro/saspro/selective_color.py +1552 -0
  284. setiastro/saspro/sfcc.py +1472 -0
  285. setiastro/saspro/shortcuts.py +3043 -0
  286. setiastro/saspro/signature_insert.py +1102 -0
  287. setiastro/saspro/stacking_suite.py +18470 -0
  288. setiastro/saspro/star_alignment.py +7435 -0
  289. setiastro/saspro/star_alignment_preset.py +329 -0
  290. setiastro/saspro/star_metrics.py +49 -0
  291. setiastro/saspro/star_spikes.py +765 -0
  292. setiastro/saspro/star_stretch.py +507 -0
  293. setiastro/saspro/stat_stretch.py +538 -0
  294. setiastro/saspro/status_log_dock.py +78 -0
  295. setiastro/saspro/subwindow.py +3328 -0
  296. setiastro/saspro/supernovaasteroidhunter.py +1719 -0
  297. setiastro/saspro/swap_manager.py +99 -0
  298. setiastro/saspro/torch_backend.py +89 -0
  299. setiastro/saspro/torch_rejection.py +434 -0
  300. setiastro/saspro/translations/all_source_strings.json +3654 -0
  301. setiastro/saspro/translations/ar_translations.py +3865 -0
  302. setiastro/saspro/translations/de_translations.py +3749 -0
  303. setiastro/saspro/translations/es_translations.py +3939 -0
  304. setiastro/saspro/translations/fr_translations.py +3858 -0
  305. setiastro/saspro/translations/hi_translations.py +3571 -0
  306. setiastro/saspro/translations/integrate_translations.py +270 -0
  307. setiastro/saspro/translations/it_translations.py +3678 -0
  308. setiastro/saspro/translations/ja_translations.py +3601 -0
  309. setiastro/saspro/translations/pt_translations.py +3869 -0
  310. setiastro/saspro/translations/ru_translations.py +2848 -0
  311. setiastro/saspro/translations/saspro_ar.qm +0 -0
  312. setiastro/saspro/translations/saspro_ar.ts +255 -0
  313. setiastro/saspro/translations/saspro_de.qm +0 -0
  314. setiastro/saspro/translations/saspro_de.ts +253 -0
  315. setiastro/saspro/translations/saspro_es.qm +0 -0
  316. setiastro/saspro/translations/saspro_es.ts +12520 -0
  317. setiastro/saspro/translations/saspro_fr.qm +0 -0
  318. setiastro/saspro/translations/saspro_fr.ts +12514 -0
  319. setiastro/saspro/translations/saspro_hi.qm +0 -0
  320. setiastro/saspro/translations/saspro_hi.ts +257 -0
  321. setiastro/saspro/translations/saspro_it.qm +0 -0
  322. setiastro/saspro/translations/saspro_it.ts +12520 -0
  323. setiastro/saspro/translations/saspro_ja.qm +0 -0
  324. setiastro/saspro/translations/saspro_ja.ts +257 -0
  325. setiastro/saspro/translations/saspro_pt.qm +0 -0
  326. setiastro/saspro/translations/saspro_pt.ts +257 -0
  327. setiastro/saspro/translations/saspro_ru.qm +0 -0
  328. setiastro/saspro/translations/saspro_ru.ts +237 -0
  329. setiastro/saspro/translations/saspro_sw.qm +0 -0
  330. setiastro/saspro/translations/saspro_sw.ts +257 -0
  331. setiastro/saspro/translations/saspro_uk.qm +0 -0
  332. setiastro/saspro/translations/saspro_uk.ts +10771 -0
  333. setiastro/saspro/translations/saspro_zh.qm +0 -0
  334. setiastro/saspro/translations/saspro_zh.ts +12520 -0
  335. setiastro/saspro/translations/sw_translations.py +3671 -0
  336. setiastro/saspro/translations/uk_translations.py +3700 -0
  337. setiastro/saspro/translations/zh_translations.py +3675 -0
  338. setiastro/saspro/versioning.py +77 -0
  339. setiastro/saspro/view_bundle.py +1558 -0
  340. setiastro/saspro/wavescale_hdr.py +645 -0
  341. setiastro/saspro/wavescale_hdr_preset.py +101 -0
  342. setiastro/saspro/wavescalede.py +680 -0
  343. setiastro/saspro/wavescalede_preset.py +230 -0
  344. setiastro/saspro/wcs_update.py +374 -0
  345. setiastro/saspro/whitebalance.py +492 -0
  346. setiastro/saspro/widgets/__init__.py +48 -0
  347. setiastro/saspro/widgets/common_utilities.py +306 -0
  348. setiastro/saspro/widgets/graphics_views.py +122 -0
  349. setiastro/saspro/widgets/image_utils.py +518 -0
  350. setiastro/saspro/widgets/minigame/game.js +986 -0
  351. setiastro/saspro/widgets/minigame/index.html +53 -0
  352. setiastro/saspro/widgets/minigame/style.css +241 -0
  353. setiastro/saspro/widgets/preview_dialogs.py +280 -0
  354. setiastro/saspro/widgets/resource_monitor.py +237 -0
  355. setiastro/saspro/widgets/spinboxes.py +275 -0
  356. setiastro/saspro/widgets/themed_buttons.py +13 -0
  357. setiastro/saspro/widgets/wavelet_utils.py +331 -0
  358. setiastro/saspro/wimi.py +7996 -0
  359. setiastro/saspro/wims.py +578 -0
  360. setiastro/saspro/window_shelf.py +185 -0
  361. setiastro/saspro/xisf.py +1123 -0
  362. setiastrosuitepro-1.6.2.post1.dist-info/METADATA +278 -0
  363. setiastrosuitepro-1.6.2.post1.dist-info/RECORD +367 -0
  364. setiastrosuitepro-1.6.2.post1.dist-info/WHEEL +4 -0
  365. setiastrosuitepro-1.6.2.post1.dist-info/entry_points.txt +6 -0
  366. setiastrosuitepro-1.6.2.post1.dist-info/licenses/LICENSE +674 -0
  367. setiastrosuitepro-1.6.2.post1.dist-info/licenses/license.txt +2580 -0
@@ -0,0 +1,986 @@
1
+ /**
2
+ * Neon Invaders: Hyperdrive
3
+ * Core Game Engine
4
+ */
5
+
6
+ const canvas = document.getElementById('gameCanvas');
7
+ const ctx = canvas.getContext('2d');
8
+
9
+ // --- CONSTANTS ---
10
+ const GAME_WIDTH = 800;
11
+ const GAME_HEIGHT = 600;
12
+ const FPS = 60;
13
+ const DT = 1 / FPS;
14
+
15
+ const COLORS = {
16
+ PLAYER: '#0ff',
17
+ PLAYER_BULLET: '#0ff',
18
+ ENEMY_BASIC: '#f0f',
19
+ ENEMY_FAST: '#0f0',
20
+ ENEMY_HEAVY: '#f00',
21
+ ENEMY_BOSS: '#fff',
22
+ ENEMY_BULLET: '#f00',
23
+ PARTICLE_EXPLOSION: '#fa0',
24
+ PARTICLE_THRUST: '#0ff',
25
+ POWERUP_HP: '#f00',
26
+ POWERUP_HP: '#f00',
27
+ POWERUP_FIRE: '#0ff',
28
+ POWERUP_SHIELD: '#00ffff' // Cyan
29
+ };
30
+
31
+ const KEYS = {
32
+ LEFT: 'ArrowLeft',
33
+ RIGHT: 'ArrowRight',
34
+ UP: 'ArrowUp',
35
+ DOWN: 'ArrowDown',
36
+ SHOOT: ' '
37
+ };
38
+
39
+ // --- GLOBAL STATE ---
40
+ let gameState = 'MENU'; // MENU, PLAYING, GAMEOVER, VICTORY, LEVEL_SELECT
41
+ let lastTime = 0;
42
+ let score = 0;
43
+ let level = 1;
44
+
45
+ // --- INPUT HANDLER ---
46
+ const Input = {
47
+ keys: {},
48
+ init() {
49
+ window.addEventListener('keydown', e => this.keys[e.key] = true);
50
+ window.addEventListener('keyup', e => this.keys[e.key] = false);
51
+ },
52
+ isDown(key) { return this.keys[key]; }
53
+ };
54
+ Input.init();
55
+
56
+ // --- UTILS ---
57
+ const dist = (x1, y1, x2, y2) => Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
58
+ const rectIntersect = (r1, r2) => !(r2.left > r1.right || r2.right < r1.left || r2.top > r1.bottom || r2.bottom < r1.top);
59
+ const rand = (min, max) => Math.random() * (max - min) + min;
60
+
61
+ // --- AUDIO (Placeholder for synth) ---
62
+ const AudioSys = {
63
+ ctx: new (window.AudioContext || window.webkitAudioContext)(),
64
+ playTone(freq, type, duration) {
65
+ if (this.ctx.state === 'suspended') this.ctx.resume();
66
+ const osc = this.ctx.createOscillator();
67
+ const gain = this.ctx.createGain();
68
+ osc.type = type;
69
+ osc.frequency.setValueAtTime(freq, this.ctx.currentTime);
70
+ gain.gain.setValueAtTime(0.1, this.ctx.currentTime);
71
+ gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + duration);
72
+ osc.connect(gain);
73
+ gain.connect(this.ctx.destination);
74
+ osc.start();
75
+ osc.stop(this.ctx.currentTime + duration);
76
+ },
77
+ shoot() { this.playTone(400, 'square', 0.1); },
78
+ explosion() { this.playTone(100, 'sawtooth', 0.3); },
79
+ hit() { this.playTone(200, 'sawtooth', 0.1); }
80
+ };
81
+
82
+ // --- CLASSES ---
83
+
84
+ class Starfield {
85
+ constructor() {
86
+ this.stars = [];
87
+ this.speed = 2; // Base speed
88
+ for (let i = 0; i < 100; i++) this.addStar(true);
89
+ }
90
+
91
+ addStar(randomY = false) {
92
+ this.stars.push({
93
+ x: rand(0, GAME_WIDTH),
94
+ y: randomY ? rand(0, GAME_HEIGHT) : -10,
95
+ z: rand(0.5, 2), // Depth factor for parallax
96
+ size: rand(0.5, 2)
97
+ });
98
+ }
99
+
100
+ update() {
101
+ // Speed locked (User Request: "same as level 1")
102
+ const currentSpeed = this.speed;
103
+ this.stars.forEach(s => {
104
+ s.y += currentSpeed * s.z;
105
+ if (s.y > GAME_HEIGHT) {
106
+ s.y = -10;
107
+ s.x = rand(0, GAME_WIDTH);
108
+ }
109
+ });
110
+ }
111
+
112
+ draw(ctx) {
113
+ // Change Star color based on level - More saturated/distinct
114
+ const colors = ['#ffffff', '#00ff00', '#ffff00', '#ff00ff', '#ff0000', '#00ffff', '#00ff00', '#ffff00', '#ff00ff', '#ff0000'];
115
+ ctx.fillStyle = colors[(level - 1) % colors.length] || '#fff';
116
+
117
+ this.stars.forEach(s => {
118
+ ctx.globalAlpha = s.z / 2;
119
+ ctx.beginPath();
120
+ ctx.arc(s.x, s.y, s.size, 0, Math.PI * 2);
121
+ ctx.fill();
122
+ });
123
+ ctx.globalAlpha = 1;
124
+ }
125
+ }
126
+
127
+ class Particle {
128
+ constructor(x, y, color, speed, life) {
129
+ this.x = x;
130
+ this.y = y;
131
+ this.color = color;
132
+ this.life = life;
133
+ this.maxLife = life;
134
+ const angle = rand(0, Math.PI * 2);
135
+ this.vx = Math.cos(angle) * speed;
136
+ this.vy = Math.sin(angle) * speed;
137
+ this.size = rand(1, 3);
138
+ }
139
+ update() {
140
+ this.x += this.vx;
141
+ this.y += this.vy;
142
+ this.life--;
143
+ this.size *= 0.95;
144
+ }
145
+ draw(ctx) {
146
+ ctx.fillStyle = this.color;
147
+ ctx.globalAlpha = this.life / this.maxLife;
148
+ ctx.beginPath();
149
+ ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
150
+ ctx.fill();
151
+ ctx.globalAlpha = 1;
152
+ }
153
+ }
154
+
155
+ class Powerup {
156
+ constructor(type) {
157
+ this.type = type; // 'hp' or 'fire'
158
+ this.x = rand(50, GAME_WIDTH - 50);
159
+ this.y = -30;
160
+ this.width = 30;
161
+ this.height = 30;
162
+ this.speed = 2;
163
+ this.active = true;
164
+ this.speed = 2;
165
+ this.active = true;
166
+ this.color = type === 'hp' ? COLORS.POWERUP_HP : (type === 'fire' ? COLORS.POWERUP_FIRE : COLORS.POWERUP_SHIELD);
167
+ }
168
+
169
+ update() {
170
+ this.y += this.speed;
171
+ if (this.y > GAME_HEIGHT + 30) this.active = false;
172
+ }
173
+
174
+ draw(ctx) {
175
+ ctx.fillStyle = this.color;
176
+ ctx.shadowBlur = 15;
177
+ ctx.shadowColor = this.color;
178
+ ctx.beginPath();
179
+ if (this.type === 'hp') {
180
+ // Heart shape approximation or Cross
181
+ ctx.fillRect(this.x - 10, this.y - 4, 20, 8);
182
+ ctx.fillRect(this.x - 4, this.y - 10, 8, 20);
183
+ } else {
184
+ // Lightning bolt / Energy
185
+ ctx.moveTo(this.x, this.y - 15);
186
+ ctx.lineTo(this.x + 10, this.y);
187
+ ctx.lineTo(this.x - 5, this.y);
188
+ ctx.lineTo(this.x + 5, this.y + 15);
189
+ ctx.lineTo(this.x - 10, this.y);
190
+ ctx.lineTo(this.x + 5, this.y);
191
+ ctx.closePath();
192
+ ctx.fill();
193
+ }
194
+ ctx.shadowBlur = 0;
195
+ ctx.fill();
196
+ }
197
+
198
+ getBounds() {
199
+ return { left: this.x - 15, right: this.x + 15, top: this.y - 15, bottom: this.y + 15 };
200
+ }
201
+ }
202
+
203
+ class Bullet {
204
+ constructor(x, y, vy, isPlayer) {
205
+ this.x = x;
206
+ this.y = y;
207
+ this.vy = vy;
208
+ this.isPlayer = isPlayer;
209
+ this.width = 4;
210
+ this.height = 10;
211
+ this.active = true;
212
+ }
213
+
214
+ update() {
215
+ this.y += this.vy;
216
+ if (this.y < -50 || this.y > GAME_HEIGHT + 50) this.active = false;
217
+ }
218
+
219
+ draw(ctx) {
220
+ ctx.fillStyle = this.isPlayer ? COLORS.PLAYER_BULLET : COLORS.ENEMY_BULLET;
221
+ ctx.shadowBlur = 5;
222
+ ctx.shadowColor = ctx.fillStyle;
223
+ ctx.fillRect(this.x - this.width / 2, this.y - this.height / 2, this.width, this.height);
224
+ ctx.shadowBlur = 0;
225
+ }
226
+
227
+ getBounds() {
228
+ return { left: this.x - 2, right: this.x + 2, top: this.y - 5, bottom: this.y + 5 };
229
+ }
230
+ }
231
+
232
+ class Player {
233
+ constructor() {
234
+ this.width = 40;
235
+ this.height = 40;
236
+ this.x = GAME_WIDTH / 2;
237
+ this.y = GAME_HEIGHT - 100;
238
+ this.speed = 5;
239
+ this.hp = 100;
240
+ this.maxHp = 100;
241
+ this.cooldown = 0;
242
+ this.fireRate = 10;
243
+ this.fireRate = 10;
244
+ this.fireRateTimer = null; // Track timeout ID
245
+ this.dead = false;
246
+ this.shield = 0; // Shield strength (0 or 1)
247
+ }
248
+
249
+ update() {
250
+ if (this.dead) return;
251
+
252
+ // Movement
253
+ let dx = 0;
254
+ let dy = 0;
255
+ if (Input.isDown(KEYS.LEFT)) dx -= this.speed;
256
+ if (Input.isDown(KEYS.RIGHT)) dx += this.speed;
257
+ if (Input.isDown(KEYS.UP)) dy -= this.speed;
258
+ if (Input.isDown(KEYS.DOWN)) dy += this.speed;
259
+
260
+ this.x += dx;
261
+ this.y += dy;
262
+
263
+ // Clamp
264
+ this.x = Math.max(this.width / 2, Math.min(GAME_WIDTH - this.width / 2, this.x));
265
+ this.y = Math.max(this.height / 2, Math.min(GAME_HEIGHT - this.height / 2, this.y));
266
+
267
+ // Shoot
268
+ if (this.cooldown > 0) this.cooldown--;
269
+ if (Input.isDown(KEYS.SHOOT) && this.cooldown <= 0) {
270
+ Game.bullets.push(new Bullet(this.x, this.y - 20, -10, true));
271
+ if (this.fireRate < 10) { // Double shot for fast fire
272
+ setTimeout(() => Game.bullets.push(new Bullet(this.x, this.y - 20, -10, true)), 100);
273
+ }
274
+ AudioSys.shoot();
275
+ this.cooldown = this.fireRate;
276
+ }
277
+
278
+ // Thruster particles
279
+ if (Math.random() < 0.5) {
280
+ Game.particles.push(new Particle(this.x + rand(-5, 5), this.y + 20, COLORS.PARTICLE_THRUST, 1, 20));
281
+ }
282
+ }
283
+
284
+ draw(ctx) {
285
+ if (this.dead) return;
286
+ ctx.strokeStyle = COLORS.PLAYER;
287
+ ctx.lineWidth = 2;
288
+ ctx.shadowColor = COLORS.PLAYER;
289
+ ctx.shadowBlur = 10;
290
+
291
+ // Draw Ship (Triangle shape)
292
+ ctx.beginPath();
293
+ ctx.moveTo(this.x, this.y - 20); // Nose
294
+ ctx.lineTo(this.x + 20, this.y + 20); // Right Wing
295
+ ctx.lineTo(this.x, this.y + 10); // Center Engine
296
+ ctx.lineTo(this.x - 20, this.y + 20); // Left Wing
297
+ ctx.closePath();
298
+ ctx.stroke();
299
+
300
+ ctx.fillStyle = COLORS.PLAYER;
301
+ ctx.fill();
302
+
303
+ ctx.shadowBlur = 0;
304
+ }
305
+
306
+ hit(damage) {
307
+ if (this.shield > 0) {
308
+ this.shield--; // Absorb hit
309
+ AudioSys.hit(); // Maybe different sound?
310
+ return;
311
+ }
312
+ this.hp -= damage;
313
+ AudioSys.hit();
314
+ updateHUD();
315
+ if (this.hp <= 0) {
316
+ this.hp = 0;
317
+ this.dead = true;
318
+ Game.createExplosion(this.x, this.y, 50, COLORS.PLAYER);
319
+ setTimeout(() => Game.gameOver(), 1000);
320
+ }
321
+ }
322
+
323
+ getBounds() {
324
+ return { left: this.x - 15, right: this.x + 15, top: this.y - 15, bottom: this.y + 15 };
325
+ }
326
+ }
327
+
328
+ class Enemy {
329
+ constructor(type) {
330
+ this.type = type;
331
+ this.width = 30;
332
+ this.height = 30;
333
+ this.x = rand(50, GAME_WIDTH - 50);
334
+ this.y = -50;
335
+ this.active = true;
336
+ this.timer = Math.floor(rand(0, 100)); // Randomize start timer so they don't sync
337
+ this.lastShot = 0; // frame count
338
+
339
+
340
+ // Define stats based on type
341
+ switch (type) {
342
+ case 'basic':
343
+ this.hp = 2; // User requested 2 hits
344
+ this.score = 100;
345
+ this.speed = 1.5;
346
+ this.color = COLORS.ENEMY_BASIC;
347
+ this.shootInterval = 3 * 60; // 3 seconds (was 5)
348
+ break;
349
+ case 'fast':
350
+ this.hp = 1;
351
+ this.score = 50; // User requested 50
352
+ this.speed = 3;
353
+ this.width = 20; this.height = 20;
354
+ this.color = COLORS.ENEMY_FAST;
355
+ this.shootInterval = 0; // Doesn't shoot usually
356
+ break;
357
+ case 'heavy':
358
+ this.hp = 5; // Reduced from 10 to 5 (User Request)
359
+ this.speed = 0.8;
360
+ this.score = 250; // User requested 250
361
+ this.width = 50; this.height = 50;
362
+ this.color = COLORS.ENEMY_HEAVY;
363
+ this.shootInterval = 3 * 60; // 3 seconds
364
+ break;
365
+ }
366
+ }
367
+
368
+ update() {
369
+ this.y += this.speed;
370
+ this.timer++;
371
+
372
+ // Shooting logic replacement
373
+ // "I nemici non sparano se non arrivano ad un certo punto" -> Check if y > 50 (fully on screen)
374
+ if (this.shootInterval > 0 && this.timer % this.shootInterval === 0 && this.y > 50 && this.y < GAME_HEIGHT - 50) {
375
+ Game.bullets.push(new Bullet(this.x, this.y + this.height / 2, 5, false));
376
+ }
377
+
378
+ // Basic AI movement
379
+ if (this.type === 'fast') {
380
+ this.x += Math.sin(this.timer * 0.1) * 2;
381
+ }
382
+
383
+ // Random erratic shots for basic rarely? No, strict interval now.
384
+ // Removing old random shooting logic.
385
+
386
+ // Constrain X
387
+ if (this.x < 0) this.x = 0;
388
+ if (this.x > GAME_WIDTH) this.x = GAME_WIDTH;
389
+ }
390
+
391
+ draw(ctx) {
392
+ ctx.strokeStyle = this.color;
393
+ ctx.lineWidth = 2;
394
+ ctx.shadowColor = this.color;
395
+ ctx.shadowBlur = 10;
396
+
397
+ ctx.beginPath();
398
+ if (this.type === 'basic') {
399
+ ctx.rect(this.x - 15, this.y - 15, 30, 30);
400
+ } else if (this.type === 'fast') {
401
+ ctx.moveTo(this.x, this.y + 10);
402
+ ctx.lineTo(this.x + 10, this.y - 10);
403
+ ctx.lineTo(this.x - 10, this.y - 10);
404
+ ctx.closePath();
405
+ } else if (this.type === 'heavy') {
406
+ ctx.arc(this.x, this.y, 25, 0, Math.PI * 2);
407
+ }
408
+ ctx.stroke();
409
+ ctx.shadowBlur = 0;
410
+ }
411
+
412
+ hit(damage) {
413
+ if (this.y < 0) return; // Cannot hit off-screen enemies
414
+ this.hp -= damage;
415
+ AudioSys.hit();
416
+ if (this.hp <= 0) {
417
+ this.active = false;
418
+ score += this.score;
419
+ Game.createExplosion(this.x, this.y, 20, this.color);
420
+ updateHUD();
421
+ Game.checkLevelProgress();
422
+ }
423
+ }
424
+
425
+ getBounds() {
426
+ let w = this.width / 2;
427
+ let h = this.height / 2;
428
+ return { left: this.x - w, right: this.x + w, top: this.y - h, bottom: this.y + h };
429
+ }
430
+ }
431
+
432
+ class Boss {
433
+ constructor() {
434
+ this.width = 120;
435
+ this.height = 80;
436
+ this.x = GAME_WIDTH / 2;
437
+ this.y = -100;
438
+ this.active = true;
439
+ this.hp = 500;
440
+ this.maxHp = 500;
441
+ this.speed = 2;
442
+ this.timer = 0;
443
+ this.dir = 1;
444
+ this.color = COLORS.ENEMY_BOSS;
445
+ this.score = 5000;
446
+ }
447
+
448
+ update() {
449
+ this.timer++;
450
+
451
+ // Entrance
452
+ if (this.y < 100) {
453
+ this.y += 1;
454
+ } else {
455
+ // Horizontal Movement
456
+ this.x += this.speed * this.dir;
457
+ if (this.x > GAME_WIDTH - 80 || this.x < 80) this.dir *= -1;
458
+ }
459
+
460
+ // Attacks
461
+ // 1. Triple Shot (Front)
462
+ if (this.timer % 60 === 0) {
463
+ Game.bullets.push(new Bullet(this.x, this.y + 40, 5, false));
464
+ Game.bullets.push(new Bullet(this.x - 30, this.y + 30, 5, false));
465
+ Game.bullets.push(new Bullet(this.x + 30, this.y + 30, 5, false));
466
+ }
467
+
468
+ // 2. Spread Shot (every 3s)
469
+ if (this.timer % 180 === 0) {
470
+ for (let i = -2; i <= 2; i++) {
471
+ // Fake angle by setting vx
472
+ let b = new Bullet(this.x, this.y + 40, 4, false);
473
+ b.x += i * 15; // Offset start
474
+ b.width = 8;
475
+ Game.bullets.push(b);
476
+ }
477
+ }
478
+ }
479
+
480
+ draw(ctx) {
481
+ ctx.fillStyle = this.color;
482
+ ctx.shadowColor = this.color;
483
+ ctx.shadowBlur = 20;
484
+
485
+ // Custom shape for Mothership
486
+ ctx.beginPath();
487
+ ctx.moveTo(this.x, this.y + 40);
488
+ ctx.lineTo(this.x + 60, this.y - 20);
489
+ ctx.lineTo(this.x + 30, this.y - 40);
490
+ ctx.lineTo(this.x - 30, this.y - 40);
491
+ ctx.lineTo(this.x - 60, this.y - 20);
492
+ ctx.closePath();
493
+ ctx.fill();
494
+
495
+ // Reactor Core
496
+ ctx.fillStyle = '#ff0000';
497
+ ctx.beginPath();
498
+ ctx.arc(this.x, this.y, 15, 0, Math.PI * 2);
499
+ ctx.fill();
500
+
501
+ ctx.shadowBlur = 0;
502
+
503
+ // HP Bar (Above Boss)
504
+ ctx.fillStyle = '#555';
505
+ ctx.fillRect(this.x - 50, this.y - 60, 100, 10);
506
+ ctx.fillStyle = '#f00';
507
+ ctx.fillRect(this.x - 50, this.y - 60, 100 * (this.hp / this.maxHp), 10);
508
+ }
509
+
510
+ hit(damage) {
511
+ this.hp -= damage;
512
+ AudioSys.hit();
513
+ if (this.hp <= 0) {
514
+ this.active = false;
515
+ score += this.score;
516
+ Game.createExplosion(this.x, this.y, 100, '#fff');
517
+ updateHUD();
518
+ Game.victory(); // Win game after boss
519
+ }
520
+ }
521
+
522
+ getBounds() {
523
+ return { left: this.x - 50, right: this.x + 50, top: this.y - 30, bottom: this.y + 30 };
524
+ }
525
+ }
526
+
527
+ // --- GAME MANAGER ---
528
+ const Game = {
529
+ player: null,
530
+ bullets: [],
531
+ enemies: [],
532
+ particles: [],
533
+ powerups: [],
534
+ starfield: null,
535
+ waveTimer: 0,
536
+ enemiesToSpawn: 0,
537
+ spawnTimer: 0,
538
+ powerupQueue: [],
539
+ starfield: null,
540
+ waveTimer: 0,
541
+ enemiesToSpawn: 0,
542
+ spawnTimer: 0,
543
+
544
+ init() {
545
+ canvas.width = GAME_WIDTH;
546
+ canvas.height = GAME_HEIGHT;
547
+ this.starfield = new Starfield();
548
+ this.setupUI();
549
+ requestAnimationFrame(loop);
550
+ },
551
+
552
+ shake: 0,
553
+ triggerShake(amount) {
554
+ this.shake = amount;
555
+ },
556
+
557
+ startLevel(startLevel) {
558
+ level = startLevel;
559
+ if (!this.player) this.player = new Player();
560
+ this.bullets = [];
561
+ this.enemies = [];
562
+ this.particles = [];
563
+ this.powerups = [];
564
+ this.enemiesToSpawn = 10 * level;
565
+ this.waveTimer = 0;
566
+
567
+ // Initialize Counts (Base: 1 Fire, 1 HP)
568
+ let hpCount = 1;
569
+ let utilCount = 1;
570
+
571
+ if (level >= 3) hpCount++;
572
+ if (level >= 6) { hpCount += 2; utilCount += 2; } // L6-8: 4 HP, 3 Util
573
+ if (level >= 9) { hpCount += 1; utilCount += 1; } // L9-10: 5 HP, 4 Util
574
+
575
+ // 1. Utility
576
+ this.powerupQueue = [];
577
+ let timeOffset = 180 + rand(100, 300);
578
+ this.powerupQueue.push({ type: 'fire', time: timeOffset });
579
+
580
+ if (level >= 2) {
581
+ for (let i = 0; i < utilCount - 1; i++) {
582
+ timeOffset += rand(150, 400); // Faster intervals
583
+ this.powerupQueue.push({ type: 'shield', time: timeOffset });
584
+ }
585
+ }
586
+
587
+ // 2. HP
588
+ for (let i = 0; i < hpCount; i++) {
589
+ timeOffset += rand(150, 400); // Faster intervals
590
+ this.powerupQueue.push({ type: 'hp', time: timeOffset });
591
+ }
592
+ },
593
+
594
+ start(startLevel = 1) {
595
+ level = startLevel;
596
+ score = 0;
597
+ this.player = new Player();
598
+ this.bullets = [];
599
+ this.enemies = [];
600
+ this.particles = [];
601
+ this.powerups = [];
602
+ this.enemiesToSpawn = 10 * level; // Simple scaling
603
+
604
+ // Schedule powerups (frame count)
605
+ this.powerupQueue = [];
606
+ let timeOffset = rand(300, 600); // Start spawning after 5-10s
607
+
608
+ // Initialize Counts (Base: 1 Fire, 1 HP)
609
+ let hpCount = 1;
610
+ let utilCount = 1;
611
+
612
+ if (level >= 3) hpCount++;
613
+ if (level >= 6) { hpCount += 2; utilCount += 2; } // L6-8: 4 HP, 3 Util
614
+ if (level >= 9) { hpCount += 1; utilCount += 1; } // L9-10: 5 HP, 4 Util
615
+
616
+ // 1. Utility Powerups (Fire / Shield)
617
+ // Fire always 1
618
+ this.powerupQueue.push({ type: 'fire', time: timeOffset });
619
+
620
+ // Remaining Utility slots used for Shield (if Level >= 2)
621
+ if (level >= 2) {
622
+ // We used 1 for Fire, so loop remaining utilCount - 1
623
+ for (let i = 0; i < utilCount - 1; i++) {
624
+ timeOffset += rand(150, 400); // Faster intervals
625
+ this.powerupQueue.push({ type: 'shield', time: timeOffset });
626
+ }
627
+ }
628
+
629
+ // 2. HP Powerups
630
+ for (let i = 0; i < hpCount; i++) {
631
+ timeOffset += rand(150, 400); // Faster intervals
632
+ this.powerupQueue.push({ type: 'hp', time: timeOffset });
633
+ }
634
+
635
+ gameState = 'PLAYING';
636
+
637
+ document.getElementById('main-menu').classList.add('hidden');
638
+ document.getElementById('level-select').classList.add('hidden');
639
+ document.getElementById('game-over').classList.add('hidden');
640
+ document.getElementById('victory').classList.add('hidden');
641
+ document.getElementById('hud').classList.remove('hidden');
642
+ updateHUD();
643
+ },
644
+
645
+ setupUI() {
646
+ // Main Menu
647
+ document.getElementById('btn-start').onclick = () => this.start(1);
648
+ document.getElementById('btn-levels').onclick = () => {
649
+ document.getElementById('main-menu').classList.add('hidden');
650
+ document.getElementById('level-select').classList.remove('hidden');
651
+ this.renderLevelGrid();
652
+ };
653
+ // Level Select
654
+ document.getElementById('btn-back').onclick = () => {
655
+ document.getElementById('level-select').classList.add('hidden');
656
+ document.getElementById('main-menu').classList.remove('hidden');
657
+ };
658
+ // Game Over
659
+ document.getElementById('btn-retry').onclick = () => this.start(level);
660
+ document.getElementById('btn-menu').onclick = () => this.showMenu();
661
+ // Victory
662
+ document.getElementById('btn-menu-win').onclick = () => this.showMenu();
663
+ },
664
+
665
+ renderLevelGrid() {
666
+ const grid = document.getElementById('level-grid');
667
+ grid.innerHTML = '';
668
+ for (let i = 1; i <= 10; i++) {
669
+ const btn = document.createElement('button');
670
+ btn.className = 'level-btn';
671
+ btn.innerText = `LEVEL ${i}`;
672
+ btn.onclick = () => this.start(i);
673
+ grid.appendChild(btn);
674
+ }
675
+ },
676
+
677
+ showMenu() {
678
+ gameState = 'MENU';
679
+ document.getElementById('game-over').classList.add('hidden');
680
+ document.getElementById('victory').classList.add('hidden');
681
+ document.getElementById('hud').classList.add('hidden');
682
+ document.getElementById('main-menu').classList.remove('hidden');
683
+ },
684
+
685
+ gameOver() {
686
+ gameState = 'GAMEOVER';
687
+ document.getElementById('final-score').innerText = score;
688
+ document.getElementById('game-over').classList.remove('hidden');
689
+ },
690
+
691
+ victory() {
692
+ gameState = 'VICTORY';
693
+ document.getElementById('victory-score').innerText = score;
694
+ document.getElementById('victory').classList.remove('hidden');
695
+ },
696
+
697
+ spawnPowerup(type) {
698
+ this.powerups.push(new Powerup(type));
699
+ },
700
+
701
+ showLevelTransition() {
702
+ // Warp Effect Removed
703
+ // Text overlay
704
+ const t = document.createElement('div');
705
+ t.innerText = `LEVEL ${level}`;
706
+ t.innerText += `\nGET READY`;
707
+ t.style.textAlign = 'center';
708
+ t.className = 'level-transition';
709
+ t.style.position = 'absolute';
710
+ t.style.top = '50%';
711
+ t.style.left = '50%';
712
+ t.style.transform = 'translate(-50%, -50%)';
713
+ t.style.fontSize = '4rem';
714
+ t.style.color = '#fff';
715
+ t.style.textShadow = `0 0 20px ${this.getLevelColor()}`;
716
+ t.style.fontWeight = 'bold';
717
+ t.style.zIndex = '100';
718
+ t.style.animation = 'fadeUp 2s forwards';
719
+ document.body.appendChild(t);
720
+
721
+ const originalSpeed = this.starfield.speed;
722
+ // User requested removing warp speed effect
723
+ // this.starfield.speed = 20;
724
+ setTimeout(() => {
725
+ t.remove();
726
+ // this.starfield.speed = originalSpeed;
727
+ }, 2000);
728
+ },
729
+
730
+ getLevelColor() {
731
+ // Enhanced colors for text shadow
732
+ const colors = ['#00ffff', '#00ff00', '#ffff00', '#ff00ff', '#ff0000', '#00ffff', '#00ff00', '#ffff00', '#ff00ff', '#ff0000'];
733
+ return colors[(level - 1) % colors.length];
734
+ },
735
+
736
+ createExplosion(x, y, count, color) {
737
+ AudioSys.explosion();
738
+ this.triggerShake(count / 2);
739
+ for (let i = 0; i < count; i++) {
740
+ this.particles.push(new Particle(x, y, color, rand(1, 4), rand(20, 40)));
741
+ }
742
+ },
743
+
744
+ spawnEnemy() {
745
+ if (this.enemiesToSpawn <= 0) return;
746
+
747
+ // Spawn logic based on level
748
+ let type = 'basic';
749
+ const r = Math.random();
750
+
751
+ if (level === 2) {
752
+ if (r < 0.2) type = 'fast';
753
+ } else if (level >= 3) {
754
+ if (r < 0.3) type = 'fast';
755
+ if (r > 0.9) type = 'heavy';
756
+ } else if (level === 5 && this.enemiesToSpawn === 1) {
757
+ // Boss Spawn (Mid-point)
758
+ this.enemies.push(new Boss());
759
+ this.enemiesToSpawn--;
760
+ return;
761
+ } else if (level >= 6 && level < 10) {
762
+ // Harder scaling for 6-9
763
+ if (r < 0.4) type = 'fast';
764
+ if (r > 0.8) type = 'heavy';
765
+ } else if (level === 10 && this.enemiesToSpawn === 1) {
766
+ // Final Boss
767
+ let boss = new Boss();
768
+ boss.hp = 1000; // Double HP
769
+ boss.maxHp = 1000;
770
+ boss.color = '#fff'; // White Boss
771
+ this.enemies.push(boss);
772
+ this.enemiesToSpawn--;
773
+ return;
774
+ }
775
+
776
+ this.enemies.push(new Enemy(type));
777
+ this.enemiesToSpawn--;
778
+ },
779
+
780
+ checkLevelProgress() {
781
+ if (this.enemiesToSpawn === 0 && this.enemies.filter(e => e.active).length === 0) {
782
+ // Next Level
783
+ if (level === 10) {
784
+ this.victory();
785
+ } else {
786
+ this.nextLevel();
787
+ }
788
+ }
789
+ },
790
+
791
+ nextLevel() {
792
+ level++;
793
+ this.enemiesToSpawn = 10 * level + 5;
794
+ this.waveTimer = 0;
795
+
796
+ // Initialize Counts (Base: 1 Fire, 1 HP)
797
+ let hpCount = 1;
798
+ let utilCount = 1;
799
+
800
+ if (level >= 3) hpCount++;
801
+ if (level >= 6) { hpCount += 2; utilCount += 2; } // L6-8: 4 HP, 3 Util
802
+ if (level >= 9) { hpCount += 1; utilCount += 1; } // L9-10: 5 HP, 4 Util
803
+
804
+ // 1. Utility
805
+ this.powerupQueue = [];
806
+ let timeOffset = 180 + rand(100, 300);
807
+ this.powerupQueue.push({ type: 'fire', time: timeOffset });
808
+
809
+ if (level >= 2) {
810
+ for (let i = 0; i < utilCount - 1; i++) {
811
+ timeOffset += rand(150, 400); // Faster intervals
812
+ this.powerupQueue.push({ type: 'shield', time: timeOffset });
813
+ }
814
+ }
815
+
816
+ // 2. HP
817
+ for (let i = 0; i < hpCount; i++) {
818
+ timeOffset += rand(150, 400); // Faster intervals
819
+ this.powerupQueue.push({ type: 'hp', time: timeOffset });
820
+ }
821
+
822
+
823
+
824
+ this.showLevelTransition();
825
+ updateHUD();
826
+ },
827
+
828
+ update() {
829
+ if (gameState !== 'PLAYING') {
830
+ this.starfield.update(); // Keep stars moving in menu
831
+ return;
832
+ }
833
+
834
+ this.starfield.update();
835
+ this.player.update();
836
+
837
+ // Spawning
838
+ this.spawnTimer++;
839
+
840
+ // Delay spawning at start of level (3 seconds = 180 frames) or if enemiesToSpawn is empty
841
+ if (this.waveTimer >= 180) {
842
+ // Faster spawn rate: Cap at Level 5
843
+ // Level 1: 90 - 8 = 82 frames.
844
+ // Level 5: 90 - 40 = 50 frames.
845
+ // Level 10: Still 50 frames (using Math.min)
846
+ const effectiveLevel = Math.min(level, 5);
847
+ if (this.spawnTimer > 90 - (effectiveLevel * 8)) {
848
+ this.spawnEnemy();
849
+ this.spawnTimer = 0;
850
+ }
851
+ }
852
+
853
+ // Powerup Spawning
854
+ this.waveTimer++;
855
+ for (let i = this.powerupQueue.length - 1; i >= 0; i--) {
856
+ if (this.waveTimer >= this.powerupQueue[i].time) {
857
+ this.spawnPowerup(this.powerupQueue[i].type);
858
+ this.powerupQueue.splice(i, 1);
859
+ }
860
+ }
861
+
862
+ // Entities
863
+ this.bullets = this.bullets.filter(b => b.active);
864
+ this.bullets.forEach(b => b.update());
865
+
866
+ this.powerups = this.powerups.filter(p => p.active);
867
+ this.powerups.forEach(p => p.update());
868
+
869
+ this.enemies = this.enemies.filter(e => e.active);
870
+ this.enemies.forEach(e => e.update());
871
+
872
+ this.particles = this.particles.filter(p => p.life > 0);
873
+ this.particles.forEach(p => p.update());
874
+
875
+ // Collisions
876
+ // Player Bullets -> Enemies
877
+ this.bullets.filter(b => b.isPlayer).forEach(b => {
878
+ this.enemies.forEach(e => {
879
+ if (rectIntersect(b.getBounds(), e.getBounds()) && e.y > 0) { // Check visibility
880
+ b.active = false;
881
+ e.hit(Game.player.fireRate < 10 ? 2 : 1); // Double damage if fast fire? Or just more bullets. Keep damage 1 but fire faster.
882
+ Game.createExplosion(b.x, b.y, 5, b.isPlayer ? COLORS.PLAYER_BULLET : COLORS.ENEMY_BULLET);
883
+ }
884
+ });
885
+ });
886
+
887
+ // Player -> Powerups
888
+ this.powerups.forEach(p => {
889
+ if (rectIntersect(p.getBounds(), this.player.getBounds())) {
890
+ p.active = false;
891
+ if (p.type === 'hp') {
892
+ this.player.hp = this.player.maxHp; // Full Restore
893
+ updateHUD();
894
+ } else if (p.type === 'fire') {
895
+ this.player.fireRate = 5;
896
+ // Clear existing timer if any to extend duration instead of cutting it short
897
+ if (this.player.fireRateTimer) clearTimeout(this.player.fireRateTimer);
898
+ this.player.fireRateTimer = setTimeout(() => {
899
+ if (this.player) {
900
+ this.player.fireRate = 10;
901
+ this.player.fireRateTimer = null;
902
+ }
903
+ }, 5000);
904
+ } else if (p.type === 'shield') {
905
+ this.player.shield = 1;
906
+ }
907
+ }
908
+ });
909
+
910
+ // Enemy Bullets -> Player
911
+ this.bullets.filter(b => !b.isPlayer).forEach(b => {
912
+ if (rectIntersect(b.getBounds(), this.player.getBounds())) {
913
+ b.active = false;
914
+ this.player.hit(20);
915
+ }
916
+ });
917
+
918
+ // Enemies -> Player (Crash) or Escaped
919
+ this.enemies.forEach(e => {
920
+ if (e.y > GAME_HEIGHT + 30) {
921
+ e.active = false;
922
+ // Penalty for letting enemy pass
923
+ this.player.hit(10);
924
+ Game.checkLevelProgress();
925
+ } else if (rectIntersect(e.getBounds(), this.player.getBounds())) {
926
+ e.hit(100); // Enemy dies
927
+ this.player.hit(30); // Player takes damage
928
+ }
929
+ });
930
+ },
931
+
932
+ draw() {
933
+ // Clear - Background Color changes significantly per level
934
+ // Clear - Background Color changes significantly per level
935
+ // More visible changes: Dark Blue, Dark Green, Dark Olive, Dark Purple, Dark Red
936
+ const bgColors = ['#000022', '#002200', '#222200', '#220022', '#220000', '#000022', '#002200', '#222200', '#220022', '#220000'];
937
+ ctx.fillStyle = bgColors[(level - 1) % bgColors.length];
938
+ ctx.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
939
+
940
+ ctx.save();
941
+ if (this.shake > 0) {
942
+ const dx = Math.random() * this.shake - this.shake / 2;
943
+ const dy = Math.random() * this.shake - this.shake / 2;
944
+ ctx.translate(dx, dy);
945
+ this.shake *= 0.9;
946
+ if (this.shake < 0.5) this.shake = 0;
947
+ }
948
+
949
+ // Stars
950
+ this.starfield.draw(ctx);
951
+
952
+ if (gameState === 'PLAYING') {
953
+ this.player.draw(ctx);
954
+ this.powerups.forEach(p => p.draw(ctx));
955
+ this.enemies.forEach(e => e.draw(ctx));
956
+ this.bullets.forEach(b => b.draw(ctx));
957
+ this.particles.forEach(p => p.draw(ctx));
958
+ }
959
+ ctx.restore();
960
+ }
961
+ };
962
+
963
+ function updateHUD() {
964
+ document.getElementById('score').innerText = score;
965
+ document.getElementById('level').innerText = level;
966
+ if (Game.player) {
967
+ const pct = (Game.player.hp / Game.player.maxHp) * 100;
968
+ document.getElementById('hp-fill').style.width = `${Math.max(0, pct)}%`;
969
+ }
970
+ }
971
+
972
+ // Game Loop
973
+ let lastTimeMs = 0;
974
+ function loop(timestamp) {
975
+ const dt = timestamp - lastTimeMs;
976
+ lastTimeMs = timestamp;
977
+
978
+ Game.update();
979
+ Game.draw();
980
+
981
+ requestAnimationFrame(loop);
982
+ }
983
+
984
+ // Start
985
+ Game.init();
986
+ window.Game = Game; // Expose for debugging