Добра всем хаброчитателям!

В прошлой статье мы рассматривали способ создания карточных игр с помощью манипуляций с DOM, без использования canvas, на подобии HeartStone.

Сегодня мы продолжим эту тему, подключив к нашему делу полезнейшую в данном случае библиотеку Matreshka.js.
image

Введение



Кратко напомню, к чему мы пришли в прошлый раз. Общение с сервером осуществляется по WebSocket'ам, передаем JSON объекты вида: { «method»: метод, «args»: аргументы}.
Серверная сторона реализована с помощью php, скрипт запустили как демона (бесконечный цикл) в поток null.
Клиент принимает такого же вида JSON строки, вызываем методы объекта Actions (подробнее в прошлой статье).

socket.onmessage
socket.onmessage = function (e){
	if (typeof e.data === "string"){
		var request = JSON.parse(e.data);
		console.log('Response: ' + request.function);
		Actions[request.function](request.args);
	};
}



Начинаем внедрять матрешку


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

Итак, задача: мы получаем список игроков в JSON, когда подключаемся, необходимо их отрисовать и назначить события.
Над дизайном не заморачиваемся.

Списки в матрешке состоят из модели и класса (ну и объекта класса).
В нашем случае
Модель списка
var listModel = Matreshka.Class({ // Модель списка
	'extends': Matreshka.Object, // Наследуется от Matreshka.Object всегда
	constructor: function(data){
		this.jset(data);
		this.on('render',function(){ // Что происходит после отрисовки
			this.bindNode('name',':sandbox .name',Matreshka.binders.innerHTML());  // Биндим имя игрока
			this.bindNode('letsFight',':sandbox .fightButton');                                        // Биндим кнопку вызова на бой
			this.on('click::letsFight',function(){
				Actions.figthRequest(this.name);
			});
		});
	}
});



Давайте разберемся, что же тут произошло, что значит «биндим игрока»?
Для матрешки конструкция
this.bindNode('name',':sandbox .name',Matreshka.binders.innerHTML());

означает, что мы связываем свойство name, доступное потом как свойство объекта (Obj.name) и некоторую html-сущность, в данном случае сущность с селектором ':sandbox .name', где sandbox — песочница, то есть тот самый элемент, который мы только что отрендрили. Напомню, что мы это событие рендера одного конкретного элемента списка.
В качестве третьего аргумента передает тип зависимости. То есть то как они (свойство и сущность) между собой связаны.
В матрешке есть стандартны набор биндеров, и в данном случае Matreshka.binders.innerHTML() ставит в зависимость значение свойства и содержимое html-контейнера ':sandbox .name'.
Какая меж ними конкретно зависимость? Самая очевидная: изменяем свойство объекта — изменяется содержимое html контейнера.

Основы модели разобрали, идем дальше к классу
var listArray = Matreshka.Class({ // Класс списка
	'extends': Matreshka.Array, 
	Model: listModel,                   // Наша модель
	itemRenderer: '<li class="player"><span class="name"></span><span class="fightButton"></span></li>', // Как рендрится каждый элемент
	constructor: function(){
		this.bindNode('sandbox','#players'); // Засовываем в песочницу
	}
});


В классе стоит заострить внимание на двух вещах, хоть и весьма несложных. Свойство itemRenderer показывает, как будет отрисовываться каждый элемент списка. В приведенном примере /> и есть :sandbox, от которого отсчитываем прочие селекотры.

Указание
	constructor: function(){
		this.bindNode('sandbox','#players'); // Засовываем в песочницу
	}

говорит о том, что все элементы списка будут отрисовываться внутри контейнера '#players'.

Матрешка в режиме сражения


Когда игроки соединились и начали игру, что мы имеем (чисто логически):
  • Список карт в моей руке
  • Список карт в руке соперника
  • Список моих карт на игровом поле
  • Список карт противника на игровом поле


Осталось реализовать эти списки с помощью матрешки и задать им некоторые события.

Карты в моей руке


Карты в моей руке
var myCardsModel = Matreshka.Class({ // Модель списка
	'extends': Matreshka.Object,
	constructor: function(data){
		this.jset(data);
		this.on('render',function(){
			this.bindNode('name',':sandbox .title',Matreshka.binders.innerHTML());
			this.bindNode('attack',':sandbox .attack .value',Matreshka.binders.innerHTML());
			this.bindNode('health',':sandbox .health .value',Matreshka.binders.innerHTML());
			this.bindNode('mana',':sandbox .mana .value',Matreshka.binders.innerHTML());
			this.bindNode('picture',':sandbox .picture',{
				setValue: function(v){
					this.innerHTML = '<img src="img/' + v + '">'
				}
			});
			this.on('click::sandbox',function(){
				myArenaCards.push(this);
				myCards.splice(myCards.indexOf(this),1);
				Actions.send('putCard',this.toJSON());
			});
		});
	}
});
var myCardsArray = Matreshka.Class({ // Класс списка
	'extends': Matreshka.Array,
	Model: myCardsModel,
	itemRenderer: '<div class="card">'
					+'<div class="title"></div>'
					+'<div class="health"><div class="svg">' + $b('#icons #heart')[0].innerHTML + '</div><div class="value"></div></div>'
					+'<div class="attack"><div class="svg">' + $b('#icons #attack')[0].innerHTML + '</div><div class="value"></div></div>'
					+'<div class="mana"><div class="svg">' + $b('#icons #diamond')[0].innerHTML + '</div><div class="value"></div></div>'
					+'<div class="picture"></div>'
					+'</div>',
	constructor: function(){
		this.bindNode('sandbox','#myhand'); // Засовываем в песочницу
	}
});
var myCards = new myCardsArray; // Экземпляр класса списка



