Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
TheGameCenter
GitHub Repository: TheGameCenter/TheGameCenter.github.io
Path: blob/main/assets/javascript/pacman/functions.js
15371 views
1
var NONE = 4,
2
UP = 3,
3
LEFT = 2,
4
DOWN = 1,
5
RIGHT = 11,
6
WAITING = 5,
7
PAUSE = 6,
8
PLAYING = 7,
9
COUNTDOWN = 8,
10
EATEN_PAUSE = 9,
11
DYING = 10,
12
Pacman = {};
13
14
Pacman.FPS = 30;
15
16
Pacman.Ghost = function (game, map, colour) {
17
18
var position = null,
19
direction = null,
20
eatable = null,
21
eaten = null,
22
due = null;
23
24
function getNewCoord(dir, current) {
25
26
var speed = isVunerable() ? 1 : isHidden() ? 4 : 2,
27
xSpeed = (dir === LEFT && -speed || dir === RIGHT && speed || 0),
28
ySpeed = (dir === DOWN && speed || dir === UP && -speed || 0);
29
30
return {
31
"x": addBounded(current.x, xSpeed),
32
"y": addBounded(current.y, ySpeed)
33
};
34
};
35
36
/* Collision detection(walls) is done when a ghost lands on an
37
* exact block, make sure they dont skip over it
38
*/
39
function addBounded(x1, x2) {
40
var rem = x1 % 10,
41
result = rem + x2;
42
if (rem !== 0 && result > 10) {
43
return x1 + (10 - rem);
44
} else if(rem > 0 && result < 0) {
45
return x1 - rem;
46
}
47
return x1 + x2;
48
};
49
50
function isVunerable() {
51
return eatable !== null;
52
};
53
54
function isDangerous() {
55
return eaten === null;
56
};
57
58
function isHidden() {
59
return eatable === null && eaten !== null;
60
};
61
62
function getRandomDirection() {
63
var moves = (direction === LEFT || direction === RIGHT)
64
? [UP, DOWN] : [LEFT, RIGHT];
65
return moves[Math.floor(Math.random() * 2)];
66
};
67
68
function reset() {
69
eaten = null;
70
eatable = null;
71
position = {"x": 90, "y": 80};
72
direction = getRandomDirection();
73
due = getRandomDirection();
74
};
75
76
function onWholeSquare(x) {
77
return x % 10 === 0;
78
};
79
80
function oppositeDirection(dir) {
81
return dir === LEFT && RIGHT ||
82
dir === RIGHT && LEFT ||
83
dir === UP && DOWN || UP;
84
};
85
86
function makeEatable() {
87
direction = oppositeDirection(direction);
88
eatable = game.getTick();
89
};
90
91
function eat() {
92
eatable = null;
93
eaten = game.getTick();
94
};
95
96
function pointToCoord(x) {
97
return Math.round(x / 10);
98
};
99
100
function nextSquare(x, dir) {
101
var rem = x % 10;
102
if (rem === 0) {
103
return x;
104
} else if (dir === RIGHT || dir === DOWN) {
105
return x + (10 - rem);
106
} else {
107
return x - rem;
108
}
109
};
110
111
function onGridSquare(pos) {
112
return onWholeSquare(pos.y) && onWholeSquare(pos.x);
113
};
114
115
function secondsAgo(tick) {
116
return (game.getTick() - tick) / Pacman.FPS;
117
};
118
119
function getColour() {
120
if (eatable) {
121
if (secondsAgo(eatable) > 5) {
122
return game.getTick() % 20 > 10 ? "#FFFFFF" : "#0000BB";
123
} else {
124
return "#0000BB";
125
}
126
} else if(eaten) {
127
return "#222";
128
}
129
return colour;
130
};
131
132
function draw(ctx) {
133
134
var s = map.blockSize,
135
top = (position.y/10) * s,
136
left = (position.x/10) * s;
137
138
if (eatable && secondsAgo(eatable) > 8) {
139
eatable = null;
140
}
141
142
if (eaten && secondsAgo(eaten) > 3) {
143
eaten = null;
144
}
145
146
var tl = left + s;
147
var base = top + s - 3;
148
var inc = s / 10;
149
150
var high = game.getTick() % 10 > 5 ? 3 : -3;
151
var low = game.getTick() % 10 > 5 ? -3 : 3;
152
153
ctx.fillStyle = getColour();
154
ctx.beginPath();
155
156
ctx.moveTo(left, base);
157
158
ctx.quadraticCurveTo(left, top, left + (s/2), top);
159
ctx.quadraticCurveTo(left + s, top, left+s, base);
160
161
// Wavy things at the bottom
162
ctx.quadraticCurveTo(tl-(inc*1), base+high, tl - (inc * 2), base);
163
ctx.quadraticCurveTo(tl-(inc*3), base+low, tl - (inc * 4), base);
164
ctx.quadraticCurveTo(tl-(inc*5), base+high, tl - (inc * 6), base);
165
ctx.quadraticCurveTo(tl-(inc*7), base+low, tl - (inc * 8), base);
166
ctx.quadraticCurveTo(tl-(inc*9), base+high, tl - (inc * 10), base);
167
168
ctx.closePath();
169
ctx.fill();
170
171
ctx.beginPath();
172
ctx.fillStyle = "#FFF";
173
ctx.arc(left + 6,top + 6, s / 6, 0, 300, false);
174
ctx.arc((left + s) - 6,top + 6, s / 6, 0, 300, false);
175
ctx.closePath();
176
ctx.fill();
177
178
var f = s / 12;
179
var off = {};
180
off[RIGHT] = [f, 0];
181
off[LEFT] = [-f, 0];
182
off[UP] = [0, -f];
183
off[DOWN] = [0, f];
184
185
ctx.beginPath();
186
ctx.fillStyle = "#000";
187
ctx.arc(left+6+off[direction][0], top+6+off[direction][1],
188
s / 15, 0, 300, false);
189
ctx.arc((left+s)-6+off[direction][0], top+6+off[direction][1],
190
s / 15, 0, 300, false);
191
ctx.closePath();
192
ctx.fill();
193
194
};
195
196
function pane(pos) {
197
198
if (pos.y === 100 && pos.x >= 190 && direction === RIGHT) {
199
return {"y": 100, "x": -10};
200
}
201
202
if (pos.y === 100 && pos.x <= -10 && direction === LEFT) {
203
return position = {"y": 100, "x": 190};
204
}
205
206
return false;
207
};
208
209
function move(ctx) {
210
211
var oldPos = position,
212
onGrid = onGridSquare(position),
213
npos = null;
214
215
if (due !== direction) {
216
217
npos = getNewCoord(due, position);
218
219
if (onGrid &&
220
map.isFloorSpace({
221
"y":pointToCoord(nextSquare(npos.y, due)),
222
"x":pointToCoord(nextSquare(npos.x, due))})) {
223
direction = due;
224
} else {
225
npos = null;
226
}
227
}
228
229
if (npos === null) {
230
npos = getNewCoord(direction, position);
231
}
232
233
if (onGrid &&
234
map.isWallSpace({
235
"y" : pointToCoord(nextSquare(npos.y, direction)),
236
"x" : pointToCoord(nextSquare(npos.x, direction))
237
})) {
238
239
due = getRandomDirection();
240
return move(ctx);
241
}
242
243
position = npos;
244
245
var tmp = pane(position);
246
if (tmp) {
247
position = tmp;
248
}
249
250
due = getRandomDirection();
251
252
return {
253
"new" : position,
254
"old" : oldPos
255
};
256
};
257
258
return {
259
"eat" : eat,
260
"isVunerable" : isVunerable,
261
"isDangerous" : isDangerous,
262
"makeEatable" : makeEatable,
263
"reset" : reset,
264
"move" : move,
265
"draw" : draw
266
};
267
};
268
269
Pacman.User = function (game, map) {
270
271
var position = null,
272
direction = null,
273
eaten = null,
274
due = null,
275
lives = null,
276
score = 5,
277
keyMap = {};
278
279
keyMap[KEY.ARROW_LEFT] = LEFT;
280
keyMap[KEY.ARROW_UP] = UP;
281
keyMap[KEY.ARROW_RIGHT] = RIGHT;
282
keyMap[KEY.ARROW_DOWN] = DOWN;
283
284
keyMap[KEY.A] = LEFT;
285
keyMap[KEY.W] = UP;
286
keyMap[KEY.D] = RIGHT;
287
keyMap[KEY.S] = DOWN;
288
289
function addScore(nScore) {
290
score += nScore;
291
if (score >= 10000 && score - nScore < 10000) {
292
lives += 1;
293
}
294
};
295
296
function theScore() {
297
return score;
298
};
299
300
function loseLife() {
301
lives -= 1;
302
};
303
304
function getLives() {
305
return lives;
306
};
307
308
function initUser() {
309
score = 0;
310
lives = 3;
311
newLevel();
312
}
313
314
function newLevel() {
315
resetPosition();
316
eaten = 0;
317
};
318
319
function resetPosition() {
320
position = {"x": 90, "y": 120};
321
direction = LEFT;
322
due = LEFT;
323
};
324
325
function reset() {
326
initUser();
327
resetPosition();
328
};
329
330
function keyDown(e) {
331
if (typeof keyMap[e.keyCode] !== "undefined") {
332
due = keyMap[e.keyCode];
333
e.preventDefault();
334
e.stopPropagation();
335
return false;
336
}
337
return true;
338
};
339
340
function getNewCoord(dir, current) {
341
return {
342
"x": current.x + (dir === LEFT && -2 || dir === RIGHT && 2 || 0),
343
"y": current.y + (dir === DOWN && 2 || dir === UP && -2 || 0)
344
};
345
};
346
347
function onWholeSquare(x) {
348
return x % 10 === 0;
349
};
350
351
function pointToCoord(x) {
352
return Math.round(x/10);
353
};
354
355
function nextSquare(x, dir) {
356
var rem = x % 10;
357
if (rem === 0) {
358
return x;
359
} else if (dir === RIGHT || dir === DOWN) {
360
return x + (10 - rem);
361
} else {
362
return x - rem;
363
}
364
};
365
366
function next(pos, dir) {
367
return {
368
"y" : pointToCoord(nextSquare(pos.y, dir)),
369
"x" : pointToCoord(nextSquare(pos.x, dir)),
370
};
371
};
372
373
function onGridSquare(pos) {
374
return onWholeSquare(pos.y) && onWholeSquare(pos.x);
375
};
376
377
function isOnSamePlane(due, dir) {
378
return ((due === LEFT || due === RIGHT) &&
379
(dir === LEFT || dir === RIGHT)) ||
380
((due === UP || due === DOWN) &&
381
(dir === UP || dir === DOWN));
382
};
383
384
function move(ctx) {
385
386
var npos = null,
387
nextWhole = null,
388
oldPosition = position,
389
block = null;
390
391
if (due !== direction) {
392
npos = getNewCoord(due, position);
393
394
if (isOnSamePlane(due, direction) ||
395
(onGridSquare(position) &&
396
map.isFloorSpace(next(npos, due)))) {
397
direction = due;
398
} else {
399
npos = null;
400
}
401
}
402
403
if (npos === null) {
404
npos = getNewCoord(direction, position);
405
}
406
407
if (onGridSquare(position) && map.isWallSpace(next(npos, direction))) {
408
direction = NONE;
409
}
410
411
if (direction === NONE) {
412
return {"new" : position, "old" : position};
413
}
414
415
if (npos.y === 100 && npos.x >= 190 && direction === RIGHT) {
416
npos = {"y": 100, "x": -10};
417
}
418
419
if (npos.y === 100 && npos.x <= -12 && direction === LEFT) {
420
npos = {"y": 100, "x": 190};
421
}
422
423
position = npos;
424
nextWhole = next(position, direction);
425
426
block = map.block(nextWhole);
427
428
if ((isMidSquare(position.y) || isMidSquare(position.x)) &&
429
block === Pacman.BISCUIT || block === Pacman.PILL) {
430
431
map.setBlock(nextWhole, Pacman.EMPTY);
432
addScore((block === Pacman.BISCUIT) ? 10 : 50);
433
eaten += 1;
434
435
if (eaten === 182) {
436
game.completedLevel();
437
}
438
439
if (block === Pacman.PILL) {
440
game.eatenPill();
441
}
442
}
443
444
return {
445
"new" : position,
446
"old" : oldPosition
447
};
448
};
449
450
function isMidSquare(x) {
451
var rem = x % 10;
452
return rem > 3 || rem < 7;
453
};
454
455
function calcAngle(dir, pos) {
456
if (dir == RIGHT && (pos.x % 10 < 5)) {
457
return {"start":0.25, "end":1.75, "direction": false};
458
} else if (dir === DOWN && (pos.y % 10 < 5)) {
459
return {"start":0.75, "end":2.25, "direction": false};
460
} else if (dir === UP && (pos.y % 10 < 5)) {
461
return {"start":1.25, "end":1.75, "direction": true};
462
} else if (dir === LEFT && (pos.x % 10 < 5)) {
463
return {"start":0.75, "end":1.25, "direction": true};
464
}
465
return {"start":0, "end":2, "direction": false};
466
};
467
468
function drawDead(ctx, amount) {
469
470
var size = map.blockSize,
471
half = size / 2;
472
473
if (amount >= 1) {
474
return;
475
}
476
477
ctx.fillStyle = "#FFFF00";
478
ctx.beginPath();
479
ctx.moveTo(((position.x/10) * size) + half,
480
((position.y/10) * size) + half);
481
482
ctx.arc(((position.x/10) * size) + half,
483
((position.y/10) * size) + half,
484
half, 0, Math.PI * 2 * amount, true);
485
486
ctx.fill();
487
};
488
489
function draw(ctx) {
490
491
var s = map.blockSize,
492
angle = calcAngle(direction, position);
493
494
ctx.fillStyle = "#FFFF00";
495
496
ctx.beginPath();
497
498
ctx.moveTo(((position.x/10) * s) + s / 2,
499
((position.y/10) * s) + s / 2);
500
501
ctx.arc(((position.x/10) * s) + s / 2,
502
((position.y/10) * s) + s / 2,
503
s / 2, Math.PI * angle.start,
504
Math.PI * angle.end, angle.direction);
505
506
ctx.fill();
507
};
508
509
initUser();
510
511
return {
512
"draw" : draw,
513
"drawDead" : drawDead,
514
"loseLife" : loseLife,
515
"getLives" : getLives,
516
"score" : score,
517
"addScore" : addScore,
518
"theScore" : theScore,
519
"keyDown" : keyDown,
520
"move" : move,
521
"newLevel" : newLevel,
522
"reset" : reset,
523
"resetPosition" : resetPosition
524
};
525
};
526
527
Pacman.Map = function (size) {
528
529
var height = null,
530
width = null,
531
blockSize = size,
532
pillSize = 0,
533
map = null;
534
535
function withinBounds(y, x) {
536
return y >= 0 && y < height && x >= 0 && x < width;
537
}
538
539
function isWall(pos) {
540
return withinBounds(pos.y, pos.x) && map[pos.y][pos.x] === Pacman.WALL;
541
}
542
543
function isFloorSpace(pos) {
544
if (!withinBounds(pos.y, pos.x)) {
545
return false;
546
}
547
var peice = map[pos.y][pos.x];
548
return peice === Pacman.EMPTY ||
549
peice === Pacman.BISCUIT ||
550
peice === Pacman.PILL;
551
}
552
553
function drawWall(ctx) {
554
555
var i, j, p, line;
556
557
ctx.strokeStyle = "#0000FF";
558
ctx.lineWidth = 5;
559
ctx.lineCap = "round";
560
561
for (i = 0; i < Pacman.WALLS.length; i += 1) {
562
line = Pacman.WALLS[i];
563
ctx.beginPath();
564
565
for (j = 0; j < line.length; j += 1) {
566
567
p = line[j];
568
569
if (p.move) {
570
ctx.moveTo(p.move[0] * blockSize, p.move[1] * blockSize);
571
} else if (p.line) {
572
ctx.lineTo(p.line[0] * blockSize, p.line[1] * blockSize);
573
} else if (p.curve) {
574
ctx.quadraticCurveTo(p.curve[0] * blockSize,
575
p.curve[1] * blockSize,
576
p.curve[2] * blockSize,
577
p.curve[3] * blockSize);
578
}
579
}
580
ctx.stroke();
581
}
582
}
583
584
function reset() {
585
map = Pacman.MAP.clone();
586
height = map.length;
587
width = map[0].length;
588
};
589
590
function block(pos) {
591
return map[pos.y][pos.x];
592
};
593
594
function setBlock(pos, type) {
595
map[pos.y][pos.x] = type;
596
};
597
598
function drawPills(ctx) {
599
600
if (++pillSize > 30) {
601
pillSize = 0;
602
}
603
604
for (i = 0; i < height; i += 1) {
605
for (j = 0; j < width; j += 1) {
606
if (map[i][j] === Pacman.PILL) {
607
ctx.beginPath();
608
609
ctx.fillStyle = "#000";
610
ctx.fillRect((j * blockSize), (i * blockSize),
611
blockSize, blockSize);
612
613
ctx.fillStyle = "#FFF";
614
ctx.arc((j * blockSize) + blockSize / 2,
615
(i * blockSize) + blockSize / 2,
616
Math.abs(5 - (pillSize/3)),
617
0,
618
Math.PI * 2, false);
619
ctx.fill();
620
ctx.closePath();
621
}
622
}
623
}
624
};
625
626
function draw(ctx) {
627
628
var i, j, size = blockSize;
629
630
ctx.fillStyle = "#000";
631
ctx.fillRect(0, 0, width * size, height * size);
632
633
drawWall(ctx);
634
635
for (i = 0; i < height; i += 1) {
636
for (j = 0; j < width; j += 1) {
637
drawBlock(i, j, ctx);
638
}
639
}
640
};
641
642
function drawBlock(y, x, ctx) {
643
644
var layout = map[y][x];
645
646
if (layout === Pacman.PILL) {
647
return;
648
}
649
650
ctx.beginPath();
651
652
if (layout === Pacman.EMPTY || layout === Pacman.BLOCK ||
653
layout === Pacman.BISCUIT) {
654
655
ctx.fillStyle = "#000";
656
ctx.fillRect((x * blockSize), (y * blockSize),
657
blockSize, blockSize);
658
659
if (layout === Pacman.BISCUIT) {
660
ctx.fillStyle = "#FFF";
661
ctx.fillRect((x * blockSize) + (blockSize / 2.5),
662
(y * blockSize) + (blockSize / 2.5),
663
blockSize / 6, blockSize / 6);
664
}
665
}
666
ctx.closePath();
667
};
668
669
reset();
670
671
return {
672
"draw" : draw,
673
"drawBlock" : drawBlock,
674
"drawPills" : drawPills,
675
"block" : block,
676
"setBlock" : setBlock,
677
"reset" : reset,
678
"isWallSpace" : isWall,
679
"isFloorSpace" : isFloorSpace,
680
"height" : height,
681
"width" : width,
682
"blockSize" : blockSize
683
};
684
};
685
686
Pacman.Audio = function(game) {
687
688
var files = [],
689
endEvents = [],
690
progressEvents = [],
691
playing = [];
692
693
function load(name, path, cb) {
694
695
var f = files[name] = document.createElement("audio");
696
697
progressEvents[name] = function(event) { progress(event, name, cb); };
698
699
f.addEventListener("canplaythrough", progressEvents[name], true);
700
f.setAttribute("preload", "true");
701
f.setAttribute("autobuffer", "true");
702
f.setAttribute("src", path);
703
f.pause();
704
};
705
706
function progress(event, name, callback) {
707
if (event.loaded === event.total && typeof callback === "function") {
708
callback();
709
files[name].removeEventListener("canplaythrough",
710
progressEvents[name], true);
711
}
712
};
713
714
function disableSound() {
715
for (var i = 0; i < playing.length; i++) {
716
files[playing[i]].pause();
717
files[playing[i]].currentTime = 0;
718
}
719
playing = [];
720
};
721
722
function ended(name) {
723
724
var i, tmp = [], found = false;
725
726
files[name].removeEventListener("ended", endEvents[name], true);
727
728
for (i = 0; i < playing.length; i++) {
729
if (!found && playing[i]) {
730
found = true;
731
} else {
732
tmp.push(playing[i]);
733
}
734
}
735
playing = tmp;
736
};
737
738
function play(name) {
739
if (!game.soundDisabled()) {
740
endEvents[name] = function() { ended(name); };
741
playing.push(name);
742
files[name].addEventListener("ended", endEvents[name], true);
743
files[name].play();
744
}
745
};
746
747
function pause() {
748
for (var i = 0; i < playing.length; i++) {
749
files[playing[i]].pause();
750
}
751
};
752
753
function resume() {
754
for (var i = 0; i < playing.length; i++) {
755
files[playing[i]].play();
756
}
757
};
758
759
return {
760
"disableSound" : disableSound,
761
"load" : load,
762
"play" : play,
763
"pause" : pause,
764
"resume" : resume
765
};
766
};
767
768
var PACMAN = (function () {
769
770
var state = WAITING,
771
audio = null,
772
ghosts = [],
773
ghostSpecs = ["#00FFDE", "#FF0000", "#FFB8DE", "#FFB847"],
774
eatenCount = 0,
775
level = 0,
776
tick = 0,
777
ghostPos, userPos,
778
stateChanged = true,
779
timerStart = null,
780
lastTime = 0,
781
ctx = null,
782
timer = null,
783
map = null,
784
user = null,
785
stored = null;
786
787
function getTick() {
788
return tick;
789
};
790
791
function drawScore(text, position) {
792
ctx.fillStyle = "#FFFFFF";
793
ctx.font = "12px BDCartoonShoutRegular";
794
ctx.fillText(text,
795
(position["new"]["x"] / 10) * map.blockSize,
796
((position["new"]["y"] + 5) / 10) * map.blockSize);
797
}
798
799
function dialog(text) {
800
ctx.fillStyle = "#FFFF00";
801
ctx.font = "18px Calibri";
802
var width = ctx.measureText(text).width,
803
x = ((map.width * map.blockSize) - width) / 2;
804
ctx.fillText(text, x, (map.height * 10) + 8);
805
}
806
807
function soundDisabled() {
808
return localStorage["soundDisabled"] === "true";
809
};
810
811
function startLevel() {
812
user.resetPosition();
813
for (var i = 0; i < ghosts.length; i += 1) {
814
ghosts[i].reset();
815
}
816
audio.play("start");
817
timerStart = tick;
818
setState(COUNTDOWN);
819
}
820
821
function startNewGame() {
822
setState(WAITING);
823
level = 1;
824
user.reset();
825
map.reset();
826
map.draw(ctx);
827
startLevel();
828
}
829
830
function keyDown(e) {
831
if (e.keyCode === KEY.N) {
832
startNewGame();
833
} else if (e.keyCode === KEY.S) {
834
audio.disableSound();
835
localStorage["soundDisabled"] = !soundDisabled();
836
} else if (e.keyCode === KEY.P && state === PAUSE) {
837
audio.resume();
838
map.draw(ctx);
839
setState(stored);
840
} else if (e.keyCode === KEY.P) {
841
stored = state;
842
setState(PAUSE);
843
audio.pause();
844
map.draw(ctx);
845
dialog("Paused");
846
} else if (state !== PAUSE) {
847
return user.keyDown(e);
848
}
849
return true;
850
}
851
852
function loseLife() {
853
setState(WAITING);
854
user.loseLife();
855
if (user.getLives() > 0) {
856
startLevel();
857
}
858
}
859
860
function setState(nState) {
861
state = nState;
862
stateChanged = true;
863
};
864
865
function collided(user, ghost) {
866
return (Math.sqrt(Math.pow(ghost.x - user.x, 2) +
867
Math.pow(ghost.y - user.y, 2))) < 10;
868
};
869
870
function drawFooter() {
871
872
var topLeft = (map.height * map.blockSize),
873
textBase = topLeft + 17;
874
875
ctx.fillStyle = "#000000";
876
ctx.fillRect(0, topLeft, (map.width * map.blockSize), 30);
877
878
ctx.fillStyle = "#FFFF00";
879
880
for (var i = 0, len = user.getLives(); i < len; i++) {
881
ctx.fillStyle = "#FFFF00";
882
ctx.beginPath();
883
ctx.moveTo(150 + (25 * i) + map.blockSize / 2,
884
(topLeft+1) + map.blockSize / 2);
885
886
ctx.arc(150 + (25 * i) + map.blockSize / 2,
887
(topLeft+1) + map.blockSize / 2,
888
map.blockSize / 2, Math.PI * 0.25, Math.PI * 1.75, false);
889
ctx.fill();
890
}
891
892
ctx.fillStyle = !soundDisabled() ? "#00FF00" : "#FF0000";
893
ctx.font = "bold 16px sans-serif";
894
//ctx.fillText("♪", 10, textBase);
895
ctx.fillText("s", 10, textBase);
896
897
ctx.fillStyle = "#FFFF00";
898
ctx.font = "14px Calibri";
899
ctx.fillText("Score: " + user.theScore(), 30, textBase);
900
ctx.fillText("Level: " + level, 260, textBase);
901
}
902
903
function redrawBlock(pos) {
904
map.drawBlock(Math.floor(pos.y/10), Math.floor(pos.x/10), ctx);
905
map.drawBlock(Math.ceil(pos.y/10), Math.ceil(pos.x/10), ctx);
906
}
907
908
function mainDraw() {
909
910
var diff, u, i, len, nScore;
911
912
ghostPos = [];
913
914
for (i = 0, len = ghosts.length; i < len; i += 1) {
915
ghostPos.push(ghosts[i].move(ctx));
916
}
917
u = user.move(ctx);
918
919
for (i = 0, len = ghosts.length; i < len; i += 1) {
920
redrawBlock(ghostPos[i].old);
921
}
922
redrawBlock(u.old);
923
924
for (i = 0, len = ghosts.length; i < len; i += 1) {
925
ghosts[i].draw(ctx);
926
}
927
user.draw(ctx);
928
929
userPos = u["new"];
930
931
for (i = 0, len = ghosts.length; i < len; i += 1) {
932
if (collided(userPos, ghostPos[i]["new"])) {
933
if (ghosts[i].isVunerable()) {
934
audio.play("eatghost");
935
ghosts[i].eat();
936
eatenCount += 1;
937
nScore = eatenCount * 50;
938
drawScore(nScore, ghostPos[i]);
939
user.addScore(nScore);
940
setState(EATEN_PAUSE);
941
timerStart = tick;
942
} else if (ghosts[i].isDangerous()) {
943
audio.play("die");
944
setState(DYING);
945
timerStart = tick;
946
}
947
}
948
}
949
};
950
951
function mainLoop() {
952
953
var diff;
954
955
if (state !== PAUSE) {
956
++tick;
957
}
958
959
map.drawPills(ctx);
960
961
if (state === PLAYING) {
962
mainDraw();
963
} else if (state === WAITING && stateChanged) {
964
stateChanged = false;
965
map.draw(ctx);
966
dialog("Press N to start a New game");
967
} else if (state === EATEN_PAUSE &&
968
(tick - timerStart) > (Pacman.FPS / 3)) {
969
map.draw(ctx);
970
setState(PLAYING);
971
} else if (state === DYING) {
972
if (tick - timerStart > (Pacman.FPS * 2)) {
973
loseLife();
974
} else {
975
redrawBlock(userPos);
976
for (i = 0, len = ghosts.length; i < len; i += 1) {
977
redrawBlock(ghostPos[i].old);
978
ghostPos.push(ghosts[i].draw(ctx));
979
}
980
user.drawDead(ctx, (tick - timerStart) / (Pacman.FPS * 2));
981
}
982
} else if (state === COUNTDOWN) {
983
984
diff = 5 + Math.floor((timerStart - tick) / Pacman.FPS);
985
986
if (diff === 0) {
987
map.draw(ctx);
988
setState(PLAYING);
989
} else {
990
if (diff !== lastTime) {
991
lastTime = diff;
992
map.draw(ctx);
993
dialog("Starting in: " + diff);
994
}
995
}
996
}
997
998
drawFooter();
999
}
1000
1001
function eatenPill() {
1002
audio.play("eatpill");
1003
timerStart = tick;
1004
eatenCount = 0;
1005
for (i = 0; i < ghosts.length; i += 1) {
1006
ghosts[i].makeEatable(ctx);
1007
}
1008
};
1009
1010
function completedLevel() {
1011
setState(WAITING);
1012
level += 1;
1013
map.reset();
1014
user.newLevel();
1015
startLevel();
1016
};
1017
1018
function keyPress(e) {
1019
if (state !== WAITING && state !== PAUSE) {
1020
e.preventDefault();
1021
e.stopPropagation();
1022
}
1023
};
1024
1025
function init(wrapper, root) {
1026
1027
var i, len, ghost,
1028
blockSize = wrapper.offsetWidth / 19,
1029
canvas = document.createElement("canvas");
1030
1031
canvas.setAttribute("width", (blockSize * 19) + "px");
1032
canvas.setAttribute("height", (blockSize * 22) + 30 + "px");
1033
1034
wrapper.appendChild(canvas);
1035
1036
ctx = canvas.getContext('2d');
1037
1038
audio = new Pacman.Audio({"soundDisabled":soundDisabled});
1039
map = new Pacman.Map(blockSize);
1040
user = new Pacman.User({
1041
"completedLevel" : completedLevel,
1042
"eatenPill" : eatenPill
1043
}, map);
1044
1045
for (i = 0, len = ghostSpecs.length; i < len; i += 1) {
1046
ghost = new Pacman.Ghost({"getTick":getTick}, map, ghostSpecs[i]);
1047
ghosts.push(ghost);
1048
}
1049
1050
map.draw(ctx);
1051
dialog("Loading ...");
1052
1053
var extension = Modernizr.audio.ogg ? 'ogg' : 'mp3';
1054
1055
var audio_files = [
1056
["start", root + "audio/opening_song." + extension],
1057
["die", root + "audio/die." + extension],
1058
["eatghost", root + "audio/eatghost." + extension],
1059
["eatpill", root + "audio/eatpill." + extension],
1060
["eating", root + "audio/eating.short." + extension],
1061
["eating2", root + "audio/eating.short." + extension]
1062
];
1063
1064
load(audio_files, function() { loaded(); });
1065
};
1066
1067
function load(arr, callback) {
1068
1069
if (arr.length === 0) {
1070
callback();
1071
} else {
1072
var x = arr.pop();
1073
audio.load(x[0], x[1], function() { load(arr, callback); });
1074
}
1075
};
1076
1077
function loaded() {
1078
1079
dialog("Press N to Start");
1080
1081
document.addEventListener("keydown", keyDown, true);
1082
document.addEventListener("keypress", keyPress, true);
1083
1084
timer = window.setInterval(mainLoop, 1000 / Pacman.FPS);
1085
};
1086
1087
return {
1088
"init" : init
1089
};
1090
1091
}());
1092
1093
/* Human readable keyCode index */
1094
var KEY = {'BACKSPACE': 8, 'TAB': 9, 'NUM_PAD_CLEAR': 12, 'ENTER': 13, 'SHIFT': 16, 'CTRL': 17, 'ALT': 18, 'PAUSE': 19, 'CAPS_LOCK': 20, 'ESCAPE': 27, 'SPACEBAR': 32, 'PAGE_UP': 33, 'PAGE_DOWN': 34, 'END': 35, 'HOME': 36, 'ARROW_LEFT': 37, 'ARROW_UP': 38, 'ARROW_RIGHT': 39, 'ARROW_DOWN': 40, 'PRINT_SCREEN': 44, 'INSERT': 45, 'DELETE': 46, 'SEMICOLON': 59, 'WINDOWS_LEFT': 91, 'WINDOWS_RIGHT': 92, 'SELECT': 93, 'NUM_PAD_ASTERISK': 106, 'NUM_PAD_PLUS_SIGN': 107, 'NUM_PAD_HYPHEN-MINUS': 109, 'NUM_PAD_FULL_STOP': 110, 'NUM_PAD_SOLIDUS': 111, 'NUM_LOCK': 144, 'SCROLL_LOCK': 145, 'SEMICOLON': 186, 'EQUALS_SIGN': 187, 'COMMA': 188, 'HYPHEN-MINUS': 189, 'FULL_STOP': 190, 'SOLIDUS': 191, 'GRAVE_ACCENT': 192, 'LEFT_SQUARE_BRACKET': 219, 'REVERSE_SOLIDUS': 220, 'RIGHT_SQUARE_BRACKET': 221, 'APOSTROPHE': 222};
1095
1096
(function () {
1097
/* 0 - 9 */
1098
for (var i = 48; i <= 57; i++) {
1099
KEY['' + (i - 48)] = i;
1100
}
1101
/* A - Z */
1102
for (i = 65; i <= 90; i++) {
1103
KEY['' + String.fromCharCode(i)] = i;
1104
}
1105
/* NUM_PAD_0 - NUM_PAD_9 */
1106
for (i = 96; i <= 105; i++) {
1107
KEY['NUM_PAD_' + (i - 96)] = i;
1108
}
1109
/* F1 - F12 */
1110
for (i = 112; i <= 123; i++) {
1111
KEY['F' + (i - 112 + 1)] = i;
1112
}
1113
})();
1114
1115
Pacman.WALL = 0;
1116
Pacman.BISCUIT = 1;
1117
Pacman.EMPTY = 2;
1118
Pacman.BLOCK = 3;
1119
Pacman.PILL = 4;
1120
1121
Pacman.MAP = [
1122
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
1123
[0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
1124
[0, 4, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 4, 0],
1125
[0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0],
1126
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
1127
[0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0],
1128
[0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0],
1129
[0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0],
1130
[2, 2, 2, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 2, 2, 2],
1131
[0, 0, 0, 0, 1, 0, 1, 0, 0, 3, 0, 0, 1, 0, 1, 0, 0, 0, 0],
1132
[2, 2, 2, 2, 1, 1, 1, 0, 3, 3, 3, 0, 1, 1, 1, 2, 2, 2, 2],
1133
[0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0],
1134
[2, 2, 2, 0, 1, 0, 1, 1, 1, 2, 1, 1, 1, 0, 1, 0, 2, 2, 2],
1135
[0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0],
1136
[0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
1137
[0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0],
1138
[0, 4, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 4, 0],
1139
[0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0],
1140
[0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0],
1141
[0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0],
1142
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
1143
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
1144
];
1145
1146
Pacman.WALLS = [
1147
1148
[{"move": [0, 9.5]}, {"line": [3, 9.5]},
1149
{"curve": [3.5, 9.5, 3.5, 9]}, {"line": [3.5, 8]},
1150
{"curve": [3.5, 7.5, 3, 7.5]}, {"line": [1, 7.5]},
1151
{"curve": [0.5, 7.5, 0.5, 7]}, {"line": [0.5, 1]},
1152
{"curve": [0.5, 0.5, 1, 0.5]}, {"line": [9, 0.5]},
1153
{"curve": [9.5, 0.5, 9.5, 1]}, {"line": [9.5, 3.5]}],
1154
1155
[{"move": [9.5, 1]},
1156
{"curve": [9.5, 0.5, 10, 0.5]}, {"line": [18, 0.5]},
1157
{"curve": [18.5, 0.5, 18.5, 1]}, {"line": [18.5, 7]},
1158
{"curve": [18.5, 7.5, 18, 7.5]}, {"line": [16, 7.5]},
1159
{"curve": [15.5, 7.5, 15.5, 8]}, {"line": [15.5, 9]},
1160
{"curve": [15.5, 9.5, 16, 9.5]}, {"line": [19, 9.5]}],
1161
1162
[{"move": [2.5, 5.5]}, {"line": [3.5, 5.5]}],
1163
1164
[{"move": [3, 2.5]},
1165
{"curve": [3.5, 2.5, 3.5, 3]},
1166
{"curve": [3.5, 3.5, 3, 3.5]},
1167
{"curve": [2.5, 3.5, 2.5, 3]},
1168
{"curve": [2.5, 2.5, 3, 2.5]}],
1169
1170
[{"move": [15.5, 5.5]}, {"line": [16.5, 5.5]}],
1171
1172
[{"move": [16, 2.5]}, {"curve": [16.5, 2.5, 16.5, 3]},
1173
{"curve": [16.5, 3.5, 16, 3.5]}, {"curve": [15.5, 3.5, 15.5, 3]},
1174
{"curve": [15.5, 2.5, 16, 2.5]}],
1175
1176
[{"move": [6, 2.5]}, {"line": [7, 2.5]}, {"curve": [7.5, 2.5, 7.5, 3]},
1177
{"curve": [7.5, 3.5, 7, 3.5]}, {"line": [6, 3.5]},
1178
{"curve": [5.5, 3.5, 5.5, 3]}, {"curve": [5.5, 2.5, 6, 2.5]}],
1179
1180
[{"move": [12, 2.5]}, {"line": [13, 2.5]}, {"curve": [13.5, 2.5, 13.5, 3]},
1181
{"curve": [13.5, 3.5, 13, 3.5]}, {"line": [12, 3.5]},
1182
{"curve": [11.5, 3.5, 11.5, 3]}, {"curve": [11.5, 2.5, 12, 2.5]}],
1183
1184
[{"move": [7.5, 5.5]}, {"line": [9, 5.5]}, {"curve": [9.5, 5.5, 9.5, 6]},
1185
{"line": [9.5, 7.5]}],
1186
[{"move": [9.5, 6]}, {"curve": [9.5, 5.5, 10.5, 5.5]},
1187
{"line": [11.5, 5.5]}],
1188
1189
1190
[{"move": [5.5, 5.5]}, {"line": [5.5, 7]}, {"curve": [5.5, 7.5, 6, 7.5]},
1191
{"line": [7.5, 7.5]}],
1192
[{"move": [6, 7.5]}, {"curve": [5.5, 7.5, 5.5, 8]}, {"line": [5.5, 9.5]}],
1193
1194
[{"move": [13.5, 5.5]}, {"line": [13.5, 7]},
1195
{"curve": [13.5, 7.5, 13, 7.5]}, {"line": [11.5, 7.5]}],
1196
[{"move": [13, 7.5]}, {"curve": [13.5, 7.5, 13.5, 8]},
1197
{"line": [13.5, 9.5]}],
1198
1199
[{"move": [0, 11.5]}, {"line": [3, 11.5]}, {"curve": [3.5, 11.5, 3.5, 12]},
1200
{"line": [3.5, 13]}, {"curve": [3.5, 13.5, 3, 13.5]}, {"line": [1, 13.5]},
1201
{"curve": [0.5, 13.5, 0.5, 14]}, {"line": [0.5, 17]},
1202
{"curve": [0.5, 17.5, 1, 17.5]}, {"line": [1.5, 17.5]}],
1203
[{"move": [1, 17.5]}, {"curve": [0.5, 17.5, 0.5, 18]}, {"line": [0.5, 21]},
1204
{"curve": [0.5, 21.5, 1, 21.5]}, {"line": [18, 21.5]},
1205
{"curve": [18.5, 21.5, 18.5, 21]}, {"line": [18.5, 18]},
1206
{"curve": [18.5, 17.5, 18, 17.5]}, {"line": [17.5, 17.5]}],
1207
[{"move": [18, 17.5]}, {"curve": [18.5, 17.5, 18.5, 17]},
1208
{"line": [18.5, 14]}, {"curve": [18.5, 13.5, 18, 13.5]},
1209
{"line": [16, 13.5]}, {"curve": [15.5, 13.5, 15.5, 13]},
1210
{"line": [15.5, 12]}, {"curve": [15.5, 11.5, 16, 11.5]},
1211
{"line": [19, 11.5]}],
1212
1213
[{"move": [5.5, 11.5]}, {"line": [5.5, 13.5]}],
1214
[{"move": [13.5, 11.5]}, {"line": [13.5, 13.5]}],
1215
1216
[{"move": [2.5, 15.5]}, {"line": [3, 15.5]},
1217
{"curve": [3.5, 15.5, 3.5, 16]}, {"line": [3.5, 17.5]}],
1218
[{"move": [16.5, 15.5]}, {"line": [16, 15.5]},
1219
{"curve": [15.5, 15.5, 15.5, 16]}, {"line": [15.5, 17.5]}],
1220
1221
[{"move": [5.5, 15.5]}, {"line": [7.5, 15.5]}],
1222
[{"move": [11.5, 15.5]}, {"line": [13.5, 15.5]}],
1223
1224
[{"move": [2.5, 19.5]}, {"line": [5, 19.5]},
1225
{"curve": [5.5, 19.5, 5.5, 19]}, {"line": [5.5, 17.5]}],
1226
[{"move": [5.5, 19]}, {"curve": [5.5, 19.5, 6, 19.5]},
1227
{"line": [7.5, 19.5]}],
1228
1229
[{"move": [11.5, 19.5]}, {"line": [13, 19.5]},
1230
{"curve": [13.5, 19.5, 13.5, 19]}, {"line": [13.5, 17.5]}],
1231
[{"move": [13.5, 19]}, {"curve": [13.5, 19.5, 14, 19.5]},
1232
{"line": [16.5, 19.5]}],
1233
1234
[{"move": [7.5, 13.5]}, {"line": [9, 13.5]},
1235
{"curve": [9.5, 13.5, 9.5, 14]}, {"line": [9.5, 15.5]}],
1236
[{"move": [9.5, 14]}, {"curve": [9.5, 13.5, 10, 13.5]},
1237
{"line": [11.5, 13.5]}],
1238
1239
[{"move": [7.5, 17.5]}, {"line": [9, 17.5]},
1240
{"curve": [9.5, 17.5, 9.5, 18]}, {"line": [9.5, 19.5]}],
1241
[{"move": [9.5, 18]}, {"curve": [9.5, 17.5, 10, 17.5]},
1242
{"line": [11.5, 17.5]}],
1243
1244
[{"move": [8.5, 9.5]}, {"line": [8, 9.5]}, {"curve": [7.5, 9.5, 7.5, 10]},
1245
{"line": [7.5, 11]}, {"curve": [7.5, 11.5, 8, 11.5]},
1246
{"line": [11, 11.5]}, {"curve": [11.5, 11.5, 11.5, 11]},
1247
{"line": [11.5, 10]}, {"curve": [11.5, 9.5, 11, 9.5]},
1248
{"line": [10.5, 9.5]}]
1249
];
1250
1251
Object.prototype.clone = function () {
1252
var i, newObj = (this instanceof Array) ? [] : {};
1253
for (i in this) {
1254
if (i === 'clone') {
1255
continue;
1256
}
1257
if (this[i] && typeof this[i] === "object") {
1258
newObj[i] = this[i].clone();
1259
} else {
1260
newObj[i] = this[i];
1261
}
1262
}
1263
return newObj;
1264
};
1265
1266
$(function(){
1267
var el = document.getElementById("pacman");
1268
1269
if (Modernizr.canvas && Modernizr.localstorage &&
1270
Modernizr.audio && (Modernizr.audio.ogg || Modernizr.audio.mp3)) {
1271
window.setTimeout(function () { PACMAN.init(el, "https://raw.githubusercontent.com/daleharvey/pacman/master/"); }, 0);
1272
} else {
1273
el.innerHTML = "Sorry, needs a decent browser<br /><small>" +
1274
"(firefox 3.6+, Chrome 4+, Opera 10+ and Safari 4+)</small>";
1275
}
1276
});
1277