<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Pacman JS David Carrero / carrero.es</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{background:#000;display:flex;flex-direction:column;align-items:center;
justify-content:center;min-height:100vh;font-family:monospace;color:#0f0;gap:8px}
h1{font-size:16px;letter-spacing:1px}
canvas{border:2px solid #222;cursor:none}
footer{font-size:11px;color:#444;text-align:center;line-height:1.6}
footer a{color:#555;text-decoration:none}
</style>
</head>
<body>
<h1>● PACMAN JS — <a href="https://carrero.es" style="color:#0a0;text-decoration:none">carrero.es</a></h1>
<canvas id="c"></canvas>
<footer>
Flechas: mover | R: reiniciar<br>
Versiones: <a href="https://programacion.net/codigo/pacman_64">C</a> ·
<a href="https://programacion.net/codigo/pacman-en-php-cli_1893">PHP</a> ·
<a href="https://programacion.net/codigo/pacman-en-python-con-curses_1894">Python</a>
</footer>
<script>
'use strict';
const ROWS=10,COLS=20,CS=28;
const canvas=document.getElementById('c');
const ctx=canvas.getContext('2d');
canvas.width=COLS*CS;
canvas.height=ROWS*CS+36;
// --- Estado ---
let board,py,px,m1y,m1x,m2y,m2x,pts,state,dir,nextDir,mouth,mouthOpen;
function init(){
board=Array.from({length:ROWS},()=>Array(COLS).fill(true));
py=0;px=0;
m1y=0;m1x=COLS-1;
m2y=ROWS-1;m2x=COLS-1;
board[py][px]=false;
pts=0;state='play';dir='R';nextDir=null;mouth=0;mouthOpen=true;
}
function hasDots(){return board.some(r=>r.some(c=>c));}
function hit(){return(m1x===px&&m1y===py)||(m2x===px&&m2y===py);}
function rnd(p,lim){return Math.max(0,Math.min(lim-1,p+(Math.random()<.5?1:-1)));}
function tryMove(){
if(!nextDir)return;
const d=nextDir; nextDir=null;
const dr={U:-1,D:1,L:0,R:0}[d];
const dc={U:0,D:0,L:-1,R:1}[d];
const nr=py+dr,nc=px+dc;
if(nr<0||nr>=ROWS||nc<0||nc>=COLS)return;
dir=d;py=nr;px=nc;
if(board[py][px]){board[py][px]=false;pts++;}
}
function update(){
if(state!=='play')return;
tryMove();
if(!hasDots()){state='win';draw();return;}
if(hit()){state='lose';draw();return;}
m1x=rnd(m1x,COLS);m1y=rnd(m1y,ROWS);
m2x=rnd(m2x,COLS);m2y=rnd(m2y,ROWS);
if(hit()){state='lose';}
draw();
}
// --- Dibujo ---
function drawPacman(x,y){
const cx=x+CS/2,cy=y+CS/2,r=CS/2-2;
const angle=mouthOpen?0.22*Math.PI:0.04*Math.PI;
const base={R:0,L:Math.PI,U:-Math.PI/2,D:Math.PI/2}[dir]||0;
ctx.fillStyle='#ff0';
ctx.beginPath();
ctx.moveTo(cx,cy);
ctx.arc(cx,cy,r,base+angle,base+Math.PI*2-angle);
ctx.closePath();
ctx.fill();
// eye
const ex=cx+Math.cos(base-Math.PI/3)*r*0.5;
const ey=cy+Math.sin(base-Math.PI/3)*r*0.5;
ctx.fillStyle='#000';
ctx.beginPath();ctx.arc(ex,ey,2,0,Math.PI*2);ctx.fill();
}
function drawGhost(x,y,color){
const cx=x+CS/2,cy=y+CS/2,r=CS/2-2;
ctx.fillStyle=color;
ctx.beginPath();
ctx.arc(cx,cy-2,r,Math.PI,0,false);
ctx.lineTo(cx+r,cy+r-1);
// wavy bottom (3 bumps)
const bw=r*2/3;
for(let i=2;i>=0;i--){
ctx.arc(cx-r+bw*i+bw/2,cy+r-1,bw/2,0,Math.PI,i%2===0);
}
ctx.lineTo(cx-r,cy+r-1);
ctx.closePath();
ctx.fill();
// eyes
[-0.3,0.3].forEach(ox=>{
ctx.fillStyle='#fff';
ctx.beginPath();ctx.arc(cx+ox*r,cy-2,r*0.22,0,Math.PI*2);ctx.fill();
ctx.fillStyle='#00e';
ctx.beginPath();ctx.arc(cx+ox*r+1,cy-1,r*0.11,0,Math.PI*2);ctx.fill();
});
}
function overlay(msg,color){
ctx.fillStyle='rgba(0,0,0,.78)';ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.textAlign='center';
ctx.fillStyle=color;ctx.font='bold 22px monospace';
ctx.fillText(msg,canvas.width/2,canvas.height/2-12);
ctx.fillStyle='#ccc';ctx.font='bold 14px monospace';
ctx.fillText('Puntos: '+(pts*15),canvas.width/2,canvas.height/2+14);
ctx.fillStyle='#555';ctx.font='13px monospace';
ctx.fillText('Pulsa R para reiniciar',canvas.width/2,canvas.height/2+38);
ctx.textAlign='left';
}
function draw(){
ctx.fillStyle='#111';ctx.fillRect(0,0,canvas.width,canvas.height);
// HUD
ctx.fillStyle='#0f0';ctx.font='bold 13px monospace';
ctx.fillText('Puntos: '+(pts*15)+' Flechas = mover R = reiniciar',8,20);
// dots
for(let r=0;r<ROWS;r++)for(let c=0;c<COLS;c++){
if(board[r][c]){
ctx.fillStyle='#484';
ctx.beginPath();ctx.arc(c*CS+CS/2,r*CS+36+CS/2,2.5,0,Math.PI*2);ctx.fill();
}
}
// monsters
drawGhost(m1x*CS,m1y*CS+36,'#e33');
drawGhost(m2x*CS,m2y*CS+36,'#e77');
// pacman or X
if(state==='lose'){
ctx.fillStyle='#f44';ctx.font='bold '+(CS)+'px monospace';
ctx.fillText('X',px*CS+4,py*CS+36+CS-2);
}else{
drawPacman(px*CS,py*CS+36);
}
if(state==='win')overlay('GANASTE!','#0f0');
if(state==='lose')overlay('PERDISTE!','#f44');
}
// --- Input ---
document.addEventListener('keydown',e=>{
if(state!=='play'){
if(e.key==='r'||e.key==='R'){init();draw();}
return;
}
const map={ArrowUp:'U',ArrowDown:'D',ArrowLeft:'L',ArrowRight:'R'};
if(map[e.key]){e.preventDefault();nextDir=map[e.key];}
if(e.key==='r'||e.key==='R'){init();draw();}
});
// Touch/swipe
let tx=0,ty=0;
canvas.addEventListener('touchstart',e=>{
tx=e.touches[0].clientX;ty=e.touches[0].clientY;e.preventDefault();
},{passive:false});
canvas.addEventListener('touchend',e=>{
const dx=e.changedTouches[0].clientX-tx,dy=e.changedTouches[0].clientY-ty;
if(Math.abs(dx)>Math.abs(dy))nextDir=dx>0?'R':'L';
else nextDir=dy>0?'D':'U';
e.preventDefault();
},{passive:false});
// --- Loop ---
setInterval(()=>{mouthOpen=!mouthOpen;draw();},200);
setInterval(update,120);
init();draw();
</script>
</body>
</html>
Pacman en JavaScript (HTML5 Canvas)
Versión jugable en el navegador del clásico Pacman, escrita como un único fichero HTML autocontenido. Usa Canvas 2D para dibujar el tablero, el personaje animado con boca que se abre y cierra, y los dos fantasmas con movimiento aleatorio. Compatible con escritorio y móvil; no requiere instalación ni servidor web, basta con abrir el HTML descargado.
Descargar adjuntos
COMPARTE ESTE TUTORIAL
COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP