Added kidney_labe and Cyste_kid
This commit is contained in:
200
kidney_lab/js/conveyor.js
Normal file
200
kidney_lab/js/conveyor.js
Normal file
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* conveyor.js
|
||||
* Left conveyor belts (3×, goods travel → right).
|
||||
* Right conveyor belt (1×, medication travels ← left).
|
||||
* Pure logic — no rendering, no DOM.
|
||||
*/
|
||||
|
||||
/* ────────────────────────────── Good ──────────────────────────── */
|
||||
|
||||
const GoodState = Object.freeze({
|
||||
SLIDING: 'sliding', // moving along belt
|
||||
READY: 'ready', // at belt's right end, waiting for pickup
|
||||
COLLECTED: 'collected', // grabbed by player
|
||||
DROPPED: 'dropped', // fell — penalty already applied
|
||||
});
|
||||
|
||||
class Good {
|
||||
constructor(beltRow, isSpecial) {
|
||||
this.id = Good._nextId++;
|
||||
this.beltRow = beltRow;
|
||||
this.isSpecial = isSpecial;
|
||||
this.x = SETTINGS.LEFT_ZONE_START + SETTINGS.GOOD_SIZE / 2;
|
||||
this.y = SETTINGS.BELT_ROWS[beltRow];
|
||||
this.state = GoodState.SLIDING;
|
||||
this.waitTimer = 0;
|
||||
}
|
||||
}
|
||||
Good._nextId = 0;
|
||||
|
||||
/* ────────────────────────── LeftConveyorSystem ─────────────────── */
|
||||
|
||||
class LeftConveyorSystem {
|
||||
constructor() {
|
||||
// Belt strip animation offset (px, wraps at 40)
|
||||
this.animOffset = 0;
|
||||
|
||||
// Per-belt state: goods array + countdown to next spawn
|
||||
this.belts = SETTINGS.BELT_INIT_OFFSETS.map(offset => ({
|
||||
goods: [],
|
||||
spawnTimer: offset,
|
||||
}));
|
||||
}
|
||||
|
||||
_randomSpawnTime() {
|
||||
return SETTINGS.BELT_MIN_SPAWN
|
||||
+ Math.random() * (SETTINGS.BELT_MAX_SPAWN - SETTINGS.BELT_MIN_SPAWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all belts.
|
||||
* @param {number} dt – delta time in ms
|
||||
* @param {function} onDrop – called with (good) when a good drops off
|
||||
*/
|
||||
update(dt, onDrop) {
|
||||
this.animOffset = (this.animOffset + SETTINGS.LEFT_BELT_SPEED * dt / 1000) % 40;
|
||||
|
||||
this.belts.forEach((belt, row) => {
|
||||
// ── Spawn ──
|
||||
belt.spawnTimer -= dt;
|
||||
if (belt.spawnTimer <= 0) {
|
||||
belt.spawnTimer = this._randomSpawnTime();
|
||||
const isSpecial = Math.random() < SETTINGS.SPECIAL_GOOD_CHANCE;
|
||||
belt.goods.push(new Good(row, isSpecial));
|
||||
}
|
||||
|
||||
// ── Move & age goods ──
|
||||
belt.goods.forEach(good => {
|
||||
if (good.state === GoodState.SLIDING) {
|
||||
good.x += SETTINGS.LEFT_BELT_SPEED * dt / 1000;
|
||||
const pickupX = SETTINGS.LEFT_ZONE_END - SETTINGS.GOOD_SIZE * 0.5;
|
||||
if (good.x >= pickupX) {
|
||||
good.x = pickupX;
|
||||
good.state = GoodState.READY;
|
||||
good.waitTimer = SETTINGS.GOOD_WAIT_TIME;
|
||||
}
|
||||
} else if (good.state === GoodState.READY) {
|
||||
good.waitTimer -= dt;
|
||||
if (good.waitTimer <= 0) {
|
||||
good.state = GoodState.DROPPED;
|
||||
onDrop(good);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ── Prune finished goods ──
|
||||
belt.goods = belt.goods.filter(
|
||||
g => g.state === GoodState.SLIDING || g.state === GoodState.READY
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to collect the READY good on the given row.
|
||||
* Returns the Good object on success, null otherwise.
|
||||
*/
|
||||
tryCollect(row) {
|
||||
const belt = this.belts[row];
|
||||
const good = belt.goods.find(g => g.state === GoodState.READY);
|
||||
if (!good) return null;
|
||||
good.state = GoodState.COLLECTED;
|
||||
return good;
|
||||
}
|
||||
|
||||
/** True if belt[row] has a READY good waiting at the pickup point. */
|
||||
hasReadyGood(row) {
|
||||
return this.belts[row].goods.some(g => g.state === GoodState.READY);
|
||||
}
|
||||
|
||||
/** All goods in SLIDING or READY state across all belts (for rendering). */
|
||||
allGoods() {
|
||||
return this.belts.flatMap(b => b.goods);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────── Medication (right belt) ──────────────── */
|
||||
|
||||
const MedState = Object.freeze({
|
||||
ON_BELT: 'on_belt', // sliding left
|
||||
READY: 'ready', // at left end, waiting for player
|
||||
PICKED: 'picked', // player has it
|
||||
EXPIRED: 'expired', // timed out while waiting
|
||||
});
|
||||
|
||||
class Medication {
|
||||
constructor() {
|
||||
this.id = Medication._nextId++;
|
||||
// Start at right edge of right zone
|
||||
this.x = SETTINGS.RIGHT_ZONE_END - SETTINGS.GOOD_SIZE;
|
||||
this.y = SETTINGS.BELT_ROWS[1]; // always middle row
|
||||
this.state = MedState.ON_BELT;
|
||||
this.waitTimer = 0;
|
||||
}
|
||||
}
|
||||
Medication._nextId = 0;
|
||||
|
||||
/* ────────────────────────── RightConveyorSystem ────────────────── */
|
||||
|
||||
class RightConveyorSystem {
|
||||
constructor() {
|
||||
this.animOffset = 0;
|
||||
this.medications = [];
|
||||
}
|
||||
|
||||
addMedication() {
|
||||
this.medications.push(new Medication());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update right belt medications.
|
||||
* @param {number} dt – delta time ms
|
||||
*/
|
||||
update(dt) {
|
||||
this.animOffset = (this.animOffset + SETTINGS.RIGHT_BELT_SPEED * dt / 1000) % 40;
|
||||
|
||||
this.medications.forEach(med => {
|
||||
if (med.state === MedState.ON_BELT) {
|
||||
med.x -= SETTINGS.RIGHT_BELT_SPEED * dt / 1000;
|
||||
const pickupX = SETTINGS.RIGHT_ZONE_START + SETTINGS.GOOD_SIZE * 0.5;
|
||||
if (med.x <= pickupX) {
|
||||
med.x = pickupX;
|
||||
med.state = MedState.READY;
|
||||
med.waitTimer = SETTINGS.MED_WAIT_TIME;
|
||||
}
|
||||
} else if (med.state === MedState.READY) {
|
||||
med.waitTimer -= dt;
|
||||
if (med.waitTimer <= 0) {
|
||||
med.state = MedState.EXPIRED;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Prune expired / picked medications
|
||||
this.medications = this.medications.filter(
|
||||
m => m.state === MedState.ON_BELT || m.state === MedState.READY
|
||||
);
|
||||
}
|
||||
|
||||
/** True if there is a READY medication at the pickup point (row 1 left end). */
|
||||
hasReadyMedication() {
|
||||
return this.medications.some(m => m.state === MedState.READY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to pick up the READY medication.
|
||||
* Returns true on success.
|
||||
*/
|
||||
tryPickup() {
|
||||
const med = this.medications.find(m => m.state === MedState.READY);
|
||||
if (!med) return false;
|
||||
med.state = MedState.PICKED;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Visible medications for rendering (ON_BELT and READY). */
|
||||
visibleMedications() {
|
||||
return this.medications.filter(
|
||||
m => m.state === MedState.ON_BELT || m.state === MedState.READY
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user