Думаю все (или хотя бы большинство читателей) помнят такие интересные девайсы как тетрисы (или Brick game). Их бум пришёлся на девяностые — начало двухтысячных годов. И хотя хитрые китайцы производители обещали 99 игр в 1, 999 в 1, и даже 9999 игр в 1, все играющие люди которых я знаю — всё равно в основном играли только в 3 игры: сам, собственно говоря, тетрис, «гоночки», и «змейку». Так как такие игры — это убивалки времени, мне пришла идея создать копию всех трёх игр в виде расширений для браузера, чтобы было чем себя занять пока что-нибудь грузится. И начал я со змейки.
Немного о настоящем девайсе
Как сообщает википедия, игровое поле устройства представляет собой монохромный экран с разрешающей способностью 10 на 20 крупных пикселей. Соответственно у пикселя может быть только 2 состояния — активный и неактивный, никакие цветовые изменения не предусмотрены. Справа, обычно, располагается дополнительное поле, где указывается текущий счёт игры, рекорд, уровень, скорость, и какие-нибудь дополнительные параметры, если они имеются.
Змейка
Наверное, очень трудно найти человека который не знал бы что это за игра такая, но на всякий случай следует объяснить её суть. Мы играем за, внезапно, змейку, которая ползает по полю, ест еду, и радуется жизни. Изначально она состоит из четырёх пикселей, но с каждым «обедом» (который представляет из себя один пиксель), змейка растёт, прибавляя к своей длине ещё один пиксель. Игра заканчивается если змея ударяется «головой» о саму себя, или же о край игрового поля. Существуют модификации разрешающие пересекать игровое поле, или добавляющие на него дополнительные блоки, но их я не рассматриватривал и реализовывал классику. Итог:
Но обо всём по порядку.
Реализация
Для реализации я выбрал, HTML5, CSS 3.0, и JavaScript (а точнее его библиотеку, JQuery 1.12). Да-да, никаких canvas и флешей! Разумеется, вместо JQuery можно было использовать какую-нибудь более легковесную библиотеку, чтобы не «микроскопом по гвоздям», как говорится, но я выбрал именно JQuery, просто потому что я его знаю.
Для написания скриптов я выбрал подобие ООП, и поэтому архитектура управляющего скрипта получилась такая: несколько объектов для разных задач, и объект _core, для управления этим всем.
Детально:
- _check — используется для проверки возможности поворота змеи, проверки проигрыша и съедания еды
- _do — используется для разной «грязной» работы, типа установки координатов пикселя, изменения скорости, движения, и т.д
- _core — используется для управления логикой игры: её инициализация, начало, конец, перезагрузка, а так же подключение стилей
- _keyHandler — небольшой объект, чисто для обработки нажатий на клавиши
Хранение данных:
Всё хранится в свойствах глобального объекта window.snake. Отдельно хранятся только текущие очки (window.score), и рекорд (localStorage['HighScore']).
Представление
Безусловно, если игра не жанра roguelike, то её внешний вид является очень важной составляющей. А так как планируется сделать копию ретро-игры — так тем более. Поэтому для воссоздания духа Brick game я нашёл и вырезал два симпатичных пикселя, которые моментально вызывают ностальгию.
Активный пиксель
и фоновый, неактивный.
Показывать всё это я решил путём добавления div -ов с фоном в виде активного пикселя, и точного их позиционирования. Поле у нас 20х10, размер пикселя — 20х20, соответственно свойство top может колебаться от 0 до 380, свойство left от 0 до 180.
Так же нам нужно поле для очков, рекорда, и скорости. В конце концов получилось так:
<div class="game-container">
<div class="screen-container"></div>
<div class="score-place">
<div id="score"></div>
<div id="hi-score"></div>
<div id="speed"></div>
<br>
<a href="/options.html?back=true">Settings</a>
<br>
<div id="about">
<span>About</span>
</div>
<div class="about">
New version of retro game "Brick game: snake"<br>Author:
<br>
<span id="author">Vlad Reshet</span>
<br>
</div>
<div class="instruction">Use arrow keys or WASD</div>
</div>
</div>
var _do = {
setY: function(pixelId, shift) {
$("#p" + pixelId).css("top", shift + "px")
},
setX: function(pixelId, shift) {
$("#p" + pixelId).css("left", shift + "px")
},
addPixel: function(e, t) {
var pixel = new Object;
var id = window.snake.length;
var div = '<div class = "pix" id = "p' + id + '"></div>';
pixel.x = e;
pixel.y = t;
window.snake.position.push(pixel);
$(".screen-container").append(div);
_do.setX(id, window.snake.position[id].x);
_do.setY(id, window.snake.position[id].y);
window.snake.length++
} //... }
Движение
Красивые пиксели это, конечно, хорошо, но хотелось бы движения, игра всё-таки. Для движения я выбрал следующий алгоритм:
— при нажатиях на клавиши, в window.snake.direction записывается направление для будущего движения
— для движения берём n — 1 пикселей (n — длина змеи), и каждому k — пикселю (k — его порядковый номер в змее) ставим координаты k — 1 (предыдущего) пикселя. А голову ставим в соответствии с нужным направлением.
Само по себе движение — это периодический запуск функции _do.move, с помощью js — функции setInterval. Частота обновления — и есть скорость.
// var _do = { ...
move: function() {
var e = new Object;
e = window.snake.position[window.snake.length - 1];
for (var t = window.snake.length - 1; t >= 1; t--) {
window.snake.position[t].x = window.snake.position[t - 1].x;
window.snake.position[t].y = window.snake.position[t - 1].y
}
switch (window.snake.direction) {
case 1:
window.snake.position[0].y = window.snake.position[0].y - 20;
break;
case 2:
window.snake.position[0].x = window.snake.position[0].x + 20;
break;
case 3:
window.snake.position[0].y = window.snake.position[0].y + 20;
break;
case 4:
window.snake.position[0].x = window.snake.position[0].x - 20;
break
}
_check.meal();
if (window.snake.growUp) {
_do.addPixel(e.x, e.y);
window.snake.growUp = false
}
if (!_check.gameOver()) {
for (var t = 0; t < window.snake.length; t++) {
_do.setX(t, window.snake.position[t].x);
_do.setY(t, window.snake.position[t].y)
}
} else {
_core.gameOver()
}
window.snake.dirChangeAllow = true
},
updateSpeed: function() {
$("#speed").html("Speed <br>" + window.snake.speed)
},
updateScore: function() {
$("#score").html("Score <br>" + Math.floor(window.score))
}, //... }
Еда
Сама суть игры — это убить об стену накормить змею. Я реализовал это так:
— при поедании еды ставим флаг «growUp»
— при следующем «кадре» добавляем один пиксель в хвост змеи
— добавляем очки
// var _do = {
// move : function(){
// var e = new Object;
// e - последний пиксель в хвосте
// e = window.snake.position[window.snake.length - 1];
if (window.snake.growUp) {
_do.addPixel(e.x, e.y);
window.snake.growUp = false
}
// }
//}
Game Over
С реализацией проигрыша всё очень просто. Перед каждым движением головы, проверяем не окажутся ли её координаты меньше нуля, либо больше {180, 380}. Так же проверяем не окажутся ли её координаты равными координатам какого-нибудь пикселя с хвоста. Если окажется или первое или второе — Game Over :).
gameOver: function() {
var e = window.snake.position[0].x;
var t = window.snake.position[0].y;
for (var n = 1; n < window.snake.length; n++) {
if (e == window.snake.position[n].x && t == window.snake.position[n].y) {
_core.stopGame();
return true
}
if (e > 180) {
_core.stopGame();
return true
}
if (e < 0) {
_core.stopGame();
return true
}
if (t > 380) {
_core.stopGame();
return true
}
if (t < 0) {
_core.stopGame();
return true
}
}
return false
},
Результат
В результате получилось вот такое расширение для Chrome/Opera 20+/Другие chromium браузеры.
На данный момент расширение уже доступно для браузера Opera.
Так же его можно потестить с любого браузера (или с IE) здесь, и если будут желающие поставить его себе в хром — я залью его в Chrome WebStore (залил бы сразу, но активация dev-аккаунта платная, не хочется тратиться не зная надо ли оно кому-то).
А так же ссылка на GitHub.
Автор: vlreshet