Add README.
[ogre.git] / ogre.js
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.
5
6 You can copy, modify, distribute and perform the work, even for
7 commercial purposes, all without asking permission.
8
9 See https://creativecommons.org/publicdomain/zero/1.0/ for details.
10 */
11
12 "use strict";
13
14 if (!String.prototype.repeat)
15 Object.defineProperty(String.prototype, "repeat", {
16 value: function (count) {
17 var string = this.toString();
18 var result = '';
19 var n = count | 0;
20 while (n) {
21 if (n % 2 === 1)
22 result += string;
23 if (n > 1)
24 string += string;
25 n >>= 1;
26 }
27 return result;
28 }
29 });
30
31 if (!Array.prototype.fill)
32 Object.defineProperty(Array.prototype, "fill", {
33 value: function (value) {
34 var beg = arguments.length > 1 ? +arguments[1] : 0;
35 var end = arguments.length > 2 ? +arguments[2] : this.length;
36 if (beg < 0) beg += this.length;
37 if (end < 0) end += this.length;
38 for (var i = beg; i < end; ++i)
39 this[i] = value;
40 return this;
41 }
42 });
43
44 if (!Element.prototype.matches)
45 Object.defineProperty(Element.prototype, "matches", {
46 value: Element.prototype.matchesSelector
47 || Element.prototype.mozMatchesSelector
48 || Element.prototype.webkitMatchesSelector
49 });
50
51 function removeUnit (element) {
52 element.parentNode.removeChild(element);
53 }
54
55 var EMPTY = "☐";
56 var CHECK = "☒";
57 var FIRST_EMPTY = /☐/;
58 var LAST_TICKED = /☒([^☒]*)$/;
59
60 function choice (a) {
61 return a[(Math.random() * a.length) | 0];
62 }
63
64 function cap (s) {
65 return s && s[0].toUpperCase() + s.slice(1);
66 }
67
68 function letters (n) {
69 var r = "";
70 n = n || 1;
71 while (n-- > 0)
72 r += choice("abcdefghijklmnopqrstuvwxyz");
73 return r;
74 }
75
76 function range (a, b) {
77 return a + (Math.random() * (b - a)) | 0;
78 }
79
80 function numerals (n) {
81 var r = "";
82 n = n || 1;
83 while (n-- > 0)
84 r += choice("0123456789");
85 return r;
86 }
87
88 function pid () {
89 return letters(range(1, 4)).toUpperCase()
90 + choice(["‑", "", ".", "/"])
91 + numerals(range(1, 4));
92 }
93
94 function roman () {
95 return choice(["I", "II", "III", "IV", "V", "VI",
96 "VII", "VIII", "IX", "X", "XI", "XII",
97 "XIV"]);
98 }
99
100 function oid () {
101 return choice([letters(1).toUpperCase() + choice("-./") + numerals(2)]);
102 }
103
104 function randomName (scheme) {
105 switch (scheme) {
106 case "id":
107 return choice([pid() + " " + cap(choice(NOUNS))],
108 [cap(choice(NOUNS)) + " " + pid()]);
109 case "air":
110 return choice([
111 oid() + " " + cap(choice(ADJECTIVES)) + " " + cap(choice(BIRDS)),
112 cap(choice(ADJECTIVES)) + " " + cap(choice(BIRDS)) + " " + roman()]);
113 default:
114 return choice([
115 oid() + " " + cap(choice(ADJECTIVES)) + " " + cap(choice(NOUNS)),
116 cap(choice(ADJECTIVES)) + " " + cap(choice(NOUNS)) + " " + roman()]);
117 }
118 }
119
120 var STATS = ["attack", "range", "defense", "aside"];
121 function createUnit (dfnName) {
122 var dfn = UNITS[dfnName];
123 var weapons = dfn.weapons || [];
124 var unit = document.getElementById("unit-template").cloneNode(true);
125 unit.removeAttribute('id');
126 unit.setAttribute("data-dfn", dfnName);
127 unit.querySelector(".name").textContent = randomName(dfn.nameScheme);
128 unit.querySelector(".type").textContent = dfnName;
129
130 if (dfn.aside) {
131 var aside = unit.appendChild(document.createElement("div"));
132 aside.className = "aside";
133 aside.innerHTML = dfn.aside;
134 }
135 if (weapons.length) {
136 var weaponList = unit.appendChild(document.createElement('ul'));
137 weaponList.className = "weapons";
138 }
139
140 weapons.forEach(function (weaponRef) {
141 var count = parseInt(weaponRef, 10) || 1;
142 var weaponName = weaponRef.replace(/^[0-9 ]*/, "");
143 var weapon = WEAPONS[weaponName]
144 || WEAPONS[weaponName.replace(/s+$/, "")];
145 var weaponItem = document.createElement("li");
146 weaponItem.setAttribute('data-name', weaponName);
147 weaponItem.setAttribute('data-count', count);
148 weaponItem.setAttribute('data-remaining', count);
149 var stats = document.createElement('ul');
150 stats.className = "stats";
151 for (var j = 0; j < STATS.length; ++j) {
152 if (weapon[STATS[j]] !== undefined) {
153 var stat = document.createElement('li');
154 stat.className = STATS[j];
155 stat.innerHTML = weapon[STATS[j]];
156 stats.appendChild(stat);
157 }
158 }
159 if (stats.children.length)
160 weaponItem.appendChild(stats);
161 var ticks = document.createElement('div');
162 ticks.className = "ticks";
163 ticks.innerHTML = ticksText(count);
164 weaponItem.appendChild(ticks);
165 weaponList.appendChild(weaponItem);
166 });
167
168 if (dfn.tread) {
169 var move = dfn.move || 3;
170 var per = dfn.tread / move;
171 var treads = document.createElement('ol');
172 treads.className = "treads";
173 treads.appendChild(document.createElement("li"));
174 treads.setAttribute('data-count', dfn.tread);
175 treads.setAttribute('data-remaining', dfn.tread);
176 for (var i = 0; i < move; ++i) {
177 var tread = treads.appendChild(document.createElement("li"));
178 tread.className = "ticks";
179 tread.innerHTML = ticksText(per);
180 }
181 unit.appendChild(treads);
182 }
183 if (dfn.propulsion) {
184 var move = dfn.move || 3;
185 var per = dfn.propulsion / move;
186 var treads = document.createElement('ol');
187 treads.className = "treads propulsion";
188 treads.appendChild(document.createElement("li"));
189 treads.setAttribute('data-count', dfn.propulsion);
190 treads.setAttribute('data-remaining', dfn.propulsion);
191 for (var i = 0; i < move; ++i) {
192 var tread = treads.appendChild(document.createElement("li"));
193 tread.className = "ticks";
194 tread.innerHTML = ticksText(per);
195 }
196 unit.appendChild(treads);
197 }
198
199 return unit;
200 }
201
202 function addUnit (dfnName) {
203 document.querySelector('main').appendChild(createUnit(dfnName));
204 }
205
206 function spre (c) {
207 return new RegExp("(^|\\s+)" + c + "(\\s+|$)");
208 }
209
210 function hasClass (e, n) {
211 return !!e.className.match(spre(n));
212 }
213
214 function addClass (e, n) {
215 if (!hasClass(e, n))
216 e.className += " " + n;
217 }
218
219 function removeClass (e, n) {
220 e.className = e.className.replace(spre(n), "");
221 }
222
223 function show (id) {
224 addClass(document.getElementById(id.id || id), "visible");
225 }
226 function hide (id) {
227 removeClass(document.getElementById(id.id || id), "visible");
228 }
229 function handleTray (evt) {
230 var id = this.getAttribute('data-tray');
231 var el = document.getElementById(id);
232 var f = (hasClass(el, "visible") ? hide : show)
233 autoClose();
234 f(el);
235 evt.stopPropagation();
236 }
237
238 function ticksText (n) {
239 var blocks = [EMPTY.repeat(n)];
240 for (var i = 5; i >= 2; --i) {
241 if (n > i && n % i === 0) {
242 blocks = (new Array(n / i)).fill(EMPTY.repeat(i));
243 break;
244 }
245 }
246 return "<span>" + blocks.join("</span><wbr><span>") + "</span>";
247 }
248
249 function rub (content) {
250 return content.replace(LAST_TICKED, EMPTY + "$1");
251 }
252 function tick (content) {
253 return content.replace(FIRST_EMPTY, CHECK);
254 }
255
256 function findParent (el, selector) {
257 while (el && el !== document && !el.matches(selector))
258 el = el.parentNode;
259 return el === document ? null : el;
260 }
261
262 function boxes (event) {
263 var target = event.target;
264 if (!target.innerHTML.match(/☐|☒/))
265 return;
266 var par = findParent(target, '[data-count]');
267 var ticks = findParent(target, '.ticks');
268 if (!ticks || !par)
269 return;
270 var content = target.innerHTML;
271 var rect = target.getBoundingClientRect();
272 var total = target.innerHTML.match(/☐|☒/g);
273 var ticked = target.innerHTML.match(/☒/g);
274 var p = (event.clientX - rect.left) / rect.width;
275 var pr = (total && total.length)
276 ? (ticked ? ticked.length : 0) / total.length : 0;
277 par.innerHTML = ((p < pr) ? rub : tick)(par.innerHTML);
278 var rem = par.innerHTML.match(/☐/g);
279 par.setAttribute('data-remaining', rem ? rem.length : 0);
280 event.preventDefault();
281 event.stopPropagation();
282 }
283
284 function fade (p) {
285 return p * p * p * (p * (p * 6.0 - 15.0) + 10.0);
286 }
287
288 function scroll (y1, t) {
289 var y0 = document.body.scrollTop || document.documentElement.scrollTop;
290 var n = (t || 150) / 15;
291 var i = 0;
292 clearInterval(scroll.owner);
293 scroll.owner = setInterval(function () {
294 var p = Math.max(0, Math.min(++i / n, 1));
295 document.body.scrollTop
296 = document.documentElement.scrollTop
297 = y0 + (y1 - y0) * fade(p);
298 if (i >= n) clearInterval(scroll.owner);
299 }, 15);
300 }
301
302 function next (el) {
303 scroll(el.nextElementSibling.offsetTop - el.parentNode.offsetTop);
304 }
305
306 function previous (el) {
307 scroll(el.previousElementSibling.offsetTop - el.parentNode.offsetTop);
308 }
309
310 function autoClose () {
311 var open = document.querySelectorAll(".tray.visible");
312 for (var i = 0; i < open.length; ++i)
313 hide(open[i]);
314 return open && open.length;
315 }
316
317 window.addEventListener("DOMContentLoaded", function () {
318 if (navigator.standalone)
319 document.body.className += " standalone";
320 var units = document.getElementById("addUnit");
321 Object.keys(UNITS).sort(function (a, b) {
322 return (b.indexOf("Ogre Mk") - a.indexOf("Ogre Mk"))
323 || (a > b) - (a < b);
324 }).forEach(function (unitName) {
325 var unit = units.appendChild(document.createElement("li"));
326 unit.textContent = unitName;
327 unit.addEventListener("click", function () {
328 addUnit(unitName);
329 hide('addUnit');
330 });
331 });
332 addUnit('Ogre Mk. V');
333 FastClick.attach(document.body, { tapDelay: 50 });
334
335 var trays = document.querySelectorAll('[data-tray]');
336 for (var i = 0; i < trays.length; ++i)
337 trays[i].addEventListener('click', handleTray);
338 window.addEventListener('click', function (evt) {
339 if (!findParent(evt.target, ".tray.visible"))
340 autoClose();
341 });
342 });