Add combat orchestration with turn flow and win/loss check
This commit is contained in:
parent
35d2176bc2
commit
45d62144bf
2 changed files with 125 additions and 0 deletions
65
src/combat.js
Normal file
65
src/combat.js
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { drawCards } from "./state.js";
|
||||
import { resolveEffects } from "./effects.js";
|
||||
import { resolveEnemyAction } from "./enemies.js";
|
||||
import { rollDie } from "./die.js";
|
||||
|
||||
export function startTurn(state) {
|
||||
const dieResult = rollDie();
|
||||
let next = {
|
||||
...state,
|
||||
player: {
|
||||
...state.player,
|
||||
energy: state.player.maxEnergy,
|
||||
block: 0,
|
||||
},
|
||||
combat: {
|
||||
...state.combat,
|
||||
phase: "player_turn",
|
||||
dieResult,
|
||||
selectedCard: null,
|
||||
},
|
||||
};
|
||||
next = drawCards(next, 5);
|
||||
return next;
|
||||
}
|
||||
|
||||
export function resolveEnemyTurn(state) {
|
||||
let next = {
|
||||
...state,
|
||||
enemy: { ...state.enemy, block: 0 },
|
||||
};
|
||||
|
||||
const action = resolveEnemyAction(
|
||||
next.enemy,
|
||||
next.combat.dieResult,
|
||||
next.enemy.trackPosition,
|
||||
);
|
||||
|
||||
if (action && action.effects) {
|
||||
next = resolveEffects(next, action.effects, "enemy", "player");
|
||||
}
|
||||
|
||||
let trackPosition = next.enemy.trackPosition;
|
||||
if (next.enemy.actionType === "cube") {
|
||||
trackPosition = Math.min(
|
||||
trackPosition + 1,
|
||||
(next.enemy.actionTrack || []).length - 1,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...next,
|
||||
enemy: { ...next.enemy, trackPosition },
|
||||
combat: {
|
||||
...next.combat,
|
||||
phase: "player_turn",
|
||||
turn: next.combat.turn + 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function checkCombatEnd(state) {
|
||||
if (state.enemy.hp <= 0) return "victory";
|
||||
if (state.player.hp <= 0) return "defeat";
|
||||
return null;
|
||||
}
|
||||
60
src/combat.test.js
Normal file
60
src/combat.test.js
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import { describe, expect, test } from "bun:test";
|
||||
import { startTurn, resolveEnemyTurn, checkCombatEnd } from "./combat.js";
|
||||
import { createCombatState } from "./state.js";
|
||||
|
||||
describe("startTurn", () => {
|
||||
test("resets energy and block, draws 5 cards", () => {
|
||||
let state = createCombatState("ironclad", "jaw_worm");
|
||||
state = {
|
||||
...state,
|
||||
player: { ...state.player, energy: 0, block: 5 },
|
||||
};
|
||||
const next = startTurn(state);
|
||||
expect(next.player.energy).toBe(3);
|
||||
expect(next.player.block).toBe(0);
|
||||
expect(next.player.hand).toHaveLength(5);
|
||||
expect(next.combat.dieResult).toBeGreaterThanOrEqual(1);
|
||||
expect(next.combat.dieResult).toBeLessThanOrEqual(6);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveEnemyTurn", () => {
|
||||
test("enemy attacks reduce player hp or block", () => {
|
||||
let state = createCombatState("ironclad", "jaw_worm");
|
||||
state = { ...state, combat: { ...state.combat, dieResult: 1 } };
|
||||
const next = resolveEnemyTurn(state);
|
||||
expect(next.combat.phase).toBe("player_turn");
|
||||
expect(next.combat.turn).toBe(state.combat.turn + 1);
|
||||
});
|
||||
|
||||
test("enemy block resets before acting", () => {
|
||||
let state = createCombatState("ironclad", "jaw_worm");
|
||||
state = {
|
||||
...state,
|
||||
enemy: { ...state.enemy, block: 5 },
|
||||
combat: { ...state.combat, dieResult: 1 },
|
||||
};
|
||||
const next = resolveEnemyTurn(state);
|
||||
// die result 1 maps to an attack action (hit 2), so no block regained
|
||||
expect(next.enemy.block).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkCombatEnd", () => {
|
||||
test("returns 'victory' when enemy hp is 0", () => {
|
||||
let state = createCombatState("ironclad", "jaw_worm");
|
||||
state = { ...state, enemy: { ...state.enemy, hp: 0 } };
|
||||
expect(checkCombatEnd(state)).toBe("victory");
|
||||
});
|
||||
|
||||
test("returns 'defeat' when player hp is 0", () => {
|
||||
let state = createCombatState("ironclad", "jaw_worm");
|
||||
state = { ...state, player: { ...state.player, hp: 0 } };
|
||||
expect(checkCombatEnd(state)).toBe("defeat");
|
||||
});
|
||||
|
||||
test("returns null when combat continues", () => {
|
||||
const state = createCombatState("ironclad", "jaw_worm");
|
||||
expect(checkCombatEnd(state)).toBeNull();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue