Files
kaltaquise-gamification/kidney_lab/js/player.js
2026-04-16 08:14:20 +02:00

141 lines
3.9 KiB
JavaScript

/**
* player.js
* Player state, grid movement, and inventory management.
* No rendering, no DOM — pure logic.
*/
class Player {
constructor() {
this.reset();
}
reset() {
// Grid position: col 0-3 (left → right), row 0-2 (top → bottom)
this.col = 0;
this.row = 1;
// Carried goods (max SETTINGS.MAX_CARRY)
this.goods = [];
// Whether the player is holding the medication vial
this.hasMedication = false;
// Movement cooldown (ms)
this.moveCooldown = 0;
// Walk-cycle: phase advances (radians) while moveCooldown > 0
this.walkPhase = 0;
this.animFrame = 0;
this.animTimer = 0;
// Flash timer when interacting
this.interactFlash = 0;
}
/* ── Helpers ──────────────────────────────────────────────── */
/** Pixel x centre of current column inside the middle zone. */
pixelX() {
// Evenly distribute columns across the middle zone with a 30px margin
// from each edge, so col 0 sits just right of the belt end and
// col (PLAYER_COLS-1) sits just left of the right-zone border.
const margin = 30;
const avail = SETTINGS.MIDDLE_ZONE_END - SETTINGS.MIDDLE_ZONE_START - margin * 2;
const step = avail / (SETTINGS.PLAYER_COLS - 1);
return SETTINGS.MIDDLE_ZONE_START + margin + this.col * step;
}
/** Pixel y centre of current row. */
pixelY() {
return SETTINGS.BELT_ROWS[this.row];
}
canMove() {
return this.moveCooldown <= 0;
}
canCarryMore() {
return this.goods.length < SETTINGS.MAX_CARRY;
}
/* ── Movement ─────────────────────────────────────────────── */
move(direction) {
if (!this.canMove()) return false;
let nc = this.col;
let nr = this.row;
switch (direction) {
case 'up': nr = Math.max(0, nr - 1); break;
case 'down': nr = Math.min(SETTINGS.PLAYER_ROWS - 1, nr + 1); break;
case 'left': nc = Math.max(0, nc - 1); break;
case 'right': nc = Math.min(SETTINGS.PLAYER_COLS - 1, nc + 1); break;
default: return false;
}
if (nc === this.col && nr === this.row) return false;
this.col = nc;
this.row = nr;
this.moveCooldown = SETTINGS.PLAYER_MOVE_COOLDOWN;
return true;
}
/* ── Inventory ────────────────────────────────────────────── */
/**
* Attempt to add a good to the inventory.
* Returns true on success, false if full.
*/
addGood(good) {
if (!this.canCarryMore()) return false;
this.goods.push(good);
this.interactFlash = 300;
return true;
}
/**
* Remove and return all carried goods (for lab deposit).
*/
depositGoods() {
const deposited = [...this.goods];
this.goods = [];
this.interactFlash = 400;
return deposited;
}
pickupMedication() {
this.hasMedication = true;
this.interactFlash = 400;
}
/**
* Deliver medication to patient.
* Returns true if medication was delivered.
*/
deliverMedication() {
if (!this.hasMedication) return false;
this.hasMedication = false;
this.interactFlash = 600;
return true;
}
/* ── Update ───────────────────────────────────────────────── */
update(dt) {
if (this.moveCooldown > 0) {
this.moveCooldown -= dt;
// Advance walk cycle: ~π radians per 180 ms move cooldown → one leg swing per step
this.walkPhase += dt * 0.0175;
}
if (this.interactFlash > 0) this.interactFlash -= dt;
this.animTimer += dt;
if (this.animTimer >= 200) {
this.animTimer -= 200;
this.animFrame = (this.animFrame + 1) % 4;
}
}
}