1 /* The person who associated a work with this deed has dedicated the
2 work to the public domain by waiving all of his or her rights to
3 the work worldwide under copyright law, including all related and
4 neighboring rights, to the extent allowed by law.
6 You can copy, modify, distribute and perform the work, even for
7 commercial purposes, all without asking permission.
9 See https://creativecommons.org/publicdomain/zero/1.0/ for details.
15 { name
: "The Greenskins are mobilized.",
16 effect
: "Greenskins gain a bonus of +1 Strength." },
17 { name
: "Big eye is watching you.",
18 effect
: "Bubbleyes gain a bonus of +1 Strength.",
19 requires
: ["bubbleyes"] },
20 { name
: "General invocation.",
21 effect
: "Demons gain a bonus of +1 Strength.",
22 requires
: ["demons"] },
23 { name
: "Stronger than ever.",
24 effect
: "Undead gain a bonus of +1 Strength.",
25 requires
: ["undead"] },
26 { name
: "Meeting with Abunakkashi.",
27 effect
: "Abunakkashii and his Offspring gain a bonus of +2 Strength.",
28 requires
: ["abunakkashii"],
30 { name
: "The start of a legend.",
31 effect
: "Abunakkashii and his Offspring gain a bonus of +1 Strength.",
32 requires
: ["abunakkashii"],
34 { name
: "Technological prowess.",
35 effect
: "Traps gain a bonus of +1 Strength.",
36 requires
: ["traps"] },
38 { name
: "Mental combat.",
39 effect
: "Psi monsters gain a bonus of +1 Strength.", },
40 { name
: "A cry in the night.",
41 action
: "An adventurer of your choice loses 1 Mana." },
43 action
: "Lose all your Mana tokens." },
44 { name
: "It's an ambush!",
45 action
: "Place a Tenacity token on each monster that does not have one." },
46 { name
: "The alarm is sounded.",
47 action
: "Place a Tenacity token on the weakest monster or monsters that do not already have one." },
48 { name
: "Flank attack.",
49 effect
: "Cards on either side corridor gain a bonus of +1 Strength.",
51 { name
: "Battle formation.",
52 effect
: "Cards in the central corridor(s) gain a bonus of +1 Strength.",
54 { name
: "Last bastion.",
55 effect
: "All cards gain a bonus of +1 Strength.",
58 { name
: "Destruction.",
59 action
: "All equipment and items are destroyed.",
61 { name
: "Epic combat.",
62 effect
: "The Final Monster gains a bonus of +3 Strength.",
64 { name
: "Red herring.",
65 action
: "Shuffle the remaining corridors together and redistribute the cards as if you were setting up the game." },
66 { name
: "The torch has gone out.",
67 action
: "The next dungeon card is placed face-down.", },
69 { name
: "Anti-psi zone.",
70 effect
: "You do not gain Mana tokens for the next three creatures you defeat, even if they have a Mana icon.",
73 { name
: "Necromancy.",
74 action
: "Return a random defeated dungeon card to the lowest corridor and shuffle it." },
75 { name
: "Reanimation.",
76 action
: "Return a random defeated dungeon card to the highest corridor and shuffle it." },
78 { name
: "Malediction.",
79 action
: "Discard a quest or a magic item." },
81 { name
: "False brethren.",
82 effect
: "All Demons gain Immunity 5 and Immunity 7.",
86 { name
: "Psi assault.",
87 effect
: "You cannot use ultimate powers.",
90 { name
: "Entering an anti-magic zone.",
91 effect
: "You cannot use ultimate powers.",
94 later
: { name
: "Leaving the anti-magic zone.",
95 effect
: "You may use ultimate powers again.",
96 clear
: ["ultimates"] }
99 // Events from Sean Allen's random scenario generator.
100 // http://boardgamegeek.com/filepage/57107/random-scenario-generator
101 { name
: "Fire from above.",
102 effect
: "Dragons gain a bonus of +1 Strength.",
103 requires
: ["dragons", "noncanonical"],
105 { name
: "Overwhelming fear.",
106 action
: "Lose 1 extra Courage token. (The dungeon does not gain another Fear token.)",
107 requires
: ["noncanonical"],
109 { name
: "Backs against the wall.",
110 effect
: "Any monster with a Tenacity token is also <strong>Fierce</strong>.",
111 requires
: ["noncanonical"],
113 { name
: "The tide has turned.",
114 effect
: "All monsters have <strong>Supremacy</strong>.",
115 requires
: ["noncanonical"],
118 action
: "Shuffle the remaining corridors together and redistribute the cards as if you were setting up the game.",
119 requires
: ["noncanonical"] },
120 { name
: "Into the darkness.",
121 effect
: "Choose one corridor. Its cards are now always placed face-down.",
122 requires
: ["noncanonical"] },
124 // Events of my own devising.
125 { name
: "Unwanted attention.",
126 effect
: "Unique monsters gain a bonus of +1 Strength.",
127 requires
: ["noncanonical"],
129 { name
: "Spiked the punch.",
130 effect
: "Greenskins have 1d8 Strength.",
131 requires
: ["noncanonical"],
133 { name
: "Twisty passages.",
134 effect
: "After defeating a card, roll a die. On an odd number its replacement is placed face-down.",
135 requires
: ["noncanonical"],
137 { name
: "Closer than you think.",
138 action
: "The dungeon gains another Fear token, and a new event occurs.",
139 requires
: ["noncanonical"],
140 another
: true, unique
: true },
141 { name
: "I thought you had it.",
142 action
: "Randomly discard four of your defeated dungeon cards.",
143 requires
: ["noncanonical"] },
144 { name
: "Adamantine armor.",
145 effect
: "All monsters gain <strong>Immunity 1</strong>.",
146 requires
: ["noncanonical"],
148 { name
: "Normative assumptions.",
149 effect
: "Effects concerning ♂ instead concern ♀, and vice versa.",
150 requires
: ["noncanonical"],
152 { name
: "Infighting.",
153 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>.",
154 requires
: ["noncanonical"],
157 effect
: "Demons have <strong>Veil of Shadow</strong>.",
158 requires
: ["noncanonical", "demons"],
160 { name
: "Camouflage.",
161 effect
: "Greenskins have <strong>Veil of Shadow</strong>.",
162 requires
: ["noncanonical"],
164 { name
: "Cryptic shades.",
165 effect
: "Undead have <strong>Veil of Shadow</strong>.",
166 requires
: ["noncanonical", "undead"],
168 { name
: "Lingering smoke.",
169 effect
: "Dragons have <strong>Veil of Shadow</strong>.",
170 requires
: ["noncanonical", "dragons"],
172 { name
: "Hypnotizing gaze.",
173 effect
: "Bubbleyes have <strong>Veil of Shadow</strong>.",
174 requires
: ["noncanonical", "bubbleyes"],
176 { name
: "Leeched power.",
177 action
: "A random adventurer loses a Mana token.",
178 requires
: ["noncanonical"] },
183 { name
: "Nothing happens." },
184 { name
: "A draft blows down the hallway.",
185 unique
: true, requires
: ["noncanonical"] },
186 { name
: "You sneeze.",
187 unique
: true, requires
: ["noncanonical"] },
188 { name
: "There's a skittering in the distance.",
189 unique
: true, requires
: ["noncanonical"] },
190 { name
: "The torch flickers.",
191 unique
: true, requires
: ["noncanonical"] },
192 { name
: "Shadows dance across the walls.",
193 unique
: true, requires
: ["noncanonical"] },
197 { name
: "It cuts both ways.",
198 action
: "Next time you roll a 1, put a Tenacity token on all face-up monsters and roll again.",
199 requires
: ["helpful"] },
200 { name
: "Breached their defense.",
201 action
: "Gain 2 Courage tokens and the dungeon gains 1 Fear token and the Fate Chart advances; <em>or</em> gain 1 Mana token.",
202 requires
: ["helpful"], unique
: true },
203 { name
: "Mana ritual.",
204 action
: "Discard up to 2 Mana tokens. For each one discarded, gain 1 Courage token.",
205 requires
: ["helpful"] }
208 var LOSE
= { name
: "Your adventuring party is defeated!" };
210 function randrange (a
, b
) {
211 return a
+ (Math
.random() * (b
- a
)) | 0;
214 function generate (flags
, events
, nop
) {
222 function pend (event
, i
) {
223 while (pending
[i
]) ++i
227 function canStillHappen (event
) {
228 return issubset(event
.requires
|| [], flags
)
229 && !(event
.unique
&& contains
.call(chosen
, event
))
230 && !(event
.lock
&& intersects(event
.lock
|| [], locks
));
233 for (i
= 0; i
< events
; ++i
) {
234 event
= pending
.shift()
235 || choice(EVENTS
.filter(canStillHappen
));
238 pend(event
.later
, (Math
.random() * chosen
[i
].duration
) | 0);
239 locks
= locks
.concat(event
.lock
|| [])
240 .filter(not(contains
), event
.clear
|| []);
243 for (i
= 0; i
< pending
.length
; ++i
)
245 chosen
.push(pending
[i
]);
248 for (i
= 0; i
< nop
/ 2; ++i
) {
249 var helpful
= HELPFUL
.filter(canStillHappen
);
250 var neutral
= NOP
.filter(canStillHappen
);
252 randrange(0, chosen
.length
), 0,
253 choice(helpful
.length
? helpful
: neutral
));
256 for (;i
< nop
; ++i
) {
257 var neutral
= NOP
.filter(canStillHappen
);
258 chosen
.splice(randrange(0, chosen
.length
), 0, choice(neutral
));
266 function toHTML (event
) {
267 return ["<span class=fate-name>" + event
.name
+ "</span>",
269 ? "<span class=fate-action>" + event
.action
+ "</span>"
272 ? "<span class=fate-effect>" + event
.effect
+ "</span>"
277 var THEME
= ("h1 { color: hsl(XXX, 25%, 75%); }\n\
278 h2, .button, select { border-color: hsl(XXX, 30%, 85%); }\n\
280 a:link, a:visited, a:active {\n\
281 color: hsl(XXX, 25%, 50%);\n\
284 select, .button, tbody tr:nth-last-child(odd) {\n\
285 background-color: hsl(XXX, 30%, 85%);\n\
289 select:hover, select:focus,\n\
290 .button:hover, .button:focus {\n\
291 border-color: hsl(XXX, 25%, 50%);\n\
294 function randomizeName () {
295 var name
= generateName();
296 document
.getElementById("name").textContent
= name
;
297 document
.title
= document
.title
.replace(/[^-]*-/, name
+ " -");
298 document
.head
.lastChild
.textContent
= THEME
.replace(
299 /XXX/g, (Math
.random() * 256) | 0);
305 function generateScenario () {
306 var parts
= location
.hash
.slice(1).split(',');
307 events
= generate(parts
, parts
.shift() | 0, parts
.shift() | 0);
308 style
= document
.createElement("style");
309 document
.head
.appendChild(style
);
313 function getEvents (matcher
) {
314 return EVENTS
.filter(matcher
);
317 function wrapRow (row
) {
318 return "<tr><td>" + row
+ "</td></tr>";
321 function iscanonical (event
) {
322 return !isnoncanonical(event
);
324 function isnoncanonical (event
) {
325 return ~(event
.requires
|| []).indexOf('noncanonical');
328 function canonicalToHTML (sender
) {
329 sender
.innerHTML
= EVENTS
.filter(iscanonical
)
330 .map(toHTML
).sort().map(wrapRow
).join('');
333 function noncanonicalToHTML (sender
) {
334 sender
.innerHTML
= EVENTS
.filter(isnoncanonical
)
335 .map(toHTML
).sort().map(wrapRow
).join('');
338 function nextEvent (sender
) {
339 if (!events
.length
) {
344 var event
= events
.shift();
345 var body
= document
.querySelector("#fate tbody");
346 var fate
= body
.children
.length
+ 1;
347 var tr
= document
.createElement('tr');
348 tr
.innerHTML
= "<td><div>" + fate
+ "</div></td>"
349 + "<td><div>" + toHTML(event
) + "</div></td>";
350 body
.insertBefore(tr
, body
.firstChild
);
352 if (events
.length
=== 0) {
353 sender
.textContent
= "Try Again ▲";
357 setTimeout(function () { nextEvent(sender
); }, 333);