{ 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.",
+ { name: "Encounter 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: "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.",
unique: true },
{ name: "Destruction.",
- action: "All equipment and items are destroyed.",
+ action: "All of your weapons and armor 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 object." },
+
+ { 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 <strong>Fierce</strong>.",
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 <strong>Supremacy</strong>.",
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.",
+ { name: "Into the darkness.",
+ effect: "Choose one corridor. Its cards are now always 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.)",
- requires: ["noncanonical"] },
- { name: "Zone of silence.",
- effect: "Temporary and Ultimate Powers cannot be used until you reveal a new card.",
- requires: ["noncanonical"] },
-
+
// Events of my own devising.
{ name: "Unwanted attention.",
- effect: "Unique monsters gain a bonus of +2 Strength.",
+ 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: "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 },
action: "Randomly discard four of your defeated dungeon cards.",
requires: ["noncanonical"] },
{ name: "Adamantine armor.",
- effect: "All monsters gain Immunity 1.",
+ effect: "All monsters gain <strong>Immunity 1</strong>.",
requires: ["noncanonical"],
unique: true },
{ name: "Normative assumptions.",
effect: "Effects concerning ♂ instead concern ♀, and vice versa.",
requires: ["noncanonical"],
unique: true },
+ { name: "Infighting.",
+ effect: "Greenskins have <strong>Undead +1</strong>, <strong>Demons -1</strong>. Demons have <strong>Greenskins +1</strong>, <strong>Undead -1</strong>. Undead have <strong>Demons +1</strong>, <strong>Greenskins -1</strong>.",
+ requires: ["noncanonical", "demons", "undead"],
+ unique: true },
+ { name: "New moon.",
+ effect: "Demons have <strong>Shrouded</strong>.",
+ requires: ["noncanonical", "demons"],
+ unique: true },
+ { name: "Camouflage.",
+ effect: "Greenskins have <strong>Shrouded</strong>.",
+ requires: ["noncanonical"],
+ unique: true },
+ { name: "Cryptic shades.",
+ effect: "Undead have <strong>Shrouded</strong>.",
+ requires: ["noncanonical", "undead"],
+ unique: true },
+ { name: "Lingering smoke.",
+ effect: "Dragons have <strong>Shrouded</strong>.",
+ requires: ["noncanonical", "dragons"],
+ unique: true },
+ { name: "Hypnotizing gaze.",
+ effect: "Bubbleyes have <strong>Shrouded</strong>.",
+ 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; <em>or</em> 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) {
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;
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 "<tr><td>" + row + "</td></tr>";
+}
+
+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) {