Zum Sieg! - Der Vector Racing Grand Prix

39

User CarpetPython hat eine neue Version dieses Problems veröffentlicht, bei der heuristische Lösungen aufgrund des größeren Suchraums einen viel größeren Stellenwert haben . Ich persönlich denke, dass diese Herausforderung viel schöner ist als meine. Versuchen Sie es also einmal!

Vektorrennen ist ein süchtig machendes Spiel, das mit einem Stift und einem Blatt Papier mit quadratischen Linien gespielt werden kann. Sie zeichnen eine beliebige Rennstrecke auf das Papier, definieren Start und Ende und lenken dann Ihr punktgroßes Auto rundenbasiert. Gehen Sie so schnell wie möglich ans Ende, aber achten Sie darauf, dass Sie nicht in eine Wand geraten!

Die Strecke

  • Die Karte ist ein zweidimensionales Gitter, in dem jede Zelle ganzzahlige Koordinaten hat.
  • Sie bewegen sich auf den Gitterzellen.
  • Jede Gitterzelle ist entweder Teil der Spur oder eine Wand.
  • Genau eine Spurzelle ist die Startkoordinate.
  • Mindestens eine Streckenzelle wird als Ziel festgelegt. Die Landung auf einem dieser Felder beendet das Rennen. Mehrere Zielzellen sind nicht unbedingt verbunden.

Das Auto lenken

Ihr Auto startet an einer bestimmten Koordinate und mit einem Geschwindigkeitsvektor (0, 0). In jeder Runde können Sie jede Komponente der Geschwindigkeit anpassen ±1oder so lassen, wie sie ist. Anschließend wird der resultierende Geschwindigkeitsvektor zur Position Ihres Fahrzeugs hinzugefügt.

Ein Bild kann helfen! Der rote Kreis war Ihre Position in der letzten Runde. Der blaue Kreis ist Ihre aktuelle Position. Ihre Geschwindigkeit ist der Vektor vom roten zum blauen Kreis. Abhängig davon, wie Sie Ihre Geschwindigkeit einstellen, können Sie sich in diesem Zug zu einem der grünen Kreise bewegen.

                                    Bildbeschreibung hier eingeben

Wenn Sie in einer Mauer landen , verlieren Sie sofort.

Deine Aufgabe

Sie haben es erraten: Schreiben Sie ein Programm, das mit einer Rennstrecke als Eingabe das Auto in möglichst wenigen Runden zu einer der Zielzellen steuert. Ihre Lösung sollte in der Lage sein, mit beliebigen Spuren einigermaßen gut umzugehen, und nicht speziell für die bereitgestellten Testfälle optimiert sein.

Eingang

Wenn Ihr Programm aufgerufen wird, lesen Sie von stdin :

target
n m
[ASCII representation of an n x m racetrack]
time

targetDies ist die maximale Anzahl von Umdrehungen, die Sie zur Fertigstellung des Tracks ausführen dürfen, und timedas Gesamtzeitbudget für den Track in Sekunden (nicht unbedingt eine Ganzzahl). Einzelheiten zum Timing finden Sie weiter unten.

Für die durch Zeilenumbrüche getrennte Spur werden die folgenden Zeichen verwendet:

  • # - Wand
  • S- der Anfang
  • *- ein Ziel
  • . - alle anderen Gleisfelder (zB Straße)

Alle Zellen außerhalb des n x mbereitgestellten Rasters sind implizit Wände.

Der Koordinatenursprung befindet sich in der oberen linken Ecke.

Hier ist ein einfaches Beispiel:

8
4.0
9 6
###...***
###...***
###...***
......###
S.....###
......###

Bei Verwendung der 0-basierten Indexierung wäre die Startkoordinate (0,4).

Nach jedem Zug erhalten Sie weitere Eingaben:

x y
u v
time

Wo x, y, u, vsind alle 0-basierten Zahlen. (x,y)ist Ihre aktuelle Position und (u,v)ist Ihre aktuelle Geschwindigkeit. Beachten Sie, dass x+uund / oder y+vaußerhalb der Grenzen liegen könnte.

timeist, was von Ihrem Zeitbudget übrig ist, in Sekunden. Fühlen Sie sich frei, dies zu ignorieren. Dies gilt nur für Teilnehmer, die ihre Implementierung wirklich auf das Zeitlimit beschränken möchten.

Wenn das Spiel vorbei ist (weil du in einer Mauer gelandet bist, außerhalb der Grenzen gelandet bist, targetKurven überschritten hast, die Zeit abgelaufen ist oder das Ziel erreicht hast), erhältst du eine leere Linie.

Ausgabe

Schreibe für jede Runde an stdout :

Δu Δv

wobei Δuund Δvjeweils ein von -1, 0, 1. Dies wird hinzugefügt (u,v), um Ihre neue Position zu bestimmen. Zur Verdeutlichung lauten die Anweisungen wie folgt

(-1,-1) ( 0,-1) ( 1,-1)
(-1, 0) ( 0, 0) ( 1, 0)
(-1, 1) ( 0, 1) ( 1, 1)

Eine optimale Lösung für das obige Beispiel wäre

1 0
1 -1
1 0

Beachten Sie, dass sich der Controller nicht an stderr anschließt , sodass Sie ihn für jede Art von Debug-Ausgabe verwenden können, die Sie möglicherweise während der Entwicklung Ihres Bots benötigen. Bitte entfernen / kommentieren Sie solche Ausgaben in Ihrem eingereichten Code.

Es kann eine halbe Sekunde dauern, bis Ihr Bot in jeder einzelnen Runde reagiert. Für Runden, die länger dauern, haben Sie ein Zeitbudget (pro Spur) von target/2Sekunden. Jedes Mal, wenn eine Runde länger als eine halbe Sekunde dauert, wird die zusätzliche Zeit von Ihrem Zeitbudget abgezogen. Wenn Ihr Zeitbudget Null erreicht, wird das aktuelle Rennen abgebrochen.

Neu: Aus praktischen Gründen muss ich ein Speicherlimit festlegen (da der Speicher für vernünftige Spurgrößen begrenzter zu sein scheint als die Zeit). Daher muss ich jeden Testlauf abbrechen, bei dem der Rennfahrer mehr als 1 GB Arbeitsspeicher verwendet, gemessen mit Process Explorer als Private Bytes .

Wertung

Es gibt einen Benchmark von 20 Tracks. Für jeden Track:

  • Wenn Sie den Track abgeschlossen haben, entspricht Ihre Punktzahl der Anzahl der Züge, die Sie benötigen, um eine Zielzelle geteilt durchtarget zu erreichen .
  • Wenn Ihnen die Zeit / das Gedächtnis ausgeht oder Sie das Ziel nicht erreichen, bevor targetdie Runden abgelaufen sind, oder Sie zu einem beliebigen Zeitpunkt in einer Mauer / außerhalb der Grenzen landen, lautet Ihre Punktzahl 2.
  • Wenn Ihr Programm nicht deterministisch ist, ist Ihre Punktzahl der Durchschnitt über 10 Läufe auf dieser Strecke (bitte geben Sie dies in Ihrer Antwort an).

Ihre Gesamtpunktzahl ist die Summe der Einzelpunktzahlen. Die niedrigste Punktzahl gewinnt!

Darüber hinaus kann (und wird nachdrücklich empfohlen) jeder Teilnehmer einen zusätzlichen Benchmark-Track bereitstellen , der in die offizielle Liste aufgenommen wird. Frühere Antworten werden einschließlich dieses neuen Titels neu bewertet. Dies dient dazu, sicherzustellen, dass keine Lösung zu genau auf die vorhandenen Testfälle zugeschnitten ist, und um interessante Randfälle zu berücksichtigen, die ich möglicherweise übersehen habe (die Sie jedoch möglicherweise bemerken).

Binden brechen

Da es bereits eine optimale Lösung gibt, wird dies wahrscheinlich der Hauptfaktor für die Punktzahl der Teilnehmer sein.

Wenn es ein Unentschieden gibt (aufgrund mehrerer Antworten, die alle Spuren optimal oder auf andere Weise lösen), werde ich zusätzliche (größere) Testfälle hinzufügen, um das Unentschieden zu lösen. Diese werden auf eine feste Art und Weise generiert, um menschliche Vorurteile bei der Erstellung dieser Binder zu vermeiden :

  • Ich werde die Seitenlänge nim 10Vergleich zum zuletzt auf diese Weise erzeugten Track erhöhen . (Ich kann Größen überspringen, wenn sie die Krawatte nicht brechen.)
  • Grundlage ist diese Vektorgrafik
  • Dies wird mit diesem Mathematica-Snippet in der gewünschten Auflösung gerastert .
  • Der Start ist in der oberen linken Ecke. Insbesondere ist dies die Zelle ganz links in der obersten Zeile dieses Spurendes.
  • Das Ziel ist in der unteren rechten Ecke. Insbesondere ist dies die Zelle ganz rechts in der untersten Zeile dieses Spurendes.
  • Der targetWille 4*n.

Der letzte Track des ersten Benchmarks wurde bereits so generiert, mit n = 50.

Der Controller

Das Programm, mit dem die Einsendungen getestet werden, ist in Ruby geschrieben und befindet sich zusammen mit der von mir verwendeten Benchmark-Datei auf GitHub . Es gibt dort auch einen Beispiel-Bot randomracer.rb, der einfach zufällige Züge auswählt. Sie können die Grundstruktur als Ausgangspunkt für Ihren Bot verwenden, um zu sehen, wie die Kommunikation funktioniert.

Sie können Ihren eigenen Bot gegen eine Trackdatei Ihrer Wahl wie folgt ausführen:

ruby controller.rb track_file_name command to run your racer

z.B

ruby controller.rb benchmark.txt ruby randomracer.rb

Das Repository enthält außerdem zwei Klassen Point2Dund Track. Wenn Ihr Beitrag in Ruby verfasst ist, können Sie ihn jederzeit wiederverwenden.

