Juego de Pacman realizado con Javascript

comentar



Todos o casi todos hemos echado unas partiditas al 'Comecocos', creado alla por principios de los 80 (ay, que jovencico era yo entonces). Pues bien, en una web/foro de programacion vi este codigo que publico un usuario del que desconozco sus datos. Este codigo realizado con Javascript (y un poquillo de CSS) reproduce fielmente el mitico juego de Pacman, con sonido incluido, la melodia del original, asi como los efectos de sonido. Para jugarlo, presionas Enter, lo mueves con las teclas de direccion y para pausarlo pulsa Escape. Si te gusta y quieres ponerlo en tu blog o web, copia el codigo de abajo o si lo prefieres tambien puedes descargartelo.




descargar ver codigo
<div id="pacman"></div>

<script>
Object.prototype.clone = function () {
    var i, newObj = (this instanceof Array) ? [] : {};
    for (i in this) {
        if (i === 'clone') {
            continue;
        }
        if (this[i] && typeof this[i] === "object") {
            newObj[i] = this[i].clone();
        } else {
            newObj[i] = this[i];
        }
    }
    return newObj;
};

var NONE        = 4,
    UP          = 3,
    LEFT        = 2,
    DOWN        = 1,
    RIGHT       = 0,
    WAITING     = 5,
    PAUSE       = 6,
    PLAYING     = 7,
    COUNTDOWN   = 8,
    EATEN_PAUSE = 9,
    DYING       = 10,
    Pacman      = {};

Pacman.WALL    = 0;
Pacman.BISCUIT = 1;
Pacman.EMPTY   = 2;
Pacman.BLOCK   = 3;
Pacman.PILL    = 4;

