game: add types

This commit is contained in:
He4eT 2025-06-06 19:27:08 +02:00
commit ed8f297c7d

159
game.js
View file

@ -6,50 +6,100 @@
// version: 0.1 // version: 0.1
// script: js // script: js
// api: https://github.com/nesbox/TIC-80/wiki/API
function TIC() { function TIC() {
gameStages[currentStage]() gameStages[currentStage]()
} }
// Stages /* Stages */
/** @type Record<string, () => void> */
const gameStages = { const gameStages = {
mainMenu, mainMenu,
gameover, gameover,
gameplay, gameplay,
} }
/** @type {keyof typeof gameStages} */
let currentStage = 'mainMenu' let currentStage = 'mainMenu'
// State /* State */
/**
* @typedef {{ x: number, y: number }} Point
*
* @typedef {{
* top: number,
* right: number,
* bottom: number,
* left: number,
* }} Bounds
*/
/**
* @typedef {{
* screenPosition: Point,
* bounds: Bounds,
* spriteHalfSize: number,
* }} Arena
*/
/** @type {Arena} */
let arena = { let arena = {
// Screen position screenPosition: {
x: 7, x: 7,
y: 7, y: 7,
// Bounds },
top: 0, bounds: {
right: 225, top: 0,
bottom: 89, right: 225,
left: 0, bottom: 89,
// Sprite size left: 0,
spriteSize: 7, },
spriteHalfSize: 3, spriteHalfSize: 3,
} }
/**
* @typedef {{
* sprite: number,
* speed: number,
* position: Point,
* }} Player
*/
/** @type {Player} */
let player = { let player = {
sprite: 64, sprite: 64,
speed: 1, speed: 1,
x: 0, position: {
y: 0, x: 0,
y: 0,
},
} }
/**
* @typedef {{
* type: 'zombie',
* dangerZone: number,
* positions: Point[],
* }} Enemy
*/
/** @type {Enemy[]} */
let enemies = [] let enemies = []
/**
* @typedef {{
* type: 'laser' | 'nuke' | 'verticalLine' | 'horizontalLine' | 'detection',
* from: Point,
* to: Point,
* frames: number[],
* }} Effect
*/
/** @type {Effect[]} */
let effects = [] let effects = []
// Main Menu /* Main Menu */
function mainMenu() { function mainMenu() {
if ([BTN_A, BTN_B, BTN_X, BTN_Y].map(btn).some(Boolean)) { if ([BTN_A, BTN_B, BTN_X, BTN_Y].map(btn).some(Boolean)) {
@ -65,7 +115,7 @@ function mainMenu() {
print(instruction, 12, 30, 4) print(instruction, 12, 30, 4)
} }
// Gameover /* Gameover */
function gameover() { function gameover() {
cls(0) cls(0)
@ -77,7 +127,7 @@ function gameover() {
print(title, 12, 12, 10, false, 2) print(title, 12, 12, 10, false, 2)
} }
// Gameplay /* Gameplay */
function gameplay() { function gameplay() {
checkColisions() checkColisions()
@ -86,7 +136,7 @@ function gameplay() {
handleMorse() handleMorse()
spawn() spawn()
// moveEnemies() /* moveEnemies() */
drawInterface() drawInterface()
drawArena() drawArena()
@ -107,9 +157,16 @@ function handleMoves() {
if (btn(BTN_D)) dy += 1 if (btn(BTN_D)) dy += 1
const norm = player.speed / ([dx, dy].every((d) => d !== 0) ? Math.SQRT2 : 1) const norm = player.speed / ([dx, dy].every((d) => d !== 0) ? Math.SQRT2 : 1)
const { bounds } = arena
player.x = Math.max(arena.left, Math.min(arena.right, player.x + dx * norm)) player.position.x = Math.max(
player.y = Math.max(arena.top, Math.min(arena.bottom, player.y + dy * norm)) bounds.left,
Math.min(bounds.right, player.position.x + dx * norm),
)
player.position.y = Math.max(
bounds.top,
Math.min(bounds.bottom, player.position.y + dy * norm),
)
} }
function handleMorse() { function handleMorse() {
@ -119,14 +176,8 @@ function handleMorse() {
if (enemies.length > 0) { if (enemies.length > 0) {
effects.unshift({ effects.unshift({
type: 'laser', type: 'laser',
from: { from: player.position,
x: player.x, to: enemies[0].positions[0],
y: player.y,
},
to: {
x: enemies[0].x[0],
y: enemies[0].y[0],
},
frames: [1, 2, 3, 4, 7, 7, 7, 6, 5, 4, 3, 2, 1], frames: [1, 2, 3, 4, 7, 7, 7, 6, 5, 4, 3, 2, 1],
}) })
enemies.shift() enemies.shift()
@ -135,14 +186,18 @@ function handleMorse() {
} }
} }
// Enemies /* Enemies */
function spawn() { function spawn() {
if (enemies.length === 0) { if (enemies.length === 0) {
enemies = [ enemies = [
{ {
x: [Math.random() * arena.right], positions: [
y: [Math.random() * arena.bottom], {
x: Math.random() * arena.bounds.right,
y: Math.random() * arena.bounds.bottom,
},
],
type: 'zombie', type: 'zombie',
dangerZone: 8, dangerZone: 8,
}, },
@ -152,10 +207,7 @@ function spawn() {
effects.unshift({ effects.unshift({
type: 'detection', type: 'detection',
from: {}, from: {},
to: { to: enemy.positions[0],
x: enemy.x[0],
y: enemy.y[0],
},
frames: Array(5).fill(4), frames: Array(5).fill(4),
}) })
}) })
@ -167,7 +219,10 @@ function checkColisions() {
enemies enemies
.map((enemy) => [ .map((enemy) => [
enemy, enemy,
Math.hypot(player.x - enemy.x[0], player.y - enemy.y[0]), Math.hypot(
player.position.x - enemy.positions[0].x,
player.position.y - enemy.positions[0].y,
),
]) ])
.some(([enemy, distance]) => distance < enemy.dangerZone) .some(([enemy, distance]) => distance < enemy.dangerZone)
) { ) {
@ -175,12 +230,12 @@ function checkColisions() {
} }
} }
// Draw /* Draw */
function arenaToScreen({ x, y }) { function arenaToScreen({ x, y }) {
return { return {
x: x + arena.x, x: x + arena.screenPosition.x,
y: y + arena.y, y: y + arena.screenPosition.y,
} }
} }
@ -214,7 +269,7 @@ function drawFX() {
.forEach((effect) => .forEach((effect) =>
({ ({
laser: ({ from, to, frames }) => { laser: ({ from, to, frames }) => {
// [1, 2, 3, 4, 7, 7, 7, 6, 5, 4, 3, 2, 1] /* [1, 2, 3, 4, 7, 7, 7, 6, 5, 4, 3, 2, 1] */
const color = frames.shift() const color = frames.shift()
line(from.x, from.y, to.x, to.y, color) line(from.x, from.y, to.x, to.y, color)
circ(from.x, from.y, frames.length / 3, color) circ(from.x, from.y, frames.length / 3, color)
@ -222,22 +277,22 @@ function drawFX() {
circb(to.x, to.y, frames.length, color + 3) circb(to.x, to.y, frames.length, color + 3)
}, },
nuke: ({ to, frames }) => { nuke: ({ to, frames }) => {
// [6, 5, 4, 3, 2] /* [6, 5, 4, 3, 2] */
const color = frames.shift() const color = frames.shift()
circ(to.x, to.y, Math.pow(frames.length, 5), color) circ(to.x, to.y, Math.pow(frames.length, 5), color)
}, },
verticalLine: ({ to, frames }) => { verticalLine: ({ to, frames }) => {
// [4, 5, 6, 7, 7, 6, 5, 4] /* [4, 5, 6, 7, 7, 6, 5, 4] */
const color = frames.shift() const color = frames.shift()
rect(0, to.y - frames.length, SCREEN_W, frames.length * 2, color) rect(0, to.y - frames.length, SCREEN_W, frames.length * 2, color)
}, },
horizontalLine: ({ to, frames }) => { horizontalLine: ({ to, frames }) => {
// [4, 5, 6, 7, 7, 6, 5, 4] /* [4, 5, 6, 7, 7, 6, 5, 4] */
const color = frames.shift() const color = frames.shift()
rect(to.x - frames.length, 0, frames.length * 2, SCREEN_W, color) rect(to.x - frames.length, 0, frames.length * 2, SCREEN_W, color)
}, },
detection: ({ to, frames }) => { detection: ({ to, frames }) => {
// Array(5).fill(4)] /* Array(5).fill(4)] */
const color = frames.shift() const color = frames.shift()
const w = arena.spriteHalfSize const w = arena.spriteHalfSize
const d = frames.length + 2 * w const d = frames.length + 2 * w
@ -264,27 +319,27 @@ function drawFX() {
function drawEnemies() { function drawEnemies() {
enemies enemies
.map((enemy) => [80, enemy.x[0], enemy.y[0]]) .map((enemy) => [80, enemy.positions[0].x, enemy.positions[0].y])
.forEach((spriteData) => drawSprite(...spriteData)) .forEach((spriteData) => drawSprite(...spriteData))
} }
function drawPlayer() { function drawPlayer() {
drawSprite(player.sprite, player.x, player.y) drawSprite(player.sprite, player.position.x, player.position.y)
} }
// Utils /* Utils */
function rnd(from, to) { function rnd(from, to) {
return Math.floor(Math.random() * (to - from + 1)) + from return Math.floor(Math.random() * (to - from + 1)) + from
} }
// Constants /* Constants */
// Screen /* Screen */
const SCREEN_W = 240 const SCREEN_W = 240
const SCREEN_H = 136 const SCREEN_H = 136
// Buttons /* Buttons */
const [BTN_U, BTN_D, BTN_L, BTN_R, BTN_A, BTN_B, BTN_X, BTN_Y] = [ const [BTN_U, BTN_D, BTN_L, BTN_R, BTN_A, BTN_B, BTN_X, BTN_Y] = [
...Array(8).keys(), ...Array(8).keys(),
] ]