Compare commits
3 Commits
bad3d62449
...
ee38d7f53c
| Author | SHA1 | Date |
|---|---|---|
|
|
ee38d7f53c | |
|
|
01c601424f | |
|
|
0977914f20 |
|
|
@ -94,10 +94,6 @@ export async function buildHTML(game: string, { production = false, portable = f
|
||||||
const minifyResult = UglifyJS.minify(script, {
|
const minifyResult = UglifyJS.minify(script, {
|
||||||
module: true,
|
module: true,
|
||||||
toplevel: true,
|
toplevel: true,
|
||||||
mangle: {
|
|
||||||
toplevel: true,
|
|
||||||
properties: true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
if (minifyResult.error) {
|
if (minifyResult.error) {
|
||||||
console.warn(`Minify error: ${minifyResult.error}`);
|
console.warn(`Minify error: ${minifyResult.error}`);
|
||||||
|
|
@ -110,11 +106,11 @@ export async function buildHTML(game: string, { production = false, portable = f
|
||||||
}
|
}
|
||||||
|
|
||||||
const resultHTML = html
|
const resultHTML = html
|
||||||
.replace('<!--$SCRIPT$-->', `${scriptPrefix}<script type="module">${script}</script>`)
|
.replace('<!--$SCRIPT$-->', () => `${scriptPrefix}<script type="module">${script}</script>`) // to avoid $& being replaced
|
||||||
.replace('<!--$TITLE$-->', title)
|
.replace('<!--$TITLE$-->', () => title)
|
||||||
.replace('<!--$ICON$-->', icon)
|
.replace('<!--$ICON$-->', () => icon)
|
||||||
.replace('<!--$MANIFEST$-->', manifest)
|
.replace('<!--$MANIFEST$-->', () => manifest)
|
||||||
.replace('/*$STYLE$*/', style);
|
.replace('/*$STYLE$*/', () => style);
|
||||||
|
|
||||||
return minify(resultHTML, {
|
return minify(resultHTML, {
|
||||||
collapseWhitespace: production,
|
collapseWhitespace: production,
|
||||||
|
|
|
||||||
8
bun.lock
8
bun.lock
|
|
@ -111,7 +111,7 @@
|
||||||
|
|
||||||
"@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="],
|
"@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.2.14", "", { "dependencies": { "bun-types": "1.2.14" } }, "sha512-VsFZKs8oKHzI7zwvECiAJ5oSorWndIWEVhfbYqZd4HI/45kzW7PN2Rr5biAzvGvRuNmYLSANY+H59ubHq8xw7Q=="],
|
"@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="],
|
||||||
|
|
||||||
"@types/clean-css": ["@types/clean-css@4.2.11", "", { "dependencies": { "@types/node": "*", "source-map": "^0.6.0" } }, "sha512-Y8n81lQVTAfP2TOdtJJEsCoYl1AnOkqDqMvXb9/7pfgZZ7r8YrEyurrAvAoAjHOGXKRybay+5CsExqIH6liccw=="],
|
"@types/clean-css": ["@types/clean-css@4.2.11", "", { "dependencies": { "@types/node": "*", "source-map": "^0.6.0" } }, "sha512-Y8n81lQVTAfP2TOdtJJEsCoYl1AnOkqDqMvXb9/7pfgZZ7r8YrEyurrAvAoAjHOGXKRybay+5CsExqIH6liccw=="],
|
||||||
|
|
||||||
|
|
@ -123,6 +123,8 @@
|
||||||
|
|
||||||
"@types/node": ["@types/node@20.14.10", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ=="],
|
"@types/node": ["@types/node@20.14.10", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ=="],
|
||||||
|
|
||||||
|
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
|
||||||
|
|
||||||
"@types/relateurl": ["@types/relateurl@0.2.33", "", {}, "sha512-bTQCKsVbIdzLqZhLkF5fcJQreE4y1ro4DIyVrlDNSCJRRwHhB8Z+4zXXa8jN6eDvc2HbRsEYgbvrnGvi54EpSw=="],
|
"@types/relateurl": ["@types/relateurl@0.2.33", "", {}, "sha512-bTQCKsVbIdzLqZhLkF5fcJQreE4y1ro4DIyVrlDNSCJRRwHhB8Z+4zXXa8jN6eDvc2HbRsEYgbvrnGvi54EpSw=="],
|
||||||
|
|
||||||
"@types/through": ["@types/through@0.0.33", "", { "dependencies": { "@types/node": "*" } }, "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ=="],
|
"@types/through": ["@types/through@0.0.33", "", { "dependencies": { "@types/node": "*" } }, "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ=="],
|
||||||
|
|
@ -145,7 +147,7 @@
|
||||||
|
|
||||||
"browser-detect": ["browser-detect@0.2.28", "", { "dependencies": { "core-js": "^2.5.7" } }, "sha512-KeWGHqYQmHDkCFG2dIiX/2wFUgqevbw/rd6wNi9N6rZbaSJFtG5kel0HtprRwCGp8sqpQP79LzDJXf/WCx4WAw=="],
|
"browser-detect": ["browser-detect@0.2.28", "", { "dependencies": { "core-js": "^2.5.7" } }, "sha512-KeWGHqYQmHDkCFG2dIiX/2wFUgqevbw/rd6wNi9N6rZbaSJFtG5kel0HtprRwCGp8sqpQP79LzDJXf/WCx4WAw=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.2.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-Kuh4Ub28ucMRWeiUUWMHsT9Wcbr4H3kLIO72RZZElSDxSu7vpetRvxIUDUaW6QtaIeixIpm7OXtNnZPf82EzwA=="],
|
"bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
|
||||||
|
|
||||||
"camel-case": ["camel-case@3.0.0", "", { "dependencies": { "no-case": "^2.2.0", "upper-case": "^1.1.1" } }, "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w=="],
|
"camel-case": ["camel-case@3.0.0", "", { "dependencies": { "no-case": "^2.2.0", "upper-case": "^1.1.1" } }, "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w=="],
|
||||||
|
|
||||||
|
|
@ -173,6 +175,8 @@
|
||||||
|
|
||||||
"cross-spawn": ["cross-spawn@7.0.5", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug=="],
|
"cross-spawn": ["cross-spawn@7.0.5", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug=="],
|
||||||
|
|
||||||
|
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||||
|
|
||||||
"delay": ["delay@6.0.0", "", {}, "sha512-2NJozoOHQ4NuZuVIr5CWd0iiLVIRSDepakaovIN+9eIDHEhdCAEvSy2cuf1DCrPPQLvHmbqTHODlhHg8UCy4zw=="],
|
"delay": ["delay@6.0.0", "", {}, "sha512-2NJozoOHQ4NuZuVIr5CWd0iiLVIRSDepakaovIN+9eIDHEhdCAEvSy2cuf1DCrPPQLvHmbqTHODlhHg8UCy4zw=="],
|
||||||
|
|
||||||
"detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="],
|
"detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="],
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import Input from "./input";
|
||||||
|
import { nextFrame } from "./utils";
|
||||||
|
|
||||||
|
type Setup<T extends Record<string, unknown> | void> = () => Promise<T> | T;
|
||||||
|
type Frame<T extends Record<string, unknown> | void> = (dt: number, state: T) => Promise<void> | void;
|
||||||
|
type GameMain = () => void;
|
||||||
|
|
||||||
|
export function gameLoop<T extends Record<string, unknown> | void>(frame: Frame<T>): GameMain;
|
||||||
|
export function gameLoop<T extends Record<string, unknown> | void>(setup: Setup<T>, frame: Frame<T>): GameMain;
|
||||||
|
export function gameLoop<T extends Record<string, unknown> | void>(setupOrFrame: Setup<T> | Frame<T>, frame?: Frame<T>): GameMain {
|
||||||
|
return async () => {
|
||||||
|
let state: T;
|
||||||
|
if (frame) {
|
||||||
|
state = await (setupOrFrame as Setup<T>)();
|
||||||
|
} else {
|
||||||
|
frame = setupOrFrame as Frame<T>;
|
||||||
|
state = {} as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
let prevFrame = performance.now();
|
||||||
|
while (true) {
|
||||||
|
await nextFrame();
|
||||||
|
Input.updateKeys();
|
||||||
|
|
||||||
|
const now = performance.now();
|
||||||
|
const dt = (now - prevFrame) / 1000;
|
||||||
|
|
||||||
|
await frame(dt, state);
|
||||||
|
|
||||||
|
prevFrame = performance.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -6,45 +6,109 @@ interface IKeyState {
|
||||||
pressed?: boolean;
|
pressed?: boolean;
|
||||||
held?: boolean;
|
held?: boolean;
|
||||||
}
|
}
|
||||||
type KeyCode = 'ArrowUp' | 'ArrowDown' | 'ArrowLeft' | 'ArrowRight' | 'Space';
|
namespace Input {
|
||||||
|
export enum KeyCode {
|
||||||
|
UP = 'ArrowUp',
|
||||||
|
DOWN = 'ArrowDown',
|
||||||
|
LEFT = 'ArrowLeft',
|
||||||
|
RIGHT = 'ArrowRight',
|
||||||
|
SPACE = 'Space',
|
||||||
|
SHIFT = 'Shift',
|
||||||
|
SHIFT_LEFT = 'ShiftLeft',
|
||||||
|
SHIFT_RIGHT = 'ShiftRight',
|
||||||
|
};
|
||||||
|
|
||||||
const KEYS: Partial<Record<KeyCode, IKeyState>> = {};
|
export enum GamepadAxis {
|
||||||
|
LX = 0,
|
||||||
document.body.addEventListener('keydown', (e) => {
|
LY = 1,
|
||||||
const keyId = e.code as KeyCode;
|
RX = 2,
|
||||||
console.debug(`[Input] Pressed ${keyId}`);
|
RY = 3,
|
||||||
if (KEYS[keyId]) {
|
|
||||||
KEYS[keyId].state = true;
|
|
||||||
} else {
|
|
||||||
KEYS[keyId] = { state: true };
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
document.body.addEventListener('keyup', (e) => {
|
export enum GamepadButton {
|
||||||
const keyId = e.code as KeyCode;
|
A = 0,
|
||||||
console.debug(`[Input] Released ${keyId}`);
|
B = 1,
|
||||||
if (KEYS[keyId]) {
|
X = 2,
|
||||||
KEYS[keyId].state = false;
|
Y = 3,
|
||||||
|
LB = 4,
|
||||||
|
RB = 5,
|
||||||
|
LT = 6,
|
||||||
|
RT = 7,
|
||||||
|
SELECT = 8,
|
||||||
|
START = 9,
|
||||||
|
L3 = 10,
|
||||||
|
R3 = 11,
|
||||||
|
D_UP = 12,
|
||||||
|
D_DOWN = 13,
|
||||||
|
D_LEFT = 14,
|
||||||
|
D_RIGHT = 15,
|
||||||
|
HOME = 16,
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
export const isPressed = (key: KeyCode): boolean => KEYS[key]?.pressed ?? false;
|
const DEAD_ZONE = 0.05;
|
||||||
export const isReleased = (key: KeyCode): boolean => KEYS[key]?.released ?? false;
|
const KEYS: Partial<Record<KeyCode, IKeyState>> = {};
|
||||||
export const isHeld = (key: KeyCode): boolean => KEYS[key]?.held ?? false;
|
|
||||||
|
|
||||||
export function updateKeys() {
|
const onStateChange = (keyId: KeyCode, state: boolean) => {
|
||||||
for (const key of Object.values(KEYS)) {
|
console.debug(`[Input] Pressed ${keyId}`);
|
||||||
key.released = false;
|
if (KEYS[keyId]) {
|
||||||
key.pressed = false;
|
KEYS[keyId].state = state;
|
||||||
|
|
||||||
if (key.state) {
|
|
||||||
key.pressed = !key.held;
|
|
||||||
key.held = true;
|
|
||||||
} else {
|
} else {
|
||||||
key.released = true;
|
KEYS[keyId] = { state };
|
||||||
key.held = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
key.prevState = key.state;
|
if (keyId === KeyCode.SHIFT_LEFT || keyId === KeyCode.SHIFT_RIGHT) {
|
||||||
|
const shiftState = Boolean(KEYS[KeyCode.SHIFT_LEFT]?.state || KEYS[KeyCode.SHIFT_RIGHT]?.state);
|
||||||
|
onStateChange(KeyCode.SHIFT, shiftState);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.body.addEventListener('keydown', (e) => onStateChange(e.code as KeyCode, true));
|
||||||
|
document.body.addEventListener('keyup', (e) => onStateChange(e.code as KeyCode, false));
|
||||||
|
|
||||||
|
export const isPressed = (key: KeyCode): boolean => KEYS[key]?.pressed ?? false;
|
||||||
|
export const isReleased = (key: KeyCode): boolean => KEYS[key]?.released ?? false;
|
||||||
|
export const isHeld = (key: KeyCode): boolean => KEYS[key]?.held ?? false;
|
||||||
|
|
||||||
|
export const getGamepad = () => navigator.getGamepads().find(g => g != null);
|
||||||
|
|
||||||
|
export const getGamepadAxis = (i: GamepadAxis) => {
|
||||||
|
const value = getGamepad()?.axes[i] ?? 0;
|
||||||
|
return Math.abs(value) < DEAD_ZONE ? 0 : value;
|
||||||
}
|
}
|
||||||
}
|
export const getGamepadButton = (btn: GamepadButton) => getGamepad()?.buttons[btn]?.value ?? 0;
|
||||||
|
|
||||||
|
export const getLeft = () => isHeld(KeyCode.LEFT) ? 1 : Math.max(0, -getGamepadAxis(GamepadAxis.LX));
|
||||||
|
export const getRight = () => isHeld(KeyCode.RIGHT) ? 1 : Math.max(0, getGamepadAxis(GamepadAxis.LX));
|
||||||
|
|
||||||
|
export const getUp = () => isHeld(KeyCode.UP) ? 1 : Math.max(0, -getGamepadAxis(GamepadAxis.LY));
|
||||||
|
export const getDown = () => isHeld(KeyCode.DOWN) ? 1 : Math.max(0, getGamepadAxis(GamepadAxis.LY));
|
||||||
|
|
||||||
|
export const getHorizontal = () => (
|
||||||
|
(Number(isHeld(KeyCode.LEFT)) - Number(isHeld(KeyCode.RIGHT)))
|
||||||
|
+ getGamepadAxis(GamepadAxis.LX)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getVertical = () => (
|
||||||
|
(Number(isHeld(KeyCode.UP)) - Number(isHeld(KeyCode.DOWN)))
|
||||||
|
+ getGamepadAxis(GamepadAxis.LY)
|
||||||
|
);
|
||||||
|
|
||||||
|
export function updateKeys() {
|
||||||
|
for (const key of Object.values(KEYS)) {
|
||||||
|
key.released = false;
|
||||||
|
key.pressed = false;
|
||||||
|
|
||||||
|
if (key.state) {
|
||||||
|
key.pressed = !key.held;
|
||||||
|
key.held = true;
|
||||||
|
} else {
|
||||||
|
key.released = true;
|
||||||
|
key.held = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
key.prevState = key.state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Input;
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { BrickDisplay } from "@common/display/brick";
|
import { BrickDisplay } from "@common/display/brick";
|
||||||
import { isPressed, updateKeys } from "@common/input";
|
import Input from "@common/input";
|
||||||
|
|
||||||
import spritesheetImage from './assets/spritesheet.png';
|
import spritesheetImage from './assets/spritesheet.png';
|
||||||
import { ITEMS, Items, MONSTERS, MONSTERS_ORDER, loadData, type Item, type Monster } from "./data";
|
import { ITEMS, Items, MONSTERS, MONSTERS_ORDER, loadData, type Item, type Monster } from "./data";
|
||||||
import { randBool, weightedChoice } from "@common/utils";
|
import { randBool, weightedChoice } from "@common/utils";
|
||||||
|
import { gameLoop } from "@common/game";
|
||||||
|
|
||||||
let display: BrickDisplay;
|
let display: BrickDisplay;
|
||||||
const spritesheet = BrickDisplay.convertImage(spritesheetImage);
|
const spritesheet = BrickDisplay.convertImage(spritesheetImage);
|
||||||
|
|
@ -47,9 +48,9 @@ let secondLootItem: Items | null = null;
|
||||||
|
|
||||||
let frames = 0;
|
let frames = 0;
|
||||||
let prevFrameTime: number = 0;
|
let prevFrameTime: number = 0;
|
||||||
async function loop(time: number) {
|
|
||||||
|
async function frame(time: number) {
|
||||||
frames++;
|
frames++;
|
||||||
const dt = time - prevFrameTime;
|
|
||||||
prevFrameTime = time;
|
prevFrameTime = time;
|
||||||
|
|
||||||
if (frames % 8 == 0) {
|
if (frames % 8 == 0) {
|
||||||
|
|
@ -105,17 +106,17 @@ async function loop(time: number) {
|
||||||
} else if (y === secondLootY) {
|
} else if (y === secondLootY) {
|
||||||
lootToConfirm = secondLootItem;
|
lootToConfirm = secondLootItem;
|
||||||
playerTurn = false;
|
playerTurn = false;
|
||||||
} else if (isPressed('ArrowLeft')) {
|
} else if (Input.isPressed(Input.KeyCode.LEFT)) {
|
||||||
selectedSlot = (selectedSlot + inventory.length - 1) % inventory.length;
|
selectedSlot = (selectedSlot + inventory.length - 1) % inventory.length;
|
||||||
} else if (isPressed('ArrowRight')) {
|
} else if (Input.isPressed(Input.KeyCode.RIGHT)) {
|
||||||
selectedSlot = (selectedSlot + 1) % inventory.length;
|
selectedSlot = (selectedSlot + 1) % inventory.length;
|
||||||
} else if (isPressed('ArrowUp') && (!monsterAlive || y < monsterY - 5)) {
|
} else if (Input.isPressed(Input.KeyCode.UP) && (!monsterAlive || y < monsterY - 5)) {
|
||||||
targetY = y + 4;
|
targetY = y + 4;
|
||||||
playerTurn = false;
|
playerTurn = false;
|
||||||
} else if (isPressed('ArrowDown')) {
|
} else if (Input.isPressed(Input.KeyCode.DOWN)) {
|
||||||
targetY = y - 4;
|
targetY = y - 4;
|
||||||
playerTurn = false;
|
playerTurn = false;
|
||||||
} else if (isPressed('Space') && (monsterAlive && (y >= monsterY - 5 || item.ranged || item.heal))) {
|
} else if (Input.isPressed(Input.KeyCode.SPACE) && (monsterAlive && (y >= monsterY - 5 || item.ranged || item.heal))) {
|
||||||
if (item.consumable) {
|
if (item.consumable) {
|
||||||
inventory.splice(selectedSlot, 1);
|
inventory.splice(selectedSlot, 1);
|
||||||
selectedSlot = (selectedSlot + inventory.length - 1) % inventory.length;
|
selectedSlot = (selectedSlot + inventory.length - 1) % inventory.length;
|
||||||
|
|
@ -138,7 +139,7 @@ async function loop(time: number) {
|
||||||
} else if (lootToConfirm) {
|
} else if (lootToConfirm) {
|
||||||
console.log('Loot confirm');
|
console.log('Loot confirm');
|
||||||
|
|
||||||
if (isPressed('Space')) {
|
if (Input.isPressed(Input.KeyCode.SPACE)) {
|
||||||
const i = ITEMS[lootToConfirm];
|
const i = ITEMS[lootToConfirm];
|
||||||
if (i.instantUse && i.heal) {
|
if (i.instantUse && i.heal) {
|
||||||
playerHealth += i.heal;
|
playerHealth += i.heal;
|
||||||
|
|
@ -150,10 +151,10 @@ async function loop(time: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
lootConfirmed = true;
|
lootConfirmed = true;
|
||||||
} else if (isPressed('ArrowUp')) {
|
} else if (Input.isPressed(Input.KeyCode.UP)) {
|
||||||
targetY = y + 4;
|
targetY = y + 4;
|
||||||
playerTurn = true;
|
playerTurn = true;
|
||||||
} else if (isPressed('ArrowDown')) {
|
} else if (Input.isPressed(Input.KeyCode.DOWN)) {
|
||||||
targetY = y - 4;
|
targetY = y - 4;
|
||||||
playerTurn = true;
|
playerTurn = true;
|
||||||
}
|
}
|
||||||
|
|
@ -279,9 +280,6 @@ async function loop(time: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
display.update();
|
display.update();
|
||||||
|
|
||||||
updateKeys();
|
|
||||||
requestAnimationFrame(loop);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function spawnNextMonster() {
|
function spawnNextMonster() {
|
||||||
|
|
@ -321,11 +319,12 @@ function damagePlayer(monster: Monster) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function main() {
|
const setup = () => {
|
||||||
display = new BrickDisplay();
|
display = new BrickDisplay();
|
||||||
display.init();
|
display.init();
|
||||||
|
|
||||||
loadData(spritesheet);
|
loadData(spritesheet);
|
||||||
spawnNextMonster();
|
spawnNextMonster();
|
||||||
requestAnimationFrame(loop);
|
}
|
||||||
}
|
|
||||||
|
export default gameLoop(setup, frame);
|
||||||
|
|
@ -92,7 +92,7 @@ export const App = () => {
|
||||||
if (taken < request) {
|
if (taken < request) {
|
||||||
score += diff * 10;
|
score += diff * 10;
|
||||||
} else if (taken > request) {
|
} else if (taken > request) {
|
||||||
score += diff;
|
score += taken;
|
||||||
} else if (taken === 0 && request === 0) {
|
} else if (taken === 0 && request === 0) {
|
||||||
score += 5;
|
score += 5;
|
||||||
} else if (taken === request) {
|
} else if (taken === request) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,34 @@
|
||||||
import awoo from "./awoo.cpp";
|
import { gameLoop } from "@common/game";
|
||||||
|
import Input from "@common/input";
|
||||||
|
|
||||||
export default function main() {
|
const setup = () => {
|
||||||
console.log(awoo, awoo.play());
|
let x = window.innerWidth / 2 - 16;
|
||||||
}
|
let y = window.innerHeight / 2 - 16;
|
||||||
|
const ball = document.createElement('div');
|
||||||
|
|
||||||
|
ball.style.display = 'block';
|
||||||
|
ball.style.width = '32px';
|
||||||
|
ball.style.height = '32px';
|
||||||
|
ball.style.borderRadius = '50%';
|
||||||
|
ball.style.backgroundColor = 'red';
|
||||||
|
ball.style.position = 'absolute';
|
||||||
|
|
||||||
|
document.body.append(ball);
|
||||||
|
|
||||||
|
const speed = Math.min(window.innerHeight, window.innerWidth);
|
||||||
|
|
||||||
|
return { x, y, speed, ball };
|
||||||
|
}
|
||||||
|
|
||||||
|
const frame = (dt: number, state: ReturnType<typeof setup>) => {
|
||||||
|
const dx = Input.getHorizontal();
|
||||||
|
const dy = Input.getVertical();
|
||||||
|
|
||||||
|
state.x += state.speed * dx * dt;
|
||||||
|
state.y += state.speed * dy * dt;
|
||||||
|
|
||||||
|
state.ball.style.left = `${state.x}px`;
|
||||||
|
state.ball.style.top = `${state.y}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default gameLoop(setup, frame);
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Color, Direction, getOppositeDirection } from './const';
|
import { Color, Direction, getOppositeDirection } from './const';
|
||||||
import { drawTextInBox, tick } from './display';
|
import { drawTextInBox, tick } from './display';
|
||||||
import { isHeld, isPressed, updateKeys } from '@common/input';
|
import Input from '@common/input';
|
||||||
import { createItems, getItemsCount } from './item';
|
import { createItems, getItemsCount } from './item';
|
||||||
import { GameMap } from './map';
|
import { GameMap } from './map';
|
||||||
import { Player } from './player';
|
import { Player } from './player';
|
||||||
|
|
@ -13,30 +13,30 @@ const player = new Player(currentRoom.x + currentRoom.width / 2, currentRoom.y +
|
||||||
|
|
||||||
let lastMove = Date.now();
|
let lastMove = Date.now();
|
||||||
function handleInput() {
|
function handleInput() {
|
||||||
const isSpacePressed = isPressed(' ');
|
const isSpacePressed = Input.isPressed(Input.KeyCode.SPACE);
|
||||||
|
|
||||||
if (Date.now() - lastMove < 75 && !isHeld('shift') && !isSpacePressed) return;
|
if (Date.now() - lastMove < 75 && !Input.isHeld(Input.KeyCode.SHIFT) && !isSpacePressed) return;
|
||||||
lastMove = Date.now();
|
lastMove = Date.now();
|
||||||
let newX = player.x;
|
let newX = player.x;
|
||||||
let newY = player.y;
|
let newY = player.y;
|
||||||
let moved = isSpacePressed;
|
let moved = isSpacePressed;
|
||||||
|
|
||||||
if (isHeld('arrowup')) {
|
if (Input.isHeld(Input.KeyCode.UP)) {
|
||||||
newY--;
|
newY--;
|
||||||
moved = true;
|
moved = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isHeld('arrowdown')) {
|
if (Input.isHeld(Input.KeyCode.DOWN)) {
|
||||||
newY++;
|
newY++;
|
||||||
moved = true;
|
moved = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isHeld('arrowleft')) {
|
if (Input.isHeld(Input.KeyCode.LEFT)) {
|
||||||
newX--;
|
newX--;
|
||||||
moved = true;
|
moved = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isHeld('arrowright')) {
|
if (Input.isHeld(Input.KeyCode.RIGHT)) {
|
||||||
newX++;
|
newX++;
|
||||||
moved = true;
|
moved = true;
|
||||||
}
|
}
|
||||||
|
|
@ -131,7 +131,7 @@ export async function main() {
|
||||||
drawTextInBox(8, 11, 'Use arrow keys to move\nSHIFT to run\nSPACE to activate\nAWOO!', { fg: Color.CYAN });
|
drawTextInBox(8, 11, 'Use arrow keys to move\nSHIFT to run\nSPACE to activate\nAWOO!', { fg: Color.CYAN });
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
updateKeys();
|
Input.updateKeys();
|
||||||
handleInput();
|
handleInput();
|
||||||
handleLogic();
|
handleLogic();
|
||||||
draw();
|
draw();
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -5,6 +5,7 @@ import police from './assets/characters/police.jpg';
|
||||||
import runner from './assets/characters/runner.jpg';
|
import runner from './assets/characters/runner.jpg';
|
||||||
|
|
||||||
import boss from './assets/enemies/boss.jpg';
|
import boss from './assets/enemies/boss.jpg';
|
||||||
|
import crow from './assets/enemies/crow.jpg';
|
||||||
import dog from './assets/enemies/dog.jpg';
|
import dog from './assets/enemies/dog.jpg';
|
||||||
import spider from './assets/enemies/spider.jpg';
|
import spider from './assets/enemies/spider.jpg';
|
||||||
import zombie from './assets/enemies/zombie.jpg';
|
import zombie from './assets/enemies/zombie.jpg';
|
||||||
|
|
@ -21,6 +22,7 @@ import grenade from './assets/weapons/grenade.jpg';
|
||||||
import knife from './assets/weapons/knife.jpg';
|
import knife from './assets/weapons/knife.jpg';
|
||||||
import pistol from './assets/weapons/pistol.jpg';
|
import pistol from './assets/weapons/pistol.jpg';
|
||||||
import rocketLauncher from './assets/weapons/rocket_launcher.jpg';
|
import rocketLauncher from './assets/weapons/rocket_launcher.jpg';
|
||||||
|
import slingshot from './assets/weapons/slingshot.jpg';
|
||||||
import shotgun from './assets/weapons/shotgun.jpg';
|
import shotgun from './assets/weapons/shotgun.jpg';
|
||||||
|
|
||||||
export const ItemType = {
|
export const ItemType = {
|
||||||
|
|
@ -31,6 +33,7 @@ export const ItemType = {
|
||||||
CHAR_RUNNER: runner,
|
CHAR_RUNNER: runner,
|
||||||
|
|
||||||
ENEMY_BOSS: boss,
|
ENEMY_BOSS: boss,
|
||||||
|
ENEMY_CROW: crow,
|
||||||
ENEMY_DOG: dog,
|
ENEMY_DOG: dog,
|
||||||
ENEMY_SPIDER: spider,
|
ENEMY_SPIDER: spider,
|
||||||
ENEMY_ZOMBIE: zombie,
|
ENEMY_ZOMBIE: zombie,
|
||||||
|
|
@ -47,6 +50,7 @@ export const ItemType = {
|
||||||
WEAPON_KNIFE: knife,
|
WEAPON_KNIFE: knife,
|
||||||
WEAPON_PISTOL: pistol,
|
WEAPON_PISTOL: pistol,
|
||||||
WEAPON_ROCKET_LAUNCHER: rocketLauncher,
|
WEAPON_ROCKET_LAUNCHER: rocketLauncher,
|
||||||
|
WEAPON_SLINGSHOT: slingshot,
|
||||||
WEAPON_SHOTGUN: shotgun,
|
WEAPON_SHOTGUN: shotgun,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
@ -65,12 +69,17 @@ export default class Item {
|
||||||
get isEnemy() {
|
get isEnemy() {
|
||||||
return [
|
return [
|
||||||
ItemType.ENEMY_BOSS,
|
ItemType.ENEMY_BOSS,
|
||||||
|
ItemType.ENEMY_CROW,
|
||||||
ItemType.ENEMY_DOG,
|
ItemType.ENEMY_DOG,
|
||||||
ItemType.ENEMY_SPIDER,
|
ItemType.ENEMY_SPIDER,
|
||||||
ItemType.ENEMY_ZOMBIE,
|
ItemType.ENEMY_ZOMBIE,
|
||||||
].includes(this.type);
|
].includes(this.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isFlyingEnemy() {
|
||||||
|
return this.type === ItemType.ENEMY_CROW;
|
||||||
|
}
|
||||||
|
|
||||||
get isItem() {
|
get isItem() {
|
||||||
return [
|
return [
|
||||||
ItemType.ITEM_FUEL,
|
ItemType.ITEM_FUEL,
|
||||||
|
|
@ -81,7 +90,7 @@ export default class Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
get isWeapon() {
|
get isWeapon() {
|
||||||
return this.isMeleeWeapon || this.isShootingWeapon || [
|
return this.isMeleeWeapon || this.isRangedWeapon || [
|
||||||
ItemType.WEAPON_GRENADE,
|
ItemType.WEAPON_GRENADE,
|
||||||
ItemType.WEAPON_ROCKET_LAUNCHER,
|
ItemType.WEAPON_ROCKET_LAUNCHER,
|
||||||
].includes(this.type);
|
].includes(this.type);
|
||||||
|
|
@ -91,18 +100,27 @@ export default class Item {
|
||||||
return [
|
return [
|
||||||
ItemType.WEAPON_AXE,
|
ItemType.WEAPON_AXE,
|
||||||
ItemType.WEAPON_KNIFE,
|
ItemType.WEAPON_KNIFE,
|
||||||
|
ItemType.ITEM_PLANKS,
|
||||||
].includes(this.type);
|
].includes(this.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isShootingWeapon() {
|
get isRangedWeapon() {
|
||||||
return [
|
return [
|
||||||
ItemType.WEAPON_ASSAULT_RIFLE,
|
ItemType.WEAPON_ASSAULT_RIFLE,
|
||||||
ItemType.WEAPON_CROSSBOW,
|
ItemType.WEAPON_CROSSBOW,
|
||||||
ItemType.WEAPON_PISTOL,
|
ItemType.WEAPON_PISTOL,
|
||||||
|
ItemType.WEAPON_SLINGSHOT,
|
||||||
ItemType.WEAPON_SHOTGUN,
|
ItemType.WEAPON_SHOTGUN,
|
||||||
].includes(this.type);
|
].includes(this.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isSpendingWeapon() {
|
||||||
|
return (
|
||||||
|
this.type === ItemType.ITEM_PLANKS ||
|
||||||
|
(this.type !== ItemType.WEAPON_SLINGSHOT && this.isRangedWeapon)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
get isBoss() {
|
get isBoss() {
|
||||||
return this.type === ItemType.ENEMY_BOSS;
|
return this.type === ItemType.ENEMY_BOSS;
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +131,7 @@ export default class Item {
|
||||||
ItemType.WEAPON_GRENADE,
|
ItemType.WEAPON_GRENADE,
|
||||||
].includes(this.type);
|
].includes(this.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return Object.entries(ItemType).find(t => t[1] === this.type)?.[0];
|
return Object.entries(ItemType).find(t => t[1] === this.type)?.[0];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,8 @@ export default class Player extends Character {
|
||||||
return this.inventory.find(i => i.isMeleeWeapon);
|
return this.inventory.find(i => i.isMeleeWeapon);
|
||||||
}
|
}
|
||||||
|
|
||||||
get gun() {
|
get rangedWeapon() {
|
||||||
return this.inventory.find(i => i.isShootingWeapon);
|
return this.inventory.find(i => i.isRangedWeapon);
|
||||||
}
|
}
|
||||||
|
|
||||||
get grenade() {
|
get grenade() {
|
||||||
|
|
@ -83,6 +83,13 @@ export default class Player extends Character {
|
||||||
return this.inventory[i];
|
return this.inventory[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public spendItem(item: Item | null | undefined): boolean {
|
||||||
|
if (!item) return false;
|
||||||
|
if (item.isSpendingWeapon) return this.removeItem(item);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public removeItem(item: Item | null | undefined): boolean {
|
public removeItem(item: Item | null | undefined): boolean {
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -130,6 +137,8 @@ export default class Player extends Character {
|
||||||
|
|
||||||
/** @returns true, if action was performed */
|
/** @returns true, if action was performed */
|
||||||
public handleSpin(action: SpinnerAction): boolean {
|
public handleSpin(action: SpinnerAction): boolean {
|
||||||
|
const enemy = this.tile.enemy;
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case SpinnerAction.RUN:
|
case SpinnerAction.RUN:
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -139,10 +148,17 @@ export default class Player extends Character {
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case SpinnerAction.MELEE:
|
case SpinnerAction.MELEE:
|
||||||
return !this.tile.enemy?.isBoss && this.meleeWeapon != null;
|
if (!enemy) return false;
|
||||||
|
if (enemy.isBoss) return false;
|
||||||
|
if (enemy.isFlyingEnemy) return false;
|
||||||
|
|
||||||
|
return this.spendItem(this.meleeWeapon);
|
||||||
|
|
||||||
case SpinnerAction.SHOOT:
|
case SpinnerAction.SHOOT:
|
||||||
return !this.tile.enemy?.isBoss && this.removeItem(this.gun);
|
if (!enemy) return false;
|
||||||
|
if (enemy.isBoss) return false;
|
||||||
|
|
||||||
|
return this.spendItem(this.rangedWeapon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,7 @@ export default class TileMap extends Entity {
|
||||||
for (const _ in range(amount)) {
|
for (const _ in range(amount)) {
|
||||||
const Constructor = [
|
const Constructor = [
|
||||||
ItemType.ENEMY_BOSS,
|
ItemType.ENEMY_BOSS,
|
||||||
|
ItemType.ENEMY_CROW,
|
||||||
ItemType.ENEMY_DOG,
|
ItemType.ENEMY_DOG,
|
||||||
ItemType.ENEMY_SPIDER,
|
ItemType.ENEMY_SPIDER,
|
||||||
ItemType.ENEMY_ZOMBIE,
|
ItemType.ENEMY_ZOMBIE,
|
||||||
|
|
@ -383,7 +384,6 @@ export default class TileMap extends Entity {
|
||||||
private nextPlayer() {
|
private nextPlayer() {
|
||||||
this.spinner.stop();
|
this.spinner.stop();
|
||||||
let overflow = false;
|
let overflow = false;
|
||||||
let startIndex = this.currentPlayerIdx;
|
|
||||||
do {
|
do {
|
||||||
let nextIdx = this.currentPlayerIdx + 1;
|
let nextIdx = this.currentPlayerIdx + 1;
|
||||||
if (nextIdx >= this.players.length) {
|
if (nextIdx >= this.players.length) {
|
||||||
|
|
@ -399,6 +399,10 @@ export default class TileMap extends Entity {
|
||||||
this.currentPlayerIdx = nextIdx;
|
this.currentPlayerIdx = nextIdx;
|
||||||
} while (!this.player.active);
|
} while (!this.player.active);
|
||||||
|
|
||||||
|
const numFoundPlayers = this.foundPlayers.size;
|
||||||
|
const enemiesActive = numFoundPlayers === Object.keys(Players).length
|
||||||
|
|| this.players.length < numFoundPlayers;
|
||||||
|
|
||||||
if (this.win) {
|
if (this.win) {
|
||||||
const endTile = this.tiles.findLast(t => t.type === TileType.END);
|
const endTile = this.tiles.findLast(t => t.type === TileType.END);
|
||||||
if (endTile) {
|
if (endTile) {
|
||||||
|
|
@ -408,7 +412,7 @@ export default class TileMap extends Entity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(alert, 2000, "🎉🎉🎉 ПОБЕДА! 🚗 🎉🎉🎉");
|
setTimeout(alert, 2000, "🎉🎉🎉 ПОБЕДА! 🚗 🎉🎉🎉");
|
||||||
} else if (overflow && this.players.length < this.foundPlayers.size) { // if someone is dead
|
} else if (overflow && enemiesActive) {
|
||||||
const enemies = this.enemies;
|
const enemies = this.enemies;
|
||||||
|
|
||||||
loop:
|
loop:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue