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