À propos de l'immuable: histoire de la 9e place de la Coupe AI russe 2019

Je m'appelle Andrey Rybalka, je participe à la Russian AI Cup sous le surnom de lama et je vais vous redire comment ne pas gagner un macbook. Heureusement, je suis une personne expérimentée dans ce domaine - avec ces mains, je n'ai pas gagné jusqu'à 7 pièces.


La tâche de cette année était donc un jeu de plateforme / shooter 2D, pour lequel vous deviez écrire un bot. Le jeu ressemblait à ceci:



Le bot ressemblait à ceci:



Si vous êtes intéressé par la façon dont l'image # 2 a joué dans l'image # 1, s'il vous plaît, sous cat.


Si vous n'avez pas participé et n'avez pas lu d'autres articles, je vous recommande de voir d'abord à quoi tout cela ressemble en dynamique sur le site ou sur le tube:



Système de tournoi


Pour commencer, plus de 2 semaines sont accordées pour la programmation. Ensuite, le premier tour commence. Cela dure 2 jours et les 300 meilleurs durent. Après le tour, les règles du jeu changent (maintenant nous contrôlons deux personnages à la fois) et une autre semaine est donnée, après quoi le deuxième tour passe. Ensuite, les règles sont à nouveau compliquées (maintenant nous jouons sur des cartes beaucoup plus complexes), une autre semaine est donnée et, enfin, nous jouons la finale.


Mais ce n'est pas la fin. Après les finales, il y a une autre semaine, à la fin de laquelle le bac à sable s'arrête simplement, et les 6 meilleurs, hors vainqueurs des finales, sont également décernés. La différence fondamentale entre la finale du bac à sable et la finale du championnat est que dans le bac à sable, les jeux sont créés dans un format aléatoire, et pas seulement dans le format de la manche en cours.


Historique de participation


Où sans ça? Vous pouvez sauter ceux qui ne sont pas intéressés. La partie technique sera plus faible.


Semaine de test bêta et première manche


J'ai commencé à programmer, semble-t-il, le lendemain du début de l'Open Beta Test. Mais personnellement, c'était un peu démotivant pour moi que les organisateurs cette fois, contrairement au passé, aient décidé de s'éloigner à nouveau de la pratique de publier un pseudo-code d'un simulateur. Certes, parmi les participants, il y avait ceux qui aiment écrire un simulateur par rétro-ingénierie, mais je ne suis pas l'un d'eux et je m'ennuyais à le faire. Comme ce n'était pas mon premier championnat, je savais que tôt ou tard je m'impliquerais, mais pour la raison décrite ci-dessus, je me suis impliqué trop tard. En conséquence, ce que certains participants ont fait au cours des deux premiers jours, j'ai pu me forcer à terminer en seulement une semaine et demie. En conséquence, j'ai écrit la première ligne de code du bot 5 jours avant le premier tour. Bref, au début du premier tour, je n'étais pas prêt et le tour s'est passé sans ma participation. J'ai décidé de me frayer un chemin jusqu'au deuxième tour, grâce à la numérotation par poste.


Deuxième tour


À ce stade, j'ai déjà programmé au maximum, en moyenne 4 à 6 heures par jour. Quelques jours avant le début du deuxième tour, j'ai téléchargé la première version du bot. Elle est immédiatement remontée rapidement et est rapidement entrée dans le top 10 des bacs à sable. Puis le tour a commencé, où j'ai pris la cinquième place.


Finale


J'ai passé la première soirée de la dernière semaine (sur quatre, car un départ était prévu vendredi) pour trouver le chemin. Il y avait encore beaucoup d'idées, mais ce sur quoi se concentrer et laquelle des idées donnerait potentiellement le plus grand résultat n'était pas clair, j'ai donc essayé d'améliorer en premier lieu ce qui a causé le plus grand nombre de défaites. Mardi et mercredi, il s'agissait de l'amélioration de la visée et du tir, ainsi que du contrôle des trousses de premiers secours.


Ensuite, j'ai vu que quelque chose s'était produit auquel je m'attendais depuis longtemps, mais j'espérais que cela ne se produirait pas - certains opposants ont commencé à utiliser activement les mines.


