From d18050b181e2edaa51f8ed0b60fce3c1d179a350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Ta=C5=84czyk?= Date: Sun, 25 Aug 2024 12:53:14 +0300 Subject: [PATCH] drop preact dependency --- js13k2024/game/index.html | 37 +- js13k2024/game/package-lock.json | 884 ------------------ js13k2024/game/package.json | 2 - .../src/game-states/game-over/game-over.ts | 41 + .../src/game-states/game-over/game-over.tsx | 20 - .../game/src/game-states/gameplay/gameplay.ts | 158 ++++ .../src/game-states/gameplay/gameplay.tsx | 163 ---- .../game/src/game-states/gameplay/hud.ts | 43 + .../game/src/game-states/gameplay/hud.tsx | 17 - .../game-states/instructions/instructions.ts | 164 ++++ .../game-states/instructions/instructions.tsx | 101 -- .../src/game-states/intro/game-preview.ts | 127 +++ .../src/game-states/intro/game-preview.tsx | 112 --- js13k2024/game/src/game-states/intro/intro.ts | 48 + .../game/src/game-states/intro/intro.tsx | 42 - .../level-complete/level-complete.ts | 35 + .../level-complete/level-complete.tsx | 18 - .../game-states/level-story/level-story.ts | 48 + js13k2024/game/src/global-styles.css | 2 +- js13k2024/game/src/index.ts | 11 + js13k2024/game/src/index.tsx | 4 - js13k2024/game/src/main.ts | 247 +++++ js13k2024/game/src/main.tsx | 150 --- js13k2024/game/src/utils/dom.ts | 124 +++ js13k2024/game/tsconfig.json | 8 - js13k2024/game/vite.config.mts | 3 +- 26 files changed, 1067 insertions(+), 1542 deletions(-) create mode 100644 js13k2024/game/src/game-states/game-over/game-over.ts delete mode 100644 js13k2024/game/src/game-states/game-over/game-over.tsx create mode 100644 js13k2024/game/src/game-states/gameplay/gameplay.ts delete mode 100644 js13k2024/game/src/game-states/gameplay/gameplay.tsx create mode 100644 js13k2024/game/src/game-states/gameplay/hud.ts delete mode 100644 js13k2024/game/src/game-states/gameplay/hud.tsx create mode 100644 js13k2024/game/src/game-states/instructions/instructions.ts delete mode 100644 js13k2024/game/src/game-states/instructions/instructions.tsx create mode 100644 js13k2024/game/src/game-states/intro/game-preview.ts delete mode 100644 js13k2024/game/src/game-states/intro/game-preview.tsx create mode 100644 js13k2024/game/src/game-states/intro/intro.ts delete mode 100644 js13k2024/game/src/game-states/intro/intro.tsx create mode 100644 js13k2024/game/src/game-states/level-complete/level-complete.ts delete mode 100644 js13k2024/game/src/game-states/level-complete/level-complete.tsx create mode 100644 js13k2024/game/src/game-states/level-story/level-story.ts create mode 100644 js13k2024/game/src/index.ts delete mode 100644 js13k2024/game/src/index.tsx create mode 100644 js13k2024/game/src/main.ts delete mode 100644 js13k2024/game/src/main.tsx create mode 100644 js13k2024/game/src/utils/dom.ts diff --git a/js13k2024/game/index.html b/js13k2024/game/index.html index 3977b35c..f936bb9e 100644 --- a/js13k2024/game/index.html +++ b/js13k2024/game/index.html @@ -1,22 +1,23 @@ - + - - - + + + Monster Steps - - - - - \ No newline at end of file + + + + + diff --git a/js13k2024/game/package-lock.json b/js13k2024/game/package-lock.json index 5eb2eb9f..95671218 100644 --- a/js13k2024/game/package-lock.json +++ b/js13k2024/game/package-lock.json @@ -9,28 +9,13 @@ "version": "1.0.0", "license": "MIT", "devDependencies": { - "@preact/preset-vite": "^2.9.0", "@types/node": "^18.15.11", "genaicode": "^0.0.35", - "preact-iso": "^2.6.3", "typescript": "^5.2.2", "vite": "^5.0.0", "vite-plugin-checker": "^0.6.4" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@anthropic-ai/sdk": { "version": "0.24.3", "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.24.3.tgz", @@ -70,168 +55,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz", - "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-module-transforms": "^7.25.2", - "@babel/helpers": "^7.25.0", - "@babel/parser": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", - "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.25.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", - "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", - "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-validator-option": "^7.24.8", - "browserslist": "^4.23.1", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", - "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", - "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", - "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", - "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-validator-identifier": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", @@ -241,28 +64,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", - "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", - "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/highlight": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", @@ -340,116 +141,6 @@ "node": ">=4" } }, - "node_modules/@babel/parser": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", - "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.25.2" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", - "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.2.tgz", - "integrity": "sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/plugin-syntax-jsx": "^7.24.7", - "@babel/types": "^7.25.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.24.7.tgz", - "integrity": "sha512-QG9EnzoGn+Qar7rxuW+ZOsbWOt56FvvI93xInqsZDC5fsekx1AlIO4KIJ5M+D0p0SqSH156EpmZyXq630B8OlQ==", - "dev": true, - "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", - "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.25.0", - "@babel/types": "^7.25.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz", - "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/parser": "^7.25.3", - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.2", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", - "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", - "dev": true, - "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@emnapi/runtime": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", @@ -1355,54 +1046,6 @@ "node": ">=18.0.0" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/@js-sdsl/ordered-map": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", @@ -1458,69 +1101,6 @@ "node": ">=14" } }, - "node_modules/@preact/preset-vite": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.9.0.tgz", - "integrity": "sha512-B9yVT7AkR6owrt84K3pLNyaKSvlioKdw65VqE/zMiR6HMovPekpsrwBNs5DJhBFEd5cvLMtCjHNHZ9P7Oblveg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/plugin-transform-react-jsx": "^7.22.15", - "@babel/plugin-transform-react-jsx-development": "^7.22.5", - "@prefresh/vite": "^2.4.1", - "@rollup/pluginutils": "^4.1.1", - "babel-plugin-transform-hook-names": "^1.0.2", - "debug": "^4.3.4", - "kolorist": "^1.8.0", - "magic-string": "0.30.5", - "node-html-parser": "^6.1.10", - "resolve": "^1.22.8", - "source-map": "^0.7.4", - "stack-trace": "^1.0.0-pre2" - }, - "peerDependencies": { - "@babel/core": "7.x", - "vite": "2.x || 3.x || 4.x || 5.x" - } - }, - "node_modules/@prefresh/babel-plugin": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@prefresh/babel-plugin/-/babel-plugin-0.5.1.tgz", - "integrity": "sha512-uG3jGEAysxWoyG3XkYfjYHgaySFrSsaEb4GagLzYaxlydbuREtaX+FTxuIidp241RaLl85XoHg9Ej6E4+V1pcg==", - "dev": true - }, - "node_modules/@prefresh/core": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@prefresh/core/-/core-1.5.2.tgz", - "integrity": "sha512-A/08vkaM1FogrCII5PZKCrygxSsc11obExBScm3JF1CryK2uDS3ZXeni7FeKCx1nYdUkj4UcJxzPzc1WliMzZA==", - "dev": true, - "peerDependencies": { - "preact": "^10.0.0" - } - }, - "node_modules/@prefresh/utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@prefresh/utils/-/utils-1.2.0.tgz", - "integrity": "sha512-KtC/fZw+oqtwOLUFM9UtiitB0JsVX0zLKNyRTA332sqREqSALIIQQxdUCS1P3xR/jT1e2e8/5rwH6gdcMLEmsQ==", - "dev": true - }, - "node_modules/@prefresh/vite": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@prefresh/vite/-/vite-2.4.6.tgz", - "integrity": "sha512-miYbTl2J1YNaQJWyWHJzyIpNh7vKUuXC1qCDRzPeWjhQ+9bxeXkUBGDGd9I1f37R5GQYi1S65AN5oR0BR2WzvQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.22.1", - "@prefresh/babel-plugin": "0.5.1", - "@prefresh/core": "^1.5.1", - "@prefresh/utils": "^1.2.0", - "@rollup/pluginutils": "^4.2.1" - }, - "peerDependencies": { - "preact": "^10.4.0", - "vite": ">=2.0.0" - } - }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -1585,19 +1165,6 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", "dev": true }, - "node_modules/@rollup/pluginutils": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", - "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", - "dev": true, - "dependencies": { - "estree-walker": "^2.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.20.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", @@ -1996,15 +1563,6 @@ "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", "dev": true }, - "node_modules/babel-plugin-transform-hook-names": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-hook-names/-/babel-plugin-transform-hook-names-1.0.2.tgz", - "integrity": "sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw==", - "dev": true, - "peerDependencies": { - "@babel/core": "^7.12.10" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2109,12 +1667,6 @@ "readable-stream": "^3.4.0" } }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2137,38 +1689,6 @@ "node": ">=8" } }, - "node_modules/browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -2199,26 +1719,6 @@ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "dev": true }, - "node_modules/caniuse-lite": { - "version": "1.0.30001651", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", - "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2387,12 +1887,6 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2407,34 +1901,6 @@ "node": ">= 8" } }, - "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, "node_modules/debug": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", @@ -2503,61 +1969,6 @@ "node": ">=0.3.1" } }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", - "dev": true, - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, "node_modules/duplexify": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", @@ -2585,12 +1996,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.7", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.7.tgz", - "integrity": "sha512-6FTNWIWMxMy/ZY6799nBlPtF1DFDQ6VQJ7yyDP27SJNt5lwtQ5ufqVvHylb3fdQefvRcgA3fKcFMJi9OLwBRNw==", - "dev": true - }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -2606,18 +2011,6 @@ "once": "^1.4.0" } }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -2674,12 +2067,6 @@ "node": ">=0.8.0" } }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -2839,15 +2226,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/gaxios": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", @@ -2904,15 +2282,6 @@ "node": ">=18" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -2984,15 +2353,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/google-auth-library": { "version": "9.13.0", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.13.0.tgz", @@ -3061,27 +2421,6 @@ "node": ">=8" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, "node_modules/http-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", @@ -3207,21 +2546,6 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, - "node_modules/is-core-module": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", - "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", - "dev": true, - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3300,18 +2624,6 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -3321,18 +2633,6 @@ "bignumber.js": "^9.0.0" } }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -3375,12 +2675,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/kolorist": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", - "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", - "dev": true - }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -3399,27 +2693,6 @@ "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", "dev": true }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3636,22 +2909,6 @@ } } }, - "node_modules/node-html-parser": { - "version": "6.1.13", - "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz", - "integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==", - "dev": true, - "dependencies": { - "css-select": "^5.1.0", - "he": "1.2.0" - } - }, - "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", - "dev": true - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -3673,18 +2930,6 @@ "node": ">=8" } }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, "node_modules/object-hash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", @@ -3766,12 +3011,6 @@ "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -3849,37 +3088,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/preact": { - "version": "10.23.2", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.23.2.tgz", - "integrity": "sha512-kKYfePf9rzKnxOAKDpsWhg/ysrHPqT+yQ7UW4JjdnqjFIeNUnNcEJvhuA8fDenxAGWzUqtd51DfVg7xp/8T9NA==", - "dev": true, - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, - "node_modules/preact-iso": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/preact-iso/-/preact-iso-2.6.3.tgz", - "integrity": "sha512-2JqNi+Elyt7rf0kULtMn/QskbZE10cDMrhmoanGKWJX3gOCTIfLt/ta5xWu+kQUsyInPkTQIVp3fS5Im0V+X8A==", - "dev": true, - "peerDependencies": { - "preact": ">=10", - "preact-render-to-string": ">=6.4.0" - } - }, - "node_modules/preact-render-to-string": { - "version": "6.5.8", - "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.8.tgz", - "integrity": "sha512-VwldmyF+5G6eqTH26uyXY2+a9fh7ry8roYnIEwarB6OnT1bVN7lnlFvh0ldeKJ7/JtvMoWO5jz9tyykRlAIDyA==", - "dev": true, - "peer": true, - "peerDependencies": { - "preact": ">=10" - } - }, "node_modules/prebuild-install": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", @@ -4089,23 +3297,6 @@ "node": ">=0.10.0" } }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/retry-request": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", @@ -4367,15 +3558,6 @@ "is-arrayish": "^0.3.1" } }, - "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, "node_modules/source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", @@ -4385,15 +3567,6 @@ "node": ">=0.10.0" } }, - "node_modules/stack-trace": { - "version": "1.0.0-pre2", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-1.0.0-pre2.tgz", - "integrity": "sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==", - "dev": true, - "engines": { - "node": ">=16" - } - }, "node_modules/stream-events": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", @@ -4549,18 +3722,6 @@ "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/tar": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", @@ -4668,15 +3829,6 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "dev": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4754,36 +3906,6 @@ "node": ">= 10.0.0" } }, - "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -5137,12 +4259,6 @@ "node": ">=10" } }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/js13k2024/game/package.json b/js13k2024/game/package.json index 36b5899e..1cc2376e 100644 --- a/js13k2024/game/package.json +++ b/js13k2024/game/package.json @@ -11,10 +11,8 @@ "author": "Grzegorz Tańczyk", "license": "MIT", "devDependencies": { - "@preact/preset-vite": "^2.9.0", "@types/node": "^18.15.11", "genaicode": "^0.0.35", - "preact-iso": "^2.6.3", "typescript": "^5.2.2", "vite": "^5.0.0", "vite-plugin-checker": "^0.6.4" diff --git a/js13k2024/game/src/game-states/game-over/game-over.ts b/js13k2024/game/src/game-states/game-over/game-over.ts new file mode 100644 index 00000000..e18b261e --- /dev/null +++ b/js13k2024/game/src/game-states/game-over/game-over.ts @@ -0,0 +1,41 @@ +import { createElement, createDiv, createButton, appendChildren } from '../../utils/dom'; + +export class GameOver { + private score: number; + private steps: number; + private onTryAgain: () => void; + private onQuit: () => void; + + constructor(score: number, steps: number, onTryAgain: () => void, onQuit: () => void) { + this.score = score; + this.steps = steps; + this.onTryAgain = onTryAgain; + this.onQuit = onQuit; + } + + render(): HTMLElement { + const container = createDiv('game-over'); + + const title = createElement('h1'); + title.textContent = 'Game Over'; + + const scoreText = createElement('p'); + scoreText.textContent = `Your score: ${this.score}`; + + const stepsText = createElement('p'); + stepsText.textContent = `Steps taken: ${this.steps}`; + + const tryAgainButton = createButton('Try Again', this.onTryAgain); + const quitButton = createButton('Quit', this.onQuit); + + appendChildren(container, [ + title, + scoreText, + stepsText, + tryAgainButton, + quitButton + ]); + + return container; + } +} \ No newline at end of file diff --git a/js13k2024/game/src/game-states/game-over/game-over.tsx b/js13k2024/game/src/game-states/game-over/game-over.tsx deleted file mode 100644 index 06a0279b..00000000 --- a/js13k2024/game/src/game-states/game-over/game-over.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { FunctionComponent } from 'react'; - -interface GameOverProps { - score: number; - steps: number; - onTryAgain: () => void; - onQuit: () => void; -} - -export const GameOver: FunctionComponent = ({ score, steps, onTryAgain, onQuit }) => { - return ( -
-