Pacman.MAP = [
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
 [0, 4, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 4, 0],
 [0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0],
 [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
 [0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0],
 [0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0],
 [0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0],
 [2, 2, 2, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 2, 2, 2],
 [0, 0, 0, 0, 1, 0, 1, 0, 0, 3, 0, 0, 1, 0, 1, 0, 0, 0, 0],
 [2, 2, 2, 2, 1, 1, 1, 0, 3, 3, 3, 0, 1, 1, 1, 2, 2, 2, 2],
 [0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0],
 [2, 2, 2, 0, 1, 0, 1, 1, 1, 2, 1, 1, 1, 0, 1, 0, 2, 2, 2],
 [0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0],
 [0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
 [0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0],
 [0, 4, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 4, 0],
 [0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0],
 [0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0],
 [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0],
 [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];

Pacman.WALLS = [
  
    [{"move": [0, 9.5]}, {"line": [3, 9.5]},
     {"curve": [3.5, 9.5, 3.5, 9]}, {"line": [3.5, 8]},
     {"curve": [3.5, 7.5, 3, 7.5]}, {"line": [1, 7.5]},
     {"curve": [0.5, 7.5, 0.5, 7]}, {"line": [0.5, 1]},
     {"curve": [0.5, 0.5, 1, 0.5]}, {"line": [9, 0.5]},
     {"curve": [9.5, 0.5, 9.5, 1]}, {"line": [9.5, 3.5]}],

    [{"move": [9.5, 1]},
     {"curve": [9.5, 0.5, 10, 0.5]}, {"line": [18, 0.5]},
     {"curve": [18.5, 0.5, 18.5, 1]}, {"line": [18.5, 7]},
     {"curve": [18.5, 7.5, 18, 7.5]}, {"line": [16, 7.5]},
     {"curve": [15.5, 7.5, 15.5, 8]}, {"line": [15.5, 9]},
     {"curve": [15.5, 9.5, 16, 9.5]}, {"line": [19, 9.5]}],

    [{"move": [2.5, 5.5]}, {"line": [3.5, 5.5]}],

    [{"move": [3, 2.5]},
     {"curve": [3.5, 2.5, 3.5, 3]},
     {"curve": [3.5, 3.5, 3, 3.5]},
     {"curve": [2.5, 3.5, 2.5, 3]},
     {"curve": [2.5, 2.5, 3, 2.5]}],

    [{"move": [15.5, 5.5]}, {"line": [16.5, 5.5]}],

    [{"move": [16, 2.5]}, {"curve": [16.5, 2.5, 16.5, 3]},
     {"curve": [16.5, 3.5, 16, 3.5]}, {"curve": [15.5, 3.5, 15.5, 3]},
     {"curve": [15.5, 2.5, 16, 2.5]}],

    [{"move": [6, 2.5]}, {"line": [7, 2.5]}, {"curve": [7.5, 2.5, 7.5, 3]},
     {"curve": [7.5, 3.5, 7, 3.5]}, {"line": [6, 3.5]},
     {"curve": [5.5, 3.5, 5.5, 3]}, {"curve": [5.5, 2.5, 6, 2.5]}],

    [{"move": [12, 2.5]}, {"line": [13, 2.5]}, {"curve": [13.5, 2.5, 13.5, 3]},
     {"curve": [13.5, 3.5, 13, 3.5]}, {"line": [12, 3.5]},
     {"curve": [11.5, 3.5, 11.5, 3]}, {"curve": [11.5, 2.5, 12, 2.5]}],

    [{"move": [7.5, 5.5]}, {"line": [9, 5.5]}, {"curve": [9.5, 5.5, 9.5, 6]},
     {"line": [9.5, 7.5]}],
    [{"move": [9.5, 6]}, {"curve": [9.5, 5.5, 10.5, 5.5]},
     {"line": [11.5, 5.5]}],


    [{"move": [5.5, 5.5]}, {"line": [5.5, 7]}, {"curve": [5.5, 7.5, 6, 7.5]},
     {"line": [7.5, 7.5]}],
    [{"move": [6, 7.5]}, {"curve": [5.5, 7.5, 5.5, 8]}, {"line": [5.5, 9.5]}],

    [{"move": [13.5, 5.5]}, {"line": [13.5, 7]},
     {"curve": [13.5, 7.5, 13, 7.5]}, {"line": [11.5, 7.5]}],
    [{"move": [13, 7.5]}, {"curve": [13.5, 7.5, 13.5, 8]},
     {"line": [13.5, 9.5]}],

    [{"move": [0, 11.5]}, {"line": [3, 11.5]}, {"curve": [3.5, 11.5, 3.5, 12]},
     {"line": [3.5, 13]}, {"curve": [3.5, 13.5, 3, 13.5]}, {"line": [1, 13.5]},
     {"curve": [0.5, 13.5, 0.5, 14]}, {"line": [0.5, 17]},
     {"curve": [0.5, 17.5, 1, 17.5]}, {"line": [1.5, 17.5]}],
    [{"move": [1, 17.5]}, {"curve": [0.5, 17.5, 0.5, 18]}, {"line": [0.5, 21]},
     {"curve": [0.5, 21.5, 1, 21.5]}, {"line": [18, 21.5]},
     {"curve": [18.5, 21.5, 18.5, 21]}, {"line": [18.5, 18]},
     {"curve": [18.5, 17.5, 18, 17.5]}, {"line": [17.5, 17.5]}],
    [{"move": [18, 17.5]}, {"curve": [18.5, 17.5, 18.5, 17]},
     {"line": [18.5, 14]}, {"curve": [18.5, 13.5, 18, 13.5]},
     {"line": [16, 13.5]}, {"curve": [15.5, 13.5, 15.5, 13]},
     {"line": [15.5, 12]}, {"curve": [15.5, 11.5, 16, 11.5]},
     {"line": [19, 11.5]}],

    [{"move": [5.5, 11.5]}, {"line": [5.5, 13.5]}],
    [{"move": [13.5, 11.5]}, {"line": [13.5, 13.5]}],

    [{"move": [2.5, 15.5]}, {"line": [3, 15.5]},
     {"curve": [3.5, 15.5, 3.5, 16]}, {"line": [3.5, 17.5]}],
    [{"move": [16.5, 15.5]}, {"line": [16, 15.5]},
     {"curve": [15.5, 15.5, 15.5, 16]}, {"line": [15.5, 17.5]}],

    [{"move": [5.5, 15.5]}, {"line": [7.5, 15.5]}],
    [{"move": [11.5, 15.5]}, {"line": [13.5, 15.5]}],
  
    [{"move": [2.5, 19.5]}, {"line": [5, 19.5]},
     {"curve": [5.5, 19.5, 5.5, 19]}, {"line": [5.5, 17.5]}],
    [{"move": [5.5, 19]}, {"curve": [5.5, 19.5, 6, 19.5]},
     {"line": [7.5, 19.5]}],

    [{"move": [11.5, 19.5]}, {"line": [13, 19.5]},
     {"curve": [13.5, 19.5, 13.5, 19]}, {"line": [13.5, 17.5]}],
    [{"move": [13.5, 19]}, {"curve": [13.5, 19.5, 14, 19.5]},
     {"line": [16.5, 19.5]}],

    [{"move": [7.5, 13.5]}, {"line": [9, 13.5]},
     {"curve": [9.5, 13.5, 9.5, 14]}, {"line": [9.5, 15.5]}],
    [{"move": [9.5, 14]}, {"curve": [9.5, 13.5, 10, 13.5]},
     {"line": [11.5, 13.5]}],

    [{"move": [7.5, 17.5]}, {"line": [9, 17.5]},
     {"curve": [9.5, 17.5, 9.5, 18]}, {"line": [9.5, 19.5]}],
    [{"move": [9.5, 18]}, {"curve": [9.5, 17.5, 10, 17.5]},
     {"line": [11.5, 17.5]}],

    [{"move": [8.5, 9.5]}, {"line": [8, 9.5]}, {"curve": [7.5, 9.5, 7.5, 10]},
     {"line": [7.5, 11]}, {"curve": [7.5, 11.5, 8, 11.5]},
     {"line": [11, 11.5]}, {"curve": [11.5, 11.5, 11.5, 11]},
     {"line": [11.5, 10]}, {"curve": [11.5, 9.5, 11, 9.5]},
     {"line": [10.5, 9.5]}]
];

Pacman.Ghost = function (game, map, colour) {
  
    var position  = null,
        direction = null,
        eatable   = null,
        eaten     = null,
        due       = null;
  
    function getNewCoord(dir, current) {  
        var speed = isVunerable() ? 1 : isHidden() ? 4 : 2;
        return {
            "x": addWithBounds(current.x,
                               (dir === LEFT && -speed ||
                                dir === RIGHT && speed || 0)),
            "y": addWithBounds(current.y,
                               (dir === DOWN && speed ||
                                dir === UP && -speed || 0))
        };
    }
  
    function addWithBounds(x1, x2) {
        var rem = x1 % 10, result = rem + x2;
        if (rem !== 0 && result > 10) {
            return x1 + (10 - rem);
        } else if(rem > 0 && result < 0) {
            return x1 - rem;
        }
        return x1 + x2;
    };
  
    function isVunerable() {
        return eatable !== null;
    };
  
    function isDangerous() {
        return eaten === null;
    };

    function isHidden() {
        return eatable === null && eaten !== null;
    };
  
    function getRandomDirection() {
        var moves = (direction === LEFT || direction === RIGHT) ?
            [UP, DOWN] : [LEFT, RIGHT];
        return moves[Math.floor(Math.random() * 2)];
    };
  
    function reset() {
        eaten     = null;
        eatable   = null;
        position  = {"x": 90, "y": 80};
        direction = getRandomDirection();
        due       = getRandomDirection();
    };
  
    function onWholeSquare(x) {
        return x % 10 === 0;
    };
  
    function makeEatable() {
        direction = (direction === LEFT) ? RIGHT :
            (direction === RIGHT) ? LEFT :
            (direction === UP) ? DOWN : UP;

        eatable = game.getTick();
    };

    function eat() {
        eatable = null;
        eaten   = game.getTick();
    };

    function pointToCoord(x) {
        return Math.round(x/10);
    };

    function nextSquare(x, dir) {
        var rem = x % 10;
        if (rem === 0) {
            return x;
        } else if (dir === RIGHT || dir === DOWN) {
            return x + (10 - rem);
        } else {
            return x - rem;
        }
    };

    function onGridSquare(pos) {
        return onWholeSquare(pos.y) && onWholeSquare(pos.x);
    };

    function secondsAgo(tick) {
        return (game.getTick() - tick) / 30;
    };

    function getColour() {
        if (eatable) {
            if (secondsAgo(eatable) > 5) {
                return game.getTick() % 20 > 10 ? "#FFF" : "#0000BB";
            } else {
                return "#0000BB";
            }
        } else if(eaten) {
            return "#222";
        }
        return colour;
    };

    function draw(ctx) {
 
        var top  = (position.y/10) * map.blockSize,
            left = (position.x/10) * map.blockSize;
  
        if (eatable && secondsAgo(eatable) > 8) {
            eatable = null;
        }
      
        if (eaten && secondsAgo(eaten) > 3) {
            eaten = null;
        }
      
        var tl = left+map.blockSize;
        var base = top+map.blockSize-3;
        var s = map.blockSize;
        var inc = map.blockSize / 10;

        var high = game.getTick() % 10 > 5 ? 3  : -3;
        var low  = game.getTick() % 10 > 5 ? -3 : 3;

        ctx.fillStyle = getColour();
        ctx.beginPath();

        ctx.moveTo(left, base);

        ctx.quadraticCurveTo(left, top, left + (s/2),  top);
        ctx.quadraticCurveTo(left+s, top, left+s,  base);
      
        // Wavy things at the bottom
        ctx.quadraticCurveTo(tl-(inc*1), base+high, tl-(inc*2),  base);
        ctx.quadraticCurveTo(tl-(inc*3), base+low, tl-(inc*4),  base);
        ctx.quadraticCurveTo(tl-(inc*5), base+high, tl-(inc*6),  base);
        ctx.quadraticCurveTo(tl-(inc*7), base+low, tl-(inc*8),  base);
        ctx.quadraticCurveTo(tl-(inc*9), base+high, tl-(inc*10), base);

        ctx.closePath();
        ctx.fill();

        ctx.beginPath();
        ctx.fillStyle = "#FFF";
        ctx.arc(left+6,top+6, map.blockSize / 6, 0, 300, false);
        ctx.arc((left+s)-6,top+6, map.blockSize / 6, 0, 300, false);
        ctx.closePath();
        ctx.fill();

        var f = map.blockSize / 12;
        var off = {};
        off[RIGHT] = [f, 0];
        off[LEFT]  = [-f, 0];
        off[UP]    = [0, -f];
        off[DOWN]  = [0, f];

        ctx.beginPath();
        ctx.fillStyle = "#000";
        ctx.arc(left+6+off[direction][0], top+6+off[direction][1],
                map.blockSize / 15, 0, 300, false);
        ctx.arc((left+s)-6+off[direction][0], top+6+off[direction][1],
                map.blockSize / 15, 0, 300, false);
        ctx.closePath();
        ctx.fill();

    };

    function pane(pos) {

        if (pos.y === 100 && pos.x >= 190 && direction === RIGHT) {
            return {"y": 100, "x": -10};
        }
      
        if (pos.y === 100 && pos.x <= -10 && direction === LEFT) {
            return position = {"y": 100, "x": 190};
        }

        return false;
    };
  
    function move(ctx) {
      
        var oldPos = position,
            onGrid = onGridSquare(position),
            npos   = null;
      
        if (due !== direction) {
          
            npos = getNewCoord(due, position);
          
            if (onGrid &&
                map.isFloorSpace(pointToCoord(nextSquare(npos.y, due)),
                                 pointToCoord(nextSquare(npos.x, due)))) {
                direction = due;
            } else {
                npos = null;
            }
        }
      
        if (npos === null) {
            npos = getNewCoord(direction, position);
        }
      
        if (onGrid &&
            map.isWallSpace(pointToCoord(nextSquare(npos.y, direction)),
                            pointToCoord(nextSquare(npos.x, direction)))) {
          
            due = getRandomDirection();          
            return move(ctx);
        }

        position = npos;      
      
        var tmp = pane(position);
        if (tmp) {
            position = tmp;
        }
      
        due = getRandomDirection();
      
        return {
            "new" : position,
            "old" : oldPos
        };
    };
  
    return {
        "eat"         : eat,
        "isVunerable" : isVunerable,
        "isDangerous" : isDangerous,
        "makeEatable" : makeEatable,
        "reset"       : reset,
        "move"        : move,
        "draw"        : draw
    };
};

Pacman.User = function (game, map) {
  
    var position  = null,
        direction = null,
        eaten     = null,
        due       = null,
        lives     = null,
        score     = 5,
        keyMap    = {};
  
    keyMap[37] = LEFT;
    keyMap[38] = UP;
    keyMap[39] = RIGHT;
    keyMap[40] = DOWN;

    function addScore(nScore) {
        score += nScore;
        if (score >= 10000 && score - nScore < 10000) {
            lives += 1;
        }
    };

    function theScore() {
        return score;
    };

    function loseLife() {
        lives -= 1;
    };

    function getLives() {
        return lives;
    };

    function initUser() {
        score = 0;
        lives = 3;
        newLevel();
    }
  
    function newLevel() {
        resetPosition();
        eaten = 0;
    };
  
    function resetPosition() {
        position  = {"x": 90, "y": 120};
        direction = LEFT;
        due       = LEFT;
    };
  
    function reset() {
        initUser();
        resetPosition();
    };      
  
    function keyDown(e) {
        if (typeof keyMap[e.keyCode] !== "undefined") {
            due = keyMap[e.keyCode];
            e.preventDefault();
            e.stopPropagation();
            return false;
        }
        return true;
 };

    function getNewCoord(dir, current) { 
        return {
            "x": current.x + (dir === LEFT && -2 || dir === RIGHT && 2 || 0),
            "y": current.y + (dir === DOWN && 2 || dir === UP    && -2 || 0)
        };
    };

    function onWholeSquare(x) {
        return x % 10 === 0;
    };

    function pointToCoord(x) {
        return Math.round(x/10);
    };
  
    function nextSquare(x, dir) {
        var rem = x % 10;
        if (rem === 0) {
            return x;
        } else if (dir === RIGHT || dir === DOWN) {
            return x + (10 - rem);
        } else {
            return x - rem;
        }
    };

    function onGridSquare(pos) {
        return onWholeSquare(pos.y) && onWholeSquare(pos.x);
    };

    function isOnSamePlane(due, dir) {
        return ((due === LEFT || due === RIGHT) &&
                (dir === LEFT || dir === RIGHT)) ||
            ((due === UP || due === DOWN) &&
             (dir === UP || dir === DOWN));
    };

    function move(ctx) {
      
        var npos = null;
      
        if (due !== direction) {
            npos = getNewCoord(due, position);
          
            if (isOnSamePlane(due, direction) ||
                (onGridSquare(position) &&
                 map.isFloorSpace(pointToCoord(nextSquare(npos.y, due)),
                                  pointToCoord(nextSquare(npos.x, due))))) {
                direction = due;
            } else {
                npos = null;
            }
        }

        if (npos === null) {
            npos = getNewCoord(direction, position);
        }
      
        if (onGridSquare(position) &&
            map.isWallSpace(pointToCoord(nextSquare(npos.y, direction)),
                            pointToCoord(nextSquare(npos.x, direction)))) {
            direction = NONE;
        }

        if (direction === NONE) {
            return {"new" : position, "old" : position};
        }
      
        if (npos.y === 100 && npos.x >= 190 && direction === RIGHT) {
            position = {"y": 100, "x": -10};
            return {"new" : position, "old" : position};
        }
      
        if (npos.y === 100 && npos.x <= -12 && direction === LEFT) {
            position = {"y": 100, "x": 190};
            return {"new" : position, "old" : position};
        }

        var oldPosition = position;      
        position = npos;      

        var block = map.block(pointToCoord(nextSquare(position.y, direction)),
                              pointToCoord(nextSquare(position.x, direction)));
      
      
        if ((isMidSquare(position.y) || isMidSquare(position.x)) &&
            block === Pacman.BISCUIT || block === Pacman.PILL) {
          
            map.setBlock(pointToCoord(nextSquare(position.y, direction)),
                         pointToCoord(nextSquare(position.x, direction)),
                         Pacman.EMPTY);

            addScore(10);
            eaten += 1;
          
            if (eaten === 182) {
                game.completedLevel();
            }

            if (block === Pacman.PILL) {
                game.eatenPill();
            }
        } 
              
        return {
            "new" : position,
            "old" : oldPosition
        };
    };

    function isMidSquare(x) {
        var rem = x % 10;
        return rem > 3 || rem < 7;
    };

    function calcAngle(dir, pos) {
        if (dir == RIGHT && (pos.x % 10 < 5)) {
            return {"start":0.25, "end":1.75, "direction": false};
        } else if (dir === DOWN && (pos.y % 10 < 5)) {
            return {"start":0.75, "end":2.25, "direction": false};
        } else if (dir === UP && (pos.y % 10 < 5)) {
            return {"start":1.25, "end":1.75, "direction": true};
        } else if (dir === LEFT && (pos.x % 10 < 5)) {           
            return {"start":0.75, "end":1.25, "direction": true};
        }
        return {"start":0, "end":2, "direction": false};
    };

    function drawDead(ctx, amount) {

        var size = map.blockSize,
            half = size / 2;

        if (amount >= 1) {
            return;
        }
        ctx.fillStyle = "#FFFF00";
        ctx.beginPath();      
        ctx.moveTo(((position.x/10) * size) + half,
                   ((position.y/10) * size) + half);
      
        ctx.arc(((position.x/10) * size) + half,
                ((position.y/10) * size) + half,
                half, 0, Math.PI * 2 * amount, true);
      
        ctx.fill();  
    };

    function draw(ctx) {

        var angle = calcAngle(direction, position);

        ctx.fillStyle = "#FFFF00";

        ctx.beginPath();      

        ctx.moveTo(((position.x/10) * map.blockSize) + map.blockSize / 2,
                   ((position.y/10) * map.blockSize) + map.blockSize / 2);
      
        ctx.arc(((position.x/10) * map.blockSize) + map.blockSize / 2,
                ((position.y/10) * map.blockSize) + map.blockSize / 2,
                map.blockSize / 2,
                Math.PI * angle.start,
                Math.PI * angle.end, angle.direction);
      
        ctx.fill();  
    };
  
    initUser();

    return {
        "draw"          : draw,
        "drawDead"      : drawDead,
        "loseLife"      : loseLife,
        "getLives"      : getLives,
        "score"         : score,
        "addScore"      : addScore,
        "theScore"      : theScore,
        "keyDown"       : keyDown,
        "move"          : move,
        "newLevel"      : newLevel,
        "reset"         : reset,
        "resetPosition" : resetPosition
    };
};

Pacman.Map = function (size) {
  
    var height    = null,
        width     = null,
        blockSize = size,
        pillSize  = 0,
        map       = null;
  
    function withinBounds(y, x) {
        return y >= 0 && y < height && x >= 0 && x < width;
    }
  
    function isWall(y, x) {
        return withinBounds(y, x) && map[y][x] === Pacman.WALL;
    }
  
    function isFloorSpace(y, x) {
        if (!withinBounds(y, x)) {
            return false;
        }
        var peice = map[y][x];
        return peice === Pacman.EMPTY ||
            peice === Pacman.BISCUIT ||
            peice === Pacman.PILL;
    }
  
    function drawWall(ctx) {

        var i, j, p, line;
      
        ctx.strokeStyle = "#0000FF";
        ctx.lineWidth   = 5;
        ctx.lineCap     = "round";
      
        for (i = 0; i < Pacman.WALLS.length; i += 1) {
            line = Pacman.WALLS[i];
            ctx.beginPath();

            for (j = 0; j < line.length; j += 1) {

                p = line[j];
              
                if (p.move) {
                    ctx.moveTo(p.move[0] * blockSize, p.move[1] * blockSize);
                } else if (p.line) {
                    ctx.lineTo(p.line[0] * blockSize, p.line[1] * blockSize);
                } else if (p.curve) {
                    ctx.quadraticCurveTo(p.curve[0] * blockSize,
                                         p.curve[1] * blockSize,
                                         p.curve[2] * blockSize,
                                         p.curve[3] * blockSize); 
                }
            }
            ctx.stroke();
        }
    }
  
    function reset() {     
        map    = Pacman.MAP.clone();
        height = map.length;
        width  = map[0].length;      
    };

    function block(y, x) {
        return map[y][x];
    };
  
    function setBlock(y, x, type) {
        map[y][x] = type;
    };

    function drawPills(ctx) {

        if (++pillSize > 30) {
            pillSize = 0;
        }
      
        for (i = 0; i < height; i += 1) {
      for (j = 0; j < width; j += 1) {
                if (map[i][j] === Pacman.PILL) {
                    ctx.beginPath();

                    ctx.fillStyle = "#000";
              ctx.fillRect((j * blockSize), (i * blockSize),
                                 blockSize, blockSize);

                    ctx.fillStyle = "#FFF";
                    ctx.arc((j * blockSize) + blockSize / 2,
                            (i * blockSize) + blockSize / 2,
                            Math.abs(5 - (pillSize/3)),
                            0,
                            Math.PI * 2, false);
                    ctx.fill();
                    ctx.closePath();
                }
      }
     }
    };
  
    function draw(ctx) {
      
        var i, j, size = blockSize;

        ctx.fillStyle = "#000";
     ctx.fillRect(0, 0, width * size, height * size);

        drawWall(ctx);
      
        for (i = 0; i < height; i += 1) {
      for (j = 0; j < width; j += 1) {
       drawBlock(i, j, ctx);
      }
     }
    };
  
    function drawBlock(y, x, ctx) {

        var layout = map[y][x];

        if (layout === Pacman.PILL) {
            return;
        }

        ctx.beginPath();
      
        if (layout === Pacman.EMPTY ||
            layout === Pacman.BLOCK ||
            layout === Pacman.BISCUIT) {
          
            ctx.fillStyle = "#000";
      ctx.fillRect((x * blockSize), (y * blockSize),
                         blockSize, blockSize);

            if (layout === Pacman.BISCUIT) {
                ctx.fillStyle = "#FFF";
          ctx.fillRect((x * blockSize) + (blockSize / 2.5),
                             (y * blockSize) + (blockSize / 2.5),
                             blockSize / 6, blockSize / 6);
         }
        }
        ctx.closePath();
    };

    reset();
  
    return {
        "draw"         : draw,
        "drawBlock"    : drawBlock,
        "drawPills"    : drawPills,
        "block"        : block,
        "setBlock"     : setBlock,
        "reset"        : reset,
        "isWallSpace"  : isWall,
        "isFloorSpace" : isFloorSpace,
        "height"       : height,
        "width"        : width,
        "blockSize"    : blockSize
    };
};

Pacman.Audio = function(game) {
  
    var files          = [],
        endEvents      = [],
        progressEvents = [],
        playing        = [];
  
    function load(name, path, cb) {

        var f = files[name] = document.createElement("audio");

        progressEvents[name] = function(event) { progress(event, name, cb); };
      
        f.addEventListener("canplaythrough", progressEvents[name], true);
        f.setAttribute("preload", "true");
        f.setAttribute("autobuffer", "true");
        f.setAttribute("src", path);
        f.pause();      
    };

    function progress(event, name, callback) {
        if (event.loaded === event.total && typeof callback === "function") {
            callback();
            files[name].removeEventListener("canplaythrough",
                                            progressEvents[name], true);
        }
    };

    function disableSound() {
        for (var i = 0; i < playing.length; i++) {
            files[playing[i]].pause();
            files[playing[i]].currentTime = 0;
        }
        playing = [];
    };

    function ended(name) {

        var i, tmp = [], found = false;

        files[name].removeEventListener("ended", endEvents[name], true);

        for (i = 0; i < playing.length; i++) {
            if (!found && playing[i]) {
                found = true;
            } else {
                tmp.push(playing[i]);
            }
        }
        playing = tmp;
    };

    function play(name) {
        if (!game.soundDisabled()) {
            endEvents[name] = function() { ended(name); };
            playing.push(name);
            files[name].addEventListener("ended", endEvents[name], true);
            files[name].play();
        }
    };

    function pause() {
        for (var i = 0; i < playing.length; i++) {
            files[playing[i]].pause();
        }
    };
  
    function resume() {
        for (var i = 0; i < playing.length; i++) {
            files[playing[i]].play();
        }      
    };
  
    return {
        "disableSound" : disableSound,
        "load"         : load,
        "play"         : play,
        "pause"        : pause,
        "resume"       : resume
    };
};

var PACMAN = (function () {

    var state        = WAITING,
        audio        = null,
        ghosts       = [],
        ghostSpecs   = ["#00FFDE", "#FF0000", "#FFB8DE", "#FFB847"],
        eatenCount   = 0,
        level        = 0,
        tick         = 0,
        ghostPos, userPos,
        stateChanged = true,
        timerStart   = null,
        lastTime     = 0,
        ctx          = null,
        timer        = null,
        map          = null,
        user         = null,
        stored       = null;

    function getTick() {
        return tick;
    };

    function drawScore(text, position) {
        ctx.fillStyle = "#FFFFFF";
        ctx.font      = "12px BDCartoonShoutRegular";
        ctx.fillText(text,
                     (position["new"]["x"] / 10) * map.blockSize,
                     ((position["new"]["y"] + 5) / 10) * map.blockSize);
    }
  
    function dialog(text) {
        ctx.fillStyle = "#FFFF00";
        ctx.font      = "14px BDCartoonShoutRegular";
        var width = ctx.measureText(text).width,
            x     = ((map.width * map.blockSize) - width) / 2;      
        ctx.fillText(text, x, (map.height * 10) + 8);
    }

    function soundDisabled() {
        return localStorage["soundDisabled"] === "true";
    };
  
    function startLevel() {      
        user.resetPosition();
        for (var i = 0; i < ghosts.length; i += 1) {
            ghosts[i].reset();
        }
        audio.play("start");
        timerStart = tick;
        setState(COUNTDOWN);
    }  

    function startNewGame() {
        setState(WAITING);
        level = 1;
        user.reset();
        map.reset();
        map.draw(ctx);
        startLevel();
    }

    function keyDown(e) {
        if (e.keyCode === 13) {
            startNewGame();
        } else if (e.keyCode === 83) {
            audio.disableSound();
            localStorage["soundDisabled"] = !soundDisabled();
        } else if (e.keyCode === 27 && state === PAUSE) {
            audio.resume();
            map.draw(ctx);
            setState(stored);
        } else if (e.keyCode === 27) {
            stored = state;
            setState(PAUSE);
            audio.pause();
            map.draw(ctx);
            dialog("PAUSA");
        } else if (state !== PAUSE) { 
            return user.keyDown(e);
        }
        return true;
    }  

    function loseLife() {      
        setState(WAITING);
        user.loseLife();
        if (user.getLives() > 0) {
            startLevel();
        }
    }

    function setState(nState) {
        state = nState;
        stateChanged = true;
    };
  
    function collided(user, ghost) {
        return (Math.sqrt(Math.pow(ghost.x - user.x, 2) +
                          Math.pow(ghost.y - user.y, 2))) < 10;
    };

    function drawFooter() {
      
        var topLeft  = (map.height * map.blockSize),
            textBase = topLeft + 17;
      
        ctx.fillStyle = "#000000";
        ctx.fillRect(0, topLeft, (map.width * map.blockSize), 30);
      
        ctx.fillStyle = "#FFFF00";

        for (var i = 0, len = user.getLives(); i < len; i++) {
            ctx.fillStyle = "#FFFF00";
            ctx.beginPath();
            ctx.moveTo(150 + (25 * i) + map.blockSize / 2,
                       (topLeft+1) + map.blockSize / 2);
          
            ctx.arc(150 + (25 * i) + map.blockSize / 2,
                    (topLeft+1) + map.blockSize / 2,
                    map.blockSize / 2, Math.PI * 0.25, Math.PI * 1.75, false);
            ctx.fill();
        }

        ctx.fillStyle = !soundDisabled() ? "#00FF00" : "#FF0000";
        ctx.font = "bold 16px sans-serif";
        //ctx.fillText("♪", 10, textBase);
        ctx.fillText("s", 10, textBase);

        ctx.fillStyle = "#FFFF00";
        ctx.font      = "14px BDCartoonShoutRegular";
        ctx.fillText("Puntos: " + user.theScore(), 30, textBase);
        ctx.fillText("Nivel: " + level, 260, textBase);
    }

    function redrawBlock(pos) {
        map.drawBlock(Math.floor(pos.y/10), Math.floor(pos.x/10), ctx);
        map.drawBlock(Math.ceil(pos.y/10), Math.ceil(pos.x/10), ctx);
    }

    function mainDraw() {

        var diff, u, i, len, nScore;
      
        ghostPos = [];

        for (i = 0, len = ghosts.length; i < len; i += 1) {
            ghostPos.push(ghosts[i].move(ctx));
        }
        u = user.move(ctx);
      
        for (i = 0, len = ghosts.length; i < len; i += 1) {
            redrawBlock(ghostPos[i].old);
        }
        redrawBlock(u.old);
      
        for (i = 0, len = ghosts.length; i < len; i += 1) {
            ghosts[i].draw(ctx);
        }                   
        user.draw(ctx);
      
        userPos = u["new"];
      
        for (i = 0, len = ghosts.length; i < len; i += 1) {
            if (collided(userPos, ghostPos[i]["new"])) {
                if (ghosts[i].isVunerable()) {
                    audio.play("eatghost");
                    ghosts[i].eat();
                    eatenCount += 1;
                    nScore = eatenCount * 50;
                    drawScore(nScore, ghostPos[i]);
                    user.addScore(nScore);                  
                    setState(EATEN_PAUSE);
                    timerStart = tick;
                } else if (ghosts[i].isDangerous()) {
                    audio.play("die");
                    setState(DYING);
                    timerStart = tick;
                }
            }
        }                           
    };

    function mainLoop() {

        var diff;

        if (state !== PAUSE) {
            ++tick;
        }

        map.drawPills(ctx);

        if (state === PLAYING) {
            mainDraw();
        } else if (state === WAITING && stateChanged) {          
            stateChanged = false;
            map.draw(ctx);
            dialog("ENTER para comenzar a jugar");          
        } else if (state === EATEN_PAUSE && tick - timerStart > 11) {
            map.draw(ctx);
            setState(PLAYING);
        } else if (state === DYING) {
            if (tick - timerStart > 60) {
                loseLife();
            } else {
                redrawBlock(userPos);
                for (i = 0, len = ghosts.length; i < len; i += 1) {
                    redrawBlock(ghostPos[i].old);
                    ghostPos.push(ghosts[i].draw(ctx));
                }                                 
                user.drawDead(ctx, (tick - timerStart) / 60);
            }
        } else if (state === COUNTDOWN) {
          
            diff = 5 + Math.floor((timerStart - tick) / 30);
          
            if (diff === 0) {
                map.draw(ctx);
                setState(PLAYING);
            } else {
                if (diff !== lastTime) {
                    lastTime = diff;
                    map.draw(ctx);
                    dialog("Comenzando en: " + diff);
                }
            }
        }

        drawFooter();
    }

    function eatenPill() {
        audio.play("eatpill");
        timerStart = tick;
        eatenCount = 0;
        for (i = 0; i < ghosts.length; i += 1) {
            ghosts[i].makeEatable(ctx);
        }      
    };
  
    function completedLevel() {
        setState(WAITING);
        level += 1;
        map.reset();
        user.newLevel();
        startLevel();
    };

    function keyPress(e) {
        if (state !== WAITING && state !== PAUSE) {
            e.preventDefault();
            e.stopPropagation();
        }
    };
  
    function init(wrapper, root) {
      
        var i, len, ghost,
            maxHeight = (wrapper.offsetHeight - 30) / 22,
            maxWidth  = wrapper.offsetWidth / 19,
            blockSize = Math.floor(Math.min(maxWidth, maxHeight)),
            canvas    = document.createElement("canvas");
      
        canvas.setAttribute("width", (blockSize * 19) + "px");
        canvas.setAttribute("height", (blockSize * 22) + 30 + "px");

        wrapper.appendChild(canvas);

        ctx  = canvas.getContext('2d');

        audio = new Pacman.Audio({"soundDisabled":soundDisabled});
        map   = new Pacman.Map(blockSize);
        user  = new Pacman.User({
            "completedLevel" : completedLevel,
            "eatenPill"      : eatenPill
        }, map);

        for (i = 0, len = ghostSpecs.length; i < len; i += 1) {
            ghost = new Pacman.Ghost({"getTick":getTick}, map, ghostSpecs[i]);
            ghosts.push(ghost);
        }
      
        map.draw(ctx);
        dialog("Cargando ...");

        var myAudio = document.createElement('audio');
        var canPlayOGG = !!(myAudio.canPlayType('audio/ogg').replace(/no/, ''));
        var canPlayMP3 = !!(myAudio.canPlayType('audio/mpeg').replace(/no/, ''));
        var extension = canPlayOGG ? 'ogg' : 'mp3';

        var audio_files = [
            ["start", root + "audio/opening_song." + extension],
            ["die", root + "audio/die." + extension],
            ["eatghost", root + "audio/eatghost." + extension],
            ["eatpill", root + "audio/eatpill." + extension],
            ["eating", root + "audio/eating.short." + extension],
            ["eating2", root + "audio/eating.short." + extension]
        ];

        load(audio_files);
    };

    function load(arr) {
      
        if (arr.length === 0) {
            loaded();
        } else {
            var x = arr.pop();
            audio.load(x[0], x[1], function() { load(arr); });
        }
    };
      
    function loaded() {

        dialog("ENTER PARA JUGAR");
      
        document.addEventListener("keydown", keyDown, true);
        document.addEventListener("keypress", keyPress, true);
      
        timer = window.setInterval(mainLoop, 35);
    };
  
    return {
        "init" : init
    };
  
}());
</script>
<script>
    var el = document.getElementById("pacman");

function supportsOggAudio() {
  var myAudio = document.createElement("audio");
  return typeof myAudio.canPlayType === "function" &&
     ("no" != myAudio.canPlayType("audio/ogg")) &&
     ("" != myAudio.canPlayType("audio/ogg"));
};

    function supports_canvas() {
      return !!document.createElement('canvas').getContext;
    };

    function supports_local_storage() {
      return ('localStorage' in window) && window['localStorage'] !== null;
    };

    function supports_audio() {
      var myAudio = document.createElement('audio');
      var canPlayOGG = typeof myAudio.canPlayType === "function" &&
        !!(myAudio.canPlayType('audio/ogg').replace(/no/, ''));
      var canPlayMP3 = typeof myAudio.canPlayType === "function" &&
        !!(myAudio.canPlayType('audio/mpeg').replace(/no/, ''));
      return canPlayOGG || canPlayMP3;
    };

    if (supports_canvas() && supports_local_storage() && supports_audio()) {
      window.setTimeout(function () { PACMAN.init(el, "http://arandomurl.com/random/pacman/"); }, 0);
    } else {
      el.innerHTML = "Tu navegador no es compatible";
    }
</script>
<style>
@font-face {
 font-family: 'BDCartoonShoutRegular';
    src: url('BD_Cartoon_Shout-webfont.ttf') format('truetype');
 font-weight: normal;
 font-style: normal;
}
#shim {
    font-family: BDCartoonShoutRegular;
    position:absolute;
    visibility:hidden;
}
#pacman {
    width:412px;
    height:450px;
    margin:20px auto;
}
</style>