Le fait est que lors de l'OBT, les organisateurs ont écouté la proposition des participants et changé l'une des mécaniques de jeu. Dans le cas général, cela est bien sûr bon lorsque les participants sont écoutés. Mais précisément cette fois, la présence de commentaires a joué une blague cruelle. Bref, les organisateurs ont permis de faire exploser des mines d'un coup de feu.


C'est en soi logique. Si j'étais une mine et qu'ils me tiraient dessus, j'exploserais aussi. Mais le problème est que nous avons joué sur des points, et des points ont été accordés non seulement pour des meurtres, mais aussi pour des dégâts. Ainsi, il s'est avéré que si vous vous approchez suffisamment de l'ennemi, placez des mines sous vous et tirez dessus, vous tuez l'ennemi et vous-même avec une explosion. Il est impossible d'esquiver. Vous obtenez tous les deux 1000 points pour les deux personnages mourants, mais vous obtenez également des points pour les dégâts. Ainsi, si l'ennemi était en bonne santé, vous recevrez 1000 points pour tuer et 100 pour les dégâts, et l'ennemi - seulement 1000.


Bien que tout le monde le sache, mais au sommet, presque personne n'a utilisé de mines jusqu'au dernier. Je ne sais pas comment les autres, mais personnellement, je ne l'ai pas utilisé simplement parce que je ne voulais pas abuser de l'erreur de calcul involontaire. Mais, comme ils l'ont dit dans un film, je savais que tôt ou tard nous passerons à ces ordures.


Bref, au milieu de la nuit du mercredi au jeudi, j'ai fouetté l'auto-perturbation. Purement situationnel, rien de bien avancé. Juste par le principe que si à la tique actuelle mon personnage est rentable de se suicider, et qu'il peut le faire, il le fait. Comme la pratique l'a montré, certains autres participants prévoyaient d'utiliser les mines à l'avance, ils ont donc mis en œuvre un suicide plus avancé, mais ont stratégiquement prévu de le déchaîner juste avant la finale. En conséquence, il s'est avéré ce qui s'est passé - 9ème place en finale, ainsi que l'année dernière.


Sandbox Finale


Après la finale, j'ai quitté la ville pour les vacances. Il est revenu une semaine plus tard et le premier soir, littéralement en 2-3 heures de travail, a considérablement amélioré les mines, dont je parlerai plus en détail dans la partie technique. Les autres changements concernaient principalement l'édition de bogues et l'achèvement de la fonction d'évaluation. Bref, un peu de programmation et beaucoup de tests.


Partie technique


Soit la vitesse de Java, soit le rayon de courbure des mains, mais plutôt, les deux ensemble, une fois de plus ne m'a pas permis de m'entendre avec une pure simulation. Par conséquent, le mouvement, le tir normal et l'installation de mines sont séparés de moi.


La simulation


À travers les yeux d'un bot, le monde ressemblait à ceci:



Sur la vidéo, je pense, et donc tout est à peu près clair. Dans le coin supérieur gauche se trouve le débogage de la fonction d'évaluation. Vous pouvez voir en quoi consiste le score de chaque trajectoire. Silhouettes jaunes (par exemple, à 2h30) - ce sont les mêmes trajectoires dans 9 directions, celles-ci seront plus basses. Les lignes de flèches quelque part à partir de 2:50 sont une recherche de chemin (rouge - à l'ennemi, vert - à la trousse de premiers soins, jaune-vert - à l'arme, bleu - à la mine). Les carrés verts à la fin de la vidéo sont mes tuiles PVS, qui sont visibles depuis la tuile sélectionnée. Les points rouges en leur centre indiquent d'où vient la visibilité inverse.


Les mouvements des personnages simulent sans microtics. Les vols de balles aussi, mais j'utilise plusieurs hacks pour que les situations soient moins susceptibles de se produire lorsque la coche complète la balle "traverse" le personnage sans le toucher, même si elle aurait dû le toucher avec microtics. Par exemple, si vous y réfléchissez, la situation décrite ne peut se produire que dans les coins:



Au tick 1, la balle est à la position 1, au tick 2 - à la position 2, respectivement. Sans Mikrotik - Boris-raifort - vous obtenez, avec Mikrotik - tir d'oreille. Donc, pour que cela se produise, la position de la puce dans les graduations 1 et 2 doit être sur des côtés différents d'au moins l'une des faces du personnage ou de la tuile (un rectangle à la menthe). Ainsi, il est possible de simuler une balle sans microtics jusqu'à ce que, comme appliqué à l'image ci-dessus, la condition old_bullet.x > character_left_side.x != new_bullet.x > character_left_side.x , et si cela se produit, alors vous devriez analyser cette coche plus attentivement .


Ensuite, à chaque tick, j'ai recalculé les trajectoires de toutes les balles volantes avant qu'elles n'entrent en collision avec le mur et j'ai enregistré leur position dans chaque tick du tableau, de sorte que dans la simulation, vous puissiez rapidement vérifier les collisions avec elles.


Pour un bazooka, après avoir heurté un mur, j'ai mathématiquement, sans microtics, calculé le point exact d'impact afin de calculer correctement l'épicentre de l'explosion.


De plus, à chaque tick, je remplissais le tableau dodge_trajectories - simulait le mouvement de chaque personnage, y compris les ennemis, dans 8 directions pour 25 ticks (silhouettes jaunes dans la vidéo du visualiseur, après avoir activé la case à cocher traj possible. Par exemple, à 2h30). Et, comme pour les balles, il a gardé toutes les positions possibles à chaque tick. Il a ensuite été utilisé dans de nombreux endroits, dont certains que je mentionnerai.


J'ai également calculé PVS par tuiles à l'avance. Pour chaque cellule, j'ai gardé une liste de tuiles, dont le centre peut être vu debout en elle. Il a été calculé par lancer de rayons. Cela peut être vu dans la vidéo du visualiseur, à la fin. Les carrés verts sont des tuiles visibles depuis la cellule sélectionnée. Les points rouges en leur centre indiquent d'où vient la visibilité inverse.


Rechercher un moyen


Implémenté par l'algorithme de Dijkstra selon les points de cheminement. La tuile dans laquelle vous pouvez vous tenir était considérée comme Waypoint. L'adaptation de l'algorithme pour le jeu de plateforme 2D est la sienne, propre au pays, et donc, dans un souci d'optimisation, il est construit sur des béquilles. Mais cela a fonctionné assez rapidement: j'ai déjà construit Dijkstra (je n'ai pas compris comment le dire correctement) à partir de chaque sujet au niveau de chaque tuile. J'ai construit des routes uniquement celles qui avaient une capacité de cross-country bidirectionnelle. C'était nécessaire pour que plus tard vous puissiez très rapidement obtenir le chemin de n'importe quelle tuile à n'importe quelle trousse de premiers soins / arme / mine. Il était possible de se débarrasser de cette restriction, mais dans la pratique, j'estimais qu'il était plus raisonnable de consacrer du temps à d'autres choses, car cette restriction n'était pas très dommageable.


De plus, chaque fois que je passais à une autre tuile avec n'importe quel personnage (le mien et celui de l'ennemi), je comptais pour lui le chemin vers l'ennemi le plus proche, une trousse de premiers soins, ainsi que vers la mine et les armes, s'il en avait encore besoin.


Sur mon ordinateur personnel dans un jeu avec 1000 ticks, la recherche de chemin entière a pris environ 100 ms au total.


Si le chemin croise un ennemi ou un ami, j'ajoute simplement quelques dizaines d'unités à son poids. Ainsi, s'il y a un détour relativement court, ou si après cela il est plus rentable de courir vers un autre objet, je le ferai. Dans la vidéo du visualiseur ci-dessus, cela peut être vu à 2h55, où le chemin vers l'ennemi le plus proche a été contourné, car un chemin droit croise le deuxième caractère.


Visualisation du chemin de recherche:



Les carrés violets translucides sont des points de cheminement, ce sont aussi les sommets du graphique. Flèches vert lime - bords 1 colonne 2
1 à ne pas confondre avec les côtes; 2 toute coïncidence de nature monarchique est aléatoire [environ]


Mouvement


Deux personnages ont marché à tour de rôle. Si vous pensiez juste - "et maintenant il est entré dans le top 10?!", Alors vous avez pensé correctement. :) Il n'y avait pas assez de ressources pour marcher avec les deux. Une exception a été le cas de l'apparition d'une nouvelle balle volante.