Befehlszeilenoptionen

Sie können Befehlszeilenoptionen hinzufügen -v, -s, -tbevor der Benchmark des Dateinamens. Wenn Sie mehrere Switches verwenden möchten, können Sie dies beispielsweise auch tun -vs. Das machen sie:

-v (ausführlich): Verwenden Sie diese Option, um die Debug-Ausgabe des Controllers zu erhöhen.

-s (lautlos): Wenn Sie Ihre Position und Geschwindigkeit lieber selbst verfolgen möchten und sich nicht um das Zeitbudget kümmern, können Sie diese drei Ausgabezeilen in jeder Runde (an Ihre Übermittlung gesendet) mit dieser Flagge ausschalten.

-t(Tracks): Ermöglicht die Auswahl einzelner zu testender Tracks. Beispielsweise -t "1,2,5..8,15"würden nur die Spuren 1, 2, 5, 6, 7, 8 und 15 getestet. Vielen Dank an Ventero für diese Funktion und den Optionsparser.

Ihre Einreichung

Bitte geben Sie in Ihrer Antwort zusammenfassend Folgendes an:

  • Ihr Ergebnis.
  • Wenn Sie Zufälligkeit verwenden, geben Sie dies bitte an, damit ich Ihre Punktzahl über mehrere Läufe mitteln kann.
  • Der Code für Ihre Einreichung.
  • Der Speicherort eines kostenlosen Compilers oder Interpreters für die Sprache Ihrer Wahl, der auf einem Windows 8-Computer ausgeführt wird.
  • Kompilierungsanweisungen, falls erforderlich.
  • Eine Windows-Befehlszeilenzeichenfolge zum Ausführen Ihrer Übermittlung.
  • Ob für Ihre Einreichung die -sFlagge erforderlich ist oder nicht.
  • (optional) Ein neuer, lösbarer Track, der dem Benchmark hinzugefügt wird. Ich werde einen vernünftigen targetfür deine Strecke manuell ermitteln. Wenn der Track zum Benchmark hinzugefügt wird, bearbeite ich ihn aus Ihrer Antwort heraus. Ich behalte mir das Recht vor, Sie nach einem anderen Titel zu fragen (nur für den Fall, dass Sie einen unverhältnismäßig großen Titel hinzufügen, obszöne ASCII-Grafiken in den Titel einfügen usw.). Wenn ich den Testfall zum Benchmark-Set hinzufüge, ersetze ich den Track in Ihrer Antwort durch einen Link zum Track in der Benchmark-Datei, um die Unordnung in diesem Beitrag zu verringern.

Wie Sie sehen, werde ich alle Einsendungen auf einem Windows 8-Computer testen. Wenn es absolut keine Möglichkeit gibt, Ihren Beitrag unter Windows zum Laufen zu bringen, kann ich auch eine Ubuntu-VM ausprobieren. Dies ist jedoch erheblich langsamer. Wenn Sie also das Zeitlimit maximieren möchten, stellen Sie sicher, dass Ihr Programm unter Windows ausgeführt wird.

Möge der beste Fahrer vektoriell hervorgehen!

Aber ich will spielen!

Wenn Sie das Spiel selbst ausprobieren möchten, um ein besseres Gefühl dafür zu bekommen, gibt es diese Implementierung . Die Regeln, die dort verwendet werden, sind etwas ausgefeilter, aber sie sind ähnlich genug, um nützlich zu sein, denke ich.

Bestenliste

Letzte Aktualisierung: 01.09.2014, 21:29 UTC
Strecken im Benchmark: 25
Tie-Breaker-Größen: 290, 440

  1. 6.86688 - Kuroi Neko
  2. 8.73108 - user2357112 - 2. Einreichung
  3. 9.86627 - nneonneo
  4. 10.66109 - user2357112 - 1. Einreichung
  5. 12.49643 - Ray
  6. 40.0759 - Pseudonym117 (probabilistisch)

Detaillierte Testergebnisse . (Die Scores für probabilistische Einreichungen wurden separat ermittelt.)

Martin Ender
quelle

Antworten:

5

C ++ 11 - 6.66109

Noch eine weitere erste Suchimplementierung, die nur optimiert wurde.

Es muss mit der Option -s ausgeführt werden .
Sein Eingang ist überhaupt nicht bereinigt, daher können falsche Spuren ihn in einen Kürbis verwandeln.

Ich habe es mit Microsoft Visual C ++ 2013 getestet und den Build mit dem Standard- / O2-Flag freigegeben (für Geschwindigkeit optimieren).
Erstellt mit g ++ und der Microsoft IDE OK.
Mein Barebone-Speicherzuweiser ist ein Kinderspiel. Erwarten Sie also nicht, dass er mit anderen STL-Implementierungen von unordered_set!

#include <cstdint>
#include <iostream>
#include <fstream>
#include <sstream>
#include <queue>
#include <unordered_set>

#define MAP_START 'S'
#define MAP_WALL  '#'
#define MAP_GOAL  '*'

#define NODE_CHUNK_SIZE   100 // increasing this will not improve performances
#define VISIT_CHUNK_SIZE 1024 // increasing this will slightly reduce mem consumption at the (slight) cost of speed

#define HASH_POS_BITS 8 // number of bits for one coordinate
#define HASH_SPD_BITS (sizeof(size_t)*8/2-HASH_POS_BITS)

typedef int32_t tCoord; // 32 bits required to overcome the 100.000 cells (insanely) long challenge

// basic vector arithmetics
struct tPoint {
    tCoord x, y;
    tPoint(tCoord x = 0, tCoord y = 0) : x(x), y(y) {}
    tPoint operator+ (const tPoint & p) { return tPoint(x + p.x, y + p.y); }
    tPoint operator- (const tPoint & p) { return tPoint(x - p.x, y - p.y); }
    bool operator== (const tPoint & p) const { return p.x == x && p.y == y;  }
};

// a barebone block allocator. Improves speed by about 30%
template <class T, size_t SIZE> class tAllocator
{
    T * chunk;
    size_t i_alloc;
    size_t m_alloc;
public:
    typedef T                 value_type;
    typedef value_type*       pointer;
    typedef const value_type* const_pointer;
    typedef std::size_t       size_type;
    typedef value_type&       reference;
    typedef const value_type& const_reference;
    tAllocator()                                              { m_alloc = i_alloc = SIZE; }
    template <class U> tAllocator(const tAllocator<U, SIZE>&) { m_alloc = i_alloc = SIZE; }
    template <class U> struct rebind { typedef tAllocator<U, SIZE> other; };
    pointer allocate(size_type n, const_pointer = 0)
    {
        if (n > m_alloc) { i_alloc = m_alloc = n; }      // grow max size if request exceeds capacity
        if ((i_alloc + n) > m_alloc) i_alloc = m_alloc;  // dump current chunk if not enough room available
        if (i_alloc == m_alloc) { chunk = new T[m_alloc]; i_alloc = 0; } // allocate new chunk when needed
        T * mem = &chunk[i_alloc];
        i_alloc += n;
        return mem;
    }
    void deallocate(pointer, size_type) { /* memory is NOT released until process exits */ }
    void construct(pointer p, const value_type& x) { new(p)value_type(x); }
    void destroy(pointer p) { p->~value_type(); }
};

// a node in our search graph
class tNode {
    static tAllocator<tNode, NODE_CHUNK_SIZE> mem; // about 10% speed gain over a basic allocation
    tNode * parent;
public:
    tPoint pos;
    tPoint speed;
    static tNode * alloc (tPoint pos, tPoint speed, tNode * parent) { return new (mem.allocate(1)) tNode(pos, speed, parent); }
    tNode (tPoint pos = tPoint(), tPoint speed = tPoint(), tNode * parent = nullptr) : parent(parent), pos(pos), speed(speed) {}
    bool operator== (const tNode& n) const { return n.pos == pos && n.speed == speed; }
    void output(void)
    {
        std::string output;
        tPoint v = this->speed;
        for (tNode * n = this->parent ; n != nullptr ; n = n->parent)
        {
            tPoint a = v - n->speed;
            v = n->speed;
            std::ostringstream ss;  // a bit of shitty c++ text I/O to print elements in reverse order
            ss << a.x << ' ' << a.y << '\n';
            output = ss.str() + output;
        }
        std::cout << output;
    }
};
tAllocator<tNode, NODE_CHUNK_SIZE> tNode::mem;

// node queueing and storing
static int num_nodes = 0;
class tNodeJanitor {
    // set of already visited nodes. Block allocator improves speed by about 20%
    struct Hasher { size_t operator() (tNode * const n) const 
    {
        int64_t hash = // efficient hashing is the key of performances
            ((int64_t)n->pos.x   << (0 * HASH_POS_BITS))
          ^ ((int64_t)n->pos.y   << (1 * HASH_POS_BITS))
          ^ ((int64_t)n->speed.x << (2 * HASH_POS_BITS + 0 * HASH_SPD_BITS))
          ^ ((int64_t)n->speed.y << (2 * HASH_POS_BITS + 1 * HASH_SPD_BITS));
        return (size_t)((hash >> 32) ^ hash);
        //return (size_t)(hash);
    }
    };
    struct Equalizer { bool operator() (tNode * const n1, tNode * const n2) const
        { return *n1 == *n2; }};
    std::unordered_set<tNode *, Hasher, Equalizer, tAllocator<tNode *, VISIT_CHUNK_SIZE>> visited;
    std::queue<tNode *> queue; // currently explored nodes queue
public:
    bool empty(void) { return queue.empty();  }
    tNode * dequeue() { tNode * n = queue.front(); queue.pop(); return n; }
    tNode * enqueue_if_new (tPoint pos, tPoint speed = tPoint(0,0), tNode * parent = nullptr)
    {
        tNode signature (pos, speed);
        tNode * n = nullptr;
        if (visited.find (&signature) == visited.end()) // the classy way to check if an element is in a set
        {
            n = tNode::alloc(pos, speed, parent);
            queue.push(n);
            visited.insert (n);
num_nodes++;
        }
        return n;
    }
};

