Add README.
[matrixcreatrix.git] / game.js
1 "use strict";
2
3 /* By Joe Wreschnig
4 Placed in the public domain.
5
6 This code is designed for use with Perlenspiel, by Brian Moriarty,
7 which is released under the terms of the GNU LGPL version 3 or
8 later.
9 */
10
11
12 /*global PS */
13
14 var EARTH = 0x00FF00;
15 var WATER = 0x0000FF;
16 var FIRE = 0xFF0000;
17 var WIND = 0xFFFF00;
18
19 var VOID = 0x330033;
20
21 var SELECTED = 0x222222;
22
23 var DISABLE_PROMPT = 3;
24
25 var X_SIZE = 12;
26 var Y_SIZE = 9;
27
28 var AUDIO_MAX = 57;
29 var AUDIO_LOUD = 0.7;
30 var AUDIO_QUIET = 0.4;
31
32 var selecting = false;
33 var score = 0;
34 var level = 1;
35
36 var proportions = [
37 EARTH,
38 WATER, WATER,
39 FIRE, FIRE, FIRE,
40 WIND, WIND,
41 VOID, VOID,
42 EARTH,
43 ];
44
45 var level_modifier = [FIRE, VOID];
46
47 function couldFinishWorldSound(score, size) {
48 score = PS.Random(score) + score;
49 if (score + 18 > AUDIO_MAX)
50 score = AUDIO_MAX - 18;
51 PS.AudioPlay(PS.Harpsichord(score, false), AUDIO_QUIET);
52 PS.AudioPlay(PS.Harpsichord(score + 6, false), AUDIO_QUIET);
53 PS.AudioPlay(PS.Harpsichord(score + 18, false), AUDIO_QUIET);
54 }
55
56 function finishWorldSound(score, size) {
57 score = PS.Random(score) + score;
58 if (score + 18 > AUDIO_MAX)
59 score = AUDIO_MAX - 18;
60 PS.AudioPlay(PS.Harpsichord(score, true), AUDIO_LOUD);
61 PS.AudioPlay(PS.Harpsichord(score + 6, true), AUDIO_LOUD);
62 PS.AudioPlay(PS.Harpsichord(score + 18, true), AUDIO_LOUD);
63 }
64
65 function levelUpSound(level) {
66 if (level + 44 > AUDIO_MAX)
67 level = AUDIO_MAX - 44;
68 PS.AudioPlay(PS.Harpsichord(20 + level, true), AUDIO_LOUD);
69 PS.AudioPlay(PS.Harpsichord(28 + level, true), AUDIO_LOUD);
70 PS.AudioPlay(PS.Harpsichord(44 + level, true), AUDIO_LOUD);
71 }
72
73 function select(x, y) {
74 if (PS.BeadBorderColor(x, y) != SELECTED) {
75 var invalid = checkInvalidReason();
76 PS.BeadBorderColor(x, y, SELECTED);
77 PS.BeadBorderWidth(x, y, 4);
78 if (invalid && !checkInvalidReason())
79 couldFinishWorldSound(score, 0);
80 }
81 }
82
83 function deselect(x, y) {
84 PS.BeadBorderColor(x, y, PS.DEFAULT);
85 PS.BeadBorderWidth(x, y, PS.DEFAULT);
86 }
87
88 function shuffle(a) {
89 for (var i = a.length - 1; i >= 1; --i) {
90 var j = PS.Random(i) - 1;
91 var tmp = a[j];
92 a[j] = a[i];
93 a[i] = tmp;
94 }
95 return a;
96 }
97
98 function countBoard(evenUnselected) {
99 var b = { fire: 0, earth: 0, water: 0, wind: 0, nothing: 0, total: 0 };
100 for (var x = 0; x < X_SIZE; ++x) {
101 for (var y = 0; y < Y_SIZE; ++y) {
102 if (evenUnselected || isSelected(x, y)) {
103 var color = PS.BeadColor(x, y);
104 if (color == FIRE) b.fire++;
105 if (color == EARTH) b.earth++;
106 if (color == WATER) b.water++;
107 if (color == WIND) b.wind++;
108 if (color == VOID) b.nothing++;
109 b.total++;
110 }
111 }
112 }
113 return b;
114 }
115
116 function isSelected(x, y) {
117 return (x >= 0 && x < X_SIZE
118 && y >= 0 && y < Y_SIZE
119 && PS.BeadBorderColor(x, y) == SELECTED);
120 }
121
122 function onlySelected(x, y) {
123 return isSelected(x, y) && countBoard().total == 1;
124 }
125
126 function checkAdjacent(x, y) {
127 return (isSelected(x, y)
128 || isSelected(x - 1, y)
129 || isSelected(x + 1, y)
130 || isSelected(x, y - 1)
131 || isSelected(x, y + 1));
132 }
133
134 function createBoard() {
135 var nothing = countBoard(true).nothing;
136 var b = [];
137 for (var y = 0; y < nothing; ++y) {
138 b.push(proportions[y % proportions.length]);
139 }
140 b = shuffle(b);
141 var j = 0;
142 for (var x = 0; x < X_SIZE; ++x)
143 for (var y = 0; y < Y_SIZE; ++y)
144 if (PS.BeadColor(x, y) == VOID)
145 PS.BeadColor(x, y, b[j++]);
146 }
147
148 function checkInvalidReason() {
149 var b = countBoard();
150 if (b.total == 1 && b.fire) {
151 return ["[Fire] Counter its heat with water", 0xFF9999];
152 } else if (b.total == 1 && b.earth) {
153 return ["[Life] Lifeless worlds are pointless", 0x99FF99];
154 } else if (b.total == 1 && b.water) {
155 return ["[Water] Quench both thirst and fire", 0x9999FF];
156 } else if (b.total == 1 && b.wind) {
157 return ["[Air] Habitable worlds need air", 0xFFFF99];
158 } else if (b.total == 1 && b.nothing) {
159 return ["[Void] Don't use too much...", 0xFF99FF];
160 } else if (b.total == 0 && score == 0) {
161 return ["Click and drag to make a world", 0xFFFFFF];
162 } else if (b.total && b.total < 3 && score < DISABLE_PROMPT) {
163 return ["Select more elements to make a world", 0xFFFFFF];
164 } else if (b.total < 3) {
165 var s = "Worlds created: " + score;
166 if (level > 1)
167 s = "Plane " + level + " / " + s;
168 return [s, 0xFFFFFF];
169 }
170
171 if (b.earth == 0)
172 return ["This world wouldn't have any life!", 0x99FF99];
173
174 if (b.water == 0)
175 return ["Life needs water to drink!", 0x9999FF];
176
177 if (b.wind == 0)
178 return ["Life needs air to breathe!", 0xFFFF99];
179
180 if (b.nothing >= (b.water + b.fire * 2 + b.earth + b.wind))
181 return ["Your world has too much nothing!", 0xFF99FF];
182
183 if (b.water < b.fire)
184 return ["Your world is burning!", 0xFF9999];
185
186 return null;
187 }
188
189 function statusUpdate() {
190 var res = checkInvalidReason();
191 if (!res)
192 res = ["Click to create a world", 0xFFFFFF];
193 PS.StatusText(res[0]);
194 PS.StatusColor(res[1]);
195 }
196
197 PS.Init = function () {
198 var path = window.location.href;
199 PS.AudioPath(path.substr(0, path.lastIndexOf("/")) + "/audio/");
200 PS.Clock(100);
201 PS.GridSize(X_SIZE, Y_SIZE);
202 PS.GridBGColor(VOID);
203 PS.BeadShow(PS.ALL, PS.ALL, true);
204 PS.BeadColor(PS.ALL, PS.ALL, VOID);
205 createBoard();
206 statusUpdate();
207 };
208
209 PS.Click = function (x, y, data) {
210 if (onlySelected(x, y)) {
211 deselect(x, y);
212 } else if (isSelected(x, y) && !checkInvalidReason()) {
213 var size = 0;
214 for (var x = 0; x < X_SIZE; ++x) {
215 for (var y = 0; y < Y_SIZE; ++y) {
216 if (isSelected(x, y)) {
217 PS.BeadColor(x, y, VOID);
218 ++size;
219 }
220 }
221 }
222 deselect(PS.ALL, PS.ALL);
223 finishWorldSound(++score, size);
224 } else if (checkAdjacent(x, y) && !isSelected(x, y)) {
225 select(x, y);
226 selecting = true;
227 } else {
228 deselect(PS.ALL, PS.ALL);
229 selecting = true;
230 select(x, y);
231 }
232 statusUpdate();
233 var b = countBoard(true);
234 if (b.earth == 0) {
235 proportions.push.apply(proportions, level_modifier);
236 createBoard();
237 levelUpSound(++level);
238
239 var b = countBoard(true);
240 if (b.earth == 0) {
241 PS.StatusText("All life thanks you! Final score: " + score);
242 } else {
243 PS.StatusText("Now try a more dangerous universe!");
244 }
245 PS.StatusColor(0xFFFFFF);
246 }
247 };
248
249 PS.Release = function (x, y, data) {
250 selecting = false;
251 };
252
253 PS.Enter = function (x, y, data) {
254 if (selecting && checkAdjacent(x, y)) {
255 select(x, y);
256 statusUpdate();
257 } else {
258 selecting = false;
259 }
260 };
261
262 PS.Leave = function (x, y, data) {
263 };
264
265 PS.KeyDown = function (key, shift, ctrl) {
266 };
267
268 PS.KeyUp = function (key, shift, ctrl) {
269 };
270
271 PS.Wheel = function (dir) {
272 };
273
274 // Chrome and Firefox bug out when asked to preload 114 files at once.
275 var loaded = 0;
276 PS.Tick = function () {
277 if (++loaded <= AUDIO_MAX) {
278 PS.AudioLoad(PS.Harpsichord(loaded));
279 PS.AudioLoad(PS.Harpsichord(loaded, true));
280 } else {
281 PS.Clock(0);
282 }
283 };