La base du choix d'une trajectoire est la génétique sans croisement. Mais si l'année dernière, dans le football, la génétique s'est montrée parfaitement, alors cette année elle a donné un tout petit avantage. Mes expériences ont montré un résultat très proche aussi bien en génétique qu'en recherches aléatoires. Je vois plusieurs raisons à cela, mais la principale me semble être la suivante: Dans le football, nous avions un objectif fixe - le ballon. La plupart du temps, son comportement était prévisible - si nous trouvions une bonne trajectoire pour frapper le ballon au but et le suivre, puis après 20 tics, cette trajectoire sera probablement toujours bonne, car le ballon ne peut pas changer spontanément sa trajectoire. Mais cette année, nous avons joué non pas avec le ballon, mais avec des personnages ennemis. Ils ont changé de comportement à chaque tick. La pertinence de la trajectoire est donc restée très courte.


J'ai toujours utilisé la génétique pour deux raisons:


  1. Je viens de copier son code de l'année dernière. C'était même drôle à quel point j'avais peu à faire des changements, à l'exception de la fonction d'évaluation, de sorte que le bot a commencé à se déplacer de manière tolérable. En bref, j'ai écrit un simulateur pendant une semaine et demie, puis pendant environ une heure, j'ai copié la logique du mouvement de l'année dernière, dans une autre heure j'ai jeté une estimation simple et le bot, lors du tournage d'un démarrage rapide, a commencé à gagner assez stable avec.
  2. J'aime apparemment la douleur. Sinon, je ne sais pas comment expliquer à nouveau pourquoi j'ai écrit en Java, et pas comme tous les tops normaux:


J'ai donc dû tout faire dans tous les sens pour que la stratégie ne tombe tout simplement pas avec un timeout à chaque premier match. Et l'approche avec la génétique m'a permis de sauvegarder un instantané de la simulation du monde et des points comptés, puis, lors du calcul des générations suivantes, de désactiver les instantanés uniquement à partir du premier gène muté .


C'est-à-dire en gros, si dans la première génération, je suis allé 10 tiques vers la droite, puis j'ai sauté, et dans la deuxième génération, j'évalue un mutant qui va de 10 tiques vers la droite puis saute vers le bas, alors le point de référence pour le mutant sera l'instantané précédemment calculé du monde et des points après 10 mouvements vers la droite. Ainsi, j'ai considérablement réduit le calcul.


Igogo, un algorithme de mouvement approximatif:


  1. Nous générons N génotypes aléatoires. Chacun se compose de M gènes aléatoires. Chaque gène est un nombre codé par action: un numéro est responsable de la direction du mouvement, le second est pour sauter / marcher / sauter vers le bas, le troisième est pour le tir de base (uniquement pour la fonction d'évaluation, l'algorithme de tir de base sera décrit ci-dessous), le quatrième pour le nombre de répétitions de ce action. Le nombre total d'actions dans le génotype, ainsi que les répétitions, ne dépasse pas la profondeur de simulation - 40 ticks.
  2. Nous leur ajoutons un certain nombre de génotypes codés en dur: mouvement direct dans 9 directions (8 côtés + immobile) et quelques préréglages simples, ce qui en pratique a aidé à sortir un peu plus rapidement de certaines situations typiques du labyrinthe. Par exemple, ce sont les trajectoires: ⮤ ⮥
  3. Ajoutez le meilleur génotype du mouvement précédent.
  4. Évaluez tout, laissez le M meilleur.
  5. Nous créons la prochaine génération dans laquelle dans chaque génotype un ou plusieurs gènes ont muté.
  6. Nous les ajoutons au pool général, qui contient déjà le M best de la dernière génération.
  7. Nous répétons, à partir du point 4, plusieurs fois.

Fonction d'évaluation


En fait, l'endroit où vit la stratégie.


Tout au long du championnat, un tas de métriques de toutes sortes ont été ajoutées (pour être précis, 57). Certains d'entre eux n'ont pas vécu pour voir la finale. L'autre partie a survécu, mais dans le contexte d'inflation du score pendant le championnat, cela n'a pratiquement pas affecté le résultat, mais le reste, de l'ordre de 20-25, était précisément responsable du mouvement et du tir de base.


Je vais donner quelques exemples de métriques importantes, dans un ordre aléatoire:


  1. Pénalité pour une explosion de balle / bazooka en moi.
  2. Bonus pour les degrés de liberté. C'est-à-dire pour le nombre de directions (haut / bas / gauche / droite) où je peux me déplacer de la tuile actuelle. Comme vous pouvez le deviner, plus il y a de degrés de liberté - plus il y a de chances d'esquiver une balle. Ce bonus a obligé le bot à s'en tenir aux plates-formes et aux escaliers. Le bonus est trois fois plus élevé si l'adversaire a un bazooka.
  3. Pénalité pour la distance (par la recherche du chemin) à la trousse de premiers soins la plus proche; pour la distance moyenne (par la recherche du chemin) à toutes les trousses de premiers soins; parce que le chemin de lui à la trousse de premiers soins la plus proche de lui est inférieur à celui de moi à sa (!) trousse de premiers soins.
  4. Pénalité pour le fait que l'ennemi voit ma tête.
  5. Pénalité pour le fait que l'ennemi voit mes jambes (les points 4 et 5 ont obligé le bot à se cacher derrière des abris partiels).
  6. Bonus d'attaque lors du rechargement des armes ennemies.
  7. Bonus pour les coups réalisés.
  8. Pénalité pour trop ou trop peu de distance de l'ennemi. Cela dépendait de plusieurs facteurs: la santé des deux, si les armes des deux étaient rechargées, etc.
  9. Bonus pour les conditions de travail dangereuses.
  10. Pénalités pour l'état de chute et l'état de vol sur le jumppad (car ces deux états ne peuvent pas être interrompus, et donc, ils sont beaucoup moins susceptibles d'échapper à la balle).
  11. Le bonus pour le nombre de trousses de premiers soins qui se trouvent de l'autre côté de moi, par rapport à l'ennemi (c'est-à-dire, par exemple, si l'ennemi est à ma droite, alors j'obtiendrai un bonus pour le nombre de trousses de premiers soins qui sont à ma gauche. Rounds 1 et 2, où le seul moyen de passer de l'ennemi à ces trousses de premiers soins était par moi).
  12. Bonus pour suivre le chemin de l'ennemi, de la trousse de premiers soins (si je suis blessé), d'une mine (si j'en ai moins de deux), des armes (s'il n'y en a pas).
  13. Pénalité pour la proximité des murs au cas où l'ennemi aurait un bazooka. Plus précisément, j'ai considéré combien de tiques la roquette ennemie, si elle la relâche, volera vers le mur près de moi, et sur cette base, cette zone près du mur d'où il est impossible d'échapper à l'explosion était considérée comme dangereuse et j'ai reçu une amende pour y être.
  14. Bonus du Nouvel An.

Bullet Evasion


Cela se produit automatiquement, en raison de ce qui précède


Viser


Il se passe beaucoup de choses. Le plus important est peut-être un algorithme simple et efficace qui décide si je dois pointer une arme vers l'ennemi (car viser augmente la propagation des armes):


  1. Nous considérons l'angle entre (last_angle - min_spread) et (last_angle + max_spread).
  2. Nous tirons les rayons du centre de mon personnage aux deux coins de l'ennemi AABB le plus proche de la perpendiculaire. Si l'un d'eux est hors de portée (last_angle - min_spread) ... (last_angle + max_spread), nous coupons cette plage.
  3. Nous considérons l'angle entre ces rayons.
  4. Divisez le premier deuxième coin par le premier, nous obtenons la couverture (couverture). Représente la probabilité actuelle en pourcentage que la trajectoire de la balle tirée croise la boîte de l'adversaire.
  5. Nous simulons une action dans laquelle la visée d'un ennemi se produit, avec un changement de dispersion.
  6. Répétez les étapes 1 à 4 pour le nouvel ensemble [last_angle, min_spread, max_spread]. Ainsi, nous considérons quelle couverture sera au cas où nous viserions.
  7. En conséquence, nous avons la couverture actuelle, ainsi que la couverture prévue au cas où nous viserions l'arme sur l'ennemi. Si la couverture estimée est supérieure à la couverture actuelle, nous visons.

Démo 2 points:



Si les lignes sont vertes, l'ennemi tombe entièrement dans la zone de visée. Orange - partiellement, rouge - ne tombe pas du tout.


Mais je ne vise généralement pas le ventre de l'ennemi, mais un point plus optimal.


Pour un pistolet et un fusil d'assaut, si l'ennemi est dans les airs, je considère combien de tiques la balle atteindra sa position actuelle, puis je prends de son tableau dodge_trajectories décrit ci-dessus toutes ses positions possibles à travers ce nombre de tiques, en moyenne toutes les 8 positions et, lorsque je viser, je considère que l'ennemi est à ce point moyen.


S'il se tient au sol, je vise «sur la tête», sur la base de ces considérations qu'il ne peut que sauter vers le haut, de sorte qu'un coup dans la tête est beaucoup plus difficile à esquiver qu'un coup dans l'estomac, et plus encore, dans les jambes.


Pour un bazooka, je prends également 8 positions possibles à travers autant de tiques que la fusée atteint l'ennemi. Pour eux, je construis un cône (plus précisément, un secteur d'un cercle) qui décrit toutes ces positions et à l'intérieur de ce cône je trie les trajectoires en quelques pas. Pour chaque trajectoire, je simule un vol de fusée, accompagné d'une explosion en cas de choc dans le mur. La trajectoire dans laquelle l'ennemi a le moins de chance d'esquiver, je l'utilise pour viser.


Même dans certains cas, pour tomber et voler du jumppad (c'est-à-dire ces états que l'ennemi ne peut pas interrompre), je considère simplement le point d'interception par la balle de l'ennemi, en résolvant le système d'équations de mouvement. Le code a été copié de son propre robot de hockey de 2014, où il a été utilisé pour intercepter la rondelle. :)


Entre autres choses, dans certaines situations, j'ai annulé la visée s'il y a une position sur mon chemin pour les 10 tics suivants dans laquelle le rayon du centre de cette position dans la direction du dernier angle actuel intersecte avec l'ennemi. Cela m'a permis de viser avec le mouvement, sans changer l'angle, et donc sans augmenter l'écart.


Tournage


Le tir de base était cousu dans la trajectoire trouvée et était purement situationnel. Mais aussi, à chaque tic, mon bot essayait de calculer s'il était logique de tirer en ce moment, et si c'était le cas, il tirait. Par exemple:


  • si l'ennemi n'est pas assuré d'esquiver;
  • si la couverture était supérieure à une valeur fixe.

De plus, si mon arme était un bazooka, j'ai simulé toutes sortes de trajectoires au sein de la propagation en cas de tir immédiat, avec un pas. Et si le résultat montrait que le bénéfice potentiel dépassait le préjudice potentiel, je tirais. J'ai évalué l'avantage par 4 métriques par ordre décroissant d'importance pour moi et pour l'ennemi: mort garantie (la mienne ou l'ennemi), mort possible, dommage garanti, dommage possible. S'il y avait au moins une trajectoire dans laquelle, par exemple, j'étais assuré de me tuer et de ne tuer que l'ennemi, je ne tirais pas.


