Pull to refresh

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

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

О чем это я


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

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

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

Заключение


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

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

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

Жду конструктивной критики в комментариях, а указания на орфографические ошибки только в личку
Tags:
Hubs:
Total votes 39: ↑33 and ↓6 +27
Views 11K
Comments Comments 49