5
\$\begingroup\$

i made a simple js snake game with canvas, the code is all mine so it can have some drawbacks or not so good parts. I will be glad to hear some opinions.

https://github.com/vitaliyterziev/snake

var Poly = {
    //polyfill Set()
    set: function (array) {
        'use strict';
        var cleanList = [];
        var bool = false;

        array.forEach(function (itemDup) {
            cleanList.forEach(function (noDup) {
                if (noDup[0] === itemDup[0] && noDup[1] === itemDup[1]) {
                    bool = true;
                }
            });
            if (!bool) {
                cleanList.push(itemDup);
            }
            bool = false;
        });
        return cleanList;
    }
};

/*
    main game object is below


*/
var SnakeGame = {

    setup: function() {
        'use strict';
        // initial variables
        var canvas = document.getElementById('basic'),
            ctx = canvas.getContext('2d'),
            gameStatus = document.querySelectorAll('h3')[0],
            gameRestart = document.querySelectorAll('#restart')[0],
            snakeLength = 5,
            snakeSpeed = 300,
            snakeWidth = 10,
            snakeHeight = 10,
            snake = [],
            x = 10,
            y = 10,
            direction = 39,
            up = 38,
            down = 40,
            left = 37,
            right = 39,
            foodCoordinates = [],
            xFoodCoordinate,
            yFoodCoordinate,
            doneMove = false,
            feeding = false,
            gameOver = false;

        // draw object, painting and clearing rectangles
        var draw = {
            fill: function(x, y) {
                ctx.fillRect(x, y, snakeWidth, snakeHeight);
            },
            clear: function(x, y) {
                ctx.clearRect(x, y, snakeWidth, snakeHeight);
            }
        };

        // gradient effects
        var grd = ctx.createLinearGradient(0, 0, 170, 0);
        grd.addColorStop(0, "yellow");
        grd.addColorStop(0.5, "green");

        ctx.fillStyle = grd;

        // initial snake coordinates
        for (var i = 0; i < snakeLength; i++) {
            draw.fill(x + (i + x * i), y);
            snake.push([x + (i + x * i), y]);
        }

        // generating the food coordinates which will be used
        for (var fcube = 0; fcube < 350; fcube++) {
            if (fcube % 11 === 10) {
                foodCoordinates.push(fcube);
            }
        }

        // event handlers
        document.onkeydown = function(key) {
            if (key.keyCode === 37 || key.keyCode === 38 || key.keyCode === 39 || key.keyCode === 40) {
                if (direction - key.keyCode != 2 && direction - key.keyCode != -2) {
                    if (doneMove) {
                        direction = key.keyCode;
                    }
                    doneMove = false; // change the direction once atleast 1 cube is drawed
                }
            }
        };

        gameRestart.onclick = function() {
            if (gameOver) {
                snake.forEach(function(el) {
                    draw.clear(el[0], el[1]);
                });
                draw.clear(xFoodCoordinate, yFoodCoordinate);
                gameStatus.className = gameStatus.className.replace(' fail', '');
                gameStatus.textContent = 'Game ON';
                SnakeGame.setup();
            }
        };

        // growing function
        var feedFunc = function(eat) {
            if (eat) {
                feeding = true;
                draw.fill(snake[0][0], snake[0][1]);
                snake.unshift([snake[0][0], snake[0][1]]);
                speedFunction();
            }
        };

        // food coordinates cleaner
        var foodCoordsCleaner = function() {
            var tempFoodCoordinates = foodCoordinates.filter(function(el) {
                for (var fi = 0; fi < snake.length; fi++) {
                    if (el === snake[fi][0] || el === snake[fi][1]) {
                        return false;
                    }
                }
                return true;
            });
            return tempFoodCoordinates;
        };

        /*
         * drawing the food cube with random x and y coordinates
         * check if there are duplicate coordinates in the snake array
         */
        var foodLoop = setInterval(function() {
            var safeFoodCoordinates = foodCoordsCleaner();
            if (!feeding) {
                draw.clear(xFoodCoordinate, yFoodCoordinate);
            }
            ctx.fillStyle = '#ffa500';
            xFoodCoordinate = safeFoodCoordinates[Math.floor(Math.random() * safeFoodCoordinates.length)];
            yFoodCoordinate = safeFoodCoordinates[Math.floor(Math.random() * safeFoodCoordinates.length)];
            draw.fill(xFoodCoordinate, yFoodCoordinate);
            ctx.fillStyle = grd;
            feeding = false;
        }, 6000);

        // end game function, clears the food loop
        var gameEndFunc = function() {
            gameOver = true;
            gameStatus.className = gameStatus.className + ' fail';
            gameStatus.textContent = 'Game Over';
            clearInterval(foodLoop);
        };

        // speed function
        var speedFunction = function() {
            switch (snake.length) {
                case 10:
                    snakeSpeed = 200;
                    break;
                case 15:
                    snakeSpeed = 150;
                    break;
                case 20:
                    snakeSpeed = 100;
                    break;
                case 25:
                    snakeSpeed = 70;
                    break;
                case 30:
                    snakeSpeed = 40;
                    break;
                default:
                    break;
            }
        };

        /*
         * snake controll, calling gameEndFunc and feedFunc
         * clear rect, eat, draw rect, add element and remove the last one
         * draw 1 cube, push one cube and shift the last one (moving forward)
         */
        var snakeFunc = function(x, y) {
            if (x > 340 || y > 340 || x < -1 || y < -1 || snake.length !== Poly.set(snake).length) {
                gameEndFunc();
            } else {
                feedFunc(x === xFoodCoordinate && y === yFoodCoordinate);
                draw.clear(snake[0][0], snake[0][1]);
                draw.fill(x, y);
                snake.push([x, y]);
                snake.shift();
                doneMove = true;
            }
        };

        /* 
         * game loop recursive function
         * moving/coloring "11px" in direction, caling the main snakeFunc
         */
        var gameLoop = function() {
            setTimeout(function() {
                x = snake[snake.length - 1][0];
                y = snake[snake.length - 1][1];

                switch (direction) {
                    case right:
                        snakeFunc(x + 11, y);
                        break;
                    case down:
                        snakeFunc(x, y + 11);
                        break;
                    case left:
                        snakeFunc(x - 11, y);
                        break;
                    case up:
                        snakeFunc(x, y - 11);
                        break;
                    default:
                        console.log('Wrong direction/keypress');
                        break;
                }
                if (!gameOver) {
                    gameLoop();
                }
            }, snakeSpeed);
        };
        gameLoop();
    }
};
SnakeGame.setup();
#basic {
    border:2px solid #000;
    margin:20px;
        -moz-box-shadow: 0 0 3px black;
    -webkit-box-shadow: 0 0 3px black;
    box-shadow: 0 0 3px black;
}

