3
\$\begingroup\$

I created a Frogger game using JavaScript and HTML5. The game runs fine, but that might be because the complexity is so low. I'm looking for ways to improve the code.

Some aspects of this game are still buggy, but I'm not concerned about that right now. I want to improve the current functionality first, then worry about the small ideas.

I decided it would be best to make this using the Object Oriented Design Pattern. To add modularity, I wanted to create separate JavaScript files for each aspect of the game.

Main.js

var canvas = document.getElementById('my-canvas');
var ctx = canvas.getContext('2d');

var currentY = canvas.height - 25;

function createTimer() {
    ctx.fillStyle = 'black';
    ctx.fillRect(0, currentY, canvas.width, 25);
    ctx.fillStyle = 'yellow';
    ctx.fillText('Time: ' + currentGame.time, 20, currentY + 20);
    currentY -= 25;
}

// Determine whether Frogger has arrived safely at the pond.

function atHome(x) {
    return (x >= 50 && x <= 70) || 
           (x >= 130 && x <= 150) || 
           (x >= 210 && x <= 230) || 
           (x >= 290 && x <= 310) || 
           (x >= 370 && x <= 390);
}

// This is if Frogger runs into one of the walls at the top

function hitWalls(x) {
    return (x >= 0 && x < 50) || 
           (x > 70 && x < 130) || 
           (x > 150 && x < 210) || 
           (x > 230 && x < 290) || 
           (x > 310 || x < 370)
}

// After Frogger arrived home, the position is reset

function resetPosition() {
    frogger.x = canvas.width / 2;
    frogger.y = canvas.height - 45;
    frogger.row = 1;
}

// This is the main scene function

function createLevel() {
    currentY = canvas.height - 25;
    createTimer();
    createSideWalk();
    createRoad();
    createSideWalk();
    createRiver();
    createHome();
    drawFrogs();
    displayScores();
}

// The main game loop that will get animated 60 times per second

var animate;

function gameLoop() {
    createLevel();
    frogger.drawFrogger();
    animate = requestAnimationFrame(gameLoop)
}

function loseLife() {
    currentGame.lives--;
    currentGame.time = 50;
    if (currentGame.lives == 0) {
        gameOver();
    }
}

var timer = setInterval(function() {
    currentGame.time--;
    if (currentGame.time == 0) {
        loseLife();
    }
},1000);

function gameOver() {
    cancelAnimationFrame(animate);
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = 'black';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = 'silver';
    ctx.font = '50px Times New Roman'
    ctx.fillText('Game Over', 50, 50);
    checkHighScore();
}

function checkHighScore() {
    var froggerData = JSON.parse(localStorage['frogger-data']);
    var currentHigh = froggerData.highScore;
    if (currentGame.score > currentHigh) {
        froggerData.highScore = currentGame.score;
        localStorage['frogger-data'] = JSON.stringify(froggerData);
        ctx.fillStyle = 'gold';
        ctx.fillText('New High Score: ' + currentGame.score, 20, 150);
    }
}

gameLoop();

window.addEventListener('keydown', function(event) {
    switch (event.keyCode) {
        case 37: 
            frogger.moveLeft();
            break;
        case 38:
            frogger.moveUp();
            break;
        case 39:
            frogger.moveRight();
            break;
        case 40:
            frogger.moveDown();
            break;
    }
});

Keep in mind that this calls some methods and objects from other JavaScript files. But in my HTML file I listed them first.

I also created a Frogger object that stores our hero.

Frogger.js

var canvas = document.getElementById('my-canvas');
var ctx = canvas.getContext('2d');