// map representation
class tMap {
    std::vector<char> cell;
    tPoint dim; // dimensions
public:
    void set_size(tCoord x, tCoord y) { dim = tPoint(x, y); cell.resize(x*y); }
    void set(tCoord x, tCoord y, char c) { cell[y*dim.x + x] = c; }
    char get(tPoint pos)
    {
        if (pos.x < 0 || pos.x >= dim.x || pos.y < 0 || pos.y >= dim.y) return MAP_WALL;
        return cell[pos.y*dim.x + pos.x];
    }
    void dump(void)
    {
        for (int y = 0; y != dim.y; y++)
        {
            for (int x = 0; x != dim.x; x++) fprintf(stderr, "%c", cell[y*dim.x + x]);
            fprintf(stderr, "\n");
        }
    }
};

// race manager
class tRace {
    tPoint start;
    tNodeJanitor border;
    static tPoint acceleration[9];
public:
    tMap map;
    tRace ()
    {
        int target;
        tCoord sx, sy;
        std::cin >> target >> sx >> sy;
        std::cin.ignore();
        map.set_size (sx, sy);
        std::string row;
        for (int y = 0; y != sy; y++)
        {
            std::getline(std::cin, row);
            for (int x = 0; x != sx; x++)
            {
                char c = row[x];
                if (c == MAP_START) start = tPoint(x, y);
                map.set(x, y, c);
            }
        }
    }

    // all the C++ crap above makes for a nice and compact solver
    tNode * solve(void)
    {
        tNode * initial = border.enqueue_if_new (start);
        while (!border.empty())
        {
            tNode * node = border.dequeue();
            tPoint p = node->pos;
            tPoint v = node->speed;
            for (tPoint a : acceleration)
            {
                tPoint nv = v + a;
                tPoint np = p + nv;
                char c = map.get(np);
                if (c == MAP_WALL) continue;
                if (c == MAP_GOAL) return new tNode (np, nv, node);
                border.enqueue_if_new (np, nv, node);
            }
        }
        return initial; // no solution found, will output nothing
    }
};
tPoint tRace::acceleration[] = {
    tPoint(-1,-1), tPoint(-1, 0), tPoint(-1, 1),
    tPoint( 0,-1), tPoint( 0, 0), tPoint( 0, 1),
    tPoint( 1,-1), tPoint( 1, 0), tPoint( 1, 1)};

#include <ctime>
int main(void)
{
    tRace race;
    clock_t start = clock();
    tNode * solution = race.solve();
    std::cerr << "time: " << (clock()-start)/(CLOCKS_PER_SEC/1000) << "ms nodes: " << num_nodes << std::endl;
    solution->output();
    return 0;
}

Ergebnisse

 No.       Size     Target   Score     Details
-------------------------------------------------------------------------------------
  1       37 x 1        36   0.22222   Racer reached goal at ( 36, 0) in 8 turns.
  2       38 x 1        37   0.24324   Racer reached goal at ( 37, 0) in 9 turns.
  3       33 x 1        32   0.25000   Racer reached goal at ( 32, 0) in 8 turns.
  4       10 x 10       10   0.40000   Racer reached goal at ( 7, 7) in 4 turns.
  5        9 x 6         8   0.37500   Racer reached goal at ( 6, 0) in 3 turns.
  6       15 x 7        16   0.37500   Racer reached goal at ( 12, 4) in 6 turns.
  7       17 x 8        16   0.31250   Racer reached goal at ( 14, 0) in 5 turns.
  8       19 x 13       18   0.27778   Racer reached goal at ( 0, 11) in 5 turns.
  9       60 x 10      107   0.14953   Racer reached goal at ( 0, 6) in 16 turns.
 10       31 x 31      106   0.23585   Racer reached goal at ( 27, 0) in 25 turns.
 11       31 x 31      106   0.24528   Racer reached goal at ( 15, 15) in 26 turns.
 12       50 x 20       50   0.24000   Racer reached goal at ( 49, 10) in 12 turns.
 13      100 x 100    2600   0.01385   Racer reached goal at ( 50, 0) in 36 turns.
 14       79 x 63      242   0.24380   Racer reached goal at ( 3, 42) in 59 turns.
 15       26 x 1        25   0.32000   Racer reached goal at ( 25, 0) in 8 turns.
 16       17 x 1        19   0.52632   Racer reached goal at ( 16, 0) in 10 turns.
 17       50 x 1        55   0.34545   Racer reached goal at ( 23, 0) in 19 turns.
 18       10 x 7        23   0.34783   Racer reached goal at ( 1, 3) in 8 turns.
 19       55 x 55       45   0.17778   Racer reached goal at ( 50, 26) in 8 turns.
 20      101 x 100     100   0.14000   Racer reached goal at ( 99, 99) in 14 turns.
 21   100000 x 1         1   1.00000   Racer reached goal at ( 0, 0) in 1 turns.
 22       50 x 50      200   0.05500   Racer reached goal at ( 47, 46) in 11 turns.
 23      290 x 290    1160   0.16466   Racer reached goal at ( 269, 265) in 191 turns.
-------------------------------------------------------------------------------------
TOTAL SCORE:                 6.66109

Vorstellungen

Diese beschissene C ++ - Sprache hat ein Händchen, mit dem man durch Reifen springen kann, nur um einen Streichholz zu bewegen. Sie können damit jedoch relativ schnellen und speichereffizienten Code erstellen.

Hashing

Hier besteht der Schlüssel darin, eine gute Hash-Tabelle für die Knoten bereitzustellen. Dies ist bei weitem der dominierende Faktor für die Ausführungsgeschwindigkeit.
Zwei Implementierungen von unordered_set(GNU und Microsoft) ergaben einen Geschwindigkeitsunterschied von 30% (zugunsten von GNU, yay!).

Der Unterschied ist nicht wirklich überraschend, was mit den Lastwagen voller Code dahinter verborgen ist unordered_set.

Aus Neugier habe ich einige Statistiken zum endgültigen Zustand der Hash-Tabelle erstellt.
Beide Algorithmen haben fast das gleiche Verhältnis von
Eimern zu Elementen, aber die Aufteilung variiert: Für den 290x290 Tie Breaker erhält GNU durchschnittlich 1,5 Elemente pro nicht leerem Eimer, während Microsoft bei 5,8 (!) Liegt.

Sieht so aus, als ob meine Hashing-Funktion von Microsoft nicht sehr gut randomisiert ist ... Ich frage mich, ob die Jungs in Redmond ihre STL wirklich verglichen haben, oder ob mein Anwendungsfall nur die GNU-Implementierung bevorzugt ...

Sicher, meine Hash-Funktion ist bei weitem nicht optimal. Ich hätte das übliche ganzzahlige Mischen auf der Basis von Mehrfachverschiebungen / Muls verwenden können, aber eine Hash-effiziente Funktion benötigt Zeit zum Berechnen.

Es scheint, dass die Anzahl der Hashtabellenabfragen im Vergleich zur Anzahl der Einfügungen sehr hoch ist. Zum Beispiel haben Sie im 290x290 Tie Breaker ungefähr 3,6 Millionen Einfügungen für 22,7 Millionen Abfragen.
In diesem Zusammenhang liefert ein suboptimales, aber schnelles Hashing bessere Leistungen.

Speicherzuweisung

Die Bereitstellung eines effizienten Speicherzuordners steht an zweiter Stelle. Es verbesserte die Leistung um etwa 30%. Ob es den hinzugefügten Mistcode wert ist, ist umstritten :).

Die aktuelle Version verwendet zwischen 40 und 55 Bytes pro Knoten.
Die Funktionsdaten benötigen für einen Knoten 24 Bytes (4 Koordinaten und 2 Zeiger).
Aufgrund des wahnsinnigen 100.000-Zeilen-Testfalls müssen Koordinaten in 4-Byte-Wörtern gespeichert werden, andernfalls könnten Sie durch Verwendung von Kurzschlüssen 8 Byte gewinnen (mit einem maximalen Koordinatenwert von 32767). Die verbleibenden Bytes werden hauptsächlich von der Hash-Tabelle der ungeordneten Menge verbraucht. Dies bedeutet, dass das Datenhandling tatsächlich etwas mehr als die "nützliche" Nutzlast verbraucht.

Und der Gewinner ist...

Auf meinem PC unter Win7 ist der Tie Breaker (Fall 23, 290x290) durch die schlechteste Version (dh Microsoft kompiliert) in ca. 2,2 Sekunden mit einem Speicherverbrauch von ca. 185 MB behoben.
Zum Vergleich: Der aktuelle Leader (Python-Code von user2357112) benötigt etwas mehr als 30 Sekunden und verbraucht ungefähr 780 MB.

Controller-Probleme

Ich bin mir nicht ganz sicher, ob ich in der Lage sein könnte, Ruby zu programmieren, um mein Leben zu retten.
Ich habe jedoch zwei Probleme aus dem Controller-Code entdeckt und gehackt:

1) Karte einlesen track.rb

Mit Ruby 1.9.3 würde der Trackreader beunruhigen, shift.to_inicht verfügbar zu sein string.lines.
Nach einigem Stöbern in der Online-Ruby-Dokumentation habe ich auf Strings verzichtet und stattdessen ein Intermediate-Array verwendet (direkt am Anfang der Datei):

def initialize(string)
    @track = Array.new;
    string.lines.each do |line|
        @track.push (line.chomp)
    end

2) töte Geister in controller.rb

Wie andere Poster bereits bemerkt haben, versucht der Controller manchmal, Prozesse zu beenden, die bereits beendet wurden. Um diese abscheulichen Fehlerausgaben zu vermeiden, habe ich die Ausnahme einfach wie folgt entfernt (um Zeile 134):

if silent
    begin # ;!;
        Process.kill('KILL', racer.pid)
    rescue Exception => e
    end

Testfall

Um den Brute-Force-Ansatz der BFS-Löser zu umgehen, ist der schlechteste Weg das Gegenteil der 100.000-Zellen-Karte: ein völlig freies Gebiet mit dem Ziel, so weit wie möglich vom Start entfernt zu sein.

In diesem Beispiel eine 100 x 400-Karte mit dem Ziel in der oberen linken Ecke und dem Start in der unteren rechten Ecke.

Diese Karte hat eine Lösung in 28 Runden, aber ein BFS-Löser wird Millionen von Staaten durchsuchen, um sie zu finden (meine zählte 10.022.658 besuchte Staaten, dauerte ungefähr 12 Sekunden und erreichte einen Höchstwert von 600 Mb!).

Mit weniger als der Hälfte der Oberfläche des 290x290 Tie Breakers sind etwa dreimal mehr Knotenbesuche erforderlich. Auf der anderen Seite sollte ein heuristischer / A * -basierter Löser es leicht besiegen können.

30
100 400
*...................................................................................................
....................................................................................................
                          < 400 lines in all >
....................................................................................................
....................................................................................................
...................................................................................................S

Bonus: eine äquivalente (aber etwas weniger effiziente) PHP-Version

Damit habe ich begonnen, bevor mich die inhärente Langsamkeit der Sprache von der Verwendung von C ++ überzeugt hat.
PHP-interne Hash-Tabellen scheinen nicht so effizient zu sein wie Pythons, zumindest in diesem speziellen Fall :).

<?php

class Trace {
    static $file;
    public static $state_member;
    public static $state_target;
    static function msg ($msg)
    {
        fputs (self::$file, "$msg\n");
    }

    static function dump ($var, $msg=null)
    {
        ob_start();
        if ($msg) echo "$msg ";
        var_dump($var);
        $dump=ob_get_contents();
        ob_end_clean();
        fputs (self::$file, "$dump\n");
    }

    function init ($fname)
    {
        self::$file = fopen ($fname, "w");
    }
}
Trace::init ("racer.txt");

class Point {
    public $x;
    public $y;

    function __construct ($x=0, $y=0)
    {
        $this->x = (float)$x;
        $this->y = (float)$y;
    }

    function __toString ()
    {
        return "[$this->x $this->y]";
    }

    function add ($v)
    {
        return new Point ($this->x + $v->x, $this->y + $v->y);
    }

    function vector_to ($goal)
    {
        return new Point ($goal->x - $this->x, $goal->y - $this->y);
    }
}

class Node {
    public $posx  , $posy  ;
    public $speedx, $speedy;
    private $parent;

    public function __construct ($posx, $posy, $speedx, $speedy, $parent)
    {
        $this->posx = $posx;
        $this->posy = $posy;
        $this->speedx = $speedx;
        $this->speedy = $speedy;
        $this->parent = $parent;
    }

    public function path ()
    {
        $res = array();
        $v = new Point ($this->speedx, $this->speedy);
        for ($node = $this->parent ; $node != null ; $node = $node->parent)
        {
            $nv = new Point ($node->speedx, $node->speedy);
            $a = $nv->vector_to ($v);
            $v = new Point ($node->speedx, $node->speedy);
            array_unshift ($res, $a);
        }
        return $res;
    }
}

class Map {

    private static $target;       // maximal number of turns
    private static $time;         // time available to solve
    private static $sx, $sy;      // map dimensions
    private static $cell;         // cells of the map
    private static $start;        // starting point
    private static $acceleration; // possible acceleration values

    public static function init ()
    {
        // read map definition
        self::$target = trim(fgets(STDIN));
        list (self::$sx, self::$sy) = explode (" ", trim(fgets(STDIN)));
        self::$cell = array();
        for ($y = 0 ; $y != self::$sy ; $y++) self::$cell[] = str_split (trim(fgets(STDIN)));
        self::$time = trim(fgets(STDIN));

        // get starting point
        foreach (self::$cell as $y=>$row)
        {
            $x = array_search ("S", $row);
            if ($x !== false)
            {
                self::$start = new Point ($x, $y);
Trace::msg ("start ".self::$start);
                break;
            }
        }

        // compute possible acceleration values
        self::$acceleration = array();
        for ($x = -1 ; $x <= 1 ; $x++)
        for ($y = -1 ; $y <= 1 ; $y++)
        {
            self::$acceleration[] = new Point ($x, $y);
        }
    }

    public static function solve ()
    {
        $now = microtime(true);
        $res = array();
        $border = array (new Node (self::$start->x, self::$start->y, 0, 0, null));
        $present = array (self::$start->x." ".self::$start->y." 0 0" => 1);
        while (count ($border))
        {
if ((microtime(true) - $now) > 1)
{
Trace::msg (count($present)." nodes, ".round(memory_get_usage(true)/1024)."K");
$now = microtime(true);
}
            $node = array_shift ($border);
//Trace::msg ("node $node->pos $node->speed");
            $px = $node->posx;
            $py = $node->posy;
            $vx = $node->speedx;
            $vy = $node->speedy;
            foreach (self::$acceleration as $a)
            {
                $nvx = $vx + $a->x;
                $nvy = $vy + $a->y;
                $npx = $px + $nvx;
                $npy = $py + $nvy;
                if ($npx < 0 || $npx >= self::$sx || $npy < 0 || $npy >= self::$sy || self::$cell[$npy][$npx] == "#")
                {
//Trace::msg ("invalid position $px,$py $vx,$vy -> $npx,$npy");
                    continue;
                }
                if (self::$cell[$npy][$npx] == "*")
                {
Trace::msg ("winning position $px,$py $vx,$vy -> $npx,$npy");
                    $end = new Node ($npx, $npy, $nvx, $nvy, $node);
                    $res = $end->path ();
                    break 2;
                }
//Trace::msg ("checking $np $nv");
                $signature = "$npx $npy $nvx $nvy";
                if (isset ($present[$signature])) continue;
//Trace::msg ("*** adding $np $nv");
                $border[] = new Node ($npx, $npy, $nvx, $nvy, $node);
                $present[$signature] = 1;
            }
        }
        return $res;
    }
}

ini_set("memory_limit","1000M");
Map::init ();
$res = Map::solve();
//Trace::dump ($res);
foreach ($res as $a) echo "$a->x $a->y\n";
?>

quelle
erf ... Mein Barebone-Allokator ist einfach ein bisschen zu barebone. Ich werde dann den nötigen Mist hinzufügen, damit es mit g ++ funktioniert. Das tut mir leid.
OK, das ist behoben. Die g ++ Version arbeitet sogar um 30% schneller. Es werden nun einige Statistiken zu stderr ausgegeben. Fühlen Sie sich frei, es auszukommentieren (aus den letzten Zeilen der Quelle). Entschuldigung nochmal für den Fehler.
Okay, es funktioniert jetzt und ich habe deine Punktzahl reproduziert. Es ist verdammt schnell! :) Ich werde Ihren Testfall zum Benchmark hinzufügen, aber ich werde das Ziel auf ändern 400, da dies mit der Art und Weise übereinstimmt, wie ich alle anderen Ziele bestimmt habe (mit Ausnahme des Tie Breakers). Ich werde den Hauptbeitrag aktualisieren, sobald ich alle anderen Beiträge erneut bearbeitet habe.
Martin Ender
Aktualisiert die Ergebnisse. Ein Tie Breaker war nicht erforderlich, da alle anderen Einsendungen das Speicherlimit auf Ihrer Teststrecke überschreiten. Herzlichen Glückwunsch dazu! :)
Martin Ender
Vielen Dank. Eigentlich gab mir diese Herausforderung die Gelegenheit, mich in diese STL-Hash-Tabellen zu vertiefen. Obwohl ich C ++ - Eingeweide hasse, kann ich nicht anders, als von meiner Neugierde getötet zu werden. Miau! :).
10

C ++, 5.4 (deterministisch, optimal)

Dynamische Programmierlösung. Nachweislich optimal. Sehr schnell: Löst alle 20 Testfälle in 0,2s. Sollte auf 64-Bit-Rechnern besonders schnell sein. Angenommen, die Tafel hat weniger als 32.000 Stellen in jeder Richtung (was hoffentlich zutreffen sollte).

Dieser Rennfahrer ist etwas ungewöhnlich. Es berechnet den optimalen Pfad an der Startlinie und führt anschließend den berechneten Pfad sofort aus. Sie ignoriert die Zeitsteuerung und geht davon aus, dass sie den Optimierungsschritt rechtzeitig abschließen kann (was für jede einigermaßen moderne Hardware zutreffen sollte). Auf übermäßig großen Karten besteht eine geringe Wahrscheinlichkeit, dass der Rennfahrer einen Fehler macht. Wenn Sie es zu segfault überreden können, erhalten Sie einen Brownie-Punkt, und ich werde es beheben, um eine explizite Schleife zu verwenden.

Kompilieren mit g++ -O3. Möglicherweise ist C ++ 11 (für <unordered_map>) erforderlich . Zum Ausführen führen Sie einfach die kompilierte ausführbare Datei aus (keine Flags oder Optionen werden unterstützt; alle Eingaben erfolgen über stdin).

#include <unordered_map>
#include <iostream>
#include <string>
#include <vector>
#include <sstream>

#include <cstdint>

#define MOVES_INF (1<<30)

union state {
    struct {
        short px, py, vx, vy;
    };
    uint64_t val;
};

struct result {
    int nmoves;
    short dvx, dvy;
};

typedef std::unordered_map<uint64_t, result> cache_t;
int target, n, m;
std::vector<std::string> track;
cache_t cache;