.fail {
    color:red;
}

#restart {
    display:block;
    background-color:#228B22;
    font-family:cursive;
    font-size:16px;
}

h3 {
    font-family:cursive;
    font-size:16px;
}
<!DOCTYPE html>
<html>

<head>
    <title>Snake GAME</title>
    
    <meta name="author" content="Vitaliy Terziev">
    <link rel="stylesheet" href="css/main.css">
</head>

<body>
    <h3>Game ON</h3>
    <button id="restart">Restart Game</button>
    <canvas id="basic" width="350" height="350"></canvas>

    <script src="js/main.js"></script>
</body>

</html>

\$\endgroup\$
4
\$\begingroup\$

I think you should try to split your program into multiple objects like Snake, FoodFactory, Field...

And then a Game object which will "just" connect them:

snake.move(direction);
snake.draw();
if(hasCollision(snake.head, snake.queue) || 
   hasCollision(snake.head, field)){
  // display game over
}

if(hasCollision(snake.head, food.position)){
  points += 1;
  snake.increaseLength();
  do{
    food = FoodFactory.create
  while(hasCollision(snake.body, food));
}
//...

So you can separate the rules of the game (the logic) and the actual implementation.

\$\endgroup\$
  • \$\begingroup\$ this actually sounds good, thanks for your answer! Especially the 'FoodFactory' ..... catching :) \$\endgroup\$ – Vitaliy Terziev Nov 27 '15 at 13:56

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.