C'est le défi bimensuel n ° 3. Thème: Algorithmes Génétiques
Ce défi est un peu une expérience. Nous voulions voir ce que nous pouvions faire, par défi, avec des algorithmes génétiques. Tout n’est peut-être pas optimal, mais nous avons fait de notre mieux pour le rendre accessible. Si cela fonctionne, qui sait ce que nous pourrions voir à l'avenir. Peut-être un roi génétique de la colline?
La spec est assez longue! Nous avons essayé de séparer la spécification dans The Basics - le strict minimum que vous devez savoir pour commencer à jouer avec le framework et soumettre une réponse - et The Gory Details - la spécification complète, avec tous les détails concernant le contrôleur, sur lesquels pourrait écrire le vôtre.
Si vous avez des questions, n'hésitez pas à nous rejoindre dans le chat!
Vous êtes un chercheur en psychologie comportementale. Nous sommes vendredi soir et vos collègues et vous décidez de vous amuser et d'utiliser vos rats de laboratoire pour une petite course de rats. En fait, avant de nous attacher trop émotionnellement à eux, appelons-les des spécimens .
Vous avez mis en place une petite piste de course pour les spécimens et, pour la rendre plus intéressante, vous avez placé des murs, des pièges et des téléporteurs sur la piste. Maintenant, vos spécimens sont toujours des rats… ils n'ont aucune idée de ce qu'est un piège ou un téléporteur. Tout ce qu'ils voient, ce sont des choses de couleurs différentes. De plus, ils n'ont aucune mémoire. Tout ce qu'ils peuvent faire, c'est prendre des décisions en fonction de leur environnement actuel. Je suppose que la sélection naturelle choisira les spécimens qui savent éviter un piège parmi ceux qui ne le font pas (cette course va prendre un certain temps ...). Que les jeux commencent! †
† 84 465 spécimens ont été blessés dans la réalisation de ce défi.
Les bases
Il s’agit d’un jeu à joueur unique (vos collègues et vous ne vouliez pas mélanger les populations afin que chacun ait construit son propre circuit). La piste de course est une grille rectangulaire, haute de 15 cellules et large de 50 cellules. Vous commencez avec 15 échantillons sur des cellules aléatoires (pas nécessairement distinctes) sur le bord gauche (où x = 0 ). Vos spécimens doivent essayer d’atteindre l’objectif correspondant à n’importe quelle cellule à x ≥ 49 et 0 ≤ y ≤ 14 (les spécimens peuvent dépasser de la piste à droite). Chaque fois que cela se produit, vous obtenez un point. Vous commencez également le jeu avec 1 point. Vous devriez essayer de maximiser vos points après 10 000 tours.
Plusieurs spécimens peuvent occuper la même cellule et ne vont pas interagir.
À chaque tour, chaque spécimen voit une grille 5x5 de leur environnement (avec lui-même au centre). Chaque cellule de cette grille contiendra une couleur -1
à 15
. -1
représente les cellules qui sont hors limites. Votre spécimen meurt s'il sort des limites. Quant aux autres couleurs, elles représentent des cellules vides, des pièges, des murs et des téléporteurs. Mais votre spécimen ne sait pas quelle couleur représente quoi et vous non plus. Il y a quelques contraintes cependant:
- 8 couleurs représenteront des cellules vides.
- 4 couleurs représenteront un téléporteur. Un téléporteur enverra le spécimen à une certaine cellule dans son voisinage 9x9. Ce décalage sera le même pour tous les téléporteurs de la même couleur.
- 2 couleurs représenteront les murs. S'installer dans un mur revient à rester immobile.
- 2 couleurs représenteront un piège. Un piège indique que l' une des 9 cellules dans son voisinage immédiat est mortelle (pas nécessairement la cellule de piège elle-même). Ce décalage sera le même pour tous les pièges de la même couleur.
Maintenant, à propos de cette sélection naturelle ... chaque spécimen a un génome, qui est un nombre de 100 bits. De nouveaux spécimens seront créés en croisant deux spécimens existants, puis en effectuant une légère mutation du génome. Plus un spécimen est réussi, plus ses chances de reproduction sont grandes.
Voici donc votre tâche: vous écrirez une seule fonction, qui reçoit en entrée la grille de couleurs 5x5 qu'un spécimen voit, ainsi que son génome. Votre fonction retournera un mouvement (Δx, Δy) pour l'échantillon, où Δx et Δy seront chacun l'un de ceux-ci {-1, 0, 1}
. Vous ne devez pas conserver de données entre les appels de fonction. Cela inclut l’utilisation de vos propres générateurs de nombres aléatoires. Votre fonction recevra un GNA que vous êtes libre d'utiliser à votre guise.
Le score de votre soumission sera la moyenne géométrique du nombre de points sur 50 pistes aléatoires. Nous avons constaté que ce score est sujet à une assez grande variance. Par conséquent, ces scores seront préliminaires . Une fois ce défi terminé, une date limite sera annoncée. À la fin de la date limite, 100 conseils seront choisis au hasard et toutes les soumissions seront rediffusées sur ces 100 conseils. Sentez-vous libre de mettre une note estimée dans votre réponse, mais nous noterons chaque soumission nous-mêmes pour éviter que quelqu'un triche.
Nous avons fourni des programmes de contrôleur dans une poignée de langues. Actuellement, vous pouvez rédiger votre soumission en Python (2 ou 3), Ruby , C ++ , C # ou Java . Le contrôleur génère les tableaux, lance le jeu et fournit un cadre pour l'algorithme génétique. Tout ce que vous avez à faire est de fournir la fonction de déplacement.
Attendez, alors qu'est-ce que je fais exactement avec le génome?
Le défi consiste à comprendre cela!
Comme les spécimens n'ont pas de mémoire, tout ce que vous avez à un tour donné est une grille de couleurs 5x5 qui ne vous dit rien. Vous devrez donc utiliser le génome pour atteindre l'objectif. L'idée générale est que vous utilisiez des parties du génome pour stocker des informations sur les couleurs ou la disposition de la grille, et que votre bot base ses décisions sur les informations supplémentaires stockées dans le génome.
Bien sûr, vous ne pouvez réellement rien y stocker manuellement. Ainsi, les informations réelles stockées ici seront initialement complètement aléatoires. Mais l'algorithme génétique sélectionnera bientôt les spécimens dont le génome contient la bonne information tout en éliminant ceux qui ont la mauvaise information. Votre objectif est de trouver un mappage à partir des bits du génome et de votre champ de vision en un mouvement, ce qui vous permet de trouver rapidement le chemin qui mène à l'objectif et qui évolue constamment vers une stratégie gagnante.
Cela devrait être suffisant pour vous aider à démarrer. Si vous le souhaitez, vous pouvez ignorer la section suivante et sélectionner votre contrôleur de votre choix dans la liste des contrôleurs en bas (qui contient également des informations sur l'utilisation de ce contrôleur particulier).
Continuez à lire si vous voulez tout ...
The Gory Détails
Cette spécification est complète. Tous les contrôleurs doivent implémenter ces règles.
Tous les caractères aléatoires utilisent une distribution uniforme, sauf indication contraire.
Génération de piste:
- La piste est une grille rectangulaire, X = 53 cellules de large et Y = 15 cellules de hauteur. Les cellules avec x ≥ 49 sont des cellules cibles (où x est basé sur zéro).
- Chaque cellule a une couleur unique et peut être ou ne pas être mortelle - les cellules ne sont pas mortelles sauf spécification contraire d'un des types de cellules ci-dessous.
- Il existe 16 couleurs de cellules différentes, étiquetées de
0
à15
, dont la signification changera de jeu en match. En outre,-1
représente les cellules qui sont hors limites - celles-ci sont mortelles . - Choisissez 8 couleurs aléatoires . Ce seront des cellules vides (sans effet).
- Choisissez 4 couleurs plus aléatoires . Ce sont des téléporteurs. Pour deux de ces couleurs, choisissez un décalage autre que zéro dans le voisinage 9x9 (de (-4, -4) à (4,4) sauf (0,0)). Pour les deux autres couleurs, inversez ces décalages. Si un spécimen monte sur un téléporteur, il est immédiatement déplacé de ce décalage.
- Choisissez 2 couleurs plus aléatoires . Ce sont des pièges. Pour chacune de ces couleurs, choisissez un décalage dans le voisinage 3x3 (de (-1, -1) à (1,1)). Un piège indique que la cellule à cet offset est mortelle . Remarque: la cellule de piégeage en elle-même n'est pas nécessairement mortelle.
- Les 2 couleurs restantes sont des murs qui empêchent le mouvement. Si vous essayez de vous déplacer sur une cellule murale, le mouvement restera immobile. Les cellules murales elles-mêmes sont mortelles .
- Pour chaque cellule de la grille sans objectif, choisissez une couleur aléatoire. Pour chaque cellule cible, choisissez une couleur vide aléatoire .
- Pour chaque cellule au bord gauche de la piste, déterminez si l'objectif peut être atteint dans un délai de 100 tours (selon les règles d' ordre des tours ci-dessous). Si tel est le cas, cette cellule est une cellule de départ admissible . S'il y a moins de 10 cellules de départ, ignorez la piste et générez-en une nouvelle.
- Créez 15 spécimens, chacun avec un génome aléatoire et un âge 0 . Placez chaque échantillon sur une cellule de départ aléatoire.
Ordre de rotation:
- Les étapes suivantes seront effectuées, dans l’ordre, pour chaque échantillon. Les spécimens n'interagissent pas ou ne se voient pas et peuvent occuper la même cellule.
- Si le spécimen a 100 ans , il meurt. Sinon, incrémentez son âge de 1.
- Le champ de vision du spécimen est donné - une grille de couleurs 5x5, centrée sur le spécimen - et renvoie un mouvement dans son voisinage 3x3. Les déplacements en dehors de cette plage entraîneront l'arrêt du contrôleur.
- Si la cellule cible est un mur, le déplacement est modifié en (0,0).
- Si la cellule cible est un téléporteur, l'échantillon est déplacé par le décalage du téléporteur. Remarque: cette étape est effectuée une fois , pas de manière itérative.
- Si la cellule actuellement occupée par l'échantillon (potentiellement après avoir utilisé un téléporteur) est mortelle, l'échantillon meurt. C'est la seule fois où les spécimens meurent (à l'exception de l'étape 1.1. Ci-dessus). En particulier, un nouveau spécimen apparaissant dans une cellule mortelle ne mourra pas immédiatement, mais aura une chance de quitter la cellule dangereuse en premier.
- Si le spécimen occupe une cellule de but, marquez un point, déplacez le spécimen dans une cellule de départ aléatoire et réinitialisez son âge à 0.
- S'il reste moins de deux spécimens sur le plateau, la partie se termine.
- Créer 10 nouveaux spécimens à l’âge 0 . Chaque génome est déterminé (individuellement) par les règles de reproduction ci-dessous. Placez chaque échantillon sur une cellule de départ aléatoire.
Reproduction:
Lorsqu'un nouveau spécimen est créé, choisissez deux parents distincts au hasard, en privilégiant les spécimens qui ont progressé plus à droite. La probabilité qu'un échantillon soit choisi est proportionnelle à son score de condition physique actuel . Le score de forme d'un spécimen est
1 + x + 50 * nombre de fois qu'il a atteint l'objectif
où x est l'index horizontal basé sur 0. Les spécimens créés au même tour ne peuvent pas être choisis comme parents.
Parmi les deux parents, choisissez-en un au hasard pour prendre le premier fragment du génome.
- Maintenant que vous parcourez le génome, changez de parent avec une probabilité de 0,05 et continuez à prendre des bits du parent résultant.
- Mutatez le génome entièrement assemblé: pour chaque bit, retournez-le avec une probabilité de 0,01 .
Notation:
- Une partie dure 10 000 tours.
- Les joueurs commencent le jeu avec 1 point (pour permettre l'utilisation de la moyenne géométrique).
- Chaque fois qu'un spécimen atteint le but, le joueur marque un point.
- Pour l'instant, la soumission de chaque joueur se déroulera sur 50 parties, chacune avec une piste aléatoire différente.
- L'approche ci-dessus résulte en plus de variance qu'il n'est souhaitable. Une fois ce défi terminé, une date limite sera annoncée. À la fin de la date limite, 100 conseils seront choisis au hasard et toutes les soumissions seront rediffusées sur ces 100 conseils.
- Le score global d'un joueur est la moyenne géométrique des scores de ces jeux individuels.
Les contrôleurs
Vous pouvez choisir l’un des contrôleurs suivants (car ils sont fonctionnellement équivalents). Nous les avons tous testés, mais si vous trouvez un bogue, souhaitez améliorer le code ou les performances, ou ajoutez une fonctionnalité telle que la sortie graphique, envoyez un problème ou envoyez une demande d'extraction sur GitHub! Vous pouvez également ajouter un nouveau contrôleur dans une autre langue!
Cliquez sur le nom de la langue de chaque contrôleur pour accéder au bon répertoire sur GitHub, qui contient README.md
les instructions d'utilisation exactes.
Si vous n'êtes pas familier avec git et / ou GitHub, vous pouvez télécharger l'intégralité du référentiel au format ZIP à partir de la page d'accueil (voir le bouton dans la barre latérale).
Python
- Le plus minutieusement testé. Ceci est notre implémentation de référence.
- Fonctionne avec Python 2.6+ et Python 3.2+!
- C'est très lent. Nous recommandons de l’utiliser avec PyPy pour une accélération substantielle.
- Prend en charge la sortie graphique à l'aide de
pygame
outkinter
.
Rubis
- Testé avec Ruby 2.0.0. Devrait fonctionner avec les nouvelles versions.
- C'est aussi assez lent, mais Ruby peut être pratique pour prototyper une idée de soumission.
C ++
- Nécessite C ++ 11.
- Prend éventuellement en charge le multithreading.
- De loin le contrôleur le plus rapide du peloton.
C #
- Utilise LINQ, il nécessite donc .NET 3.5.
- Plutôt lent.
Java
- Pas particulièrement lent. Pas particulièrement rapide.
Classement préliminaire
Tous les scores sont préliminaires. Néanmoins, si quelque chose ne va pas ou est obsolète, faites-le moi savoir. Notre exemple de soumission est répertorié à des fins de comparaison, mais pas en conflit.
Score | # Games | User | Language | Bot
===================================================================================
2914.13 | 2000 | kuroi neko | C++ | Hard Believers
1817.05097| 1000 | TheBestOne | Java | Running Star
1009.72 | 2000 | kuroi neko | C++ | Blind faith
782.18 | 2000 | MT0 | C++ | Cautious Specimens
428.38 | | user2487951 | Python | NeighborsOfNeighbors
145.35 | 2000 | Wouter ibens | C++ | Triple Score
133.2 | | Anton | C++ | StarPlayer
122.92 | | Dominik Müller | Python | SkyWalker
89.90 | | aschmack | C++ | LookAheadPlayer
74.7 | | bitpwner | C++ | ColorFarSeeker
70.98 | 2000 | Ceribia | C++ | WallGuesser
50.35 | | feersum | C++ | Run-Bonus Player
35.85 | | Zgarb | C++ | Pathfinder
(34.45) | 5000 | Martin Büttner | <all> | ColorScorePlayer
9.77 | | DenDenDo | C++ | SlowAndSteady
3.7 | | flawr | Java | IAmARobotPlayer
1.9 | | trichoplax | Python | Bishop
1.04 | 2000 | fluffy | C++ | Gray-Color Lookahead
Crédits
Ce défi était un énorme effort de collaboration:
- Nathan Merril: A écrit les contrôleurs Python et Java. Transforme le concept de défi du roi de la colline en une course de rats.
- trichoplax: test de jeu. Travaillé sur le contrôleur Python.
- feersum: écrit le contrôleur C ++.
- VisualMelon: écrit le contrôleur C #.
- Martin Büttner: Concept. A écrit le contrôleur Ruby. Playtesting. Travaillé sur le contrôleur Python.
- T Abraham: Playtesting. Testé Python et passé en revue les contrôleurs C # et C ++.
Tous les utilisateurs ci-dessus (et probablement quelques autres que j'ai oubliés) ont contribué à la conception générale du défi.
Mise à jour du contrôleur C ++
Si vous utilisez le C ++ avec Visual Studio et le multithreading, vous devriez obtenir la dernière mise à jour à cause d'un bogue lié à l'ensemencement de leur générateur de nombres aléatoires, qui permet de générer des cartes dupliquées.
'In particular, a new specimen which spawns on a lethal cell will not die immediately, but has a chance to move off the dangerous cell first.'