Le défi consiste à écrire un solveur pour le jeu de crayons et de papier classique Dots and Boxes . Votre code doit prendre deux entiers m
et n
en entrée qui spécifie la taille de la carte.
En commençant par une grille de points vide, les joueurs se relaient, en ajoutant une seule ligne horizontale ou verticale entre deux points adjacents non joints. Un joueur qui termine le quatrième côté d'une case 1 × 1 gagne un point et prend un autre tour. (Les points sont généralement enregistrés en plaçant dans la boîte une marque d'identification du joueur, telle qu'une initiale). Le jeu se termine quand plus aucune ligne ne peut être placée. Le gagnant du jeu est le joueur avec le plus de points.
Vous pouvez supposer que soit n = m
ou n = m - 1
et m
est au moins égal à 2.
Le défi consiste à solve
créer le plus grand jeu de points et de boîtes possible en moins d'une minute. La taille d'un jeu est simple n*m
. La sortie de votre code devrait être win
, draw
ou lose
devrait être le résultat pour le premier joueur en supposant que les deux joueurs jouent de manière optimale.
Votre code doit être compilable / exécutable sur Ubuntu à l'aide d'outils faciles à installer et gratuits. Veuillez rapporter votre score comme la plus grande zone que vous pouvez résoudre sur votre ordinateur en 1 minute avec le temps. Je vais ensuite tester le code sur mon ordinateur et créer un tableau d'entrées classées par rang.
Dans le cas d'un bris d'égalité, le gagnant sera le code le plus rapide sur le tableau le plus grand qu'il pourra résoudre en moins d'une minute.
Il serait préférable que le code génère non seulement des gains ou des pertes, mais également le score réel. Cela permet une vérification de la justesse de l'exactitude.
Réponses:
C99 - Carte 3x3 en 0,084 s
Edit: j'ai refactorisé mon code et fait une analyse plus approfondie des résultats.
Modifications supplémentaires: ajout de l'élagage par symétrie. Cela fait 4 configurations d'algorithmes: avec ou sans symétries X avec ou sans élagage alpha-bêta
Modifications les plus éloignées: Ajout de la mémorisation à l'aide d'une table de hachage, réalisant enfin l'impossible: résoudre une carte 3x3!
Caractéristiques principales:
#define HASHTABLE_BITWIDTH
). Lorsque cette taille est supérieure ou égale au nombre de murs, elle ne garantit aucune collision ni mise à jour O (1). Les petites tables de hachage auront plus de collisions et seront légèrement plus lentes.-DDEBUG
pour les impressionsAméliorations potentielles:
correction d'une petite fuite de mémoirecorrigée lors de la première modificationélagage alpha / bêtaajouté dans la 2e éditionélaguer les symétriesajoutées dans la troisième édition (notez que les symétries ne sont pas gérées par la mémorisation, ce qui reste une optimisation distincte.)mémorisationajoutée dans la 4e éditionCode
Faute d'organisation, le nombre de fichiers est devenu incontrôlable. Tout le code a été déplacé vers ce référentiel Github . Dans l'édition de mémorisation, j'ai ajouté un makefile et un script de test.
Résultats
Notes sur la complexité
Les approches par force brute des points et des boîtes explosent très rapidement en complexité .
Prenons un tableau avec des
R
lignes et desC
colonnes. Il y a desR*C
carrés,R*(C+1)
des murs verticaux et desC*(R+1)
murs horizontaux. C'est un total deW = 2*R*C + R + C
.Parce que Lembik nous a demandé de résoudre le jeu avec minimax, nous devons traverser jusqu'aux feuilles de l'arbre à gibier. Ignorons pour l'instant la taille, car ce qui compte, ce sont les ordres de grandeur.
Il existe des
W
options pour le premier coup. Pour chacun d'eux, le joueur suivant peut jouer n'importe lequel desW-1
murs restants, etc. Cela nous donne un espace de recherche deSS = W * (W-1) * (W-2) * ... * 1
, ouSS = W!
. Les factoriels sont énormes, mais ce n'est que le début.SS
est le nombre de nœuds feuilles dans l'espace de recherche. Plus pertinent pour notre analyse est le nombre total de décisions qui ont dû être prises (c'est-à-dire le nombre de branchesB
dans l'arbre). La première couche de branches a desW
options. Pour chacun d'eux, le niveau suivant aW-1
, etc.Regardons quelques petites tailles de table:
Ces chiffres deviennent ridicules. Au moins, ils expliquent pourquoi le code de force brute semble se bloquer indéfiniment sur une carte 2x3. L'espace de recherche d'une carte 2x3 est 742560 fois plus grand que 2x2 . Si 2x2 prend 20 secondes, une extrapolation prudente prédit plus de 100 jours de temps d'exécution pour 2x3. De toute évidence, nous devons tailler.
Analyse d'élagage
J'ai commencé par ajouter un élagage très simple en utilisant l'algorithme alpha-bêta. Fondamentalement, il arrête de chercher si un adversaire idéal ne lui donnerait jamais ses opportunités actuelles. "Hé, regarde - je gagne beaucoup si mon adversaire me laisse gagner chaque case!", Ne pensait jamais à l'IA.
edit J'ai également ajouté un élagage basé sur des planches symétriques.
Je n'utilise pas une approche de mémorisation, juste au cas où un jour j'ajouterais la mémorisation et que je voudrais garder cette analyse séparée. Au lieu de cela,cela fonctionne comme ceci: la plupart des lignes ont une "paire symétrique" ailleurs sur la grille. Il existe jusqu'à 7 symétries (horizontale, verticale, rotation de 180, rotation de 90, rotation de 270, diagonale et l'autre diagonale). Les 7 s'appliquent aux planches carrées, mais les 4 dernières ne s'appliquent pas aux planches non carrées. Chaque mur a un pointeur sur sa "paire" pour chacune de ces symétries. Si, dans un tour, le plateau est symétrique horizontalement, alors une seule de chaque paire horizontale doit être jouée.modifier modifier Mémorisation! Chaque mur reçoit un identifiant unique, que j'ai commodément défini comme un bit indicateur; le nième mur a l'identifiant
1 << n
. Le hachage d'une planche n'est alors que le OU de tous les murs joués. Ceci est mis à jour à chaque branche en temps O (1). La taille de la table de hachage est définie dans a#define
. Tous les tests ont été exécutés avec une taille 2 ^ 12, car pourquoi pas? Lorsqu'il y a plus de murs que de bits indexant la table de hachage (12 bits dans ce cas), les 12 moins significatifs sont masqués et utilisés comme index. Les collisions sont gérées avec une liste chaînée à chaque index de table de hachage. Le graphique suivant est mon analyse rapide et sale de la façon dont la taille de la table de hachage affecte les performances. Sur un ordinateur avec une RAM infinie, nous définirions toujours la taille de la table au nombre de murs. Une carte 3x4 aurait une table de hachage de 2 ^ 31 de long. Hélas, nous n'avons pas ce luxe.Ok, revenons à l'élagage. En arrêtant la recherche en haut de l'arbre, on peut gagner beaucoup de temps en ne descendant pas vers les feuilles. Le «facteur d'élagage» est la fraction de toutes les branches possibles que nous avons dû visiter. La force brute a un facteur d'élagage de 1. Plus elle est petite, mieux c'est.
la source
rows columns
spécifiant la taille de la cartePython - 2x2 en 29s
Cross-affichage de puzzles . Pas spécialement optimisé, mais peut constituer un point de départ utile pour d'autres participants.
la source
Javascript - carte 1x2 en 20ms
Démonstration en ligne disponible ici (avertissement - très lent si supérieur à 1x2 avec une profondeur de recherche complète ): https://dl.dropboxusercontent.com/u/141246873/minimax/index.html
A été développé pour les critères de victoire d'origine (code golf) et non pour la vitesse.
Testé dans google chrome v35 sur windows 7.
la source