X-Git-Url: https://git.yukkurigames.com/?p=heroik.git;a=blobdiff_plain;f=scenarios.js;h=12bfa3e857c16bd2559200fbb1d6357711478ad8;hp=dfef81a03b7b4dfe0d716fef7c9005e4cd488a99;hb=80f79d9125ef2ed4a368ce3a17f7dd81ba95bd30;hpb=c8be41dab411c4c11712d9d2b37879ea45b1efdc diff --git a/scenarios.js b/scenarios.js index dfef81a..12bfa3e 100644 --- a/scenarios.js +++ b/scenarios.js @@ -27,6 +27,10 @@ var EVENTS = [ effect: "Abunakkashii and his Offspring gain a bonus of +2 Strength.", requires: ["abunakkashii"], unique: true }, + { name: "The start of a legend.", + effect: "Abunakkashii and his Offspring gain a bonus of +1 Strength.", + requires: ["abunakkashii"], + unique: true }, { name: "Technological prowess.", effect: "Traps gain a bonus of +1 Strength.", requires: ["traps"] }, @@ -35,6 +39,8 @@ var EVENTS = [ effect: "Psi monsters gain a bonus of +1 Strength.", }, { name: "A cry in the night.", action: "An adventurer of your choice loses 1 Mana." }, + { name: "Vague psi.", + action: "Lose all your Mana tokens." }, { name: "It's an ambush!", action: "Place a Tenacity token on each monster that does not have one." }, { name: "The alarm is sounded.", @@ -55,45 +61,66 @@ var EVENTS = [ { name: "Epic combat.", effect: "The Final Monster gains a bonus of +3 Strength.", unique: true }, - + { name: "Red herring.", + action: "Shuffle the remaining corridors together and redistribute the cards as if you were setting up the game." }, + { name: "The torch has gone out.", + action: "The next dungeon card is placed face-down.", }, + + { name: "Anti-psi zone.", + effect: "You do not gain Mana tokens for the next three creatures you defeat, even if they have a Mana icon.", + unique: true }, + + { name: "Necromancy.", + action: "Return a random defeated dungeon card to the lowest corridor and shuffle it." }, + { name: "Reanimation.", + action: "Return a random defeated dungeon card to the highest corridor and shuffle it." }, + + { name: "Malediction.", + action: "Discard a quest or a magic item." }, + + { name: "False brethren.", + effect: "All Demons gain Immunity 5 and Immunity 7.", + requires: ["demons"], + unique: true }, + + { name: "Psi assault.", + effect: "You cannot use ultimate powers.", + lock: ["ultimates"], + unique: true }, + { name: "Entering an anti-magic zone.", + effect: "You cannot use ultimate powers.", + lock: ["ultimates"], + duration: 3, + later: { name: "Leaving the anti-magic zone.", + effect: "You may use ultimate powers again.", + clear: ["ultimates"] } + }, + // Events from Sean Allen's random scenario generator. // http://boardgamegeek.com/filepage/57107/random-scenario-generator { name: "Fire from above.", - effect: "Dragons gains a bonus of +1 Strength.", + effect: "Dragons gain a bonus of +1 Strength.", requires: ["dragons", "noncanonical"], unique: true }, - { name: "Bad dreams.", + { name: "Overwhelming fear.", action: "Lose 1 extra Courage token. (The dungeon does not gain another Fear token.)", requires: ["noncanonical"], unique: true }, { name: "Backs against the wall.", - effect: "Any monster with a Tenacity token is also Fierce.", + effect: "Any monster with a Tenacity token is also Fierce.", requires: ["noncanonical"], unique: true }, - { name: "Dead end.", - action: "Shuffle the remaining corridors together and redistribute the cards as if you were setting up the game.", - requires: ["noncanonical"] }, - { name: "Surrounded.", - effect: "All monsters have Supremacy.", + { name: "The tide has turned.", + effect: "All monsters have Supremacy.", requires: ["noncanonical"], unique: true }, - - // Events from Stephane Renard's scenario. - // http://docfox.free.fr/spip.php?article129 - { name: "What was that?", - action: "The next dungeon card is placed face-down.", - requires: ["noncanonical"] }, - { name: "Malediction.", - action: "Discard all quests.", - requires: ["noncanonical"], - unique: true }, - { name: "Reanimation.", - action: "Return a random spent dungeon card to the smallest corridor and shuffle it. (If you haven't spent any cards, nothing happens.)", + { name: "Lost.", + action: "Shuffle the remaining corridors together and redistribute the cards as if you were setting up the game.", requires: ["noncanonical"] }, - { name: "Zone of silence.", - effect: "Temporary and Ultimate Powers cannot be used until you reveal a new card.", + { name: "Into the darkness.", + effect: "Choose one corridor. Its cards are now always placed face-down.", requires: ["noncanonical"] }, - + // Events of my own devising. { name: "Unwanted attention.", effect: "Unique monsters gain a bonus of +1 Strength.", @@ -103,7 +130,7 @@ var EVENTS = [ effect: "Greenskins have 1d8 Strength.", requires: ["noncanonical"], unique: true }, - { name: "Dropped the torch.", + { name: "Twisty passages.", effect: "After defeating a card, roll a die. On an odd number its replacement is placed face-down.", requires: ["noncanonical"], unique: true }, @@ -115,16 +142,69 @@ var EVENTS = [ action: "Randomly discard four of your defeated dungeon cards.", requires: ["noncanonical"] }, { name: "Adamantine armor.", - effect: "All monsters gain Immunity 1.", + effect: "All monsters gain Immunity 1.", requires: ["noncanonical"], unique: true }, { name: "Normative assumptions.", effect: "Effects concerning ♂ instead concern ♀, and vice versa.", requires: ["noncanonical"], unique: true }, + { name: "Infighting.", + effect: "Greenskins have Undead +1, Demons -1. Demons have Greenskins +1, Undead -1. Undead have Demons +1, Greenskins -1.", + requires: ["noncanonical", "demons", "undead"], + unique: true }, + { name: "New moon.", + effect: "Demons have Veil of Shadow.", + requires: ["noncanonical", "demons"], + unique: true }, + { name: "Camouflage.", + effect: "Greenskins have Veil of Shadow.", + requires: ["noncanonical"], + unique: true }, + { name: "Cryptic shades.", + effect: "Undead have Veil of Shadow.", + requires: ["noncanonical", "undead"], + unique: true }, + { name: "Lingering smoke.", + effect: "Dragons have Veil of Shadow.", + requires: ["noncanonical", "dragons"], + unique: true }, + { name: "Hypnotizing gaze.", + effect: "Bubbleyes have Veil of Shadow.", + requires: ["noncanonical", "bubbleyes"], + unique: true }, + { name: "Leeched power.", + action: "A random adventurer loses a Mana token.", + requires: ["noncanonical"] }, + ]; -var NOTHING = { name: "Nothing happens." }; +var NOP = [ + { name: "Nothing happens." }, + { name: "A draft blows down the hallway.", + unique: true, requires: ["noncanonical"] }, + { name: "You sneeze.", + unique: true, requires: ["noncanonical"] }, + { name: "There's a skittering in the distance.", + unique: true, requires: ["noncanonical"] }, + { name: "The torch flickers.", + unique: true, requires: ["noncanonical"] }, + { name: "Shadows dance across the walls.", + unique: true, requires: ["noncanonical"] }, +]; + +var HELPFUL = [ + { name: "It cuts both ways.", + action: "Next time you roll a 1, put a Tenacity token on all face-up monsters and roll again.", + requires: ["helpful"] }, + { name: "Breached their defense.", + action: "Gain 2 Courage tokens and the dungeon gains 1 Fear token and the Fate Chart advances; or gain 1 Mana token.", + requires: ["helpful"], unique: true }, + { name: "Mana ritual.", + action: "Discard up to 2 Mana tokens. For each one discarded, gain 1 Courage token.", + requires: ["helpful"] } +]; + var LOSE = { name: "Your adventuring party is defeated!" }; function randrange (a, b) { @@ -135,16 +215,49 @@ function generate (flags, events, nop) { var chosen = []; var i; + var pending = []; + var locks = []; + var event; + + function pend (event, i) { + while (pending[i]) ++i + pending[i] = event; + } + function canStillHappen (event) { return issubset(event.requires || [], flags) - && !(event.unique && contains.call(chosen, event)); + && !(event.unique && contains.call(chosen, event)) + && !(event.lock && intersects(event.lock || [], locks)); } - for (i = 0; i < events; ++i) - chosen.push(choice(EVENTS.filter(canStillHappen))); + for (i = 0; i < events; ++i) { + event = pending.shift() + || choice(EVENTS.filter(canStillHappen)); + chosen.push(event); + if (event.later) + pend(event.later, (Math.random() * chosen[i].duration) | 0); + locks = locks.concat(event.lock || []) + .filter(not(contains), event.clear || []); + } - for (i = 0; i < nop; ++i) - chosen.splice(randrange(0, chosen.length), 0, NOTHING); + for (i = 0; i < pending.length; ++i) + if (pending[i]) + chosen.push(pending[i]); + + + for (i = 0; i < nop / 2; ++i) { + var helpful = HELPFUL.filter(canStillHappen); + var neutral = NOP.filter(canStillHappen); + chosen.splice( + randrange(0, chosen.length), 0, + choice(helpful.length ? helpful : neutral)); + + } + for (;i < nop; ++i) { + var neutral = NOP.filter(canStillHappen); + chosen.splice(randrange(0, chosen.length), 0, choice(neutral)); + + } chosen.push(LOSE); return chosen; @@ -188,13 +301,39 @@ function randomizeName () { var events = []; var style; -window.addEventListener('DOMContentLoaded', function () { + +function generateScenario () { var parts = location.hash.slice(1).split(','); events = generate(parts, parts.shift() | 0, parts.shift() | 0); style = document.createElement("style"); document.head.appendChild(style); randomizeName(); -}); +} + +function getEvents (matcher) { + return EVENTS.filter(matcher); +} + +function wrapRow (row) { + return "" + row + ""; +} + +function iscanonical (event) { + return !isnoncanonical(event); +} +function isnoncanonical (event) { + return ~(event.requires || []).indexOf('noncanonical'); +} + +function canonicalToHTML (sender) { + sender.innerHTML = EVENTS.filter(iscanonical) + .map(toHTML).sort().map(wrapRow).join(''); +} + +function noncanonicalToHTML (sender) { + sender.innerHTML = EVENTS.filter(isnoncanonical) + .map(toHTML).sort().map(wrapRow).join(''); +} function nextEvent (sender) { if (!events.length) {