/* The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. See https://creativecommons.org/publicdomain/zero/1.0/ for details. */ "use strict"; var EVENTS = [ { name: "The Greenskins are mobilized.", effect: "Greenskins gain a bonus of +1 Strength." }, { name: "Big eye is watching you.", effect: "Bubbleyes gain a bonus of +1 Strength.", requires: ["bubbleyes"] }, { name: "General invocation.", effect: "Demons gain a bonus of +1 Strength.", requires: ["demons"] }, { name: "Stronger than ever.", effect: "Undead gain a bonus of +1 Strength.", requires: ["undead"] }, { name: "Meeting with Abunakkashi.", 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"] }, { name: "Mental combat.", 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.", action: "Place a Tenacity token on the weakest monster or monsters that do not already have one." }, { name: "Flank attack.", effect: "Cards on either side corridor gain a bonus of +1 Strength.", unique: true }, { name: "Battle formation.", effect: "Cards in the central corridor(s) gain a bonus of +1 Strength.", unique: true }, { name: "Last bastion.", effect: "All cards gain a bonus of +1 Strength.", unique: true }, { name: "Destruction.", action: "All equipment and items are destroyed.", unique: true }, { 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 gain a bonus of +1 Strength.", requires: ["dragons", "noncanonical"], unique: true }, { 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.", requires: ["noncanonical"], unique: true }, { name: "The tide has turned.", effect: "All monsters have Supremacy.", requires: ["noncanonical"], unique: true }, { name: "Lost.", action: "Shuffle the remaining corridors together and redistribute the cards as if you were setting up the game.", requires: ["noncanonical"] }, { 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.", requires: ["noncanonical"], unique: true }, { name: "Spiked the punch.", effect: "Greenskins have 1d8 Strength.", requires: ["noncanonical"], unique: true }, { 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 }, { name: "Closer than you think.", action: "The dungeon gains another Fear token, and a new event occurs.", requires: ["noncanonical"], another: true, unique: true }, { name: "I thought you had it.", action: "Randomly discard four of your defeated dungeon cards.", requires: ["noncanonical"] }, { name: "Adamantine armor.", 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"], 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 NOP = [ { name: "Nothing happens." }, { name: "A draft blows down the hallway.", requires: ["noncanonical"] }, { name: "You sneeze.", requires: ["noncanonical"] }, { name: "There's a skittering in the distance.", 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) { return a + (Math.random() * (b - a)) | 0; } 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.lock && intersects(event.lock || [], locks)); } 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 < 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; } function toHTML (event) { return ["" + event.name + "", event.action ? "" + event.action + "" : "", event.effect ? "" + event.effect + "" : "", ].join(" "); } var THEME = ("h1 { color: hsl(XXX, 25%, 75%); }\n\ h2, .button, select { border-color: hsl(XXX, 30%, 85%); }\n\ \n\ a:link, a:visited, a:active {\n\ color: hsl(XXX, 25%, 50%);\n\ }\n\ \n\ select, .button, tbody tr:nth-last-child(odd) {\n\ background-color: hsl(XXX, 30%, 85%);\n\ }\n\ \n\ h1,\n\ select:hover, select:focus,\n\ .button:hover, .button:focus {\n\ border-color: hsl(XXX, 25%, 50%);\n\ }"); function randomizeName () { var name = generateName(); document.getElementById("name").textContent = name; document.title = document.title.replace(/[^-]*-/, name + " -"); document.head.lastChild.textContent = THEME.replace( /XXX/g, (Math.random() * 256) | 0); } var events = []; var style; 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 "