Look I can OO.
[123456789.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 var LEVEL_SELECT = 0;
12 var LEVEL_IN = 1;
13 var LEVEL_LOST = 2;
14 var LEVEL_WON = 3;
15 var GAME_WON = 4;
16
17 var bead_change = [];
18 var sounds = [];
19
20 var LEVEL_IDX = [
21 [7, 8, 9],
22 [4, 5, 6],
23 [1, 2, 3]
24 ];
25
26 var LEVEL_POS = [
27 null,
28 [0, 2], [1, 2], [2, 2],
29 [0, 1], [1, 1], [2, 1],
30 [0, 0], [1, 0], [2, 0]
31 ];
32
33 var state = LEVEL_SELECT;
34
35 var max_level = 1;
36 var current_level = 1;
37 var current_level_inst = null;
38
39 function QueueChange(bead, sound) {
40 if (sound == null)
41 sound = [];
42 bead_change.push(bead)
43 sounds.push(sound);
44 }
45
46 function Shuffle(a) {
47 for (var i = a.length - 1; i >= 1; --i) {
48 var j = PS.Random(i) - 1;
49 var tmp = a[j];
50 a[j] = a[i];
51 a[i] = tmp;
52 }
53 return a;
54 }
55
56 function Choose(a) {
57 return a[PS.Random(a.length) - 1];
58 }
59
60 function CheckExact(pos) {
61 for (var n = 1; n <= 9; ++n) {
62 var x = LEVEL_POS[n][0];
63 var y = LEVEL_POS[n][1];
64 var on = PS.BeadColor(x, y) != PS.COLOR_WHITE;
65 var should = pos.indexOf(n) >= 0;
66 if ((on && !should) || (!on && should)) {
67 return false;
68 }
69 }
70 return true;
71 };
72
73 function FollowMe () {
74 var count = 0;
75 PS.BeadColor(PS.Random(3) - 1, PS.Random(3) - 1, PS.COLOR_BLACK);
76
77 this.Press = function (x, y) {
78 if (PS.BeadColor(x, y) == PS.COLOR_BLACK) {
79 PS.BeadColor(x, y, PS.COLOR_WHITE);
80 count++;
81 if (count >= 4) {
82 Win();
83 } else {
84 PS.BeadColor(PS.Random(3) - 1, PS.Random(3) - 1,
85 PS.COLOR_BLACK);
86 PS.AudioPlay(PS.Xylophone(count == 3 ? 14 : 13), 0.5);
87 }
88 } else {
89 Mistake();
90 count = 0;
91 }
92 };
93 };
94
95 function Enumerate () {
96 this.Press = function (x, y) {
97 for (var n = 1; n < LEVEL_IDX[y][x]; ++n) {
98 if (PS.BeadColor(LEVEL_POS[n][0], LEVEL_POS[n][1])
99 != PS.COLOR_BLACK) {
100 Mistake();
101 return;
102 }
103 }
104 if (PS.BeadColor(x, y) == PS.COLOR_BLACK) {
105 Mistake();
106 } else {
107 PS.BeadColor(x, y, PS.COLOR_BLACK);
108 if (LEVEL_IDX[y][x] == 9)
109 Win();
110 else if (LEVEL_IDX[y][x] == 3)
111 PS.AudioPlay(PS.Xylophone(15), 0.5);
112 else if (LEVEL_IDX[y][x] == 6)
113 PS.AudioPlay(PS.Xylophone(16), 0.5);
114 else if (LEVEL_IDX[y][x] > 6)
115 PS.AudioPlay(PS.Xylophone(15), 0.5);
116 else if (LEVEL_IDX[y][x] > 3)
117 PS.AudioPlay(PS.Xylophone(14), 0.5);
118 else
119 PS.AudioPlay(PS.Xylophone(13), 0.5);
120 }
121 };
122 };
123
124 function Repeat() {
125 var repeatorder = Shuffle([1, 2, 3, 4, 5, 6, 7, 8, 9]).slice(0, 6);
126 PS.Clock(45);
127 for (var n = 0; n < repeatorder.length; ++n) {
128 QueueChange([[PS.COLOR_BLACK, repeatorder[n]]],
129 [PS.Xylophone(2 + n)]);
130
131 this.Press = function Press (x, y) {
132 var n = repeatorder.shift();
133 if (LEVEL_IDX[y][x] == n) {
134 PS.BeadColor(x, y, PS.COLOR_GREEN);
135 if (repeatorder.length == 0) {
136 Win();
137 } else {
138 PS.AudioPlay(PS.Xylophone(9 + repeatorder.length), 0.5);
139 PS.AudioPlay(PS.Xylophone(9 + repeatorder.length), 0.5);
140 }
141 } else {
142 Mistake();
143 }
144 };
145 };
146 };
147
148 function TickMissing() {
149 var repeatorder;
150 var missing_valid;
151 var count = 0;
152
153 PS.Clock(45);
154 var d = {};
155 missing_valid = [];
156 repeatorder = Shuffle([1, 2, 3, 4, 5, 6, 7, 8, 9]).slice(0, 6);
157 repeatorder = Shuffle(repeatorder.concat(repeatorder));
158 for (var n = 0; n < repeatorder.length; ++n) {
159 if (d[repeatorder[n]]) {
160 QueueChange([[PS.COLOR_WHITE, repeatorder[n]]],
161 [PS.Xylophone(15 - n)]);
162 } else {
163 d[repeatorder[n]] = true;
164 QueueChange([[PS.COLOR_RED, repeatorder[n]]],
165 [PS.Xylophone(13 + n)]);
166 }
167 }
168
169 for (var n = 1; n <= 9; ++n)
170 if (repeatorder.indexOf(n) < 0)
171 missing_valid.push(n);
172
173 this.Press = function (x, y) {
174 if (missing_valid.indexOf(LEVEL_IDX[y][x]) < 0) {
175 Mistake();
176 } else {
177 PS.BeadColor(x, y, PS.COLOR_BLACK);
178 if (CheckExact(missing_valid)) {
179 Win();
180 } else {
181 count++;
182 PS.AudioPlay(PS.Xylophone(count == 2 ? 14 : 13), 0.5);
183 }
184 }
185 };
186 };
187
188 function ThreeRow () {
189 var third_row;
190 var count = 0;
191 PS.Clock(45);
192 Generate();
193
194 function Generate() {
195 third_row = Shuffle(Choose([
196 [1, 2, 3], [4, 5, 6], [7, 8, 9],
197 [1, 4, 7], [2, 5, 8], [3, 6, 9],
198 [1, 5, 9], [3, 5, 7]]));
199 QueueChange([[PS.COLOR_BLACK, third_row[0]]], [PS.Xylophone(13)]);
200 QueueChange([[PS.COLOR_BLACK, third_row[1]]], [PS.Xylophone(13)]);
201 };
202
203 this.Press = function (x, y) {
204 if (LEVEL_IDX[y][x] == third_row[2]) {
205 count++;
206 PS.BeadColor(x, y, PS.COLOR_BLACK);
207 PS.Clock(PS.Clock());
208 if (count >= 3) {
209 Win();
210 } else {
211 PS.AudioPlay(PS.Xylophone(14), 0.5);
212 QueueChange([[PS.COLOR_GREEN, third_row[2]],
213 [PS.COLOR_GREEN, third_row[1]],
214 [PS.COLOR_GREEN, third_row[0]]],
215 [PS.Xylophone(34)]);
216 QueueChange([[PS.COLOR_WHITE, third_row[2]],
217 [PS.COLOR_WHITE, third_row[1]],
218 [PS.COLOR_WHITE, third_row[0]]]);
219 Generate();
220 }
221 } else {
222 count = 0;
223 Mistake();
224 }
225 };
226 };
227
228 function SudokuRow () {
229 var sudoku_row;
230 var count = 0;
231 PS.Clock(45);
232 Generate();
233
234 function Generate() {
235 sudoku_row = Shuffle(
236 Choose([[1, 8, 6], [4, 8, 3], [4, 2, 9], [7, 2, 6]]));
237 QueueChange([[PS.COLOR_RED, sudoku_row[0]]],
238 [PS.Xylophone(3)]);
239 QueueChange([[PS.COLOR_RED, sudoku_row[1]]],
240 [PS.Xylophone(2)]);
241 };
242
243 this.Press = function (x, y) {
244 if (LEVEL_IDX[y][x] == sudoku_row[2]) {
245 count++;
246 PS.BeadColor(x, y, PS.COLOR_BLACK);
247 if (count >= 3) {
248 Win();
249 } else {
250 PS.AudioPlay(PS.Xylophone(13), 0.5);
251 QueueChange([[PS.COLOR_GREEN, sudoku_row[2]],
252 [PS.COLOR_GREEN, sudoku_row[1]],
253 [PS.COLOR_GREEN, sudoku_row[0]]],
254 [PS.Xylophone(14)]);
255 QueueChange([[PS.COLOR_WHITE, sudoku_row[2]],
256 [PS.COLOR_WHITE, sudoku_row[1]],
257 [PS.COLOR_WHITE, sudoku_row[0]]]);
258 Generate();
259 }
260 } else {
261 count = 0;
262 Mistake();
263 }
264 };
265 };
266
267 function Digits () {
268 QueueChange([[PS.COLOR_BLACK, 1]], ["perc_hihat_closed"]);
269 QueueChange([[PS.COLOR_WHITE, 1],
270 [PS.COLOR_BLACK, 4]], ["perc_hihat_closed"]);
271 QueueChange([[PS.COLOR_WHITE, 4],
272 [PS.COLOR_BLACK, 7]], ["perc_hihat_closed"]);
273 QueueChange([[PS.COLOR_WHITE, 7]]);
274 QueueChange([]);
275 QueueChange([[PS.COLOR_BLACK, 2],
276 [PS.COLOR_BLACK, 5],
277 [PS.COLOR_BLACK, 8]],
278 [PS.Xylophone(13)]);
279 QueueChange([[PS.COLOR_GREEN, 2],
280 [PS.COLOR_GREEN, 5],
281 [PS.COLOR_GREEN, 8]],
282 [PS.Xylophone(14)]);
283 QueueChange([[PS.COLOR_WHITE, 2],
284 [PS.COLOR_WHITE, 5],
285 [PS.COLOR_WHITE, 8]]);
286 var count = 4;
287
288 this.Press = function (x, y) {
289 if (PS.BeadColor(x, y) == PS.COLOR_WHITE) {
290 PS.BeadColor(x, y, PS.COLOR_BLACK);
291 } else {
292 PS.BeadColor(x, y, PS.COLOR_WHITE);
293 }
294
295 if (count == 4 && ApproveIfExact([7, 9, 4, 5, 6, 3]))
296 count = 7;
297 if (count == 7 && (CheckExact([7, 8, 9, 6, 3])
298 || CheckExact([7, 8, 9, 6, 2]))) {
299 Win();
300 return;
301 }
302
303 if (PS.BeadColor(x, y) == PS.COLOR_WHITE) {
304 PS.AudioPlay(PS.Xylophone(10), 0.5);
305 } else {
306 PS.AudioPlay(PS.Xylophone(13), 0.3);
307 }
308 };
309 };
310
311 function Shapes() {
312 PS.Clock(40);
313 QueueChange([[PS.COLOR_BLACK, 1]], [PS.Xylophone(13)]);
314 QueueChange([[PS.COLOR_GREEN, 1]], [PS.Xylophone(14)]);
315 QueueChange([[PS.COLOR_BLACK, 1]]);
316 QueueChange([[PS.COLOR_BLACK, 4]], [PS.Xylophone(13)]);
317 QueueChange([[PS.COLOR_BLACK, 7]], [PS.Xylophone(13)]);
318
319 QueueChange([[PS.COLOR_GREEN, 1],
320 [PS.COLOR_GREEN, 4],
321 [PS.COLOR_GREEN, 7]],
322 [PS.Xylophone(14)]);
323 QueueChange([[PS.COLOR_BLACK, 1],
324 [PS.COLOR_BLACK, 4],
325 [PS.COLOR_BLACK, 7]]);
326 var count = 3;
327
328 this.Press = function (x, y) {
329 if (PS.BeadColor(x, y) == PS.COLOR_WHITE) {
330 PS.BeadColor(x, y, PS.COLOR_BLACK);
331 } else {
332 PS.BeadColor(x, y, PS.COLOR_WHITE);
333 }
334
335 if (count == 3
336 && (ApproveIfExact([1, 4, 7, 2, 5, 3], PS.COLOR_BLACK)
337 || ApproveIfExact([1, 4, 7, 8, 5, 3], PS.COLOR_BLACK))) {
338 count = 4;
339 if (PS.BeadColor(x, y) == PS.COLOR_WHITE)
340 PS.AudioPlay(PS.Xylophone(10), 0.3);
341 else
342 PS.AudioPlay(PS.Xylophone(13), 0.5);
343 } else if (count == 4 && CheckExact([1, 2, 3, 4, 5, 6, 7, 8, 9])) {
344 Win();
345 } else {
346 if (PS.BeadColor(x, y) == PS.COLOR_WHITE)
347 PS.AudioPlay(PS.Xylophone(10), 0.3);
348 else
349 PS.AudioPlay(PS.Xylophone(13), 0.5);
350 }
351 };
352 };
353
354 function ApproveIfExact(a, color) {
355 if (color == null)
356 color = PS.COLOR_WHITE;
357 if (CheckExact(a)) {
358 var green = [];
359 var white = [];
360 for (var n = 0; n < a.length; ++n) {
361 green.push([PS.COLOR_GREEN, a[n]]);
362 white.push([color, a[n]]);
363 }
364 QueueChange(green, [PS.Xylophone(14)]);
365 QueueChange(white);
366 PS.Clock(PS.Clock());
367 if (PS.StatusText() != "…")
368 PS.StatusText("…");
369 return true;
370 } else {
371 return false;
372 }
373 };
374
375 function Die () {
376 PS.Clock(40);
377 QueueChange([[PS.COLOR_BLACK, 5]], [PS.Xylophone(13)]);
378 QueueChange([[PS.COLOR_GREEN, 5]], [PS.Xylophone(14)]);
379 QueueChange([[PS.COLOR_WHITE, 5]]);
380 QueueChange([[PS.COLOR_BLACK, 1]], [PS.Xylophone(13)]);
381 QueueChange([[PS.COLOR_BLACK, 9]], [PS.Xylophone(13)]);
382 QueueChange([[PS.COLOR_GREEN, 1],
383 [PS.COLOR_GREEN, 9]], [PS.Xylophone(14)]);
384 QueueChange([[PS.COLOR_WHITE, 1],
385 [PS.COLOR_WHITE, 9]]);
386 var count = 3;
387
388 this.Press = function (x, y) {
389 if (PS.BeadColor(x, y) == PS.COLOR_WHITE) {
390 PS.BeadColor(x, y, PS.COLOR_BLACK);
391 } else {
392 PS.BeadColor(x, y, PS.COLOR_WHITE);
393 }
394 switch (count) {
395 case 3:
396 if (ApproveIfExact([1, 5, 9]) || ApproveIfExact([7, 5, 3]))
397 count = 4;
398 break;
399 case 4:
400 if (ApproveIfExact([1, 3, 7, 9]))
401 count = 5;
402 break;
403 case 5:
404 if (ApproveIfExact([1, 3, 5, 7, 9]))
405 count = 6;
406 break;
407 case 6:
408 if (CheckExact([1, 2, 3, 7, 8, 9])
409 || CheckExact([1, 4, 7, 3, 6, 9])) {
410 Win();
411 return;
412 }
413 }
414
415 if (PS.BeadColor(x, y) == PS.COLOR_WHITE) {
416 PS.AudioPlay(PS.Xylophone(14), 0.5);
417 } else {
418 PS.AudioPlay(PS.Xylophone(13), 0.3);
419 }
420 };
421 };
422
423 var LEVELS = [
424 null,
425 FollowMe,
426 ThreeRow,
427 Enumerate,
428 Repeat,
429 TickMissing,
430 SudokuRow,
431 Shapes,
432 Die,
433 Digits,
434 ];
435
436 function Reset(color) {
437 if (max_level < 10 && PS.StatusText() != "?")
438 PS.StatusText("?");
439 if (max_level == 10 && PS.StatusText() != ".")
440 PS.StatusText(".");
441
442 PS.Clock(0);
443 bead_change = [];
444 PS.BeadColor(PS.ALL, PS.ALL, color);
445 }
446
447 function Win() {
448 Reset(PS.COLOR_GREEN);
449 state = LEVEL_WON;
450 current_level += 1;
451 if (max_level < current_level) {
452 max_level = current_level;
453 if (max_level == 10) {
454 setTimeout(function() {
455 PS.AudioPlay(PS.Xylophone(25), 0.5)
456 }, 0);
457 setTimeout(function() {
458 PS.AudioPlay(PS.Xylophone(30), 0.5)
459 }, 500);
460 setTimeout(function() {
461 PS.AudioPlay(PS.Xylophone(30), 0.5)
462 }, 600);
463 setTimeout(function() {
464 PS.AudioPlay(PS.Xylophone(31), 0.5)
465 }, 1000);
466 setTimeout(function() {
467 PS.AudioPlay(PS.Xylophone(37), 0.5)
468 }, 1300);
469 } else {
470 PS.AudioPlay(PS.Xylophone(34), 0.5);
471 PS.AudioPlay(PS.Xylophone(38), 0.5);
472 PS.AudioPlay(PS.Xylophone(39), 0.5);
473 }
474 } else {
475 PS.AudioPlay(PS.Xylophone(34), 0.5);
476 PS.AudioPlay(PS.Xylophone(38), 0.5);
477 PS.AudioPlay(PS.Xylophone(39), 0.5);
478 }
479 };
480
481 function Mistake() {
482 Reset(PS.COLOR_RED);
483 PS.AudioPlay(PS.Xylophone(2), 0.5);
484 PS.AudioPlay(PS.Xylophone(8), 0.5);
485 PS.AudioPlay(PS.Xylophone(22), 0.5);
486 PS.StatusText("X");
487 state = LEVEL_LOST;
488 };
489
490 PS.Init = function (options) {
491 var path = window.location.href;
492 PS.AudioPath(path.substr(0, path.lastIndexOf("/")) + "/audio/");
493 PS.GridSize(3, 3);
494 LevelSelect();
495 PS.StatusText("?");
496 for (var i = 1; i < 39; ++i)
497 PS.AudioLoad(PS.Xylophone(i));
498 PS.AudioLoad("perc_hihat_closed");
499 };
500
501 function StartLevel(level) {
502 if (level > max_level)
503 return;
504 PS.AudioPlay(PS.Xylophone(1), 0.5);
505 Reset(PS.COLOR_WHITE);
506 current_level = level;
507 current_level_inst = new LEVELS[current_level]();
508 state = LEVEL_IN;
509 };
510
511 function LevelSelect() {
512 Reset(PS.COLOR_WHITE);
513 for (var n = 1; n <= 9; ++n) {
514 var x = LEVEL_POS[n][0];
515 var y = LEVEL_POS[n][1];
516 if (n == max_level)
517 PS.BeadColor(x, y, PS.COLOR_VIOLET);
518 else if (n < max_level)
519 PS.BeadColor(x, y, PS.COLOR_BLUE);
520 }
521 if (max_level == 10)
522 PS.BeadColor(PS.ALL, PS.ALL, PS.COLOR_BLUE);
523 state = LEVEL_SELECT;
524 };
525
526 PS.Click = function (x, y, data, options) {
527 if (PS.Clock() > 0 && bead_change.length)
528 return;
529 switch (state) {
530 case LEVEL_SELECT:
531 StartLevel(LEVEL_IDX[y][x]);
532 break;
533 case LEVEL_IN:
534 current_level_inst.Press(x, y);
535 break;
536 case LEVEL_WON:
537 LevelSelect();
538 break;
539 case LEVEL_LOST:
540 StartLevel(current_level);
541 break;
542 case GAME_WON:
543 break;
544 }
545 };
546
547 PS.Release = function (x, y, data, options) {
548 };
549
550 PS.Enter = function (x, y, data, options) {
551 if (state == LEVEL_SELECT && PS.BeadColor(x, y) != PS.COLOR_WHITE) {
552 PS.AudioPlay( "fx_click" , 0.5);
553 }
554 };
555
556 PS.Leave = function (x, y, data, options) {
557 };
558
559 PS.KeyDown = function (key, shift, ctrl, options) {
560 if (ctrl && key == 32) {
561 Win();
562 LevelSelect();
563 }
564 switch (key) {
565 case PS.KEYPAD_1: case 49: PS.Click(0, 2); break;
566 case PS.KEYPAD_2: case 50: PS.Click(1, 2); break;
567 case PS.KEYPAD_3: case 51: PS.Click(2, 2); break;
568 case PS.KEYPAD_4: case 52: PS.Click(0, 1); break;
569 case PS.KEYPAD_5: case 53: PS.Click(1, 1); break;
570 case PS.KEYPAD_6: case 54: PS.Click(2, 1); break;
571 case PS.KEYPAD_7: case 55: PS.Click(0, 0); break;
572 case PS.KEYPAD_8: case 56: PS.Click(1, 0); break;
573 case PS.KEYPAD_9: case 57: PS.Click(2, 0); break;
574 case PS.KEY_ESCAPE: LevelSelect(); break;
575 }
576 };
577
578 PS.KeyUp = function (key, shift, ctrl, options) {
579 };
580
581 PS.Wheel = function (dir, options) {
582 };
583
584 PS.Tick = function (options) {
585 var beads = bead_change.shift();
586 if (beads) {
587 if (PS.StatusText() != "…")
588 PS.StatusText("…");
589 for (var n = 0; n < beads.length; ++n) {
590 var bead = beads[n];
591 var x = LEVEL_POS[bead[1]][0];
592 var y = LEVEL_POS[bead[1]][1];
593 PS.BeadColor(x, y, bead[0]);
594 }
595 }
596
597 var sound = sounds.shift();
598 if (sound) {
599 for (var n = 0; n < sound.length; ++n) {
600 PS.AudioPlay(sound[n], 0.5);
601 }
602 }
603
604 if (bead_change.length == 0 && PS.StatusText() != "?")
605 PS.StatusText("?");
606 };