Аналогичный список, не будем повторяться, рассмотрим, как здесь применены бинды.
			this.bindNode('name',':sandbox .title',Matreshka.binders.innerHTML());
			this.bindNode('attack',':sandbox .attack .value',Matreshka.binders.innerHTML());
			this.bindNode('health',':sandbox .health .value',Matreshka.binders.innerHTML());
			this.bindNode('mana',':sandbox .mana .value',Matreshka.binders.innerHTML());


Как мы рассматривали выше, эти строки связывают содержимое html узла и свойства объекта.
Связав их вышеуказанным способом мы легко можем создать карту, просто сделав push в наш список:
var Actions = {
  .........
	cardToHand: function(card){
		myCards.push({
			name: card.name,
			attack: card.attack,
			health: card.health,
			picture: card.picture,
			mana: card.mana
		});
	}
  .........
}


Крайне просто. Но еще проще то, как мы можем менять эти свойства:
this.health = 0;

Не только задаст показатель здоровья равным нулю, но и отрисует это в html в нужном объекте.
Но и это еще не все, нам же надо отслеживать изменения здоровья, и если оно станет меньшим единицы, инициировать смерть юнита. Для этого свяжем свойство health объекта с самой картой:
			this.bindNode('health',':sandbox',{
				setValue: function(v){
					if (v < 1){
						this.className += ' die';
						var iot = myArenaCards.indexOf(this);
						setTimeout(function(){
							myArenaCards.splice(iot,1);
						},2000);
					};
				}
			});

Третий аргумент, как я говорил, задает логику связи. В данном примере логика следующая:
Когда поменялось (установилось) значение health объекта, запускаем функцию
                              function(v){
					if (v < 1){
						this.className += ' die';
						var iot = myArenaCards.indexOf(this);
						setTimeout(function(){
							myArenaCards.splice(iot,1);
						},2000);
					};
				}

This указывает на карту целиком, на песочницу (второй аргумент: ':sandbox').

Заключение


В сложных приложениях, где действительно нужно двусторонее и множественное связывание, матрешка великолепно облегчает жизнь и создает комфорт при разработке.
Ведь связывать можно как угодно, в одном случае ставим обработку только на принимаемое значание (setValue), в другом на изменение свойства по событию (on: 'click', getValue: function(){}).

  • Пример (косяков море, цель — показать технологию)
  • Github

Комментарии (9)


  1. chlp
    09.06.2015 16:52
    +1

    Извините, но откуда вообще может возникнуть идея делать карточную игру на веб через canvas?


    1. seokirill Автор
      09.06.2015 17:01

      Речь не о карточных играх, а вообще. Тк темы именно такие поднимаются в основном: создание игр с помощью canvas.
      Можно и не карточную, любую двухмерную — SVG в помощь. Просто захотелось именно эту.


    1. Alexeyco
      09.06.2015 17:27

      Надо flash?


      1. chlp
        09.06.2015 17:42
        +1

        Нет, надо юзать HTML. Очень здорово, что нам дали инструмент, позволяющий реализовать всю механику подобных игр нативными средствами, потом дали возможности всячески их украшать, анимировать и даже небольшие возможности 3D, а мы начинаем через канвас рендерить свой собственный механизм. Потом удивляемся, что браузер отжирает всю оперативку.


        1. eme
          09.06.2015 18:47

          посмотрите pixi.js и удивитесь!


          1. chlp
            09.06.2015 21:33
            +1

            Я говорю про конкретный пример игры из статьи. Для реализации идеи не нужен webGL. И говорю я о том, что если цель можно достичь явно меньшими затратами, то зачем делать иначе?


  1. eme
    09.06.2015 16:59
    +1

    Конечно, в примере вашей игры можно легко обойтись без канваса.
    Сравнение очень сомнительное )

    p.s. Для понимания, сравните любой из демо примеров html5gameengine.com с вашим.


    1. seokirill Автор
      09.06.2015 17:03

      Смысл статьи в другом — обкатать матрешку. Название сохранил для понимание связи с прошлой статьей.
      Полагаете, лучше сменить название?


      1. eme
        09.06.2015 18:17

        Приведите пример змейки, тетриса или арканоида с использованием вашей либы и уже будет о чем судить.