Good day, readers. Today, we will continue to improve our toy and realize the opportunity to attack the opponent’s cards, as well as some profit in using ShadowDOM for the admin panel.
Our way of implementing the queue from the last article (as opposed to the players' message on WebSockets) will unexpectedly help us in the implementation of the attack.
I will make a reservation right away that we will develop the version on MatreshkaJS, and not on Angular.

What we have at the moment
At the present time, we have implemented the ability to call the enemy to battle, and already in the game lay out cards on the arena (table). But what kind of game is it if you can not interact with the enemy?
')
We sketch the algorithm:
- Select the card, which we are going to make a move by clicking on it.
- When you click on another card, other cards are not highlighted.
- When you click on an opponent card, the highlighted card (if any) performs an attack event (card.attack (opponentCard))
- We show these actions to the opponent.
And yes, creatures cannot attack on the turn they were played.
And the cost of mana ... But let's do everything in order.
Admin and ShadowDOM
If you remember, we’ve assigned the admin panel to Webix, it’s very easy to interact with DataBoom, which we use to locate the map data and the order of events.
Since we want to make all the goodies of our prototype (HeartStone) like: “battle cry”, “death wheeze”, etc., we need to store descriptions of various instructions for these events in a separate collection. Recall that the relationship between collections in DataBoom is set as a property of the collection object, in which we specify an array of objects of the form
{ "id": "obj_ID"}
where obj_ID is the ID of the associated object.
In the official documentation, an example of establishing such relationships is as follows:
var pers = [{ name: 'John' }, { name: 'Jane' }] pers[0].wife = pers[1]; pers[1].husband = pers[0];
That is, we assign another object to a certain property without bothering how DataBoom links them. In the database, it looks as described above:
{"wife":[{ "id": "a1303015-77ae-472d-8961-94ea2838b9b2"}]}
Using this knowledge, we can, without rewriting our admin panel at the root, make it possible to add a link.
What do we really need? Replace the standard input when editing a record (we use the Webix widget gridpanel) to select with a list of IDs of the desired collection. And we will do this with the help of ShadowDOM.
What is ShadowDOM?
ShadowDOM, as the name suggests, is a shadow DOM construct.
The word “shadow” implies that what we do not see in the shadow is a backend on the front end. The user interacts with what we show him. But it is worth remembering that we change only the visual display. the DOM structure remains the same. We just replace the display of the usual input [type = text] with a more complex and convenient input element.
More deployed here .
Tk the gridpanel text fields store the string data in the database, and we need to save the object, we have to pass it in string representation (JSON). Writing such hands is hard, so let's start.
Initialize the root of the shadow tree on our input
var root = elem.createShadowRoot();
This action will already hide the contents of the elem element, but in order to display something in it, you need to fill it with innerHTML.
Since we will have a drop-down list there, we will get the data for it from the collection:
db.load("death_xpun").then(function(data){ var selectOptions = '<select>'; for(key in data){ selectOptions += '<option value="' + data[key].id + '">' + data[key].name + '</option>' } selectOptions += '</select>'; });
So far we have only done the mapping, instead of the text input, we can see select, but the data for writing to the database is taken from the input, which, by the way, has not gone anywhere, but is in the same place in the DOM tree. Hang an event handler on it:
root.querySelector('select').onchange = function() { var ourValue = {} ourValue.id = this.value; elem.value = JSON.stringify(ourValue);
We attack!
Choosing a card, which we are going to make a move
Here the algorithm is quite simple, but I will describe it to understand the overall picture.
In the myUnits module in the map model in the arena, we will see a click event on the map, that is, on the sandbox itself (MatreshkaJS, I recall). We will additionally connect the card activation with highlighting, that is, it will add / remove the active class:
this.bindNode('class',':sandbox',{
Since the card should not be able to attack on the same turn, we will add a certain enabled flag to the constructor, which is “disabled” by default. Later I will think whether it is better to replace it with true / false:
constructor: function(data){ this.enable = 'disable'; }
Maps are activated in the arena at the beginning of the turn. According to this logic, it is necessary at the beginning of each Hov to raise some kind of event enableMyUnits, which we will issue as a directive:
Actions / myTimerStart.js define(['Directive', 'timer', 'myUnits', 'mana'],function(Directive, timer, myUnits, mana){ var action = { run: function(){ timer.start();
The names of the methods speaking, and there is nothing to add.
So, the card is active and ready to attack, click on the opponent's card that we want to attack. To do this, of course, we will add the opUnits module (opponent's units), we will click on them:
this.on('click::sandbox',function(){ if($('#myUnits .active').length){
I took out the attack mechanism to a separate directive for the simple reason that we will trigger the attack event also when receiving the corresponding instruction from the server.
Actions / attack.js define(['Directive', 'stack', 'User'],function(Directive, stack, User){ var action = { run: function(args){ args.agressor.attacking(args.victim); var moreProps = { agressor: args.agressor.getIndex(), victim: args.victim.getIndex() } stack.push(User.opponent, 'opAttack', null, moreProps);
Since DataBoom contains collections and not tables, we can store completely different data there, with a different set of parameters. This is one of the reasons why I chose this service. In the example above, we are expanding the object that we are going to write to the database with the moreProps object, which can contain any parameters. But it is worth remembering that some parameters cannot be overwritten. Later I will check for an attempt to change such "system" parameters. But it popoooozhe.
The attacking () method of the myUnits module in the pilot version looks pretty clumsy
Code attacking: function(victim){ var agressor = this; victim.sandbox.style.zIndex = 5; agressor.sandbox.style.zIndex = 10; var yPos = victim.sandbox.offsetTop - (agressor.sandbox.offsetTop + $('#opUnits')[0].offsetHeight) + 100; var xPos = victim.sandbox.offsetLeft - agressor.sandbox.offsetLeft; agressor.sandbox.style.top = yPos + 'px'; agressor.sandbox.style.left = xPos + 'px'; var at = setTimeout(function(){ agressor.sandbox.style.top = 0; agressor.sandbox.style.left = 0; clearTimeout(at); },200); agressor.enable = 'disable'; victim.health = victim.health - agressor.attack; agressor.health = agressor.health - victim.attack; }
We simply move the map using styles so that it is on top of the victim card, and then back to its place. Naturally, for this the map must have position: relative.
Receiving instructions about the attack from our event queue from the server, we launch it in a similar way, simply by calling the attacking method on the object of the suicide card, and with the argument we transfer the victim:
define(['opUnits', 'myUnits'],function(opUnits, myUnits){ var action = { run: function(args){ var agressor = opUnits[args.agressor]; var victim = myUnits[args.victim]; agressor.attacking(victim); } } return action; })
I will not tire, on it we will finish the given article.
Play around in the still full jambs example.
I would be extremely grateful if you tell me where to read, how to disable ShadowDOM so that you can play: on / off.
Resources