Game Over

-

Your score: {score}

-

Steps taken: {steps}

- - -
- ); -}; diff --git a/js13k2024/game/src/game-states/gameplay/gameplay.ts b/js13k2024/game/src/game-states/gameplay/gameplay.ts new file mode 100644 index 00000000..ec4654da --- /dev/null +++ b/js13k2024/game/src/game-states/gameplay/gameplay.ts @@ -0,0 +1,158 @@ +import { + createElement, + createDiv, + addEventHandler, + addWindowEventHandler, + removeWindowEventHandler, +} from '../../utils/dom'; +import { drawGameState } from './game-render'; +import { drawMoveArrows } from './render/move-arrows-render'; +import { doGameUpdate, handleKeyPress, isGameEnding } from './game-logic'; +import { getMoveFromClick, getValidMoves } from './move-utils'; +import { HUD } from './hud'; +import { generateLevel } from './level-generator'; +import { GameState } from './gameplay-types'; + +const MAX_LEVEL = 13; +const ANIMATION_DURATION = 2000; // 2 seconds for ending animations + +export class Gameplay { + private level: number; + private onGameOver: () => void; + private onLevelComplete: () => void; + private onGameComplete: () => void; + private updateScore: (newScore: number) => void; + private updateSteps: (newSteps: number) => void; + + private container: HTMLElement; + private canvas: HTMLCanvasElement; + private ctx: CanvasRenderingContext2D; + private gameState: GameState; + private levelConfig: any; + private hud: HUD; + private animationFrameId: number | null = null; + + constructor( + level: number, + score: number, + onGameOver: () => void, + onLevelComplete: () => void, + onGameComplete: () => void, + updateScore: (newScore: number) => void, + updateSteps: (newSteps: number) => void, + ) { + this.level = level; + this.onGameOver = onGameOver; + this.onLevelComplete = onLevelComplete; + this.onGameComplete = onGameComplete; + this.updateScore = updateScore; + this.updateSteps = updateSteps; + + this.container = createDiv('gameplay'); + this.canvas = createElement('canvas'); + this.ctx = this.canvas.getContext('2d')!; + [this.gameState, this.levelConfig] = generateLevel(level); + this.hud = new HUD(level, score, this.gameState.monsterSpawnSteps); + addWindowEventHandler('keydown', this.handleKeyDown); + } + + render(): HTMLElement { + this.container.innerHTML = ''; + this.container.appendChild(this.hud.render()); + + const canvasContainer = createDiv('canvas-container'); + canvasContainer.appendChild(this.canvas); + this.container.appendChild(canvasContainer); + + this.initializeCanvas(); + this.startGameLoop(); + this.addEventListeners(); + + return this.container; + } + + private initializeCanvas() { + const isometricWidth = (this.levelConfig.gridSize + this.levelConfig.gridSize) * this.levelConfig.cellSize; + const isometricHeight = ((this.levelConfig.gridSize + this.levelConfig.gridSize) * this.levelConfig.cellSize) / 2; + this.canvas.width = isometricWidth; + this.canvas.height = isometricHeight + 100; // Add extra space for the platform + } + + private startGameLoop() { + const animate = () => { + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + + drawGameState(this.ctx, this.gameState, this.levelConfig); + + if (!isGameEnding(this.gameState)) { + drawMoveArrows( + this.ctx, + getValidMoves(this.gameState, this.levelConfig), + this.levelConfig.gridSize, + this.levelConfig.cellSize, + ); + } + + this.animationFrameId = requestAnimationFrame(animate); + }; + + animate(); + } + + private addEventListeners() { + addEventHandler(this.canvas, 'click', this.handleClick.bind(this)); + } + + handleKeyDown = (e: KeyboardEvent) => { + if (!isGameEnding(this.gameState)) { + const newGameState = handleKeyPress(e, this.gameState, this.levelConfig); + this.updateGameState(newGameState); + } + }; + + private handleClick(e: MouseEvent) { + if (isGameEnding(this.gameState)) return; + + const rect = this.canvas.getBoundingClientRect(); + const clickedMove = getMoveFromClick( + (e.clientX - rect.left) * (this.canvas.width / rect.width) - this.canvas.width / 2, + (e.clientY - rect.top) * (this.canvas.height / rect.height) - 100, + this.gameState, + this.levelConfig, + ); + + if (clickedMove) { + this.updateGameState(doGameUpdate(clickedMove.direction, this.gameState, this.levelConfig)); + } + } + + private updateGameState(newGameState: GameState) { + this.gameState = newGameState; + this.updateScore(newGameState.score); + this.updateSteps(newGameState.steps); + this.hud.update(this.level, newGameState.score, newGameState.monsterSpawnSteps); + + if (newGameState.gameEndingState !== 'none') { + setTimeout(() => { + if (newGameState.gameEndingState === 'levelComplete') { + if (this.level === MAX_LEVEL) { + this.onGameComplete(); + } else { + this.onLevelComplete(); + } + } + + if (newGameState.gameEndingState === 'gameOver') { + this.onGameOver(); + } + }, ANIMATION_DURATION); + } + } + + destroy() { + if (this.animationFrameId !== null) { + cancelAnimationFrame(this.animationFrameId); + } + removeWindowEventHandler('keydown', this.handleKeyDown); + } +} \ No newline at end of file diff --git a/js13k2024/game/src/game-states/gameplay/gameplay.tsx b/js13k2024/game/src/game-states/gameplay/gameplay.tsx deleted file mode 100644 index fa55d653..00000000 --- a/js13k2024/game/src/game-states/gameplay/gameplay.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import { FunctionComponent, useState, useRef, useEffect } from 'react'; -import { drawGameState } from './game-render'; -import { drawMoveArrows } from './render/move-arrows-render'; -import { doGameUpdate, handleKeyPress, isGameEnding } from './game-logic'; -import { getMoveFromClick, getValidMoves } from './move-utils'; -import { HUD } from './hud'; -import { generateLevel } from './level-generator'; -import { GameState } from './gameplay-types'; - -interface GameplayProps { - level: number; - score: number; - onGameOver: () => void; - onLevelComplete: () => void; - onGameComplete: () => void; - updateScore: (newScore: number) => void; - updateSteps: (newSteps: number) => void; -} - -const MAX_LEVEL = 13; -const ANIMATION_DURATION = 2000; // 2 seconds for ending animations - -export const Gameplay: FunctionComponent = ({ - level, - score, - onGameOver, - onLevelComplete, - onGameComplete, - updateScore, - updateSteps, -}) => { - const canvasRef = useRef(null); - const [[gameState, levelConfig, levelStory], setGameState] = useState(() => generateLevel(level)); - const [showStory, setShowStory] = useState(true); - - const updateGameState = (newGameState: GameState) => { - setGameState([newGameState, levelConfig, levelStory]); - updateScore(newGameState.score); - updateSteps(newGameState.steps); - }; - - useEffect(() => { - if (gameState.gameEndingState !== 'none') { - setTimeout(() => { - if (gameState.gameEndingState === 'levelComplete') { - if (level === MAX_LEVEL) { - onGameComplete(); - } else { - onLevelComplete(); - } - } - - if (gameState.gameEndingState === 'gameOver') { - onGameOver(); - } - }, ANIMATION_DURATION); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [gameState.gameEndingState]); - - useEffect(() => { - if (!gameState || !levelConfig) return; - - const canvas = canvasRef.current; - let animationFrameId: number; - if (canvas) { - // Adjust canvas size for isometric view - const isometricWidth = (levelConfig.gridSize + levelConfig.gridSize) * levelConfig.cellSize; - const isometricHeight = ((levelConfig.gridSize + levelConfig.gridSize) * levelConfig.cellSize) / 2; - canvas.width = isometricWidth; - canvas.height = isometricHeight + 100; // Add extra space for the platform - - const ctx = canvas.getContext('2d'); - if (ctx) { - const animate = () => { - // Draw the game state - drawGameState(ctx, gameState, levelConfig); - - // Draw move arrows only if the game is not ending - if (!isGameEnding(gameState)) { - drawMoveArrows(ctx, getValidMoves(gameState, levelConfig), levelConfig.gridSize, levelConfig.cellSize); - } - - animationFrameId = requestAnimationFrame(animate); - }; - animate(); - } - } - - return () => { - if (animationFrameId !== null) { - cancelAnimationFrame(animationFrameId); - } - }; - }, [gameState, levelConfig, showStory]); - - useEffect(() => { - if (!gameState || !levelConfig) return; - - const handleKeyDown = (e: KeyboardEvent) => { - if (showStory) { - setShowStory(false); - return; - } - - if (!isGameEnding(gameState)) { - const newGameState = handleKeyPress(e, gameState, levelConfig); - updateGameState(newGameState); - } - }; - - const handleClick = (e: MouseEvent) => { - if (showStory) { - setShowStory(false); - return; - } - - if (isGameEnding(gameState)) return; - - const canvas = canvasRef.current; - if (!canvas) return; - - const rect = canvas.getBoundingClientRect(); - const clickedMove = getMoveFromClick( - (e.clientX - rect.left) * (canvas.width / rect.width) - canvas.width / 2, - (e.clientY - rect.top) * (canvas.height / rect.height) - 100, - gameState, - levelConfig, - ); - - if (clickedMove) { - updateGameState(doGameUpdate(clickedMove.direction, gameState, levelConfig)); - } - }; - - window.addEventListener('click', handleClick); - window.addEventListener('keydown', handleKeyDown); - return () => { - window.removeEventListener('click', handleClick); - window.removeEventListener('keydown', handleKeyDown); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [gameState, levelConfig, showStory, onGameOver, onLevelComplete, updateScore, updateSteps]); - - if (showStory) { - return ( -
-

