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