Add state module with combat init, draw, play, end turn
This commit is contained in:
parent
08214fc8cb
commit
a1f242d54e
4 changed files with 188 additions and 0 deletions
3
src/effects.js
vendored
Normal file
3
src/effects.js
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export function resolveEffects(state) {
|
||||
return state;
|
||||
}
|
||||
5
src/enemies.js
Normal file
5
src/enemies.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import enemyDb from "../data/enemies.json";
|
||||
|
||||
export function getEnemy(id) {
|
||||
return enemyDb[id];
|
||||
}
|
||||
106
src/state.js
Normal file
106
src/state.js
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import { getCard, getStarterDeck } from "./cards.js";
|
||||
import { resolveEffects } from "./effects.js";
|
||||
import { getEnemy } from "./enemies.js";
|
||||
|
||||
export function createCombatState(character, enemyId) {
|
||||
const enemy = getEnemy(enemyId);
|
||||
return {
|
||||
player: {
|
||||
hp: 11,
|
||||
maxHp: 11,
|
||||
energy: 3,
|
||||
maxEnergy: 3,
|
||||
block: 0,
|
||||
strength: 0,
|
||||
vulnerable: 0,
|
||||
weak: 0,
|
||||
drawPile: shuffle([...getStarterDeck(character)]),
|
||||
hand: [],
|
||||
discardPile: [],
|
||||
exhaustPile: [],
|
||||
powers: [],
|
||||
},
|
||||
enemy: {
|
||||
id: enemy.id,
|
||||
name: enemy.name,
|
||||
hp: enemy.hp,
|
||||
maxHp: enemy.hp,
|
||||
block: 0,
|
||||
strength: 0,
|
||||
vulnerable: 0,
|
||||
weak: 0,
|
||||
actionType: enemy.actionType,
|
||||
actions: enemy.actions,
|
||||
actionTrack: enemy.actionTrack || null,
|
||||
trackPosition: 0,
|
||||
},
|
||||
combat: {
|
||||
turn: 1,
|
||||
phase: "player_turn",
|
||||
dieResult: null,
|
||||
selectedCard: null,
|
||||
log: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function drawCards(state, count) {
|
||||
let drawPile = [...state.player.drawPile];
|
||||
let discardPile = [...state.player.discardPile];
|
||||
const hand = [...state.player.hand];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (drawPile.length === 0) {
|
||||
drawPile = shuffle(discardPile);
|
||||
discardPile = [];
|
||||
}
|
||||
if (drawPile.length > 0) {
|
||||
hand.push(drawPile.pop());
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
player: { ...state.player, drawPile, hand, discardPile },
|
||||
};
|
||||
}
|
||||
|
||||
export function playCard(state, handIndex) {
|
||||
const cardId = state.player.hand[handIndex];
|
||||
const card = getCard(cardId);
|
||||
if (state.player.energy < card.cost) return null;
|
||||
|
||||
const hand = [...state.player.hand];
|
||||
hand.splice(handIndex, 1);
|
||||
const discardPile = [...state.player.discardPile, cardId];
|
||||
const energy = state.player.energy - card.cost;
|
||||
|
||||
let next = {
|
||||
...state,
|
||||
player: { ...state.player, hand, discardPile, energy },
|
||||
};
|
||||
|
||||
next = resolveEffects(next, card.effects, "player", "enemy");
|
||||
return next;
|
||||
}
|
||||
|
||||
export function endTurn(state) {
|
||||
return {
|
||||
...state,
|
||||
player: {
|
||||
...state.player,
|
||||
hand: [],
|
||||
discardPile: [...state.player.discardPile, ...state.player.hand],
|
||||
},
|
||||
combat: { ...state.combat, phase: "enemy_turn" },
|
||||
};
|
||||
}
|
||||
|
||||
function shuffle(arr) {
|
||||
const a = [...arr];
|
||||
for (let i = a.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[a[i], a[j]] = [a[j], a[i]];
|
||||
}
|
||||
return a;
|
||||
}
|
||||
74
src/state.test.js
Normal file
74
src/state.test.js
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import { describe, expect, test } from "bun:test";
|
||||
import { createCombatState, drawCards, endTurn, playCard } from "./state.js";
|
||||
|
||||
describe("createCombatState", () => {
|
||||
test("creates initial state with shuffled deck and correct values", () => {
|
||||
const state = createCombatState("ironclad", "jaw_worm");
|
||||
expect(state.player.hp).toBe(11);
|
||||
expect(state.player.maxHp).toBe(11);
|
||||
expect(state.player.energy).toBe(3);
|
||||
expect(state.player.block).toBe(0);
|
||||
expect(state.player.strength).toBe(0);
|
||||
expect(state.player.hand).toHaveLength(0);
|
||||
expect(state.player.drawPile).toHaveLength(10);
|
||||
expect(state.enemy.name).toBe("Jaw Worm");
|
||||
expect(state.enemy.hp).toBeGreaterThan(0);
|
||||
expect(state.combat.turn).toBe(1);
|
||||
expect(state.combat.phase).toBe("player_turn");
|
||||
});
|
||||
});
|
||||
|
||||
describe("drawCards", () => {
|
||||
test("moves cards from draw pile to hand", () => {
|
||||
const state = createCombatState("ironclad", "jaw_worm");
|
||||
const next = drawCards(state, 5);
|
||||
expect(next.player.hand).toHaveLength(5);
|
||||
expect(next.player.drawPile).toHaveLength(5);
|
||||
});
|
||||
|
||||
test("shuffles discard into draw when draw pile runs out", () => {
|
||||
let state = createCombatState("ironclad", "jaw_worm");
|
||||
state = {
|
||||
...state,
|
||||
player: {
|
||||
...state.player,
|
||||
drawPile: state.player.drawPile.slice(0, 2),
|
||||
discardPile: state.player.drawPile.slice(2),
|
||||
},
|
||||
};
|
||||
const next = drawCards(state, 5);
|
||||
expect(next.player.hand).toHaveLength(5);
|
||||
expect(next.player.discardPile).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("playCard", () => {
|
||||
test("deducts energy and moves card to discard", () => {
|
||||
let state = createCombatState("ironclad", "jaw_worm");
|
||||
state = drawCards(state, 5);
|
||||
const cardIndex = state.player.hand.indexOf("strike_r");
|
||||
const next = playCard(state, cardIndex);
|
||||
expect(next.player.energy).toBe(2);
|
||||
expect(next.player.hand).toHaveLength(4);
|
||||
});
|
||||
|
||||
test("returns null if not enough energy", () => {
|
||||
let state = createCombatState("ironclad", "jaw_worm");
|
||||
state = drawCards(state, 5);
|
||||
state = { ...state, player: { ...state.player, energy: 0 } };
|
||||
const cardIndex = state.player.hand.indexOf("strike_r");
|
||||
const result = playCard(state, cardIndex);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("endTurn", () => {
|
||||
test("discards hand and switches to enemy phase", () => {
|
||||
let state = createCombatState("ironclad", "jaw_worm");
|
||||
state = drawCards(state, 5);
|
||||
const next = endTurn(state);
|
||||
expect(next.player.hand).toHaveLength(0);
|
||||
expect(next.player.discardPile.length).toBeGreaterThan(0);
|
||||
expect(next.combat.phase).toBe("enemy_turn");
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue