Dans le jeu Flood Paint, le but du jeu est de faire en sorte que le tableau entier soit de la même couleur en aussi peu de tours que possible.
Le jeu commence avec un tableau ressemblant à ceci:
3 3 5 4 1 3 4 1 5
5 1 3 4 1 1 5 2 1
6 5 2 3 4 3 3 4 3
4 4 4 5 5 5 4 1 4
6 2 5 3[3]1 1 6 6
5 5 1 2 5 2 6 6 3
6 1 1 5 3 6 2 3 6
1 2 2 4 5 3 5 1 2
3 6 6 1 5 1 3 2 4
Actuellement, le nombre (représentant une couleur) au centre du tableau est 3. Chaque tour, le carré au centre change de couleur et tous les carrés de la même couleur accessibles depuis le centre en se déplaçant horizontalement ou verticalement ( c'est-à-dire dans la zone inondée du carré central) changera de couleur avec elle. Donc, si le carré du centre change de couleur en 5:
3 3 5 4 1 3 4 1 5
5 1 3 4 1 1 5 2 1
6 5 2 3 4 3 3 4 3
4 4 4 5 5 5 4 1 4
6 2 5 5[5]1 1 6 6
5 5 1 2 5 2 6 6 3
6 1 1 5 3 6 2 3 6
1 2 2 4 5 3 5 1 2
3 6 6 1 5 1 3 2 4
alors le 3 qui était à gauche du centre 3 changera également de couleur. Maintenant, il y a un total de sept 5 accessibles depuis le centre, et donc si nous changeons la couleur en 4:
3 3 5 4 1 3 4 1 5
5 1 3 4 1 1 5 2 1
6 5 2 3 4 3 3 4 3
4 4 4 4 4 4 4 1 4
6 2 4 4[4]1 1 6 6
5 5 1 2 4 2 6 6 3
6 1 1 5 3 6 2 3 6
1 2 2 4 5 3 5 1 2
3 6 6 1 5 1 3 2 4
la région peinte augmente de nouveau considérablement.
Votre tâche consiste à créer un programme prenant comme entrée une grille de couleurs 19-sur-19, sous la forme de votre choix:
4 5 1 1 2 2 1 6 2 6 3 4 2 3 2 3 1 6 3
4 2 6 3 4 4 5 6 4 4 5 3 3 3 3 5 4 3 4
2 3 5 2 2 5 5 1 2 6 2 6 6 2 1 6 6 1 2
4 6 5 5 5 5 4 1 6 6 3 2 6 4 2 6 3 6 6
1 6 4 4 4 4 6 4 2 5 5 3 2 2 4 1 5 2 5
1 6 2 1 5 1 6 4 4 1 5 1 3 4 5 2 3 4 1
3 3 5 3 2 2 2 4 2 1 6 6 6 6 1 4 5 2 5
1 6 1 3 2 4 1 3 3 4 6 5 1 5 5 3 4 3 3
4 4 1 5 5 1 4 6 3 3 4 5 5 6 1 6 2 6 4
1 4 2 5 6 5 5 3 2 5 5 5 3 6 1 4 4 6 6
4 6 6 2 6 6 2 4 2 6 1 5 6 2 3 3 4 3 6
6 1 3 6 3 5 5 3 6 1 3 4 4 5 1 2 6 4 3
2 6 1 3 2 4 2 6 1 1 5 2 6 6 6 6 3 3 3
3 4 5 4 6 6 3 3 4 1 1 6 4 5 1 3 4 1 2
4 2 6 4 1 5 3 6 4 3 4 5 4 2 1 1 4 1 1
4 2 4 1 5 2 2 3 6 6 6 5 2 5 4 5 4 5 1
5 6 2 3 4 6 5 4 1 3 2 3 2 1 3 6 2 2 4
6 5 4 1 3 2 2 1 1 1 6 1 2 6 2 5 6 4 5
5 1 1 4 2 6 2 5 6 1 3 3 4 1 6 1 2 1 2
et retourne une séquence de couleurs que le carré central va changer à chaque tour, toujours dans le format de votre choix:
263142421236425431645152623645465646213545631465
À la fin de chaque séquence de mouvements, les carrés de la grille 19 x 19 doivent tous être de la même couleur.
Votre programme doit être entièrement déterministe; Les solutions pseudo-aléatoires sont autorisées, mais le programme doit générer la même sortie pour le même scénario de test à chaque fois.
Le programme gagnant prendra le moins de mesures possible pour résoudre les 100 000 tests élémentaires contenus dans ce fichier (fichier texte compressé, 14,23 Mo). Si deux solutions prennent le même nombre d'étapes (par exemple, si les deux ont trouvé la stratégie optimale), le programme le plus court l'emportera.
BurntPizza a écrit un programme en Java pour vérifier les résultats du test. Pour utiliser ce programme, lancez votre soumission et dirigez la sortie vers un fichier appelé steps.txt
. Ensuite, exécutez ce programme avec steps.txt
et le floodtest
fichier dans le même répertoire. Si votre entrée est valide et produit des solutions correctes pour tous les fichiers, elle doit réussir tous les tests et renvoyerAll boards solved successfully.
import java.io.*;
import java.util.*;
public class PainterVerifier {
public static void main(String[] args) throws FileNotFoundException {
char[] board = new char[361];
Scanner s = new Scanner(new File("steps.txt"));
Scanner b = new Scanner(new File("floodtest"));
int lineNum = 0;
caseloop: while (b.hasNextLine()) {
for (int l = 0; l < 19; l++) {
String lineb = b.nextLine();
if (lineb.isEmpty())
continue caseloop;
System.arraycopy(lineb.toCharArray(), 0, board, l * 19, 19);
}
String line = s.nextLine();
if (line.isEmpty())
continue;
char[] steps = line.toCharArray();
Stack<Integer> nodes = new Stack<Integer>();
for (char c : steps) {
char targetColor = board[180];
char replacementColor = c;
nodes.push(180);
while (!nodes.empty()) {
int n = nodes.pop();
if (n < 0 || n > 360)
continue;
if (board[n] == targetColor) {
board[n] = replacementColor;
if (n % 19 > 0)
nodes.push(n - 1);
if (n % 19 < 18)
nodes.push(n + 1);
if (n / 19 > 0)
nodes.push(n - 19);
if (n / 19 < 18)
nodes.push(n + 19);
}
}
}
char center = board[180];
for (char c : board)
if (c != center) {
s.close();
b.close();
System.out.println("\nIncomplete board found!\n\tOn line " + lineNum + " of steps.txt");
System.exit(0);
}
if (lineNum % 5000 == 0)
System.out.printf("Verification %d%c complete...\n", lineNum * 100 / 100000, '%');
lineNum++;
}
s.close();
b.close();
System.out.println("All boards solved successfully.");
}
}
En outre, un tableau de bord, puisque les résultats ne sont pas réellement triés par score et ici, cela compte beaucoup:
- 1 985 078 - smack42, Java
- 2 075 452 - utilisateur1502040, C
- 2 098 382 - tigrou, C #
- 2 155 834 - CoderTao, C #
- 2 201 995 - MrBackend, Java
- 2 383 569 - CoderTao, C #
- 2 384 020 - Herjan, C
- 2 403 189 - Origineil, Java
- 2,445,761 - Herjan, C
- 2 475 056 - Jeremy List, Haskell
- 2 480 714 - SteelTermite, C (2 395 octets)
- 2 480 714 - Herjan, Java (4 702 octets)
- 2 588 847 - BurntPizza, Java (2 748 octets)
- 2 588 847 - Gero3, node.js (4 641 octets)
- 2 979 145 - Teun Pronk, Delphi XE3
- 4 780 841 - BurntPizza, Java
- 10 800 000 - Joe Z., Python
Réponses:
Java - 1 985 078 étapes
https://github.com/smack42/ColorFill
Une autre entrée tardive. Le fichier de résultat contenant les 1 985 078 étapes peut être trouvé ici .
Quelques informations de fond:
J'ai découvert ce défi il y a quelques années, lorsque j'ai commencé à programmer mon propre clone du jeu Flood-it.
Algorithmes DFS et A * "best-of incomplets"
Depuis le début, je voulais créer un bon algorithme de résolution pour ce jeu. Au fil du temps, je pouvais améliorer mon solveur en incluant plusieurs stratégies faisant différentes recherches incomplètes (similaires à celles utilisées dans les autres programmes ici) et en utilisant le meilleur résultat de ces stratégies pour chaque solution. J'ai même ré-implémenté l'algorithme A * de tigrou en Java et je l'ai ajouté à mon solveur pour obtenir des solutions globalement meilleures que le résultat de tigrou.
algorithme DFS exhaustif
Ensuite, je me suis concentré sur un algorithme qui trouve toujours les solutions optimales. J'ai déployé beaucoup d'efforts pour optimiser ma stratégie exhaustive de recherche en profondeur d'abord. Pour accélérer la recherche, j'ai inclus une table de hachage qui stocke tous les états explorés, afin que la recherche évite de les explorer à nouveau. Bien que cet algorithme fonctionne correctement et résolve tous les énigmes 14x14 assez rapidement, il utilise trop de mémoire et s'exécute très lentement avec les énigmes 19x19 de ce défi de code.
Algorithme Puchert A *
Il y a quelques mois, j'ai été contacté par Aaron et Simon Puchert pour examiner le solveur Flood-It . Ce programme utilise un algorithme de type A * avec une heuristique admissible (contrairement à celle de tigrou) et déplace l'élagage de la même manière que la recherche de points de saut. J'ai vite remarqué que ce programme est très rapide et trouve les solutions optimales !
Bien sûr, j'ai dû ré-implémenter cet excellent algorithme et l'a ajouté à mon programme. Je me suis efforcé d’optimiser mon programme Java pour qu’il fonctionne à peu près aussi vite que le programme C ++ original des frères Puchert. Ensuite, j'ai décidé d'essayer les 100 000 tests élémentaires de ce défi. Sur ma machine, le programme a fonctionné pendant plus de 120 heures pour trouver les 1 985 078 étapes, en utilisant l’implémentation de l’ algorithme Puchert A * .
C’est la meilleure solution possible à ce problème, à moins que le programme ne contienne que quelques bugs qui conduiraient à des solutions sous-optimales. Tous les commentaires sont les bienvenus!
edit: ajouté les parties pertinentes du code ici:
classe AStarPuchertStrategy
partie de la classe AStarSolver
partie de la classe AStarNode
la source
C # - 2 098 382 étapes
J'ai essayé beaucoup de choses, la plupart ont échoué et n'ont tout simplement pas fonctionné, jusqu'à récemment. J'ai quelque chose d'intéressant à poster une réponse.
Il existe certainement des moyens d'améliorer encore cette situation. Je pense que passer sous les étapes 2M pourrait être possible.
Il a fallu environ
7 hours
pour générer des résultats. Voici un fichier txt avec toutes les solutions, au cas où quelqu'un voudrait les vérifier: results.zipPlus de détails sur son fonctionnement:
Il utilise l' algorithme A * Pathfinding .
Ce qui est difficile, c'est de trouver un bien
heuristic
. Si leheuristic
coût est sous-estimé, il fonctionnera presque comme l' algorithme de Dijkstra et, en raison de la complexité d'une carte 19x19 à 6 couleurs, il fonctionnera à l'infini. Si elle surestime le coût, elle convergera rapidement vers une solution mais ne donnera pas de bonne solution (quelque chose comme 26 coups contre 19 était possible). Trouver ceheuristic
qu'il y a de mieux qui donne exactement le nombre restant d'étapes pour trouver la solution serait le meilleur (ce serait rapide et donnerait la meilleure solution possible). C'est (à moins que je me trompe) impossible. En fait, il faut d'abord résoudre le tableau lui-même, alors que c'est ce que vous essayez réellement de faire (problème de la poule et de l'œuf)J'ai essayé beaucoup de choses, voici ce qui a fonctionné pour le
heuristic
:node
représente un ensemble de carrés de même couleur contigus. Grâce à celagraph
, je peux facilement calculer la distance minimale exacte entre le centre et tout autre nœud. Par exemple, la distance entre le centre et le coin supérieur gauche serait de 10, car au moins 10 couleurs les séparent.heuristic
: je joue jusqu'au tableau actuel. Pour chaque étape, je choisis la couleur qui minimisera la somme des distances entre la racine et tous les autres nœuds.Le nombre de mouvements nécessaires pour atteindre ce but est le
heuristic
.Estimated cost
(utilisé par A *) =moves so far
+heuristic
Ce n’est pas parfait car il surestime légèrement le coût (on trouve donc une solution non optimale). Quoi qu'il en soit, il est rapide de calculer et de donner de bons résultats.
J'ai pu obtenir une amélioration considérable de la vitesse en utilisant un graphique pour effectuer toutes les opérations. Au début j'avais un
2-dimension
tableau. Je l'inonde et recalcule le graphe si nécessaire (par exemple: pour calculer l'heuristique). Maintenant, tout est fait en utilisant le graphique, qui est calculé uniquement au début. Pour simuler des étapes, l'inondation n'est plus nécessaire, je fusionne plutôt des nœuds. C'est beaucoup plus rapide.la source
code blocks
pour mettre en valeur le texte. Nous avons italique et gras pour cela.Python - 10 800 000 pas
En tant que solution de référence de dernière place, considérons cette séquence:
En parcourant tous les
n
temps de couleurs,n
vous garantissez que chaque pas carré sera de la même couleur que le carré central. Chaque carré est au plus à 18 pas du centre, ainsi 18 cycles garantissent que tous les carrés ont la même couleur. Très probablement, il se terminera dans moins que cela, mais le programme n'est pas obligé de s'arrêter dès que tous les carrés sont de la même couleur; c'est juste beaucoup plus bénéfique de le faire. Cette procédure constante compte 108 étapes par test élémentaire, pour un total de 10 800 000.la source
1 2 3 4 5 6 ...
au lieu de votre solution actuelle qui donne123456...
.Java - 2 480 714 étapes
J'ai fait une petite erreur avant (j'ai mis une phrase cruciale avant une boucle au lieu de la boucle:
la source
C - 2.075.452
Je sais que je suis extrêmement en retard pour la fête, mais j'ai vu ce défi et je voulais tenter ma chance.
L'algorithme est basé sur une recherche d'arborescence Monte-Carlo avec échantillonnage Thompson et sur un tableau de transposition pour réduire l'espace de recherche. Cela prend environ 12 heures sur ma machine. Si vous souhaitez vérifier les résultats, vous pouvez les trouver à l' adresse https://dropfile.to/pvjYDMV .
la source
hash ^= zobrist_table[i][(int)solution[i]];
pourhash ^= zobrist_table[i%len][(int)solution[i]];
de plantage du programme correctif.Java -
24341082588847 étapesEn train de gagner (~ 46K devant Herjan) à partir du 4/26Welp, non seulement MrBackend m'a battu, mais j'ai aussi trouvé un bug qui produisait un score trompeusement bon. C'est corrigé maintenant (c'était aussi dans le vérificateur! Ack), mais malheureusement, je n'ai pas le temps pour le moment d'essayer de récupérer la couronne. Je vais essayer plus tard.
Ceci est basé sur mon autre solution, mais au lieu de peindre avec la couleur la plus commune aux bords de remplissage, il peint avec la couleur qui va exposer les bords qui ont beaucoup de carrés adjacents de la même couleur. Appelez ça LookAheadPainter. Je vais jouer au golf plus tard si nécessaire.
EDIT: J'ai écrit un vérificateur, n'hésitez pas à l'utiliser, il attend un fichier steps.txt contenant les étapes que votre programme génère ainsi que le fichier floodtest: Edit-Edit: (voir OP)
Si quelqu'un trouve un problème, merci de me le signaler!
la source
C - 2 480 714 étapes
Pas encore optimal, mais il est maintenant plus rapide et obtient de meilleurs résultats.
la source
Java -
22455292201995 étapesRecherche parallèle et en cache dans la profondeur 5, minimisant le nombre d'îlots. Puisque l'amélioration de la profondeur 4 à la profondeur 5 était si faible, je ne pense pas qu'il soit vraiment utile de l'améliorer davantage. Mais si cela devait être amélioré, mon instinct me dit de travailler avec le calcul du nombre d’îles comme un différentiel entre deux États, au lieu de tout recalculer.
Présente actuellement les sorties sur stdout, jusqu'à ce que je connaisse le format d'entrée du vérificateur.
la source
24
résultant d'une exécution beaucoup plus efficace.Ma dernière entrée: C - 2 384 020 étapes
Cette fois, il s'agit d'un contrôle de toutes les possibilités ... Ce score est obtenu avec la profondeur définie sur 3. La profondeur à 5 devrait donner ~ 2,1 millions d'étapes ... TROP LENTE. Profondeur 20+ donne le moins de pas possible (il vérifie simplement tous les matches et les victoires les plus courtes du parcours) ... Il comporte le moins de pas possible, même si je le déteste, car il est un peu meilleur, mais les performances sont nulles. Je préfère mon autre entrée C, qui est également dans ce post.
Une autre IA améliorée écrite en C - 2 445 761 étapes
Basé sur SteelTermite:
la source
Java -
26107974780841 étapes(Fill-Bug corrigé, le score est maintenant bien pire -_-)
Ceci est ma soumission d'algorithme de référence de base, crée simplement un histogramme des carrés sur les bords de la zone peinte et peint avec la couleur la plus commune. Exécute le 100k en quelques minutes.
Évidemment, il ne gagnera pas, mais ce n’est certainement pas le dernier. Je ferai probablement une autre soumission pour des choses intelligentes. N'hésitez pas à utiliser cet algorithme comme point de départ.
Décommentez les lignes commentées pour la sortie complète. Par défaut, le nombre d'étapes prises est imprimé.
Golfs à 860 caractères (sans compter les nouvelles lignes pour le formatage), mais pourrait être réduit davantage si j'avais envie d'essayer:
la source
Haskell - 2 475 056 étapes
L'algorithme est similaire à celui suggéré par MrBackend dans les commentaires. La différence est la suivante: sa suggestion trouve le chemin le moins cher vers le carré de coût le plus élevé, le mien réduit avec avidité l’excentricité du graphique à chaque étape.
la source
C # - 2.383.569
C'est un parcours en profondeur de solutions possibles qui choisit grossièrement la voie de la meilleure amélioration (similaire / identique à l'entrée C de Herjan), sauf que j'ai astucieusement inversé l'ordre de génération de solutions candidates après avoir vu Herjan afficher les mêmes chiffres. Prend bien plus de 12 heures à courir si.
la source
Java - 2 403 189
C'était supposé être ma tentative de force brute. Mais! Ma première implémentation du "meilleur" choix de profondeur unique a donné:
Le code utilisé pour les deux est identique, la force brute stockant un "instantané" des autres mouvements possibles et exécutant l'algorithme sur chacun d'eux.
Si vous utilisez l'approche "multi" passes, des échecs aléatoires se produiront. J'ai configuré les 100 premières entrées de puzzle dans un test unitaire et je peux réussir à 100%, mais pas à 100% du temps. Pour compenser, j'ai juste suivi le numéro du casse-tête actuel au moment de l'échec et commencé un nouveau thread en ramassant où le dernier s'est arrêté. Chaque fil a écrit leurs résultats respectifs dans un fichier. Le pool de fichiers a ensuite été condensé en un seul fichier.
Node
représente une tuile / un carré du tableau et stocke une référence à tous ses voisins. Suivre troisSet<Node>
variablesRemaining
,Painted
,Targets
. Chaque itération cherche àTargets
regrouper tous lescandidate
nœuds par valeur, en sélectionnanttarget value
le nombre de nœuds "affectés". Ces nœuds affectés deviennent alors les cibles de la prochaine itération.La source est répartie dans de nombreuses classes et les extraits ne sont pas très significatifs en dehors du contexte global. Ma source peut être parcourue via GitHub . J'ai aussi joué avec une démonstration de JSFiddle pour la visualisation.
Néanmoins, ma méthode cheval de bataille de
Solver.java
:la source
C # -
21964622155834C’est effectivement la même approche de recherche du meilleur descendant que mon autre solutionneur, mais avec quelques optimisations qui permettent à peine, avec un parallélisme, d’aller à la profondeur 5 en un peu moins de 10 heures. En testant cela, j'ai aussi trouvé un bogue dans l'original, de telle sorte que l'algorithme prenait parfois des routes inefficaces vers l'état final (il ne tenait pas compte de la profondeur d'états avec score = 64; découvert en jouant avec des résultats de profondeur = 7).
La principale différence entre ce résolveur et le solveur précédent est qu’il garde les états du jeu Flood en mémoire afin qu’il n’ait pas à régénérer 6 ^ 5 états. En fonction de l’utilisation du processeur en cours d’exécution, je suis à peu près certain que cela est passé du processeur lié à la bande passante mémoire. Très amusant. Tant de péchés.
Edit: pour des raisons, l’algorithme de profondeur 5 ne produit pas toujours le meilleur résultat. Pour améliorer les performances, prenons la profondeur 5 ... et 4 ... et 3 et 2 et 1 et voyons quelle est la meilleure solution. Est-ce que nous avons encore rasé 40k mouvements? Étant donné que la profondeur 5 représente la majeure partie du temps, l’ajout de 4 à 1 ne fait qu'augmenter la durée d’exécution de ~ 10 heures à ~ 11 heures. Yay!
la source
Delphi XE3 2 979 145 étapes
Ok, c'est ma tentative. J'appelle la partie changeante une goutte, à chaque tour, il en fera une copie et testera toutes les couleurs possibles pour voir quelle couleur donnera la plus grosse goutte.
Exécute tous les puzzles en 3 heures et 6 minutes
Pensez aussi à une méthode de traçage bruteforce.
Peut-être amusant pour ce week-end ^^
la source
Javascript / node.js - 2 588 847
L'algorithme est un peu différent de la plupart des logiciels ici, car il utilise des régions précalculées et des états différents entre les calculs. Si vous êtes inquiet pour la vitesse à cause de javascript, il dure moins de 10 minutes.
la source
C code qui est garanti pour trouver une solution optimale par simple force brute. Fonctionne pour les grilles de taille arbitraire et toutes les entrées. Cela prend très très longtemps de fonctionner sur la plupart des réseaux.
Le remplissage d'inondation est extrêmement inefficace et repose sur la récursivité. Vous devrez peut-être agrandir votre pile si elle est très petite. Le système de force brute utilise une chaîne pour contenir les numéros et un simple ajout avec transfert pour parcourir toutes les options possibles. Ceci est également extrêmement inefficace car il répète la plupart des étapes quadrillions de fois.
Malheureusement, je n'ai pas pu tester tous les tests, car je vais mourir de vieillesse avant la fin.
Autant que je sache, c'est le gagnant actuel. Le concours exige que:
Vérifier
Comme cela trouve toujours le plus petit nombre d’étapes pour compléter chaque tableau et aucun des autres ne le fait, il est actuellement en avance. Si quelqu'un peut proposer un programme plus court, il pourrait gagner, je présente donc la version optimisée suivante. L'exécution est un peu plus lente, mais le temps d'exécution ne fait pas partie des conditions gagnantes:
la source