
Au début de l'automne, le concours pour écrire des bots Mini AI Cup # 3 (alias Mad Cars) s'est terminé, dans lequel les participants devaient se battre sur des voitures. Les participants ont beaucoup discuté de ce qui fonctionnera et de ce qui ne fonctionnera pas, des idées ont été exprimées et testées à partir de simples ifs pour former des réseaux de neurones, mais les premiÚres places ont été prises par les gars avec la soi-disant «simulation». Essayons de comprendre de quoi il s'agit, comparons les solutions pour les 1Úre, 3Úme et 4Úme places et discutons sur le sujet d'autres solutions possibles.
Clause de non-responsabilité
L'article a été écrit en collaboration avec Alexei Dichkovsky (Commandos) et Vladimir Kiselev (Valdemar) .
Pour ceux qui veulent simplement lire les décisions des gagnants, je vous conseille de commencer tout de suite avec le point «Simulation».
ĂnoncĂ© du problĂšme
Cette fois, la mĂ©canique du monde ressemblait beaucoup au jeu mobile Drive Ahead: les joueurs ont reçu une voiture avec un bouton situĂ© dessus; la tĂąche consiste Ă appuyer sur le bouton de l'ennemi plus rapidement que lui. Si personne ne gagne en 600 ticks de jeu, la carte commence Ă couler dans un tas d'ordures, qui peut Ă©galement appuyer sur un bouton. En d'autres termes, vous devez protĂ©ger votre bouton contre les ennemis, le monde qui vous entoure et les tas d'ordures (vitalement, oui). Chaque joueur a eu 5 vies, le jeu est passĂ© de 5 Ă 9 tours, tandis que quelqu'un n'a pas mis fin Ă sa vie. Chaque manche s'est dĂ©roulĂ©e sur une carte alĂ©atoire et des voitures, les mĂȘmes pour les deux participants. Au total, il y avait 6 cartes diffĂ©rentes et 3 types de voitures - un total de 18 combinaisons diffĂ©rentes.
Chaque tour est divisĂ© en tiques. Une tique est un mouvement, comme aux Ă©checs. La seule diffĂ©rence est que les deux joueurs vont en mĂȘme temps. Il y a des compĂ©titions oĂč tout le monde se relaie, ou vous ne pouvez faire une action qu'une fois tous les quelques mouvements et sĂ©lectionner des unitĂ©s comme cadre .
Chaque tick au bot vient un état de paix et a la possibilité d'effectuer 3 actions:
,
,
. Ces actions font aller la voiture dans une des directions, et si en mĂȘme temps elle ne touche pas les roues de la terre, alors elles donnent une petite rotation Ă tout le corps (un peu de physique d'arcade). AprĂšs que les deux adversaires ont choisi une action, une simulation du monde du jeu est lancĂ©e, un nouvel Ă©tat est considĂ©rĂ© et envoyĂ© aux joueurs. Si quelqu'un a cliquĂ© sur un bouton, le tour se termine et le suivant commence. Tout est simple, mais il y a des nuances.
Des rĂšgles plus complĂštes peuvent ĂȘtre trouvĂ©es ici . Et voyez les matchs de la finale ici .
Description générale de la solution
La plupart des compĂ©titions d'Ă©criture de bots sont trĂšs similaires: il y a un nombre fini de ticks (il y a environ 1500 maximum pour un tour), il y a un nombre fini d'actions possibles, vous devez choisir une sĂ©quence d'actions pour ĂȘtre meilleur que vos adversaires. Un peu plus tard, nous reviendrons sur ce que signifie ĂȘtre meilleur, mais pour l'instant, nous trouverons comment rĂ©soudre le problĂšme principal - un grand nombre d'options: au dĂ©but, nous avons un Ă©tat initial, puis chaque machine peut se dĂ©placer de trois maniĂšres diffĂ©rentes, ce qui nous donne 9 combinaisons diffĂ©rentes pour deux voitures, d'ici le 1500Ăšme ce sera 9 ^ 1500 combinaisons diffĂ©rentes ... Ce qui est un peu plus que ce que nous aimerions si nous prĂ©voyons de les trier pendant l'existence de l'Univers.
Nous arrivons ici à ce qu'est la simulation . Ce n'est pas une sorte d'algorithme, mais simplement une recréation des rÚgles du jeu avec une précision suffisante ou complÚte pour qu'il soit possible de trier les solutions. Bien sûr, nous ne passerons pas en revue toutes les solutions, mais seulement une partie d'entre elles. Un algorithme de recherche sera utilisé à cet effet - dans l'arbre d'état du jeu, nous recherchons le meilleur pour nous. Il y a beaucoup d'algorithmes (de minimax aux SCTM), chacun a ses propres nuances. Il est préférable de vous familiariser avec les décisions écrites par les participants aux précédents concours d'IA. Cela fournira une compréhension de base de dans quelles conditions les algorithmes fonctionnent et dans quels cas pas. Il existe de nombreux liens pour cela dans un référentiel spécial .
Lors du choix d'un algorithme, vous devez considérer:
- limite de temps pour 1 tick (ici j'ai beaucoup mal calculé cette année, mais j'ai pu rester à la 3Úme place);
- nombre de joueurs. Par exemple, s'il y a trois joueurs, il sera difficile d'utiliser minimax;
- précision de la simulation, cela peut permettre la réutilisation d'anciens calculs;
- «Ramification» de l'arbre d'état (est-il possible de calculer tous les états possibles au moins 10 mouvements en avant);
- bon sens - ne commencez pas à écrire des SCTM si la compétition dure 4 heures.
Dans cette compétition, 1 tick a donné environ 10-13 ms (2 minutes pour tout le match). Pendant ce temps, le bot a dû lire les données, prendre une décision et envoyer une commande pour se déplacer. C'était suffisant pour stimuler environ 500 à 1000 mouvements. Itérer sur tous les états. L'algorithme de recherche le plus simple peut ressembler à une comparaison de trois options de mouvement: "50 ticks vont à gauche", "50 ticks vont à droite", "50 ticks click stop". Et aussi simple que cela puisse paraßtre, ce n'est pas trÚs loin de la décision du vainqueur.
Parce que nous ne comptons que 50 coups d'avance, ce qui dans la plupart des cas ne compte pas avant la fin du jeu, alors nous avons besoin d' une fonction d'Ă©valuation qui dira Ă quel point l'Ă©tat du monde est bon et mauvais pour nous. Le plus souvent, il repose sur l'heuristique et la comprĂ©hension de ce qui est important pour la victoire. Par exemple, dans la compĂ©tition de la Coupe AI russe de 2014, il y avait des courses, mais vous pourriez gagner si vous ĂȘtes arrivĂ© le dernier, si vous obtenez plus de points bonus. Par consĂ©quent, la fonction d'Ă©valuation devrait stimuler la collecte de points en mĂȘme temps qu'un mouvement rapide le long de l'autoroute. Le score ne peut ĂȘtre calculĂ© que pour le dernier Ă©tat de la simulation (aprĂšs 50 ticks) ou comme la somme des estimations des Ă©tats intermĂ©diaires. Souvent, l'estimation «s'estompe» dans le temps afin que les Ă©tats qui se produisent plus tĂŽt soient plus influencĂ©s. Parce que nous ne pouvons pas Ă coup sĂ»r prĂ©dire l'ennemi, alors les options futures sont moins susceptibles de se produire, nous ne compterons pas beaucoup sur eux. De plus, cette technique rend le bot plus rapide pour terminer ses tĂąches, et ne remet pas tout Ă plus tard. Mais il convient de noter que le bot prendra moins de risques pour le bien des avantages ultĂ©rieurs.
Puisque nous allons prédire l'état du monde en réponse à nos actions, nous devons en quelque sorte modéliser le comportement des ennemis. Il n'y a rien de compliqué et il y a quelques options courantes:
- Stub ou heuristique
Une logique de comportement simple est Ă©crite oĂč l'ennemi ne fait rien, ou choisit des actions basĂ©es sur de simples heuristiques (par exemple, vous pouvez utiliser vos premiĂšres versions de la stratĂ©gie ou simplement rĂ©pĂ©ter le mouvement prĂ©cĂ©dent de l'adversaire). - Utilisez le mĂȘme algorithme que pour vous
Nous essayons d'abord de trouver les meilleures actions pour l'ennemi (contre notre meilleure sĂ©rie d'actions du dernier coup, ou contre un talon), puis nous recherchons la meilleure action pour nous-mĂȘmes, en utilisant le comportement que l'ennemi a trouvĂ©. Ici, le bot tentera de rĂ©sister Ă des ennemis difficiles. Cette logique ne fonctionne pas bien au dĂ©but de la compĂ©tition, car de nombreux bots sont encore trĂšs faibles et votre dĂ©cision sera trop prudente avec eux. - Autre
Le mĂȘme minimax itĂšre sur tous les mouvements des joueurs en mĂȘme temps, et il n'aura tout simplement pas besoin d'heuristique.
Si vous implĂ©mentez toutes les Ă©tapes ci-dessus, vous obtiendrez trĂšs probablement un trĂšs bon bot, surtout si vous pouvez choisir une bonne fonction de notation. Mais, en regardant Ă travers ses combats, vous pouvez voir que dans certaines situations, il se comporte Ă©trangement. Corriger la fonction d'Ă©valuation de ces situations peut ĂȘtre difficile, ou il y a un grand risque de briser une autre logique. Ici, des bĂ©quilles et des ifs viennent Ă la rescousse. Oui, les derniers jours de la compĂ©tition se rĂ©sument souvent Ă Ă©crire des bĂ©quilles et des ifs afin de corriger les dĂ©fauts dans des conditions spĂ©cifiques. Personnellement, je n'aime vraiment pas cette partie, mais j'ai remarquĂ© plus d'une fois que ce sont les bĂ©quilles en finale qui peuvent affecter la disposition des places dans le top dix, ce qui signifie qu'un si non Ă©crit peut vous coĂ»ter un prix (mon cĆur me fait mal quand j'Ă©cris ces mots, je J'aime aussi les beaux algorithmes et solutions).
Q: Est-il possible de se passer du tout de simulation?
R: Oui, vous pouvez utiliser des solutions sur l'heuristique (arbres de décision, un tas d'if, etc.). Il y a un bon article avec les architectures IA sur l'heuristique.
Q: Dans quelle mesure l'utilisation de la simulation est-elle meilleure que les approches heuristiques?
R: Tout dĂ©pend de la tĂąche. Par exemple, ici, certaines combinaisons de cartes et de voitures peuvent ĂȘtre codĂ©es en dur avec des ifs et toujours gagner (ou tirer). Cependant, la simulation trouve souvent des solutions difficiles Ă penser par vous-mĂȘme ou Ă mettre en Ćuvre une heuristique difficile. Dans ce concours, lorsque vous retournez une autre voiture, les solutions sur les simulations mettent leur roue sur la roue de l'ennemi, ce qui Ă©teint le drapeau "en l'air", ce qui signifie que l'ennemi ne peut pas appliquer la rotation du corps et revenir sur les roues. Mais la dĂ©cision n'a pas rĂ©flĂ©chi Ă la signification de cela, elle a juste trouvĂ© des options oĂč l'ennemi tomberait plus vite sur le toit et presserait son bouton.

