Field/Field.js

//@include:FieldManager
//@include:FieldBuilder

/**
* Конструктор полей карт ({@link Card}).  
* Производит размещение карт на экране. Контролирует позицию карт при наведении курсора.
* Отвечает за подсветку пространства под картами и самих карт.  
* Основные компоненты: {@link Field#area}, {@link Field#cards}.  
* Карты добавляются в поле двумя методами:  
*
* {@link Field#queueCards} -> {@link Field#placeQueuedCards}  
* {@link Field#addCards}  
*
* Использование второго метода до финализации первого добавляет карты в очередь и запускает очередь  
* `.queueCards(c1)` -> `.addCards(c2)` => `.queueCards(c1)` -> `.queueCards(c2)` -> `.placeQueuedCards()`  
*  
* @class
* @extends {external:Phaser.Group}
* 
* @param {object}           options                                                  Настройки поля. {@link Field#options}. 
*                                                                                    Будут пересохранены в `this`, изменения объекта `options` не повлияют на поле.
* @param {string}           options.id=null                                          {@link Field#id}	
* @param {string}           options.type='GENERIC'                                   {@link Field#type}	
* @param {string}           options.name=null                                        {@link Field#name}	
*
* @param {number}           options.moveTime=cardManager.defaultMoveTime             {@link Field#moveTime}	
* @param {number}           options.delayTime=100                                    {@link Field#delayTime}	
* @param {boolean}          options.debug=false                                      {@link Field#inDebugMode}	
* @param {number}           options.specialId=null                                   {@link Field#specialId}	
*
*
* @param {object}           style                                                    Внешний вид поля. {@link Field#style} 
* @param {number}           style.x=0                                                {@link Field#x} позиция по горизонтали
* @param {number}           style.y=0                                                {@link Field#y} позиция по вертикали
* @param {number}           style.width=0                                            {@link Field#area} ширина поверхности
* @param {number}           style.height=0                                           {@link Field#area} высота поверхности
* @param {number}           style.margin=0                                           Отступ от края поля до видимого края поля.
* @param {number}           style.padding=10                                         Отступ от видимого края поля до карт внутри.
* @param {number}           style.spacing=10                                         Отступ между картами.
* 
* @param {number}           style.minActiveSpace=fieldManager.builder.minActiveSpace Минимальная ширина\высота для расположения карт.
* @param {number}           style.raisedOffset=skinManager.skin.height/2             На сколько поднимать карты с `raised == true`
* @param {(boolean|number)} style.forcedSpace=false                                  Нужно ли рассчитывать сдвиг карт по отношению друг к другу или использовать заданное значение.
* @param {number}           style.scaleDiff=0.025                                    На сколько увеличивается масштаб карты при наведении.
* 
* @param {boolean}          style.focusable=false                                    Нужно ли сдвигать карты при наведении
* @param {boolean}          style.sortable=false                                     Нужно ли сортировать карты 
* @param {boolean}          style.draggable=false                                    Можно ли перетаскивать карты в этом поле
*          
* @param {string}           style.horizontalAlign='center'                           Горизонтальное выравнивание поля.  
*                                                                                    Значения: `'left', 'center', 'right', 'centerLeft'`
* @param {string}           style.verticalAlign='middle'                             Вертикальное выравнивание поля.  
*                                                                                    Значения: `'top', 'middle', 'bottom'`
*          
* @param {string}           style.axis='horizontal'                                  Ориентация поля.
*                                                                                    Меняет местами horizontalAlign и verticalAlign (right станет bottom и т.д.), не влияет на width и height.  
* 	                                                                                 Значения: `'vertical', 'horizontal'`
* @param {string}           style.direction='forward'                                Направление поля.  
*                                                                                    Значения: `'forward', 'backward'`
* @param {string}           style.addTo='front'                                      В какой конец поля добавляются карты.  
*                                                                                    Значения: `'front', 'back'`
* @param {boolean}          style.flipped=false                                      Карты располагаются повернутыми на 180 градусов
* @param {(boolean|string)} style.randomAngle=false                                  Нужно ли класть карты в поле под случайным углом.  
*                                                                                    Значения: `false, 'uni', 'bi'`  
*                                                                                    `'uni'` - карты поворачиваются по направлению поля   
*                                                                                    `'bi'` - карты поворачиваются в случайную сторону
* @param {boolean}          style.adjust=true                                        Нужно ли пытаться двигать карты, которые уже находятся в поле, при добавлении новых.
* @param {number}           style.alpha=0.35                                         Прозрачность поля.
* @param {number}           style.corner=5                                           Радиус закругленного угла.
* @param {number}           style.border=4                                           Ширина рамки.
*
* @param {(boolean|string)} style.animateAppearance                                  Нужно ли анимировать появление поля и откуда это делать.  
*                                                                                    Значения: `false, 'left', 'right', 'top', 'bottom'`
*/

var Field = function(options, style){


	this._applyOptions(options, style);


	Phaser.Group.call(this, game, null, this.options.name);
	/**
	* Имя поля.
	* @member Field#name
	* @type {string}
	*/

	// Сохраняем опции на самом объекте для быстрого доступа

	/**
	* Тип поля
	* @type {string}
	*/
	this.type = this.options.type;

	/**
	* id поля
	* @type {string}
	*/
	this.id = this.options.id;

	/**
	* Запомненная позиция поля ({@link FieldManager#swapFields}).
	* @type {object}
	*/
	this.savedPosition = this.savedPosition;

	/**
	* Специальное id поля для полей, пренадлежащих одной группе.
	* @type {number}
	*/
	this.specialId = this.options.specialId;

	/** 
	* Время движения карт.
	* @type {number}
	*/
	this.moveTime = this.options.moveTime;

	/**
	* Задержка между движением карт.
	* @type {number}
	*/
	this.delayTime = this.options.delayTime;

	/**
	* Находится ли поле в дебаг режиме
	* @type {boolean}
	*/
	this.inDebugMode = this.options.debug;


	// Меняемые свойства

	/**
	* Подсвечено ли поле.
	* @type {Boolean}
	* @default  false
	*/
	this.highlighted = false;

	/**
	* Можно ли играть карты на это поле и тип действия.
	* @type {(string|boolean)}
	* @default  false
	*/
	this.playable = false;

	/**
	* Интерактивно ли поле.
	* @type {Boolean}
	* @default  true
	*/
	this.interactible = true;

	/**
	* Увеличен ли масштаб поля
	* @type {Boolean}
	* @default  false
	*/
	this.poppedOut = false;


	/**
	* Карты поля.
	* @type {Card[]}
	*/
	this.cards = [];

	/**
	* Карты на удаление.
	* @type {Card[]}
	* @see {@link FieldManager#queueCards}
	*/
	this.cardsToRemove = [];

	/**
	* Карты, которые могут быть сыграны на это поле.
	* @type {Card[]}
	* @see  {@link highlightPossibleActions}
	*/
	this.validCards = [];

	/**
	* Задержки карт по id карт.
	* @type {object<string>}
	*/
	this._delays = {};

	/**
	* Карты в очереди на добавление.
	* @type {Card[]}
	*/
	this._queuedCards = [];

	/**
	* Углы карт по id карт.
	* @type {Object<number>}
	*/
	this._angles = {};

	/**
	* Связанное поле.
	* @type {Field}
	* @see {@link CardControl#cardMoveToField}
	*/
	this.linkedField = null;


	/**
	* Выделенная карта.
	* @type {Card}
	*/
	this.focusedCard = null;

	/**
	* Ожидаемая задержка для установки {@link Field#_uninteractibleTimer}
	* @type {Number}
	*/
	this._expectedDelay = 0;

	/**
	* Расчитанное расстояние между картами для {@link Field#cardIsInside}
	* @type {Number}
	*/
	this._cardSpacing = 0;


	/**
	* Таймер {@link Field#_setUninteractibleTimer}
	*/
	this._uninteractibleTimer = null;

	/**
	* Твин появления поля.
	* @type {Phaser.Tween}
	*/
	this._entranceTween = null;

	/**
	* BitmapData поверхности поля.
	* @type {Phaser.BitmapData}
	*/
	this._bitmapArea = game.make.bitmapData();

	/**
	* Поверхность поля.
	* @type {Phaser.Image}
	*/
	this.area = game.make.image(0, 0, this._bitmapArea);
	this.area.alpha = this.style.alpha;
	this.area.visible = false;
	this.add(this.area);

	/**
	* Размер активного места поля для дебага.
	* @type {Phaser.Rectangle}
	*/
	this._debugActiveSpace = new Phaser.Rectangle();
};

extend(Field, Phaser.Group);

Field.prototype.initialize = function(){

	this.setOwnHighlight(false);
	this.setBase(this.style.x, this.style.y);
	this.setSize(this.style.width, this.style.height);

	this.setupAnimatedAppearance();
};

/**
* Возвращает опции по умолчанию
*/
Field.prototype.getDefaultOptions = function(){
	return {
		options: {
			moveTime: cardManager.defaultMoveTime,
			delayTime: 100,		

			id: null,
			specialId: null,
			type: 'GENERIC',
			name: null,

			debug: false
		},
		style: {
			x: 0,
			y: 0,
			width: 0,
			height: 0,

			padding: 10,
			margin: 0,
			spacing: 10,
			minActiveSpace: fieldManager.builder.minActiveSpace,
			raisedOffset: skinManager.skin.height/2,
			forcedSpace: false,	
			scaleDiff: 0.025,

			focusable: false,	
			sortable: false,	
			draggable: false,

			horizontalAlign: 'center',	
			verticalAlign: 'middle',		

			axis: 'horizontal', 	
			direction: 'forward',	
			addTo: 'front',		
			flipped: false,		
			randomAngle: false,	
			adjust: true,

			animateAppearance: false,
			
			alwaysVisible: false,
			alpha: 0.35,
			alphaActive: 0.85,
			corner: 5,
			border: 4
		}
	};
};

/**
* Совмещает переданные опции со стандартными и сохраняет их, как свойства объекта.
* @param {object} [options]   Настройки поля.
* @param {object} [style]     Внешний вид поля.
*/
Field.prototype._applyOptions = function(options, style){
	var defaults = this.getDefaultOptions();

	/**
	* Настройки поля.
	* Изменения не повлияют на само поле, т.к. все свойства сохранены в `this`.
	* @type {object}
	*/
	this.options = mergeOptions(defaults.options, options);

	/**
	* Внешний вид поля.
	* @type {object}
	*/
	this.style = mergeOptions(defaults.style, style);

};

/**
* Проверяет нахождение карты внутри поля (по координатам).
* @param {Card}    card                     проверяемая карта
* @param {boolean} [includeSpacing=true]    нужно ли учитывать сдвиг карт друг от друга
* @param {boolean} [includeWholeCard=false] любая часть карты
*
* @return {boolean} Находится ли карта в поле.
*/
Field.prototype.cardIsInside = function(card, includeSpacing, includeWholeCard){

	if(includeSpacing === undefined){
		includeSpacing = true;
	}

	if(includeWholeCard === undefined){
		includeWholeCard = false;
	}

	var spacing = 0;
	if(includeSpacing){
		spacing = skinManager.skin.width - this._cardSpacing;
	}

	var addX = 0,
		addY = 0;
	if(includeWholeCard){
		addX = skinManager.skin.width/2;
		addY = skinManager.skin.height/2;
	}

	return card && Phaser.Rectangle.containsRaw(
		this.x - spacing - addX,
		this.y - addY,
		this.area.width + addX*2 + spacing*2,
		this.area.height + addY*2,
		card.x + card.sprite.x,
		card.y + card.sprite.y
	);
};

//@include:FieldPosition
//@include:FieldValue
//@include:FieldQueue
//@include:FieldAdd
//@include:FieldDelete
//@include:FieldPlacePrivate
//@include:FieldPlacePublic
//@include:FieldCursor
//@include:FieldAnimate
//@include:FieldDebug

//@include:IconField
//@include:PopupField
//@include:BadgeField
//@include:PlayerField
//@include:TableField