Add glow wands.
[labelle.git] / acnl-outfit.js
1 // http://creativecommons.org/publicdomain/zero/1.0/
2 //
3 // To the extent possible under law, the person who associated CC0
4 // with this work has waived all copyright and related or neighboring
5 // rights to this work.
6
7 "use strict";
8
9 Array.prototype.contains = function (o) {
10 return this.indexOf(o) >= 0;
11 };
12
13 Array.prototype.lacks = function (o) {
14 return !this.contains(o);
15 };
16
17 Array.prototype.containsSubset = function (o) {
18 return o.every(this.contains, this);
19 };
20
21 Array.prototype.pushUnique = function (o) {
22 if (this.lacks(o))
23 this.push(o);
24 };
25
26 var BLOCKS = {
27 helmets: ["hats", "accessories"],
28 wetsuits: ["tops", "bottoms", "socks", "dresses"],
29 dresses: ["tops", "bottoms"]
30 };
31 Object.keys(ACNL_WEARABLES).forEach(function (type) {
32 BLOCKS[type] = (BLOCKS[type] || []).concat(type);
33 });
34 Object.keys(BLOCKS).forEach(function (k) {
35 BLOCKS[k].forEach(function (v) {
36 BLOCKS[v].pushUnique(k);
37 });
38 });
39
40 function choice (os) {
41 return os[Math.floor(os.length * Math.random())];
42 }
43
44 var flatten1 = Array.prototype.concat.apply.bind(Array.prototype.concat, []);
45
46 function get (attr) {
47 return this[attr];
48 }
49
50 var category = get.bind(ACNL_WEARABLES_CATEGORY);
51 var wearables = get.bind(ACNL_WEARABLES);
52
53 function isLewd (outfit) {
54 return ![["wetsuits"], ["dresses"], ["tops", "bottoms"]].some(
55 Array.prototype.containsSubset, outfit.map(category));
56 }
57
58 function impossibilities (outfit) {
59 return flatten1(outfit.map(category).map(get, BLOCKS));
60 }
61
62 function possibilities (outfit) {
63 return Object.keys(ACNL_WEARABLES).filter(
64 Array.prototype.lacks, impossibilities(outfit));
65 }
66
67 function outfit () {
68 var ps = [];
69 while (isLewd(ps))
70 ps.push(choice(flatten1(possibilities(ps).map(wearables))));
71 return ps;
72 }
73
74 function isPosessive (item) {
75 return item[0].toUpperCase() === item[0] && item.indexOf("'s") >= 0;
76 }
77
78 function isPlural (item) {
79 return (item.substr(-1) === "s"
80 && item.substr(-2) !== "ss"
81 && item.substr(-6) !== "cosmos"); // :(
82 }
83
84 function article (item) {
85 if (item[0].toUpperCase() === item[0] && item.indexOf("'s") >= 0)
86 return "";
87 else if (isPlural(item))
88 return "";
89 else if ("aeiouAEIO".indexOf(item[0]) >= 0
90 || ("FHLMNRSX".indexOf(item[0]) >= 0
91 && item.split(" ")[0].toUpperCase() == item.split(" ")[0]))
92 return "an ";
93 else
94 return "a ";
95 }
96
97 function dblink (name) {
98 return ["<a target=\"_blank\" href=\"http://moridb.com/items/search?q=",
99 encodeURIComponent(name),
100 "\">", name.replace("&", "&amp;"), "</a>"].join("");
101 }
102
103 function litanize (names, link) {
104 names = names.map(function (name) {
105 return article(name) + (link ? dblink(name) : name);
106 });
107 switch (names.length) {
108 case 1: break;
109 case 2:
110 names[names.length - 1] = (choice(["and ", "with "])
111 + names[names.length - 1]);
112 break;
113 default:
114 if (Math.random() > 0.5 && names.length >= 4) {
115 names[names.length - 2] = "and " + names[names.length - 2];
116 names[names.length - 1] = "with " + names[names.length - 1];
117 } else {
118 names[names.length - 1] = "and " + names[names.length - 1];
119 }
120 break;
121 }
122
123 return names.join(names.length == 2 ? " " : ", ");
124 }
125
126 function litanizeElement (eid, tid) {
127 var o = outfit();
128 document.getElementById(eid).innerHTML = litanize(o, true) + "?";
129 // TODO: This often generates > 140 characters. Do I care?
130 // Maybe pick shorter prefixes if it would help.
131 var t = document.getElementById(tid);
132 t.href = t.href.replace(
133 /&text=[^&]+/, "&text=" + encodeURIComponent(
134 choice(["Going out in ",
135 "Labelle suggested ",
136 "I'll wear "]) + litanize(o, false) + "."));
137 }