Q: Réseaux de neurones et RL?
R: Peu importe leur popularitĂ©, dans les compĂ©titions de robots, ces solutions fonctionnent rarement bien. Bien que les rĂ©seaux de neurones n'aient pas besoin de simulation, car ils peuvent simplement Ă©mettre une action basĂ©e sur les paramĂštres d'entrĂ©e de l'Ă©tat actuel, ils ont encore besoin d'apprendre quelque chose, et pour cela, ils doivent souvent Ă©crire un simulateur pour piloter des milliers de jeux localement. Personnellement, je crois qu'ils ont du potentiel. Ils peuvent peut-ĂȘtre rĂ©soudre une partie du problĂšme ou l'utiliser dans des conditions de temps de rĂ©ponse trĂšs limitĂ©.
Remarque
En ce qui concerne le nombre fini d'actions possibles, il convient de prĂ©ciser que parfois il est permis d'ajuster "en douceur" certains paramĂštres. Par exemple, non seulement avancer, mais avec un certain pourcentage de puissance. Dans ce cas, la «finitude» du nombre de conclusions peut ĂȘtre facilement obtenue simplement en utilisant plusieurs valeurs, par exemple 0%, 25%, 50%, 75% et 100%. Le plus souvent, deux seulement suffisent: "complĂštement allumĂ©" et "complĂštement Ă©teint".
La simulation
Dans ce concours, nous avons utilisé le moteur physique de tamia préparé. Les organisateurs s'attendaient à ce qu'il soit vieux, éprouvé et qu'il ait de nombreux emballages pour que tout le monde puisse l'inclure dans sa décision ...
Dans la dure rĂ©alitĂ©, le moteur produisait des valeurs diffĂ©rentes Ă chaque fois, ce qui rendait difficile le redĂ©marrage pour calculer les options de dĂ©placement. Le problĂšme a Ă©tĂ© rĂ©solu «de front» - un allocateur de mĂ©moire a Ă©tĂ© Ă©crit en C et un morceau de mĂ©moire avec l'Ă©tat du monde a Ă©tĂ© complĂštement copiĂ©. Un tel allocateur a mis fin Ă la possibilitĂ© d'Ă©crire des solutions dans des langages autres que C ++ (en fait, c'Ă©tait possible, mais trĂšs laborieux et un allocateur devrait encore ĂȘtre Ă©crit en C). De plus, la prĂ©cision de la prĂ©diction a Ă©tĂ© influencĂ©e par l'ordre d'ajout d'Ă©lĂ©ments au monde du jeu, qui nĂ©cessitait une copie trĂšs prĂ©cise du code que les organisateurs utilisaient pour calculer les jeux. Mais il Ă©tait dĂ©jĂ en Python. Le dernier point fort du cercueil des autres langages de programmation Ă©tait que le moteur est ancien et contient de nombreuses optimisations qui ne peuvent pas ĂȘtre recréées avec prĂ©cision pendant la compĂ©tition pour obtenir votre propre version dĂ©coupĂ©e de la simulation physique.
En conséquence, le moteur, qui était censé offrir à tous les participants des conditions égales pour simuler les mouvements, est devenu l'obstacle le plus difficile à cela. Plus de 10 personnes ont pu le surmonter et les 7 premiÚres places du classement ont été prises exclusivement par les gars qui ont fait une simulation précise, ce qui peut servir de preuve de son importance dans de telles compétitions.
Ă l'exception de quelques participants qui ont pu pĂ©nĂ©trer Ă l'intĂ©rieur du tamia et optimiser la copie de son Ă©tat, les autres ont eu une simulation d'environ les mĂȘmes performances (ce qui a rendu la compĂ©tition un peu plus intĂ©ressante, car vous savez que la lutte concerne l'algorithme de dĂ©cision, non "qui compte le plus de coups").
Algorithme pour rechercher et prédire un adversaire
à partir de ce point, une description séparée des solutions commence. Les algorithmes seront décrits au nom de son auteur.
Vladimir Kiselev (Valdemar) 4e place
Une recherche aléatoire (Monte Carlo) a été utilisée pour rechercher l'espace de la solution. L'algorithme est le suivant:
- Nous initialisons le gĂ©nome - une sĂ©quence d'actions (gauche, droite, arrĂȘt) pour 60 tiques - des donnĂ©es alĂ©atoires.
- Prenez le meilleur génome trouvé
- Modifier aléatoirement l'une des actions
- En utilisant la fonction d'évaluation, nous obtenons un nombre - un indicateur de la qualité du nouveau génome
- Si vous obtenez une meilleure solution, mettez Ă jour la meilleure solution.
- Répétez à nouveau à partir de l'étape 2
Mon simulateur a produit ~ 100k simulations du monde en 1 seconde, considérant qu'il y a en moyenne ~ 12ms par tick, nous obtenons 1200 actions par tick. Autrement dit, en 1 tick, nous parvenons à parcourir le cycle complet environ 20 fois.
Pour trouver la solution optimale, ce nombre d'itĂ©rations n'Ă©tait clairement pas suffisant. Par consĂ©quent, l'idĂ©e d'actions «d'Ă©tirement» a Ă©tĂ© mise en Ćuvre: au lieu d'un gĂ©nome de 60 mouvements, nous fonctionnerons avec une chaĂźne de 12 mouvements «étirĂ©s» - nous pensons que chaque action dure 5 ticks d'affilĂ©e.
Le plus: AmĂ©lioration de la qualitĂ© des mutations en rĂ©duisant la longueur du gĂ©nome, la simulation peut Ă©galement ĂȘtre exĂ©cutĂ©e toutes les 5 ticks et vĂ©rifier 100 gĂ©nomes au lieu de 20 (pour Ă©viter les chutes de temps, je me suis finalement arrĂȘtĂ© Ă 70).
Moins: les actions d'étirement peuvent conduire à des solutions non optimales (par exemple, se balancer sur le pare-chocs, au lieu d'un rack stable)
Il convient de noter les techniques qui ont considérablement amélioré la qualité de l'algorithme:
- Nous n'effectuons une initialisation aléatoire que sur le premier tick, le reste du temps nous réutilisons la meilleure solution trouvée avec un décalage de 1 mouvement (l'action sur le 2Úme tick est décalée sur le 1er, etc., une action aléatoire est ajoutée à la fin). Cela améliore considérablement la qualité de la recherche, car sinon l'algorithme "oublie" ce qu'il allait faire au dernier tick et fait des saccades insensées dans des directions différentes.
- Au début du cours, on fait des changements plus intensifs (on change le génome 2 ou 3 fois au lieu d'un) dans l'espoir de casser le maximum local (similitude de température dans la méthode de simulation du recuit).
L'intensitĂ© a Ă©tĂ© sĂ©lectionnĂ©e manuellement: les 30 premiĂšres itĂ©rations font 3 mutations, les 10 suivantes par 2, puis par 1. - La prĂ©vision des actions ennemies est trĂšs importante. Au dĂ©triment du temps pour rechercher notre propre solution, nous lançons une recherche alĂ©atoire du cĂŽtĂ© de l'adversaire, Ă 20 itĂ©rations, puis 50 pour nous-mĂȘmes, en utilisant des informations sur les mouvements optimaux de l'adversaire.
La meilleure dĂ©cision de l'adversaire est Ă©galement rĂ©utilisĂ©e au coup suivant avec un dĂ©calage. Dans le mĂȘme temps, lors de la recherche d'une solution Ă l'ennemi, le gĂ©nome du dernier mouvement est utilisĂ© comme mes actions prĂ©vues.
Pendant le concours, il a activement utilisé des outils de développement local, ce qui a permis de trouver rapidement des bugs et de se concentrer sur les points faibles de la stratégie:
- arÚne locale - lancement de nombreux matchs contre la version précédente;
- visualiseur pour le comportement de débogage;
- un script pour collecter des statistiques sur les matchs du site - vous permet de comprendre sur quelles cartes et machines la défaite se produit le plus souvent.
mortido:
Compter tous les 5 ticks semble risqué, surtout si l'ennemi s'éloigne des options que vous aviez prévues. En revanche, dans ce monde de jeu à 5 ticks, il ne s'est pas passé grand-chose.
De plus, dans ma décision, j'ai néanmoins ajouté des combinaisons aléatoires à chaque tick, mais je ne dirai certainement pas comment cela a affecté la décision.
Commandos:
Changer quelques actions avec un tel nombre de simulations ne semble pas trÚs significatif, car trÚs peu de changements se produisent en une seule action. Mais lorsque vous étirez une action à 5 ticks de sens, cela semble devenir plus.
Je n'aime pas non plus l'idĂ©e elle-mĂȘme - nous prenons le meilleur ensemble et essayons de le modifier quelque part au dĂ©but. Il semble illogique que le changement des premiĂšres tiques laisse les suivantes plus ou moins adĂ©quates.
Alexander Kiselev (mortido) 3e place
ArmĂ© d'articles de laurĂ©ats d'autres concours, j'ai dĂ©cidĂ© d'utiliser l'algorithme gĂ©nĂ©tique. Il s'est avĂ©rĂ©, cependant, quelque chose de similaire Ă une recherche alĂ©atoire ou mĂȘme une imitation de recuit, mais plus Ă ce sujet plus tard.
Nous encodons la solution avec un tableau de 40 nombres, oĂč -1, 0 et 1 correspondent aux mouvements
,
et
.
Au début de chaque tour, j'ai calculé combien de temps j'avais déjà passé pour tout le jeu, compté une nouvelle limite de temps basée sur le nombre de tours restants, et chaque tour que je supposais était de 1200 ticks. T.O. Initialement, j'ai essayé de ne pas passer plus de 11 ms par tour, mais je pouvais "marcher" un peu à la fin si les tours précédents étaient plus rapides que 1200 ticks.
Valdemar:
Fait intéressant, cette puce a aggravé le jeu pour moi. Il s'est avéré qu'il est toujours préférable de passer 20 à 30 ms que 11 en premier et 60 à la fin
Un tiers de cette fois, je cherchais le meilleur coup de l'ennemi, le reste a été dans le calcul de ma propre décision. Lors de la recherche d'un mouvement pour l'ennemi, mon comportement a été modélisé comme le meilleur du dernier mouvement, décalé de 1 tick. C'est-à -dire comme si je continuais à agir selon le plan établi dans la derniÚre tique, et il essaie de me résister.
La recherche de la solution elle-mĂȘme Ă©tait la mĂȘme pour lui-mĂȘme que pour l'adversaire:
- Nous prenons la décision du dernier coup et la décalons d'un coup (ce que nous avons déjà fait)
- Nous prouvons à la population de solutions aléatoires jusqu'à ce que nous remplissions tout
- Nous simulons toutes les décisions et définissons la forme physique à l'aide de la fonction d'évaluation. Nous nous souvenons du meilleur.
- Bien qu'il y ait du temps pour les calculs
- Astuce, ajoutez toujours 1 mutation de la meilleure solution actuelle Ă la population, rappelez-vous si c'est mieux
- Tant qu'il y a une place dans la nouvelle population et que le temps de calcul n'a pas été dépassé (vous pouvez aller sur le parquet d'une population peuplée)
- Nous prenons deux personnes différentes et repartons avec la meilleure forme physique - maman
- Nous prenons deux personnes différentes et partons avec la meilleure forme physique - papa (ne devrait pas coïncider avec maman)
- Croisez-les
- Muter si
RND <
- Nous simulons une solution et nous nous en souvenons, si elle est la meilleure
En consĂ©quence, nous retournerons la sĂ©quence d'actions considĂ©rĂ©e comme optimale. Le premier mouvement est envoyĂ© comme une action de bot. Malheureusement, il y avait un sĂ©rieux inconvĂ©nient dans mon plan, car le nombre de simulations pouvant ĂȘtre effectuĂ©es dans une tique Ă©tait trĂšs faible (y compris en raison de la longue fonction d'Ă©valuation), puis sur le serveur de compĂ©tition 4 points ont Ă©tĂ© effectuĂ©s une seule fois, et pour l'ennemi, cela n'a pas Ă©tĂ© effectuĂ© du tout. Cela a rendu l'algorithme plus comme une recherche alĂ©atoire ou un recuit simulĂ© (puisque nous avons rĂ©ussi Ă muter la solution 1 fois depuis le dernier mouvement). Il Ă©tait dĂ©jĂ trop tard pour changer quelque chose et nous avons rĂ©ussi Ă conserver la 3Ăšme place.
Il est important de mettre en Ćuvre les algorithmes de croisement, de mutation et de gĂ©nĂ©ration de solutions alĂ©atoires initiales, car cela dĂ©pend des dĂ©cisions qui seront testĂ©es, et une dĂ©cision alĂ©atoire complĂšte n'est pas aussi bonne qu'elle peut paraĂźtre Ă premiĂšre vue (cela fonctionnera, mais beaucoup plus d'options seront nĂ©cessaires).
Dans la version finale, des décisions aléatoires ont été générées en segments, ce qui excluait les solutions «saccadées» en un seul endroit:
- Ăquipe alĂ©atoire sĂ©lectionnĂ©e
- Pour toute la longueur de la solution (40 coups)
- On écrit la commande courante dans la cellule
- Avec une probabilité de 10%, nous modifions l'équipe actuelle au hasard
Selon une technologie similaire, une mutation s'est également produite - un segment aléatoire de la solution a été remplacé par une commande aléatoire. Le croisement a eu lieu en choisissant le point auquel la décision a été prise de 1 parent, et aprÚs du 2.
J'ai aimé que nous utilisions tout le temps dont nous disposions pour trouver la meilleure solution. Ce n'est pas grave si la solution n'est pas la meilleure - nous pouvons l'améliorer au prochain tick, car l'optimisation se révÚle "floue" dans le temps. , . , - , . ,
Valdemar:
1 , , .
Commandos:
â - .
â , . , ⊠, . " â. -.
(Commandos) 1
( ), n m . 3^2=9 . m + n 40 .
:
|----------- n -----------|---------- m --------| | ... | ... |
: , , . ( ).
n m , . , .
:
- , ( , ):
- , , , .
- , , . . . , , , .
- . ; ( ).
- n m . , .1, , . - ( ) , â , ;
- . , â . , ( ).
Valdemar:
, 2 . . , .
mortido:
Ouah! , . . , 2 , 40-60 . , 3 .
n + m == const ?
. n + m != const
, . , . - .
(Valdemar) 4
, . , ( , , ..) [0..1].
. : , .
, , : , .
, :
- â 70 180 ( : ).
, . - 0..500
- â [2pi, pi/4] [0, 1]
- â , ( ), ( , , )
- â , , , .
, , . - â . .
- Y â .
, 2 , .
.
:
mortido:
, .. , .
Commandos:
, . -
(mortido) 3
, chipmunk. . , , , , . .
3 .
, ( , , ):
- . , , ( , );
- , â , ; , 1 ;
- ;
- ( , );
- ( â+â, â-â);
- ( â+â, â-â); , , , ;
30 , , ( ); - , .
, , (, , )
Valdemar:
. , â , , â , ( ..) .
, , . .
Commandos:
, , ââ⊠? , ââ .
(Commandos) 1
SquaredWheelsBuggy
, .. , . Buggy , , ( /).
:
- ;
- ; â , , 1 0; .. ;
- . ; 10 ( );
- ( , );
- (, );
- â - , ;
- / ; , â ; .
1-5 , . 2 â â.
Valdemar:
, . , .
mortido:
, 10 .
IF'
(Valdemar) 4
, if'. 3 , , . , , -.
: , ââ â , - , ( , ) â .
:
- . , .
- â , .
- . â â .
, , .
, , .
, : . , , if' .
mortido:
, . .
Commandos:
if'. , , ⊠, , .
(mortido) 3
- .
3 . . . ââ, . , , .
, ââ . . , , , - . . , , .. .
, , , , , . ⊠. - â , ( , ).
Valdemar:
, . . ââ , if'. , â .
, + . , .
Commandos:
⊠, - â , , . , , .
(Commandos) 1
. (, , ). ( ) /.
pill carcass map , , ( ). island map, , .
island hole buggy. / , , ( ). â . , , . SquaredWheelBuggy . , , , . , ⊠, , .
(Pill map, Bus) , ( / 100% ).
pill hubble map. , ( ), . .
â , ...
, . , . ( ).
Valdemar:
, â . , .
mortido:
, ââ .
Valdemar:
. , . ( ) .
. ââ, , , , :)
, mailru , .
mortido:
: , ⊠, , ( ). , 3 , , ⊠.
Commandos:
- , . , , , . ⊠. â , .
â ++. . , . 1 -.
, . , . , , .
Mail.Ru Group .
Valdemar
mortido