밴딧 공용 효과와 문서 정리
This commit is contained in:
@@ -13,6 +13,85 @@ test('rarityForRoll: 70/25/5 경계 (Lua OfferReward 미러)', () => {
|
||||
assert.equal(rarityForRoll(100), 'legend');
|
||||
});
|
||||
|
||||
test("simulateCombat: nextTurnBlock grants block on the following turn", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
GuardLater: { name: "예약 방어", cost: 0, kind: "Skill", nextTurnBlock: 4 },
|
||||
Pass: { name: "대기", cost: 99, kind: "Skill", block: 0 },
|
||||
},
|
||||
starterDeck: ["GuardLater", "Pass"],
|
||||
monsters: [{ name: "Dummy", maxHp: 99, intents: [{ kind: "Attack", value: 3 }, { kind: "Attack", value: 3 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0.999999);
|
||||
assert.equal(r.win, false);
|
||||
assert.equal(r.draw, true);
|
||||
assert.equal(r.playerHpRemaining, 77);
|
||||
});
|
||||
|
||||
test("simulateCombat: nextTurnDraw draws extra cards next turn", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
Setup: { name: "설치", cost: 0, kind: "Skill", nextTurnDraw: 2 },
|
||||
Hit1: { name: "타격1", cost: 0, kind: "Attack", damage: 3 },
|
||||
Hit2: { name: "타격2", cost: 0, kind: "Attack", damage: 3 },
|
||||
Pass1: { name: "대기1", cost: 99, kind: "Skill", block: 0 },
|
||||
Pass2: { name: "대기2", cost: 99, kind: "Skill", block: 0 },
|
||||
Pass3: { name: "대기3", cost: 99, kind: "Skill", block: 0 },
|
||||
Pass4: { name: "대기4", cost: 99, kind: "Skill", block: 0 },
|
||||
},
|
||||
starterDeck: ["Hit1", "Hit2", "Pass1", "Pass2", "Pass3", "Pass4", "Setup"],
|
||||
monsters: [{ name: "Dummy", maxHp: 6, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0.999999);
|
||||
assert.equal(r.win, true);
|
||||
assert.equal(r.turns, 2);
|
||||
});
|
||||
|
||||
test("simulateCombat: nextTurnKeepBlock preserves current block", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
BlurLater: { name: "흐릿함", cost: 0, kind: "Skill", block: 5, nextTurnKeepBlock: true },
|
||||
Pass: { name: "대기", cost: 99, kind: "Skill", block: 0 },
|
||||
},
|
||||
starterDeck: ["BlurLater", "Pass"],
|
||||
monsters: [{ name: "Dummy", maxHp: 99, intents: [{ kind: "Attack", value: 3 }, { kind: "Attack", value: 3 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0.999999);
|
||||
assert.equal(r.win, false);
|
||||
assert.equal(r.draw, true);
|
||||
assert.equal(r.playerHpRemaining, 80);
|
||||
});
|
||||
|
||||
test("simulateCombat: nextTurnAttackMultiplier boosts attacks next turn", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
Prep: { name: "그림자 걸음", cost: 0, kind: "Skill", nextTurnAttackMultiplier: 2 },
|
||||
Hit: { name: "타격", cost: 0, kind: "Attack", damage: 3 },
|
||||
Pass: { name: "대기", cost: 99, kind: "Skill", block: 0 },
|
||||
},
|
||||
starterDeck: ["Prep", "Pass", "Hit"],
|
||||
monsters: [{ name: "Dummy", maxHp: 6, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0.999999);
|
||||
assert.equal(r.win, true);
|
||||
assert.equal(r.turns, 2);
|
||||
});
|
||||
|
||||
test("simulateCombat: nextTurnSelectHandCard queues selected copies for next turn", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
Nightmare: { name: "악몽", cost: 0, kind: "Skill", nextTurnCopies: 3, nextTurnSelectHandCard: true },
|
||||
Hit: { name: "타격", cost: 0, kind: "Attack", damage: 2 },
|
||||
Pass: { name: "대기", cost: 99, kind: "Skill", block: 0 },
|
||||
},
|
||||
starterDeck: ["Pass", "Nightmare", "Hit"],
|
||||
monsters: [{ name: "Dummy", maxHp: 6, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0.999999);
|
||||
assert.equal(r.win, true);
|
||||
assert.equal(r.turns, 4);
|
||||
});
|
||||
|
||||
test('applyDamage: 방어 우선 차감 후 hp', () => {
|
||||
assert.deepEqual(applyDamage(80, 0, 10), { hp: 70, block: 0 });
|
||||
assert.deepEqual(applyDamage(80, 5, 10), { hp: 75, block: 0 });
|
||||
@@ -461,3 +540,147 @@ test("simulateCombat: addShiv creates shuriken cards in hand", () => {
|
||||
assert.equal(r.win, true);
|
||||
assert.equal(r.turns, 1);
|
||||
});
|
||||
|
||||
test("simulateCombat: innate cards are drawn into the opening hand first", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
Backstab: { name: "배신", cost: 0, kind: "Attack", damage: 11, innate: true, exhaust: true },
|
||||
Pass1: { name: "대기1", cost: 99, kind: "Skill", block: 0 },
|
||||
Pass2: { name: "대기2", cost: 99, kind: "Skill", block: 0 },
|
||||
Pass3: { name: "대기3", cost: 99, kind: "Skill", block: 0 },
|
||||
Pass4: { name: "대기4", cost: 99, kind: "Skill", block: 0 },
|
||||
Pass5: { name: "대기5", cost: 99, kind: "Skill", block: 0 },
|
||||
},
|
||||
starterDeck: ["Pass1", "Pass2", "Pass3", "Pass4", "Pass5", "Backstab"],
|
||||
monsters: [{ name: "Dummy", maxHp: 11, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0);
|
||||
assert.equal(r.win, true);
|
||||
assert.equal(r.turns, 1);
|
||||
});
|
||||
|
||||
test("simulateCombat: GrandFinale waits until draw pile is empty", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
Finale: { name: "피날레", cost: 0, kind: "Attack", damage: 60, aoe: true, playableWhenDrawPileEmpty: true },
|
||||
Pass1: { name: "대기1", cost: 99, kind: "Skill", block: 0 },
|
||||
Pass2: { name: "대기2", cost: 99, kind: "Skill", block: 0 },
|
||||
Pass3: { name: "대기3", cost: 99, kind: "Skill", block: 0 },
|
||||
Pass4: { name: "대기4", cost: 99, kind: "Skill", block: 0 },
|
||||
Pass5: { name: "대기5", cost: 99, kind: "Skill", block: 0 },
|
||||
},
|
||||
starterDeck: ["Pass1", "Pass2", "Pass3", "Pass4", "Pass5", "Finale"],
|
||||
monsters: [{ name: "Dummy", maxHp: 60, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0);
|
||||
assert.equal(r.win, false);
|
||||
assert.equal(r.draw, true);
|
||||
});
|
||||
|
||||
test("simulateCombat: turnStartDraw and turnStartDiscard powers resolve at turn start", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
Tool: { name: "작업 도구", cost: 0, kind: "Power", turnStartDraw: 1, turnStartDiscard: 1 },
|
||||
Hit1: { name: "타격1", cost: 0, kind: "Attack", damage: 3 },
|
||||
Hit2: { name: "타격2", cost: 0, kind: "Attack", damage: 3 },
|
||||
Pass1: { name: "대기1", cost: 99, kind: "Skill", block: 0 },
|
||||
Pass2: { name: "대기2", cost: 99, kind: "Skill", block: 0 },
|
||||
Pass3: { name: "대기3", cost: 99, kind: "Skill", block: 0 },
|
||||
},
|
||||
starterDeck: ["Tool", "Pass1", "Pass2", "Pass3", "Hit1", "Hit2"],
|
||||
monsters: [{ name: "Dummy", maxHp: 6, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0);
|
||||
assert.equal(r.win, true);
|
||||
assert.equal(r.turns, 1);
|
||||
});
|
||||
|
||||
test("chooseAction: GrandFinale is blocked until draw pile is empty", () => {
|
||||
const cards = {
|
||||
Finale: { name: "피날레", cost: 0, kind: "Attack", damage: 60, playableWhenDrawPileEmpty: true },
|
||||
Defend: { name: "방어", cost: 1, kind: "Skill", block: 5 },
|
||||
};
|
||||
assert.equal(chooseAction(["Finale", "Defend"], cards, 3, { drawPileCount: 1 }), 1);
|
||||
assert.equal(chooseAction(["Finale"], cards, 3, { drawPileCount: 0 }), 0);
|
||||
});
|
||||
|
||||
test("simulateCombat: damagePerAttackPlayedThisTurn scales Finisher", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
Hit: { name: "타격", cost: 0, kind: "Attack", damage: 6 },
|
||||
Finisher: { name: "마무리", cost: 0, kind: "Attack", damage: 0, damagePerAttackPlayedThisTurn: 6 },
|
||||
},
|
||||
starterDeck: ["Hit", "Finisher"],
|
||||
monsters: [{ name: "Dummy", maxHp: 12, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0);
|
||||
assert.equal(r.win, true);
|
||||
assert.equal(r.turns, 2);
|
||||
});
|
||||
|
||||
test("simulateCombat: damagePerOtherHandCard and damagePerSkillInHand are applied", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
Precise: { name: "정밀", cost: 0, kind: "Attack", damage: 13, damagePerOtherHandCard: -2 },
|
||||
Flechettes: { name: "프레췌", cost: 0, kind: "Attack", damage: 0, damagePerSkillInHand: 5 },
|
||||
Skill1: { name: "스킬1", cost: 99, kind: "Skill", block: 0 },
|
||||
Skill2: { name: "스킬2", cost: 99, kind: "Skill", block: 0 },
|
||||
Blank: { name: "공백", cost: 99, kind: "Skill", block: 0 },
|
||||
},
|
||||
starterDeck: ["Skill1", "Skill2", "Blank", "Precise", "Flechettes"],
|
||||
monsters: [{ name: "Dummy", maxHp: 21, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0);
|
||||
assert.equal(r.win, true);
|
||||
assert.equal(r.turns, 5);
|
||||
});
|
||||
|
||||
test("simulateCombat: damagePerDiscardedThisTurn and bonusHitsWhenOtherHandAtLeast work", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
Toss: { name: "버리기", cost: 0, kind: "Skill", discard: 1 },
|
||||
Memento: { name: "메멘토", cost: 0, kind: "Attack", damage: 9, damagePerDiscardedThisTurn: 4 },
|
||||
Follow: { name: "완수", cost: 0, kind: "Attack", damage: 7, otherHandAtLeast: 2, bonusHitsWhenOtherHandAtLeast: 1 },
|
||||
Blank1: { name: "공백1", cost: 99, kind: "Skill", block: 0 },
|
||||
Blank2: { name: "공백2", cost: 99, kind: "Skill", block: 0 },
|
||||
},
|
||||
starterDeck: ["Toss", "Memento", "Follow", "Blank1", "Blank2"],
|
||||
monsters: [{ name: "Dummy", maxHp: 27, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0.999999);
|
||||
assert.equal(r.win, true);
|
||||
assert.equal(r.turns, 2);
|
||||
});
|
||||
|
||||
test("simulateCombat: gainEnergy, drawUntilHandSize, and drawPerDiscarded are applied", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
Adrenaline: { name: "Adrenaline", cost: 0, kind: "Skill", gainEnergy: 1, draw: 2, exhaust: true },
|
||||
Expertise: { name: "Expertise", cost: 1, kind: "Skill", drawUntilHandSize: 6 },
|
||||
Gamble: { name: "Gamble", cost: 0, kind: "Skill", discardAll: true, drawPerDiscarded: 1, exhaust: true },
|
||||
Tactician: { name: "Tactician", cost: 99, kind: "Skill", gainEnergy: 1, sly: true },
|
||||
Hit1: { name: "Hit1", cost: 1, kind: "Attack", damage: 6 },
|
||||
Hit2: { name: "Hit2", cost: 1, kind: "Attack", damage: 6 },
|
||||
Hit3: { name: "Hit3", cost: 1, kind: "Attack", damage: 6 },
|
||||
},
|
||||
starterDeck: ["Adrenaline", "Expertise", "Gamble", "Tactician", "Hit1", "Hit2", "Hit3"],
|
||||
monsters: [{ name: "Dummy", maxHp: 18, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0);
|
||||
assert.equal(r.win, true);
|
||||
assert.equal(r.turns, 1);
|
||||
});
|
||||
|
||||
test("simulateCombat: cardPlayedBlock grants block whenever a card is played", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
After: { name: "Afterimage", cost: 1, kind: "Power", cardPlayedBlock: 1 },
|
||||
Hit: { name: "Hit", cost: 1, kind: "Attack", damage: 1 },
|
||||
},
|
||||
starterDeck: ["After", "Hit", "Hit", "Hit", "Hit"],
|
||||
monsters: [{ name: "Dummy", maxHp: 9999, intents: [{ kind: "Attack", value: 1 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0.999999);
|
||||
assert.equal(r.draw, true);
|
||||
assert.equal(r.playerHpRemaining, 80);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user