1
0
Fork 0

Compare commits

...

3 Commits

Author SHA1 Message Date
Pabloader ee38d7f53c Fix build minification 2025-08-25 11:56:30 +00:00
Pabloader 01c601424f Refactor input handling 2025-07-05 09:02:40 +00:00
Pabloader 0977914f20 Add new mob & item 2025-07-03 14:26:25 +00:00
13 changed files with 245 additions and 82 deletions

View File

@ -94,10 +94,6 @@ export async function buildHTML(game: string, { production = false, portable = f
const minifyResult = UglifyJS.minify(script, {
module: true,
toplevel: true,
mangle: {
toplevel: true,
properties: true,
},
});
if (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
.replace('<!--$SCRIPT$-->', `${scriptPrefix}<script type="module">${script}</script>`)
.replace('<!--$TITLE$-->', title)
.replace('<!--$ICON$-->', icon)
.replace('<!--$MANIFEST$-->', manifest)
.replace('/*$STYLE$*/', style);
.replace('<!--$SCRIPT$-->', () => `${scriptPrefix}<script type="module">${script}</script>`) // to avoid $& being replaced
.replace('<!--$TITLE$-->', () => title)
.replace('<!--$ICON$-->', () => icon)
.replace('<!--$MANIFEST$-->', () => manifest)
.replace('/*$STYLE$*/', () => style);
return minify(resultHTML, {
collapseWhitespace: production,

View File

@ -111,7 +111,7 @@
"@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=="],
@ -123,6 +123,8 @@
"@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/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=="],
"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=="],
@ -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=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"delay": ["delay@6.0.0", "", {}, "sha512-2NJozoOHQ4NuZuVIr5CWd0iiLVIRSDepakaovIN+9eIDHEhdCAEvSy2cuf1DCrPPQLvHmbqTHODlhHg8UCy4zw=="],
"detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="],

33
src/common/game.ts Normal file
View File

@ -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();
}
}
};

View File

@ -6,33 +6,94 @@ interface IKeyState {
pressed?: 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,
LY = 1,
RX = 2,
RY = 3,
}
document.body.addEventListener('keydown', (e) => {
const keyId = e.code as KeyCode;
export enum GamepadButton {
A = 0,
B = 1,
X = 2,
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,
}
const DEAD_ZONE = 0.05;
const KEYS: Partial<Record<KeyCode, IKeyState>> = {};
const onStateChange = (keyId: KeyCode, state: boolean) => {
console.debug(`[Input] Pressed ${keyId}`);
if (KEYS[keyId]) {
KEYS[keyId].state = true;
KEYS[keyId].state = state;
} else {
KEYS[keyId] = { state: true };
KEYS[keyId] = { state };
}
});
document.body.addEventListener('keyup', (e) => {
const keyId = e.code as KeyCode;
console.debug(`[Input] Released ${keyId}`);
if (KEYS[keyId]) {
KEYS[keyId].state = false;
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);
}
});
};
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;
document.body.addEventListener('keydown', (e) => onStateChange(e.code as KeyCode, true));
document.body.addEventListener('keyup', (e) => onStateChange(e.code as KeyCode, false));
export function updateKeys() {
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;
@ -47,4 +108,7 @@ export function updateKeys() {
key.prevState = key.state;
}
}
}
export default Input;

View File