Il y a eu d'autres contrôles. Par exemple, je pourrais annuler un tir si dans le prochain tick de ma trajectoire la couverture potentielle est plus élevée que dans l'actuelle.


En général, la logique m'a dit que la prise de vue de base devrait être désactivée, mais les tests ont montré le contraire. Je ne peux pas imaginer pourquoi.


Mines


Au moment de la finale, ils étaient à leurs balbutiements: j'ai coché chaque tick ce qui se passerait si je mettais 1 ou 2 mines en ce moment, selon la santé de l'adversaire, et les tirais. Si je suis assuré de tuer l'ennemi et si cela est rentable pour moi (soit sur des points, soit parce que seul l'ennemi mourra), je l'ai fait.


Après la nouvelle année, au retour d'un voyage, j'ai écrit l'algorithme le premier soir, ce qui m'a amené à 2-4 places dans le tableau. L'algorithme était simple: je simulais à chaque tick ce qui se passerait si je passais immédiatement en mode berserk et courais vers l'ennemi afin de le faire exploser avec une mine dès que possible. Pour l'ennemi, j'ai simulé une simple évasion en utilisant les mêmes dodge_trajectories: j'ai pris trois trajectoires qui augmentaient la distance de moi. Par exemple, si j'étais à gauche de l'ennemi, j'ai analysé trois cas: l'ennemi s'enfuit à droite; l'ennemi court à droite et saute; court à droite et saute vers le bas. Si, dans les trois cas, j'avais la garantie d'avoir le temps de le tuer avec une mine et qu'il ne pouvait pas mettre les mines devant moi, je l'ai fait.


En outre, l'algorithme a pu sauter ou sauter au niveau de la stratégie de démarrage rapide - simplement en fonction de la différence dans la coordonnée Y.


En conséquence, sur les cartes finales, presque chaque match s'est terminé en moins de 1000 ticks avec l'auto-dynamitage de mes deux personnages.


Pacifisme


Voyant l'efficacité de mes mines, j'ai décidé de vérifier mon hypothèse faite plus tôt sur le canal télégramme qu'en finale il était possible de prendre une place au sommet sans tirer du tout. J'ai donc juste pris et désactivé le tournage dans mon bot, à l'exception d'un tir de mine. La seule chose est que pour ne pas perdre la note, ce mode n'était activé que si j'avais suffisamment de mines pour tuer l'ennemi.


Bref, mon bot est devenu pacifiste. Pour démontrer sa paix et son humilité, il n'a même pas regardé l'ennemi (voir vidéo). Eh bien, il a parfois tiré des mines, vous pensez. Je n'ai pas tiré sur des adversaires! Et s'ils sont morts en même temps - eh bien, que pouvez-vous faire, un accident tragique.


En général, j'ai téléchargé cette version sur le site, créé 4 jeux avec chacun des 3 premiers puis ...



... a remporté 3 des 4 matchs contre chacun. En général, il est étrange que les voisins ne soient pas venus se plaindre de mon rire homérique au milieu de la nuit. :)


Admirez-vous:



Eh, si j'ai passé ces 2-3 heures avant la finale, et non après, cet article dirait peut-être comment gagner un MacBook, et non comment l'éviter par tous les moyens, qui sait. Hélas, l'histoire ne connaît pas l'humeur subjonctive.


Test


En général, j'ai testé toutes les modifications si souvent que le résultat était statistiquement significatif. Plus précisément, la limite inférieure de l'intervalle de confiance devrait dépasser 50%. Pour les finales du bac à sable, le script a sélectionné les coefficients. Au cours des années précédentes, j'ai essayé la génétique, mais cela a mal fonctionné - pour un résultat normal, il fallait jouer à trop de jeux. Cette fois, je viens de changer un coefficient à la fois et j'ai récupéré 200 matchs. Dans les cas où le résultat était bon, j'ai effectué des tests supplémentaires. Bref, a laissé du jour au lendemain deux douzaines de coefficients et a eu un résultat le matin.


En conclusion


C'est en quelque sorte que tout cela a fonctionné avec le chagrin en deux.


Au cours des derniers jours avant la fin du bac à sable, j'ai passé la plupart du temps à la deuxième place du tableau avec une marge importante par rapport au troisième, mais quelques heures avant la fin du bac à sable, je n'ai pas eu de chance et j'ai commencé une série de jeux selon les règles des tours 1 et 2, tandis que ma stratégie a le plus joué par les règles de la finale. Par exemple, ici, sur 13 matchs, il n'y en a qu'un selon les règles finales. Donc moi aussi, mon taux de victoire dans ces matchs est ressorti bien pire que d'habitude. En général, le tout-puissant aléatoire:



En conséquence, j'ai perdu trop de matchs, perdu tout mon avantage et je suis tombé de 2 à 4 places, et je n'ai pas eu le temps de récupérer, car le championnat était terminé. C'est bien que le bac à sable n'ait pas perdu la première place dans la liste des gagnants.


Encore une fois, je tiens à remercier Mail.ru Group pour ce prochain merveilleux championnat. , , , ( 1100 ). — , . , . , , , !


, . , , . " 8 ".

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


All Articles