static int solve(uint64_t val) {
    cache_t::iterator it = cache.find(val);
    if(it != cache.end())
        return it->second.nmoves;

    // prevent recursion
    result res;
    res.nmoves = MOVES_INF;
    cache[val] = res;

    state cur;
    cur.val = val;
    for(int dvx = -1; dvx <= 1; dvx++) for(int dvy = -1; dvy <= 1; dvy++) {
        state next;
        next.vx = cur.vx + dvx;
        next.vy = cur.vy + dvy;
        next.px = cur.px + next.vx;
        next.py = cur.py + next.vy;
        if(next.px < 0 || next.px >= n || next.py < 0 || next.py >= m)
            continue;
        char c = track[next.py][next.px];
        if(c == '*') {
            res.nmoves = 1;
            res.dvx = dvx;
            res.dvy = dvy;
            break;
        } else if(c == '#') {
            continue;
        } else {
            int score = solve(next.val) + 1;
            if(score < res.nmoves) {
                res.nmoves = score;
                res.dvx = dvx;
                res.dvy = dvy;
            }
        }
    }

    cache[val] = res;
    return res.nmoves;
}

bool solve_one() {
    std::string line;
    float time;

    std::cin >> target;
    // std::cin >> time; // uncomment to use "time" control
    std::cin >> n >> m;
    if(!std::cin)
        return false;
    std::cin.ignore(); // skip newline at end of "n m" line

    track.clear();
    track.reserve(m);

    for(int i=0; i<m; i++) {
        std::getline(std::cin, line);
        track.push_back(line);
    }

    cache.clear();

    state cur;
    cur.vx = cur.vy = 0;
    for(int y=0; y<m; y++) for(int x=0; x<n; x++) {
        if(track[y][x] == 'S') {
            cur.px = x;
            cur.py = y;
            break;
        }
    }

    solve(cur.val);

    int sol_len = 0;
    while(track[cur.py][cur.px] != '*') {
        cache_t::iterator it = cache.find(cur.val);
        if(it == cache.end() || it->second.nmoves >= MOVES_INF) {
            std::cerr << "Failed to solve at p=" << cur.px << "," << cur.py << " v=" << cur.vx << "," << cur.vy << std::endl;
            return true;
        }

        int dvx = it->second.dvx;
        int dvy = it->second.dvy;
        cur.vx += dvx;
        cur.vy += dvy;
        cur.px += cur.vx;
        cur.py += cur.vy;
        std::cout << dvx << " " << dvy << std::endl;
        sol_len++;
    }

    //std::cerr << "Score: " << ((float)sol_len) / target << std::endl;

    return true;
}

int main() {
    /* benchmarking: */
    //while(solve_one())
    //    ;

    /* regular running */
    solve_one();
    std::string line;
    while(std::cin) std::getline(std::cin, line);

    return 0;
}

Ergebnisse

 No.    Size     Target   Score     Details
-------------------------------------------------------------------------------------
  1    37 x 1        36   0.22222   Racer reached goal at ( 36, 0) in 8 turns.
  2    38 x 1        37   0.24324   Racer reached goal at ( 37, 0) in 9 turns.
  3    33 x 1        32   0.25000   Racer reached goal at ( 32, 0) in 8 turns.
  4    10 x 10       10   0.40000   Racer reached goal at ( 7, 7) in 4 turns.
  5     9 x 6         8   0.37500   Racer reached goal at ( 6, 0) in 3 turns.
  6    15 x 7        16   0.37500   Racer reached goal at ( 12, 4) in 6 turns.
  7    17 x 8        16   0.31250   Racer reached goal at ( 15, 0) in 5 turns.
  8    19 x 13       18   0.27778   Racer reached goal at ( 1, 11) in 5 turns.
  9    60 x 10      107   0.14953   Racer reached goal at ( 2, 6) in 16 turns.
 10    31 x 31      106   0.25472   Racer reached goal at ( 28, 0) in 27 turns.
 11    31 x 31      106   0.24528   Racer reached goal at ( 15, 15) in 26 turns.
 12    50 x 20       50   0.24000   Racer reached goal at ( 49, 10) in 12 turns.
 13   100 x 100    2600   0.01385   Racer reached goal at ( 50, 0) in 36 turns.
 14    79 x 63      242   0.26860   Racer reached goal at ( 3, 42) in 65 turns.
 15    26 x 1        25   0.32000   Racer reached goal at ( 25, 0) in 8 turns.
 16    17 x 1        19   0.52632   Racer reached goal at ( 16, 0) in 10 turns.
 17    50 x 1        55   0.34545   Racer reached goal at ( 23, 0) in 19 turns.
 18    10 x 7        23   0.34783   Racer reached goal at ( 1, 3) in 8 turns.
 19    55 x 55       45   0.17778   Racer reached goal at ( 52, 26) in 8 turns.
 20    50 x 50      200   0.05500   Racer reached goal at ( 47, 46) in 11 turns.
-------------------------------------------------------------------------------------
TOTAL SCORE:              5.40009

Neuer Testfall

