diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b0657df..266e561 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,7 +5,8 @@ "customizations": { "vscode": { "extensions": [ - "EditorConfig.EditorConfig" + "EditorConfig.EditorConfig", + "YoavBls.pretty-ts-errors" ] } } diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml new file mode 100644 index 0000000..0bd94f6 --- /dev/null +++ b/.github/workflows/gh-pages.yml @@ -0,0 +1,51 @@ +name: Deploy to Github Pages + +on: + push: + branches: ['master'] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: 'pages' + cancel-in-progress: true + +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + + - name: Install Dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Setup Github Pages staging area + uses: actions/configure-pages@v5 + + - name: Upload build to Github Pages staging area + uses: actions/upload-pages-artifact@v3 + with: + path: './dest' + + - name: Deploy to Github Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index b25c15b..a2b4a64 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ *~ +node_modules/ +dist/ diff --git a/index.html b/index.html index a4ed308..5941639 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ Grid Warriors - +
@@ -15,7 +15,6 @@ High Score: 0
-
@@ -24,14 +23,12 @@
- - - + diff --git a/js/keyboard.js b/js/keyboard.js deleted file mode 100644 index 5afa52d..0000000 --- a/js/keyboard.js +++ /dev/null @@ -1,55 +0,0 @@ -// Keyboard State Object -// Manages the state of keyboard key presses -export const KeyboardState = { - pressed: {}, - - movement: { - UP: ['KeyW'], - DOWN: ['KeyS'], - LEFT: ['KeyA'], - RIGHT: ['KeyD'] - }, - - BLOCK: ['Numpad5'], - - disc: { - UP: ['Numpad8'], - UPRIGHT: ['Numpad9'], - RIGHT: ['Numpad6'], - DOWNRIGHT: ['Numpad3'], - DOWN: ['Numpad2'], - DOWNLEFT: ['Numpad1'], - LEFT: ['Numpad4'], - UPLEFT: ['Numpad7'] - }, - - isDown: function(keyList) { - for (const key of keyList) { - if (this.pressed[key]) { - return true - } - } - return false - }, - keyDown: function(code) { this.pressed[code] = true }, - keyUp: function(code) { delete this.pressed[code] }, - - discKeyPressed: function() { - for (const direction in this.disc) { - if (this.isDown(KeyboardState.disc[direction])) { - return direction - } - } - - return null - }, - releaseDiscKey: function(direction) { - const keys = this.disc[direction] - for (const key of keys) { - this.keyUp(key) - } - } -} - -addEventListener('keyup', e => { KeyboardState.keyUp(e.code) }) -addEventListener('keydown', e => { KeyboardState.keyDown(e.code) }) diff --git a/js/main.js b/js/main.js deleted file mode 100644 index 145425f..0000000 --- a/js/main.js +++ /dev/null @@ -1,7 +0,0 @@ -import config from "./config.js" -import { GridWarriors } from "./gridwarriors.js" - -window.onload = function() { - window.config = config - window.gridwarriors = new GridWarriors() -} diff --git a/js/overlay.js b/js/overlay.js deleted file mode 100644 index 7c9941a..0000000 --- a/js/overlay.js +++ /dev/null @@ -1,22 +0,0 @@ -export class Overlay { - constructor() { - this.element = document.getElementById('overlay') - this._setup() - } - - _setup() { - document.querySelector('button.start') - .addEventListener('click', () => { - this.hide() - dispatchEvent(new CustomEvent('GameStart')) - }) - } - - show() { - this.element.classList.add('show') - } - - hide() { - this.element.classList.remove('show') - } -} diff --git a/js/scoreboard.js b/js/scoreboard.js deleted file mode 100644 index d1b345d..0000000 --- a/js/scoreboard.js +++ /dev/null @@ -1,49 +0,0 @@ -export class Scoreboard { - constructor() { - this.scoreElement = document.getElementById('score') - this.highScoreElement = document.getElementById('highscore') - - this.currentScore = 0 - this.highScore = this._getHighScore() - - this._updateBoard() - } - - reset() { - this.currentScore = 0 - this.highScore = this._getHighScore() - } - - score(points) { - const newScore = this.currentScore + points - if (newScore >= 0) - this.currentScore = newScore - - this._updateHighScore() - this._updateBoard() - } - - _getHighScore() { - const highScore = localStorage.getItem('highScore') || 0 - try { - parseInt(highScore) - } - catch { - highScore = 0 - } - - return highScore - } - - _updateHighScore() { - if (this.currentScore <= this.highScore) return - - this.highScore = this.currentScore - localStorage.setItem('highScore', this.highScore) - } - - _updateBoard() { - this.scoreElement.innerHTML = this.currentScore - this.highScoreElement.innerHTML = this.highScore - } -} diff --git a/js/units/warrior.js b/js/units/warrior.js deleted file mode 100644 index a052c8c..0000000 --- a/js/units/warrior.js +++ /dev/null @@ -1,12 +0,0 @@ -import { Unit } from "./unit.js" -import { BeginnerDisc } from "../discs/beginner.js" - -export class Warrior extends Unit { - constructor(gameGrid, location) { - super(gameGrid, 'Warrior', config.warriorColor, location) - - this.baseSpeed = config.warriorSpeed - - this.disc = new BeginnerDisc(this) - } -} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9c84a9b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,872 @@ +{ + "name": "gridwarriors", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "gridwarriors", + "version": "0.0.1", + "license": "MIT", + "devDependencies": { + "@types/node": "^20.11.30", + "typescript": "^5.4.3", + "vite": "^5.2.6", + "vite-tsconfig-paths": "^4.3.2" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.1.tgz", + "integrity": "sha512-4C4UERETjXpC4WpBXDbkgNVgHyWfG3B/NKY46e7w5H134UDOFqUJKpsLm0UYmuupW+aJmRgeScrDNfvZ5WV80A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.1.tgz", + "integrity": "sha512-TrTaFJ9pXgfXEiJKQ3yQRelpQFqgRzVR9it8DbeRzG0RX7mKUy0bqhCFsgevwXLJepQKTnLl95TnPGf9T9AMOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.1.tgz", + "integrity": "sha512-fz7jN6ahTI3cKzDO2otQuybts5cyu0feymg0bjvYCBrZQ8tSgE8pc0sSNEuGvifrQJWiwx9F05BowihmLxeQKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.1.tgz", + "integrity": "sha512-WTvdz7SLMlJpektdrnWRUN9C0N2qNHwNbWpNo0a3Tod3gb9leX+yrYdCeB7VV36OtoyiPAivl7/xZ3G1z5h20g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.1.tgz", + "integrity": "sha512-dBHQl+7wZzBYcIF6o4k2XkAfwP2ks1mYW2q/Gzv9n39uDcDiAGDqEyml08OdY0BIct0yLSPkDTqn4i6czpBLLw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.1.tgz", + "integrity": "sha512-bur4JOxvYxfrAmocRJIW0SADs3QdEYK6TQ7dTNz6Z4/lySeu3Z1H/+tl0a4qDYv0bCdBpUYM0sYa/X+9ZqgfSQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.1.tgz", + "integrity": "sha512-ssp77SjcDIUSoUyj7DU7/5iwM4ZEluY+N8umtCT9nBRs3u045t0KkW02LTyHouHDomnMXaXSZcCSr2bdMK63kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.1.tgz", + "integrity": "sha512-Jv1DkIvwEPAb+v25/Unrnnq9BO3F5cbFPT821n3S5litkz+O5NuXuNhqtPx5KtcwOTtaqkTsO+IVzJOsxd11aQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.13.1.tgz", + "integrity": "sha512-U564BrhEfaNChdATQaEODtquCC7Ez+8Hxz1h5MAdMYj0AqD0GA9rHCpElajb/sQcaFL6NXmHc5O+7FXpWMa73Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.1.tgz", + "integrity": "sha512-zGRDulLTeDemR8DFYyFIQ8kMP02xpUsX4IBikc7lwL9PrwR3gWmX2NopqiGlI2ZVWMl15qZeUjumTwpv18N7sQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.1.tgz", + "integrity": "sha512-VTk/MveyPdMFkYJJPCkYBw07KcTkGU2hLEyqYMsU4NjiOfzoaDTW9PWGRsNwiOA3qI0k/JQPjkl/4FCK1smskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.1.tgz", + "integrity": "sha512-L+hX8Dtibb02r/OYCsp4sQQIi3ldZkFI0EUkMTDwRfFykXBPptoz/tuuGqEd3bThBSLRWPR6wsixDSgOx/U3Zw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.1.tgz", + "integrity": "sha512-+dI2jVPfM5A8zme8riEoNC7UKk0Lzc7jCj/U89cQIrOjrZTCWZl/+IXUeRT2rEZ5j25lnSA9G9H1Ob9azaF/KQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.1.tgz", + "integrity": "sha512-YY1Exxo2viZ/O2dMHuwQvimJ0SqvL+OAWQLLY6rvXavgQKjhQUzn7nc1Dd29gjB5Fqi00nrBWctJBOyfVMIVxw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.1.tgz", + "integrity": "sha512-hFi+fU132IvJ2ZuihN56dwgpltpmLZHZWsx27rMCTZ2sYwrqlgL5sECGy1eeV2lAihD8EzChBVVhsXci0wD4Tg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.13.1", + "@rollup/rollup-android-arm64": "4.13.1", + "@rollup/rollup-darwin-arm64": "4.13.1", + "@rollup/rollup-darwin-x64": "4.13.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.13.1", + "@rollup/rollup-linux-arm64-gnu": "4.13.1", + "@rollup/rollup-linux-arm64-musl": "4.13.1", + "@rollup/rollup-linux-riscv64-gnu": "4.13.1", + "@rollup/rollup-linux-s390x-gnu": "4.13.1", + "@rollup/rollup-linux-x64-gnu": "4.13.1", + "@rollup/rollup-linux-x64-musl": "4.13.1", + "@rollup/rollup-win32-arm64-msvc": "4.13.1", + "@rollup/rollup-win32-ia32-msvc": "4.13.1", + "@rollup/rollup-win32-x64-msvc": "4.13.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tsconfck": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.0.3.tgz", + "integrity": "sha512-4t0noZX9t6GcPTfBAbIbbIU4pfpCwh0ueq3S4O/5qXI1VwK1outmxhe9dOiEWqMz3MW2LKgDTpqWV+37IWuVbA==", + "dev": true, + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/vite": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.6.tgz", + "integrity": "sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==", + "dev": true, + "dependencies": { + "esbuild": "^0.20.1", + "postcss": "^8.4.36", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-tsconfig-paths": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz", + "integrity": "sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c047aba --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "gridwarriors", + "version": "1.0.0", + "description": "Clone of old Intellivision game", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "author": "Justin La Sotten", + "license": "MIT", + "devDependencies": { + "@types/node": "^20.11.30", + "typescript": "^5.4.3", + "vite": "^5.2.6", + "vite-tsconfig-paths": "^4.3.2" + } +} diff --git a/css/gridwarriors.css b/public/css/gridwarriors.css similarity index 100% rename from css/gridwarriors.css rename to public/css/gridwarriors.css diff --git a/favicon.ico b/public/images/favicon.ico similarity index 100% rename from favicon.ico rename to public/images/favicon.ico diff --git a/js/config.js b/src/config.ts similarity index 98% rename from js/config.js rename to src/config.ts index 4cfe162..a13c0a9 100644 --- a/js/config.js +++ b/src/config.ts @@ -1,4 +1,4 @@ -export default { +export const config = { /// Grid width: 1200, height: 800, diff --git a/js/discs/beginner.js b/src/discs/beginner.ts similarity index 53% rename from js/discs/beginner.js rename to src/discs/beginner.ts index b77f786..c5c92b6 100644 --- a/js/discs/beginner.js +++ b/src/discs/beginner.ts @@ -1,7 +1,9 @@ -import { Disc } from "./disc.js" +import { config } from "@/config" +import { Disc } from "@/discs/disc" +import { Unit } from "@/units/unit" export class BeginnerDisc extends Disc { - constructor(unit) { + constructor(unit: Unit) { super('Beginner', config.beginnerDiscColor, unit) this.baseSpeed = config.beginnerDiscSpeed diff --git a/js/discs/disc.js b/src/discs/disc.ts similarity index 84% rename from js/discs/disc.js rename to src/discs/disc.ts index 74aeafa..bc16e9a 100644 --- a/js/discs/disc.js +++ b/src/discs/disc.ts @@ -1,27 +1,41 @@ -import { Sprite } from "../sprite.js" -import { Vector } from "../vector.js" +import { config } from "@/config" -export const DiscStates = Object.freeze({ - HELD: Symbol("held"), - DEADLY: Symbol("deadly"), - BOUNCING: Symbol("bouncing"), - RETURNING: Symbol("returning") -}) +import { Sprite } from "@/sprite" +import { Vector } from "@/vector" +import { Unit } from "@/units/unit" + +export enum DiscStates { + HELD, + DEADLY, + BOUNCING, + RETURNING +} export class Disc extends Sprite { - constructor(name, color, unit) { + private discSize: number + protected owner: Unit + protected strength: number + protected speedModifier: number + protected baseSpeed: number + + public status: DiscStates + public returnable: boolean + public primed: boolean + private collided: Unit | null + + constructor(name: string, color: string, unit: Unit) { const discSize = config.unitSize / 4 super(unit.gameGrid, name, discSize, discSize, color, unit.location) this.discSize = discSize - this.owner = unit - this.strength = 1 - this.status = DiscStates.HELD this.speedModifier = 0 - this.velocity = null + this.baseSpeed = 0 + + // Stateful + this.status = DiscStates.HELD this.returnable = false this.primed = false this.collided = null @@ -75,7 +89,7 @@ export class Disc extends Sprite { } } - checkCollide(unit) { + checkCollide(unit: Unit) { // We don't care about non-deadly discs if (this.status !== DiscStates.DEADLY) return @@ -108,7 +122,7 @@ export class Disc extends Sprite { } } - thrown(direction) { + thrown(direction: Vector) { this.status = DiscStates.DEADLY const velocity = new Vector([0, 0]) @@ -116,7 +130,7 @@ export class Disc extends Sprite { direction.mul(this.baseSpeed + this.speedModifier) velocity.add(direction) - velocity.limit(this.baseSpeed + this.speedModfier) + velocity.limit(this.baseSpeed + this.speedModifier) this.velocity = velocity } diff --git a/js/discs/homing.js b/src/discs/homing.ts similarity index 84% rename from js/discs/homing.js rename to src/discs/homing.ts index f59e95f..a271096 100644 --- a/js/discs/homing.js +++ b/src/discs/homing.ts @@ -1,8 +1,13 @@ -import { Vector } from "../vector.js" -import { Disc, DiscStates } from "./disc.js" +import { config } from "@/config" + +import { Vector } from "@/vector" +import { Disc, DiscStates } from "@/discs/disc" +import { Unit } from "@/units/unit" export class HomingDisc extends Disc { - constructor(unit) { + private homing: boolean + + constructor(unit: Unit) { super('Homing', config.homingDiscColor, unit) this.baseSpeed = config.homingDiscSpeed @@ -32,7 +37,7 @@ export class HomingDisc extends Disc { } } - thrown(direction) { + thrown(direction: Vector) { // After one second of regular movement, start homing setTimeout(this.startHoming.bind(this), 1000) super.thrown(direction) @@ -48,6 +53,8 @@ export class HomingDisc extends Disc { } homeInOnPlayer() { + if (! this.gameGrid.player) return + this.velocity = new Vector([0, 0]) const playerForce = Vector.subFactory(this.gameGrid.player.location, this.location) diff --git a/js/discs/intermediate.js b/src/discs/intermediate.ts similarity index 59% rename from js/discs/intermediate.js rename to src/discs/intermediate.ts index ae3d398..08c55cc 100644 --- a/js/discs/intermediate.js +++ b/src/discs/intermediate.ts @@ -1,7 +1,10 @@ -import { Disc } from "./disc.js" +import { config } from "@/config" + +import { Disc } from "@/discs//disc" +import { Unit } from "@/units/unit" export class IntermediateDisc extends Disc { - constructor(unit) { + constructor(unit: Unit) { super('Intermediate', config.intermediateDiscColor, unit) this.baseSpeed = config.intermediateDiscSpeed diff --git a/js/discs/player.js b/src/discs/player.ts similarity index 52% rename from js/discs/player.js rename to src/discs/player.ts index 970f774..5fd0654 100644 --- a/js/discs/player.js +++ b/src/discs/player.ts @@ -1,7 +1,10 @@ -import { Disc } from "./disc.js" +import { config } from "@/config" + +import { Disc } from "@/discs/disc" +import { Unit } from "@/units/unit" export class PlayerDisc extends Disc { - constructor(unit) { + constructor(unit: Unit) { super('Player', config.playerDiscColor, unit) this.baseSpeed = config.playerDiscSpeed diff --git a/js/door.js b/src/door.ts similarity index 76% rename from js/door.js rename to src/door.ts index 6b52a02..e5ba292 100644 --- a/js/door.js +++ b/src/door.ts @@ -1,26 +1,49 @@ -import config from "./config.js" -import { Vector } from "./vector.js" -import { UnitFacings } from "./units/unit.js" - -export const DoorStates = Object.freeze({ - CLOSED: { color: config.doorClosedColor }, - OPEN: { color: config.doorOpenColor }, - JAMMED: { color: config.doorJammedColor } -}) +import { config } from "@/config" + +import { Sprite } from "@/sprite" +import { Vector } from "@/vector" +import { UnitFacings } from "@/units/unit" +import { GameGrid } from "@/gamegrid" + +type DoorStateNames = "CLOSED" | "OPEN" | "JAMMED" +type DoorStateInfo = Symbol +export type DoorStates = Record +export const DoorStates: DoorStates = { + CLOSED: Symbol('closed'), + OPEN: Symbol('open'), + JAMMED: Symbol('jammed') +} -export const DoorOrientations = Object.freeze({ +type DoorOrientationNames = "HORIZONTAL" | "VERTICAL" +type DoorOrientationInfo = Symbol +export type DoorOrientations = Record +export const DoorOrientations: DoorOrientations = { HORIZONTAL: Symbol("horizontal"), VERTICAL: Symbol("vertical") -}) +} -export const DoorSides = Object.freeze({ +export type DoorSideNames = "TOP" | "BOTTOM" | "LEFT" | "RIGHT" +export type DoorSideInfo = Symbol +export type DoorSides = Record +export const DoorSides: DoorSides = { TOP: Symbol("top"), BOTTOM: Symbol("bottom"), LEFT: Symbol("left"), RIGHT: Symbol("right") -}) +} -export const DoorPositions = Object.freeze({ +export type DoorPositionNames = "TOPLEFT" | "TOPCENTER" | "TOPRIGHT" + | "BOTTOMLEFT" | "BOTTOMCENTER" | "BOTTOMRIGHT" + | "LEFTTOP" | "LEFTCENTER" | "LEFTBOTTOM" + | "RIGHTTOP" | "RIGHTCENTER" | "RIGHTBOTTOM" +export type DoorPositionInfo = { + side: DoorStateInfo, + orientation: DoorOrientationInfo, + facing: UnitFacings, + teleportsTo: DoorPositionNames +} +export type DoorPositions = Record +export const DoorPositions: DoorPositions = { TOPLEFT: { side: DoorSides.TOP, orientation: DoorOrientations.HORIZONTAL, facing: UnitFacings.DOWN, teleportsTo: 'BOTTOMLEFT' }, TOPCENTER: { side: DoorSides.TOP, orientation: DoorOrientations.HORIZONTAL, facing: UnitFacings.DOWN, teleportsTo: 'BOTTOMCENTER' }, TOPRIGHT: { side: DoorSides.TOP, orientation: DoorOrientations.HORIZONTAL, facing: UnitFacings.DOWN, teleportsTo: 'BOTTOMRIGHT' }, @@ -33,23 +56,39 @@ export const DoorPositions = Object.freeze({ RIGHTTOP: { side: DoorSides.RIGHT, orientation: DoorOrientations.VERTICAL, facing: UnitFacings.LEFT, teleportsTo: 'LEFTTOP' }, RIGHTCENTER: { side: DoorSides.RIGHT, orientation: DoorOrientations.VERTICAL, facing: UnitFacings.LEFT, teleportsTo: 'LEFTCENTER' }, RIGHTBOTTOM: { side: DoorSides.RIGHT, orientation: DoorOrientations.VERTICAL, facing: UnitFacings.LEFT, teleportsTo: 'LEFTBOTTOM' }, -}) +} export class Door { - constructor(gameGrid, position) { + private gameGrid: GameGrid + private canvas: HTMLCanvasElement + private context: CanvasRenderingContext2D + + public position: DoorPositionInfo + private rect: number[] + private color: string + private location: Vector + private width: number + private height: number + + public state: DoorStateInfo + + public spawnLocation: Vector + + constructor(gameGrid: GameGrid, position: DoorPositionInfo) { this.gameGrid = gameGrid this.canvas = this.gameGrid.canvas this.context = this.gameGrid.context this.position = position this.rect = [] - this.location + this.color = config.doorClosedColor + this.location = new Vector([0, 0]) this.width = 0 this.height = 0 this.state = DoorStates.CLOSED - this.spawnLocation + this.spawnLocation = new Vector([0, 0]) this.setup() } @@ -151,7 +190,7 @@ export class Door { } draw() { - this.context.fillStyle = this.state.color + this.context.fillStyle = this.color this.context.fillRect( this.rect[0], this.rect[1], this.width, this.height @@ -162,12 +201,14 @@ export class Door { if (this.state !== DoorStates.CLOSED) return this.state = DoorStates.OPEN + this.color = config.doorOpenColor } jam() { if (this.state !== DoorStates.OPEN) return this.state = DoorStates.JAMMED + this.color = config.doorJammedColor dispatchEvent(new CustomEvent("Score", { detail: { points: 100 } })) } @@ -179,9 +220,10 @@ export class Door { reset() { this.state = DoorStates.CLOSED + this.color = config.doorClosedColor } - isCollided(sprite) { + isCollided(sprite: Sprite) { if ( this.rect[0] > sprite.boundingBox[2] || sprite.boundingBox[0] > this.rect[2] diff --git a/js/gamegrid.js b/src/gamegrid.ts similarity index 72% rename from js/gamegrid.js rename to src/gamegrid.ts index fb710f7..7ffd4df 100644 --- a/js/gamegrid.js +++ b/src/gamegrid.ts @@ -1,14 +1,26 @@ -import { Player } from "./units/player.js" -import { DiscStates } from "./discs/disc.js" -import { Door, DoorPositions, DoorStates } from "./door.js" -import { Vector } from "./vector.js" +import { config } from "@/config" + +import { Player, Enemy, EnemyTypes, Unit } from "@/units/index" +import { DiscStates } from "@/discs/disc" +import { Door, DoorPositionNames, DoorPositions, DoorStates, DoorSideInfo } from "@/door" +import { Vector } from "@/vector" export class GameGrid { + public canvas: HTMLCanvasElement + public context: CanvasRenderingContext2D + public diagonal: number + private doors: Door[] + public enemies: Enemy[] + public player: Player | null + constructor() { console.log("Grid: Rezzing") - this.canvas = document.getElementById('gamegrid') - this.context = this.canvas.getContext('2d') + const canvas = document.getElementById('gamegrid') + if (! canvas) throw new Error('Unable to find GameGrid element') + + this.canvas = canvas as HTMLCanvasElement + this.context = this.canvas.getContext('2d') as CanvasRenderingContext2D this.canvas.width = config.width this.canvas.height = config.height @@ -32,7 +44,8 @@ export class GameGrid { new Vector([this.canvas.width / 2, this.canvas.height / 2]) ) for (const position in DoorPositions) { - this.doors.push(new Door(this, DoorPositions[position])) + const info = DoorPositions[position as DoorPositionNames] + this.doors.push(new Door(this, info)) } } @@ -72,7 +85,7 @@ export class GameGrid { if (!this.player) return // Check for hits/deaths by player - if (this.player.disc.status === DiscStates.DEADLY) { + if (this.player?.disc?.status === DiscStates.DEADLY) { for (const enemy of this.enemies) { this.player.disc.checkCollide(enemy) } @@ -80,7 +93,7 @@ export class GameGrid { // Check for player hits/death for (const enemy of this.enemies) { - enemy.disc.checkCollide(this.player) + enemy?.disc?.checkCollide(this.player) } // Player might not exist after collisions @@ -90,8 +103,8 @@ export class GameGrid { for (const unit of [...this.enemies, this.player]) { for (const door of this.doors) { // Did units disc hit a door - if (door.state === DoorStates.OPEN && unit.disc.status === DiscStates.DEADLY && door.isCollided(unit.disc)) { - unit.isPlayer ? door.jam() : door.close() + if (door.state === DoorStates.OPEN && unit?.disc?.status === DiscStates.DEADLY && door.isCollided(unit.disc)) { + unit?.isPlayer ? door.jam() : door.close() } } } @@ -105,10 +118,10 @@ export class GameGrid { // Teleportation for (const unit of [...this.enemies, this.player]) { for (const door of this.doors) { - if (door.state === DoorStates.JAMMED && door.isCollided(unit) && ! unit.isTeleporting) { + if (door.state === DoorStates.JAMMED && door.isCollided(unit) && ! unit?.isTeleporting) { const teleportsTo = DoorPositions[door.position.teleportsTo] const teleportDoor = this.doors.find(door => door.position === teleportsTo) - if (teleportDoor.state !== DoorStates.JAMMED) break + if (teleportDoor?.state !== DoorStates.JAMMED) break unit.teleportTo(teleportDoor) } @@ -116,7 +129,7 @@ export class GameGrid { } } - addEnemies(enemyUnits, doorSide) { + addEnemies(enemyUnits: EnemyTypes[], doorSide: DoorSideInfo) { this.closeDoors() const doors = this.doors.filter(door => door.position.side === doorSide ) @@ -130,7 +143,7 @@ export class GameGrid { } } - removeEnemy(enemy) { + removeEnemy(enemy: Unit) { console.log(`${enemy.name} derezzed`) this.enemies = this.enemies.filter((unit) => { return unit != enemy diff --git a/js/gridwarriors.js b/src/gridwarriors.ts similarity index 64% rename from js/gridwarriors.js rename to src/gridwarriors.ts index c265c33..af356d1 100644 --- a/js/gridwarriors.js +++ b/src/gridwarriors.ts @@ -1,9 +1,30 @@ -import { GameGrid } from "./gamegrid.js" -import { Scoreboard } from "./scoreboard.js" -import { Overlay } from "./overlay.js" -import { WaveManager } from "./wave.js" +import { config } from "@/config" + +import { GameGrid } from "@/gamegrid" +import { Scoreboard } from "@/scoreboard" +import { Overlay } from "@/overlay" +import { WaveManager } from "@/wave" + +import { Unit } from "@/units/unit" + +type UnitHit = { + winner: Unit, + loser: Unit +} + +type Score = { + points: number +} export class GridWarriors { + private scoreBoard: Scoreboard + private overlay: Overlay + private gameGrid: GameGrid + private waveManager: WaveManager + + private playing: boolean + private paused: boolean + constructor() { this.scoreBoard = new Scoreboard() this.overlay = new Overlay() @@ -16,26 +37,26 @@ export class GridWarriors { addEventListener('keypress', this.pause.bind(this)) addEventListener('GameStart', this.startGame.bind(this)) addEventListener('GameOver', this.gameOver.bind(this)) - addEventListener('UnitHit', this.unitHit.bind(this)) - addEventListener('Score', this.score.bind(this)) + addEventListener('UnitHit', this.unitHit.bind(this) as EventListener) + addEventListener('Score', this.score.bind(this) as EventListener) this.overlay.show() } - startGame() { + private startGame() { this.scoreBoard.reset() this.gameGrid.reset() this.gameGrid.setup() this.waveManager.reset() - this.waveManager.trigger(config.gameStartTime) + this.waveManager.trigger(window.config.gameStartTime) this.playing = true this.play() } - play() { + private play() { if (! this.playing) return if (this.paused) { @@ -49,7 +70,7 @@ export class GridWarriors { requestAnimationFrame(this.play.bind(this)) } - pause(e) { + private pause(e: KeyboardEvent) { // Look for 'P' key' if (!['KeyP'].includes(e.code)) return @@ -58,33 +79,31 @@ export class GridWarriors { console.log(this.paused ? 'Paused' : 'Unpaused') } - unitHit(e) { + private unitHit(e: CustomEvent) { const winner = e.detail.winner const loser = e.detail.loser console.log(`${winner.name} hit ${loser.name}`) - this._playerhit(winner, loser) - this._enemyhit(winner, loser) + this.playerhit(winner, loser) + this.enemyhit(winner, loser) } - score(e) { + private score(e: CustomEvent) { this.scoreBoard.score(e.detail.points) } - gameOver() { + private gameOver() { console.log('Game Over') this.playing = false this.overlay.show() } - // ----------------------------------- - - _playerhit(winner, loser) { + private playerhit(winner: Unit, loser: Unit) { if (! loser.isPlayer) return // Lose points when the player is hit dispatchEvent( - new CustomEvent('Score', { + new CustomEvent('Score', { detail: { points: -winner.points } @@ -99,14 +118,14 @@ export class GridWarriors { } } - _enemyhit(winner, loser) { + private enemyhit(winner: Unit, loser: Unit) { if (loser.isPlayer) return if (! loser.isDead()) return this.gameGrid.removeEnemy(loser) this.waveManager.trigger(config.respawnInterval) dispatchEvent( - new CustomEvent('Score', { + new CustomEvent('Score', { detail: { points: winner.points } diff --git a/src/keyboard.ts b/src/keyboard.ts new file mode 100644 index 0000000..2c1feec --- /dev/null +++ b/src/keyboard.ts @@ -0,0 +1,66 @@ +type KeyboardMovementKeys = "UP" | "DOWN" | "LEFT" | "RIGHT" + +type KeyboardDiscKeys = "UPLEFT" | "UP" | "UPRIGHT" + | "LEFT" | "RIGHT" + | "DOWNLEFT" | "DOWN" | "DOWNRIGHT" + +type KeyboardState = { + pressed: { [key: string]: boolean } + movement: Record + BLOCK: string + disc: Record + isDown: Function + keyDown: Function + keyUp: Function + discKeyPressed: Function + releaseDiscKey: Function +} + +// Keyboard State Object +// Manages the state of keyboard key presses +export const KeyboardState: KeyboardState = { + pressed: {}, + + movement: { + UP: 'KeyW', + DOWN: 'KeyS', + LEFT: 'KeyA', + RIGHT: 'KeyD' + }, + + BLOCK: 'Numpad5', + + disc: { + UP: 'Numpad8', + UPRIGHT: 'Numpad9', + RIGHT: 'Numpad6', + DOWNRIGHT: 'Numpad3', + DOWN: 'Numpad2', + DOWNLEFT: 'Numpad1', + LEFT: 'Numpad4', + UPLEFT: 'Numpad7' + }, + + isDown: function(key: string) { + return this.pressed[key] + }, + keyDown: function(code: string) { this.pressed[code] = true }, + keyUp: function(code: string) { delete this.pressed[code] }, + + discKeyPressed: function() { + for (const direction in this.disc) { + if (this.isDown(this.disc[direction as KeyboardDiscKeys])) { + return direction + } + } + + return null + }, + releaseDiscKey: function(direction: string) { + const key = this.disc[direction as KeyboardDiscKeys] + this.keyUp(key) + } +} + +addEventListener('keyup', e => { KeyboardState.keyUp(e.code) }) +addEventListener('keydown', e => { KeyboardState.keyDown(e.code) }) diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..7db1bac --- /dev/null +++ b/src/main.ts @@ -0,0 +1,14 @@ +import { config } from "@/config" +import { GridWarriors } from "@/gridwarriors" + +declare global { + interface Window { + config: typeof config + gridwarriors: GridWarriors + } +} + +window.onload = function() { + window.config = config + window.gridwarriors = new GridWarriors() +} diff --git a/src/overlay.ts b/src/overlay.ts new file mode 100644 index 0000000..25ddad0 --- /dev/null +++ b/src/overlay.ts @@ -0,0 +1,27 @@ +export class Overlay { + private overlayElement: HTMLElement + + constructor() { + const overlay = document.getElementById('overlay') + if (! overlay) throw new Error('Unable to find Overlay element') + + this.overlayElement = overlay + this.setup() + } + + private setup() { + document.querySelector('button.start') + ?.addEventListener('click', () => { + this.hide() + dispatchEvent(new CustomEvent('GameStart')) + }) + } + + show() { + this.overlayElement.classList.add('show') + } + + hide() { + this.overlayElement.classList.remove('show') + } +} diff --git a/src/scoreboard.ts b/src/scoreboard.ts new file mode 100644 index 0000000..1c3b27a --- /dev/null +++ b/src/scoreboard.ts @@ -0,0 +1,58 @@ +export class Scoreboard { + private scoreElement: HTMLElement + private highScoreElement: HTMLElement + + private currentScore: number + private highScore: number + + constructor() { + const score = document.getElementById('score') + if (! score) throw new Error('Unable to find Score element') + this.scoreElement = score + + const highScore = document.getElementById('highscore') + if (! highScore) throw new Error('Unable to find Highscore element') + this.highScoreElement = highScore + + this.currentScore = 0 + this.highScore = this.getHighScore() + + this.updateBoard() + } + + public reset() { + this.currentScore = 0 + this.highScore = this.getHighScore() + } + + public score(points: number) { + const newScore = this.currentScore + points + if (newScore >= 0) + this.currentScore = newScore + + this.updateHighScore() + this.updateBoard() + } + + private getHighScore() { + let highScore = 0 + try { + highScore = parseInt(localStorage.getItem('highScore') || '0') + } + catch {} + + return highScore + } + + private updateHighScore() { + if (this.currentScore <= this.highScore) return + + this.highScore = this.currentScore + localStorage.setItem('highScore', this.highScore.toString()) + } + + private updateBoard() { + this.scoreElement.innerHTML = this.currentScore.toString() + this.highScoreElement.innerHTML = this.highScore.toString() + } +} diff --git a/js/sprite.js b/src/sprite.ts similarity index 76% rename from js/sprite.js rename to src/sprite.ts index ba5df23..eaf2814 100644 --- a/js/sprite.js +++ b/src/sprite.ts @@ -1,7 +1,19 @@ -import { Vector } from "./vector.js" +import { Vector } from "@/vector" +import { GameGrid } from "@/gamegrid" export class Sprite { - constructor(gameGrid, name, width, height, color, location) { + public gameGrid: GameGrid + public name: string + protected width: number + protected height: number + protected color: string + public location: Vector + + protected velocity: Vector + + public boundingBox: [number, number, number, number] + + constructor(gameGrid: GameGrid, name: string, width: number, height: number, color: string, location: Vector) { this.gameGrid = gameGrid this.name = name this.width = width @@ -10,24 +22,24 @@ export class Sprite { this.location = Vector.clone(location) - this.buildBoundingBox() + this.boundingBox = this.buildBoundingBox() // Filled in by child class - this.velocity = null + this.velocity = new Vector([0, 0]) console.log('Sprite: ' + this.name + ' Rezzed') } - changeWidth(width) { + changeWidth(width: number) { this.width = width } - changeHeight(height) { + changeHeight(height: number) { this.height = height } - buildBoundingBox() { - this.boundingBox = [ + buildBoundingBox(): [number, number, number, number] { + return [ this.location.points[0] - Math.round(this.width / 2), this.location.points[1] - Math.round(this.height / 2), this.location.points[0] + Math.round(this.width / 2), @@ -65,7 +77,7 @@ export class Sprite { return bounded } - collision(sprite) { + collision(sprite: Sprite) { // See if the 2 boxes intersect in any way this.gameGrid.context.beginPath() this.gameGrid.context.rect( @@ -82,14 +94,14 @@ export class Sprite { ) } - touchLocation(location) { + touchLocation(location: Vector) { this.gameGrid.context.beginPath() this.gameGrid.context.rect(...this.boundingBox) return this.gameGrid.context.isPointInPath(...location.points) } drawSprite() { - this.buildBoundingBox() + this.boundingBox = this.buildBoundingBox() this.gameGrid.context.fillStyle = this.color this.gameGrid.context.fillRect( diff --git a/js/units/bulldog.js b/src/units/bulldog.ts similarity index 57% rename from js/units/bulldog.js rename to src/units/bulldog.ts index 1c2de75..2a8dae2 100644 --- a/js/units/bulldog.js +++ b/src/units/bulldog.ts @@ -1,8 +1,12 @@ -import { Unit } from "./unit.js" -import { BeginnerDisc } from "../discs/beginner.js" +import { config } from "@/config" + +import { Unit } from "@/units/unit" +import { Vector } from "@/vector" +import { GameGrid } from "@/gamegrid" +import { BeginnerDisc } from "@/discs/beginner" export class Bulldog extends Unit { - constructor(gameGrid, location) { + constructor(gameGrid: GameGrid, location: Vector) { super(gameGrid, 'Bulldog', config.bulldogColor, location) this.baseSpeed = config.bulldogSpeed diff --git a/src/units/index.ts b/src/units/index.ts new file mode 100644 index 0000000..987eb9a --- /dev/null +++ b/src/units/index.ts @@ -0,0 +1,14 @@ +import { Unit } from "@/units/unit" +import { Player } from "@/units/player" +import { Warrior } from "@/units/warrior" +import { Bulldog } from "@/units/bulldog" +import { Leader } from "@/units/leader" + +export { Unit } +export { Player } +export { Warrior } +export { Bulldog } +export { Leader } + +export type Enemy = Warrior | Bulldog | Leader +export type EnemyTypes = typeof Warrior | typeof Bulldog | typeof Leader diff --git a/js/units/leader.js b/src/units/leader.ts similarity index 59% rename from js/units/leader.js rename to src/units/leader.ts index 9fab6c5..80b7adf 100644 --- a/js/units/leader.js +++ b/src/units/leader.ts @@ -1,9 +1,13 @@ -import { Unit } from "./unit.js" -import { IntermediateDisc } from "../discs/intermediate.js" -import { HomingDisc } from "../discs/homing.js" +import { config } from "@/config" + +import { Unit } from "@/units/unit" +import { Vector } from "@/vector" +import { GameGrid } from "@/gamegrid" +import { IntermediateDisc } from "@/discs/intermediate" +import { HomingDisc } from "@/discs/homing" export class Leader extends Unit { - constructor(gameGrid, location) { + constructor(gameGrid: GameGrid, location: Vector) { super(gameGrid, 'Leader', config.leaderColor, location) this.baseSpeed = config.leaderSpeed diff --git a/js/units/player.js b/src/units/player.ts similarity index 82% rename from js/units/player.js rename to src/units/player.ts index 0eec630..825a2d8 100644 --- a/js/units/player.js +++ b/src/units/player.ts @@ -1,11 +1,14 @@ -import { Unit } from "./unit.js" -import { DiscStates } from "../discs/disc.js" -import { PlayerDisc } from "../discs/player.js" -import { Vector } from "../vector.js" -import { KeyboardState } from "../keyboard.js" +import { config } from "@/config" + +import { Unit } from "@/units/unit" +import { DiscStates } from "@/discs/disc" +import { PlayerDisc } from "@/discs/player" +import { Vector } from "@/vector" +import { GameGrid } from "@/gamegrid" +import { KeyboardState } from "@/keyboard" export class Player extends Unit { - constructor(gameGrid, location) { + constructor(gameGrid: GameGrid, location: Vector) { super(gameGrid, 'Adora', config.playerColor, location) this.gameGrid = gameGrid @@ -25,7 +28,7 @@ export class Player extends Unit { this.bindToGameGrid() this.updateDiscStatus() - this.disc.update() + this.disc?.update() this.resolveTeleportation() } @@ -56,7 +59,7 @@ export class Player extends Unit { } throwDisc() { - if (this.disc.status !== DiscStates.HELD) return + if (this.disc?.status !== DiscStates.HELD) return const direction = KeyboardState.discKeyPressed() if (! direction) return @@ -72,7 +75,7 @@ export class Player extends Unit { } returnDisc() { - if (this.disc.status !== DiscStates.DEADLY) return + if (this.disc?.status !== DiscStates.DEADLY) return const direction = KeyboardState.discKeyPressed() if (! direction) { diff --git a/js/units/unit.js b/src/units/unit.ts similarity index 78% rename from js/units/unit.js rename to src/units/unit.ts index e70e85c..bbd758c 100644 --- a/js/units/unit.js +++ b/src/units/unit.ts @@ -1,40 +1,65 @@ -import { Sprite } from "../sprite.js" -import { Vector } from "../vector.js" -import { DiscStates } from "../discs/disc.js" - -export const UnitFacings = Object.freeze({ - UP: Symbol("up"), - DOWN: Symbol("down"), - LEFT: Symbol("left"), - RIGHT: Symbol("right") -}) +import { config } from "@/config" + +import { Sprite } from "@/sprite" +import { Vector } from "@/vector" +import { DiscStates } from "@/discs/disc" +import { GameGrid } from "@/gamegrid" +import { Disc } from "@/discs/disc" +import { Door } from "@/door" + +export enum UnitFacings { + UP, + DOWN, + LEFT, + RIGHT +} export class Unit extends Sprite { - constructor(gameGrid, name, color, location) { + public isPlayer: boolean + protected canBlock: boolean + protected regenerates: boolean + protected baseSpeed: number + protected speedModifier: number + protected throwFrequencyModifier: number + protected recoveryRate: number + protected maxHits: number + protected baseAccuracy: number + protected accuracyModifier: number + public points: number + + private regenerateTimer: number | null + private hits: number + public isBlocking: boolean + public isTeleporting: boolean + private teleportDoor: Door | null + private facing: UnitFacings + public disc: Disc | null + private destination: Vector | null + + constructor(gameGrid: GameGrid, name: string, color: string, location: Vector) { super(gameGrid, name, config.unitSize, config.unitSize, color, location) this.isPlayer = false this.canBlock = false this.regenerates = false - this.baseSpeed = 1 this.speedModifier = 1 this.throwFrequencyModifier = 0 this.recoveryRate = 4 this.maxHits = 1 - this.hits = 0 this.baseAccuracy = config.warriorAccuracy this.accuracyModifier = 0 - this.regenerateTimer = null this.points = 100 - // General direction unit is facing - this.facing = null - + // Stateful + this.regenerateTimer = null + this.hits = 0 this.isBlocking = false - this.isTeleporting = false this.teleportDoor = null + this.facing = UnitFacings.DOWN + this.disc = null + this.destination = null } draw() { @@ -57,7 +82,7 @@ export class Unit extends Sprite { this.updateLocation() // Are we at our destination - if (this.touchLocation(this.destination)) { + if (this.destination && this.touchLocation(this.destination)) { // console.log(`Unit: ${this.name} got to destination, setting new destination`) this.setDestination() } @@ -79,7 +104,7 @@ export class Unit extends Sprite { this.resolveTeleportation() } - teleportTo(door) { + teleportTo(door: Door) { if (this.isTeleporting) return this.isTeleporting = true @@ -93,6 +118,9 @@ export class Unit extends Sprite { } updateDiscStatus() { + if (! this.disc) return + if (! this.gameGrid.player) return + if (this.disc.status === DiscStates.HELD && !this.disc.primed) { this.disc.primed = true @@ -106,6 +134,7 @@ export class Unit extends Sprite { throwDisc() { if (! this.gameGrid.player) return + if (! this.disc) return // Aim at player const aimFor = Vector.clone(this.gameGrid.player.location) @@ -121,6 +150,8 @@ export class Unit extends Sprite { } catchDisc() { + if (! this.disc) return + if (this.disc.status === DiscStates.RETURNING && this.collision(this.disc)) { //console.log('Unit: ' + this.name + ' caught disc') this.disc.status = DiscStates.HELD @@ -156,7 +187,7 @@ export class Unit extends Sprite { this.setFacing(this.findFacing(this.velocity)) } - findFacing(vector) { + findFacing(vector: Vector) { let facing = UnitFacings.DOWN const angle = vector.angle() @@ -172,14 +203,14 @@ export class Unit extends Sprite { return facing } - setFacing(facing) { + setFacing(facing: UnitFacings) { if (this.facing !== facing) { this.facing = facing // console.log(`${this.name} - Facing: ${this.facing.toString()}`) } } - hit(strength) { + hit(strength: number) { this.hits += strength || 1 console.log(`${this.name} hit. ${this.maxHits - this.hits} left`) @@ -209,7 +240,7 @@ export class Unit extends Sprite { } resolveTeleportation() { - if (this.isTeleporting) { + if (this.isTeleporting && this.teleportDoor) { if (! this.teleportDoor.isCollided(this)) { this.isTeleporting = false this.teleportDoor = null diff --git a/src/units/warrior.ts b/src/units/warrior.ts new file mode 100644 index 0000000..71d59ef --- /dev/null +++ b/src/units/warrior.ts @@ -0,0 +1,16 @@ +import { config } from "@/config" + +import { Unit } from "@/units/unit" +import { Vector } from "@/vector" +import { GameGrid } from "@/gamegrid" +import { BeginnerDisc } from "@/discs/beginner" + +export class Warrior extends Unit { + constructor(gameGrid: GameGrid, location: Vector) { + super(gameGrid, 'Warrior', config.warriorColor, location) + + this.baseSpeed = config.warriorSpeed + + this.disc = new BeginnerDisc(this) + } +} diff --git a/js/vector.js b/src/vector.ts similarity index 80% rename from js/vector.js rename to src/vector.ts index f17dce5..030fa48 100644 --- a/js/vector.js +++ b/src/vector.ts @@ -1,10 +1,15 @@ +type twod = [number, number] + export class Vector { - constructor(points) { + public points: twod + public dimensions: number + + constructor(points: twod) { this.points = points this.dimensions = this.points.length } - add(vector) { + add(vector: Vector) { if (this.dimensions !== vector.dimensions) { throw "Cannot add vectors of different dimensions" } @@ -14,7 +19,7 @@ export class Vector { } } - sub(vector) { + sub(vector: Vector) { if (this.dimensions !== vector.dimensions) { throw "Cannot subtract vectors of different dimensions" } @@ -24,13 +29,13 @@ export class Vector { } } - mul(n) { + mul(n: number) { for (let i = 0; i < this.dimensions; i++) { this.points[i] *= n } } - div(n) { + div(n: number) { if (n === 0) { throw "Cannot divide a vector by zero" } @@ -63,14 +68,14 @@ export class Vector { return (360 + Math.round(degs)) % 360 } - limit(max) { + limit(max: number) { if (this.magnitude() > max) { this.normalize() this.mul(max) } } - distance(vector) { + distance(vector: Vector) { return Math.sqrt( Math.abs( Math.pow(vector.points[0] - this.points[0], 2) + @@ -79,7 +84,7 @@ export class Vector { ) } - static random2D(width, height) { + static random2D(width: number, height: number) { const vector = new Vector([ Math.round(Math.random() * width), Math.round(Math.random() * height) @@ -88,21 +93,21 @@ export class Vector { return vector } - static addFactory(a, b) { + static addFactory(a: Vector, b: Vector) { const vector = Vector.clone(a) vector.add(b) return vector } - static subFactory(a, b) { + static subFactory(a: Vector, b: Vector) { const vector = Vector.clone(a) vector.sub(b) return vector } - static clone(oldVector) { - return new Vector(oldVector.points.slice(0)) + static clone(oldVector: Vector) { + return new Vector([...oldVector.points]) } } diff --git a/js/wave.js b/src/wave.ts similarity index 77% rename from js/wave.js rename to src/wave.ts index 07ae2ab..3389c88 100644 --- a/js/wave.js +++ b/src/wave.ts @@ -1,12 +1,16 @@ -import { Warrior } from "./units/warrior.js" -import { Bulldog } from "./units/bulldog.js" -import { Leader } from "./units/leader.js" -import { DoorSides } from "./door.js" +import { config } from "@/config" + +import { GameGrid } from "@/gamegrid" +import { Warrior, Bulldog, Leader } from "@/units/index" +import { DoorSides, DoorSideNames } from "@/door" export class WaveManager { - constructor(gameGrid) { + private gameGrid: GameGrid + private timer: NodeJS.Timeout | undefined + private count: number + + constructor(gameGrid: GameGrid) { this.gameGrid = gameGrid - this.timer this.count = 0 } @@ -29,11 +33,11 @@ export class WaveManager { end() { if (this.timer) { clearTimeout(this.timer) - this.timer = null + this.timer = undefined } } - trigger(interval) { + trigger(interval: number) { this.end() this.timer = setTimeout(this.next.bind(this), interval * 1000) @@ -61,9 +65,7 @@ export class WaveManager { randomSide() { const sideNames = Object.keys(DoorSides) - return DoorSides[ - sideNames[Math.floor(Math.random() * sideNames.length)] - ] + return DoorSides[sideNames[Math.floor(Math.random() * sideNames.length)] as DoorSideNames ] } spawnUnits(count = 1) { diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..fef9772 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES6", + "isolatedModules": true, + "sourceMap": true, + "outDir": "dist", + "strict": true, + "allowJs": false, + "noImplicitAny": true, + "noImplicitReturns": true, + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/vite.config.mts b/vite.config.mts new file mode 100644 index 0000000..07e06d0 --- /dev/null +++ b/vite.config.mts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import tsconfigPaths from 'vite-tsconfig-paths' + +export default defineConfig({ + base: "/GridWarriors/", + plugins: [tsconfigPaths()], +});