Level {level}

-

{levelStory}

-

Press any key to start...

-
- ); - } - - return ( -
- -
- -
-
- ); -}; diff --git a/js13k2024/game/src/game-states/gameplay/hud.ts b/js13k2024/game/src/game-states/gameplay/hud.ts new file mode 100644 index 00000000..21fc7088 --- /dev/null +++ b/js13k2024/game/src/game-states/gameplay/hud.ts @@ -0,0 +1,43 @@ +import { createElement, createDiv, updateText } from '../../utils/dom'; + +export class HUD { + private level: number; + private score: number; + private steps: number; + private container: HTMLElement; + private levelElement: HTMLElement; + private scoreElement: HTMLElement; + private stepsElement: HTMLElement; + + constructor(level: number, score: number, steps: number) { + this.level = level; + this.score = score; + this.steps = steps; + this.container = createDiv('hud'); + this.levelElement = createElement('div'); + this.scoreElement = createElement('div'); + this.stepsElement = createElement('div'); + } + + render(): HTMLElement { + this.levelElement.textContent = `Level: ${this.level}`; + this.scoreElement.textContent = `Score: ${this.score}`; + this.stepsElement.textContent = `Steps: ${this.steps}`; + + this.container.appendChild(this.levelElement); + this.container.appendChild(this.scoreElement); + this.container.appendChild(this.stepsElement); + + return this.container; + } + + update(level: number, score: number, steps: number): void { + this.level = level; + this.score = score; + this.steps = steps; + + updateText(this.levelElement, `Level: ${this.level}`); + updateText(this.scoreElement, `Score: ${this.score}`); + updateText(this.stepsElement, `Steps: ${this.steps}`); + } +} \ No newline at end of file diff --git a/js13k2024/game/src/game-states/gameplay/hud.tsx b/js13k2024/game/src/game-states/gameplay/hud.tsx deleted file mode 100644 index 6ebc79a1..00000000 --- a/js13k2024/game/src/game-states/gameplay/hud.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { FunctionComponent } from 'react'; - -interface HUDProps { - level: number; - score: number; - steps: number; -} - -export const HUD: FunctionComponent = ({ level, score, steps }) => { - return ( -
-
Level: {level}
-
Score: {score}
-
Steps: {steps}
-
- ); -}; diff --git a/js13k2024/game/src/game-states/instructions/instructions.ts b/js13k2024/game/src/game-states/instructions/instructions.ts new file mode 100644 index 00000000..f52b66e6 --- /dev/null +++ b/js13k2024/game/src/game-states/instructions/instructions.ts @@ -0,0 +1,164 @@ +import { createElement, createDiv, createButton, addWindowEventHandler } from '../../utils/dom'; + +export class Instructions { + private onBack: () => void; + + constructor(onBack: () => void) { + this.onBack = onBack; + } + + render(): HTMLElement { + const container = createDiv('instructions'); + + const title = createElement('h1'); + title.textContent = 'How to Play Monster Steps'; + + const instructionsList = this.createInstructionsList(); + const bonusDetails = this.createBonusDetails(); + const tips = this.createTips(); + + const backButton = createButton('Back to Menu', this.onBack); + + const escapeInstruction = createElement('p'); + escapeInstruction.className = 'instructions-tip'; + escapeInstruction.textContent = 'Press Escape to return to the main menu'; + + container.appendChild(title); + container.appendChild(instructionsList); + container.appendChild(bonusDetails); + container.appendChild(tips); + container.appendChild(backButton); + container.appendChild(escapeInstruction); + + this.addKeyboardListener(); + + return container; + } + + private createInstructionsList(): HTMLElement { + const list = createElement('ul'); + const instructions = [ + 'Use arrow keys, touch controls, or mouse clicks to move your character', + 'Avoid obstacles and monsters', + 'Reach the goal (green square) to complete the level', + 'New monsters appear every 13 steps', + 'Collect bonuses for special abilities', + 'Complete all 13 levels to win', + ]; + + instructions.forEach((instruction) => { + const item = createElement('li'); + item.textContent = instruction; + list.appendChild(item); + }); + + return list; + } + + private createBonusDetails(): HTMLElement { + const container = createDiv(); + const title = createElement('h2'); + title.textContent = 'New Bonus Details'; + + const bonusList = createElement('ul'); + const bonuses = [ + { + name: 'Tsunami', + details: [ + 'Water gradually floods the grid over 13 steps', + 'Movement becomes slower for both you and monsters', + 'At the 13th step, everything not on an obstacle is eliminated', + 'Use the Climber bonus to survive on obstacles', + ], + }, + { + name: 'Monster', + details: [ + 'You become a monster for 13 steps', + 'Monsters become vulnerable "players" during this time', + 'Eliminate all monster-players to win, but be careful not to let them reach the goal!', + ], + }, + { + name: 'Slide', + details: [ + 'Your movement becomes a slide in the chosen direction', + "You'll keep moving until you hit an obstacle or the edge of the grid", + 'Use this to quickly traverse the grid, but plan your moves carefully!', + ], + }, + { + name: 'Sokoban', + details: [ + 'Gain the ability to push obstacles', + 'Use this to create new paths or crush monsters', + 'Strategic obstacle placement can help block monster paths', + ], + }, + { + name: 'Blaster', + details: [ + "Equip a blaster that shoots in the direction you're moving", + 'Eliminate monsters in your path', + 'Use carefully to clear your way to the goal', + ], + }, + ]; + + bonuses.forEach((bonus) => { + const item = createElement('li'); + const bonusName = createElement('strong'); + bonusName.textContent = bonus.name + ': '; + item.appendChild(bonusName); + + const detailsList = createElement('ul'); + bonus.details.forEach((detail) => { + const detailItem = createElement('li'); + detailItem.textContent = detail; + detailsList.appendChild(detailItem); + }); + + item.appendChild(detailsList); + bonusList.appendChild(item); + }); + + container.appendChild(title); + container.appendChild(bonusList); + return container; + } + + private createTips(): HTMLElement { + const container = createDiv(); + const title = createElement('h2'); + title.textContent = 'Tips'; + + const tipsList = createElement('ul'); + const tips = [ + 'Plan your route to avoid getting trapped', + 'Use obstacles and new bonuses strategically to outmaneuver monsters', + 'Combine bonuses for powerful effects (e.g., Climber during Tsunami)', + 'Think ahead when using Slide or Sokoban to avoid putting yourself in danger', + 'Keep track of your steps to anticipate new monster spawns', + ]; + + tips.forEach((tip) => { + const item = createElement('li'); + item.textContent = tip; + tipsList.appendChild(item); + }); + + container.appendChild(title); + container.appendChild(tipsList); + return container; + } + + private addKeyboardListener() { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + this.onBack(); + } + }; + + addWindowEventHandler('keydown', handleKeyDown); + } +} diff --git a/js13k2024/game/src/game-states/instructions/instructions.tsx b/js13k2024/game/src/game-states/instructions/instructions.tsx deleted file mode 100644 index 3fd90c58..00000000 --- a/js13k2024/game/src/game-states/instructions/instructions.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { FunctionComponent, useEffect } from 'react'; - -interface InstructionsProps { - onBack: () => void; -} - -export const Instructions: FunctionComponent = ({ onBack }) => { - useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { - if (event.key === 'Escape') { - onBack(); - } - }; - - window.addEventListener('keydown', handleKeyDown); - - return () => { - window.removeEventListener('keydown', handleKeyDown); - }; - }, [onBack]); - - return ( -
-

How to Play Monster Steps

-
    -
  • Use arrow keys, touch controls, or mouse clicks to move your character
  • -
  • Avoid obstacles and monsters
  • -
  • Reach the goal (green square) to complete the level
  • -
  • New monsters appear every 13 steps
  • -
  • - Collect bonuses for special abilities: -
      -
    • Cap of Invisibility: Temporary invisibility to monsters
    • -
    • Confused Monsters: Monsters move randomly
    • -
    • Land Mine: Place a trap for monsters
    • -
    • Time Bomb: Delayed explosion
    • -
    • Crusher: Destroy nearby obstacles
    • -
    • Builder: Create new platforms
    • -
    • Climber: Climb on walls and obstacles
    • -
    • Teleport: Instantly move to another location on the grid
    • -
    • Tsunami: Water floods the grid, slowing movement. Climb to survive!
    • -
    • Monster: Transform into a monster and hunt other monsters
    • -
    • Slide: Glide across the grid until hitting an obstacle
    • -
    • Sokoban: Push obstacles to crush monsters or create paths
    • -
    • Blaster: Shoot in the direction you're moving to eliminate monsters
    • -
    -
  • -
  • Complete all 13 levels to win
  • -
-

New Bonus Details

-
    -
  • Tsunami: -
      -
    • Water gradually floods the grid over 13 steps
    • -
    • Movement becomes slower for both you and monsters
    • -
    • At the 13th step, everything not on an obstacle is eliminated
    • -
    • Use the Climber bonus to survive on obstacles
    • -
    -
  • -
  • Monster: -
      -
    • You become a monster for 13 steps
    • -
    • Monsters become vulnerable "players" during this time
    • -
    • Eliminate all monster-players to win, but be careful not to let them reach the goal!
    • -
    -
  • -
  • Slide: -
      -
    • Your movement becomes a slide in the chosen direction
    • -
    • You'll keep moving until you hit an obstacle or the edge of the grid
    • -
    • Use this to quickly traverse the grid, but plan your moves carefully!
    • -
    -
  • -
  • Sokoban: -
      -
    • Gain the ability to push obstacles
    • -
    • Use this to create new paths or crush monsters
    • -
    • Strategic obstacle placement can help block monster paths
    • -
    -
  • -
  • Blaster: -
      -
    • Equip a blaster that shoots in the direction you're moving
    • -
    • Eliminate monsters in your path
    • -
    • Use carefully to clear your way to the goal
    • -
    -
  • -
-

Tips

-
    -
  • Plan your route to avoid getting trapped
  • -
  • Use obstacles and new bonuses strategically to outmaneuver monsters
  • -
  • Combine bonuses for powerful effects (e.g., Climber during Tsunami)
  • -
  • Think ahead when using Slide or Sokoban to avoid putting yourself in danger
  • -
  • Keep track of your steps to anticipate new monster spawns
  • -
- -

Press Escape to return to the main menu

-
- ); -}; \ No newline at end of file diff --git a/js13k2024/game/src/game-states/intro/game-preview.ts b/js13k2024/game/src/game-states/intro/game-preview.ts new file mode 100644 index 00000000..1801076a --- /dev/null +++ b/js13k2024/game/src/game-states/intro/game-preview.ts @@ -0,0 +1,127 @@ +import { createElement } from '../../utils/dom'; +import { drawGameState } from '../gameplay/game-render'; +import { drawGrid } from '../gameplay/render/grid-render'; +import { GameState, BonusType } from '../gameplay/gameplay-types'; + +const PREVIEW_WIDTH = 300; +const PREVIEW_HEIGHT = 200; +const GRID_SIZE = 5; +const CELL_SIZE = Math.min(PREVIEW_WIDTH / GRID_SIZE, PREVIEW_HEIGHT / GRID_SIZE) / 2; + +export class GamePreview { + private canvas: HTMLCanvasElement; + private ctx: CanvasRenderingContext2D; + private gameState: GameState; + private animationFrameId: number | null = null; + + constructor() { + this.canvas = createElement('canvas') as HTMLCanvasElement; + this.canvas.width = PREVIEW_WIDTH; + this.canvas.height = PREVIEW_HEIGHT; + this.ctx = this.canvas.getContext('2d')!; + this.gameState = this.createPreviewGameState(); + } + + render(): HTMLCanvasElement { + this.startAnimation(); + return this.canvas; + } + + private startAnimation() { + const animate = () => { + this.ctx.clearRect(0, 0, PREVIEW_WIDTH, PREVIEW_HEIGHT); + + this.ctx.save(); + this.ctx.scale(0.8, 0.8); + this.ctx.translate(PREVIEW_WIDTH * 0.1, PREVIEW_HEIGHT * 0.1); + + drawGrid(this.ctx, GRID_SIZE, this.gameState); + drawGameState(this.ctx, this.gameState, { + gridSize: GRID_SIZE, + cellSize: CELL_SIZE, + initialMonsterCount: 0, + monsterSpawnSectors: [], + obstacleCount: 0, + initialBonusCount: 0, + levelName: 'Preview', + levelStory: 'Preview', + levelUpdater: () => {}, + }); + + this.ctx.restore(); + + // Animate monsters + this.gameState.monsters.forEach((monster) => { + const newX = monster.position.x + (Math.random() - 0.5) * 0.1; + const newY = monster.position.y + (Math.random() - 0.5) * 0.1; + monster.previousPosition = { ...monster.position }; + monster.position = { + x: Math.max(0, Math.min(GRID_SIZE - 1, newX)), + y: Math.max(0, Math.min(GRID_SIZE - 1, newY)), + }; + monster.moveTimestamp = Date.now(); + }); + + this.animationFrameId = requestAnimationFrame(animate); + }; + + animate(); + } + + private createPreviewGameState(): GameState { + return { + player: { + position: { x: 2, y: 2 }, + previousPosition: { x: 2, y: 2 }, + moveTimestamp: Date.now(), + isVictorious: false, + isVanishing: false, + }, + goal: { x: 4, y: 4 }, + obstacles: [ + { position: { x: 1, y: 1 }, creationTime: Date.now(), isRaising: false, isDestroying: false }, + { position: { x: 3, y: 3 }, creationTime: Date.now(), isRaising: false, isDestroying: false }, + ], + monsters: [ + { + position: { x: 0, y: 4 }, + previousPosition: { x: 0, y: 4 }, + moveTimestamp: Date.now(), + path: [], + seed: Math.random(), + isConfused: false, + spawnPoint: { x: 0, y: 4 }, + }, + { + position: { x: 4, y: 0 }, + previousPosition: { x: 4, y: 0 }, + moveTimestamp: Date.now(), + path: [], + seed: Math.random(), + isConfused: false, + spawnPoint: { x: 4, y: 0 }, + }, + ], + steps: 0, + monsterSpawnSteps: 0, + bonuses: [ + { type: BonusType.CapOfInvisibility, position: { x: 1, y: 3 } }, + { type: BonusType.ConfusedMonsters, position: { x: 3, y: 1 } }, + ], + activeBonuses: [], + explosions: [], + timeBombs: [], + landMines: [], + score: 0, + gameEndingState: 'none', + tsunamiLevel: 0, + blasterShots: [], + }; + } + + destroy() { + if (this.animationFrameId !== null) { + cancelAnimationFrame(this.animationFrameId); + } + } +} \ No newline at end of file diff --git a/js13k2024/game/src/game-states/intro/game-preview.tsx b/js13k2024/game/src/game-states/intro/game-preview.tsx deleted file mode 100644 index 8eddeee5..00000000 --- a/js13k2024/game/src/game-states/intro/game-preview.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { FunctionComponent, useEffect, useRef } from 'react'; -import { drawGameState } from '../gameplay/game-render'; -import { drawGrid } from '../gameplay/render/grid-render'; -import { GameState, BonusType } from '../gameplay/gameplay-types'; - -const PREVIEW_WIDTH = 300; -const PREVIEW_HEIGHT = 200; -const GRID_SIZE = 5; -const CELL_SIZE = Math.min(PREVIEW_WIDTH / GRID_SIZE, PREVIEW_HEIGHT / GRID_SIZE) / 2; - -const createPreviewGameState = (): GameState => ({ - player: { - position: { x: 2, y: 2 }, - previousPosition: { x: 2, y: 2 }, - moveTimestamp: Date.now(), - isVictorious: false, - isVanishing: false, - }, - goal: { x: 4, y: 4 }, - obstacles: [ - { position: { x: 1, y: 1 }, creationTime: Date.now(), isRaising: false, isDestroying: false }, - { position: { x: 3, y: 3 }, creationTime: Date.now(), isRaising: false, isDestroying: false }, - ], - monsters: [ - { - position: { x: 0, y: 4 }, - previousPosition: { x: 0, y: 4 }, - moveTimestamp: Date.now(), - path: [], - seed: Math.random(), - isConfused: false, - spawnPoint: { x: 0, y: 4 }, - }, - { - position: { x: 4, y: 0 }, - previousPosition: { x: 4, y: 0 }, - moveTimestamp: Date.now(), - path: [], - seed: Math.random(), - isConfused: false, - spawnPoint: { x: 4, y: 0 }, - }, - ], - steps: 0, - monsterSpawnSteps: 0, - bonuses: [ - { type: BonusType.CapOfInvisibility, position: { x: 1, y: 3 } }, - { type: BonusType.ConfusedMonsters, position: { x: 3, y: 1 } }, - ], - activeBonuses: [], - explosions: [], - timeBombs: [], - landMines: [], - score: 0, - gameEndingState: 'none', - tsunamiLevel: 0, - blasterShots: [], -}); - -export const GamePreview: FunctionComponent = () => { - const canvasRef = useRef(null); - const gameStateRef = useRef(createPreviewGameState()); - - useEffect(() => { - const canvas = canvasRef.current; - if (!canvas) return; - - const ctx = canvas.getContext('2d'); - if (!ctx) return; - - const animate = () => { - ctx.clearRect(0, 0, PREVIEW_WIDTH, PREVIEW_HEIGHT); - - ctx.save(); - ctx.scale(0.8, 0.8); - ctx.translate(PREVIEW_WIDTH * 0.1, PREVIEW_HEIGHT * 0.1); - - drawGrid(ctx, GRID_SIZE, gameStateRef.current); - drawGameState(ctx, gameStateRef.current, { - gridSize: GRID_SIZE, - cellSize: CELL_SIZE, - initialMonsterCount: 0, - monsterSpawnSectors: [], - obstacleCount: 0, - initialBonusCount: 0, - levelName: 'Preview', - levelStory: 'Preview', - levelUpdater: () => {}, - }); - - ctx.restore(); - - // Animate monsters - gameStateRef.current.monsters.forEach((monster) => { - const newX = monster.position.x + (Math.random() - 0.5) * 0.1; - const newY = monster.position.y + (Math.random() - 0.5) * 0.1; - monster.previousPosition = { ...monster.position }; - monster.position = { - x: Math.max(0, Math.min(GRID_SIZE - 1, newX)), - y: Math.max(0, Math.min(GRID_SIZE - 1, newY)), - }; - monster.moveTimestamp = Date.now(); - }); - - requestAnimationFrame(animate); - }; - - animate(); - }, []); - - return ; -}; diff --git a/js13k2024/game/src/game-states/intro/intro.ts b/js13k2024/game/src/game-states/intro/intro.ts new file mode 100644 index 00000000..52780580 --- /dev/null +++ b/js13k2024/game/src/game-states/intro/intro.ts @@ -0,0 +1,48 @@ +import { GamePreview } from './game-preview'; +import { createElement, createDiv, createButton, appendChildren } from '../../utils/dom'; + +export class Intro { + private onStart: () => void; + private onInstructions: () => void; + + constructor(onStart: () => void, onInstructions: () => void) { + this.onStart = onStart; + this.onInstructions = onInstructions; + } + + render(): HTMLElement { + const container = createDiv('intro'); + + const title = createElement('h1'); + title.className = 'game-title'; + title.textContent = 'Monster Steps'; + + const introResponsive = createDiv('game-intro-responsive'); + + const previewContainer = createDiv('game-preview-container'); + const gamePreview = new GamePreview(); + previewContainer.appendChild(gamePreview.render()); + + const introColumn = createDiv('game-intro-column'); + + const buttonContainer = createDiv('intro-buttons'); + const startButton = createButton('Start Game', this.onStart); + const instructionsButton = createButton('Instructions', this.onInstructions); + appendChildren(buttonContainer, [startButton, instructionsButton]); + + const tip = createElement('p'); + tip.className = 'intro-tip'; + tip.textContent = 'Press right arrow to start'; + + appendChildren(introColumn, [buttonContainer, tip]); + appendChildren(introResponsive, [previewContainer, introColumn]); + + const authorInfo = createElement('p'); + authorInfo.className = 'author-name'; + authorInfo.innerHTML = 'Created by Grzegorz Tańczyk | Source code (GitHub)'; + + appendChildren(container, [title, introResponsive, authorInfo]); + + return container; + } +} \ No newline at end of file diff --git a/js13k2024/game/src/game-states/intro/intro.tsx b/js13k2024/game/src/game-states/intro/intro.tsx deleted file mode 100644 index 69097d39..00000000 --- a/js13k2024/game/src/game-states/intro/intro.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { FunctionComponent, useEffect } from 'react'; -import { GamePreview } from './game-preview'; - -interface IntroProps { - onStart: () => void; - onInstructions: () => void; -} - -export const Intro: FunctionComponent = ({ onStart, onInstructions }) => { - useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { - if (event.key === 'ArrowRight') { - onStart(); - } - }; - - window.addEventListener('keydown', handleKeyDown); - - return () => { - window.removeEventListener('keydown', handleKeyDown); - }; - }, [onStart]); - - return ( -
-

Monster Steps

-
-
- -
-
-
- - -
-

Press right arrow to start

-
-
-

Created by Grzegorz Tańczyk | Source code (GitHub)

-
- ); -}; diff --git a/js13k2024/game/src/game-states/level-complete/level-complete.ts b/js13k2024/game/src/game-states/level-complete/level-complete.ts new file mode 100644 index 00000000..da8c8fac --- /dev/null +++ b/js13k2024/game/src/game-states/level-complete/level-complete.ts @@ -0,0 +1,35 @@ +import { createElement, createDiv, createButton, appendChildren } from '../../utils/dom'; + +export class LevelComplete { + private level: number; + private onNextLevel: () => void; + private onQuit: () => void; + + constructor(level: number, onNextLevel: () => void, onQuit: () => void) { + this.level = level; + this.onNextLevel = onNextLevel; + this.onQuit = onQuit; + } + + render(): HTMLElement { + const container = createDiv('level-complete'); + + const title = createElement('h1'); + title.textContent = `Level ${this.level} Complete!`; + + const message = createElement('p'); + message.textContent = "Congratulations! You've completed the level."; + + const nextLevelButton = createButton('Next Level', this.onNextLevel); + const quitButton = createButton('Quit', this.onQuit); + + appendChildren(container, [ + title, + message, + nextLevelButton, + quitButton + ]); + + return container; + } +} \ No newline at end of file diff --git a/js13k2024/game/src/game-states/level-complete/level-complete.tsx b/js13k2024/game/src/game-states/level-complete/level-complete.tsx deleted file mode 100644 index febcfb25..00000000 --- a/js13k2024/game/src/game-states/level-complete/level-complete.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { FunctionComponent } from 'react'; - -interface LevelCompleteProps { - level: number; - onNextLevel: () => void; - onQuit: () => void; -} - -export const LevelComplete: FunctionComponent = ({ level, onNextLevel, onQuit }) => { - return ( -
-

Level {level} Complete!

-

Congratulations! You've completed the level.

- - -
- ); -}; diff --git a/js13k2024/game/src/game-states/level-story/level-story.ts b/js13k2024/game/src/game-states/level-story/level-story.ts new file mode 100644 index 00000000..68e3840c --- /dev/null +++ b/js13k2024/game/src/game-states/level-story/level-story.ts @@ -0,0 +1,48 @@ +import { createElement, createDiv, addWindowEventHandler, removeWindowEventHandler } from '../../utils/dom'; +import { generateLevel } from '../gameplay/level-generator'; + +export class LevelStory { + private level: number; + private onStoryComplete: () => void; + private container: HTMLElement; + private levelStory: string; + + constructor(level: number, onStoryComplete: () => void) { + this.level = level; + this.onStoryComplete = onStoryComplete; + this.container = createDiv('level-story'); + [, , this.levelStory] = generateLevel(level); + + this.handleKeyPress = this.handleKeyPress.bind(this); + setTimeout(() => { + addWindowEventHandler('keydown', this.handleKeyPress); + }); + } + + render(): HTMLElement { + const title = createElement('h2'); + title.textContent = `Level ${this.level}`; + + const story = createElement('p'); + story.textContent = this.levelStory; + + const instruction = createElement('p'); + instruction.textContent = 'Press any key to start...'; + + this.container.appendChild(title); + this.container.appendChild(story); + this.container.appendChild(instruction); + + return this.container; + } + + private handleKeyPress(e: KeyboardEvent) { + e.preventDefault(); + this.onStoryComplete(); + this.destroy(); + } + + destroy() { + removeWindowEventHandler('keydown', this.handleKeyPress); + } +} diff --git a/js13k2024/game/src/global-styles.css b/js13k2024/game/src/global-styles.css index a1d44283..8a0d07a0 100644 --- a/js13k2024/game/src/global-styles.css +++ b/js13k2024/game/src/global-styles.css @@ -43,7 +43,7 @@ html { height: 100%; } -.game-container { +#game-container { display: flex; justify-content: center; align-items: center; diff --git a/js13k2024/game/src/index.ts b/js13k2024/game/src/index.ts new file mode 100644 index 00000000..382e648c --- /dev/null +++ b/js13k2024/game/src/index.ts @@ -0,0 +1,11 @@ +import { MonsterStepsApp } from './main'; +import { createElement } from './utils/dom'; + +// Create a container for the game +const gameContainer = createElement('div'); +gameContainer.id = 'game-container'; +document.body.appendChild(gameContainer); + +// Initialize the game +const game = new MonsterStepsApp(gameContainer); +game.init(); \ No newline at end of file diff --git a/js13k2024/game/src/index.tsx b/js13k2024/game/src/index.tsx deleted file mode 100644 index bcb97f67..00000000 --- a/js13k2024/game/src/index.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import { render } from 'react-dom'; -import { MonsterStepsApp } from './main'; - -render(, document.body); diff --git a/js13k2024/game/src/main.ts b/js13k2024/game/src/main.ts new file mode 100644 index 00000000..90b123d2 --- /dev/null +++ b/js13k2024/game/src/main.ts @@ -0,0 +1,247 @@ +import { Intro } from './game-states/intro/intro'; +import { Instructions } from './game-states/instructions/instructions'; +import { Gameplay } from './game-states/gameplay/gameplay'; +import { GameOver } from './game-states/game-over/game-over'; +import { LevelComplete } from './game-states/level-complete/level-complete'; +import { LevelStory } from './game-states/level-story/level-story'; +import { soundEngine } from './sound/sound-engine'; +import { clearElement, createDiv } from './utils/dom'; +import './global-styles.css'; + +enum GameState { + Intro, + Instructions, + LevelStory, + Gameplay, + GameOver, + LevelComplete, + GameComplete +} + +export class MonsterStepsApp { + private container: HTMLElement; + private gameState: GameState; + private level: number; + private score: number; + private steps: number; + private currentScreen: any; // This will hold the current screen object + + constructor(container: HTMLElement) { + this.container = container; + this.gameState = GameState.Intro; + this.level = parseInt(document.location.hash.split('level')[1] ?? '1'); + this.score = 0; + this.steps = 0; + this.currentScreen = null; + } + + init() { + this.addKeyboardListeners(); + this.renderCurrentState(); + } + + private addKeyboardListeners() { + document.addEventListener('keydown', (e: KeyboardEvent) => { + if (e.key === 'ArrowRight') { + switch (this.gameState) { + case GameState.Intro: + this.startGame(); + break; + case GameState.LevelComplete: + this.nextLevel(); + break; + case GameState.GameOver: + this.restartLevel(); + break; + case GameState.GameComplete: + this.restartGame(); + break; + } + } else if (e.key === 'Escape') { + if ( + this.gameState === GameState.Instructions || + this.gameState === GameState.GameOver || + this.gameState === GameState.GameComplete + ) { + this.quitGame(); + } + } + }); + } + + private renderCurrentState() { + clearElement(this.container); + let screenElement: HTMLElement; + + switch (this.gameState) { + case GameState.Intro: + this.currentScreen = new Intro(this.startGame.bind(this), this.showInstructions.bind(this)); + break; + case GameState.Instructions: + this.currentScreen = new Instructions(this.quitGame.bind(this)); + break; + case GameState.LevelStory: + this.currentScreen = new LevelStory( + this.level, + this.startGameplay.bind(this) + ); + break; + case GameState.Gameplay: + this.currentScreen = new Gameplay( + this.level, + this.score, + this.gameOver.bind(this), + this.levelComplete.bind(this), + this.gameComplete.bind(this), + this.updateScore.bind(this), + this.updateSteps.bind(this) + ); + break; + case GameState.GameOver: + this.currentScreen = new GameOver( + this.score, + this.steps, + this.restartLevel.bind(this), + this.quitGame.bind(this) + ); + break; + case GameState.LevelComplete: + this.currentScreen = new LevelComplete( + this.level, + this.nextLevel.bind(this), + this.quitGame.bind(this) + ); + break; + case GameState.GameComplete: + this.currentScreen = new GameComplete( + this.score, + this.steps, + this.restartGame.bind(this), + this.quitGame.bind(this) + ); + break; + } + + screenElement = this.currentScreen.render(); + this.container.appendChild(screenElement); + } + + private startGame() { + this.gameState = GameState.LevelStory; + this.renderCurrentState(); + } + + private startGameplay() { + this.gameState = GameState.Gameplay; + this.renderCurrentState(); + } + + private showInstructions() { + this.gameState = GameState.Instructions; + this.renderCurrentState(); + } + + private restartGame() { + this.level = 1; + this.score = 0; + this.steps = 0; + this.gameState = GameState.LevelStory; + this.renderCurrentState(); + } + + private restartLevel() { + this.gameState = GameState.LevelStory; + this.renderCurrentState(); + } + + private quitGame() { + this.gameState = GameState.Intro; + this.renderCurrentState(); + } + + private gameOver() { + this.gameState = GameState.GameOver; + this.renderCurrentState(); + } + + private levelComplete() { + this.level++; + this.gameState = GameState.LevelComplete; + this.renderCurrentState(); + } + + private nextLevel() { + this.gameState = GameState.LevelStory; + this.renderCurrentState(); + } + + private gameComplete() { + soundEngine.playLevelComplete(); // We can reuse the level complete sound for game completion + this.gameState = GameState.GameComplete; + this.renderCurrentState(); + } + + private updateScore(newScore: number) { + this.score = newScore; + } + + private updateSteps(newSteps: number) { + this.steps = newSteps; + } +} + +class GameComplete { + private score: number; + private steps: number; + private onPlayAgain: () => void; + private onQuit: () => void; + + constructor(score: number, steps: number, onPlayAgain: () => void, onQuit: () => void) { + this.score = score; + this.steps = steps; + this.onPlayAgain = onPlayAgain; + this.onQuit = onQuit; + } + + render(): HTMLElement { + const container = createDiv('game-complete intro'); + + const title = document.createElement('h1'); + title.className = 'game-title'; + title.textContent = 'Monster Steps'; + + const subtitle = document.createElement('h2'); + subtitle.className = 'game-complete-subtitle'; + subtitle.textContent = 'Congratulations!'; + + const stats = createDiv('game-complete-stats'); + stats.innerHTML = ` +

Final Score: ${this.score}

+

Total Steps: ${this.steps}

+ `; + + const buttons = createDiv('intro-buttons'); + const playAgainButton = document.createElement('button'); + playAgainButton.textContent = 'Play Again'; + playAgainButton.onclick = this.onPlayAgain; + + const quitButton = document.createElement('button'); + quitButton.textContent = 'Quit'; + quitButton.onclick = this.onQuit; + + buttons.appendChild(playAgainButton); + buttons.appendChild(quitButton); + + const tip = document.createElement('p'); + tip.className = 'intro-tip'; + tip.textContent = 'Press right arrow to play again'; + + container.appendChild(title); + container.appendChild(subtitle); + container.appendChild(stats); + container.appendChild(buttons); + container.appendChild(tip); + + return container; + } +} \ No newline at end of file diff --git a/js13k2024/game/src/main.tsx b/js13k2024/game/src/main.tsx deleted file mode 100644 index 328ea95e..00000000 --- a/js13k2024/game/src/main.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { useState, useEffect, useCallback } from 'react'; -import { Intro } from './game-states/intro/intro'; -import { Instructions } from './game-states/instructions/instructions'; -import { Gameplay } from './game-states/gameplay/gameplay'; -import { GameOver } from './game-states/game-over/game-over'; -import { LevelComplete } from './game-states/level-complete/level-complete'; -import { soundEngine } from './sound/sound-engine'; -import './global-styles.css'; - -enum GameState { - Intro, - Instructions, - Gameplay, - GameOver, - LevelComplete, - GameComplete, -} - -export function MonsterStepsApp() { - const [gameState, setGameState] = useState(GameState.Intro); - const [level, setLevel] = useState(parseInt(document.location.hash.split('level')[1] ?? '1')); - const [score, setScore] = useState(0); - const [steps, setSteps] = useState(0); - - const startGame = useCallback(() => { - setGameState(GameState.Gameplay); - }, []); - - const showInstructions = () => { - setGameState(GameState.Instructions); - }; - - const restartGame = () => { - setLevel(1); - setScore(0); - setSteps(0); - setGameState(GameState.Gameplay); - }; - - const restartLevel = () => { - setGameState(GameState.Gameplay); - }; - - const quitGame = () => { - setGameState(GameState.Intro); - }; - - const gameOver = () => { - setGameState(GameState.GameOver); - }; - - const levelComplete = () => { - setLevel(level + 1); - setGameState(GameState.LevelComplete); - }; - - const nextLevel = useCallback(() => { - setGameState(GameState.Gameplay); - }, []); - - const gameComplete = () => { - soundEngine.playLevelComplete(); // We can reuse the level complete sound for game completion - setGameState(GameState.GameComplete); - }; - - const updateScore = (newScore: number) => setScore(newScore); - const updateSteps = (newSteps: number) => setSteps(newSteps); - - useEffect(() => { - const handleKeyPress = (e: KeyboardEvent) => { - if (e.key === 'ArrowRight') { - if (gameState === GameState.Intro) { - startGame(); - } else if (gameState === GameState.LevelComplete) { - nextLevel(); - } else if (gameState === GameState.GameOver) { - restartLevel(); - } else if (gameState === GameState.GameComplete) { - restartGame(); - } - } else if (e.key === 'Escape') { - if ( - gameState === GameState.Instructions || - gameState === GameState.GameOver || - gameState === GameState.GameComplete - ) { - quitGame(); - } - } - }; - - window.addEventListener('keydown', handleKeyPress); - return () => { - window.removeEventListener('keydown', handleKeyPress); - }; - }, [gameState, startGame, nextLevel]); - - return ( -
- {gameState === GameState.Intro && } - {gameState === GameState.Instructions && } - {gameState === GameState.Gameplay && ( - - )} - {gameState === GameState.GameOver && ( - - )} - {gameState === GameState.LevelComplete && ( - - )} - {gameState === GameState.GameComplete && ( - - )} -
- ); -} - -// GameComplete component -interface GameCompleteProps { - score: number; - steps: number; - onPlayAgain: () => void; - onQuit: () => void; -} - -function GameComplete({ score, steps, onPlayAgain, onQuit }: GameCompleteProps) { - return ( -
-

Monster Steps

-

Congratulations!

-
-

Final Score: {score}

-

Total Steps: {steps}

-
-
- - -
-

Press right arrow to play again

-
- ); -} diff --git a/js13k2024/game/src/utils/dom.ts b/js13k2024/game/src/utils/dom.ts new file mode 100644 index 00000000..e0cfb763 --- /dev/null +++ b/js13k2024/game/src/utils/dom.ts @@ -0,0 +1,124 @@ +/** + * Utility functions for DOM manipulation and event handling + */ + +/** + * Creates an HTML element with the given tag name + * @param tagName - The tag name of the element to create + * @returns The created HTML element + */ +export function createElement(tagName: K): HTMLElementTagNameMap[K] { + return document.createElement(tagName); +} + +/** + * Sets multiple attributes on an HTML element + * @param element - The element to set attributes on + * @param attributes - An object containing attribute key-value pairs + */ +export function setAttributes(element: HTMLElement, attributes: Record): void { + Object.entries(attributes).forEach(([key, value]) => { + element.setAttribute(key, value); + }); +} + +/** + * Adds an event listener to an element + * @param element - The element to add the event listener to + * @param eventType - The type of event to listen for + * @param handler - The function to call when the event occurs + */ +export function addEventHandler( + element: HTMLElement, + eventType: K, + handler: (event: HTMLElementEventMap[K]) => void, +): void { + element.addEventListener(eventType, handler); +} + +/** + * + * @param eventType + * @param handler + */ +export function addWindowEventHandler( + eventType: K, + handler: (event: GlobalEventHandlersEventMap[K]) => void, +): void { + window.addEventListener(eventType, handler); +} + +/** + * + * @param eventType + * @param handler + */ +export function removeWindowEventHandler( + eventType: K, + handler: (event: GlobalEventHandlersEventMap[K]) => void, +): void { + window.removeEventListener(eventType, handler); +} + +/** + * Updates the text content of an element + * @param element - The element to update + * @param text - The new text content + */ +export function updateText(element: HTMLElement, text: string): void { + element.textContent = text; +} + +/** + * Creates a button element with text and click handler + * @param text - The text content of the button + * @param onClick - The function to call when the button is clicked + * @returns The created button element + */ +export function createButton(text: string, onClick: () => void): HTMLButtonElement { + const button = createElement('button'); + updateText(button, text); + addEventHandler(button, 'click', onClick); + return button; +} + +/** + * Removes all child nodes from an element + * @param element - The element to clear + */ +export function clearElement(element: HTMLElement): void { + while (element.firstChild) { + element.removeChild(element.firstChild); + } +} + +/** + * Appends multiple child elements to a parent element + * @param parent - The parent element + * @param children - The child elements to append + */ +export function appendChildren(parent: HTMLElement, children: HTMLElement[]): void { + children.forEach((child) => parent.appendChild(child)); +} + +/** + * Creates a div element with the given class name + * @param className - The class name to apply to the div + * @returns The created div element + */ +export function createDiv(className?: string): HTMLDivElement { + const div = createElement('div'); + if (className) { + div.className = className; + } + return div; +} + +/** + * Sets the display style of an element + * @param element - The element to update + * @param display - The display value to set + */ +export function setDisplayStyle(element: HTMLElement, display: string): void { + element.style.display = display; +} diff --git a/js13k2024/game/tsconfig.json b/js13k2024/game/tsconfig.json index b7735095..b68e7025 100644 --- a/js13k2024/game/tsconfig.json +++ b/js13k2024/game/tsconfig.json @@ -13,14 +13,6 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react-jsx", - "jsxImportSource": "preact", - "paths": { - "react": ["./node_modules/preact/compat/"], - "react/jsx-runtime": ["./node_modules/preact/jsx-runtime"], - "react-dom": ["./node_modules/preact/compat/"], - "react-dom/*": ["./node_modules/preact/compat/*"] - }, /* Linting */ "strict": true, diff --git a/js13k2024/game/vite.config.mts b/js13k2024/game/vite.config.mts index 53069e70..eca6fe8e 100644 --- a/js13k2024/game/vite.config.mts +++ b/js13k2024/game/vite.config.mts @@ -1,5 +1,4 @@ import { defineConfig } from 'vite'; -import preact from '@preact/preset-vite'; import checker from 'vite-plugin-checker'; // https://vitejs.dev/config/ @@ -7,5 +6,5 @@ export default defineConfig({ build: { outDir: 'dist', }, - plugins: [preact(), checker({ typescript: true })], + plugins: [checker({ typescript: true })], });