I will tell about my participation in the Russian AI Cup. I am a participant with the nickname
Hohol , who finished second in the final.
I already had some experience in writing a bot to manage a tank. The fact is that I have been participating in the ACM ICPC for five years now. The quarter-finals of our region are held within the walls of the Saratov State University, which, let me remind you, is one of the organizers of the Russian AI Cup. At the quarter-finals, an unofficial Code Game Challenge game contest is held every time. The essence is the same - write a bot that will tear all. And although the bots turn out to be wizards in a hat and with a staff, then racing cars, then hovercraft - we always called them tanchiki.
Since I participated in the CGC five times already, I didn’t have aroused much enthusiasm at first.
')
But having got acquainted with the rules, I decided that the competition was interesting to me.
Attracted the following items:
- Greater than usual in the CGC was the complexity of the game world. Tracks of the tank are controlled separately. The tower moves separately from the body. The objects of the world are rectangular, not round. And the coolest - control of several tanks at once. All this promised a significant depth of the game, a variety of tactics, the ability to do something unrealistically cool.
- Duration - a month (as opposed to only four hours, which are given for writing a bot in CGC). During this time, strategies should develop very strongly. Even if you do not participate yourself, it will be extremely interesting to observe the progress of the participants.
- It is clear that there will be a lot of participants. Sports interest is very large.
- Nice prizes, where without them.
Starting to deal with the system in more detail, I noticed that the organizers tried to make the entry threshold low. To view the fights on the server you do not need anything at all except the browser. The language pack immediately after downloading just works (at least in my case - C #, Windows).
Low entry threshold will have a positive effect on the number of participants. And the more participants, the more fun.
Round 1
Let me remind you that the competition began at midnight. I have sent some treshak to get acquainted with the system, spinning on the spot and trying to shoot at enemies. For some reason, he got into about zero cases (it turns out, to determine whether to shoot, I used self.GetAngleTo, and I needed self.GetTurretAngleTo). However, I made sure that the bot moves, does not fall, and went to bed. During the night, my rating was merged into minus infinity.
The next day I replaced this treshak with a slightly doped Quikstartgay (QuickStartGuy is a bot with the simplest strategy, the code of which was posted on the website). He had already chosen bonuses according to the utility / distance ratio and shot enemies with anticipation, considering that they move uniformly in a straight line. With my fusion rating, this was enough to win almost all the system battles. But here I was faced with a problem, which was subsequently much discussed among the participants: the first few battles the rating changes greatly, and the subsequent ones weakly. Accordingly, all the sharp jumps in the rating fell on the trash strategy, and then, despite the victories, the rating grew very, very slowly.
Engaged in movement. Learned to drive up to bonuses backwards, if so closer. This dramatically increased survival. I tried to improve the trajectory in order to drive not along broken lines, like Quikstartgay, but along beautiful curves. I tried to make an effort on a caterpillar proportional to the angle, proportional to the square of the angle, proportional to all functions of the angle and distance — nothing good could come of it. The tank stubbornly drove round bonuses, and was embarrassed to pick up. He scored and left the movement Quikstatgai.
By that time, the problem arose more important than curved movement. There was a fashion to disperse in the corners and shoot those who are in the center. For a long time I did not want to merge into this mainstream wave, wandered through the center and quickly died. In the end, I had to accept and go about the fashion - in the corner.
The next problem - the tank is often stuck, nose fit in the edge of the map. I had to do it - if we already have many ticks in place, and at the same time we are not in a warm cozy corner, it seems we are stuck, let's move to the center of the map.
I noticed that the gun of my tank too often stands idle because it does not have time to turn to the enemy. I almost turned around, ready to shoot, but then we picked up a bonus, turn to the next, the gun deviates from the enemy, and there is no place to shoot again. I decided that I needed to help turn the cannon with caterpillars. If the speed of the turn of the gun is not enough to turn to the target for the remaining reload time, turn to the target with the whole body. Now the gun never stood idle, the number of points gained per batch increased markedly. It also funny distorted the trajectory of movement - now all the turns have become a little smoother, so that the gun was always aimed at the enemy. It seemed that the tank was a better ride, although the change seemed to relate to a completely different part of the strategy.
It seems that the description of almost all the features at that time fit in a couple of paragraphs, but this was already enough to rise to about 10 places. Yes, after draining the rating grew very slowly, but surely. For a few days I went home to a village where there was no Internet, and forgot about tanks. When I remembered, I decided to look from the phone - to see how much the tank had merged in these days, and what the top looks like right now. My tank came first. This crooked, driving like a quikstartgay creation, miraculously got to the top line of the rating. Then I realized that there was no way back. If I was in first place, you need to bring your tanks to the final.
The first round was near.
I decided to re-engage in improving the movement. Too often, according to the Qikstartgaev algorithm, I drove past a saving bonus. Again I sort out the dependencies of efforts on the tracks from the angle. In proportion to the corner - already tried, the nonsense turns out. In proportion to the square of the angle - the same garbage. What else to try? Is it proportional to a cube? Well ok, come on. Suddenly I find out that it looks quite good. From a distance, the tank rushes to a bonus along a rather beautiful line. Only if the bonus is close, and the tank is not turned to it, it moves poorly. Well, if the bonus is close, we will move as best we can - like a quikstartgay. More by bonuses did not go.
Tops already knew how to dodge more or less normally. I am hopelessly behind in this regard. Place in the sandbox has gone down. I tried to implement a dodge using some strange heuristics, but nothing good worked out. As a result, all attempts had to be rolled back, and to enter the first half of the round, lagging behind the tops by a century in technical terms.
By the beginning of the break, I was somewhere in the 25th place. This is, of course, the passage to the next round, but being obviously backward is somehow unpleasant. It was clear that for the implementation of normal evasion I lacked knowledge of local physics. I knew by what law there was a change in the velocity of the shells, but I did not know the laws of movement of the tank. So you need to find out. Here I used the help of
anonymous . I put simple experiments - straight forward, backward movement - and found the characteristics of the tank in each tick. And
anonymous on these characteristics figured out the laws of motion. I decided that for the beginning I would have enough dodge with the use of full gas backward / forward. After clarifying these laws, I was ready to write the simplest evasion.
I realized it this way: I imagined that my tank was moving straight ahead evenly, checked if a bullet hit me in the next 100 ticks, and if so, checked it, and hit if I turned on the gas forward / back right now.
Such evasion worked well. To heighten the effect, being in the corner, I turned sideways to the enemy, who was now aiming at me.
These measures allowed me to rise to 8th place by the end of the first round. It's time for fighting 2in2na2.
Round 2
Looking through the battles from the first round, I noticed several cases in which the tank tried to evade, but the bullet in spite of this caught up with him. In this case, no interference with evasion was not. The expected behavior is either to dodge, or not to try to dodge at all. Obviously a bug. I chase the fight in Repeater, debazh. I find out that the acceleration of the tank during movement was much less than expected. But why? In the mud he drove chtoli, stalled? I am sending the movement log of the
anonymous tank - so and so, check if I’m not dumb. Confirms that the acceleration is too small. “Did you accidentally hit a tank?” - Well, hp crew was in the middle, and what's the difference? - And the movement of the tank from the hp crew just depends. - What? Movement depends on the crew? Where did you get such a thing from? “The rules say ...”
I open the rules, read:
“With decreasing crew health, the effectiveness of tank control decreases: it begins to move more slowly, turn the turret, and the time for reloading the gun increases.”
For some reason, re-reading the rules several times, I have never noticed this phrase. And in battles I never noticed that the wrecked tanks were slowed down by some. RTFM, comrades.
And yet, what is the matter? Does the pedal crew spin so that the tracks move? Does the tower turn by hands? Shells manually reload? 21st century in the yard like. Well at least shells do not throw their hands, otherwise they would fall directly under their tracks with a low HP.
In general, finding out the dependence of the engine power on the crew crew, I corrected the evasion.
It's time to once again improve the movement. I noticed that a tank often starts to strangely shake its nose when moving to a bonus, if this movement was preceded by a sharp turn. Because of this, the speed of movement was greatly reduced. This was why: we just turned out to face the bonus and now we are moving forward, but before that we turned to it with a high angular velocity, and the angular velocity by inertia turns us away from the bonus, and quite strongly. Then again we turn to the bonus with force, this time in the opposite direction, and everything repeats again. Then I began to extinguish the angular velocity, if it is now directed to the bonus. Fluctuations have stopped, the speed of movement to the bonuses has increased.
No matter how I thought, no matter how I experimented, I still did not understand how to optimally locate two tanks. If we spawn from the side, it seems to work well along a vertical wall. Well, yes, in the case of a spawn from the side, everything is generally good, because opponents from the other side kill each other. But if spawns fell between enemies, or even on a vertical line with the enemy, I don’t understand what to do. In the end, he somehow became clogged with two tanks into a corner, but nothing particularly good came out of it.
That is, now I know that nothing good happened. And then I was in the sandbox in the top five, and I thought that everything was chocolate. The second round came, and according to the results of his first half I was in 75th place. The final was held 50.
So break. Something needs to be urgently improved.
Tops obviously dodging better than me. I was able to dodge only gas back or forward. It was not enough. But in order to properly dodge with a turn, you need to know the physics of the world in much more detail. I prepared for a new stage of reverse engineering, which would take me a lot of time. But here
Noob gave me a link to the discussion of the gamedev.ru contest - a multipage theme that began simultaneously with the competition. People there have long figured out the whole physics of the world by experiment or by decompiling the locale runner. It is clear that if this data was made publicly available, many may have already used it. And I, reluctantly, used these ready-made data to write an accurate predictor of motion.
From now on, knowing the current state of the tank, and what efforts will be made to the tracks, I could accurately determine its condition through several ticks. Now I’ve been sorting out not two possible ways of evading a projectile, but 121 - 11 different efforts on the left and right caterpillar. A rebound avoidance was also added - for this, it was simply necessary to determine at what angle the bullet would hit the armor. Dodging efficiency has increased dramatically.
Tired of greasing on well evading enemies. It was often clear: if you shoot now, the enemy will easily dodge; however, after a few ticks, he found himself in a position from which he could no longer dodge. Maybe try to shoot only if the enemy just can not dodge? Fortunately, we have an ideal predictor of his position with various paths of retreat chosen. Implemented it. I got better, I was pleased. But after a while, he noticed that for some reason he would die faster. What nonsense? I improved the shooting, why did it worsen my survival rate? Comparing several fights before and after improvement, I realized what was happening. It turns out that spamming bullets, I often knocked the bullets flying at me, which I could not dodge. This happened from the very first versions, I never thought about it and simply did not notice that this was happening. But it turned out that in such a way
a lot of dangerous projectiles get off. Now, rarely shooting, I often fell under the bullets. Discouraged, I cut out a feature for ordinary projectiles, leaving only for premium ones - for them, it was completely appropriate.
He taught the tanks not to take the bonus that the team mate needed. Prior to this, the priority was always with those having TeammateIndex = 0, which, of course, is nonsense.
I started not to pick up the bonus corresponding to which hp is full, but to stand next to it, in case I receive damage. And pick up if there is an enemy nearby. Quite often it turned out in this way to drag out an unnecessary bonus to me right from under the nose of the enemy, who needed it.
And now, the time has come for the second half of the round. As far as I remember, for the first 8 hours of testing, I swayed somewhere around 65 places, with no visible chances of improvement. I was ready to say goodbye to the competition and continue to live a normal life, but the dizzying series of victories in the last hours raised me to 48th place.
It is a pity that some of the strong players I remembered did not make it to the final, such as
MrDindows and
twrlx .
The final
So, I reached the final, and this is already a serious business. Even though I was joking with friends, that I did not guarantee the killing of at least one enemy tank in the final, in fact, I was immediately set to hit the prize places. Even despite the discharge of the second round. Now I was a scientist - I knew that you could not trust the sandbox, and you only need to rely on statistics on duels.
We return to the idea of ​​accurate shooting. I abandoned her because she was badly hit by dangerous bullets. But what prevents me from checking whether this bullet is dangerous, and will I shoot it with a bullet? Nothing prevents, it is not clear why it did not immediately occur. Implemented, the shoot has become much better.
Now it was possible to fully concentrate on duels. At the moment, each of my three tanks considered itself part of a team of two tanks. They strangely wandered in search of bonuses and ridiculously swarmed in the corner, trying to stay there, not realizing that there were three of them, not two. I had to still create a separate three-tank strategy. But what will this strategy do? Again turned to the experience of current tops. I found out that the tactic of “taking the wall and standing with her, dodging” is very fashionable. No matter how boring it looked, this strategy, apparently, was the most optimal in relation to the complexity of implementation to efficiency.
So, it was decided - tanks are uniformly arranged along the vertical wall. At first, I tried to orient them with the face towards the middle of the map — perhaps it would be possible to effectively dodge the rebound. But it turned out only effectively catching hit in the forehead. Had to have vertically. Already at this stage a version turned out that was almost indistinguishable from the final one with the naked eye. But in fact there was still a lot of work to do.
It was the last day before the final.
It became clear that the recently improved shooting system no longer works. I only fired when I was sure to hit. Before I switched to the ranged strategy, this kind of shooting worked well. But now the enemy was almost always far away. So, in the hit, I was almost never sure, and almost never shot. At the same time, the enemy, from afar spamming shots at me, at least sometimes, but fell. I had to make a compromise - if the enemy is close, shoot only if he is sure of a hit, otherwise spam. With shooting on this finished.
Further, it turned out that the importance of evasions increased dramatically. Other types of fights almost always ended with the destruction of all rivals, the remaining points usually had more, and he won. One random hit in the weather did not do you - you could pick up a bonus, recover, and destroy the offender. In a duel, you can restore hp, but you will not be able to select the points that the enemy received for hitting. Like destroy him if he sits far away.
It was obvious that the current tops again dodge better than me. What is the problem? After analyzing the fights, I found out.
I'll tell you a little more about how I dodged at this time. I had a list of 121 possible pairs of efforts. There was a boolean function that determines whether a bullet will hit me, if I will use the next 100 ticks to apply this pair of efforts. It turns out that a bullet flying from me at a distance of 1 pixel did not threaten me. As a result, almost all the bullets touched the tank, but ricocheted. Only the whole party could hear the characteristic tinkling of the rebound from my tanks.
The problem was that in case of any accident - the wall was prevented from turning, the team mate pushed, the tank itself fired - the movement of the tank slightly deviated from the calculated one, and instead of a ricochet a clear hit occurred.
How dodged tops? Ricochets they were rare. They tried to stay away from the bullet, with a margin of distance. How can I achieve the same? Maybe just count your tank 10 pixels more in all directions? Yes, in this case, the tank really tries to be from a bullet at least at a distance of 10. But imagine that a bullet flies at us, and we can dodge it, remaining at best at a distance of 5 from it. The tank, which thinks that it is 10 pixels more, will consider that there is no chance to dodge it. And with peace of mind will allow her to get into herself.
I found the following way out: stop considering the danger of a bullet as a boolean and consider it real. Let minDist be the minimum distance from the tank the bullet has visited. Then we will assume that its danger is equal to max (0.20-minDist). Problem solved. Now the tank is kept away from any bullet, flying at a distance of at least 20 pixels.
Test fights have shown that the new dodge system really works much better. Now I almost consistently beat all but the very tops -
Milanin ,
Mr.Smile ,
SDiL ,
Romka .
Two hours before the start of the first half of the final. Time after time I look at the battles in which I merge
SDiL , in search of things that can be quickly improved. I find out that the majority of hits for me happen when several bullets fly at me. The tank dodges only the closest one, and catches the hits of the next ones. It is necessary to write dodge from several shells simultaneously. Do you have two hours? I have time to write. And potestit? For this you need to send a strategy to a server. And what if he is sticking up after this (it happened already before the start of the round), the version will be buggy, and I will not have time to re-send it? Take the risk.
After some time, the code is ready. I'm testing in locale runner. Bugs are not visible, it is not clear if what is needed is working - smart guys do not shoot particularly amicably. I send to a server, create fights with five tops, leave for a while from the company. I come back, I update the page, and the spirit intercepts when I see that all five fights have been won. I create again, beat everyone except
Mr.Smile . Watching fights - dodges perfectly. Well, by the beginning of the final, the tanks are ready.
With the start of testing, it was immediately in the first place. After watching some time, I went to bed contentedly. In the morning I found myself on the second,
Mr.Smile on the first. Indeed, his win rate is higher, and he kills me steadily. It seems there is nothing to be done, we will try to keep the second one.
The break began, it's time to improve the strategy. I was afraid that, following
Mr.Smile, many would start rashit long-range strategies. This would mean the need to make major changes in the event of such high performance rashes. But so far nothing of the kind has happened. I decided to wait for changes in the tactics of the opponents so that it was clear what to react to. In the meantime, look for obvious shortcomings in the existing strategy.
After reviewing several merged fights, I noticed in several cases a very strange spinning of my tanks - in the wrong direction I would like to at the moment. Repeater, debug. I go into several nested function calls, and I see the following code in one of them:
if (toy > 0) TurnTo(self.X, self.Y+1); else TurnTo(self.X, self.Y+1);
Hmm, not bad for a strategy in second place. Of course, the else must be "-1". I fix it.
I continue to view the merged fights.
I discover a battle in which two bullets fly into my tank, approximately parallel, one flying to the bottom of my tank, the other to the top. Expected behavior - the tank turns slightly and allows the bullets to fly on opposite sides of themselves. But instead he does some strange body movement, deviates from one, and gets a second in the stern. Repeater, debug. After some time, I understand what's wrong. I considered the danger from two projectiles as the sum of their dangers separately. Which, remember, is equal to max (0.20-minDist). But then it turns out that a pair of bullets with distances (19.1) has the same danger as a couple with distances (10.10). Obviously, the first couple is still more dangerous. Hence, we will consider the final danger as the maximum of individual hazards, and not the sum.
More that day almost did not change anything. I decided to go to bed early, wake up in the morning and check if the opponents had written something to me for the night.
In the morning, I see a message from
Megabyte on gamedev.ru, in which he says that he scolded me,
Mr.Smile and
Romka - now he will immediately rashit us. Hmm, interesting, check. I create five fights with him, and with a slack jaw I observe how he endures me in all fights. His tanks completely insolently fit close to mine, and methodically in turn shoot them. I checked several other players - there are no radical changes. Only at
Romka I found something interesting. If the initial location of the tanks is such that one of the players is located directly above the bunker, the other is under the bunker,
Romka travels in the same direction as me, takes one of my corners before me, and shoots a tank that goes to this angle after which deals with the rest.
In general, I had to urgently finish the tactics of close combat. However, this doping has been reduced only to an increase in the priority of bonuses (especially premium projectiles), when we are under attack. And I thought that I was under attack if there were at least two enemy tanks in my half of the field.
Megabyte I still merged. It seemed to me, because he was too late to react to his rush. I had to add the following code to the function that determines whether we are under attack:
if(enemies[0].PlayerName == "Megabyte") return true;
After such a hack, the response rate increased, and the win rate improved.
The second half of testing did not present any surprises. There was only intrigue, who will take the 6th place (the last prize) -
Romka or Commandos. Still,
Romka kept the place behind him.
Epilogue
In fact, a little disappointed with his own strategy in duels. Boring fights look.
A typical fight with the same as my long-range strategy from
valex :

Beginning of the epic battle

The atmosphere is heating up

Nerves are stretched to the limit.

Friendship won
Whatever the business
Mr.Smile - his rash in duels are just great. Killing almost any opponent in less than half the time allotted is something.
Let me remind those interested that the sandbox on the site will continue to work for several more months. Anyone who complains that he learned too late about the competition can play around with the tanks, not going too fast.
Thanks to the organizers for the wonderful competition. We hope to see the continuation in a year.
UPD:
code .