JavaScript

индекс
246,38

Когда программисту нечего делать, пишем игры сами. Часть 1

Недавно на хабре была статья «Когда программисту нечего делать…», вот и у меня такая ситуация но я не просто хочу дать ссылку на Цветные линии, а рассказать как можно самому сделать её. Всем кого интересует добро пожаловать под кат

О чем это я


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

Подготовка


Шаблон страницы для игры будет очень простым, и содержать в себе два поля. Одно для поля игры, второе для отображения набранных балов. Ячейки поля будем генерировать с помощью скрипта.
Файл index.html
<html>
<head>
<meta HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8"/>
<title>Игра ColorLines</title>
<script type="text/javascript" src="jsfw.js"></script>
<script type="text/javascript" src="main.js"></script>
<script type="text/javascript" src="lines.js"></script>
<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body>
  <a href="/">На главную</a>
  <h1>Игра - Color Lines</h1>
  <table>
    <tr>
      <td>
        <div class="gamepole">
          <div id="pole">
          </div>
          <div class="gameOver" id="gameover" style="display:none">
            <div class="bg"></div>
            <div class="block">
              <h4>Конец игре</h4>
            </div>
          </div>
        </div>
      </td>
      <td class="menu">
        <p>Счет: <span id="score">0</span></p>
        <input type="button" onclick="game.start()" value="Начать заново"/>
      </td>
    </tr>
  </table>
</body>
</html>


* This source code was highlighted with Source Code Highlighter.


Файл style.css
.gamepole {position:relative;}
.Pole {width:414px;}
.Cell {width:30px;height:30px;background:#333;float:left;margin:1px;color:#FFF}
.Ball {width:26px;height:26px;margin:2px;position:relative;}
.Ball1 {background:#F00;}
.Ball2 {background:#0F0;}
.Ball3 {background:#00F;}
.Ball4 {background:#0FF;}
.Ball5 {background:#F0F;}
.Ball6 {background:#FF0;}
.gameOver {position:absolute;top:0;left:0;width:384px;padding-top:75px;text-align:center;}
.gameOver .bg {z-index:1;position:absolute;top:0;left:0;width:384px;height:384px;background:#C78BA3; filter:alpha(opacity=60);-moz-opacity:.6;opacity:.6;}
.gameOver h4{font-size:30px;}
.gameOver .block{position:relative;z-index:2;background:#FFF;padding:30px;width:300px;margin:0 auto;}
td.menu {vertical-align:top;background:#333;color:#FFF;width:260px;padding:10px;}

Начнем


В реализации я использую свой js велосипед, код которого конечно я не привожу, но встречающиеся функции буду комментировать, кому интересно могут найти в исходниках. Спросите почему не jquery, prototype, и др,, а потому что мне так удобней.
В игре будет всего три класса:
Game — основной класс игры
Cell — клас ячейки
Ball — класс шарика

Создадим файл main.js который будет создавать, и связывать игру с шаблоном
jsfw.ready(function()
{
  game = new Game('pole');
  game.addEvent('onchangescore',function(score){
    $('score').innerHTML = score;
  });
  game.addEvent('onstart',function(score){
    $('gameover').style.display = 'none';
  });
  game.addEvent('ongameover',function(score){
    $('gameover').style.display = '';
  });
  game.start();
});


* This source code was highlighted with Source Code Highlighter.

Игра будет построена на системе событий. По этому создадим объект Game и подпишемся на его события. У него их будет всего три:
onchangescore- изменение счета;
onstart – начало игры;
ongameover – конец игры;
При первом мы будем ловить изменение набранных балов, и выводить их на экран. При двух других мы будем скрывать или показывать сообщение о конце игры.

Весь остальной код я поместил в файл lines.js
Класс Game

var Game = jsfw.Class(function(){
/**
  здесь будут приватные методы
*/
function setScore(score)
{
  this.score = score;
  this.callEvent('onchangescore',[this.score]); // Бросим событие об изменение счета
}
return {
  width:12,    // Ширина поля
  height:12,    // Высота поля
  speed:30,    // Скорость перемещения шарика
  minline:5,    // Минимальное количество шаров для удаления
  countNewBall:3,  // Количество добавляемых шаров
  costBall:1,    // стоимость шара
  costBonus:5,  // стоимость бонуса
  /**
  * Конструктор
  * @param ID элемента для от рисовки игрового поля
  */
  __constructor:function(el)
  {
    this.dom = $(el);  // Сохраним контейнер
    this.score = 0;    // Обнулим количество балов
    this.cell = [];    // Здесь будут хранится ячейки игрового поля
    /*
      Функция $d создает функцию которая запускает переданную функцию в контексте объекта переданного первым аргументом
    */
    this.dSelectBall = $d(this,this.selectBall); // Создадим делегатов для событии щелчка по ячейки и шарику
    this.dSelectCell = $d(this,this.selectCell);
    this.countCell = this.width*this.height;  // Для ускорения расчетов подсчитаем количество ячеек
  },
  /**
  * Начало новой игры
  */
  start:function()
  {
    setScore.call(this,0); // обнулим счет
    this.createCell(); // Создадим новое поле
    this.callEvent('onstart'); // Кинем событие начала игры
  },
  /**
  * Конец игры
  */
  gameOver:function()
  {
    // Сдесь просто кинем событие об окончание игры
    this.callEvent('ongameover');
  },
  /**
  * Создание нового игрового поля
  */
  createCell:function()
  {
    /* Создание поля */
  }
}},
jsfw.Object // Класс реализующий события
)


* This source code was highlighted with Source Code Highlighter.

Вроде бы все описал в комментариях, могу только пояснить фунция jsfw.Class(Object|Function,baseClass) реализует наследование и принимает первым параметром либо прототип объекта либо, функцию которая возвращает прототип (Для реализации приватных методов). Это основные методы которые видны из вне, можно даже попробовать запустить :). Метод createCell мы реализуем после создания класса ячейки
Класс Cell

Ячейка будет у нас уметь только содержать шарик и реагировать на клик по ней по этому мы реализуем методы добавления, удаления шарика и событие onclick.
var Cell = jsfw.Class({
  __constructor:function(x,y)
  {
// Координаты ячеки
    this.x = x;
    this.y = y;
    /*
    Функция $.create облегчает создание DOM объекта первый параметр это название тэга, второй свойства обьекта, третий родительский DOM объект
    */
    this.dom = $.create('div',{
      className:'Cell',  // Класс ячейки
      onclick:$d(this,this.click)  // Навешаем на нее событие он клик выполняющей соответствующюю функцию в ячейки
    },this.dom);
  },
  /**
  *  Вставка шарика в ячейку
  *  @param Шарик
  */
  addBall:function(ball)
  {
    if(ball.cell) ball.cell.ball = null; // Если в ячейки уже есть шарик то разрываем с ним связь (В принципе это не должно быть но на всякий случай лучше предусмотреть)
    this.ball = ball;  // Сохраняем шарик во внутренней переменной
    ball.cell = this;  // Указываем шарику в какой ячейки он находится
    this.dom.appendChild(ball.getDom()); // Добавляем его в DOM объект
  },
  /**
  *  Удаление шарика из ячейки
  */
  removeBall:function()
  {
    $.remove(this.ball.getDom()); // Удаление из DOM
    this.ball.cell = null;  // Разрываем все связи с шариком
    this.ball = null;
  },
  /**
  *  Есть ли шарик в ячейки
  */
  isBall:function()
  {
    return !!this.ball;
  },
  /**
  *  Получить шарик из ячейки
  */
  getBall:function()
  {
    return this.ball;
  },
  /**
  *  Получить DOM ячейки
  */
  getDom:function()
  {
    return this.dom;
  },
  /**
  *  Клик по ячейки
  */
  click:function()
  {
    // Просто бросаем событие onclick
    return this.callEvent('onclick');
  }
},jsfw.Object);


* This source code was highlighted with Source Code Highlighter.

Стили у нас уже есть, ячейки с помощью float:left и жестким заданием ширины и высоты ячейки и контейнера, просто сложатся так как нам надо. Больше этот класс мы трогать не будет все что нам надо от него он умеет. Теперь реализуем создание ячеек. Нам надо просто пройтись по всему полю, создать ячейки и добавит к ним события. Массив для хранения ячеек у нас одно мерный. Расположение ячейки в массиве будет вычисляться по формуле x*this.width+y, создадим для удобства функцию getIndex(x,y) которая за одно будет проверять входит ли координаты в заданный диапазон и если не входит будет возвращать -1. Давайте напишем для этого функции и добавим их в класс Game.
  /**
  * Создание нового игравого поля
  */
  createCell:function()
  {
    $.empty(this.dom); // Отчистим DOM
    var p = $.create('div',{className:'Pole'}); // Создадим контейнер поля
    // Переберем все поле
    for(var y=0,lenY=this.height;y<lenY;y++)
      for(var x=0,lenX=this.width;x<lenX;x++)
      {
        var cell = new Cell(x,y); // Создадим ячеку
        cell.addEvent('onclick',this.dSelectCell); // Подпишемся на событие щелчка по ячейки, используя делегата ранее созданного
        p.appendChild(cell.getDom());    // Получим DOM у ячейки и добавим его в наш контейнер
        this.cell[this.getIndex(x,y)] = cell; // Сохраним ячейку в массиве, используя функцию нахождения индекса по координатам
      }
      this.dom.appendChild(p); // Добавим контейнер в DOM игры
  },
  /**
  * Функция выбора ячейки
  */
  selectCell:function(cell)
  {
    alert('Вы выбрали ячейку с координатами x='+cell.x+' y='+cell.y);
  },
  /**
  * Получение индекса массива по координатам поля
  * @param Координата по оси X
  * @param Координата по оси Y
  */
  getIndex:function(x,y)
  {
    return (x >= 0 && x < this.width && y>=0 && y<this.height)?x*this.width+y:-1;
  }


* This source code was highlighted with Source Code Highlighter.

Теперь если вы сделали все как надо то при запуске скрипта отобразится симпатичная сеточка :).
Класс Ball

Класс шарика у нас будет уметь только реагировать на событие onclick и выделять себя путем моргания. Ище шарик сам будет выбирать какого он цвета.
var Ball = jsfw.Class({
  countColor:6,  // Количество цветов
  __constructor:function()
  {
    this.color = c = Math.rand(1,this.countColor); // Случайно с генерируем индекс цвета, цвета у нас будут прописаны в стилях как .Ball{Индекс}
    this.dom = $.create('div',{className:'Ball Ball'+this.color,onclick:$d(this,this.click)},this.dom); // Создадим DOM элемент шарика, подпишемся на событие onclick и выставим цвет
  },
  /**
  * Получение DOM шарика
  */
  getDom:function()
  {
    return this.dom;
  },
  /**
  *  Клик по шарику
  */
  click:function()
  {
    return this.callEvent('onclick');
  },
  /**
  *  Выделить шарик
  */
  select:function()
  {
    // jsFW.fx.blink заставляет элемент маргать с периодом в time
    this.blink = jsFW.fx.blink(this.dom,{time:300}); // Сохраним идентификатор чтобы можно было потом мигание прекращать
  },
  /**
  *  Снять выделение
  */
  unselect:function()
  {
    if(this.blink) // Если моргал тогда останавливаем
    {
      this.blink.stop();
      delete this.blink;
    }
  },
  /**
  * Удалить шарик с поля, и разорвать все связи с ячейкой
  */
  remove:function()
  {
    if(this.cell)
    {
      $.remove(this.dom);
      this.cell.ball=null;
      this.cell = null;
    }
  }
},jsfw.Object);


* This source code was highlighted with Source Code Highlighter.


Теперь можно добавить еще несколько методов в класс Game для создания и добавления шариков в случайном свободном месте.
/**
  * Добавляет шарик в свободное поле
  */
  addRandBall:function(ball)
  {
    /*
      в масиве this.emptyCell хранятся индексы свободных ячеек
    */
    var i = Math.rand(0,this.emptyCell.length-1); // Выберем случайно индекс свободной ячейки
    this.cell[this.emptyCell[i]].addBall(ball); // И добавим в нее шарик
    this.emptyCell.splice(i,1); // Удалим из массива эту ячейку, т.к. мы ее заняли
  },
  /**
  *  Помещает на игровое поле новые шарики
  */
  newBall:function(){
    this.emptyCell = []; // Обнулим массив свободных шариков, и заполним его заново, он мог изменится
    for(var i=this.cell.length;i--;)
    {
      if(!this.cell[i].isBall()) this.emptyCell.push(i); // Если ячейка пустая добавим в массив
    }
    /*
      Создадим новые шарики количеством указанным в переменной this.countNewBall
      Но так как свободных мест может не хватить по этому мы выберем минимальное значение
    */
    for(var i=Math.min(this.countNewBall,this.emptyCell.length);i--;)
    {
      var ball = new Ball();
      ball.addEvent('onclick',this.dSelectBall); // Подпишемся на событие
      this.addRandBall(ball);  // Добавим шарик
    }
    // Дальше проверим если количество свободных мест меньше чем добавляемые шарики то конец игры
    if(this.emptyCell.length<=this.countNewBall) this.gameOver();
  },
  /**
  * Выделение шарика
  */
  selectBall:function(ball){
    if(this.seletedBall) this.seletedBall.unselect(); // Если уже был выбран шарик снимаем выделение
    this.seletedBall = ball; // Сохраняем новый шарик и выделяем его
    ball.select();
    return false;
  }


* This source code was highlighted with Source Code Highlighter.


Еще надо добавить в функцию start вызов метода newBall вот так
start:function()
  {
    setScore.call(this,0); // обнулим счет
    this.createCell(); // Создадим новое поле
    this.newBall();
    this.callEvent('onstart'); // Кинем собитие начала игры
  },


* This source code was highlighted with Source Code Highlighter.


Давайте немного изменим функцию выбора ячейки selectCell
selectCell:function(cell)
  {
    if(!cell.isBall() && this.seletedBall) // Если в ячейке нет шарика и есть выделенный
    {
      cell.addBall(this.seletedBall); // Перенесем шарик в выбранную ячейку
      this.newBall();
    }
    return false;
  }


* This source code was highlighted with Source Code Highlighter.

Запустите и посмотрите что у нас получилось. Теперь у нас есть поле на которое вываливаются шарики которые можно выделать, и перемещать на свободные места.

Статья получилась большая по этому я решил ее разбить на несколько частей.

Заключение


Мы подготовили все чтобы взяться за логику игры, а это самая интересная часть программирования игр. В следующей части мы рассмотрим алгоритм проверки собранных линий, подсчета балов за сгоревшие шарики. И самое интересное алгоритм поиска пути, наши шарики ведь не должны летать как сейчас — мы подрежем им крылья. >:)

Да чуть не забыл пример и исходники для первой части

В примере вместо шариков были использованы квадраты, т.к. шары были заняты по неизвестным причинам.

Жду конструктивной критики в комментариях, а указания на орфографические ошибки только в личку
+27
15 января 2009, 13:35
31

комментарии (49)

+10
eudo #
Что-то нифига они не сгорают
0
no_smoking #
Это будет в Часть 2, я же написал.
+4
eudo #
Вот так у программистов всегда -)
0
no_smoking #
вот законченная игра www.softcoder.ru/lines/, ссылку в начале я уже давал
+1
Saben #
Не критика, а по моему мнению желательное дополнение. когда «квадратик» появляется в почти достроенной линии, сделать бы так чтобы линия исчезала.
пример стояло 4 «квадратика» через 1 клетку еще 1
«оооо о»
и в пустом месте появился «квадратик» того же цвета что и линия
«оооооо»
но она не исчезла.
0
no_smoking #
Я про это знаю, если посмотрите исходники они немного другие. Я планирую при написание следующей части переписать код с логикой, исправить баги и еще дописать много чего.
0
Saben #
Прекрасно это понимаю =)
Еще раз скажу спасибо за игру. С большим удовольствием в нее сейчас играю.
0
sol1tude #
Порой квадратик не добегает до конца а потом и вовсе перестает оттуда двигаться. Что-то с алгоритмом не то.
0
Saben #
И не должны. В посте автор написал
В следующей части мы рассмотрим алгоритм проверки собранных линий, подсчета балов за сгоревшие шарики. И самое интересное алгоритм поиска пути, наши шарики ведь не должны летать как сейчас — мы подрежем им крылья. >:)
Замечательная статья! Спасибо огромной автору =)
+2
iDemy #
К слову — вот тут есть еще одна реализация этой игры.
www.webhackers.ru/lines/
0
twi #
>Когда программисту нечего делать
+1
twi #
уже смешно :)
0
no_smoking #
Я рад что вам понравилось.
0
jrip #
Ну вот, намутил себе 10000 очков, а в рейтинге только в общем появился, так не честно =)
0
no_smoking #
За то баг нашли :). А читерство не хорошо. Надо будет это как то присеч. К стати не кто не знает как это лучше сделать, я знаю один способ но вам его не скажу а то перестанет работать.
0
leonard #
Писать лог появления шариков и их перестановок, а потом на сервере прогонять игру. Если сошлось — то результат честный.
0
no_smoking #
Ну это самый верный способ, перенести логику игры на сервер. Но не хочется дублировать код, но придется но это будет в следующих частях.
0
leonard #
Если дублировать не хочется из-за нагрузки на сервер то можно тем у кого флэшплеер есть делать проверку в нем)
0
no_smoking #
Ну проще тогда сделать на флеше полностью.
0
leonard #
Просто я почему-то флеш не люблю. Не спрашивайте почему — я не знаю)
0
Poliakov #
флеш всегда можно помотать туда-сюда, и, наверно, есть аналогичные методы его взлома.
0
no_smoking #
Ну данные можно за шифровать. Ну и другой вопрос это надо будет кому, это защита не для банковского щета :)
0
jrip #
Единственный способ, имхо, перенести всю логику на серверную часть, все остальное всегда можно раскопать и поломать =)
Всегда есть момент, когда конечный результат передается на сервер и на данном этапе его всегда можно подменить.
Есть вариант с отсылкой очков при каждом ходе, проверкой, т.е. очки не могли измениться более чем на максимально возможное число за ход, но это лишь немного осложнит задачу взлома т.к. мы не можем точно знать игра ли послала эти очки.
0
no_smoking #
Тоже это думал,. Можно при этом еще слать в разные промежутки времени по определенному алгоритму потомм все усреднять и высчитывать в каких пределах могут быть балы.
А перенос на сервер логики, приведет к задержка между ходами что не есть Ase.
0
Poliakov #
логика на сервере, отсылать на сервер ход (как в шахматах Е2Е6), сервер возвращает результат хода (игровое поле), вывести клиенту. взламывать нечего =), но придётся на сервере хранить игру пользователя, что опасно при большом количестве посещений, можно поставить ограничение на ход 10 секунд, тогда после этого у пользователя игра gameOver(), а на сервере игра удаляется из памяти, но это как-то несерьёзно для такой игры
0
Poliakov #
я сразу появился ;)
0
no_smoking #
Ну тык я баг исправил :)
0
Poliakov #
наверно, ещё нужно форматирование сделать, чтобы длинное имя или большое количество очков не портили внешний вид
0
no_smoking #
Вот как дойдем до этой части, еще нужно над защитой подумать не охота дублировать код на сервере.
+2
kalisha #
>> Когда программисту нечего делать…
Такого не бывает, когда ему нечего делать он старается выспаться ;)
0
omella #
здорово :) мне понравилось :))
0
vasilyev #
IE7 — при выборе «шарика» (который квадратик на самом деле у меня), все остальные пропадают.
0
no_smoking #
Уже исправил в стилях надо немного магии
.gamepole {position:relative;zoom:1}
помогает :)
–2
Poliakov #
а чего столько орфографических ошибок в jsfw?
+2
no_smoking #
ну вы его не должны были читать ну а раз прочли так терпите.
–1
bigbes #
Недавно пишется слитно^^ а так большое спасибо… было очень интересно.
+4
no_smoking #
Вот смотрю с какими именами игроки встречаются
t<script>alert(\«XSS\»)asdf\'+union+select+*+from+users/*

