📜 ⬆️ ⬇️

The first acquaintance with the architecture of the collectible card game "Last Argument"

Good day!

My name is Sergey, I am an independent game developer. In September 2014, I set a goal to realize the game in many ways similar to Hearthstone. I thought for a long time before taking on this project: can I do it? At that moment, the task seemed overwhelming for one developer. From the credits to the original game, it was obvious that at least ten people were working on it, besides Blizzard, there is already an established community and enough money for marketing. The game itself is based on the existing game world, which also somewhat simplifies the development of the Blizzards themselves. I do not have any of the above described concurrent conditions, and therefore I still have doubts about the expected success of this initiative. Nevertheless, I still took up this project - primarily because I like collectible card games and the work on this game itself brings me pleasure. I decided that this project, one way or another, would give me the opportunity to gain practical experience in developing such games. Even if, on the first attempt, I fail to transform this into some kind of commercially successful enterprise, the general cumulative experience gained during the work on this project will give me the opportunity, in the future, to experiment in this genre and, ultimately, Otherwise, I still feel for the original mechanics and the setting that will allow you to create your own gaming studio, specializing in the development of original collectible card games.

At the moment I have been working on the game for the eighth month and would like to start a series of articles with this publication, which opens up some of the nuances of how collectible card games work in principle. Probably for many experienced developers these articles are unlikely to be able to tell something fundamentally new; and yet, I hope that if you have never come across such games before, you will have an additional interest in them and maybe you will start developing your own game or maybe join this project.
')
In my work, I use python 3.3, django, redis and tornado for the server side of the action script + robotlegs project for the client. I do not exclude the possibility that in the near future I will also start writing a C ++ client under the Unreal Engine 4. Until recently, I was focused on working directly on the code providing game mechanics and therefore at this stage it was not too important for me, What technology to use to write the client part of the game. I just chose what I knew best.

Django is used for the administrative panel, which allows you to customize the effects when drawing certain cards, and also works with requests regarding the creation of new cards of the players themselves and the creation of a specific deck from the set that is open to the player. Matches themselves do not use the database - instead, they simply cache their own model in redis. The fight between two real players is carried out through the tornado application, which uses a permanent socket connection between two clients.

Very briefly, the architecture of the game looks like this:

image

Customers have both services to establish contact with a tornado application within a particular match, as well as individual services relating to the collection of maps. Making certain moves, one of the clients informs the server about it, based on the data received, the server analyzes the action taken by the player and creates a game script that sends back to both clients. Having received the game script from the server, the client gives it to the team responsible for playing it. In general, smooth and complex playback of all game events occurs due to two recursive functions: one function is on the server, it analyzes the settings of a particular card, passes through all the variables of a particular effect and forms an array of game actions; the second recursive client function, successively loses every game action found in the game scenario.

Probably, I will write a separate article about what the settings of this or that card are. Today, I will confine myself to just one superficial example of how this might look like in a real match: suppose a player has a card with a simple “Provocation” ability on his hands. This defensive ability compels first attacking a creature with the provocation ability and only after its death other creatures and an enemy hero.

Initially, one of the players notifies the server that this or that card is played. Based on the card index, the server determines its settings and gives the card settings to a special gaming reactor. The gaming reactor skips all the card settings through its recursive function and returns the already finished game script. Specifically, the "Provocation" ability works this way. The card itself stores the description of the ability in constants:



In our case, the reactor will skip all abilities through the period EtitudePeriod.SELF_PLACED. This means that he is trying to find the ability that needs to be activated immediately, as soon as the chip is on the field. As soon as he discovers this ability, by the level of influence he will be able to understand to whom it will be necessary to apply this ability. In this case, by the constant EtitudeLevel.SELF, he will understand that the ability needs to be applied to the very creature that triggered the triggering of this ability. At the third stage, the recursive function will set the type of the ability to EtitudeType.Provocation, then the reactor will change the characteristics of this creature in its model and form a game scenario, indicating the creature index and the ability to apply to this creature. The generated scenario will return the tornado to the application, which in turn will give it to its compounds.

A bit of code to complete the picture:

# match.py def place_unit (self, index, attachment) unit = self.get_unit(index, attachment) scenario = [] reactor = new Reactor(scenario) scenario = reactor.place_unit(unit) return scenario 


 # reactor.py def place_unit (self, unit) self.initiator = unit self.etitudes = initiator.etitudes[:] self.activate(EtitudePeriod.SELF_PLACED) return self.scenario def activate(self, period): if len(self.etitudes): etitude = self.etitudes[0] del self.etitudes[0] if period == etitude.period: targets = self.get_targets(etitude.level) # some other etitudes ... if etitude.type == EtitudeType.PROVOCATION: for target in targets: target.provocation = True action = {} action['type'] = 'provocation' action['target'] = {'index':target.index} self.scenario.append(action) self.activate(period) 


On the client, a similar recursive function iterates through the components of the game scenario, determines by index what element (card, creature) will be transformed and visualizes one or another effect depending on its type.

In general, this is all I wanted to say in my introduction. Thank you for attention!

Source: https://habr.com/ru/post/256889/


All Articles