Browse Source

Card updates

main
Stephanie Gredell 4 months ago
parent
commit
94676fae77
  1. 85
      src/data/cards.js
  2. 28
      src/engine/battle.js
  3. 21
      src/main.js
  4. 6
      style.css

85
src/data/cards.js

@ -187,7 +187,7 @@ export const CARDS = { @@ -187,7 +187,7 @@ export const CARDS = {
},
production_deploy: {
id: "production_deploy", name: "Production Deploy", cost: 3, type: "attack", text: "Deal 25. Lose 5 HP.",
id: "production_deploy", name: "Production Deploy", cost: 2, type: "attack", text: "Deal 25. Lose 5 HP.",
effect: (ctx) => {
ctx.deal(ctx.enemy, ctx.scalarFromWeak(25));
ctx.player.hp = Math.max(1, ctx.player.hp - 5);
@ -203,13 +203,87 @@ export const CARDS = { @@ -203,13 +203,87 @@ export const CARDS = {
ctx.log("The sugar crash hits hard, draining your energy!");
}
},
stack_overflow: {
id: "stack_overflow", name: "Stack Overflow", cost: 1, type: "attack", text: "Deal damage equal to cards in hand.",
effect: (ctx) => ctx.deal(ctx.enemy, ctx.scalarFromWeak(ctx.player.hand.length))
},
ctrl_z: {
id: "ctrl_z", name: "Ctrl+Z", cost: 1, type: "skill", text: "Return a random card from discard to hand.",
effect: (ctx) => {
if (ctx.player.discard.length > 0) {
const randomId = ctx.player.discard[Math.floor(Math.random() * ctx.player.discard.length)];
if (ctx.moveFromDiscardToHand(randomId)) {
ctx.log(`Ctrl+Z brings back ${CARDS[randomId].name}!`);
} else {
ctx.log("Ctrl+Z failed to undo anything.");
}
} else {
ctx.log("Nothing to undo!");
}
}
},
rubber_duck: {
id: "rubber_duck", name: "Rubber Duck Debug", cost: 0, type: "skill", text: "Draw 1. Reveal enemy intent.",
effect: (ctx) => {
ctx.draw(1);
const intent = ctx.enemy.intent;
ctx.log(`Rubber duck reveals: Enemy will ${intent.type} for ${intent.value || 'unknown'} next turn.`);
}
},
infinite_loop: {
id: "infinite_loop", name: "Infinite Loop", cost: 2, type: "skill", text: "Play the same card twice this turn. Exhaust.",
exhaust: true,
effect: (ctx) => {
if (ctx.lastCard && ctx.lastCard !== "infinite_loop") {
const card = ctx.player.hand.find(c => c.id === ctx.lastCard);
if (card) {
ctx.replayCard(card);
} else {
ctx.log("Infinite loop has nothing to repeat!");
}
} else {
ctx.log("Infinite loop needs a previous card to repeat!");
}
}
},
npm_audit: {
id: "npm_audit", name: "npm audit", cost: 1, type: "skill", text: "Gain 3 Block per curse in deck.",
effect: (ctx) => {
const curseCount = ctx.countCardType("curse");
const blockGain = curseCount * 3;
ctx.player.block += blockGain;
ctx.log(`npm audit found ${curseCount} vulnerabilities. Gain ${blockGain} Block.`);
}
},
git_push_force: {
id: "git_push_force", name: "git push --force", cost: 0, type: "attack", text: "Deal 15. Put random card from hand on top of draw pile.",
effect: (ctx) => {
ctx.deal(ctx.enemy, ctx.scalarFromWeak(15));
if (ctx.player.hand.length > 1) { // Don't remove this card itself
const otherCards = ctx.player.hand.filter(c => c.id !== "git_push_force");
if (otherCards.length > 0) {
const randomCard = otherCards[Math.floor(Math.random() * otherCards.length)];
const handIdx = ctx.player.hand.findIndex(c => c === randomCard);
const [card] = ctx.player.hand.splice(handIdx, 1);
ctx.player.draw.push(card.id);
ctx.log(`${card.name} was force-pushed back to your deck!`);
}
}
}
},
};
export const STARTER_DECK = [
"segfault", "raw_dog", "coffee_rush",
"skill_issue", "vibe_code", "404",
"git_commit", "ligma", "task_failed_successfully", "virgin"
"strike", "strike", "defend", "defend",
"segfault", "coffee_rush", "skill_issue", "git_commit",
"ligma", "raw_dog"
];
export const CARD_POOL = [
@ -217,5 +291,6 @@ export const CARD_POOL = [ @@ -217,5 +291,6 @@ export const CARD_POOL = [
"dark_mode", "object_object", "just_one_game", "colon_q", "vibe_code",
"raw_dog", "task_failed_successfully", "recursion", "git_commit", "memory_leak",
"code_review", "pair_programming", "hotfix", "ligma", "merge_conflict",
"virgin", "production_deploy"
"virgin", "production_deploy", "stack_overflow", "ctrl_z", "rubber_duck",
"infinite_loop", "npm_audit", "git_push_force"
];

28
src/engine/battle.js

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
import { ENEMIES } from "../data/enemies.js";
import { RELICS } from "../data/relics.js";
import { draw, endTurnDiscard, clamp } from "./core.js";
import { CARDS } from "../data/cards.js";
import { draw, endTurnDiscard, clamp, cloneCard } from "./core.js";
export function createBattle(ctx, enemyId) {
const enemyData = ENEMIES[enemyId];
@ -174,6 +175,31 @@ export function makeBattleContext(root) { @@ -174,6 +175,31 @@ export function makeBattleContext(root) {
showDamageNumber: root.showDamageNumber,
lastCard: null,
flags: {},
// New mechanics for advanced cards
moveFromDiscardToHand: (cardId) => {
const idx = root.player.discard.findIndex(id => id === cardId);
if (idx >= 0) {
const [id] = root.player.discard.splice(idx, 1);
const originalCard = CARDS[id];
if (originalCard) {
const clonedCard = cloneCard(originalCard);
root.player.hand.push(clonedCard);
return true;
}
}
return false;
},
countCardType: (type) => {
const allCards = [...root.player.deck, ...root.player.hand.map(c => c.id), ...root.player.draw, ...root.player.discard];
return allCards.filter(id => CARDS[id]?.type === type).length;
},
replayCard: (card) => {
// Temporarily replay a card without removing it from hand
if (typeof card.effect === 'function') {
card.effect(root);
root.log(`${card.name} is replayed!`);
}
},
};
}

21
src/main.js

@ -131,10 +131,6 @@ const root = { @@ -131,10 +131,6 @@ const root = {
},
save() {
if (this._battleInProgress) {
return;
}
try {
const saveData = {
player: this.player,
@ -142,6 +138,7 @@ const root = { @@ -142,6 +138,7 @@ const root = {
relicStates: this.relicStates,
completedNodes: this.completedNodes,
logs: this.logs.slice(-50), // Keep last 50 logs
battleInProgress: this._battleInProgress || false,
timestamp: Date.now()
};
localStorage.setItem('birthday-spire-save', JSON.stringify(saveData));
@ -160,6 +157,7 @@ const root = { @@ -160,6 +157,7 @@ const root = {
this.relicStates = data.relicStates || [];
this.completedNodes = data.completedNodes || [];
this.logs = data.logs || [];
this._battleInProgress = data.battleInProgress || false;
this.restoreCardEffects();
@ -382,12 +380,21 @@ function showCountdown(birthday) { @@ -382,12 +380,21 @@ function showCountdown(birthday) {
}
function loadNormalGame() {
// Clear old saves to prevent card ID conflicts after refactoring
root.clearOldSaves();
const hasLoadedData = root.load();
if (hasLoadedData) {
// If we were in a battle, resume it
if (root._battleInProgress) {
const node = root.map.nodes.find(n => n.id === root.nodeId);
if (node && (node.kind === "battle" || node.kind === "elite" || node.kind === "boss")) {
root.go(root.nodeId);
} else {
// Battle state inconsistent, go to map
root._battleInProgress = false;
renderMap(root);
}
} else {
renderMap(root);
}
} else {
root.reset();
}

6
style.css

@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
body {
margin: 0;
font-family: "JetBrains Mono", ui-monospace, Menlo, Consolas;
background: linear-gradient(180deg, var(--bg), var(--bg2));
background: #000;
color: var(--text)
}
@ -3364,8 +3364,8 @@ h3 { @@ -3364,8 +3364,8 @@ h3 {
.deck-stack-card .card-count-badge {
position: absolute;
top: -3px;
right: -3px;
top: 3px;
right: 5%;
background: #dc3545;
color: white;
border-radius: 50%;

Loading…
Cancel
Save