From 7ed28a567bff8e5f3a0693d9332f205e0c850054 Mon Sep 17 00:00:00 2001 From: Justin La Sotten Date: Wed, 3 Apr 2024 17:36:34 -0400 Subject: [PATCH] Code Overhaul v1.0.0 - Ported to Typescript - Added Vite build/bundler - Added Github workflow to publish to Github Pages Porting to Typescript revealed several issues with the code structure, fixes to those issues are included in this port. Most types were build around the existing shape of an object, however, there were some objects which were rebuild do to some Javascript abuse. I ported to Typescript because: * I like Typescript more than JSDoc * I like explicit in-code types rather than JSDoc comments * I like the Typescript LSP and the resulting IDE integration * It's my project and I can do what I want :) --- .devcontainer/devcontainer.json | 3 +- .github/workflows/gh-pages.yml | 51 + .gitignore | 2 + index.html | 7 +- js/keyboard.js | 55 -- js/main.js | 7 - js/overlay.js | 22 - js/scoreboard.js | 49 - js/units/warrior.js | 12 - package-lock.json | 872 ++++++++++++++++++ package.json | 18 + {css => public/css}/gridwarriors.css | 0 favicon.ico => public/images/favicon.ico | Bin js/config.js => src/config.ts | 2 +- js/discs/beginner.js => src/discs/beginner.ts | 6 +- js/discs/disc.js => src/discs/disc.ts | 46 +- js/discs/homing.js => src/discs/homing.ts | 15 +- .../discs/intermediate.ts | 7 +- js/discs/player.js => src/discs/player.ts | 7 +- js/door.js => src/door.ts | 82 +- js/gamegrid.js => src/gamegrid.ts | 43 +- js/gridwarriors.js => src/gridwarriors.ts | 61 +- src/keyboard.ts | 66 ++ src/main.ts | 14 + src/overlay.ts | 27 + src/scoreboard.ts | 58 ++ js/sprite.js => src/sprite.ts | 34 +- js/units/bulldog.js => src/units/bulldog.ts | 10 +- src/units/index.ts | 14 + js/units/leader.js => src/units/leader.ts | 12 +- js/units/player.js => src/units/player.ts | 21 +- js/units/unit.js => src/units/unit.ts | 79 +- src/units/warrior.ts | 16 + js/vector.js => src/vector.ts | 29 +- js/wave.js => src/wave.ts | 24 +- tsconfig.json | 15 + vite.config.mts | 7 + 37 files changed, 1485 insertions(+), 308 deletions(-) create mode 100644 .github/workflows/gh-pages.yml delete mode 100644 js/keyboard.js delete mode 100644 js/main.js delete mode 100644 js/overlay.js delete mode 100644 js/scoreboard.js delete mode 100644 js/units/warrior.js create mode 100644 package-lock.json create mode 100644 package.json rename {css => public/css}/gridwarriors.css (100%) rename favicon.ico => public/images/favicon.ico (100%) rename js/config.js => src/config.ts (98%) rename js/discs/beginner.js => src/discs/beginner.ts (53%) rename js/discs/disc.js => src/discs/disc.ts (84%) rename js/discs/homing.js => src/discs/homing.ts (84%) rename js/discs/intermediate.js => src/discs/intermediate.ts (59%) rename js/discs/player.js => src/discs/player.ts (52%) rename js/door.js => src/door.ts (76%) rename js/gamegrid.js => src/gamegrid.ts (72%) rename js/gridwarriors.js => src/gridwarriors.ts (64%) create mode 100644 src/keyboard.ts create mode 100644 src/main.ts create mode 100644 src/overlay.ts create mode 100644 src/scoreboard.ts rename js/sprite.js => src/sprite.ts (76%) rename js/units/bulldog.js => src/units/bulldog.ts (57%) create mode 100644 src/units/index.ts rename js/units/leader.js => src/units/leader.ts (59%) rename js/units/player.js => src/units/player.ts (82%) rename js/units/unit.js => src/units/unit.ts (78%) create mode 100644 src/units/warrior.ts rename js/vector.js => src/vector.ts (80%) rename js/wave.js => src/wave.ts (77%) create mode 100644 tsconfig.json create mode 100644 vite.config.mts 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()], +});