улыбнуло :)
0
no_smoking #
Самое забавное я базу не использую :)
0
pxx #
Какой-то баг с перемещением в левую-верхнюю клетку. А так ничего, интересно. С разбегу не смог хакнуть :)
0
no_smoking #
Да забавно с левой верхней клеткой получается буду смотреть в чем дело.
0
mkaz #
Ситуация: стоит 4 одноцветных квадрата в ряд, пятый появляется при некотором перемещении. В итоге я вижу пять квадратов подряд и они не сгорают. Баг или фича?
0
no_smoking #
Фича :) ставите еще один квадрат и получаете на одно очко больше
0
t0os #
чуть опоздал, скриншот ниже :) таже ситуация.
0
t0os #


no_smoking, а почему у меня слева не сгорели зеленые квадраты? Играю в полную версию.

Может быть поможет — линия достроилась сама (в результате очередного хода).
0
no_smoking #
Это бета версия со своими багами со второй частью статьи я их все исправлю и вообще планирую переписать некоторые куски кода. Пока что решение поставить рядом еще шарик того же цвета :)
0
t0os #
тогда прошу прощения :)
0
no_smoking #
Баг поправил :)
0
tinkerbell #
отличная игра Lines, жаль только, что в вашей реализации еще не реализована проверка возможности хода, ведь поле может быть перекрто. Я когда делал клон Lines (см. мой следующий пост в этом блоге) нашел это самой интересной задачей. Она решается несколькими способами, я просто беру две клетки, откуда и куда нужно перенести шар, и перед переносом проверяю возможность — иду по спирали от клетки «откуда» и если не упираюсь в границы или в другие шары и при этом пути встречаю клетку «куда» то переход возможен, все довольно просто и быстро.
Ну и конечно, в оригинальной игре, если линия заполнлась сама при появлении нужного шара в нужном месте, то она сгорала, жаль, что у вас этого нет. Потенциально это может привести к появлению цельной линии, которую не убрать
0
no_smoking #
Проверка на путь есть в самой игре, как ее сделать я уже сказал что опешу в следующей части она почти готова. На счет сгорание появляющихся шаров то я в самой игре поправил этот баг.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.