Игра «Змейка» на JavaScript

Змейка. Скриншот.Не так давно я имел опыт в написании игр на JavaScript. Довольно увлекательный процесс. Простая игра демонстрирует некоторый приём, который я вычитал в книге Makzan — «HTML5 Games Development by Example Beginner’s Guide». Приём связан с неким игровым «циклом», который раз, например, в 50 миллисекунд перерисовывает отображение в соответствии с изменениями, которые произошли в игре. Использовался jQuery и HTML.

Инициализация

Игровой процесс начинается как правило с инициализации.

Во время инициализации рисуется игровое поле, а точнее — серая рамочка по периметру окна браузера. Это граница поля, за которую нельзя заступать. Это делает функция prepareGamePane().

Далее функция initStartPosition() размещает на игровом поле змейку в соответствии с начальными координатами.

На следующем шаге с помощью функции addRandomRabbit() на игровом поле размещается так называемый «кролик» (квадратик, который нужно собрать).

Для того, чтобы была возможность управлять змейкой нужно определить обработчик событий нажатия кнопок, а именно — стрелок:

// Обработчик события
snake.keyPress = function( e ) {
    switch(e.which) {
        case KEY.LEFT:
            snake.direction = snake.direction != snake.directions.RIGHT ? snake.directions.LEFT : snake.direction;
            break;
        case KEY.RIGHT:
            snake.direction = snake.direction != snake.directions.LEFT ? snake.directions.RIGHT : snake.direction;
            break;
        case KEY.UP:
            snake.direction = snake.direction != snake.directions.DOWN ? snake.directions.UP : snake.direction;
            break;
        case KEY.DOWN:
            snake.direction = snake.direction != snake.directions.UP ? snake.directions.DOWN : snake.direction;
            break;
        default:break;
    }
    return false;
};

Кроме того, нужно определять какая именно стрелка была нажата. Для это заведён такой объект с данными о кодах кнопок:

var KEY = {
    'LEFT':37,
    'RIGHT':39,
    'UP':38,
    'DOWN':40
};

В конце инициализации начинаем «игровой цикл» с помощью setInterval().

Таким образом, инициализация сводится к вызову всех этих функций один раз после загрузки страницы:

snake.init = function() {
    this.prepareGamePane();
    this.initStartPosition();
    this.addRandomRabbit();
    $(document).keydown(this.keyPress);
    this.gameLoop( this );
    this.game_interval = setInterval( this.gameLoop, this.speed );
};

Игровой цикл

Итак, игровой цикл у меня состоит из 2 функций, а скорость цикла — 400 миллисекунд. Функция snake.drawSnake() рисует змею на поле по текущим данным о её местоположении, а функция snake.moveSnake() соответственно — меняет эти данные в зависимости от направления движения змеи.

То есть 2 функции выполняются раз в 400 миллисекунд:

snake.gameLoop = function() {
    snake.drawSnake();
    snake.moveSnake();
};

Змея перерисовывается на каждом шаге цикла предварительно удаляя предыдущую змею с игрового поля:

snake.drawSnake = function() {
    $( '.snake', this.pane.instance ).remove();
    $.each( this.snake, function( k, v ) {
        snake.drawSnakeBodySection( k, v );
    });
};

Тело змеи представляет собой массив координат в сетке, которая ограничена границами игрового поля. Поэтому, для того, чтобы подвинуть змею на одну клетку на шаге цикла нужно просто добавить в начало массива элемент с новыми координатами «головы» змеи, и удалить один элемент с конца массива. Новые координаты «головы» рассчитываются в зависимости от направления движения змейки.

snake.moveSnake = function() {
    if ( !this.hitTest() ) {
        if ( !this.increase_this_step ) {
            this.snake.pop();
        }
        this.increase_this_step = false;
        var left = snake.direction[0] == 'h' ? this.getHead().left+snake.body_section.width*snake.direction[1] : this.getHead().left ;
        var top  = snake.direction[0] == 'v' ? this.getHead().top+snake.body_section.height*snake.direction[1] : this.getHead().top ;
        this.snake.unshift({
            'left':left,
            'top':top
        });
        return true;
    } else {
        this.handleHitEvent( this.hitTest() );
        return false;
    }
};

В этой функции, также, происходит проверка на столкновения с границами поля и «кроликом».

Поиграть в готовую игру и ознакомиться с исходным кодом можно тут.