nneonneo
quelle
1
Nun, so etwas wurde ziemlich erwartet. Es gibt einfach nicht genug Status im Puzzle, um eine dynamische Programmierung unmöglich zu machen. Wenn ich eintrete, muss ich eine Karte einreichen, deren Lösung komplexere Suchstrategien erfordert.
user2357112
Wie schneidet Ihr Rennfahrer in Ihrem Testfall ab?
user2357112
0,14 (14 Züge)
nneonneo
Wird diese Zeit benötigt oder bewegt sich das Ziel? Wenn es sich um Bewegungen / Ziele handelt, wie verhält es sich in Bezug auf die Zeit?
user2357112
1
Ich glaube, ich habe einen Fehler im Code zur Zyklusverhütung gefunden. Er geht davon aus, dass für jeden Zustand des Suchlauf von einem Zustand S, ein optimaler Pfad nicht zu S. zurückkehren kann Es könnte das scheint , wenn ein optimaler Weg ist Rückkehr in S, dann ist der Zustand nicht auf einem optimalen Weg sein kann (da wir konnten Entfernen Sie einfach die eingeschaltete Schleife und Sie erhalten einen kürzeren Pfad. Es ist uns also egal, ob wir für diesen Zustand ein zu hohes Ergebnis erhalten. Wenn jedoch ein optimaler Pfad die Zustände A und B in dieser Reihenfolge durchläuft, die Suche jedoch zuerst A findet, während sich B noch auf dem Stapel befindet, werden die Ergebnisse von A durch die Schleifenverhinderung vergiftet.
user2357112
6

Python 2 , deterministisch, optimal

Hier ist mein Rennfahrer. Ich habe es noch nicht im Benchmark getestet (ich waffle immer noch darum, welche Version und welches Installationsprogramm von Ruby installiert werden soll), aber es sollte alles optimal und unter dem Zeitlimit lösen. Der Befehl zum Ausführen lautet python whateveryoucallthefile.py. Benötigt das -sController-Flag.

# Breadth-first search.
# Future directions: bidirectional search and/or A*.

import operator
import time

acceleration_options = [(dvx, dvy) for dvx in [-1, 0, 1] for dvy in [-1, 0, 1]]

class ImpossibleRaceError(Exception): pass

def read_input(line_source=raw_input):
    # We don't use the target.
    target = int(line_source())

    width, height = map(int, line_source().split())
    input_grid = [line_source() for _ in xrange(height)]

    start = None
    for i in xrange(height):
        for j in xrange(width):
            if input_grid[i][j] == 'S':
                start = i, j
                break
        if start is not None:
            break

    walls = [[cell == '#' for cell in row] for row in input_grid]
    goals = [[cell == '*' for cell in row] for row in input_grid]

    return start, walls, goals

def default_bfs_stop_threshold(walls, goals):
    num_not_wall = sum(sum(map(operator.not_, row)) for row in walls)
    num_goals = sum(sum(row) for row in goals)
    return num_goals * num_not_wall

def bfs(start, walls, goals, stop_threshold=None):
    if stop_threshold is None:
        stop_threshold = default_bfs_stop_threshold(walls, goals)

    # State representation is (x, y, vx, vy)
    x, y = start
    initial_state = (x, y, 0, 0)
    frontier = {initial_state}
    # Visited set is tracked by a map from each state to the last move taken
    # before reaching that state.
    visited = {initial_state: None}

    while len(frontier) < stop_threshold:
        if not frontier:
            raise ImpossibleRaceError

        new_frontier = set()
        for x, y, vx, vy in frontier:
            for dvx, dvy in acceleration_options:
                new_vx, new_vy = vx+dvx, vy+dvy
                new_x, new_y = x+new_vx, y+new_vy
                new_state = (new_x, new_y, new_vx, new_vy)

                if not (0 <= new_x < len(walls) and 0 <= new_y < len(walls[0])):
                    continue
                if walls[new_x][new_y]:
                    continue
                if new_state in visited:
                    continue

                new_frontier.add(new_state)
                visited[new_state] = dvx, dvy

                if goals[new_x][new_y]:
                    return construct_path_from_bfs(new_state, visited)
        frontier = new_frontier

def construct_path_from_bfs(goal_state, best_moves):
    reversed_path = []
    current_state = goal_state
    while best_moves[current_state] is not None:
        move = best_moves[current_state]
        reversed_path.append(move)

        x, y, vx, vy = current_state
        dvx, dvy = move
        old_x, old_y = x-vx, y-vy # not old_vx or old_vy
        old_vx, old_vy = vx-dvx, vy-dvy
        current_state = (old_x, old_y, old_vx, old_vy)
    return reversed_path[::-1]

def main():
    t = time.time()

    start, walls, goals = read_input()
    path = bfs(start, walls, goals, float('inf'))
    for dvx, dvy in path:
        # I wrote the whole program with x pointing down and y pointing right.
        # Whoops. Gotta flip things for the output.
        print dvy, dvx

if __name__ == '__main__':
    main()

Nachdem ich den Renner von nneonneo untersucht hatte (aber nicht getestet habe, da ich auch keinen C ++ - Compiler besitze), stellte ich fest, dass er eine fast erschöpfende Suche im Zustandsraum durchführt, egal wie nah das Ziel ist oder wie kurz es ist Ein Pfad wurde bereits gefunden. Ich habe auch festgestellt, dass die Zeitregeln bedeuten, dass das Erstellen einer Karte mit einer langen, komplexen Lösung ein langes, langweiliges Zeitlimit erfordert. Somit ist meine Kartenübermittlung ziemlich einfach:

Neuer Testfall

(GitHub kann die lange Reihe nicht anzeigen. Der Track ist *S.......[and so on].....)


Zusätzliche Einreichung: Python 2, bidirektionale Suche

Dies ist ein Ansatz, den ich vor ungefähr zwei Monaten geschrieben habe, als ich versuchte, meine erste Einreichung zu optimieren. Für die Testfälle, die zu der Zeit existierten, bot es keine Verbesserung, so dass ich es nicht einreichte, aber für Kurois neue Karte scheint es sich gerade noch unter der Speicherkappe hindurchzuquetschen. Ich erwarte immer noch, dass Kurois Löser dies schlägt, aber ich bin daran interessiert, wie es hält.

# Bidirectional search.
# Future directions: A*.

import operator
import time

acceleration_options = [(dvx, dvy) for dvx in [-1, 0, 1] for dvy in [-1, 0, 1]]

class ImpossibleRaceError(Exception): pass

def read_input(line_source=raw_input):
    # We don't use the target.
    target = int(line_source())

    width, height = map(int, line_source().split())
    input_grid = [line_source() for _ in xrange(height)]

    start = None
    for i in xrange(height):
        for j in xrange(width):
            if input_grid[i][j] == 'S':
                start = i, j
                break
        if start is not None:
            break

    walls = [[cell == '#' for cell in row] for row in input_grid]
    goals = [[cell == '*' for cell in row] for row in input_grid]

    return start, walls, goals

def bfs_to_bidi_threshold(walls, goals):
    num_not_wall = sum(sum(map(operator.not_, row)) for row in walls)
    num_goals = sum(sum(row) for row in goals)
    return num_goals * (num_not_wall - num_goals)

class GridBasedGoalContainer(object):
    '''Supports testing whether a state is a goal state with `in`.

    Does not perform bounds checking.'''
    def __init__(self, goal_grid):
        self.goal_grid = goal_grid
    def __contains__(self, state):
        x, y, vx, vy = state
        return self.goal_grid[x][y]

def forward_step(state, acceleration):
    x, y, vx, vy = state
    dvx, dvy = acceleration

    new_vx, new_vy = vx+dvx, vy+dvy
    new_x, new_y = x+new_vx, y+new_vy

    return (new_x, new_y, new_vx, new_vy)

def backward_step(state, acceleration):
    x, y, vx, vy = state
    dvx, dvy = acceleration

    old_x, old_y = x-vx, y-vy
    old_vx, old_vy = vx-dvx, vy-dvy

    return (old_x, old_y, old_vx, old_vy)

def bfs(start, walls, goals):
    x, y = start
    initial_state = (x, y, 0, 0)
    initial_frontier = {initial_state}
    visited = {initial_state: None}

    goal_state, frontier, visited = general_bfs(
        frontier=initial_frontier,
        visited=visited,
        walls=walls,
        goalcontainer=GridBasedGoalContainer(goals),
        stop_threshold=float('inf'),
        step_function=forward_step
    )

    return construct_path_from_bfs(goal_state, visited)

def general_bfs(
        frontier,
        visited,
        walls,
        goalcontainer,
        stop_threshold,
        step_function):

    while len(frontier) <= stop_threshold:
        if not frontier:
            raise ImpossibleRaceError

        new_frontier = set()
        for state in frontier:
            for accel in acceleration_options:
                new_state = new_x, new_y, new_vx, new_vy = \
                        step_function(state, accel)

                if not (0 <= new_x < len(walls) and 0 <= new_y < len(walls[0])):
                    continue
                if walls[new_x][new_y]:
                    continue
                if new_state in visited:
                    continue

                new_frontier.add(new_state)
                visited[new_state] = accel

                if new_state in goalcontainer:
                    return new_state, frontier, visited
        frontier = new_frontier
    return None, frontier, visited

def max_velocity_component(n):
    # It takes a distance of at least 0.5*v*(v+1) to achieve a velocity of
    # v in the x or y direction. That means the map has to be at least
    # 1 + 0.5*v*(v+1) rows or columns long to accomodate such a velocity.
    # Solving for v, we get a velocity cap as follows.
    return int((2*n-1.75)**0.5 - 0.5)

def solver(
        start,
        walls,
        goals,
        mode='bidi'):

    x, y = start
    initial_state = (x, y, 0, 0)
    initial_frontier = {initial_state}
    visited = {initial_state: None}
    if mode == 'bidi':
        stop_threshold = bfs_to_bidi_threshold(walls, goals)
    elif mode == 'bfs':
        stop_threshold = float('inf')
    else:
        raise ValueError('Unsupported search mode: {}'.format(mode))

    goal_state, frontier, visited = general_bfs(
        frontier=initial_frontier,
        visited=visited,
        walls=walls,
        goalcontainer=GridBasedGoalContainer(goals),
        stop_threshold=stop_threshold,
        step_function=forward_step
    )

    if goal_state is not None:
        return construct_path_from_bfs(goal_state, visited)

    # Switching to bidirectional search.

    not_walls_or_goals = []
    goal_list = []
    for x in xrange(len(walls)):
        for y in xrange(len(walls[0])):
            if not walls[x][y] and not goals[x][y]:
                not_walls_or_goals.append((x, y))
            if goals[x][y]:
                goal_list.append((x, y))
    max_vx = max_velocity_component(len(walls))
    max_vy = max_velocity_component(len(walls[0]))
    reverse_visited = {(goal_x, goal_y, goal_x-prev_x, goal_y-prev_y): None
                        for goal_x, goal_y in goal_list
                        for prev_x, prev_y in not_walls_or_goals
                        if abs(goal_x-prev_x) <= max_vx
                        and abs(goal_y - prev_y) <= max_vy}
    reverse_frontier = set(reverse_visited)
    while goal_state is None:
        goal_state, reverse_frontier, reverse_visited = general_bfs(
            frontier=reverse_frontier,
            visited=reverse_visited,
            walls=walls,
            goalcontainer=frontier,
            stop_threshold=len(frontier),
            step_function=backward_step
        )
        if goal_state is not None:
            break
        goal_state, frontier, visited = general_bfs(
            frontier=frontier,
            visited=visited,
            walls=walls,
            goalcontainer=reverse_frontier,
            stop_threshold=len(reverse_frontier),
            step_function=forward_step
        )
    forward_path = construct_path_from_bfs(goal_state, visited)
    backward_path = construct_path_from_bfs(goal_state,
                                            reverse_visited,
                                            step_function=forward_step)
    return forward_path + backward_path[::-1]

def construct_path_from_bfs(goal_state,
                            best_moves,
                            step_function=backward_step):
    reversed_path = []
    current_state = goal_state
    while best_moves[current_state] is not None:
        move = best_moves[current_state]
        reversed_path.append(move)
        current_state = step_function(current_state, move)
    return reversed_path[::-1]

def main():
    start, walls, goals = read_input()
    t = time.time()
    path = solver(start, walls, goals)
    for dvx, dvy in path:
        # I wrote the whole program with x pointing down and y pointing right.
        # Whoops. Gotta flip things for the output.
        print dvy, dvx

if __name__ == '__main__':
    main()
user2357112
quelle
Dies schlägt manchmal in den Fällen 12 und 13 fehl. Weiß nicht warum, da die Fehlermeldungen etwas ... unfreundlich sind
Ray
@Ray Ich bekomme auch Fehlermeldungen, aber für diese bekomme ich immer das Ergebnis. Ich denke, es könnte eher etwas in meinem Controller sein, denn es sieht so aus, als würde der Controller versuchen, den Racer-Prozess abzubrechen, obwohl er bereits beendet ist.
Martin Ender
@m.buettner Ich habe den Grund gefunden, add -s dann wird es OK sein.
Ray
@ Ray Oh ja, das mache ich. Auf den Spuren 13 und 14 wird immer noch ein Fehler angezeigt, wenn der Controller versucht, den Vorgang abzubrechen, obwohl das Ergebnis bereits vorliegt. Ich denke, ich sollte das untersuchen, aber es hat keinen Einfluss auf die Wertung, also habe ich mich noch nicht darum gekümmert.
Martin Ender
Leider musste ich eine andere Regel hinzufügen. Der Arbeitsspeicher scheint bei dieser Herausforderung begrenzender zu sein als die Zeit, daher musste ich den Arbeitsspeicherverbrauch schwer einschränken. Jeder Lauf, in dem Ihr Rennfahrer mehr als 1 GB Arbeitsspeicher benötigt, wird mit dem gleichen Effekt abgebrochen, wenn das Zeitlimit überschritten wird. Für den aktuellen Satz von Tracks wurde Ihre Partitur von dieser Änderung nicht beeinflusst. (Ich denke, Sie werden diese Grenze bei den Tie-Breakern erreichen n = 400.) Bitte lassen Sie mich wissen, wenn Sie Optimierungen anwenden, damit ich die Tests wiederholen kann.
Martin Ender
3

Python 3: 6.49643 (Optimal, BFS)

Für die alte Benchmark-Datei mit 20 Fällen wurde eine Punktzahl von 5,35643 erreicht. Die Lösung von @nneonneo ist nicht optimal, da es 5.4 bekam. Einige Bugs vielleicht.

Bei dieser Lösung wird BFS zum Durchsuchen des Diagramms verwendet. Jeder Suchstatus hat die Form (x, y, dx, dy). Dann benutze ich eine Karte, um von Staaten zu Entfernungen abzubilden. Im schlimmsten Fall ist die zeitliche und räumliche Komplexität O (n ^ 2 m ^ 2). Dies wird selten passieren, da die Geschwindigkeit nicht zu hoch ist oder der Rennfahrer abstürzt. Tatsächlich kostete es 3 Sekunden auf meinem Computer, alle 22 Testfälle abzuschließen.

from collections import namedtuple, deque
import itertools

Field = namedtuple('Map', 'n m grids')

class Grid:
    WALL = '#'
    EMPTY = '.'
    START = 'S'
    END = '*'

def read_nums():
    return list(map(int, input().split()))

def read_field():
    m, n = read_nums()
    return Field(n, m, [input() for i in range(n)])

def find_start_pos(field):
    return next((i, j)
        for i in range(field.n) for j in range(field.m)
        if field.grids[i][j] == Grid.START)

def can_go(field, i, j):
    return 0 <= i < field.n and 0 <= j < field.m and field.grids[i][j] != Grid.WALL

def trace_path(start, end, prev):
    if end == start:
        return
    end, step = prev[end]
    yield from trace_path(start, end, prev)
    yield step

def solve(max_turns, field, time):
    i0, j0 = find_start_pos(field)
    p0 = i0, j0, 0, 0
    prev = {}
    que = deque([p0])
    directions = list(itertools.product((-1, 0, 1), (-1, 0, 1)))

    while que:
        p = i, j, vi, vj = que.popleft()
        for dvi, dvj in directions:
            vi1, vj1 = vi + dvi, vj + dvj
            i1, j1 = i + vi1, j + vj1
            if not can_go(field, i1, j1):
                continue
            p1 = i1, j1, vi1, vj1
            if p1 in prev:
                continue
            que.append(p1)
            prev[p1] = p, (dvi, dvj)
            if field.grids[i1][j1] == Grid.END:
                return trace_path(p0, p1, prev)
    return []

def main():
    for dvy, dvx in solve(int(input()), read_field(), float(input())):
        print(dvx, dvy)

main()

# Ergebnisse

± % time ruby controller.rb benchmark.txt python ../mybfs.py                                                                                                                                                                             !9349
["benchmark.txt", "python", "../mybfs.py"]

Running 'python ../mybfs.py' against benchmark.txt

 No.       Size     Target   Score     Details
-------------------------------------------------------------------------------------
  1       37 x 1        36   0.22222   Racer reached goal at ( 36, 0) in 8 turns.
  2       38 x 1        37   0.24324   Racer reached goal at ( 37, 0) in 9 turns.
  3       33 x 1        32   0.25000   Racer reached goal at ( 32, 0) in 8 turns.
  4       10 x 10       10   0.40000   Racer reached goal at ( 7, 7) in 4 turns.
  5        9 x 6         8   0.37500   Racer reached goal at ( 6, 0) in 3 turns.
  6       15 x 7        16   0.37500   Racer reached goal at ( 12, 4) in 6 turns.
  7       17 x 8        16   0.31250   Racer reached goal at ( 14, 0) in 5 turns.
  8       19 x 13       18   0.27778   Racer reached goal at ( 0, 11) in 5 turns.
  9       60 x 10      107   0.14953   Racer reached goal at ( 0, 6) in 16 turns.
 10       31 x 31      106   0.23585   Racer reached goal at ( 27, 0) in 25 turns.
 11       31 x 31      106   0.24528   Racer reached goal at ( 15, 15) in 26 turns.
 12       50 x 20       50   0.24000   Racer reached goal at ( 49, 10) in 12 turns.
 13      100 x 100    2600   0.01385   Racer reached goal at ( 50, 0) in 36 turns.
 14       79 x 63      242   0.24380   Racer reached goal at ( 3, 42) in 59 turns.
 15       26 x 1        25   0.32000   Racer reached goal at ( 25, 0) in 8 turns.
 16       17 x 1        19   0.52632   Racer reached goal at ( 16, 0) in 10 turns.
 17       50 x 1        55   0.34545   Racer reached goal at ( 23, 0) in 19 turns.
 18       10 x 7        23   0.34783   Racer reached goal at ( 1, 3) in 8 turns.
 19       55 x 55       45   0.17778   Racer reached goal at ( 50, 26) in 8 turns.
 20      101 x 100     100   0.14000   Racer reached goal at ( 99, 99) in 14 turns.
 21   100000 x 1         1   1.00000   Racer reached goal at ( 0, 0) in 1 turns.
 22       50 x 50      200   0.05500   Racer reached goal at ( 47, 46) in 11 turns.
-------------------------------------------------------------------------------------
TOTAL SCORE:                 6.49643

ruby controller.rb benchmark.txt python ../mybfs.py  3.06s user 0.06s system 99% cpu 3.146 total
Strahl
quelle
Ja, laut user2357112's Kommentar gibt es einen Fehler in der Zyklusprävention von Nneonneo. Soweit ich weiß, ist die Geschwindigkeit begrenzt, durch O(√n)die Ihre Implementierung O(n³)auf quadratischen Gittern erfolgen würde (wie bei den anderen, nehme ich an). Ich werde einen Tie-Breaker hinzufügen, um Ihre Einreichung mit der von user2357112 zu vergleichen.
Martin Ender
Übrigens, haben Sie vor, einen weiteren Testfall hinzuzufügen?
Martin Ender
@m.buettner Nein, ich habe nicht genug Verständnis für dieses Spiel. Mein Testfall wird also kein interessanter sein.
Ray
Leider musste ich eine weitere Regel hinzufügen. Der Speicher scheint bei dieser Herausforderung begrenzender zu sein als die Zeit, daher musste ich den Speicherverbrauch schwer einschränken. Jeder Lauf, in dem Ihr Rennfahrer mehr als 1 GB Arbeitsspeicher benötigt, wird mit dem gleichen Effekt abgebrochen, wenn das Zeitlimit überschritten wird. Mit dieser Regel ist Ihr Beitrag der erste, der diese Grenze für einen Gleichstand überschreitet, n=270weshalb Sie jetzt hinter den beiden anderen "optimalen" Beiträgen zurückbleiben. Abgesehen davon ist Ihr Beitrag auch der langsamste der drei, wäre also sowieso der dritte gewesen, nur mit einem größeren Tie-Breaker.
Martin Ender
Bitte lassen Sie mich wissen, wenn Sie Optimierungen anwenden, damit ich die Tests wiederholen kann.
Martin Ender
1

RandomRacer, ~ 40.0 (gemittelt über 10 Läufe)

Es ist nicht so, dass dieser Bot niemals einen Track beendet, aber definitiv viel seltener als einmal in 10 Versuchen. (Ich erhalte alle 20 bis 30 Simulationen oder so einen Nicht-Worst-Case-Score.)

Dies dient hauptsächlich als Basisfall und um eine mögliche (Ruby-) Implementierung für einen Rennfahrer zu demonstrieren:

# Parse initial input
target = gets.to_i
size = gets.split.map(&:to_i)
track = []
size[1].times do
    track.push gets
end
time_budget = gets.to_f

# Find start position
start_y = track.find_index { |row| row['S'] }
start_x = track[start_y].index 'S'

position = [start_x, start_y]
velocity = [0, 0]

while true
    x = rand(3) - 1
    y = rand(3) - 1
    puts [x,y].join ' '
    $stdout.flush

    first_line = gets
    break if !first_line || first_line.chomp.empty?

    position = first_line.split.map(&:to_i)
    velocity = gets.split.map(&:to_i)
    time_budget = gets.to_f
end

Führen Sie es mit

ruby controller.rb benchmark.txt ruby randomracer.rb
Martin Ender
quelle
1

Random Racer 2.0, ~ 31

Nun, das wird den veröffentlichten optimalen Löser nicht schlagen, aber es ist eine leichte Verbesserung gegenüber einem zufälligen Rennfahrer. Der Hauptunterschied besteht darin, dass dieser Rennfahrer nur willkürlich überlegt, wohin keine Mauer führt, es sei denn, er hat nicht genügend gültige Bewegungsplätze und kann sich in dieser Kurve zu einem Ziel bewegen. Es bewegt sich auch nicht, um an der gleichen Stelle zu bleiben, es sei denn, es ist kein anderer Zug verfügbar (unwahrscheinlich, aber möglich).

In Java implementiert, mit java8 kompiliert, aber Java 6 sollte in Ordnung sein. Keine Befehlszeilenparameter. Es gibt einen ziemlich guten Clusterfick von Hierarchien, also denke ich, dass ich Java richtig mache.

import java.util.Scanner;
import java.util.Random;
import java.util.ArrayList;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

public class VectorRacing   {
    private static Scanner in = new Scanner(System.in);
    private static Random rand = new Random();
    private static Track track;
    private static Racer racer;
    private static int target;
    private static double time;
    public static void main(String[] args)  {
        init();
        main_loop();
    }
    private static void main_loop() {
        Scanner linescan;
        String line;
        int count = 0,
            x, y, u, v;

        while(!racer.lost() && !racer.won() && count < target)  {
            Direction d = racer.think();
            racer.move(d);
            count++;
            System.out.println(d);

            line = in.nextLine();
            if(line.equals("")) {
                break;
            }
            linescan = new Scanner(line);
            x = linescan.nextInt();
            y = linescan.nextInt();
            linescan = new Scanner(in.nextLine());
            u = linescan.nextInt();
            v = linescan.nextInt();
            time = Double.parseDouble(in.nextLine());

            assert x == racer.location.x;
            assert y == racer.location.y;
            assert u == racer.direction.x;
            assert v == racer.direction.y;
        }
    }
    private static void init()  {
        target = Integer.parseInt(in.nextLine());
        int width = in.nextInt();
        int height = Integer.parseInt(in.nextLine().trim());
        String[] ascii = new String[height];
        for(int i = 0; i < height; i++) {
            ascii[i] = in.nextLine();
        }
        time = Double.parseDouble(in.nextLine());
        track = new Track(width, height, ascii);
        for(int y = 0; y < ascii.length; y++)   {
            int x = ascii[y].indexOf("S");
            if( x != -1)    {
                racer = new RandomRacer(track, new Location(x, y));
                break;
            }
        }
    }

    public static class RandomRacer extends Racer   {
        public RandomRacer(Track t, Location l) {
            super(t, l);
        }
        public Direction think()    {
            ArrayList<Pair<Location, Direction> > possible = this.getLocationsCanMoveTo();
            if(possible.size() == 0)    {
                return Direction.NONE;
            }
            Pair<Location, Direction> ret = null;
            do  {
                ret = possible.get(rand.nextInt(possible.size()));
            }   while(possible.size() != 1 && ret.a.equals(this.location));
            return ret.b;
        }
    }

    // Base things
    public enum Direction   {
        NORTH("0 -1"), SOUTH("0 1"), EAST("1 0"), WEST("-1 0"), NONE("0 0"),
        NORTH_EAST("1 -1"), NORTH_WEST("-1 -1"), SOUTH_EAST("1 1"), SOUTH_WEST("-1 1");

        private final String d;
        private Direction(String d) {this.d = d;}
        public String toString()    {return d;}
    }
    public enum Cell    {
        WALL('#'), GOAL('*'), ROAD('.'), OUT_OF_BOUNDS('?');

        private final char c;
        private Cell(char c)    {this.c = c;}
        public String toString()    {return "" + c;}
    }

    public static class Track   {
        private Cell[][] track;
        private int target;
        private double time;
        public Track(int width, int height, String[] ascii) {
            this.track = new Cell[width][height];
            for(int y = 0; y < height; y++) {
                for(int x = 0; x < width; x++)  {
                    switch(ascii[y].charAt(x))  {
                        case '#':   this.track[x][y] = Cell.WALL; break;
                        case '*':   this.track[x][y] = Cell.GOAL; break;
                        case '.':
                        case 'S':   this.track[x][y] = Cell.ROAD; break;
                        default:    System.exit(-1);
                    }
                }
            }
        }
        public Cell atLocation(Location loc)    {
            if(loc.x < 0 || loc.x >= track.length || loc.y < 0 || loc.y >= track[0].length) return Cell.OUT_OF_BOUNDS;
            return track[loc.x][loc.y];
        }

        public String toString()    {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            PrintStream ps = new PrintStream(bos);
            for(int y = 0; y < track[0].length; y++)    {
                for(int x = 0; x < track.length; x++)   {
                    ps.append(track[x][y].toString());
                }
                ps.append('\n');
            }
            String ret = bos.toString();
            ps.close();
            return ret;
        }
    }

    public static abstract class Racer  {
        protected Velocity tdir;
        protected Location tloc;
        protected Track track;
        public Velocity direction;
        public Location location;

        public Racer(Track track, Location start)   {
            this.track = track;
            direction = new Velocity(0, 0);
            location = start;
        }
        public boolean canMove() throws GoHereDammitException {return canMove(Direction.NONE);}
        public boolean canMove(Direction d) throws GoHereDammitException    {
            tdir = new Velocity(direction);
            tloc = new Location(location);
            tdir.add(d);
            tloc.move(tdir);
            Cell at = track.atLocation(tloc);
            if(at == Cell.GOAL) {
                throw new GoHereDammitException();
            }
            return at == Cell.ROAD;
        }
        public ArrayList<Pair<Location, Direction> > getLocationsCanMoveTo()    {
            ArrayList<Pair<Location, Direction> > ret = new ArrayList<Pair<Location, Direction> >(9);
            for(Direction d: Direction.values())    {
                try {
                    if(this.canMove(d)) {
                        ret.add(new Pair<Location, Direction>(tloc, d));
                    }
                }   catch(GoHereDammitException e)  {
                    ret.clear();
                    ret.add(new Pair<Location, Direction>(tloc, d));
                    return ret;
                }
            }
            return ret;
        }
        public void move()  {move(Direction.NONE);}
        public void move(Direction d)   {
            direction.add(d);
            location.move(direction);
        }
        public boolean won()    {
            return track.atLocation(location) == Cell.GOAL;
        }
        public boolean lost()   {
            return track.atLocation(location) == Cell.WALL || track.atLocation(location) == Cell.OUT_OF_BOUNDS;
        }
        public String toString()    {
            return location + ", " + direction;
        }
        public abstract Direction think();

        public class GoHereDammitException extends Exception    {
            public GoHereDammitException()  {}
        }
    }

    public static class Location extends Point  {
        public Location(int x, int y)   {
            super(x, y);
        }
        public Location(Location l) {
            super(l);
        }
        public void move(Velocity d)    {
            this.x += d.x;
            this.y += d.y;
        }
    }

    public static class Velocity extends Point  {
        public Velocity(int x, int y)   {
            super(x, y);
        }
        public Velocity(Velocity v) {
            super(v);
        }
        public void add(Direction d)    {
            if(d == Direction.NONE) return;
            if(d == Direction.NORTH || d == Direction.NORTH_EAST || d == Direction.NORTH_WEST)  this.y--;
            if(d == Direction.SOUTH || d == Direction.SOUTH_EAST || d == Direction.SOUTH_WEST)  this.y++;
            if(d == Direction.EAST || d == Direction.NORTH_EAST || d == Direction.SOUTH_EAST)   this.x++;
            if(d == Direction.WEST || d == Direction.NORTH_WEST || d == Direction.SOUTH_WEST)   this.x--;
        }
    }

    public static class Point   {
        protected int x, y;
        protected Point(int x, int y)   {
            this.x = x;
            this.y = y;
        }
        protected Point(Point c)    {
            this.x = c.x;
            this.y = c.y;
        }
        public int getX()   {return x;}
        public int getY()   {return y;}
        public String toString()    {return "(" + x + ", " + y + ")";}
        public boolean equals(Point p)  {
            return this.x == p.x && this.y == p.y;
        }
    }

    public static class Pair<T, U>  {
        public T a;
        public U b;
        public Pair(T t, U u)   {
            a=t;b=u;
        }
    }
}

Die Ergebnisse (Bester Fall, den ich gesehen habe)

Running 'java VectorRacing' against ruby-runner/benchmark.txt

 No.    Size     Target   Score     Details
-------------------------------------------------------------------------------------
  1    37 x 1        36   0.38889   Racer reached goal at ( 36, 0) in 14 turns.
  2    38 x 1        37   0.54054   Racer reached goal at ( 37, 0) in 20 turns.
  3    33 x 1        32   0.62500   Racer reached goal at ( 32, 0) in 20 turns.
  4    10 x 10       10   0.40000   Racer reached goal at ( 9, 8) in 4 turns.
  5     9 x 6         8   0.75000   Racer reached goal at ( 6, 2) in 6 turns.
  6    15 x 7        16   2.00000   Racer did not reach the goal within 16 turns.
  7    17 x 8        16   2.00000   Racer hit a wall at position ( 8, 2).
  8    19 x 13       18   0.44444   Racer reached goal at ( 16, 2) in 8 turns.
  9    60 x 10      107   0.65421   Racer reached goal at ( 0, 6) in 70 turns.
 10    31 x 31      106   2.00000   Racer hit a wall at position ( 25, 9).
 11    31 x 31      106   2.00000   Racer hit a wall at position ( 8, 1).
 12    50 x 20       50   2.00000   Racer hit a wall at position ( 27, 14).
 13   100 x 100    2600   2.00000   Racer went out of bounds at position ( 105, 99).
 14    79 x 63      242   2.00000   Racer went out of bounds at position (-2, 26).
 15    26 x 1        25   0.32000   Racer reached goal at ( 25, 0) in 8 turns.
 16    17 x 1        19   2.00000   Racer went out of bounds at position (-2, 0).
 17    50 x 1        55   2.00000   Racer went out of bounds at position ( 53, 0).
 18    10 x 7        23   2.00000   Racer went out of bounds at position ( 10, 2).
 19    55 x 55       45   0.33333   Racer reached goal at ( 4, 49) in 15 turns.
 20    50 x 50      200   2.00000   Racer hit a wall at position ( 14, 7).
-------------------------------------------------------------------------------------
TOTAL SCORE:             26.45641
Pseudonym117
quelle
Ja, ich habe es zum Laufen gebracht, obwohl ich es aus .classirgendeinem Grund aus dem Verzeichnis ausführen musste, in dem sich die Datei befindet (anstelle des Verzeichnisses, in dem sich der Controller befindet). Pingen Sie mich an (mit einem Kommentar), wenn Sie einen Testfall hinzufügen möchten, damit ich ihn zum Benchmark hinzufügen kann. Ihre Punktzahl lag bei 33 über 10 Läufen (siehe Rangliste), dies kann sich jedoch mit jeder neuen Teststrecke ändern, die dem Benchmark hinzugefügt wird.
Martin Ender
Ah bekam es von dem anderen Verzeichnis auch zu laufen. Für diejenigen, die nicht mit Java auf der Kommandozeile vertraut sind:java -cp path/to/class/file VectorRacing
Martin Ender
Ah, ja, ich habe eine Menge Unterricht gemacht (13, um genau zu sein). Ich habe Ihr Skript immer aus meinem Klassenverzeichnis ausgeführt, also habe ich das nicht getestet. Ich kann einen Testfall machen, aber ich denke, ich werde versuchen, einen Renner zu machen, der nicht zufällig ist.
Pseudonym117
Sicher. Wenn Sie dies tun, fügen Sie dies bitte als separate Antwort hinzu. (Und zögern Sie nicht, mit jedem einen Testfall hinzuzufügen.)
Martin Ender
Leider musste ich eine andere Regel hinzufügen. Der Arbeitsspeicher scheint bei dieser Herausforderung begrenzender zu sein als die Zeit, daher musste ich den Arbeitsspeicherverbrauch schwer einschränken. Jeder Lauf, in dem Ihr Rennfahrer mehr als 1 GB Arbeitsspeicher benötigt, wird mit dem gleichen Effekt abgebrochen, wenn das Zeitlimit überschritten wird. Für den aktuellen Satz von Tracks wurde Ihre Partitur von dieser Änderung nicht beeinflusst (und wird es wahrscheinlich auch nie sein).
Martin Ender