Skip to content

Commit c05c0d8

Browse files
Andrew Bakerclaude
andcommitted
Add Snake and Pac-Man games to 404 page (v1.8.77)
- Snake: tick-based movement, wall/self collision, speed ramp every 5 apples - Pac-Man: 31×14 maze, 3 ghosts with chase AI, power pellets, 3 lives - Shared 4-directional d-pad for touch (snake/pacman) - Updated REST regex and score caps for both new games - Version bump to 1.8.77 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent dfb405c commit c05c0d8

3 files changed

Lines changed: 338 additions & 7 deletions

File tree

assets/cs-custom-404.js

Lines changed: 320 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ if(!CanvasRenderingContext2D.prototype.roundRect){
1515
var ctx=c.getContext('2d'),W=c.width,H=c.height;
1616

1717
/* ── Per-game leaderboards (top 10) ─────────────── */
18-
var GNAMES=['runner','jetpack','racer','miner','asteroids'];
18+
var GNAMES=['runner','jetpack','racer','miner','asteroids','snake','pacman'];
1919
var lbData={};
2020
GNAMES.forEach(function(g){
2121
var raw=localStorage.getItem('cs404_lb_'+g);
@@ -39,7 +39,7 @@ function renderLeaderboard(game){
3939
var panel=document.getElementById('cs404-lb-body');
4040
var title=document.getElementById('cs404-lb-title');
4141
if(!panel)return;
42-
var gname={runner:'Runner',jetpack:'Jetpack',racer:'Racer',miner:'Miner',asteroids:'Asteroids'};
42+
var gname={runner:'Runner',jetpack:'Jetpack',racer:'Racer',miner:'Miner',asteroids:'Asteroids',snake:'Snake',pacman:'Pac-Man'};
4343
if(title)title.textContent='\uD83C\uDFC6 '+(gname[game]||game)+' \u2014 Top 10';
4444
var lb=lbData[game];
4545
if(!lb||lb.length===0){panel.innerHTML='<p class="cs404-lb-empty">No scores yet \u2014 be the first!</p>';return;}
@@ -870,6 +870,302 @@ function asDraw(){
870870
el.addEventListener('mouseup',up);
871871
});
872872

873+
/* ═══════════════════════════════════════════════
874+
GAME 6 — SNAKE
875+
═══════════════════════════════════════════════ */
876+
var SN_CELL=20,SN_COLS=31,SN_ROWS=14;
877+
var snKeys={up:false,dn:false,lt:false,rt:false};
878+
var SN={run:false,over:false,score:0,fr:0,tick:8,dir:{x:1,y:0},nxt:{x:1,y:0},seg:[],apple:{x:15,y:7},newHi:false};
879+
function snPlace(){
880+
var ok=false,ax,ay,i;
881+
while(!ok){
882+
ax=1+Math.floor(Math.random()*(SN_COLS-2));
883+
ay=1+Math.floor(Math.random()*(SN_ROWS-2));
884+
ok=true;
885+
for(i=0;i<SN.seg.length;i++){if(SN.seg[i].x===ax&&SN.seg[i].y===ay){ok=false;break;}}
886+
}
887+
SN.apple={x:ax,y:ay};
888+
}
889+
function snReset(){
890+
SN.dir={x:1,y:0};SN.nxt={x:1,y:0};
891+
var mx=Math.floor(SN_COLS/2),my=Math.floor(SN_ROWS/2);
892+
SN.seg=[{x:mx,y:my},{x:mx-1,y:my},{x:mx-2,y:my}];
893+
SN.score=0;SN.fr=0;SN.tick=8;SN.newHi=false;
894+
namePending=false;particles=[];
895+
if(nameOverlay)nameOverlay.style.display='none';
896+
snPlace();SN.run=true;SN.over=false;
897+
}
898+
function snUpdate(){
899+
if(!SN.run||SN.over)return;
900+
SN.fr++;if(SN.fr<SN.tick)return;SN.fr=0;
901+
// queue direction, block 180 reversal
902+
var nd=SN.nxt;
903+
if(!(nd.x===-SN.dir.x&&nd.y===-SN.dir.y))SN.dir={x:nd.x,y:nd.y};
904+
var hd=SN.seg[0],nx=hd.x+SN.dir.x,ny=hd.y+SN.dir.y,i;
905+
if(nx<0||nx>=SN_COLS||ny<0||ny>=SN_ROWS){snDie();return;}
906+
for(i=0;i<SN.seg.length;i++){if(SN.seg[i].x===nx&&SN.seg[i].y===ny){snDie();return;}}
907+
SN.seg.unshift({x:nx,y:ny});
908+
if(nx===SN.apple.x&&ny===SN.apple.y){
909+
SN.score+=10;
910+
if(SN.score%50===0&&SN.tick>3)SN.tick--;
911+
snPlace();
912+
} else {SN.seg.pop();}
913+
}
914+
function snDraw(){
915+
ctx.fillStyle='rgba(8,20,40,0.96)';ctx.fillRect(0,0,W,H);
916+
// subtle grid
917+
ctx.fillStyle='rgba(42,96,144,0.15)';
918+
for(var gx=0;gx<SN_COLS;gx++)for(var gy=0;gy<SN_ROWS;gy++){ctx.beginPath();ctx.arc(gx*SN_CELL+SN_CELL/2,gy*SN_CELL+SN_CELL/2,1,0,Math.PI*2);ctx.fill();}
919+
// apple
920+
ctx.fillStyle='#ff3a3a';ctx.beginPath();ctx.arc(SN.apple.x*SN_CELL+SN_CELL/2,SN.apple.y*SN_CELL+SN_CELL/2,SN_CELL/2-2,0,Math.PI*2);ctx.fill();
921+
ctx.fillStyle='#55bb33';ctx.fillRect(SN.apple.x*SN_CELL+SN_CELL/2,SN.apple.y*SN_CELL+1,3,5);
922+
// snake body
923+
for(var i=SN.seg.length-1;i>=0;i--){
924+
var s=SN.seg[i],t=i/Math.max(SN.seg.length-1,1);
925+
ctx.fillStyle=i===0?'#44ff88':('rgba('+(54+Math.round(t*60))+','+(160-Math.round(t*60))+',80,1)');
926+
ctx.beginPath();ctx.roundRect(s.x*SN_CELL+2,s.y*SN_CELL+2,SN_CELL-4,SN_CELL-4,3);ctx.fill();
927+
}
928+
// eyes on head
929+
if(SN.seg.length>0){
930+
var h=SN.seg[0];
931+
var dx=SN.dir.x,dy=SN.dir.y;
932+
var cx2=h.x*SN_CELL+SN_CELL/2,cy2=h.y*SN_CELL+SN_CELL/2;
933+
var perp={x:-dy,y:dx};
934+
ctx.fillStyle='#111';
935+
ctx.beginPath();ctx.arc(cx2+dx*4+perp.x*3,cy2+dy*4+perp.y*3,2,0,Math.PI*2);ctx.fill();
936+
ctx.beginPath();ctx.arc(cx2+dx*4-perp.x*3,cy2+dy*4-perp.y*3,2,0,Math.PI*2);ctx.fill();
937+
}
938+
drawHiPanel('snake');drawScore(SN.score);drawParticles();
939+
if(!SN.run&&!SN.over)drawWelcome('Snake','Arrow keys or d-pad \u2022 eat apples');
940+
if(SN.over)drawGameOver(SN.score,SN.newHi);
941+
}
942+
function snDie(){SN.run=false;SN.over=true;SN.newHi=checkNewHi('snake',SN.score);}
943+
// Shared 4-directional d-pad for Snake and Pac-Man
944+
(function(){
945+
var map={'4up':{x:0,y:-1},'4dn':{x:0,y:1},'4lt':{x:-1,y:0},'4rt':{x:1,y:0}};
946+
Object.keys(map).forEach(function(id){
947+
var el=document.getElementById('cs404-'+id);if(!el)return;
948+
var dir=map[id];
949+
function press(e){
950+
e.preventDefault();
951+
if(currentGame==='snake'){if(!SN.run&&!SN.over)snReset();else if(SN.over)snReset();else SN.nxt={x:dir.x,y:dir.y};}
952+
else if(currentGame==='pacman'){if(!PM.run&&!PM.over&&!PM.win)pmReset();else if(PM.over||PM.win)pmReset();else PM.pNxt={x:dir.x,y:dir.y};}
953+
}
954+
el.addEventListener('touchstart',press,{passive:false});el.addEventListener('mousedown',press);
955+
});
956+
})();
957+
958+
/* ═══════════════════════════════════════════════
959+
GAME 7 — PAC-MAN
960+
═══════════════════════════════════════════════ */
961+
var PM_CELL=20,PM_COLS=31,PM_ROWS=14;
962+
// 31×14 maze: '#'=wall, '.'=dot, 'o'=power, ' '=open, 'G'=ghost-spawn
963+
var PM_MAP=[
964+
'#############################.#',
965+
'#....#.....#.....#.....#....#.#',
966+
'#.##.#.###.#.###.#.###.#.##.#.#',
967+
'#o#..#.###.#.###.#.###.#..#.#o#',
968+
'#.##.#.###.#.###.#.###.#.##.#.#',
969+
'#.............................#.',
970+
'##.##.##.### ###.##.##.####',
971+
'##.##.## GGGGGGGGGGG ##.##.####',
972+
'##.##.##.### ###.##.##.####',
973+
'#.............................#.',
974+
'#.##.#.###.#.###.#.###.#.##.#.#',
975+
'#o#..#.###.#.###.#.###.#..#.#o#',
976+
'#.##.#.###.#.###.#.###.#.##.#.#',
977+
'#....#.....#.....#.....#....#.#',
978+
];
979+
// Validate & normalise to exactly PM_COLS chars per row
980+
(function(){for(var i=0;i<PM_MAP.length;i++){while(PM_MAP[i].length<PM_COLS)PM_MAP[i]+=' ';PM_MAP[i]=PM_MAP[i].substring(0,PM_COLS);}})();
981+
function pmCell(r,c){if(r<0||r>=PM_ROWS||c<0||c>=PM_COLS)return'#';return PM_MAP[r][c];}
982+
function pmIsWall(r,c){return pmCell(r,c)==='#';}
983+
// Collect all dots and power pellets
984+
var PM={run:false,over:false,win:false,score:0,lives:3,newHi:false,
985+
px:0,py:0,pDir:{x:1,y:0},pNxt:{x:1,y:0},pMouth:0,pMouthDir:1,
986+
dots:[],total:0,scared:0,
987+
ghosts:[],ghostTimer:0};
988+
var pmKeys={up:false,dn:false,lt:false,rt:false};
989+
function pmInit(){
990+
PM.dots=[];PM.total=0;
991+
for(var r=0;r<PM_ROWS;r++){
992+
for(var c=0;c<PM_COLS;c++){
993+
var ch=PM_MAP[r][c];
994+
if(ch==='.'||ch==='o')PM.dots.push({r:r,c:c,type:ch,eaten:false});
995+
}
996+
}
997+
PM.total=PM.dots.length;
998+
}
999+
pmInit();
1000+
function pmReset(){
1001+
PM.score=0;PM.lives=3;PM.over=false;PM.win=false;PM.newHi=false;PM.scared=0;
1002+
namePending=false;particles=[];if(nameOverlay)nameOverlay.style.display='none';
1003+
pmInit();
1004+
PM.px=15*PM_CELL+PM_CELL/2;PM.py=13*PM_CELL+PM_CELL/2;
1005+
PM.pDir={x:1,y:0};PM.pNxt={x:1,y:0};PM.pMouth=0.25;PM.pMouthDir=1;
1006+
// 3 ghosts starting at row 7 col 10,15,20
1007+
PM.ghosts=[
1008+
{x:9*PM_CELL+PM_CELL/2,y:7*PM_CELL+PM_CELL/2,dir:{x:1,y:0},col:'#ff4444',mode:'chase',dead:false,ret:0},
1009+
{x:14*PM_CELL+PM_CELL/2,y:7*PM_CELL+PM_CELL/2,dir:{x:-1,y:0},col:'#ff88cc',mode:'scatter',dead:false,ret:0},
1010+
{x:19*PM_CELL+PM_CELL/2,y:7*PM_CELL+PM_CELL/2,dir:{x:1,y:0},col:'#44ccff',mode:'chase',dead:false,ret:0},
1011+
];
1012+
PM.run=true;
1013+
}
1014+
function pmTileRC(px,py){return{r:Math.round((py-PM_CELL/2)/PM_CELL),c:Math.round((px-PM_CELL/2)/PM_CELL)};}
1015+
function pmCanMove(px,py,dx,dy,spd){
1016+
var nx=px+dx*spd,ny=py+dy*spd;
1017+
// sample corners
1018+
var off=PM_CELL/2-3;
1019+
function blocked(x,y){
1020+
var r=Math.floor(y/PM_CELL),c=Math.floor(x/PM_CELL);
1021+
return pmIsWall(r,c);
1022+
}
1023+
if(dx!==0){
1024+
var cx=(dx>0?nx+off:nx-off);
1025+
return !blocked(cx,ny-off)&&!blocked(cx,ny+off);
1026+
} else {
1027+
var cy=(dy>0?ny+off:ny-off);
1028+
return !blocked(nx-off,cy)&&!blocked(nx+off,cy);
1029+
}
1030+
}
1031+
function pmMoveGhost(g){
1032+
if(g.dead)return;
1033+
var spd=PM.scared>0?1:1.5;
1034+
// every ~16px of movement, choose new dir at intersections
1035+
var tc=pmTileRC(g.x,g.y);
1036+
var cx=tc.c*PM_CELL+PM_CELL/2,cy=tc.r*PM_CELL+PM_CELL/2;
1037+
var atCenter=(Math.abs(g.x-cx)<spd+1&&Math.abs(g.y-cy)<spd+1);
1038+
if(atCenter){
1039+
g.x=cx;g.y=cy;
1040+
// pick best dir toward/away from pac or random
1041+
var dirs=[{x:1,y:0},{x:-1,y:0},{x:0,y:1},{x:0,y:-1}];
1042+
var best=null,bestScore=PM.scared>0?-Infinity:Infinity;
1043+
var pr=pmTileRC(PM.px,PM.py);
1044+
dirs.forEach(function(d){
1045+
if(d.x===-g.dir.x&&d.y===-g.dir.y)return; // no U-turn
1046+
if(pmIsWall(tc.r+d.y,tc.c+d.x))return;
1047+
var dr=tc.r+d.y-pr.r,dc=tc.c+d.x-pr.c;
1048+
var dist=dr*dr+dc*dc;
1049+
if(PM.scared>0){if(dist>bestScore){bestScore=dist;best=d;}}
1050+
else{if(dist<bestScore){bestScore=dist;best=d;}}
1051+
});
1052+
if(!best){dirs.forEach(function(d){if(!(d.x===-g.dir.x&&d.y===-g.dir.y)&&!pmIsWall(tc.r+d.y,tc.c+d.x))best=d;});}
1053+
if(best)g.dir=best;
1054+
}
1055+
g.x+=g.dir.x*spd;g.y+=g.dir.y*spd;
1056+
// clamp
1057+
if(g.x<PM_CELL/2)g.x=PM_CELL/2;if(g.x>PM_COLS*PM_CELL-PM_CELL/2)g.x=PM_COLS*PM_CELL-PM_CELL/2;
1058+
if(g.y<PM_CELL/2)g.y=PM_CELL/2;if(g.y>PM_ROWS*PM_CELL-PM_CELL/2)g.y=PM_ROWS*PM_CELL-PM_CELL/2;
1059+
}
1060+
function pmUpdate(){
1061+
if(!PM.run||PM.over||PM.win)return;
1062+
// pac movement
1063+
var spd=2;
1064+
var nd=PM.pNxt;
1065+
if(pmCanMove(PM.px,PM.py,nd.x,nd.y,spd))PM.pDir={x:nd.x,y:nd.y};
1066+
if(pmCanMove(PM.px,PM.py,PM.pDir.x,PM.pDir.y,spd)){PM.px+=PM.pDir.x*spd;PM.py+=PM.pDir.y*spd;}
1067+
PM.px=Math.max(PM_CELL/2,Math.min(PM_COLS*PM_CELL-PM_CELL/2,PM.px));
1068+
PM.py=Math.max(PM_CELL/2,Math.min(PM_ROWS*PM_CELL-PM_CELL/2,PM.py));
1069+
// mouth animation
1070+
PM.pMouth+=0.07*PM.pMouthDir;if(PM.pMouth>0.35||PM.pMouth<0.02)PM.pMouthDir*=-1;
1071+
// eat dots
1072+
var pr=pmTileRC(PM.px,PM.py);
1073+
PM.dots.forEach(function(d){
1074+
if(!d.eaten&&d.r===pr.r&&d.c===pr.c){
1075+
d.eaten=true;
1076+
if(d.type==='o'){PM.score+=50;PM.scared=240;}
1077+
else PM.score+=10;
1078+
}
1079+
});
1080+
// check win
1081+
if(PM.dots.every(function(d){return d.eaten;})){PM.win=true;PM.run=false;PM.newHi=checkNewHi('pacman',PM.score);return;}
1082+
// ghosts
1083+
if(PM.scared>0)PM.scared--;
1084+
PM.ghosts.forEach(function(g){
1085+
pmMoveGhost(g);
1086+
// collision with pac
1087+
var dx=g.x-PM.px,dy=g.y-PM.py;
1088+
if(Math.sqrt(dx*dx+dy*dy)<PM_CELL*0.75){
1089+
if(PM.scared>0&&!g.dead){
1090+
g.dead=true;PM.score+=200;
1091+
} else if(!g.dead){
1092+
PM.lives--;
1093+
if(PM.lives<=0){PM.run=false;PM.over=true;PM.newHi=checkNewHi('pacman',PM.score);}
1094+
else{// respawn pac
1095+
PM.px=15*PM_CELL+PM_CELL/2;PM.py=13*PM_CELL+PM_CELL/2;
1096+
PM.pDir={x:1,y:0};PM.pNxt={x:1,y:0};PM.scared=0;
1097+
}
1098+
}
1099+
}
1100+
// revive dead ghost after a moment
1101+
if(g.dead){g.ret++;if(g.ret>120){g.dead=false;g.ret=0;}}
1102+
});
1103+
}
1104+
function pmDraw(){
1105+
ctx.fillStyle='#000';ctx.fillRect(0,0,W,H);
1106+
// maze
1107+
for(var r=0;r<PM_ROWS;r++){
1108+
for(var c=0;c<PM_COLS;c++){
1109+
var ch=PM_MAP[r][c];
1110+
if(ch==='#'){
1111+
ctx.fillStyle='#1a3acc';
1112+
ctx.fillRect(c*PM_CELL,r*PM_CELL,PM_CELL,PM_CELL);
1113+
ctx.strokeStyle='#3355ff';ctx.lineWidth=1;
1114+
ctx.strokeRect(c*PM_CELL+0.5,r*PM_CELL+0.5,PM_CELL-1,PM_CELL-1);
1115+
}
1116+
}
1117+
}
1118+
// dots
1119+
PM.dots.forEach(function(d){
1120+
if(d.eaten)return;
1121+
if(d.type==='o'){
1122+
ctx.fillStyle='#ffcc00';ctx.beginPath();ctx.arc(d.c*PM_CELL+PM_CELL/2,d.r*PM_CELL+PM_CELL/2,4,0,Math.PI*2);ctx.fill();
1123+
} else {
1124+
ctx.fillStyle='#ffeecc';ctx.beginPath();ctx.arc(d.c*PM_CELL+PM_CELL/2,d.r*PM_CELL+PM_CELL/2,2,0,Math.PI*2);ctx.fill();
1125+
}
1126+
});
1127+
// pac
1128+
var ang=Math.atan2(PM.pDir.y,PM.pDir.x);
1129+
ctx.fillStyle='#ffff00';
1130+
ctx.beginPath();ctx.moveTo(PM.px,PM.py);
1131+
ctx.arc(PM.px,PM.py,PM_CELL/2-2,ang+PM.pMouth*Math.PI,ang+(2-PM.pMouth)*Math.PI);
1132+
ctx.closePath();ctx.fill();
1133+
// ghosts
1134+
PM.ghosts.forEach(function(g){
1135+
if(g.dead){
1136+
ctx.fillStyle='rgba(200,200,255,0.3)';ctx.beginPath();ctx.arc(g.x,g.y,PM_CELL/2-3,0,Math.PI*2);ctx.fill();return;
1137+
}
1138+
var col=PM.scared>0?(PM.scared<60&&Math.floor(PM.scared/8)%2===0?'#fff':'#2244ff'):g.col;
1139+
ctx.fillStyle=col;
1140+
// ghost body
1141+
ctx.beginPath();ctx.arc(g.x,g.y-2,PM_CELL/2-2,Math.PI,0);
1142+
ctx.lineTo(g.x+PM_CELL/2-2,g.y+PM_CELL/2-2);
1143+
var bw=(PM_CELL-4)/3;
1144+
for(var i=0;i<3;i++){ctx.lineTo(g.x+PM_CELL/2-2-i*bw,g.y+PM_CELL/2-2-(i%2===0?4:0));ctx.lineTo(g.x+PM_CELL/2-2-(i+1)*bw,g.y+PM_CELL/2-2-(i%2===1?4:0));}
1145+
ctx.lineTo(g.x-(PM_CELL/2-2),g.y+PM_CELL/2-2);ctx.closePath();ctx.fill();
1146+
// eyes (skip when scared)
1147+
if(PM.scared===0){
1148+
ctx.fillStyle='#fff';ctx.beginPath();ctx.arc(g.x-3,g.y-3,3,0,Math.PI*2);ctx.fill();ctx.beginPath();ctx.arc(g.x+3,g.y-3,3,0,Math.PI*2);ctx.fill();
1149+
ctx.fillStyle='#00f';ctx.beginPath();ctx.arc(g.x-3,g.y-3,1.5,0,Math.PI*2);ctx.fill();ctx.beginPath();ctx.arc(g.x+3,g.y-3,1.5,0,Math.PI*2);ctx.fill();
1150+
}
1151+
});
1152+
// HUD
1153+
ctx.save();ctx.fillStyle='rgba(0,0,0,0.6)';ctx.fillRect(0,0,W,18);
1154+
ctx.font='bold 11px monospace';ctx.fillStyle='#ffff00';ctx.textAlign='left';ctx.fillText('LIVES: '+'\u2665'.repeat(PM.lives),8,13);
1155+
ctx.fillStyle='#fff';ctx.textAlign='right';ctx.fillText(String(PM.score).padStart(6,'0'),W-8,13);
1156+
var lb=lbData['pacman'];if(lb&&lb.length>0){ctx.fillStyle='#ffcc00';ctx.textAlign='center';ctx.font='9px monospace';ctx.fillText('\uD83C\uDFC6 '+(lb[0].n||'Anonymous')+' '+lb[0].s,W/2,13);}
1157+
ctx.restore();
1158+
drawParticles();
1159+
if(!PM.run&&!PM.over&&!PM.win)drawWelcome('Pac-Man','Arrow keys or d-pad \u2022 eat all dots');
1160+
if(PM.win){
1161+
ctx.fillStyle='rgba(0,0,0,0.7)';ctx.fillRect(0,0,W,H);
1162+
ctx.textAlign='center';ctx.fillStyle='#ffff00';ctx.font='bold 18px monospace';ctx.fillText('YOU WIN!',W/2,H/2-12);
1163+
ctx.fillStyle='#fff';ctx.font='12px monospace';ctx.fillText('Score: '+PM.score,W/2,H/2+8);
1164+
ctx.fillStyle='#f57c00';ctx.font='bold 12px monospace';ctx.fillText('SPACE or TAP to play again',W/2,H/2+28);
1165+
}
1166+
if(PM.over)drawGameOver(PM.score,PM.newHi);
1167+
}
1168+
8731169
/* ── Input ──────────────────────────────────────── */
8741170
var keysDown={};
8751171
document.addEventListener('keydown',function(e){
@@ -898,6 +1194,22 @@ document.addEventListener('keydown',function(e){
8981194
}
8991195
return;
9001196
}
1197+
if(currentGame==='snake'){
1198+
if(e.code==='ArrowUp'||e.code==='KeyW'){e.preventDefault();SN.nxt={x:0,y:-1};}
1199+
else if(e.code==='ArrowDown'||e.code==='KeyS'){e.preventDefault();SN.nxt={x:0,y:1};}
1200+
else if(e.code==='ArrowLeft'||e.code==='KeyA'){e.preventDefault();SN.nxt={x:-1,y:0};}
1201+
else if(e.code==='ArrowRight'||e.code==='KeyD'){e.preventDefault();SN.nxt={x:1,y:0};}
1202+
if(e.code==='Space'){if(!SN.run&&!SN.over)snReset();else if(SN.over)snReset();}
1203+
return;
1204+
}
1205+
if(currentGame==='pacman'){
1206+
if(e.code==='ArrowUp'||e.code==='KeyW'){e.preventDefault();PM.pNxt={x:0,y:-1};}
1207+
else if(e.code==='ArrowDown'||e.code==='KeyS'){e.preventDefault();PM.pNxt={x:0,y:1};}
1208+
else if(e.code==='ArrowLeft'||e.code==='KeyA'){e.preventDefault();PM.pNxt={x:-1,y:0};}
1209+
else if(e.code==='ArrowRight'||e.code==='KeyD'){e.preventDefault();PM.pNxt={x:1,y:0};}
1210+
if(e.code==='Space'){if(!PM.run&&!PM.over&&!PM.win)pmReset();else if(PM.over||PM.win)pmReset();}
1211+
return;
1212+
}
9011213
if(e.code==='Space'||e.key===' '){
9021214
var el=document.getElementById('cs404-game');
9031215
if(el){var r=el.getBoundingClientRect();if(r.top<window.innerHeight&&r.bottom>0){e.preventDefault();onAction();}}
@@ -943,11 +1255,14 @@ function onAction(){
9431255
else if(currentGame==='racer'){if(!RC.run&&!RC.over)rcReset();else if(RC.over)rcReset();}
9441256
else if(currentGame==='miner'){if(!MM.run&&!MM.over)mmReset();else if(MM.over)mmReset();}
9451257
else if(currentGame==='asteroids'){if(!AS.run&&!AS.over)asReset();else if(AS.over)asReset();}
1258+
else if(currentGame==='snake'){if(!SN.run&&!SN.over)snReset();else if(SN.over)snReset();}
1259+
else if(currentGame==='pacman'){if(!PM.run&&!PM.over&&!PM.win)pmReset();else if(PM.over||PM.win)pmReset();}
9461260
}
9471261

9481262
/* ── Tab switching ──────────────────────────────── */
9491263
var mcCtrl=document.getElementById('cs404-miner-ctrl');
9501264
var asCtrl=document.getElementById('cs404-asteroids-ctrl');
1265+
var d4Ctrl=document.getElementById('cs404-4dir-ctrl');
9511266
document.querySelectorAll('.cs404-tab').forEach(function(tab){
9521267
tab.addEventListener('click',function(){
9531268
currentGame=tab.getAttribute('data-game');
@@ -959,6 +1274,7 @@ document.querySelectorAll('.cs404-tab').forEach(function(tab){
9591274
asKeys.left=false;asKeys.right=false;asKeys.up=false;asKeys.shoot=false;
9601275
if(mcCtrl)mcCtrl.style.display=currentGame==='miner'?'flex':'none';
9611276
if(asCtrl)asCtrl.style.display=currentGame==='asteroids'?'flex':'none';
1277+
if(d4Ctrl)d4Ctrl.style.display=(currentGame==='snake'||currentGame==='pacman')?'grid':'none';
9621278
renderLeaderboard(currentGame);
9631279
});
9641280
});
@@ -970,6 +1286,8 @@ function loop(){
9701286
else if(currentGame==='racer'){rcUpdate();rcDraw();}
9711287
else if(currentGame==='miner'){mmUpdate();mmDraw();}
9721288
else if(currentGame==='asteroids'){asUpdate();asDraw();}
1289+
else if(currentGame==='snake'){snUpdate();snDraw();}
1290+
else if(currentGame==='pacman'){pmUpdate();pmDraw();}
9731291
updateParticles();
9741292
requestAnimationFrame(loop);
9751293
}

0 commit comments

Comments
 (0)