Pull to refresh

Создание игр без canvas: Matreshka.js

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

В прошлой статье мы рассматривали способ создания карточных игр с помощью манипуляций с 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
Tags:
Hubs:
+8
Comments10

Articles