function Frogger() {
    this.x = canvas.width / 2;
    this.y = canvas.height - 45;
    this.color = 'yellow';
    this.row = 1;
    this.onLog = false;

    // This array keeps track of which home Frogger returned to.

    this.safeFrogs = [
                        { 'x': 65, 'y': 60, 'safe': false },
                        { 'x': 145, 'y': 60, 'safe': false },
                        { 'x': 225, 'y': 60, 'safe': false },
                        { 'x': 305, 'y': 60, 'safe': false },
                        { 'x': 380, 'y': 60, 'safe': false }
                     ];

    // This checks if Frogger has arrived home safely

    this.addFrogs = function() {
        if (this.y == 55 && atHome(this.x)) {

            if      (this.x <= 70)  { this.safeFrogs[0].safe = true; }
            else if (this.x <= 150) { this.safeFrogs[1].safe = true; }
            else if (this.x <= 230) { this.safeFrogs[2].safe = true; }
            else if (this.x <= 310) { this.safeFrogs[3].safe = true; }
            else                    { this.safeFrogs[4].safe = true; }

            currentGame.score += 100;
            currentGame.time = 50;
            resetPosition();
        }
    }

    this.moveLeft = function() {
        if (this.x >= 20) {
            this.x -= 20;
            this.drawFrogger();
        }
    }

    this.moveRight = function() {
        if (this.x <= canvas.width - 40) {
            this.x += 20;
            this.drawFrogger();
        }
    }

    this.moveUp = function() {
        if (this.y >= 60) {
            this.y -= 25;
            this.row++;

            this.addFrogs();
            this.drawFrogger();
        }
    }

    this.moveDown = function() {
        if (this.y <= canvas.height - 60) {
            this.y += 25;
            this.row--;
            this.drawFrogger();
        }
    }

    // This part is still buggy, but it's acceptable for now. This basically determines whether Frogger has ran into a vehicle

    this.detectCollision = function() {
        if (vehicles.find(v => 
            (v.y >= this.y - 10) && 
            (v.y <= this.y) && 
            (v.x >= this.x - (v.length)) 
            && (v.x <= this.x - 14))
        ) {
            loseLife();
            resetPosition();
        }
    }

    // Determines whether Frogger ran into one of the walls at the top of the board

    this.hitWall = function() {
        if (this.y == 55 && (hitWalls(this.x))) {
            loseLife();
            resetPosition();
        }
    }

    this.jumpOnLog = function() {

        if (logs.find(l => l.row == this.row && this.x <= l.x + l.length && this.x >= l.x)) {
            this.onLog = true;
        }
        else {
            this.onLog = false;
        }
    }

    this.drown = function() {
        if (this.row >= 8 && this.row <= 12) {
            if (!this.onLog) {
                loseLife();
                resetPosition();
            }
        }
    }

    // Frogger will move when on the log

    this.drift = function() {
        if (this.onLog) {
            var logType = logs.find(l => l.row == this.row);
            this.x += logType.direction == 'right' ? (logType.speed * 2) : (logType.speed * -2);
        }
    }

    // Two separate functions for drawing our hero, since there is a lot of code

    this.drawBody = function() {
        ctx.fillStyle = this.color;
        ctx.beginPath();
        ctx.arc(this.x, this.y + 6, 6, 0, 2 * Math.PI);
        ctx.fill();
    }

    this.drawLegs = function() {
        ctx.strokeStyle = 'darkgreen';
        ctx.lineWidth = 2;
        ctx.moveTo(this.x - 3, this.y);
        ctx.lineTo(this.x - 5, this.y - 5);
        ctx.stroke();
        ctx.moveTo(this.x + 3, this.y);
        ctx.lineTo(this.x + 5, this.y - 5);
        ctx.stroke();
        ctx.moveTo(this.x - 3, this.y + 10);
        ctx.lineTo(this.x - 5, this.y + 15);
        ctx.stroke();
        ctx.moveTo(this.x + 3, this.y + 10);
        ctx.lineTo(this.x + 5, this.y + 15);
        ctx.stroke();
    }

    this.checkObstacles = function() {
        this.detectCollision();
        this.hitWall();
        this.jumpOnLog();
        this.drift();
        this.drown();
    }

    this.drawFrogger = function() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        createLevel();
        this.checkObstacles();
        this.drawBody();
        this.drawLegs();
    }

}

var frogger = new Frogger();

I also had to create a scene, that keeps track of the background. I created separate functions for each of the general areas. Each of them has a different background.

scene.js

function createSideWalk() {
    ctx.fillStyle = 'purple';
    ctx.fillRect(0, currentY, canvas.width, 25);
    currentY -= 25;
}

function createVehicles() {
    for (var i = 0; i < vehicles.length; i++) {
        ctx.fillStyle = vehicles[i].color;
        ctx.fillRect(vehicles[i].x, vehicles[i].y, vehicles[i].length, 15);

        // Add wheels to vehicles

        ctx.fillStyle = vehicles[i].wheelColor;
        ctx.fillRect(vehicles[i].x, (vehicles[i].y - 2), 5, 3);
        ctx.fillRect(vehicles[i].x, (vehicles[i].y + 15), 5, 3);
        ctx.fillRect(vehicles[i].x + (vehicles[i].length - 5), (vehicles[i].y - 2), 5, 3);
        ctx.fillRect(vehicles[i].x + (vehicles[i].length - 5), (vehicles[i].y + 15), 5, 3);

        vehicles[i].move();
    }
}

function createRoad() {
    for (var i = 0; i < 5; i++) {
        ctx.fillStyle = 'black';
        ctx.fillRect(0, currentY, canvas.width, 25);
        currentY -= 25;
    }

    createVehicles();

}

function createLogs() {
    for (var i = 0; i < logs.length; i++) {
        ctx.fillStyle = logs[i].color;
        ctx.fillRect(logs[i].x, logs[i].y, logs[i].length, 20);
        logs[i].move();
    }
}

function createRiver() {
    ctx.fillStyle = 'blue';

    for (var i = 1; i <= 5; i++) {
        ctx.fillRect(0, currentY, canvas.width, 25);
        currentY -= 25;
    }

    createLogs();

}

function createHome() {
    ctx.fillStyle = 'blue';
    ctx.fillRect(0, currentY, canvas.width, 25);
    for (var i = 0; i < 5; i++) {
        ctx.fillStyle = 'green';
        ctx.fillRect((80 * i) + 10, currentY, 40, 25);
    }
}

// This draws frogs in the homes Frogger has arrived safely

function drawFrogs() {
    frogger.safeFrogs.forEach(function(frog) {
        if (frog.safe) {
            ctx.strokeStyle = 'gold';
            ctx.moveTo(frog.x, frog.y);
            ctx.lineTo(frog.x - 5, frog.y - 5);
            ctx.stroke();
            ctx.moveTo(frog.x + 7, frog.y);
            ctx.lineTo(frog.x + 12, frog.y - 5);
            ctx.stroke();
            ctx.fillStyle = 'green';
            ctx.beginPath();
            ctx.arc(frog.x + 4, frog.y + 5, 4, 0, 2 * Math.PI);
            ctx.fill();
            ctx.moveTo(frog.x, frog.y + 10);
            ctx.lineTo(frog.x - 5, frog.y + 15);
            ctx.stroke();
            ctx.moveTo(frog.x + 7, frog.y + 10);
            ctx.lineTo(frog.x + 12, frog.y + 15);
            ctx.stroke();
        }
    });
}

A Vehicle object is also needed to keep track of our vehicles.

vehicles.js

function Vehicle(color, wheelColor, length, x, y, speed, direction) {
    this.color = color;
    this.wheelColor = wheelColor;
    this.length = length;
    this.x = x;
    this.y = y;
    this.speed = speed;
    this.direction = direction;

    this.move = function() {
        this.x += this.direction == 'right' ? this.speed : (this.speed * -1);
        if (this.direction == 'right') {
            if (this.x >= 450) {
                this.x = -1;
            }
        }
        else {
            if (this.x <= -40) {
                this.x = 400;
            }
        }
    }

}

var vehicles = [
                 new Vehicle('yellow', 'red', 20, 20, 330, 0.65, 'left'),
                 new Vehicle('yellow', 'red', 20, 220, 330, 0.65, 'left'),
                 new Vehicle('white', 'silver', 20, 20, 305, 0.55, 'right'),
                 new Vehicle('white', 'silver', 20, 220, 305, 0.55, 'right'),
                 new Vehicle('purple', 'yellow', 20, 20, 280, 0.75, 'left'),
                 new Vehicle('purple', 'yellow', 20, 220, 280, 0.75, 'left'),
                 new Vehicle('green', 'red', 20, 20, 255, 1, 'right'),
                 new Vehicle('green', 'red', 20, 220, 255, 1, 'right'),
                 new Vehicle('white', 'green', 40, 20, 230, 0.4, 'left'),
                 new Vehicle('white', 'green', 40, 220, 230, 0.4, 'left')
               ];

I also needed to create an object for Logs

logs.js

function Log(color, length, x, y, speed, row, direction) {
    this.color = color;
    this.length = length;
    this.x = x;
    this.y = y;
    this.speed = speed;
    this.row = row;
    this.direction = direction;

    this.move = function() {
        this.x += this.direction == 'right' ? this.speed : (this.speed * -1);

        if (this.direction == 'right') {
            if (this.x >= 450) {
                this.x = -1;
            }
        }
        else {
            if (this.x <= -50) {
                this.x = 400;
            }
        }
    }
}

var logs = [
             new Log('red', 50, 20, 180, 0.75, 8, 'left'),
             new Log('red', 50, 120, 180, 0.75, 8, 'left'),
             new Log('red', 50, 220, 180, 0.75, 8, 'left'),
             new Log('red', 50, 320, 180, 0.75, 8, 'left'),
             new Log('brown', 50, 20, 155, 0.6, 9, 'right'),
             new Log('brown', 50, 150, 155, 0.6, 9, 'right'),
             new Log('brown', 50, 280, 155, 0.6, 9, 'right'),
             new Log('brown', 100, 20, 130, 0.75, 10, 'right'),
             new Log('brown', 100, 220, 130, 0.75, 10, 'right'),
             new Log('red', 30, 20, 105, 1, 11, 'left'),
             new Log('red', 30, 1220, 105, 1, 11, 'left'),
             new Log('red', 30, 220, 105, 1, 11, 'left'),
             new Log('red', 30, 320, 105, 1, 11, 'left'),
             new Log('brown', 75, 20, 80, 0.5, 12, 'right'),
             new Log('brown', 75, 140, 80, 0.5, 12, 'right'),
             new Log('brown', 75, 260, 80, 0.5, 12, 'right')
           ];

Since I believe in descriptive variable and function names, this code should be mostly self-explanatory.

\$\endgroup\$
  • 1
    \$\begingroup\$ ES6 class syntax and modules would lend itself well to your design. Are you open to those suggestions? \$\endgroup\$ – le_m Apr 15 '17 at 0:20
2
\$\begingroup\$

There are many little errors. For example in main.js:

Five warnings
31  Missing semicolon.
63  Missing semicolon.
69  Use '===' to compare with '0'.
76  Use '===' to compare with '0'.
87  Missing semicolon.

Use a linter like JSHint to spot those on your own. I would also recommend to write a

const settings = {
    //example entries
    frogger = {
        speed = 10,
        life = 100,
        eyes = 1000
    },
    logs = {
        speed = 4,
        color = "#123321"
    }
}

This way, you have all settings in one place, you are safe you dont change them accidentally and, most important of all: you can read your code more easily. In scene.js, line 10, there is a "random" number, 25. Can you say where it comes from? What it stands for? If you had a variable here that refers to a setting, you could tell more easily.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.