FÉLICITATIONS à @kuroineko. Gagne la prime pour son excellente vitesse (672 coups) sur la piste de Gauntlet.
LEADER: * Nimi a marqué un 2129 léger.
* Leader peut changer en raison d'entrées ultérieures.
Votre tâche consiste à écrire un petit programme capable de conduire rapidement une voiture de course.
Règles
Votre programme lira une image de la piste. Vous pouvez démarrer votre voiture sur n’importe quel pixel jaune et vous devez finir par croiser n’importe quel pixel noir. Le chemin de votre voiture doit être uniquement sur la piste grise ((c, c, c) où 30 <= c <= 220).
Votre voiture se déplacera en ligne droite à chaque tour avec une vitesse v composée d’entiers vx et vy (commençant par (0,0)). Au début de chaque tour, votre programme peut changer de vx et de vy de telle sorte que:
abs(vx2-vx1) + abs(vy2-vy1) <= 15
Mise à jour: accélération maximale augmentée à 15.
Votre programme trace ensuite une ligne droite allant de votre position actuelle à (position + v) en blanc avec un point bleu au début. Si un pixel sous cette ligne est noir, vous avez terminé la course. Sinon, si tous les pixels sous cette ligne sont gris ou jaunes, vous pouvez continuer jusqu'au prochain tour.
Votre programme doit sortir l'image de la piste avec votre chemin en blanc et bleu ajouté.
Sortie supplémentaire (ajoutée le 2015-01-15):
Si vous souhaitez concourir pour le gain ou le bonus , votre programme doit également générer votre liste de points (les points bleus) pour la ville ou le Gauntlet, respectivement. Inclure la liste des points avec votre réponse (pour vérification). Les points devraient ressembler à : (x0,y0), (x1,y1), ... (xn,yn)
. Vous pouvez utiliser librement des espaces, y compris des '\n'
caractères, pour adapter les données de la page.
Vous pouvez utiliser des bibliothèques tierces de lecture et d'écriture d'images, de dessin au trait et d'accès par pixel. Vous ne pouvez pas utiliser les bibliothèques de recherche de chemin. Si vous le souhaitez, vous pouvez convertir les images PNG en d'autres formats graphiques (tels que GIF, JPG, BMP) si vous en avez besoin.
Quelques pistes à parcourir
Une piste simple pour commencer:
Une piste de course:
Un parcours d'obstacle:
La ville:
Nightmare Track: The Gauntlet (si les autres sont trop faciles)
Notation
Votre score sera basé sur votre résultat sur la piste de la ville. Les points sont égaux à la longueur de votre programme en octets plus 10 points pour chaque virage que prend votre voiture de course pour finir. Le score le plus bas gagne. Veuillez inclure votre image de piste de ville avec votre réponse - nous aimerions connaître votre style de conduite.
Veuillez utiliser un titre pour votre réponse dans le format suivant:
<Racing Driver or Team Name> <Language> <Score>
par exemple: Slowpoke Perl 5329
Votre programme doit pouvoir conduire sur n’importe quelle piste en suivant les règles ci-dessus. Vous ne devez pas coder en dur le chemin optimal ni aucun paramètre des pistes de test. Les autres failles standard s'appliquent.
Défis similaires
C’est un casse-tête semblable à celui de Martin: To Vectory! - Le Grand Prix Vector Racing . Ce puzzle a un certain nombre de différences:
- Conduire à travers les murs n'est pas autorisé.
- Vous pouvez utiliser une mémoire et un temps illimités.
- Vous n'êtes pas obligé d'exécuter le code de quelqu'un d'autre sur votre ordinateur.
- Vous n'avez pas à télécharger quoi que ce soit sauf une image.
- La taille de votre code compte dans l'évaluation. Plus c'est petit, mieux c'est.
- Vous tracez votre solution sur l'image de la piste.
- Vous pouvez facilement créer vos propres pistes avec un package de peinture.
- La résolution plus élevée encourage un freinage et des virages plus réalistes.
- Une accélération de 15 crée environ 450 possibilités par tour. Cela rend BFS moins faisable et encourage de nouveaux algorithmes intéressants.
Ce casse-tête devrait inspirer une nouvelle série de programmeurs à essayer des solutions et permettre aux programmeurs disposant d'anciennes solutions de les repenser dans le nouvel environnement.
la source
Réponses:
TS n ° 1 - Haskell - 1699 + 430 = 2129
Tutu frère # 1
Identique à la version originale de Tutu, à la différence qu’elle utilise une épaisseur de 3 pour le chemin ballonné et que le second A * (espace vitesse-pos) est associé à une heuristique constante
1
. L'image d'entrée n'est plus transmise comme argument de ligne de commande, elle doit être nomméei
. Le nom de l'image de sortie esto
. Le programme imprime les points calculés sur le chemin sous la forme d'une liste de paires x, y (la sortie d'origine est une seule ligne):Je pouvais économiser beaucoup d'octets lorsque je commençais à supprimer toute la carte, à définir des structures de données et à les remplacer par de simples listes chaînées. Seules les deux instructions d'importation économiseraient 60 octets. Cependant, cela ralentirait le programme, de sorte que l'attente d'un résultat est une douleur pure. Cette version prend plus de 45 minutes pour The City, comparé aux 7 de l'original. Je vais m'arrêter ici pour échanger des octets pour une exécution rapide.
Le code nécessite l'indicateur -XNoMonomorphismRestriction à compiler.
The City - TS # 1 - 43 marches
la source
FirstRacer Java (5825 + 305 * 10 = 8875)
Juste pour avoir un début. Nécessite 305 segments sur la ville.
Ce programme Java le fait en pipeline:
Je pense que la piste de course vous donne une meilleure idée du fonctionnement de FirstRacer.
la source
tête de lit PHP (5950 + 470 = 6420)
Encore une autre variante (simplifiée) de BFS, avec certains prétraitements pour réduire l’espace de recherche.
Choix de la langue
PHP est assez bon pour gérer les images.
Il possède également une mémoire associative native, ce qui facilite grandement la programmation de la recherche de nœud BFS.
L'inconvénient est que le hachage des identificateurs de nœud n'est pas extrêmement rapide, de sorte que le résultat est tout sauf rapide.
D'après mes expériences avec le précédent défi de course, je ne doute pas que C ++ 11 et ses tables de hachage fonctionneraient beaucoup mieux, mais le code source serait au moins doublé, plus le besoin d'une bibliothèque externe png (même LodePNG) faire pour une construction en désordre.
Perl et ses progiciels plus avancés permettraient probablement un code plus compact et efficace (en raison de meilleures performances de hachage), mais je ne connais pas ces méthodes pour essayer un port.
Noyau BFS
La recherche fonctionne sur un espace position + vitesse, c’est-à-dire qu’un nœud représente un lieu visité visité avec une vitesse donnée.
Ceci constitue bien sûr un espace de recherche assez vaste, mais donne des résultats optimaux si tous les emplacements de piste possibles sont examinés.
De toute évidence, étant donné le nombre de pixels sur une petite image, une recherche exhaustive est hors de question.
Coupes
J'ai choisi de couper l'espace de la position en sélectionnant uniquement un sous-ensemble de pixels de piste.
Le solveur considérera toutes les positions à portée de main, limitées uniquement par une accélération.
La piste est pavée de carrés dont la taille maximale est calculée de sorte que deux carrés adjacents puissent être atteints avec l'accélération maximale autorisée (c'est-à-dire 14 x 14 pixels avec la limite de vitesse actuelle).
Après avoir emballé la piste avec de grands carrés, des carreaux de plus en plus petits sont utilisés pour remplir l'espace restant.
Seul le centre de chaque tuile est considéré comme une destination possible.
Voici un exemple:
Ce choix heuristique suffit à faire échouer le solutionneur sur la carte cauchemardesque. Je suppose que l’on pourrait essayer de réduire la taille maximale des mosaïques jusqu’à ce qu’une solution soit trouvée, mais avec les paramètres actuels, le solveur a une durée d’environ une heure et utilise 600 Mo. Des résultats plus précis nécessiteraient donc une quantité de temps et une mémoire déraisonnables.
Lors de la deuxième découpe, des carrés de 1 pixel seulement pourraient être omis.
Cela va bien sûr dégrader la solution ou même empêcher le solveur d'en trouver, mais cela améliore beaucoup le temps de calcul et donne généralement des résultats assez proches sur des cartes "simples".
Par exemple, en ville, laisser 1 x 1 pixel réduit les nœuds d'arbre BFS explorés de 660K à environ 90K pour des solutions à 47 vs 53 déplacements.
BFS vs A *
A * a besoin de plus de code et rien ne garantit même des résultats plus rapides dans l’espace position / vitesse, car évaluer le meilleur candidat suivant n’est pas aussi simple que dans l’espace classique en position (qui peut facilement être vaincu avec une les sacs de toute façon).
En outre, bien que PHP ait des files d’attente prioritaires qui, en passant, prennent en charge le réarrangement dynamique de leurs cousins C ++, je doute qu’elles suffiraient pour une implémentation A * efficace et la réécriture d’un tas binaire ou de toute structure de file d’attente A * dédiée nécessite beaucoup trop de lignes de code.
Contrôle mural
Je n'ai pas utilisé d'algorithme de Bresenham pour vérifier les collisions de mur, de sorte que la trajectoire peut couper le pixel de mur impair. Cela ne devrait cependant pas permettre de traverser un mur.
Les performances
Ce solveur est sûr qu'aucun jackrabbit à six pattes.
Sans la découpe supplémentaire, la résolution d'une carte peut prendre plus de 10 minutes sur un ordinateur de milieu de gamme.
Je suggère de définir la taille minimale de la mosaïque sur 2 ou 3 si vous souhaitez manipuler le code sans passer beaucoup de temps à attendre un résultat.
La consommation de mémoire est raisonnable pour ce type d'algorithme et de langage: environ 100 Mo environ ou moins, sauf pour les cauchemars dépassant les 600 Mo.
Résultats et notation
Les scores sont donnés sans la coupe "taille minimale du carreau".
Comme un lecteur attentif peut déduire de mes commentaires généraux, je me fiche de la partie golfique de ce défi. Je ne vois pas en quoi l'exécution de mon code via un obfuscateur ou la suppression de certaines optimisations et / ou de routines de débogage pour réduire la source rendrait cela plus amusant.
Soit simplement S la taille de l'octet source pour l'instant:
voie S + 1020
ville S + 470
obstacles S + 280
cauchemar -> échoue
la source
SecondRacer Java (1788 + 72 * 10 = 2508)
(2708) (2887) (3088) (3382) (4109 + 72 * 10 = 4839) (4290 + 86 * 10 = 5150)Similaire à FirstRacer. Mais différent dans les étapes 2.2 et 3. ce qui conduit à une conduite prévoyante et à l'utilisation du matériel.
Performance
Aucune de ces pistes n’est un problème pour A *. Il ne reste que quelques secondes (<10) à résoudre (même le "Nightmare Track: The Gauntlet") sur mon PC milieu de gamme.
Style de chemin
Je l'appelle poulpe. De loin pas aussi élégante que la solution pighead (de Kuroi Neko).
Style de code
Je suis entré dans un mode de combat modéré en gardant la lisibilité. Mais ajouté une version golfée.
golfed -> ATTENTION: il ne remplace pas le fichier d'origine!
Toutes les images sont affichées sur leur paysage dégradé A *. De jaune clair à brun (= jaune foncé). Alors que - selon A * - le chemin résultant est suivi en arrière (ici du brun au jaune clair).
Circuit de course S + 175
Course à obstacles S + 47
Ville S + 72
Gauntlet S + 1133
la source
Tutu - Haskell - (
3460265424762221206019921900 + 50x10 = 2400)Stratégie:
Le golf:
Je ne suis pas un golfeur Haskell, donc je ne sais pas ce qui peut être économisé davantage (Edit: s’est avéré être tout un tas).
Performance:
Le Gauntlet fonctionne en 9: 21 minutes sur mon Core i5 cadencé à 1,7 GHz à partir de 2011. La ville prend 7,2 s. (utilisé -O1 avec ghc, -O2 rend le programme terriblement lent)
Les options de réglage sont l’épaisseur du chemin gonflé. Pour les cartes plus petites, distance 3 enregistre un ou deux pas, mais le Gauntlet est trop long, je reste donc avec la distance 2. La course commence toujours au premier pixel jaune (en haut à gauche), l'optimisation manuelle épargnant un pas supplémentaire.
Conformité aux règles:
„Vous ne pouvez pas utiliser de bibliothèques de recherche de chemins.“ - Oui, mais le source est inclus. Les fonctions de recherche A * sont des versions
légèrementmodifiées de la bibliothèque Data.Graph.AStar de Cale Gibbard (voir http://hackage.haskell.org/package/astar ).La ville - 50 marches
Le Gauntlet - 722 étapes
Ungolfed:
Golfé:
Tutu frères et sœurs -TS # 1 - (1764 + 43 = 2194)Edit: TS # 1 maintenant réponse séparée.
Edit II: Le chemin pour la ville est
Dans The Gauntlet, Tutu se déplace comme suit
la source
-O2
ralentit le programme? bizarre. essayé-O3
?Maybe
beaucoup. Peut-être que vous pouvez remplacerMaybe
par des listes:Maybe a
est[a]
,Nothing
est[]
etJust x
est[x]
. leMaybe
monade devient équivalente à laList
monade. vous pouvez alors utiliser beaucoup de fonctions de liste pour eux:null
,head
,(:[])
,map
et ainsi de suite.Star Cross Racer - PHP - 4083 + 440 = beaucoup trop lourd
Ok, après 3 semaines sans connexion Internet (c'est ce qui se produit lorsqu'un des concurrents les plus féroces de votre fournisseur est en charge du patch bay du bâtiment, ou du moins à Paris, en France au moins), je peux enfin publier ma prochaine tentative.
Cette fois-ci, j'ai utilisé l'algorithme A * et une stratégie de distribution des points de cheminement plus efficace.
En prime, j'ai écrit une sorte de PHP Cruncher pour répondre à la partie golfique du défi.
Et maintenant que le solveur fonctionne sur toutes les cartes proposées, le bogue de traçage de ligne a été corrigé.
Plus de coupures de mur (bien que le pâturage soit toujours présent, comme il se doit :)).
exécuter le code
donnez au code le nom de votre choix (
runner.php
par exemple), puis appelez-le comme suit:Après être resté silencieux pendant un bon moment, il devrait produire une
_track.png
sortie montrant la solution.Comme vous pouvez le voir sur les images de sortie, le code est très lent. Tu as été prévenu.
Bien sûr, ma version privée est pleine de traces et produit de jolies représentations de diverses informations (y compris une image périodique montrant les progrès de A * pour aider à tuer le temps), mais il y a un prix à payer pour jouer au golf ...
code de golf
version non golfée
Résultats
Les images sont produites par une version plus riche qui affiche exactement la même solution avec quelques statistiques en bas (et trace le chemin avec l'antialiasing).
La carte de la ville est un très bon exemple de la raison pour laquelle les algorithmes basés sur la position doivent forcément trouver des résultats médiocres dans de nombreux cas: plus court n'est pas synonyme de rapidité.
(672 mouvements si vous ne voulez pas zoomer)
UNE*
À ma grande surprise, A * se comporte plutôt bien dans l'espace vitesse de positionnement. Mieux que BFS, en tout cas.
Cependant, j'ai dû transpirer un peu pour produire une estimation heuristique de la distance de travail.
Je devais aussi l'optimiser un peu, car le nombre d'états possibles est énorme (quelques millions) par rapport à une variante de position seulement, qui nécessite encore plus de code.
La limite inférieure choisie pour le nombre de coups nécessaires pour atteindre un but depuis une position donnée est simplement le temps nécessaire pour parcourir la distance jusqu'au but le plus proche en ligne droite avec une vitesse initiale nulle .
Bien sûr, le trajet en ligne droite mène généralement directement à un mur, mais il s’agit du même problème que l’utilisation de la distance droite euclidienne pour les recherches A * dans l’espace.
Tout comme la distance euclidienne pour les variantes d’espace seul, le principal avantage de cette métrique, en plus d’être acceptable pour utiliser la variante A * la plus efficace (avec des nœuds fermés), est de ne nécessiter que très peu d’analyse topologique de la piste.
Étant donné une accélération maximale A , le nombre n de déplacements nécessaires pour couvrir une distance d est le plus petit nombre entier qui satisfait la relation:
d <= An (n + 1) / 2
En résolvant cela pour n, on obtient une estimation de la distance restante.
Pour calculer cela, une carte de distance par rapport à l'objectif le plus proche est construite à l'aide d'un algorithme de remplissage inondé contenant les positions des objectifs.
Cela a pour effet agréable d’éliminer les points de trace où aucun objectif ne peut être atteint (comme cela se produit dans certaines zones de la piste cauchemardesque).
Le nombre de déplacements est calculé en tant que valeur à virgule flottante, de sorte que les nœuds plus proches de l'objectif puissent être davantage discriminés.
Waypoints
Comme lors de ma précédente tentative, l’idée est de réduire le nombre de points de trace à un sous-échantillon le plus petit possible de points de cheminement. L'astuce consiste à essayer de choisir les positions les plus "utiles" sur la piste.
L'heuristique commence par une répartition régulière sur toute la piste, mais augmente la densité dans deux types de zones:
Voici un exemple.
Les zones à forte densité sont en rouge, les zones à faible densité en vert. Les pixels bleus sont les points de passage sélectionnés.
Remarquez les groupes de points de cheminement dans les virages serrés (parmi beaucoup de taches inutiles sur les courbes inclinées, en raison d'un filtrage insuffisant).
Pour calculer la position des voies, la totalité de la piste est balayée pour déterminer la distance au mur le plus proche. Le résultat est un champ de vecteurs pointant vers la bordure de piste la plus proche.
Ce champ de vecteurs est ensuite traité pour produire une estimation approximative de la courbure locale.
Enfin, la courbure et la distance au mur sont combinées pour produire une densité locale souhaitée, et un algorithme plutôt maladroit tente de disperser en conséquence les points de cheminement sur la trace.
Une amélioration notable par rapport à la stratégie précédente est que les voies étroites auront (apparemment) toujours suffisamment de points de cheminement à traverser, ce qui permet de naviguer sur la carte cauchemardesque.
Comme toujours, il s’agit de trouver un juste équilibre entre temps de calcul et efficacité.
Une diminution de 50% de la densité divisera le temps de calcul par plus de 4, mais avec des résultats plus approximatifs (48 coups au lieu de 44 en ville, 720 au lieu de 670 en cauchemar).
Le golf
Je pense toujours que le golf nuit à la créativité dans ce cas précis: supprimer l'antialiasing de la sortie est suffisant pour gagner 30 points et nécessite beaucoup moins d'effort que de passer de 47 à 44 coups sur la carte de la ville.
Même passer de 720 à 670 coups par cauchemar ne rapporterait que 500 points, bien que je doute fort qu’une seule position, A *, puisse se rapprocher de cela.
Juste pour le plaisir, j'ai quand même décidé d'écrire mon propre compresseur PHP.
Comme il apparaît, renommer les identifiants efficacement en PHP n’est pas une tâche facile. En fait, je ne pense même pas qu'il soit possible de le faire dans le cas général. Même avec une analyse sémantique complète, la possibilité d'utiliser des chaînes ou des variables indirectes pour désigner des objets nécessiterait la connaissance de la sémantique de chaque fonction.
Cependant, comme l'analyseur intégré très pratique permet de passer immédiatement à l'analyse sémantique, j'ai réussi à produire quelque chose qui semble fonctionner sur un sous-ensemble de PHP suffisant pour écrire du code "golfable" (éloignez-vous de $$ et utiliser des appels de fonction indirects ou une autre chaîne d'accès aux objets).
Aucune utilisation pratique à proprement parler et rien à voir avec le problème initial, mais beaucoup de plaisir à coder quand même.
J'aurais pu couper le code plus loin pour gagner environ 500 caractères supplémentaires, mais comme les noms de la bibliothèque graphique PHP sont malheureusement assez longs, c'est une sorte de lutte acharnée.
Développements ultérieurs
Le code de sélection des points de cheminement est un gâchis horrible, réglé par essais et erreurs. Je pense que faire plus de maths (en utilisant des opérateurs de dégradé et de curl appropriés) améliorerait grandement le processus.
Je suis curieux de voir si une meilleure heuristique de distance peut être trouvée. J'ai essayé de prendre en compte la vitesse de plusieurs manières, mais cela a cassé l'A * ou produit des résultats plus lents.
Il serait peut-être possible de recoder tout cela dans un langage plus rapide comme le C ++, mais la version de PHP repose énormément sur le garbage collection pour maintenir la consommation de mémoire à un niveau raisonnable. Nettoyer des nœuds fermés en C ++ nécessiterait un peu de travail et une quantité considérable de code supplémentaire.
Si les scores avaient été basés sur les performances, j'aurais vivement essayé d'améliorer les algorithmes. Mais puisque le critère du golf est tellement écrasant, il n’ya pas de réel intérêt, ou est-ce vrai?
la source
ThirdRacer Java (1224 + 93 * 10 = 2154)
Similaire à SecondRacer. Mais passer de la vitesse à la taille du code (tout en utilisant Java). L’optimisation de l’accélération est maintenant beaucoup simplifiée, entraînant malheureusement une voiture plus lente.
Performance
Mieux que SecondRacer.
Style de chemin
Comme SecondRacer.
Style de code
Je suis entré dans un mode de combat lourd.
golfed -> AVERTISSEMENT: le remplacement du fichier d'origine est effectué sur place!
Ville S + 93
la source
Chemin traversé par les étoiles sur la carte du cauchemar
(selon la demande populaire)
(code non mis à jour car les modifications sont triviales et le défi de performance uniquement n'est pas joué)
Désolé de poster une autre entrée, mais j'atteins la limite de 30 000 caractères par rapport à la précédente.
Dites juste le mot et je supprimerai celui-ci.
la source
Sunday Driver, Python 2, 3242
Code minifié = 2382 octets
Performance: ville = 86 obstacles = 46 hippodromes = 188 défis à relever = 1092
Voici mon programme de validation de principe qui devait démontrer qu'une solution était possible. Cela nécessite une optimisation et un meilleur golf.
Opération
Créer une structure de données de distance de la destination (simple dérivé A *, comme un remplissage)
Recherchez les séries de lignes droites raccourcies vers la destination qui ne croisent pas les pixels non suivis.
Pour chaque ligne droite, accélérez et freinez pour minimiser les virages.
Code Golfé (minifié)
Exemples
la source