@ -1,9 +1,10 @@
import { BrickDisplay } from "@common/display/brick";
import { isPressed, updateKeys } from "@common/input";
import Input from "@common/input";
import spritesheetImage from './assets/spritesheet.png';
import { ITEMS, Items, MONSTERS, MONSTERS_ORDER, loadData, type Item, type Monster } from "./data";
import { randBool, weightedChoice } from "@common/utils";
import { gameLoop } from "@common/game";
let display: BrickDisplay;
const spritesheet = BrickDisplay.convertImage(spritesheetImage);
@ -47,9 +48,9 @@ let secondLootItem: Items | null = null;
let frames = 0;
let prevFrameTime: number = 0;
async function loop(time: number) {
async function frame(time: number) {
frames++;
const dt = time - prevFrameTime;
prevFrameTime = time;
if (frames % 8 == 0) {
@ -105,17 +106,17 @@ async function loop(time: number) {
} else if (y === secondLootY) {
lootToConfirm = secondLootItem;
playerTurn = false;
} else if (isPressed('ArrowLeft')) {
} else if (Input.isPressed(Input.KeyCode.LEFT)) {
selectedSlot = (selectedSlot + inventory.length - 1) % inventory.length;
} else if (isPressed('ArrowRight')) {
} else if (Input.isPressed(Input.KeyCode.RIGHT)) {
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;
playerTurn = false;
} else if (isPressed('ArrowDown')) {
} else if (Input.isPressed(Input.KeyCode.DOWN)) {
targetY = y - 4;
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) {
inventory.splice(selectedSlot, 1);
selectedSlot = (selectedSlot + inventory.length - 1) % inventory.length;
@ -138,7 +139,7 @@ async function loop(time: number) {
} else if (lootToConfirm) {
console.log('Loot confirm');
if (isPressed('Space')) {
if (Input.isPressed(Input.KeyCode.SPACE)) {
const i = ITEMS[lootToConfirm];
if (i.instantUse && i.heal) {
playerHealth += i.heal;
@ -150,10 +151,10 @@ async function loop(time: number) {
}
lootConfirmed = true;
} else if (isPressed('ArrowUp')) {
} else if (Input.isPressed(Input.KeyCode.UP)) {
targetY = y + 4;
playerTurn = true;
} else if (isPressed('ArrowDown')) {
} else if (Input.isPressed(Input.KeyCode.DOWN)) {
targetY = y - 4;
playerTurn = true;
}
@ -279,9 +280,6 @@ async function loop(time: number) {
}
display.update();
updateKeys();
requestAnimationFrame(loop);
}
function spawnNextMonster() {
@ -321,11 +319,12 @@ function damagePlayer(monster: Monster) {
}
}
export default function main() {
const setup = () => {
display = new BrickDisplay();
display.init();
loadData(spritesheet);
spawnNextMonster();
requestAnimationFrame(loop);
}
export default gameLoop(setup, frame);

View File

@ -92,7 +92,7 @@ export const App = () => {
if (taken < request) {
score += diff * 10;
} else if (taken > request) {
score += diff;
score += taken;
} else if (taken === 0 && request === 0) {
score += 5;
} else if (taken === request) {

View File

@ -1,5 +1,34 @@
import awoo from "./awoo.cpp";
import { gameLoop } from "@common/game";
import Input from "@common/input";
export default function main() {
console.log(awoo, awoo.play());
const setup = () => {
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);

View File

@ -1,6 +1,6 @@
import { Color, Direction, getOppositeDirection } from './const';
import { drawTextInBox, tick } from './display';
import { isHeld, isPressed, updateKeys } from '@common/input';
import Input from '@common/input';
import { createItems, getItemsCount } from './item';
import { GameMap } from './map';
import { Player } from './player';
@ -13,30 +13,30 @@ const player = new Player(currentRoom.x + currentRoom.width / 2, currentRoom.y +
let lastMove = Date.now();
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();
let newX = player.x;
let newY = player.y;
let moved = isSpacePressed;
if (isHeld('arrowup')) {
if (Input.isHeld(Input.KeyCode.UP)) {
newY--;
moved = true;
}
if (isHeld('arrowdown')) {
if (Input.isHeld(Input.KeyCode.DOWN)) {
newY++;
moved = true;
}
if (isHeld('arrowleft')) {
if (Input.isHeld(Input.KeyCode.LEFT)) {
newX--;
moved = true;
}
if (isHeld('arrowright')) {
if (Input.isHeld(Input.KeyCode.RIGHT)) {
newX++;
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 });
while (true) {
updateKeys();
Input.updateKeys();
handleInput();
handleLogic();
draw();

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -5,6 +5,7 @@ import police from './assets/characters/police.jpg';
import runner from './assets/characters/runner.jpg';
import boss from './assets/enemies/boss.jpg';
import crow from './assets/enemies/crow.jpg';
import dog from './assets/enemies/dog.jpg';
import spider from './assets/enemies/spider.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 pistol from './assets/weapons/pistol.jpg';
import rocketLauncher from './assets/weapons/rocket_launcher.jpg';
import slingshot from './assets/weapons/slingshot.jpg';
import shotgun from './assets/weapons/shotgun.jpg';
export const ItemType = {
@ -31,6 +33,7 @@ export const ItemType = {
CHAR_RUNNER: runner,
ENEMY_BOSS: boss,
ENEMY_CROW: crow,
ENEMY_DOG: dog,
ENEMY_SPIDER: spider,
ENEMY_ZOMBIE: zombie,
@ -47,6 +50,7 @@ export const ItemType = {
WEAPON_KNIFE: knife,
WEAPON_PISTOL: pistol,
WEAPON_ROCKET_LAUNCHER: rocketLauncher,
WEAPON_SLINGSHOT: slingshot,
WEAPON_SHOTGUN: shotgun,
} as const;
@ -65,12 +69,17 @@ export default class Item {
get isEnemy() {
return [
ItemType.ENEMY_BOSS,
ItemType.ENEMY_CROW,
ItemType.ENEMY_DOG,
ItemType.ENEMY_SPIDER,
ItemType.ENEMY_ZOMBIE,
].includes(this.type);
}
get isFlyingEnemy() {
return this.type === ItemType.ENEMY_CROW;
}
get isItem() {
return [
ItemType.ITEM_FUEL,
@ -81,7 +90,7 @@ export default class Item {
}
get isWeapon() {
return this.isMeleeWeapon || this.isShootingWeapon || [
return this.isMeleeWeapon || this.isRangedWeapon || [
ItemType.WEAPON_GRENADE,
ItemType.WEAPON_ROCKET_LAUNCHER,
].includes(this.type);
@ -91,18 +100,27 @@ export default class Item {
return [
ItemType.WEAPON_AXE,
ItemType.WEAPON_KNIFE,
ItemType.ITEM_PLANKS,
].includes(this.type);
}
get isShootingWeapon() {
get isRangedWeapon() {
return [
ItemType.WEAPON_ASSAULT_RIFLE,
ItemType.WEAPON_CROSSBOW,
ItemType.WEAPON_PISTOL,
ItemType.WEAPON_SLINGSHOT,
ItemType.WEAPON_SHOTGUN,
].includes(this.type);
}
get isSpendingWeapon() {
return (
this.type === ItemType.ITEM_PLANKS ||
(this.type !== ItemType.WEAPON_SLINGSHOT && this.isRangedWeapon)
);
}
get isBoss() {
return this.type === ItemType.ENEMY_BOSS;
}

View File

@ -45,8 +45,8 @@ export default class Player extends Character {
return this.inventory.find(i => i.isMeleeWeapon);
}
get gun() {
return this.inventory.find(i => i.isShootingWeapon);
get rangedWeapon() {
return this.inventory.find(i => i.isRangedWeapon);
}
get grenade() {
@ -83,6 +83,13 @@ export default class Player extends Character {
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 {
if (!item) {
return false;
@ -130,6 +137,8 @@ export default class Player extends Character {
/** @returns true, if action was performed */
public handleSpin(action: SpinnerAction): boolean {
const enemy = this.tile.enemy;
switch (action) {
case SpinnerAction.RUN:
return true;
@ -139,10 +148,17 @@ export default class Player extends Character {
return true;
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:
return !this.tile.enemy?.isBoss && this.removeItem(this.gun);
if (!enemy) return false;
if (enemy.isBoss) return false;
return this.spendItem(this.rangedWeapon);
}
}
}

View File

@ -174,6 +174,7 @@ export default class TileMap extends Entity {
for (const _ in range(amount)) {
const Constructor = [
ItemType.ENEMY_BOSS,
ItemType.ENEMY_CROW,
ItemType.ENEMY_DOG,
ItemType.ENEMY_SPIDER,
ItemType.ENEMY_ZOMBIE,
@ -383,7 +384,6 @@ export default class TileMap extends Entity {
private nextPlayer() {
this.spinner.stop();
let overflow = false;
let startIndex = this.currentPlayerIdx;
do {
let nextIdx = this.currentPlayerIdx + 1;
if (nextIdx >= this.players.length) {
@ -399,6 +399,10 @@ export default class TileMap extends Entity {
this.currentPlayerIdx = nextIdx;
} while (!this.player.active);
const numFoundPlayers = this.foundPlayers.size;
const enemiesActive = numFoundPlayers === Object.keys(Players).length
|| this.players.length < numFoundPlayers;
if (this.win) {
const endTile = this.tiles.findLast(t => t.type === TileType.END);
if (endTile) {
@ -408,7 +412,7 @@ export default class TileMap extends Entity {
}
}
setTimeout(alert, 2000, "🎉🎉🎉 ПОБЕДА! 🚗 🎉🎉🎉");
} else if (overflow && this.players.length < this.foundPlayers.size) { // if someone is dead
} else if (overflow && enemiesActive) {
const enemies = this.enemies;
loop: