Initial import.
[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.firstWhere = function (callback, thisArg) {
10 for (var i = 0; i < this.length; ++i)
11 if (callback.call(thisArg, this[i]))
12 return this[i];
13 };
14
15 Array.prototype.contains = function (o) {
16 return this.indexOf(o) >= 0;
17 };
18
19 Array.prototype.lacks = function (o) {
20 return !this.contains(o);
21 };
22
23 Array.prototype.containsSubset = function (o) {
24 return o.every(this.contains, this);
25 };
26
27 Array.prototype.pushUnique = function (o) {
28 if (this.lacks(o))
29 this.push(o);
30 };
31
32 var BLOCKS = {
33 helmets: ["hats", "accessories"],
34 wetsuits: ["tops", "bottoms", "socks", "dresses"],
35 dresses: ["tops", "bottoms"]
36 };
37 Object.keys(ACNL_WEARABLES).forEach(function (type) {
38 BLOCKS[type] = (BLOCKS[type] || []).concat(type);
39 });
40 Object.keys(BLOCKS).forEach(function (k) {
41 BLOCKS[k].forEach(function (v) {
42 BLOCKS[v].pushUnique(k);
43 });
44 });
45
46 function choice (os) {
47 return os[Math.floor(os.length * Math.random())];
48 }
49
50 function category (item) {
51 return Object.keys(ACNL_WEARABLES).firstWhere(function (type) {
52 return ACNL_WEARABLES[type].contains(item);
53 });
54 }
55
56 function notLewd (outfit) {
57 var categories = outfit.map(category);
58 return (categories.contains("wetsuits")
59 || categories.contains("dresses")
60 || categories.containsSubset(["tops", "bottoms"]));
61 }
62
63 function buildArray (a, l) {
64 return (a || []).concat(l);
65 }
66
67 function impossibilities (categories) {
68 return categories.map(function (c) { return BLOCKS[c]; }
69 ).reduce(buildArray, []);
70 }
71
72 function possibilities (outfit) {
73 return Object.keys(ACNL_WEARABLES
74 ).filter(Array.prototype.lacks, impossibilities(outfit.map(category))
75 ).map(function (k) { return ACNL_WEARABLES[k] }
76 ).reduce(buildArray, []);
77 }
78
79 function isFinished(outfit) {
80 return notLewd(outfit) && Math.random() > 0.5;
81 }
82
83 function nextItem (outfit) {
84 return !isFinished(outfit) && choice(possibilities(outfit));
85 }
86
87 function outfit () {
88 var ps = [];
89 var next;
90 while (next = nextItem(ps))
91 ps.push(next);
92 return ps;
93 }
94
95 function article (item) {
96 if (item[0].toUpperCase() === item[0] && item.indexOf("'s") >= 0)
97 return "";
98 else if (item.substr(-1) === "s" && item.substr(-2) !== "ss"
99 && item.substr(-6) !== "cosmos") // :(
100 return "";
101 else if ("aeiouAEIO".indexOf(item[0]) >= 0
102 || ("FHLMNRSX".indexOf(item[0]) >= 0
103 && item.split(" ")[0].toUpperCase() == item.split(" ")[0]))
104 return "an ";
105 else
106 return "a ";
107 }
108
109 function dblink (name) {
110 return ["<a target=\"_blank\" href=\"http://moridb.com/items/search?q=",
111 encodeURIComponent(name),
112 "\">", name.replace("&", "&amp;"), "</a>"].join("");
113 }
114
115 function litanize (names, link) {
116 names = names.map(function (name) {
117 return article(name) + (link ? dblink(name) : name);
118 });
119 switch (names.length) {
120 case 1: break;
121 case 2:
122 names[names.length - 1] = (choice(["and ", "with "])
123 + names[names.length - 1]);
124 break;
125 default:
126 if (Math.random() > 0.5 && names.length >= 4) {
127 names[names.length - 2] = "and " + names[names.length - 2];
128 names[names.length - 1] = "with " + names[names.length - 1];
129 } else {
130 names[names.length - 1] = "and " + names[names.length - 1];
131 }
132 break;
133 }
134
135 return names.join(names.length == 2 ? " " : ", ");
136 }
137
138 function litanizeElement (eid, tid) {
139 var o = outfit();
140 document.getElementById(eid).innerHTML = litanize(o, true) + "?";
141 // TODO: This often generates > 140 characters. Do I care?
142 // Maybe pick shorter prefixes if it would help.
143 var t = document.getElementById(tid);
144 t.href = t.href.replace(
145 /&text=[^&]+/, "&text=" + encodeURIComponent(
146 choice(["Going out in ",
147 "Labelle suggested ",
148 "I'll wear "]) + litanize(o, false) + "."));
149 }