Lab Rat Race: ćwiczenie z algorytmów genetycznych

113

To dwutygodniowe wyzwanie nr 3. Temat: Algorytmy genetyczne

To wyzwanie jest trochę eksperymentem. Chcieliśmy zobaczyć, co możemy zrobić, pod kątem wyzwań, za pomocą algorytmów genetycznych. Nie wszystko może być optymalne, ale staraliśmy się, aby było to dostępne. Jeśli to się powiedzie, kto wie, co możemy zobaczyć w przyszłości. Może genetyczny król wzgórza?

Specyfikacja jest dość długa! Próbowaliśmy podzielić specyfikację na Podstawy - absolutne minimum, które musisz wiedzieć, aby zacząć grać z frameworkiem i przesłać odpowiedź - oraz The Gory Details - pełną specyfikację ze wszystkimi szczegółami na temat kontrolera, na podstawie których mógłbym napisać własny.
Jeśli masz jakiekolwiek pytania, dołącz do nas na czacie!

Jesteś badaczem psychologii behawioralnej. Jest piątek wieczorem, a ty i twoi koledzy decydujecie się zabawić i wykorzystać swoje szczury laboratoryjne do małego wyścigu szczurów. W rzeczywistości, zanim przywiążemy się do nich zbyt emocjonalnie, nazwijmy je okazami .

Przygotowałeś mały tor wyścigowy dla okazów, a żeby był bardziej interesujący, umieściłeś na nim kilka ścian, pułapek i teleporterów. Teraz twoje okazy są nadal szczurami ... nie mają pojęcia, czym jest pułapka lub teleporter. Widzą tylko niektóre rzeczy w różnych kolorach. Nie mają też żadnej pamięci - wszystko, co mogą zrobić, to podejmować decyzje w oparciu o ich obecne otoczenie. Myślę, że dobór naturalny wyodrębni okazy, które potrafią uniknąć pułapki od tych, które tego nie robią (ten wyścig zajmie trochę czasu ...). Niech rozpocznie się gra!

Przykładowy obraz używanej płyty

† 94 465 okazów zostało poszkodowanych przy podejmowaniu tego wyzwania.

Podstawy

Jest to gra dla jednego gracza (ty i twoi koledzy nie chcieliście mieszać populacji, więc każdy zbudował własny tor wyścigowy). Tor wyścigowy jest prostokątną siatką o wysokości 15 komórek i szerokości 50 komórek. Zaczynasz z 15 okazami na losowych (niekoniecznie odrębnych) komórkach na lewej krawędzi (gdzie x = 0 ). Próbki powinny próbować osiągnąć cel, którym jest dowolna komórka przy x ≥ 49 i 0 ≤ y ≤ 14 (okazy mogą przekroczyć ścieżkę w prawo). Za każdym razem, gdy tak się dzieje, dostajesz punkt. Grę rozpoczynasz również z 1 punktem. Powinieneś spróbować zmaksymalizować swoje punkty po 10 000 tur.

Wiele próbek może zajmować tę samą komórkę i nie będzie oddziaływać.

Na każdym kroku każdy okaz widzi siatkę 5x5 swojego otoczenia (z samym sobą na środku). Każda komórka tej siatki będzie zawierała kolor -1do 15. -1reprezentuje komórki, które są poza granicami. Twój okaz umrze, jeśli wyjdzie poza granice. Jeśli chodzi o inne kolory, reprezentują puste komórki, pułapki, ściany i teleportery. Ale twój okaz nie wie, który kolor reprezentuje to, co ty, i ty też. Istnieją jednak pewne ograniczenia:

  • 8 kolorów będzie reprezentować puste komórki.
  • 4 kolory będą reprezentować teleporter. Teleporter wyśle ​​próbkę do określonej komórki w jej sąsiedztwie 9x9. To przesunięcie będzie takie samo dla wszystkich teleporterów tego samego koloru.
  • 2 kolory będą reprezentować ściany. Poruszanie się w ścianie jest tym samym, co stanie w bezruchu.
  • 2 kolory będą stanowić pułapkę. Pułapka wskazuje, że jedna z 9 komórek w jej bezpośrednim sąsiedztwie jest śmiertelna (niekoniecznie sama komórka pułapki). To przesunięcie będzie takie samo dla wszystkich pułapek tego samego koloru.

O tej naturalnej selekcji ... każdy okaz ma genom, który jest liczbą 100 bitów. Nowe okazy zostaną utworzone przez krzyżowanie dwóch istniejących okazów, a następnie nieznaczną mutację genomu. Im bardziej udany okaz, tym większa szansa na jego rozmnażanie.

Oto twoje zadanie: napiszesz jedną funkcję, która otrzymuje jako dane wejściowe siatkę kolorów 5x5, którą widzi próbka, a także jej genom. Twoja funkcja zwróci ruch (Δx, yy) dla próbki, gdzie Δx i Δy będą jednym z nich {-1, 0, 1}. Nie wolno utrwalać żadnych danych między wywołaniami funkcji. Obejmuje to używanie własnych generatorów liczb losowych. Twoja funkcja otrzyma rozstawiony RNG, z którego możesz dowolnie korzystać.

Wynik Twojego zgłoszenia będzie średnią geometryczną liczby punktów na 50 losowych ścieżkach. Stwierdziliśmy, że wynik ten jest dość zróżnicowany. Dlatego te wyniki będą wstępne . Kiedy to wyzwanie dobiegnie końca, zostanie ogłoszony termin. Po upływie terminu 100 losowo wybieranych jest 100 plansz, a wszystkie zgłoszenia zostaną zapisane na tych 100 planszach. Możesz wpisać szacunkowy wynik w odpowiedzi, ale sami ocenimy każde zgłoszenie, aby nikt nie oszukiwał.

Udostępniliśmy programy kontrolera w kilku językach. Obecnie możesz napisać swoje zgłoszenie w języku Python (2 lub 3), Ruby , C ++ , C # lub Java . Kontroler generuje plansze, uruchamia grę i zapewnia ramy dla algorytmu genetycznego. Wszystko, co musisz zrobić, to zapewnić funkcję ruchu.

Czekaj, więc co dokładnie robię z genomem?

Wyzwanie polega na zrozumieniu tego!

Ponieważ okazy nie mają pamięci, wszystko, co masz w danej turze, to siatka kolorów 5x5, które nic dla ciebie nie znaczą. Musisz więc użyć genomu, aby osiągnąć cel. Ogólna idea polega na tym, że używasz części genomu do przechowywania informacji o kolorach lub układzie siatki, a twój bot opiera swoje decyzje na dodatkowych informacjach przechowywanych w genomie.

Teraz oczywiście nie można ręcznie niczego tam przechowywać. Tak więc przechowywane tam rzeczywiste informacje będą początkowo całkowicie losowe. Ale algorytm genetyczny wkrótce wybierze te okazy, których genom zawiera właściwą informację, jednocześnie zabijając te, które mają niewłaściwą informację. Twoim celem jest znalezienie mapowania z fragmentów genomu i pola widzenia na ruch, który pozwala szybko znaleźć ścieżkę do celu i który konsekwentnie ewoluuje do zwycięskiej strategii.

To powinno wystarczyć do rozpoczęcia pracy. Jeśli chcesz, możesz pominąć następną sekcję i wybrać kontroler z listy kontrolerów u dołu (która zawiera również informacje o tym, jak używać tego konkretnego kontrolera).

Czytaj dalej, jeśli chcesz wszystko ...

Krwawe szczegóły

Ta specyfikacja jest kompletna. Wszyscy administratorzy muszą wdrożyć te reguły.

Wszelka losowość wykorzystuje rozkład równomierny, chyba że zaznaczono inaczej.

Generowanie torów:

  • Ścieżka ma prostokątną siatkę, szerokość X = 53 komórki i wysokość Y = 15 komórek. Komórki zx ≥ 49komórkami docelowymi (gdzie x jest zerowy).
  • Każda komórka ma jeden kolor i może, ale nie musi, być śmiertelna - komórki nie są śmiertelne, chyba że określi je jeden z poniższych typów komórek.
  • Istnieje 16 różnych kolorów komórek, oznaczonych od 0do 15, których znaczenie zmieni się z gry na grę. Ponadto -1reprezentuje komórki, które są poza granicami - są śmiertelne .
  • Wybierz 8 losowych kolorów . Będą to puste komórki (które nie działają).
  • Wybierz 4 więcej losowych kolorów . To są teleportery. Dla dwóch z tych kolorów wybierz niezerowe przesunięcie w sąsiedztwie 9x9 (od (-4, -4) do (4,4) z wyjątkiem (0,0)). Dla pozostałych dwóch kolorów odwróć te przesunięcia. Jeżeli próbka nadepnie na teleporter, zostanie natychmiast przesunięta o to przesunięcie.
  • Wybierz jeszcze 2 losowe kolory . To są pułapki. Dla każdego z tych kolorów wybierz przesunięcie w sąsiedztwie 3x3 (od (-1, -1) do (1,1)). Pułapka wskazuje, że komórka w tym przesunięciu jest śmiertelna . Uwaga: Sama komórka pułapkowa niekoniecznie jest śmiertelna.
  • Do 2 Pozostałe kolory są ściany, które utrudniają ruch. Próba przejścia na komórkę ścienną zmieni ruch w nieruchomy. Same komórki ścienne są śmiertelne .
  • Dla każdej komórki niebędącej celem siatki wybierz losowy kolor. Dla każdej komórki celu wybierz losowy pusty kolor.
  • Dla każdej komórki na lewej krawędzi toru ustal, czy cel można osiągnąć w ciągu 100 obrotów (zgodnie z poniższymi zasadami kolejności zwrotów ). Jeśli tak, ta komórka jest dopuszczalną komórką początkową . Jeśli jest mniej niż 10 komórek początkowych, odrzuć ścieżkę i wygeneruj nową.
  • Utwórz 15 okazów, każdy z losowym genomem i wieku 0 . Umieść każdą próbkę w losowej początkowej komórce.

Kolejność:

  1. Dla każdej próbki zostaną wykonane kolejno następujące kroki. Okazy nie wchodzą w interakcje ani nie widzą się nawzajem i mogą zajmować tę samą komórkę.
    1. Jeśli wiek okazu wynosi 100 , umiera. W przeciwnym razie zwiększ jego wiek o 1.
    2. Próbka otrzymuje swoje pole widzenia - siatkę kolorów 5x5, wyśrodkowaną na próbce - i zwraca ruch w sąsiedztwie 3x3. Przesunięcie poza ten zakres spowoduje zakończenie pracy sterownika.
    3. Jeśli komórką docelową jest ściana, ruch zmienia się na (0,0).
    4. Jeśli komórką docelową jest teleporter, próbka zostaje przesunięta o przesunięcie teleportera. Uwaga: Ten krok jest wykonywany raz , a nie iteracyjnie.
    5. Jeśli komórka zajmowana obecnie przez próbkę (potencjalnie po użyciu jednego teleportera) jest śmiertelna, próbka umiera. Jest to jedyny czas, w którym próbki umierają (oprócz kroku 1.1. Powyżej). W szczególności nowy okaz, który odradza się w śmiertelnej komórce, nie umrze natychmiast, ale ma szansę na pierwsze zejście z niebezpiecznej komórki.
    6. Jeśli próbka zajmuje komórkę celu, zdobądź punkt, przenieś próbkę do losowej komórki początkowej i zresetuj jej wiek do 0.
  2. Jeśli na planszy pozostały mniej niż dwa okazy, gra się kończy.
  3. Utwórz 10 nowych próbek w wieku 0 lat . Każdy genom jest określany (indywidualnie) na podstawie poniższych reguł hodowlanych. Umieść każdą próbkę w losowej początkowej komórce.

Hodowla:

  • Po utworzeniu nowego okazu wybierz losowo dwóch różnych rodziców, z nastawieniem na osobniki, które posunęły się dalej w prawo. Prawdopodobieństwo wyboru próbki jest proporcjonalne do jej aktualnej oceny kondycji . Ocena kondycji okazu wynosi

    1 + x + 50 * ile razy osiągnął cel

    gdzie x jest wskaźnikiem poziomym opartym na 0. Okazy utworzone w tej samej turze nie mogą być wybrane jako rodzice.

  • Z dwojga rodziców wybierz losowego, z którego zostanie pobrany pierwszy bit genomu.

  • Teraz, gdy idziesz wzdłuż genomu, przełączaj rodziców z prawdopodobieństwem 0,05 i nadal pobieraj kawałki od wynikowego rodzica.
  • Zmutuj w pełni złożony genom: dla każdego bitu odwróć go z prawdopodobieństwem 0,01 .

Punktacja:

  • Jedna gra trwa 10 000 tur.
  • Gracze rozpoczynają grę z 1 punktem (aby umożliwić użycie średniej geometrycznej).
  • Za każdym razem, gdy okaz osiąga cel, gracz zdobywa punkt.
  • Na razie zgłoszenie każdego gracza zostanie uruchomione na 50 gier, każda z inną losową ścieżką.
  • Powyższe podejście powoduje większą wariancję niż jest to pożądane. Kiedy to wyzwanie dobiegnie końca, zostanie ogłoszony termin. Po upływie terminu 100 losowo wybieranych jest 100 plansz, a wszystkie zgłoszenia zostaną zapisane na tych 100 planszach.
  • Ogólny wynik gracza jest średnią geometryczną wyników poszczególnych gier.

Kontrolery

Możesz wybrać jeden z następujących kontrolerów (ponieważ są one funkcjonalnie równoważne). Przetestowaliśmy je wszystkie, ale jeśli zauważysz błąd, chcesz poprawić kod lub wydajność, lub dodasz funkcję graficzną, wyślij zgłoszenie problemu lub wyślij żądanie ściągnięcia na GitHub! Możesz również dodać nowy kontroler w innym języku!

Kliknij nazwę języka dla każdego kontrolera, aby przejść do właściwego katalogu na GitHub, który zawiera README.mddokładne instrukcje użytkowania.

Jeśli nie znasz git i / lub GitHub, możesz pobrać całe repozytorium jako plik ZIP ze strony głównej (patrz przycisk na pasku bocznym).

Pyton

  • Najbardziej dokładnie przetestowany. To jest nasza referencyjna implementacja.
  • Działa zarówno z Python 2.6+, jak i Python 3.2+!
  • Jest bardzo wolny. Zalecamy korzystanie z PyPy w celu znacznego przyspieszenia.
  • Obsługuje wyjście graficznego korzystając albo pygamealbo tkinter.

Rubin

  • Testowane z Ruby 2.0.0. Powinien działać z nowszymi wersjami.
  • Jest również dość powolny, ale Ruby może być dogodny do prototypowania pomysłu na przesłanie.

C ++

  • Wymaga C ++ 11.
  • Opcjonalnie obsługuje wielowątkowość.
  • Zdecydowanie najszybszy kontroler w grupie.

DO#

  • Używa LINQ, więc wymaga .NET 3.5.
  • Raczej powolny.

Jawa

  • Niezbyt wolno. Niezbyt szybko.

Wstępna tablica wyników

Wszystkie wyniki są wstępne. Jeśli jednak coś jest nie tak lub nieaktualne, daj mi znać. Nasze przykładowe przesłanie jest wymienione w celach porównawczych, ale nie jest niezgodne.

  Score   | # Games | User               | Language   | Bot           
===================================================================================
2914.13   |   2000  | kuroi neko         | C++        | Hard Believers
1817.05097|   1000  | TheBestOne         | Java       | Running Star
1009.72   |   2000  | kuroi neko         | C++        | Blind faith
 782.18   |   2000  | MT0                | C++        | Cautious Specimens
 428.38   |         | user2487951        | Python     | NeighborsOfNeighbors
 145.35   |   2000  | Wouter ibens       | C++        | Triple Score
 133.2    |         | Anton              | C++        | StarPlayer
 122.92   |         | Dominik Müller     | Python     | SkyWalker
  89.90   |         | aschmack           | C++        | LookAheadPlayer
  74.7    |         | bitpwner           | C++        | ColorFarSeeker
  70.98   |   2000  | Ceribia            | C++        | WallGuesser
  50.35   |         | feersum            | C++        | Run-Bonus Player
  35.85   |         | Zgarb              | C++        | Pathfinder
 (34.45)  |   5000  | Martin Büttner     | <all>      | ColorScorePlayer
   9.77   |         | DenDenDo           | C++        | SlowAndSteady
   3.7    |         | flawr              | Java       | IAmARobotPlayer
   1.9    |         | trichoplax         | Python     | Bishop
   1.04   |   2000  | fluffy             | C++        | Gray-Color Lookahead

Kredyty

To wyzwanie było ogromnym wysiłkiem polegającym na współpracy:

  • Nathan Merril: Napisałem kontrolery Python i Java. Przekształcił koncepcję wyzwania z King-of-the-Hill w wyścig szczurów.
  • trichoplax: Playtesting . Pracował na kontrolerze Python.
  • feersum: Napisałem kontroler C ++.
  • VisualMelon: Napisałem kontroler C #.
  • Martin Büttner: Koncepcja. Napisałem kontroler Ruby. Testowanie Pracował na kontrolerze Python.
  • T Abraham: Playtesting. Przetestowałem Python i przejrzałem kontrolery C # i C ++.

Wszyscy powyżsi użytkownicy (i pewnie jeszcze kilku zapomniałem) przyczynili się do ogólnej konstrukcji wyzwania.

Aktualizacja kontrolera C ++

Jeśli używasz C ++ z Visual Studio i wielowątkowością, powinieneś otrzymać najnowszą aktualizację z powodu błędu w ich generowaniu liczb losowych, który pozwala na tworzenie duplikatów kart.

Martin Ender
źródło
3
Czy ktoś nie mógłby po prostu stworzyć genetycznego algorytmu genetycznego, aby znaleźć optymalny algorytm genetyczny dla tego problemu?
mbomb007
1
@ anon3202 Cóż, to oczywiście dałoby ci więcej informacji na temat układu toru, ponieważ możesz ocenić, gdzie jesteś. Zasadniczo chcieliśmy, aby interfejs dla botów był prosty i uczynić go czysto lokalnym problemem, w którym będziesz potrzebował genomu, aby dowiedzieć się, które lokalne rozwiązanie jest najbardziej korzystne dla twojego globalnego postępu.
Martin Ender
1
@matovitch Patrz sekcja 5 sekcji Zakręt w szczegółach Gory (pełna specyfikacja):'In particular, a new specimen which spawns on a lethal cell will not die immediately, but has a chance to move off the dangerous cell first.'
trichoplax
1
Poprawiłem kod C ++, aby pokazać średnią próbkę, stddev, stderr i 99% interwał konf (przed twoim „geometrycznym” log / exp) i dokonałem zaskakującego odkrycia. Odpowiedź „Ślepa wiara” zawierała „Średnia próbki 116529 + - 2,78337e + 010 (99%) stddev = 7,77951e + 010” po 50 cyklach. ”Zmniejszenie przedziału ufności do 50% wcale nie poprawia w znacznym stopniu spraw. Średnia geometryczna była jednak stabilna: „Średnia z 159,458 + - 117262 (99%) stddev = 32,6237” (Przed aktualizacją 800 punktów)
Kaczka Mooing
1
Zrobiłem eksperyment z częstością mutacji i myślę, że wyzwanie byłoby bardziej interesujące (a kontrolery działałyby znacznie szybciej), gdyby prawdopodobieństwo wzrosło z 0,01 do 0,0227, co daje DNA tylko 10% szans na przejście mutacja bez zmian zamiast 37% przy aktualnej wartości. Pozwala to uniknąć śmiesznych eksplozji populacji (co z kolei oszczędza dużo czasu obliczeń) i zapobiega wielu awariom z powodu niewystarczającej różnorodności. Indywidualne wyniki są niższe, ale ponieważ więcej serii przynosi zwycięzców, średnia globalna zwykle rośnie.

Odpowiedzi:

37

Ślepa wiara - C ++ - wydaje się osiągać ponad 800 (!) W 2000 rund

Genom kodujący kolory z tajemniczym sprzężeniem zwrotnym śladu i skutecznym środkiem odstraszającym uderzenia w ścianę

#include "./gamelogic.cpp"

#define NUM_COLORS 16

// color meanings for our rats
typedef enum { good, bad, trap } colorType_t;
struct colorInfo_t {
    colorType_t type;
    coord_t offset; // trap relative location
    colorInfo_t() : type(good) {} // our rats are born optimists
};

// all 8 possible neighbours, carefully ordered
coord_t moves_up  [] = { { 1, 0 }, { 1,  1 }, { 1, -1 }, { 0,  1 }, { 0, -1 }, { -1, 0 }, { -1,  1 }, { -1, -1 } };  // toward the goal, going up   first
coord_t moves_down[] = { { 1, 0 }, { 1, -1 }, { 1,  1 }, { 0, -1 }, { 0,  1 }, { -1, 0 }, { -1, -1 }, { -1,  1 } };  // toward the goal, going down first

// map of the surroundings
struct map_t {
    static const size_t size = 5;
    static const int max = size / 2;
    static const int min = -max;
    colorType_t map[size*size];
    colorType_t & operator() (int x, int y) { return map[(x + max)*size + y + max]; }
    colorType_t & operator() (coord_t pos) { return operator()(pos.x, pos.y); }
    bool is_inside(int x, int y) { return abs(x) <= max && abs(y) <= max; }
    bool is_inside(coord_t pos) { return is_inside(pos.x,pos.y); }
};

// trap mapping info
struct trap_t {
    coord_t detector;
    colorInfo_t color;
    trap_t(int x, int y, colorInfo_t & color) : color(color) { detector.x = x; detector.y = y; }
    trap_t() {}
};

coord_t blindFaith(dna_t d, view_t v)
{
    colorInfo_t color[NUM_COLORS]; // color informations

    // decode colors
    for (size_t c = 0; c != 16; c++)
    {
        size_t base = c * 4;
        if (d[base])
        {
            color[c].type = d[base+1] ? good : bad;
        }
        else // decode trap location
        {
            color[c].type = trap;
            int offset = d[base+1] + 2 * d[base+2] + 4 * d[base+3];
            color[c].offset = moves_up[offset]; // the order is irrelevant as long as all 8 neighbours are listed
        }
    }

    // build a map of the surrounding cells
    map_t map;
    unsigned signature = 0;
    int i = 0;
    for (int x = map.min; x <= map.max; x++)
    for (int y = map.min; y <= map.max; y++)
    {
        int c = v(x, y);
        map(x, y) = (c == -1) ? bad : color[c].type;
        if (c != -1) signature ^= v(x, y) << ((i++) % 28);
    }

    // map traps
    for (int x = map.min; x <= map.max; x++)
    for (int y = map.min; y <= map.max; y++)
    {
        if (map(x, y) != trap) continue;
        const colorInfo_t & trap = color[v(x, y)];
        int bad_x = x + trap.offset.x;
        int bad_y = y + trap.offset.y;
        if (!map.is_inside(bad_x, bad_y)) continue;
        map(bad_x, bad_y) = bad;
        map(x, y) = good;
    }

    // pick a vertical direction according to surroundings signature
    int go_up = d[64 + signature % (DNA_BITS - 64)];

    // try to move to a good cell nearer the goal
    for (const coord_t &move : go_up ? moves_up : moves_down) if (map(move.x, move.y) == good) return move;

    // try not to increase fitness of this intellectually impaired specimen
    return{ -1, 0 };
}

int main() {
    time_t start = time(NULL);
    double score = runsimulation(blindFaith);
    slog << "Geometric mean score: " << score << " in " << time(NULL) - start << " seconds";
}

Przykładowe wyniki:

Scores: 15 4113306 190703 1 1 44629 118172 43594 63023 2 4 1 1 205027 1 455951 4194047 1 5 279 1 3863570 616483 17797 42584 1 37442 1 37 1 432545 5 94335 1 1 187036 1 4233379 1561445 1 1 1 1 35246 1 150154 1 1 1 1 90141 6 1 1 1 26849 1 161903 4 123972 1 55 988 7042063 694 4711342 90514 3726251 2 1 383389 1 593029 12088 1 149779 69144 21218 290963 17829 1072904 368771 84 872958 30456 133784 4843896 1 2 37 381780 14 540066 3046713 12 5 1 92181 5174 1 156292 13 1 1 29940 66678 125975 52714 1 5 3 1 101267 69003 1 1 10231 143110 282328 4 71750 324545 25 1 22 102414 1 3884626 4 28202 64057 1 1 1 1 70707 4078970 1623071 5047 1 1 549040 1 1 66 3520283 1 6035495 1 79773 1 1 1 218408 1 1 15 33 589875 310455 112274 1 1 4 1 3716220 14 180123 1 2 12785 113116 12 2 1 59286 822912 2244520 1840950 147151 1255115 1 49 2 182262 109717 2 9 1049697 59297 1 11 64568 1 57093 52588 63990 331081 54110 1 1 1537 3 38043 1514692 360087 1 260395 19557 3583536 1 4 152302 2636569 12 1 105991 374793 14 3934727 1 2 182614 1 1675472 121949 11 5 283271 207686 175468 1 1 173240 1 138778 1 1 59964 3290382 1 4 1757946 1 23520 1 2 94 1 124577 497071 1749760 39238 1 301144 3 1 2871836 1 1 10486 1 11 8 1 111421 11 1807900 1 587479 1 42725 116006 3 1 6 5441895 1 1 22 52465 952 1 18 1 1 46878 2 1 1 1994 4 593858 123513 4692516 820868 4247357 1 1 2 1 2 8770 2 1 95371 4897243 2 22741 1 1 1 1 325142 6 33650 4 51 102993 1 182664 1 4040608 18153 2045673 462339 1 1 617575 2 2551800 3 7760 1 108012 76167 143362 1148457 1 53460 1 71503 1 1 1 1 81482 3208 62286 69 139 1 3503941 1 253624 101903 3081954 80123 84701 9 16 1 1070688 71604 613064 2076 15009 9 1 1 1 199731 1 2 1 63132 1 1843855 27808 1 3569689 273144 1 460524 2703719 22443 10876 51242 1 6972678 4591939 1 140506 43981 45076 2 1 91301 5 1 1874615 1758284 608 13 1 96545 75161 1 618144 4 2056133 1 1 2 57401 1394307 6 188116 83545 1 41883 1 1 467189 371722 1 1122993 1 17912 159499 1 5 3355398 33 1 2 246304 1 2 168349 1 50292 12 141492 2723076 3 1 6 3060433 223360 171472 106409 1 2 1 102729 8814 1 285154 1 11 1 65 930 2 689644 3271116 1 5 4 60 77447 1 1 1477538 256023 100403 2480335 1 39888 1 1 70052 66090 1 250 1 2 8 115371 1523106 1424 168148 1 1 1 42938 17 1 364285 185080 1 1 36 4903764 13 51987 1106 276212 67460 1 251257 2 6867732 1 1 1890073 1 1 8 5 2118932 210 0 3792346 5209168 1 1 1 1 51 1 4621148 1 37 337073 3506096 1 1 1 1 458964 2 16 52930 1 15375 267685 1 1 1259646 14930 3248678 527105 1 103 24 1 3252685 6009 1 1 176340 3971529 121 1722808 1 31483 194232 2314706 95952 3625407 3 216755 56 1 8 1 1 1 1 885 229 9056 172027 31516 2526805 1 76076 1589061 1 1 8 90812 1 21 72036 1681271 2 212431 1581814 85993 79967 4 7 514708 1070070 1 71698 1 23478 15882 94453 1 27382 495493 277308 12127 91928 248593 1 1 1 26540 1709344 2119856 1 1 48867 107017 251374 64041 15924 15 87474 8 1 23 9 48 1 1 1 51793 2 61029 84803 15 689851 1 1 873503 10 140084 420034 87087 82223 1 163273 12 1 5 570463 19 26665 1 170311 1 39983 1 475306 1 2 36417 746105 11 141345 1 3 1 30 3 1 1 1 1 1312289 408117 1 42210 273871 561592 1 1 1 1 4448568 48448 7 378508 1 351858 278331 1 79515 1169309 3670107 14711 4686395 1156554 33 2528441 24537 76 335390 63545 122108 76675 21929 34 1 861361 83000 417781 1 90487 1 1 85116 7 2 1 60129 647991 79 1 2755780 726845 244217 50007 187212 1 3674051 286071 44068 3 307427 26973 1 26059 1957457 230783 58102 545318 1 4 172542 168365 1 89402 1 4 1 1 1 1 2 3 16 62935 5643183 117961 109942 85762 5 117376 118883 1 61 23893 122536 70185 1 64252 208409 179269 55381 1579240 3434491 1 4964284 3356245 3 21 2197119 346542 44340 59976 772220 5590844 199721 90858 63785 125989 57219 129737 81836 1 3671 16810 1 4151040 1 15 40108 1 443679 3224921 2 27498 2 3 146529 169409 19 1 1 1 1 41627 1 3 2722438 1 2013730 1 1649406 1 1 6943 125772 58652 1 1 1 2413522 1 2 48 36067 253807 2 146464 1 248 07 3359223 139896 395985 65241 43988 594638 69033 275085 1 17973 1 1 1 594835 1 1 4468341 3496274 222854 94769 55 161056 36185 8793 277592 3 1 6746 1 138151 66 37365 1 2729315 1 3 57091 22408 249875 246514 85058 1 20 5463152 1 3 1 45293 1 70488 2792458 461 441 951926 2236205 2 171980 1 1 48 3893009 1 458077 1 268203 1 70005 7 19299 1 278978 1 45286 26 2 1883506 274393 342679 1 1 913722 911600 12688 1 1 115020 1249307 1529878 53426 1 226862 3721440 23537 86033 397433 1 1 1 161423 96343 94496 1 1 1 2 1 111576 1 4039782 1 1 1 5742393 3569 46072 1 1 2 1 1 85335 219988 1 78871 115876 43405 1 300835 1 166684 53134 1 3 111487 6 3 3 77 1 115971 3 205782 10 1932578 356857 43258 47998 1 27648 127096 573939 32650 523906 45193 1 2 128992 1 10144 1 257941 1 19841 5077836 14670 5 3 6 1 1 21 14651 2906084 37942 45032 9 304192 3035905 6214026 2 177952 1 51338 1 65594 46426 553875 2676479 245774 95881 3 216364 3064811 1198509 223982 3 6 1 533254 1 590363 264940 68346 127284 1 7 1 1 4617874 5 45400 1 1 3097950 360274 1 3 1 8421 14 469681 418563 3 1 6 1 1 575766 405239 11 2631108 152667 1 1 1 467383 1 1 775499 1 157998 2 1 143351 92021 1 1 1173046 3636579 1 70635 162303 1 1534876 834682 2 1 1 11981 346908 245124 607794 17 1570641 126995 13 57050 1 2 33731 29739 1 1 35460 1 33716 168190 214704 1 443156 701674 2636870 108081 1604895 1 1 11 115901 23 571891 360680 1 1 35 1 2036975 1 1 2555536 4742615 5 360553 287044 1 1814255 7 59632 1 216 41546 1 540920 353424 2625301 223744 1 1 1 15717 3 429871 1 4 2329632 18 11 1 2 4 1 3905 5 1 1 1 2 5431442 1 859628 1 3 338378 15236 13764 1 3384362 1 15 65293 24 619599 152620 2 189921 35854 16647 7 2 404790 360096 1 2 189459 1097768 191610 1 1 470254 1 12 2 330299 364219 2365542 312023 2273374 2 10527 1 115453 1 2 3845592 52388 913449 1 14695 1 44 37352 90302 1 1 1 233577 51639 3474983 44010 1650727 31 2 2 1 8 7 1 3 5 25603 17799 45630 758457 1 4571839 37 4 3 2 1 1 1351271 196673 12 2880765 263886 2926173 1 2 1 241502 5 6 1 278576 9 7 290722 42749 143391 82753 21771 57887 1 1 60400 1766903 1 296392 1 5 2861787 125560 1 9 199218 1 1 308226 517598 2246753 12 1168981 3 98447 1 488613 9 842865 202108 10 1 238493 1 1523706 5383982 29435 1 1 207071 1 8 4 125742 70531 253135 72207 124291 23364 184376 2 40034 9569353 194109 102854 2 3247153 58313 85995 1 598 63 1 2676692 10 3573233 1 36651 118016 2486962 65456 46760 1 5813 723 178120 2 153305 1 1 2 1 2354413 3 1 17126 132953 437123 299778 3070490 1 6490 403704 2261 511439 1 39 33410 173045 1 1 120970 641346 132042 1 44906 1 33940 132124 467702 45472 9 44 1 1 1 107008 1 46635 1 121431 130760 1 7 3 1 56251 1299306 3 1 1 1 15 2147678 215169 1374943 1 332995 231089 269310 1 7816944 1 1 1 46 134426 1 1 1 2 76112 1 1 30438 299927 25 139373 76048 278757 71 3474997 1 294046 1 3126554 2518019 2 1 6 1 3054393 1 1 1 2 525 96 419528 1 1 154718 233 207879 26 1 6 57436 3 5944942 1 1 318198 147536 1 22 420557 1 1 120938 1 1 167412 4082969 73299 1 11 3557361 1 4 330028 269051 1 2569546 2 1 1 4 1 1 377412 1 1 1 213800 58131 1422177 54 109617 117751 12432 3830664 419046 3 6821 741 919 1 22335 1 1 15069 80694 488809 2389 2308679 145548 51411 115786 110984 107713 1 12 6 1 5 8365 1 2001874 210250 4674015 14 1 1204101 314354 89066 1 1 2438200 68350 1 1575329 5593838 2743787 151670 57 16 5948210 597158 128060 189160 23628 1 1 15 4171774 1 8206 4157492 1 2 315607 1618680 24736 18520 4787225 33842 134431 1 1 1 1 1 1115809 17759 1 33016 123117 1 77322 169633 219091 1 321593 57231 135536 175401 4 1 435702 1 253132 100707 114547 1 119324 6382967 1472898 3 72567 1707408 177958 26 208719 1 27083 74 12 576410 19375 177069 4 3 1 31 507048 2 1 1 2 1 2 1 40 7 99892 95202 60649 241396 232370 1 136579 70649 1 2877 280695 13603 102860 404583 29717 112769 1 54089 1 97579 40819 2 868629 64848 2 63432 5 1 1888426 99623 2 1 7911 53646 3047637 1 2 3 152910 1 3244662 105187 1 1 1 1 8966 200347 1 1 22 302654 6 17 1 10 328150 55259 1016 117291 2 1 224524 23846 74645 1 1 1 1 1 3117394 10847 33976 144613 4 201584 1 1 26959 3 4410588 27019 6 66749 55935 23 4126812 4089989 99959 1 1 1 1 55490 1 4275599 13652 33967 2 8126062 337093 320653 128015 4 1 7729132 1 10594 116651 20990 3046630 1 353731 132989 2066431 4 80 15575 147430 1 621461 3100943 2306122 5 33439 407945 25634 1 2911806 32511 2174235 298281 15159 54125 1 2 3063577 2205013 1 407984 1 319713 1 22171 1 2763843 1 2607606 1 100015 3096036 1 55905 1 1 635265 2890760 1 1 1 1 35854 1 352022 2652014 1 2 274366 1 4 1 602980 4 83828 602270 2816 2 59116 25340 1 11 1 5162051 34 8 218372 1186732 142966 1 1 170557 503302 1 84924 5 1 1350329 1 1 1 130273 78055 902762 1 8581 5 1 3635882 1 1 1 224255 44044 61250 2 438453 8 1 2729357 28 1 17658 82640 1 31809 10 1 33 1 1 45495 5798 5000217 40018 588787 67269 1 12 83512 2798339 1 609271 1 3 1 7 67912 189808 3388775 60961 81311 1167 24939 433791 405306 85934 1 1170651 2 1 66 552579 122985 515363 2188340 1 1 1 3807012 1502582 4 13 149593 1 1 2108196 3 34279 24613 1282047 27 1 2 1 1 584435 27487 1 1 5 33278 1 1 1202843 1 1 1 6 3649820 3100 2 266150 13 164117 10 53163 3295075 1 1 1 1 77890 1 286220 90823 18866 3139039 481826 1 3994676 23 116901 132290 6 3927 84948 1 1 1 1 256310 1 11 8 1 102002 8392 887732 98483 444991 1 1 49408 409967 1158979 1 1 1 81469 189764 3960930 296231 64258 1 1 176030 4 1 2 1 486856 1 1135146 31 2 13112 227077 31
Geometric mean score: 831.185 in 14820 seconds

Na podstawie nieludzko długiego testu Feersum, uważam, że 2000 przebiegów wystarczy, aby uzyskać akceptowalnie stabilny wynik.
Ponieważ mój zmodyfikowany kontroler wyświetla bieżącą średnią geometryczną po każdym przebiegu, wizualnie potwierdziłem, że zmiana w ciągu ostatnich 50 przebiegów była stosunkowo niewielka (+ - 10 punktów).

Co sprawia, że ​​te zwierzątka tykają

Zamiast nadawać równe priorytety każdemu kolorowi, rozważam te możliwe wartości:

  1. dobrze -> szczur myśli, że może tam bezpiecznie iść
  2. źle -> szczur tam nie pójdzie
  3. pułapka -> szczur uzna położenie pułapki za złe, a komórka wskazująca pułapkę będzie dobra .
    Chociaż jestem zbyt leniwy, aby zmienić jego nazwę, to raczej „wykrywacz niebezpieczeństwa” wskazujący (rzekomą) lokalizację rzeczywistej pułapki, ściany, teleportera czekającego na wysłanie niczego nie podejrzewającego wędrowca w nieprzyjemne miejsce, a nawet wejścia do martwego -koniec. Krótko mówiąc, miejsce, do którego mądry szczur raczej by nie poszedł.

dobre lub złe geny przechowują tylko 2 bity (na przykład 11i 10), ale pułapki wymagają 4 bitów ( 0tttgdzie tttreprezentuje jedną z 8 możliwych „niebezpiecznych” lokalizacji).

Aby zachować spójność każdego genu (tj. Zachować jego znaczenie po zmieszaniu z całkowicie innym genomem, co wymaga, aby każdy gen kodujący kolor znajdował się w ustalonej lokalizacji), wszystkie wartości są kodowane na 4 bitach (tak dobre są kodowane jako złe,11xx a złe jako 10xx), w sumie 16 * 4 = 64 bity.

Pozostałe 36 bitów jest używanych jako „anti-wall-banger” (więcej na ten temat później). 25 otaczających kolorów jest mieszanych w indeksie tych 36 bitów. Każdy bit wskazuje preferowany kierunek pionowy (w górę lub w dół), który jest używany, gdy istnieje możliwość wyboru między dwiema komórkami.

Strategia jest następująca:

  • dekoduj każdy kolor zgodnie z genomem (lub bezpośredni raport kontrolera dla „złych” komórek poza ścieżką)
  • zbuduj mapę najbliższego otoczenia (komórki 3x3, 8 możliwych sąsiadów)
  • obliczyć sygnaturę otoczenia (skrót 25 kolorów z wyjątkiem komórek off-track)
  • wybierz preferowany kierunek pionowy z podpisu (spośród 36 wiader mieszających)
  • spróbuj przejść do sąsiada oznaczonego jako „dobry”, zaczynając od tych najbliższych bramki i kierując się najpierw w preferowanym kierunku pionowym
  • jeśli nie można znaleźć „dobrego” sąsiada, spróbuj przenieść jedną komórkę do tyłu (prawdopodobnie będąc ofiarą nieszczęśliwego wypadku i unikając w każdym razie zwiększenia sprawności)

Wy, gryzonie, patrzcie na wrogów waszych

przerażająca ściana pętla teleportacyjna

Najgorsze, co może spotkać populację, to nie dać jeszcze zwycięzcy, ale wiele szczurów utknęło albo pod ścianą, albo w nieskończonej pętli teleportacyjnej wystarczająco blisko celu, aby mieć dominującą szansę na selekcję do hodowli .
W przeciwieństwie do szczurów zgniatanych w pułapkę lub teleportowanych do ścian, gryzonie te zostaną zabite tylko do starości.
Nie mają przewagi konkurencyjnej nad kuzynami, którzy od początku utknęli w 3 komórkach, ale będą mieli wystarczająco dużo czasu na rozmnażanie pokolenia za pokoleniem kretyn, aż ich genom stanie się dominujący, szkodząc w ten sposób różnorodności genetycznej bez uzasadnionego powodu.

Aby złagodzić to zjawisko, chodzi o to, aby potomstwo tych złych, złych szczurów unikało podążania śladami ich przodków.
Wskazanie kierunku pionowego ma tylko 1 bit (mówiąc w zasadzie „spróbuj najpierw wejść w górę lub w dół w tych okolicach”), a całkiem sporo bitów prawdopodobnie będzie miało wpływ na ścieżkę, więc mutacje i / lub zwrotnice powinny mieć Znaczący wpływ.
Wiele potomstwa będzie miało inne zachowanie i nie będzie w końcu uderzać głową w tę samą ścianę (wśród zwłok ich głodujących przodków).
Subtelność polega na tym, że to wskazanie nie jest dominującym czynnikiem w zachowaniu szczura. W większości przypadków nadal dominować będzie interpretacja kolorów (wybór góra / dół będzie miał znaczenie tylko wtedy, gdy rzeczywiście są dwa „dobre”a to, co szczur uważa za nieszkodliwy kolor, nie jest teleporterem czekającym na wrzucenie go do ściany).

Dlaczego to (wydaje się) działa?

Nadal nie wiem dokładnie dlaczego.

Absolutnym ciosem szczęścia, który pozostaje nierozwiązaną tajemnicą, jest logika mapowania pułapek. Bez wątpienia jest kamieniem węgielnym sukcesu, ale działa na swój tajemniczy sposób.

Przy stosowanym kodowaniu losowy genom wytworzy 25% „dobrych”, 25% „złych” i 50% „pułapkowych” identyfikatorów kolorów.
identyfikatory „pułapki” z kolei generują „dobre” i „złe” oszacowania w korelacji z otoczeniem 5x5.
W rezultacie szczur w danym miejscu „zobaczy” świat jako mieszankę stabilnych i kontekstowych kolorów „iść / nie iść”.

Jak się wydaje dość skuteczny mechanizm przeciwdziałający uderzeniom, najgorszym elementem na torze jest przerażająca ściana (i jej kuzyn pętla teleportacyjna, ale myślę, że są one znacznie mniej powszechne).

Wniosek jest taki, że udany program musi przede wszystkim ewoluować szczurom zdolnym do wykrycia pozycji, które doprowadzą do powolnego głodu bez osiągnięcia celu.

Nawet bez „odgadnięcia” dwóch kolorów reprezentujących ściany, kolory „pułapki” wydają się przyczyniać do unikania ścian, umożliwiając szczurowi omijanie kilku przeszkód nie dlatego, że „widział” ściany, ale dlatego, że oszacowanie „pułapki” wykluczyło te konkretne komórki ścienne w tym konkretnym otoczeniu.

Chociaż szczur próbuje zbliżyć się do celu (co może prowadzić do przekonania, że ​​najbardziej „przydatne” wskaźniki pułapek wskazują na niebezpieczeństwo z przodu), myślę, że wszystkie kierunki pułapek mają w przybliżeniu taki sam wpływ: pułapka wskazująca „niebezpieczeństwo za sobą” „umieszczone 2 komórki przed szczurem mają taki sam wpływ, jak ten, który wskazuje„ niebezpieczeństwo przed sobą ”, gdy szczur stoi tuż nad nim.

Dlaczego ta mieszanka ma tę właściwość, że genom tak skutecznie się zbiega, jest niestety daleko poza moimi matematykami.

Czuję się lepiej z odstraszającym uderzeniem w ścianę. Po prostu zadziałało to zgodnie z planem, ale znacznie powyżej moich oczekiwań (wynik został w zasadzie pomnożony przez cztery).

Mocno zhakowałem kontroler, aby wyświetlić niektóre dane. Oto kilka przebiegów:

Turns:2499 best rat B  B  B  G  B  G  T3 G  T4 B  G  B  B  B  G  G  ^vv^^vv^v^v^^^vvv^v^^^v^^^v^vv^^v^^^ Max fitness: 790 Specimens: 1217 Score: 2800
Turns:4999 best rat B  B  B  G  B  G  T3 G  T4 B  G  B  B  B  G  G  ^vv^^vv^v^v^^^vvv^v^^^v^^^v^vv^^v^^^ Max fitness: 5217 Specimens: 15857 Score: 685986
Turns:7499 best rat B  B  B  G  B  G  T3 G  T4 B  G  B  B  B  G  G  ^vv^^vv^v^v^^^vvv^v^^^vvvvv^^v^v^^^^ Max fitness: 9785 Specimens: 31053 Score: 2695045
Turns:9999 best rat B  B  B  G  B  G  T3 G  T4 B  G  B  B  B  G  G  ^vv^^vv^v^v^^^vvv^v^^^vvvvv^^v^v^^^^ Max fitness: 14377 Specimens: 46384 Score: 6033904
Scored 6035495 in game 146 current mean 466.875

Tutaj wcześnie pojawiła się rasa superszczurów (tor prawdopodobnie pozwalał biegać w linii prostej, a niektórzy szczur w pierwszych pokoleniach mieli odpowiednie DNA, aby z niego skorzystać). Liczba okazów na końcu to około połowa teoretycznego maksimum około 100 000 szczurów, co oznacza, że ​​prawie połowa zwierząt zyskała zdolność do przetrwania na tym konkretnym torze w nieskończoność (!).
Oczywiście wynik jest po prostu nieprzyzwoity - tak na marginesie - czas obliczeń.

Turns:2499 best rat B  T0 G  B  T7 B  G  B  T6 T0 T3 B  G  G  G  T4 ^v^^^^^v^^v^v^^^^^^^^v^v^v^^vvv^v^^^ Max fitness: 18 Specimens: 772 Score: 1
Turns:4999 best rat T7 G  G  G  G  T7 G  B  T6 T0 T3 T5 G  G  B  T4 ^vvvvvvv^^^vvv^^v^v^^^^^^^^^^^^^v^^^ Max fitness: 26 Specimens: 856 Score: 1
Turns:7499 best rat G  T0 G  T3 G  T0 G  B  T6 T0 T2 B  T4 G  B  T4 ^^v^vvv^^^vv^^v^vvv^v^^vvvv^^^^^^^^^ Max fitness: 55 Specimens: 836 Score: 5
Turns:9999 best rat T6 T0 G  T5 B  T1 G  B  T6 T0 T3 B  T4 G  B  T4 ^^vv^^^^vv^^v^v^^v^^vvv^vv^vvv^^v^^v Max fitness: 590 Specimens: 1223 Score: 10478
Scored 10486 in game 258 current mean 628.564

Tutaj możemy zobaczyć udoskonalenie genomu w pracy. Linia między dwoma ostatnimi genomami jest wyraźnie widoczna. Najważniejsze są dobre i złe oceny. Te pułapki przesłanki zdają się drgać, aż do ustabilizowania albo „pożyteczne” pułapkę lub mutować w dobry lub zły .

Wygląda na to, że geny kolorów mają kilka przydatnych cech:

  • mają samodzielne znaczenie
    (konkretny kolor należy traktować w określony sposób)
    Każde kodowanie kolorów można wrzucić do zupełnie innego genomu bez radykalnej zmiany zachowania - chyba że kolor rzeczywiście jest decydujący (zazwyczaj ściana lub teleporter prowadzący do nieskończonej pętli).
    Jest tak mniej w przypadku podstawowego kodowania priorytetowego, ponieważ najbardziej priorytetowy kolor jest jedyną informacją używaną do decydowania, gdzie się przenieść. Tutaj wszystkie „dobre” kolory są równe, więc dany kolor dodany do listy „dobrych” będzie miał mniejszy wpływ.
  • są one stosunkowo odporne na mutacje,
    dobre / złe kodowanie ma tylko 2 znaczące bity z 4, a lokalizacja pułapki może być zmieniana przez większość czasu bez znaczącej zmiany zachowania szczura.
  • są małe (4 bity), więc prawdopodobieństwo zniszczenia przez crossover jest bardzo niskie.
  • mutacje wywołują albo nieszkodliwe dla znaczących zmian
    Gen mutujący się w „dobry” albo będzie miał niewielki wpływ (jeśli na przykład odpowiada pustej komórce, może pozwolić na znalezienie nowej, krótszej ścieżki, ale może również doprowadzić szczura do pułapka) lub dramatyczna (jeśli kolor reprezentuje ścianę, nowy szczur prawdopodobnie utknie gdzieś).
    Gen przechylający się do „pułapki” albo pozbawi szczura niezbędnego koloru, albo nie będzie miał zauważalnego efektu.
    Mutacja lokalizacji pułapki będzie miała znaczenie tylko wtedy, gdy rzeczywiście będzie przed nią pułapka (lub coś szkodliwego), co ma względnie małe prawdopodobieństwo (powiedziałbym, że 1/3).

Wreszcie, wydaje mi się, że ostatnie 36 bitów przyczynia się nie tylko do uniknięcia utknięcia szczurów, ale także do bardziej równomiernego rozłożenia szczurów na torze, zachowując w ten sposób różnorodność genetyczną, dopóki nie powstanie zwycięski genom i stanie się dominujący poprzez część kodującą kolory.

Dalsza praca

Muszę powiedzieć, że uważam te małe stworzenia za fascynujące.
Jeszcze raz dziękuję wszystkim uczestnikom tego wspaniałego wyzwania.

Zastanawiam się, czy jeszcze bardziej zahartować kontrolera, aby wyświetlał bardziej znaczące dane, takie jak pochodzenie udanego szczura.

Bardzo chciałbym też zobaczyć te szczury w akcji, ale ten język C ++ b ** ch języka sprawia, że ​​tworzenie - nie mówiąc już o animacji - obrazów (wśród wielu innych rzeczy) jest bałaganiarskim obowiązkiem.

Na koniec chciałbym przedstawić przynajmniej wyjaśnienie systemu pułapek i ewentualnie je ulepszyć.

Hakowanie kontrolera

Jeśli ktoś jest zainteresowany, mogę opublikować wprowadzone przeze mnie zmiany w kontrolerze.
Są brudne i tanie, ale wykonują swoją pracę.

Nie jestem zaznajomiony z GitHub, więc musiałbym przejść przez zwykły post.

Społeczność
źródło
16
Zdobył wynik 208,14 z 10 000 gier. Próbowałem go przetestować na 1000, ale nigdy nie zdawałem sobie sprawy, że wpisałem dodatkowe 0, więc zajęło mi to ponad 7 godzin.
feersum
LOL mimo wszystko dziękuję. W porównaniu z moimi dwoma tysiącami przebiegów wydaje się, że około 2000 przebiegów może wtedy dać stabilny wynik.
Co masz na ^^v^vvv^^^vv^^v^vvv^v^^vvvv^^^^^^^^^myśli? Resztę zgaduję, ale mam z tym problem?
Mooing Duck
Zastanawiałem się nad stworzeniem osobnego kontrolera „debugującego”, który uruchamiałby jednego szczura na raz, i za każdym razem, gdy generowany jest nowy szczur, pokazuje DNA zarówno rodziców, jak i dziecka (poprzez jakąś konfigurowalną funkcję). To znacznie ułatwiłoby zbadanie działania szczura.
Mooing Duck
2
który reprezentuje 36 bitów wskaźnika „góra / dół”, ale w tych przykładach zwycięski DNA już stał się dominujący, więc nie różnią się zbytnio.
18

Trudni wyznawcy - C ++ - (ulepszone teleportery): 10.000+ na 2000 przebiegów

(jest to ewolucja ślepej wiary , więc możesz wspiąć się na kolejną ścianę tekstu przed tą)

#ifndef NDEBUG
#define NDEBUG
#include "./gamelogic.cpp"
#endif // NDEBUG
#include <cassert>

#define NUM_COLORS 16
#define BITS_OFFSET  3
#define BITS_TYPE    2
#define BITS_SUBTYPE 2
#define BITS_COLOR (BITS_TYPE+BITS_OFFSET)

// how our rats see the world
typedef unsigned char enumSupport_t;
typedef unsigned char trapOffset_t;
typedef enum : enumSupport_t {
    danger,   // code      trap detector
    beam,     // code      safe teleporter
    empty,    // code      empty
    block,    // code      wall, pit or teleporter
    trap,     // computed  detected trap
    pit,      // read      off-board cell
} colorType_t;

// color type encoding (4 first bits of a color gene)
// the order is critical. A single block/empty inversion can cost 2000 points or more
const colorType_t type_decoder[16] = {
    /*00xx-*/
    danger,
    empty,
    beam,
    block,
    /*01xx-*/
    beam,
    danger,
    empty,
    block,
    /*10xx-*/
    empty,
    beam,
    block,
    danger,
    /*11xx-*/
    block,
    empty,
    danger,
    beam,
};

// all 8 possible neighbours, carefully ordered
typedef coord_t neighborhood_t[8];
neighborhood_t moves_up =   { { 1, 0 }, { 1,  1 }, { 1, -1 }, { 0,  1 }, { 0, -1 }, { -1, 0 }, { -1,  1 }, { -1, -1 } };  // toward the goal, going up   first
neighborhood_t moves_down = { { 1, 0 }, { 1, -1 }, { 1,  1 }, { 0, -1 }, { 0,  1 }, { -1, 0 }, { -1, -1 }, { -1,  1 } };  // toward the goal, going down first

// using C++ as a macro-assembler to speedup DNA reading
/*
Would work like a charm *if* a well-paid scatterbrain at Microsoft had not defined
std::bitset::operator[] as

bool operator[](size_t _Pos) const
{   // subscript nonmutable sequence
return (test(_Pos));
}

Bounds checking on operator[] violates the spec and defeats the optimization.
Not only does it an unwanted check; it also prevents inlining and thus generates
two levels of function calls where none are necessary.
The fix is trivial, but how long will it take for Microsoft to implement it, if
the bug ever makes it through their thick layer of tech support bullshit artists?
Just one of the many reasons why STL appears not to live up to the dreams of
Mr Stroustrup & friends...
*/
template<size_t BITS> int DNA_read(dna_t dna, size_t base)
{
    const size_t offset = BITS - 1;
    return (dna[base + offset] << offset) | DNA_read<offset>(dna, base);
}
template<> int DNA_read<0>(dna_t, size_t) { return 0; }

// color gene
struct colorGene_t {
    colorType_t  type;
    trapOffset_t offset;  // trap relative location
    colorGene_t() : type(empty) {} // our rats are born optimists
};

// decoded DNA
class dnaInfo_t {
private:
    const dna_t & dna;
    static const size_t
        direction_start = NUM_COLORS*(BITS_TYPE + BITS_OFFSET),
        direction_size = DNA_BITS - direction_start;

public:
    colorGene_t color[NUM_COLORS];
    int         up_down; // anti-wall-banger

    // decode constant informations during construction
    dnaInfo_t(const dna_t & d) : dna(d)
    {
        for (size_t c = 0; c != NUM_COLORS; c++)
        {
            unsigned raw = DNA_read<BITS_COLOR>(d, c * BITS_COLOR);
            color[c].type = type_decoder[raw >> 1];
            if      (color[c].type == danger) color[c].offset = raw & 7;
            else if (color[c].type == beam  ) color[c].offset = raw & 3;
        }
    }

    // update with surroundings signatures
    void update(size_t signature)
    {
        // anti-blocker
        up_down = (direction_size > 0) ? dna[direction_start + signature % direction_size] : 0;
    }
};

// map of the surroundings
class map_t {
    struct cell_t {
        coord_t pos;
        int     color;
    };

    static const size_t size = 5;
    static const int max = size / 2;
    static const int min = -max;

    size_t local_signature[size*size]; // 8 neighbours signatures for teleporters
    cell_t track_cell[size*size]; // on-track cells
    size_t cell_num;
    colorType_t map[size*size];
    size_t raw_index(int x, int y) { size_t res = x * size + y + max + max * size; assert(res < size*size); return res; }
    size_t raw_index(coord_t pos) { return raw_index(pos.x, pos.y); }

    bool is_inside(int x, int y) { return abs(x) <= max && abs(y) <= max; }

public:
    size_t compute_signatures(view_t v, dnaInfo_t genome)
    {
        cell_num = 0;
        size_t signature = 0;
        memset (local_signature, 0, sizeof(local_signature));
        int i = 0;
        for (int x = min; x <= max; x++)
        for (int y = min; y <= max; y++)
        {
            int c = v(x, y);
            if (c == -1)
            {
                (*this)(x, y) = pit; continue;
            }
            track_cell[cell_num++] = { { x, y }, c };
            signature ^= c << (4 * (i++ & 1));

            if (genome.color[c].type == beam)
            {
                int in = 0;
                for (coord_t n : moves_up)
                {
                    coord_t pn = {x+n.x,y+n.y};
                    if (!is_inside(pn)) continue;
                    int cn = v(pn.x, pn.y);
//                    if (cn == -1) continue;
                    local_signature[raw_index(pn.x,pn.y)] ^= cn << (4 * (in++ & 1));
                }
            }
        }
        return signature;
    }

    void build(dnaInfo_t genome)
    {
        coord_t traps[size*size];
        size_t t_num = 0;

        // plot color meanings
        for (size_t c = 0; c != cell_num; c++)
        {
            const cell_t& cell = track_cell[c];
            const colorGene_t& color = genome.color[cell.color];
            (*this)(cell.pos) = (color.type == beam && (local_signature[raw_index(cell.pos.x,cell.pos.y)] % 4) == color.offset)
                    ? block
                    : color.type;

            // build a list of trap locations
            if (color.type == danger)
            {
                coord_t location = cell.pos + moves_up[color.offset];
                if (is_inside(location)) traps[t_num++] = location;
            }
        }

        // plot trap locations
        while (t_num) (*this)(traps[--t_num]) = trap;
    }

    // quick & dirty pathing
    struct candidate_t {
        coord_t pos;
        candidate_t * parent;
        candidate_t() {} // default constructor does not waste time in initializations
        candidate_t(int) : parent(nullptr) { pos.x = pos.y = 0; } // ...this is ugly...
        candidate_t(coord_t pos, candidate_t * parent) : pos(pos), parent(parent) {} // ...but so much fun...
    };

    coord_t path(const neighborhood_t & moves)
    {
        candidate_t pool[size*size]; // private allocation for express garbage collection...
        size_t alloc;

        candidate_t * border[size*size]; // fixed-size FIFO 
        size_t head, tail;

        std::bitset<size*size>closed;

        // breadth first search. A* would be a huge overkill for 25 cells, and BFS is already slow enough.
        alloc = head = tail = 0;
        closed = 0;
        closed[raw_index(candidate_t(0).pos)] = 1;
        border[tail++] = new (&pool[alloc++]) candidate_t(0);
        while (tail > head)
        {
            candidate_t & candidate = *(border[head++]); // FIFO pop
            for (const coord_t move : moves)
            {
                coord_t new_pos = candidate.pos + move;
                if (is_inside(new_pos))
                {
                    size_t signature = raw_index(new_pos);
                    if (closed[signature]) continue;
                    closed[signature] = 1;
                    if ((*this)(new_pos) > empty) continue;
                    if (new_pos.x == 2) goto found_exit; // a path to some location 2 cells forward
                    assert(alloc < size*size);
                    assert(tail < size*size);
                    border[tail++] = new(&pool[alloc++]) candidate_t(new_pos, &candidate); // allocation & FIFO push
                    continue;
                }
                // a path out of the 5x5 grid, though not 2 cells forward
            found_exit:
                if (candidate.parent == nullptr) return move;
                candidate_t * origin;
                for (origin = &candidate; origin->parent->parent != nullptr; origin = origin->parent) {}
                return origin->pos;
            }
        }

        // no escape
        return moves[1]; // one cell forward, either up or down
    }

    colorType_t & operator() (int x, int y) { return map[raw_index(x, y)]; }
    colorType_t & operator() (coord_t pos) { return operator()(pos.x, pos.y); }
    bool is_inside(coord_t pos) { return is_inside(pos.x, pos.y); }
};

std::string trace_DNA(const dna_t d, bool graphics = false)
{
    std::ostringstream res;
    dnaInfo_t genome(d);
    for (size_t c = 0; c != NUM_COLORS; c++)
    {
        if (graphics)
        {
            res << "tbew--"[genome.color[c].type];
            if (genome.color[c].type == danger) res << ' ' << moves_up[genome.color[c].offset].x << ' ' << moves_up[genome.color[c].offset].y;
            if (genome.color[c].type == beam) res << ' ' << genome.color[c].offset << " 0";
            if (c != NUM_COLORS - 1) res << ',';
        }
        else switch (genome.color[c].type)
        {
        case danger: res << "01234567"[genome.color[c].offset]; break;
        case beam  : res <<     "ABCD"[genome.color[c].offset]; break;
        default: res << "!*-#X@"[genome.color[c].type]; break;
        }
    }
    return res.str();
}

coord_t hardBelievers(dna_t d, view_t v)
{
    dnaInfo_t genome(d); // decoded DNA
    map_t     map;       // the surroundings seen by this particular rodent

    // update genome with local context
    genome.update(map.compute_signatures(v, genome));

    // build a map of the surrounding cells
    map.build(genome);

    // move as far to the right as possible, in the contextually preffered direction
    return map.path(genome.up_down ? moves_up : moves_down);
}

int main() {
    time_t start = time(NULL);
    double score = runsimulation(hardBelievers, trace_DNA);
    slog << "Geometric mean score: " << score << " in " << time(NULL) - start << " seconds";
}

Odcinek IV: Ukierunkowanie się na starcie

Wyniki

Scores: 309371 997080 1488635 1 19 45832 9 94637 2893543 210750 742386 1677242 206614 111809 1 1738598 1 1 342984 2868939 190484 3354458 568267 280796 1 1 1 679704 2858998 1 409584 3823 200724 1 973317 849609 3141119 1 1987305 1 1 57105 245412 1223244 2 1603915 2784761 9 12 1 1839136 1 298951 2 14 138989 501726 1365264 308185 707440 22 772719 17342 63461 3142044 19899 3 409837 48074 3549774 138770 32833 1 1 1184121 67473 310905 1996452 4201 1701954 2799895 2041559 218816 174 433010 51036 1731159 1871641 1 23 2877765 1 127305 27875 626814 142177 2101427 167548 2328741 4 8433 2674119 2990146 466684 1 2 8 83193 388542 2350563 1 1140807 100543 1313548 31949 73117 73300 121364 1899620 1280524 1 10726 12852 7 2165 1 3 44728 2 122725 41 2 1902290 3 1 8581 70598 1148129 429767 1 112335 1931563 521942 3513722 1 2400069 1 3331469 141319 220942 205616 57033 63515 34 6 1419147 1983123 1057929 1 599948 2730727 2438494 5586 268312 1728955 1183258 95241 1537803 11 13 1157309 1750630 1 1 2690947 101211 3463501 1 258589 101615 212924 137664 19624 251591 509429 510302 1878788 1 4045925 1 21598 459159 118663 7 3606309 3 13016 17765 640403 1 72841 695439 1 135297 2380810 1 43 31516 14 1442940 1001957 95903 194951 1 238773 773431 1 1 975692 2 4990979 52016 3261784 2 413095 12 3 420624 7905 60087 760051 2702333 2572405 1 1717432 1 12 3040935 1 1 31787 60114 513777 1 3270813 9639 581868 127091 270 164228 274393 1275008 261419 597715 138913 28923 13059 1848733 2895136 7754 14 1 107592 1 3557771 2067538 147790 112677 119004 1 13791082842974 249727 838699 4067558 6 470799 695141 1 3 1 1276069 23691 831013 5 165142 1236901 1 187522 2599203 1 67179 81345 44111 2909946 94752 7 406018 991024 4 1 3 573689 6 748463 2166290 33865 670769 322844 5657 1131171 1990155 5 4536811 1785704 3226501 2030929 25987 3055355 192547 1761201 433330 27235 2 312244 13203 756723 81459 12 1 1 54142 307858 2 25657 30507 1920292 3945574 1 191775 3748702 3348794 4188197 366019 1540980 3638591 1 1840852 1 26151 2888481 112861 8 11 2 1 27231 1 74 106853 3 173389 2390495 25 1 83116 3238625 75443 1 1 2125260 1 49626 1 6 312084 159735 358268 54351 367201 2868856 5779 172554 119016 141728 3 1 6 9 1 1504011 1 168968 1868493 1 5 1 244563 2 2887999 3144375 1598674 1 1578910 45313 176469 30969 8 127652 1911075 9 1300092 224328 168752 8 1619669 292559 9090 2040459 705819 1852774 10 139217 16 1221670 355060 339599 3 2184244 2546028 1 1 11 70958 242187 1 80737 1 190246 3 1 1 577711 150064 1 1047154 3851461 92399 224270 612237 1 3 3330053 1 1 1192533 615756 267923 144724 2 1 150018 4621881 1 6 299247 115996 2 10 6 185495 76351 465554 178786 1802565 257101 56 2491615 1 24547 1 1203267 32 5741149 541203 11393 1 368082 540534 16167 113481 2004136 13045 17 1 12 333803 14 1955075 1 4 38034 1286203 2382725 26777 1 180312 1 87161 4773392 1244024 1146401 3 80598 2983715 1 63741 1 1 2561436 16 1 1 1807854 1239680 200398 2 46153 1400933 11 5058787 8787 1 98841 89162 1106459 112566 1 4138891 2858906 101835 81375 539485 6587808 1 5359988 1 1 869106 443452 120748 436156 2 2 3944932 1 1875599 2 3081185 733911 447824 1 1 23187 3082414 33 3 1 1 2053904 410824 104571 885952 1946162 2 294773 364169 1 101310 2166548 1177524 2192461 12 4 3457016 90975 2356374 573234 53746 187527 7837 1441335 458407 52139 3387239 2030900 38 1648216 215105 212589 8278 1201586 244282 1 1 1897515 3957343 46 1 134481 1 1 2041785 3 1 37593 163173 1565457 3 1026885 1 34530 4655639 2 18 1940645 1550444 593209 1 2270700 706918 1 1 610113 9 1287883 3 1472134 1998685 1916822 1 296017 2 1 1737607 4155665 1510560 553342 56130 14436 13240604 4025888 1 4253261 174177 2043316 504151 2370989 420666 155232 1 219327 3752236 130062 571247 24 1 29015 31392 1020196 3 1117502 460873 7 1 228 8 133656 1 147008 1 93471 1 1 1 513410 4834094 1 14 1875636 182714 1504903 95263 4418053 1 357853 1135536 3698641 3 239316 4237884 131730 3878724 2158931 55650 1906785 1 26372 32 99217 1645677 379838 1 450352 7329657 112909 1 897980 2114198 308917 126215 1 53839 539997 238036 2 2270000 5 2388928 1668820 519153 58227 347528 1 1 2339954 10 5 2031341 54 2341529 2189774 112731 1 21918 748662 2068921 2 2232504 2923457 97740 3858 16604 398940 388755 1875003 667810 53633 315866 839868 1 7 1 14238 185 4 14 1 2 178947 1965719 398323 120849 48 1397222 961772 34124 2 160652 1 252629 246554 14529 1 299866 135255 490837 2863773 8 10 2 1906405 57 9782 118940 870003 255097 6 4187677 50965 3354376 17611 1804789 183601 158748 1539773 116107 77684 34738 2862836 1 2081903 727739 50328 2740070 17 923524 18 3089706 3144082 1 20 205247 347420 2076952 3725220 39270 2 15 49329 422629 5 1693818 2570558 2146654 1 5 129085 653766 47438 102243 389910 59715 21769 1246783 361571 4 120502 255235 1314165 3 3 5 2902624 76351 3117137 174413 2546645 14534 166054 1013583 1 1 2 9 3027288 3173742 338261 94929 1071263 4659804 1 506576 42798 4 984508 1 4 4 1 18541 7 1 269761 188905 2 1 92011 147031 677955 27484 1291675 2420682 99970 57943 1 4081062 1 250953 704904 4 349180 4273479 30528 2092508 2352781 3700946 1 77799 328993 3684623 3930179 1250080 1975798 54981 1621677 91664 1355832 1084049 721612 56950 197563 246868 5031 1 924076 1328694 58562 1 457662 2445958 1345169 957845 1056809 2485300 1687907 199029 3 9474 86928 1 2419980 3585265 570673 1 1514184 437383 1596697 29709 199606 126031 2 1541777 1 3 2090249 2402438 15 19 1423959 28 37852 4 1652596 1 405512 52 3 1948029 1 2 376 1155902 3 631665 3741991 57673 284026 424787 1 11569 5 1200313 1 20 2360854 1 119994 3889143 673424 797763 1 1 144306 1007659 1231874 75607 1 15 66187 8763 21366 146277 2684501 4458542 162223 3 1 5 94232 3036009 401312 19775 510737 3305062 58905 125783 274094 3089988 118483 1 106213 1 1289180 127905 30 528859 2 1215596 1955900 30 2236528 218643 1 2396631 1598175 1148688 452064 1 1840394 198540 1 1307187 107463 341396 2684981 9602 536871 1 148107 4068 4918434 1 2430254 2066144 88915 3585780 6464 259394 3098337 49601 42 79205 925658 1 2513666 26817 2738302 1 28 345735 5086930 361294 505662 386194 1103890 2653001 412247 4074274 2217918 1 519433 1338570 4289317 140138 18 2519983 168656 4546204 8 1 76545 511580 979214 9318 210013 50508 40 152908 17969 922507 1 7 32 1 388579 1 49886 13319 1066048 4663 27883 38419 1418098 2538216 1 778734 3556791 490764 666880 22746 5666164 4 20 1806284 21142 1 527906 2 12417 182224 49536 105029 206917 2427623 294247 1405136 321480 354137 84225 50 128073 1391176 352835 26074 91159 34229 237942 1 1519676 1 2428669 272681 148689 528951 560736 1 3548197 3833513 1438699 286613 1 1290904 47145 3456135 249648 277045 1012397 271073 1 6 149276 94843 11 177134 32336 2772732 7 22 37065 1 105299 76735 44 2211334 511942 30639 522056 5162 1899842 74 1 1448039 1 88817 21 1027532 555416 1 364383 1335609 167332 283252 49564 220972 1006800 3108886 801258 265596 61651 1 2413276 252747 416606 960925 54 311956 267135 3871698 22581 8978 2 10 1966155 3123429 28 46409 1 18433963725323 1769396 114766 49071 1 1 4228762 3483932 1139490 602592 2700468 770273 3 1 1 212087 281247 27093 156094 286299 1204001 18374 1 330780 1 1 25384 906728 99334 1250819 2161201 34 1027892 1 33449 2 129787 52246 94872 1536841 23470 1 1700323 1 1 3785351 1 95315 1014155 56570 22586 66842 7 156840 48752 1 3143722 1 1168309 2 4 101423 385892 42868 2893851 7 1783109 217499 24 460497 2003214 180135 3503010 131137 2 5240 1621601 2754811 11198 1 1 1105643 1 1671021 3 139611 18268 107229 44582 2211034 1 2880152747163 231008 262504 1 257760 1 1 52992 804418 2 2 4811272 1772250 3 1796530 1918647 1 1934549 1 100550 3448657 1681262 3 604526 320865 1901079 556908 2794800 2472000 637735 123663 1 3213187 118199 2553610 1 1750628 2563806 1 1670872 1 999609 50200 654831 1 164612 2865759 1841739 9 3744159 1331395 3202501 1 7 1 1 239868 1 1 581984 112413 401 1 29656 359367 74532 27226 51752 2583 1 645443 1559731 1 114195 1 85473 229474 111353 1 1521653 1 2568733 444398 2593568 18546 1 158085 1211147 1020006 23407 42514941388799 158442 1 1660358 5 34874 1594789 1551270 386464 502417 32280 170606 1954278 72486 3406066 11 52896 345631 4010742 33307 1951926 1441325 1886066 1 3 402778 3089364 351 28028 4301364 1 431569 5 3054030 375986 404966 1 449317 1230292 1 7 763949 1 2 3197443 1537806 335317 2 1 161263 1 1959902 1664530 139136 447570 1 1 50 158825 222939 1842131 11252 1680094 1017889 71 144808 1 53679 1 41278 1226724 1 1 2 10 2 1 112451 42133 1406662 1 112593 2 2832116 1544488 3579017 3029492 2752014 6 255091 731329 540861 1 426725 440330 212602 202358 173553 4 1189793 11031 84073 2084554 3963 1473295 1 642570 1 1423688 34509 75056 163273 490193 3200250 451777 157797 4156542 2386299 2794795 2735308 1332758 1193296 1131014 1001570 414257 4415511 4 3 1 3499595 536583 16731 93839 92382 1 45890 1 17695 8 867246 18 1607123 3197052 5 40009 1 329895 3497309 2416600 2316390 11 118179 2166659 2 136426 76762 2 14 2 3632525 214889 6 3900942 270409 230143 120414 417489 16706 1563597 31418 2 73 468763 88585 428274 3537347 2 1 491461 2806485 1 7 2950804 115684 4 1 429002 85771 2480 285541 186486 1 1 2430862 6 9 4 1833423 17143 353689 2568741 408890 2929237 208679 2198380 1 2501053 1933666 180843 1 1 2569886 1 17035 3449472 71357 246257 217898 1 47601 589824 401679 362878 13178 34464 1076419 1 554417 1 21248 2136449 1068 23029 8 766649 4 302879 274751 19 1 390259 1899931 233910 1392272 184492 2 2752059 55813 1 6 64674 205205 595508 1714309 582492 4821971 63973 1708726 189200 4548446 479425 2866037 1 1 1 2139319 1 1 3 1572621 2086152 2341038 1 619612 1 78942 772466 18932 1404368 936790 2263929 230200 3009227 251065 835010 88225 642856 824193 5559048 1 36348 2338046 481447 108132 2728223 3539009 1 197164 181408 171634 2172263 2317332 1598340 1318829 1746303 7 59657 1 1415452 122924 915828 1063890 40339 430186 4 2165185 2250922 704568 85138 4417453 255 326360 33541 3 49759 72127 912537 599665 1 29169 168741 349838 996835 1548193 2 28449 803521 4 2 2 3359043 3243259 1 491574 1675000 186105 3203018 11 39127 959876 334480 873131 70262 137080 1076591 1 2155613 74804 893022 2473922 1 1 269835 5 2407308 3 55200 905207 1 1 1245609 65934 7 1372126 530582 1383562 1 1 2718341 1 3947638 4 76837 412551 11 1 1 1208080 3024670 277 46485 1 9 562183 46 2985858 3379885 67816 1896527 1 105478 2035453 3026415 1 189256 2992616 2098002 1099666 775250 5913 13 406948 166773 1 322250 41919 480047 64950 17435 2147428 2336270 3330243 352709 86029 1398723 106236 312951 1 408211 252689 847088 2 17 34088 13128 187366 2 1559482 2349010 1651122 2371088 401005 1715445 1 29483921 1464444 50228 2365851 1651636 768715 226704 23677 83501 1 252623 444628 34 3640316 3602127 45369 1 1 1978261 1 3019189 1 25411 2177552 192839 191146 293712 3840622 182598 4069200 175757 1 2250458 4 1 7 2740824 2753005 1 2836428 1 12 19 2 1788326 3302198122211 3386546 1176663 20847 28 1194294 794665 2630378 13624 722012 2273872 1549353 1 3 1735700 1668388 416 970581 258382 295427 1 121571 3193610 3764806 1 368985 20436 89411 3 16130 2 241879 1 2996216 136958 2382095 510146 1762872 1372194 4215387 346915 4423 1 904153 2004500 248495 836598 3529163 27 2547535 1424181 1885308 1 1056747 289743 176929 2299073 170473 1 1 839941 12382 51457 608526 1684239 4843522 34550 929855 2767014 2979286 1 340808 184830 131077 57298 63854 381689 201998 1715328 118687 69190 123466 1 2 69392 159797 382756 1513430 2506318 457 1
Geometric mean score: 10983.8 in 31214 seconds

Zmieniłem na g ++ / MinGW i 3 wątki.
Kod generowany przez GNU jest ponad dwa razy szybszy niż Microsoft.
Nic dziwnego, co z ich przerażającą implementacją STL.

Teleportery

Efekt teleportacji jest wysoce zależny od pozycji. Do tej pory z przyjemnością uważałem teleporter za zawsze dobry (postrzegany jako pusta przestrzeń) lub zawsze zły (postrzegany jako ściana, aby żaden gryzoń nigdy go nie wziął).

To zbyt gruby model.
Dany teleporter może pchać szczura do przodu, aż kilka komórek od bramki, ale gdy już tam jest, ten sam teleporter może wyrzucić szczura z planszy.
Taki teleporter najprawdopodobniej zostanie rozpoznany jako przejezdny (ponieważ zwiększa sprawność szybciej niż podczas „chodzenia” do tej samej lokalizacji x), stanie się częścią dominującego genomu i zabije prawie wszystkie szczury, które ufają mu jako „zawsze bezpieczne”.
Ponieważ szczury nie mają możliwości poznania swojej pozycji X, jedynym rozwiązaniem do wykrycia tych zdradzieckich teleporterów jest decyzja, czy nadepnąć na nie w oparciu o jedyne dostępne dane kontekstowe, tj. Siatkę kolorów 5x5.

Aby to zrobić, zdefiniowałem 4 rodzaje genów kolorów:

  • wykrywacz niebezpieczeństw
  • puste przejezdne w dowolnym miejscu na torze
  • blokowanie zabronione w dowolnym miejscu na torze
  • wiązka postrzegana jako pusta lub blokowana w zależności od otoczenia

Chodzi o to, aby spróbować odróżnić teleporter, patrząc na jego najbliższych 8 sąsiadów. Ponieważ prawdopodobieństwo posiadania 8 identycznych sąsiadów w danej lokalizacji jest bardzo niskie, powinno to pozwolić na zidentyfikowanie unikalnego wystąpienia każdego teleportera.

8 sąsiednich kolorów można łączyć w celu utworzenia lokalnego podpisu, który jest niezmienny w zależności od pozycji w labiryncie. Niestety, 8 sąsiadów jest widocznych tylko dla komórek znajdujących się w wewnętrznym kwadracie pola widzenia 3x3, więc podpisy będą niedokładne na brzegu pola widzenia.
Niemniej jednak da nam to stałą informację kontekstową w bezpośrednim sąsiedztwie, co wystarcza, aby zwiększyć prawdopodobieństwo pomyślnej nawigacji teleporterów.

geny wiązki mają zmienne pole bitowe o długości 2 bitów.
Dla danego podpisu lokalnego teleportera istnieje jedna szansa na cztery, że komórka wiązki zostanie uznana za nieprzekraczalną. Każda wartość pola wybiera jedną z tych czterech możliwości.
W rezultacie mutacja genu wiązki na tych 2 bitach przejdzie przez 4 możliwe kontekstowe znaczenia koloru.

Poza tym najważniejszymi kolorami do odgadnięcia są wciąż ściany i pułapki. Oznacza to, że powinniśmy pozwolić na wykrycie teleportera dopiero po tym, jak szczury dowiedzą się, gdzie są ściany i pułapki.

Odbywa się to poprzez jedynie rzadkie aktualizowanie lokalnych podpisów. Obecne kryterium aktualizacji podpisu lokalnego ma być w pobliżu koloru określonego jako potencjalny teleporter.

Kodowanie wykorzystuje 5 bitów na gen koloru i grupy w celu uwolnienia 3 mniej znaczących bitów w celu zakodowania wartości 0..7:

  • 4 niebezpieczeństwo
  • 4 puste
  • 4 bloki
  • 4 wiązki

Każdy gen wiązki ma 1/4 szansy na uznanie za blok i 3/4 szansy na uznanie za puste, więc 4 wiązki reprezentują średnio 1 blok i 3 puste.

Średni odsetek reprezentowany przez losowy rozkład 16 kolorów wynosi zatem:

  • 4 niebezpieczeństwo
  • 7 pustych
  • 5 bloków

Ta mieszanka wydaje się dawać jak dotąd najlepsze wyniki, ale nie skończyłem jej przerabiania.

Zmienność genu

Jedno jest pewne: wartości kodu wybrane do reprezentowania typów genów mają kluczowe znaczenie. Odwrócenie dwóch wartości może kosztować 2000 punktów lub więcej.

Tu znowu powód, dla którego nie mam matematyki.

Domyślam się, że prawdopodobieństwa mutacji z jednego rodzaju na inny muszą być zrównoważone, bo inaczej, podobnie jak w macierzy Markowskiej, skumulowane prawdopodobieństwa ograniczają wartości do podzbioru o najwyższych prawdopodobieństwach przejścia.

Ścieżka na ratunek

Ścieżka znacznie zmniejszy liczbę odwiedzanych komórek, umożliwiając testowanie tylko tych, którzy najprawdopodobniej doprowadzą do celu. W ten sposób nie tylko unika się częstych ślepych zaułków, ale również znacznie częściej wykrywa się błędne kody kolorów.
W rezultacie czas konwergencji jest znacznie skrócony.

Nie pomaga to jednak w rozwiązywaniu map, w których genom nie jest w stanie uzyskać właściwej reprezentacji ścieżki.

Co zrobić z kretynami?

Po wizualnym spojrzeniu na tor zrozumiałem, dlaczego domyślna strategia, która próbuje iść naprzód, nawet gdy wydaje się, że są tylko ściany z przodu, jest naprawdę lepsza niż powstrzymywanie się.
„ściany” mogą być w rzeczywistości teleporterami, które dają tak wiele niefortunnych wyników, że genom odwzorowuje je jako przeszkody, których nigdy nie należy deptać, ale w rzadkich przypadkach szczególny przypadek tego niegrzecznego teleportera może mieć pozytywny (lub przynajmniej nie śmiertelny) efekt , więc zabranie go zamiast cofnięcia się zwiększa szanse na znalezienie drogi do zwycięstwa.

Wczesna konwergencja

Wydaje mi się, że częstość mutacji jest nieco za niska (przynajmniej dla moich gryzoni).

Obecne ustawienie 0,01 daje DNA 37% szans na przetrwanie nienaruszonego procesu mutacji. Zmiana parametru na 0,0227 obniża to prawdopodobieństwo do około 10%

Tajemniczą formułą jest mutacja P 1 bit = 1-P cały genom w stanie nienaruszonym 1/100 , 100 oznacza długość bitu genomu.

Na przykład dla 10% prawdopodobieństwa mutacja P 1 bit = 1 - 0,1 1/100 = 0,0277
Dla 5% prawdopodobieństwa, P = 1 - 0,05 1/100 = 0,0295 Odwracając
wzór, okazuje się, że 0,01 daje 37% szans na bycie niezmieniony przez mutację.

Ponownie wykonałem dokładnie ten sam test (używając ustalonej sekwencji losowych nasion) z prawdopodobieństwem 10%.
Na wielu mapach poprzednie awarie okazały się (ograniczonymi) sukcesami. Z drugiej strony, ogromne eksplozje populacji były mniejsze (co miało ciekawy efekt uboczny przyśpieszania obliczeń).
Mimo że bardzo wysokie wyniki (ponad milion) były mniej powszechne, liczba udanych przebiegów była więcej niż wystarczająca, aby to zrekompensować.
Ostatecznie średnia wzrosła z 1400+ do około 2000.

Przeciwnie, ustawienie P na 5% dało średnią około 600.
Zakładam, że wskaźnik mutacji był tak wysoki, że genom zwycięskich szczurów zbyt często przekształcał się w mniej wydajne warianty.

Jak to działa?

Dzięki dodanym detektorom teleportera liczba nieudanych gier (wynik <10) znacznie spadła.
W próbie z 2000 przebiegami odnotowano tylko 1/3 awarii.
Średnia geometryczna wzrosła tylko z 2900 do 3300, ale liczba ta nie odzwierciedla poprawy.

Puste kolory są często zgadywane jako belki i niebezpieczeństwa (zwykle 2 do 5). Genom „używa” tych kolorów do blokowania ścieżek, które mogłyby wpędzić szczury w kłopoty.

Genom jest całkiem dobry w zgadywaniu pułapek (tj. Gdy szczury osiągną cel, kolory reprezentujące rzeczywiste detektory pułapek są zgadywane w około 90% przypadków).
Wykorzystuje także nowe kody wiązek dla teleporterów, choć rzadziej (prawdopodobnie dlatego, że „zdradzieckie” teleportery są mniej powszechne niż pułapki, a inne kolory wiązek / niebezpieczeństwa ewoluują, aby zablokować ścieżkę do ostatnich przypadków tych zdrajców).

Sądząc po liczbie gier, w których zwycięski genom pojawia się po 5000 lub więcej turach, sądzę, że ta nowa rasa skorzystałaby znacznie na zwiększonej częstości mutacji.

Społeczność
źródło
Ponieważ istnieje parzysta liczba pułapek, pustych ścian i teleportacji, potrzebujesz tylko 3 bitów, aby dokładnie zachować proporcje (nawet jeśli weźmiesz pod uwagę pułapki == ściany). Czy zastanawiałeś się też / czy odrzuciłeś pomysł użycia nieużywanych bitów przesunięcia pułapki w anti-wallbanging? Ponieważ celem jest, aby nie dziedziczyć po rodzicach, możesz faktycznie użyć wszystkich bitów w anti-wallbanging. Nie ma powodu, by były wyjątkowe, nie sądzę.
Mooing Duck
1
@MooingDuck Przetestowałem twój pomysł ponownego wykorzystania bitów offsetowych, ale się nie udało. Jak się obawiałem, ponowne wykorzystanie informacji do dwóch różnych celów nie wydaje się działać. Załóżmy na przykład, że bity przesunięcia danego koloru są potrzebne, aby genom wybrał właściwy kierunek pionowy na danej ścieżce, kolor ten nie może już stanowić znaczącej pułapki bez zniszczenia ścieżki zależnej od tych samych danych. Próbowałem także użyć 6 bitów, ale, jak się obawiałem, również pozostałe 4 uderzenia antyścienne były zbyt krótkie.
1
Dobrze wiedzieć, ale zasugerowałem tam dwa pomysły, jeden polegał na użyciu wszystkich bitów (ponowne użycie niektórych), a drugi na wykorzystaniu nieużywanych bitów przesunięcia pułapki dla ścian / pustych. Próbowałeś obu? (Całkowicie rozumiem, jeśli nie chcesz próbować, nie musisz próbować, jeśli nie chcesz)
Kaczka Mooing
1
Próbowałem obu i oba zakończyły się niepowodzeniem. Przesunięcia pułapek są ważne nawet wtedy, gdy gen ich nie używa, ponieważ gen ten nadal może mutować z powrotem w kolor pułapki, w którym to przypadku przesunięcie pułapki prawdopodobnie zmutuje się do dowolnego kontekstu, który jest najbardziej opłacalny, i straciło znaczenie jako przesunięcie . Teraz zmutuje się z powrotem do zyskownej wartości kompensacji i zniszczy ścieżki szczurów, które od niej zależały, jako wskaźniki kontekstowe. Wydaje mi się, że widziałem przypadek takiej oscylacji za pomocą mojego narzędzia graficznego, ale nie jest łatwo wykazać wyraźny przykład tego problemu.
16

ColorScorePlayer, wstępny wynik ≈ 22

To jest bot, którego widzisz w pracy w GIF w wyzwaniu.

To był nasz bot testowy w fazie projektowania. Wykorzystuje genom do przechowywania wyniku jakości dla każdego z 16 kolorów. Następnie wykonuje ruch do przodu, który przesuwa go na kolor z najlepszym wynikiem (i nigdy nie przechodzi na -1). W przypadku remisu wybierany jest losowy ruch między komórkami wiążącymi.

Ten odtwarzacz został przeniesiony na wszystkie języki kontrolera, więc działa on jako przykład sposobu ich użycia:

Pyton

class ColorScorePlayer(Player):
    def __init__(self):
        Player.__init__(self)
        self.coords = [Coordinate( 1, 0),
                       Coordinate( 1,-1),
                       Coordinate( 1, 1)]
        self.n_moves = len(self.coords)

    def turn(self):
        max_score = max([self.bit_chunk(6*self.vision_at(c.x, c.y), 6) for c in self.coords if self.vision_at(c.x, c.y)>=0])
        restricted_coords = [c for c in self.coords if self.vision_at(c.x, c.y)>=0 and self.bit_chunk(6*self.vision_at(c.x,c.y), 6) == max_score]

        return random.choice(restricted_coords)

Rubin

class ColorScorePlayer < Player
    def initialize(rng)
        super(rng)
        @coords = [Vector2D.new( 1,-1),
                   Vector2D.new( 1, 0),
                   Vector2D.new( 1, 1)]
    end

    def vision_at(vec2d)
        @vision[vec2d.x+2][vec2d.y+2]
    end

    def turn
        max_score = @coords.map { |c|
            color = vision_at(c)
            color < 0 ? -1 : bit_chunk(6*color, 6)
        }.max

        restricted_coords = @coords.select { |c|
            color = vision_at(c)
            color >= 0 && bit_chunk(6*color, 6) == max_score
        }

        restricted_coords.sample(random: @rng)
    end
end

C ++

coord_t colorScorePlayer(dna_t d, view_t v) {
    const int chunklen = DNA_BITS / N_COLORS;
    int ymax[3], nmax, smax = -1;
    for(int y = -1; y <= 1; y++) {
        if(v(1, y) == OUT_OF_BOUNDS) continue;
        int score = dnarange(d, v(1, y)*chunklen, chunklen);
        if(score > smax) {
            smax = score;
            nmax = 0;
        }
        if(score == smax) ymax[nmax++] = y;
    }
    return {1, ymax[v.rng.rint(nmax)]};
}

DO#

public static void ColorScorePlayer(GameLogic.IView v, GameLogic.IGenome g, Random rnd, out int ox, out int oy)
{
    ox = 0;
    oy = 0;

    var max_score = cspcoords.Where(c => v[c.x, c.y] > -1).Select(c => g.cutOutInt(6 * v[c.x, c.y], 6)).Max();
    var restrictedCoords = cspcoords.Where(c => v[c.x, c.y] > -1 && g.cutOutInt(6 * v[c.x, c.y], 6) == max_score).ToArray();

    Coord res = restrictedCoords[rnd.Next(restrictedCoords.Length)];

    ox = res.x;
    oy = res.y; 
}

Jawa

package game.players;

import java.awt.*;
import java.util.Map;

public class ColorScorePlayer extends Player{
    private static final Point[] possibleMoves = {new Point(1, 0), new Point(1, -1), new Point(1, 1)};

    @Override
    public Point takeTurn(String genome, Map<Point, Integer> vision) {
        int chunkLength = genome.length()/16;
        int maxSum = -1;
        Point maxSumMove = possibleMoves[0];
        for (Point move: possibleMoves){
            if (vision.get(move) == -1){
                continue;
            }
            int initialPoint = chunkLength*vision.get(move);
            int sum = 0;
            for (int i = initialPoint; i < initialPoint + chunkLength; i++){
                sum = (sum<<1)+Integer.parseInt(genome.charAt(i)+"");
            }
            if (sum > maxSum){
                maxSum = sum;
                maxSumMove = move;
            }
        }
        return maxSumMove;
    }
}

Gracz zdobywa dość niekonsekwentnie. Oto 50 losowych przebiegów:

Scores: 1 1 1132581 3 43542 1 15 67 57 1 11 8 623162 1 1 1 134347 93198 6 1 2 1 1 245 3 1 1 27 1 31495 65897 9 5 1 2 20 2 117715 1 1 1 20 64616 5 38 1 2 1 2 12
Martin Ender
źródło
12

ColorFarSeeker, C ++ ≈ 74,7

To wyzwanie jest naprawdę zabawne i proste, jeśli spróbujesz.

Nie zniechęcaj się długim opisem.
Wystarczy odwiedzić GitHub i sprawdzić ... wszystko będzie znacznie wyraźniejsze! :)

Symulator C ++ jest wysoce zalecany ze względu na szybkość. Nawet po tym, jak skończyłem tłumaczyć mój program pythonowy na C ++, symulacja pythonowa wciąż się nie kończy.

To ulepszona odmiana ColorScorePlayer. Aby dobrze wykorzystać widok 5x5, rozważa przesunięcie o 2 kroki od niego za pomocą funkcji ważonej. Przesuwa się o 1 krok przed nim, zyskuje większą wagę, ponieważ mają bardziej bezpośredni wpływ na przetrwanie. Przejdź 2 kroki do przodu, aby otrzymać mniejszą wagę.

Próbuje iść do przodu, ale jeśli nie widać bezpiecznego ruchu ... to próbuje na boki ... a jeśli wszystko inne zawiedzie, przesuwa się losowo do tyłu.

coord_t colorFarSeeker(dna_t d, view_t v) {
#define s(w,x,y) (v(x,y)>-1?((b+dnarange(d,l+m+n*v(x,y),n))*w):0)
#define max2(a,b) (((a)>(b))?(a):(b))
#define max3(a,b,c) (max2(a,max2(b,c)))
#define push(vec,maxScore,score,x,y) if(score==maxScore&&v(x,y)>-1)vec.push_back({x,y});
#define tryReturn() if(vec.size()){return vec[v.rng.rint((int)vec.size())];}vec.clear();

    // Some constants to tweak
    int k = 4;
    int l = 3;
    int m = dnarange(d, 0, l);
    int n = 4;
    int b = dnarange(d, l, k) + 10;

    std::vector<coord_t> vec;

    // Looks forward for good moves...
    int upRightScore = s(1,0,-2) + s(1,1,-2) + s(1,2,-2) + s(5,1,-1);
    int forwardScore = s(1,2,-1) + s(1,2,0) + s(1,2,1) + s(5,1,0);
    int downRightScore = s(1,0,2) + s(1,1,2) + s(1,2,2) + s(5,1,1);
    int maxForwardScore = max3(upRightScore,forwardScore,downRightScore);
    push(vec,maxForwardScore,upRightScore,1,-1);
    push(vec,maxForwardScore,forwardScore,1,0);
    push(vec,maxForwardScore,downRightScore,1,1);
    tryReturn();

    // Looks sideways for good moves...
    int upScore = s(1,-1,-2) + s(1,0,-2) + s(1,1,-2) + s(5,0,-1);
    int downScore = s(1,-1,2) + s(1,0,2) + s(1,1,2) + s(5,0,1);
    int maxSideScore = max2(upScore,downScore);
    push(vec,maxSideScore,upScore,0,-1);
    push(vec,maxSideScore,downScore,0,1);
    tryReturn();

    // If all else fails, move backwards randomly.
    // I have tried considering the scores of backmoves,
    // but it seems worse than just randomly moving backwards. 
    vec.push_back({-1,-1});
    vec.push_back({-1,0});
    vec.push_back({-1,1});
    return vec[v.rng.rint((int)vec.size())];

}

Wynik:

Jest całkiem sporo jedynek ... które mogą być odrobinę przygnębiające, gdy konsola wyrzuca 1 po sobie. Jak planeta ze wszystkimi niezbędnymi do życia rzeczami, ale bez oznak zaawansowanej cywilizacji szczurów ...
Potem okazjonalny skok. :)

Hmm ... najwyraźniej miałem szczęście z pierwszą partią biegów, otrzymując geometrię 300+. Wyniki naprawdę bardzo się wahają. Ale w każdym razie, przy większej liczbie uruchomień symulatora, jest on prawdopodobnie bliższy 74.. (Dzięki za pomoc w symulacji i jego niesamowity szybki program)

Wyniki z moich serii: 6 6 53 1 5 101223 89684 17 2 303418 4 85730 24752 1 1 1 3482515 39752 1 59259 47530 13 554321 1 563794 1 1770329 1 57376 1 123870 4 1 1 79092 69931 594057 1 69664 59 1 6 37857 1733138 55616 2 1 51704 1 254006 4 24749 1 117987 49591 220151 26 4292194 23 57616 72 67 1 4 308039 1 1 103 89258 1 286032 1 5 3 1 5 114851 46 143712 5 15 9 80 7425 1 1 7 1 108379 70122 97238 1 1 5 2 23 104794 1 10476 59245 1 204 1 1 12 1 29641 1 314894 18785 13 1 3 1 1 1 2 526001 1 1 1 27559 29285 3 3 128708 70386 30 2 2 1 208531 331 1 2 1 61 114993 1 15 51997 1 2 1 146191 1 31 4 3 1 161422 207 1 64 1 1 1 68594 145434 87763 150187 169 185518 1 1 1 24208 2570 1 1537 1 1 462284 1 2 55 1 1 1 214365 1 40147 2 213952 1 29 3 1 2144435 5 4502444 72111 1 1 1 1 1 774547

Vectorized
źródło
1
Mam geometryczną średnią 74,7 z 1000 gier, niezła robota.
feersum
8

Bishop - Python, wstępny wynik 1,901

Biskup zawsze porusza się po przekątnej, więc połowa planszy jest niedostępna podczas danej wędrówki w poprzek planszy, ale oznacza to mniej potencjalnych ruchów do zakodowania, więc każdy pojedynczy fragment genomu może reprezentować ruch (Biskup nigdy się nie wycofuje). Który bit, do którego należy się odwoływać, jest ustalany na podstawie bloku kwadratów 3x3 przed (po prawej stronie) próbką. Najlepszym ruchem w danej sytuacji jest tylko jedna bitowa mutacja.

Ten bot najpierw szybko się uczy, ale potem często uderza w sufit, zanim dotrze do mety, prawdopodobnie w przypadku wystąpienia jednego z dwóch następujących problemów:

  • Dwie lub więcej części planszy mapują w tym samym kawałku, ale wymagają różnych ruchów.
  • Niektóre plansze nie są przejezdne przy użyciu tylko ruchów ukośnych.

Kod

class BishopPlayer(Player):
    def __init__(self):
        Player.__init__(self)
        self.coords = [Coordinate(1,-1),
                       Coordinate(1, 1),
                       ]
        self.inputs = [(x,y) for x in (0,1,2) for y in (-1,0,1)]

    def turn(self):
        # Move away from out of bounds areas
        if self.vision_at(0,-1) == -1:
            return self.coords[1]
        if self.vision_at(0,1) == -1:
            return self.coords[0]

        # Move right, and either up or down based on one bit of the genome
        bit_to_use = sum(self.vision_at(self.inputs[i][0],
                                        self.inputs[i][1]
                                        ) * (16 ** i) for i in range(9)
                         ) % 100
        return self.coords[self.bit_at(bit_to_use)]

Pomimo tych ograniczeń, w rzadkich przypadkach Biskup ma się dobrze, a poszczególne osobniki wypełniają kilka okrążeń planszy każdy. Myślałem, że na danym okrążeniu okaz może poruszać się tylko o połowę planszy (co odpowiada tylko czarnym kwadratom lub tylko białym kwadratom na szachownicy). Jednak, jak zauważył Martin Büttner, teleporter może przenieść okaz z czarnego kwadratu na biały kwadrat lub odwrotnie, więc na większości desek nie będą one ograniczone.

(Istnieją dwie pary dopasowanych typów teleporterów i każda z nich ma prawdopodobieństwo 0,5 przesunięcia, które przesuwa próbkę do drugiej połowy czarnych i białych kwadratów. Zatem prawdopodobieństwo, że tablica będzie miała tylko teleportery ograniczające próbkę do jednego połowa planszy na jedno okrążenie to tylko 0,25).

Wyniki pokazują, że okazjonalne triumfy są przeplatane długimi okresami nieosiągnięcia mety:

Wyniki: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 11 1 2 1 1 1 1 1 6 1 8 1 10 15 1 1 12544 1 2 1 1 1 1 3 7554 1 1 1 1 1

trichopaks
źródło
8

Run-bonus player: średnia geometryczna 50,35 (test w 5000 gier)

Ten bot ocenia kwadraty według indywidualnych kolorów na podstawie 6-bitowej sekcji DNA, takiej jak gracz oceniający kolory, ale z innym systemem liczbowym. Bot ten był motywowany myślą, że jeden z bitów zmienia wartość wyniku o 32, a inny tylko o 1. Przypisuje wartość n (n + 1) / 2 do serii n kolejnych 1 bitów. Dodatkowo dodaje mechanizm losowy, aby uniknąć utknięcia. Wykona losowy ruch do przodu z szansą 1 na 30.

Dla porównania, gracz z wynikiem kolorowym zdobył 30 do 35 punktów w kilku testach na 1000 gier. Co ciekawe, maksymalny wynik gracza z kolorem mieścił się w przedziale 3-5 milionów, podczas gdy maksymalny bonus run wynosił tylko 200 tys. Premia do uruchomienia korzysta z logarytmicznego systemu punktacji, uzyskując bardziej niezerowy wynik.

Uruchomienie 5000 gier zajęło około 20 minut z 6 wątkami na kontrolerze C ++.

coord_t runbonus(dna_t d, view_t v) {
    int ymax[3], nmax, smax = -1;
    if(!v.rng.rint(30)) {
        int y;
        while(!~v(1, y = v.rng.rint(-1, 1)));
        return {1, y};
    }
    for(int y = -1; y <= 1; y++) {
        if(v(1, y) == OUT_OF_BOUNDS) continue;
        int score = 0;
        int streak = 0;
        for(int i = 0; i < 6; i++) {
            if(d[6*v(1,y) + i])
                score += ++streak;
            else
                streak = 0;
        }
        if(score > smax) {
            smax = score;
            nmax = 0;
        }
        if(score == smax) ymax[nmax++] = y;
    }
    return {1, ymax[v.rng.rint(nmax)]};
}
feersum
źródło
z ciekawości, jak długo trwał test 5000 ścieżek? Moje szczury potrzebują ponad godziny, aby ukończyć 1000 utworów, więc musiałbym pozwolić, aby komputer działał całą noc, aby odtworzyć przypadek testowy.
@kuroineko Odpowiedź na twoje pytanie była już w mojej odpowiedzi.
feersum
UPS przepraszam. Spróbuję wtedy kodu na komputerze, aby zobaczyć, jaką rolę sprzęt odgrywa w różnicy prędkości. I może spróbuj użyć gcc zamiast MSVC. Zauważyłem 30% wzrost wydajności w porównaniu z MSVC na kilku innych bitach kodu obciążających obliczenia.
Twój kod działał w nieco ponad 20 minut dla 1000 ścieżek na moim [email protected] z 4 wątkami. Wynik wyniósł około 56 . Wydaje się, że oznacza to, że mój komputer jest 5 razy wolniejszy od twojego, a mój kod byłby około 6 razy wolniejszy na danym komputerze (ale uzyskanie lepszego wyniku mechanicznego oznacza dłuższy czas obliczeń). Ponieważ jestem zbyt spłukany, aby kupić nowy komputer, czas na trochę optymalizacji ...
8

StarPlayer | C ++ | Wynik: 162 (na podstawie przebiegu 500 gier)

Ten gracz próbuje użyć A *, aby znaleźć najlepszą drogę do przodu. Przypisuje wagi w taki sam sposób, jak ColorScorePlayer i próbuje znaleźć ścieżkę do prawej krawędzi widoku. Wdrożenie nie jest najładniejsze, jakie kiedykolwiek zrobiłem, ale przynajmniej nie jest zbyt wolne.

#include <utility>

#define IDX(a,b) a[VIEW_DIST + b.x][VIEW_DIST + b.y]

std::pair<coord_t,int> planAhead(int weights[N_COLORS], view_t &v, coord_t target) {
    bool open[VIEW_DIST*2+1][VIEW_DIST*2+1] = {false};
    bool closed[VIEW_DIST*2+1][VIEW_DIST*2+1] = {false};
    int f_score[VIEW_DIST*2+1][VIEW_DIST*2+1] = {0};
    int g_score[VIEW_DIST*2+1][VIEW_DIST*2+1] = {0};
    coord_t came_from[VIEW_DIST*2+1][VIEW_DIST*2+1] = {{0,0}};
    open[VIEW_DIST][VIEW_DIST] = true;
    g_score[VIEW_DIST][VIEW_DIST] = v.rng.rint(5);
    f_score[VIEW_DIST][VIEW_DIST] = (abs(target.x) + abs(target.y)) * 10;
    for (;;) {
        coord_t current{VIEW_DIST+1,0};
        for (int x = 0; x < (VIEW_DIST*2+1); x++)
            for (int y = 0; y < (VIEW_DIST*2+1); y++)
                if (open[x][y] && (current.x > VIEW_DIST || f_score[x][y] < IDX(f_score,current)))
                    current = {x - VIEW_DIST, y - VIEW_DIST};
        if (current.x > VIEW_DIST)
            return {{1,0}, 1000000};
        if (current.x == target.x && current.y == target.y)
            break;
        IDX(open,current) = false;
        IDX(closed,current) = true;
        for (int dx = -1; dx <= 1; dx++) for (int dy = -1; dy <= 1; dy++) {
            if (dx == 0 && dy == 0)
                continue;
            coord_t tentative{current.x + dx, current.y + dy};
            if (abs(tentative.x) > VIEW_DIST || abs(tentative.y) > VIEW_DIST)
                continue;
            if (IDX(closed,tentative))
                continue;
            auto color = v(tentative.x, tentative.y);
            if (color == OUT_OF_BOUNDS)
                continue;
            auto tentative_g = IDX(g_score,current) + weights[color];
            if (!IDX(open,tentative) || tentative_g < IDX(g_score,tentative)) {
                IDX(came_from,tentative) = current;
                auto distance = abs(tentative.x - target.x) + abs(tentative.y - target.y);
                IDX(f_score,tentative) = tentative_g + distance * 10;
                IDX(g_score,tentative) = tentative_g;
                IDX(open,tentative) = true;
            }
        }
    }
    auto prev = target, current = target;
    while (current.x != 0 || current.y != 0)
        prev = current, current = IDX(came_from,current);
    return {prev, IDX(g_score,target)};
}

coord_t starPlayer(dna_t d, view_t v) {
    const int chunklen = DNA_BITS / N_COLORS;
    int weights[N_COLORS];
    for (int i = 0; i < N_COLORS; i++)
        weights[i] = dnarange(d, i*chunklen, chunklen);
    std::pair<coord_t,int> choice{{1,0}, 1000000};
    for (int y = -VIEW_DIST; y <= VIEW_DIST; y++) {
        auto plan = planAhead(weights, v, {VIEW_DIST, y});
        if (plan.second < choice.second)
            choice = plan;
    }
    return choice.first;
}

Przykładowe wyniki:

4 92078 1 10 1 1 3 2 2862314 5 24925 1 3 2 126502 1 24 1097182 39 1 1 1 47728 227625 137944 15 1 30061 1 1 1 3171790 19646 10 345866 1 1 1 829756 425 6699 22 8 1 1 6 6 104889 125608 1

Anton
źródło
1
W 1000 grach uzyskałem wynik 133,2, fajnie.
feersum
7

WallGuesser - Zdobył 113,266 w teście 1000 gier

Kodowanie

Zrobiłem naprawdę proste kodowanie 6-bitowe / kolorowe. Aby zdekodować kolor [n]

  • Sumuj co n-ty bit w genomie do 96
  • Jeśli suma punktów wynosi> = 4, powiedz, że ten kwadrat jest zablokowany
  • Jeśli wynik sumaryczny wynosi <= 4, to wynik końcowy wynosi 2 ^ wyniku sumarycznego

Rozprowadzając bity koloru w genomie, zwiększam szansę, że bity od obojga rodziców zostaną użyte dla każdego koloru.

Ruch

Korzystam z (na pewno niezbyt wydajnego) wyszukiwania opartego na *, aby wyszukać ścieżkę o najniższym koszcie do dowolnego kwadratu z prawej krawędzi. Jeśli kolor zostanie odwzorowany na „zablokowany”, wyszukiwanie nigdy go nie wprowadzi. Jeśli wyszukiwanie nie może znaleźć ścieżki, zakłada, że ​​ten szczur nie jest zdolny do rozmnażania i próbuje go zakończyć, przesuwając jednego w lewo.

Zmniejszenie liczby niezdolnych szczurów

Ponieważ mój genom skutecznie zgaduje, które kwadraty są ścianami lub teleporterami do tyłu, szczury, które nie mają odgadnięć (żadnych kolorów, które odwzorowują na zablokowane), nie są bardzo sprawne. Aby spróbować usunąć te szczury, jeśli żaden kolor nie zostanie oznaczony jako zablokowany, KAŻDY kolor zostanie oznaczony jako zablokowany, a szczur zawsze przesunie się o jeden w lewo.

DO ZROBIENIA

Obecnie w zachowaniu nie ma losowości, więc szczury łatwo mogą utknąć.

#include "./gamelogic.cpp"

#include <algorithm>
#include <set>
#include <map>
#include <climits>

bool operator< (const coord_t &a, const coord_t &b){
    if(a.x != b.x){ return a.x < b.x; }
    else if (a.y != b.y){ return a.y < b.y; }
    else{ return false; }
}

bool operator== (const coord_t &a, const coord_t &b){
    return (a.x == b.x) && (a.y == b.y);
}

int coordDistance(const coord_t &a, const coord_t &b){
    int xDif = abs(a.x - b.x);
    int yDif = abs(a.y - b.y);
    return xDif > yDif ? xDif : yDif;
}

int coordMinSetDistance(const coord_t &a, const std::set<coord_t> &ends){
    int min = INT_MAX;
    for (auto i : ends){
        int cur = coordDistance(a, i);
        if (cur < min){
            min = cur;
        }
    }
    return min;
}


class ColorMap{
public:
    view_t *v;
    int colors[16] = {};
    const int Blocked = -1;

    ColorMap(dna_t &d, view_t *v){
        this->v = v;

        //Decode the genome
        for (int i = 0; i <= (16*6); i++){
            if (d.at(i) == true){
                colors[i % 16]++;
            }
        }

        //Encode the result
        bool guessedWalls = false;
        for (int i = 0; i < 16; i++){
            if (colors[i] >= 4){
                colors[i] = Blocked;
                guessedWalls = true;
            }
            else{
                colors[i] = pow(2, colors[i]);
            }
        }

        if (guessedWalls == false){
            for (auto i : colors){
                i = Blocked;
            }
        }
    }

    int operator() (coord_t pos){
        if (abs(pos.x) > VIEW_DIST || abs(pos.y) > VIEW_DIST){
            return Blocked;
        }

        int value = (*v)(pos.x, pos.y);
        if (value == OUT_OF_BOUNDS){
            return Blocked;
        }
        else{
            return colors[value];
        }
    }

    void print(){
        int lower = -1 * VIEW_DIST;
        int upper = VIEW_DIST;
        for (int y = lower; y <= upper; y++){
            for (int x = lower; x <= upper; x++){
                std::cout << std::setw(3) << this->operator()({ x, y });
            }
            std::cout << std::endl;
        }
    }
};

class node{
public:
    coord_t pos;
    coord_t cameFrom;
    int gScore;
    int minDistance;

    node(coord_t pos, coord_t cameFrom, int gScore, int minDistance){
        this->pos = pos;
        this->cameFrom = cameFrom;
        this->gScore = gScore;
        this->minDistance = minDistance;
    }

    int fScore() const{ return gScore + minDistance; };

    bool operator< (const node &rhs) const{ return fScore() < rhs.fScore(); }
};

class EditablePriorityQueue{
private:
    //This is reversed so smallest are on top
    struct lesser{
        bool operator()(node *a, node *b) const{
            return (*b) < (*a);
        }
    };

    std::vector<node*> queue; // Use heap functions to maintain the priority queue ourself
    std::map<coord_t, node*> members;

public:
    EditablePriorityQueue(){};

    ~EditablePriorityQueue(){
        for (auto &m : members){
            delete m.second;
        }
    }

    bool empty(){ return members.empty(); }

    node *top(){
        auto top = this->queue.front();
        std::pop_heap(queue.begin(), queue.end(), lesser());
        queue.pop_back();
        members.erase(top->pos);
        return top;
    }

    void set(coord_t target, coord_t cameFrom, int gScore, int minDistance){
        auto targetLocation = members.find(target);

        //If the target isn't a member add it
        if (targetLocation == members.end()){
            auto *newNode = new node(target, cameFrom, gScore, minDistance);
            queue.push_back(newNode);
            std::push_heap(queue.begin(), queue.end(), lesser());
            members[target] = newNode;
        }
        //The target must be updated
        else{
            auto currentNode = targetLocation->second;
            if (currentNode->gScore > gScore){
                currentNode->gScore = gScore;
                currentNode->cameFrom = cameFrom;
                std::make_heap(queue.begin(), queue.end()); //More efficient way to do this?
            }
        }
    }
};

std::pair<coord_t, int> pathCost(ColorMap &m, coord_t start, const std::set<coord_t> &ends){
    EditablePriorityQueue openSet;
    std::set<coord_t> closedSet;
    std::map<coord_t, coord_t> cameFrom;

    openSet.set(start, start, 0, coordMinSetDistance(start, ends));
    while (openSet.empty() == false){
        auto current = openSet.top();
        closedSet.insert(current->pos);
        cameFrom[current->pos] = current->cameFrom;

        //Check if we're done
        if (ends.count(current->pos) != 0){
            //Recover the path
            coord_t path = current->pos;
            int finalScore = current->gScore;
            delete current;
            while (!(cameFrom[path] == start)){
                path = cameFrom[path];
            }

            return{ path, finalScore };
        }               

        //Examine current's neighbours
        for (int x = -1; x <= 1; x++) for (int y = -1; y <= 1; y++){
            coord_t neighbour = { current->pos.x + x, current->pos.y + y };

            if (x == 0 && y == 0){ continue; }

            closedSet.count(neighbour);
            if (closedSet.count(neighbour) != 0){ continue; }

            int neighbourScore = m(neighbour);
            if (neighbourScore == m.Blocked){ continue; }

            int tentativeScore = current->gScore + neighbourScore;
            openSet.set(neighbour, current->pos, tentativeScore, coordMinSetDistance(neighbour, ends));

        }
        delete current;
    }

    return{ { -1, 0 }, INT_MAX }; //Try to end it
}

coord_t myPlayer(dna_t d, view_t v) {
    auto ourMap = ColorMap(d, &v);

    std::set<coord_t> edges;
    for (coord_t edge = { VIEW_DIST, -1 * VIEW_DIST }; edge.y <= VIEW_DIST; edge.y++){
        edges.insert(edge);
    }

    //Move to the neighbor closest to a square on the right
    auto result = pathCost(ourMap, { 0, 0 }, edges);
    auto minMove = result.first;

    return minMove;
}

int main() {
    slog << "Geometric mean score: " << runsimulation(myPlayer) << std::endl;
}
Ceribia
źródło
Hm, to się dla mnie nie kompiluje g++ -std=c++11 .\wallguesser.cpp -O2 -o .\wallguesser.exe. Dostaję dużo błędów, ale pierwszy to.\wallguesser.cpp:47:19: error: 'dna_t' has no member named 'at' if (d.at(i) == true){
Martin Ender
Nie ma problemu, wystarczy zmienić, ataby []to naprawić.
feersum
7

FITTEST - średni wynik geometryczny: ~ 922 (2 tys. Przebiegów)

Moje podejście polega na:

  1. Dowiedz się, co zabija gatunek i zdefiniuj pożądane zachowanie (funkcjonalne)
  2. Zaimplementuj pożądane zachowanie w kodzie (techniczne)
  3. Daj temu priorytet . Czy to jest ważniejsze czy mniej ważne niż inne pożądane zachowanie.
  4. Zoptymalizuj średnią geometryczną, modyfikując parametry roztworów.

Przetestowałem ponad 2000 zestawów parametrów z tymi samymi 50 nasionami. Wybrano najbardziej obiecujące zestawy, które oceniono za pomocą 250 identycznych nasion, a te z najwyższą rangą stanowiły wkład do następnej rundy testu. Udało mi się stworzyć algorytm genetyczny, aby znaleźć optymalny algorytm genetyczny dla tego problemu, zgodnie z sugestią użytkownika mbomb007 .

Pożądane zachowanie:

  1. Gatunek powinien dowiedzieć się, które kolory są bezpieczne, a które złe.
  2. Gatunek powinien przede wszystkim skoncentrować swoją decyzję na tym, gdzie się przenieść, na podstawie 3 komórek z przodu, ale jeśli nie są dostępne dobre ruchy, należy rozważyć ruchy pionowe lub do tyłu
  3. Gatunek powinien również spojrzeć na to, co znajduje się poza 8 komórkami wokół niego i wykorzystać to w informacjach przy podejmowaniu decyzji
  4. Gatunek powinien nauczyć się rozpoznawać pułapki .
  5. Niektóre gatunki niepoprawnie zakładają, że ściany są dobre i starają się do nich cały czas się poruszać, dlatego utkną przed ścianami. Jeśli są gatunkiem o najwyższym wyniku w tej chwili, ich DNA z błędnym założeniem o ścianie jest wielokrotnie powielane na noworodki . Po pewnym czasie wszystkie gatunki utknęły przed ścianami i żaden z nich nie osiągnął celu, aby zdobyć punkty. Jak zatrzymać kretynów?

Metody przechowywania danych:

Chcemy, aby gatunek nauczył się różnych rzeczy, przystosował do swojego środowiska, stał się najsilniejszy. Nieuchronnie działa to tylko wtedy, gdy nauka może być w jakiś sposób przechowywana. Uczenie zostanie „zapisane” w 100 bitach DNA. To dziwny sposób przechowywania, ponieważ nie możemy zmienić wartości naszego DNA. Więc założyć, że DNA już przechowuje informacje o złych i dobrych ruchów. Jeśli dla określonego gatunku przechowywana jest poprawna informacja w jego DNA, przejdzie on szybko do przodu i stworzy wiele nowych gatunków z jego DNA.

Odkryłem, że średnia geometryczna wyniku jest wrażliwa na sposób przechowywania informacji. Załóżmy, że czytamy pierwsze 4 bity ze 100 bitów DNA i chcemy zapisać to jako zmienną całkowitą. Możemy to zrobić na kilka sposobów:

  1. dziesiętne miejsce do przechowywania danych: używając funkcji „wbudowanej” dnarange, przykład: 10114 bity zamieniają się w „1x2 ^ 3 + 0x2 ^ 2 + 1x2 ^ 1 + 1x2 ^ 0 = 15. Możliwe wartości (dla 4 bitów): [0, 1 , 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
  2. smugi przechowywania danych: za pomocą dnaStreakRangefunkcji (zdefiniowanej poniżej), przykład: stanie się 4bity 1011 1x1 + 0x1 + 1x1+ 1x2 = 4. Możliwe wartości (dla 4 bitów): [0, 1, 2, 3, 6, 10]
int dnaStreakRange(dna_t &d, int start, int chunklen) {
    int score = 0;
    int streak = 0;
    for(int i = 0; i < chunklen; i++) {
        if(d[start + i])
            score += ++streak;
        else
            streak = 0;
    };  
    return score;
}
  1. przechowywanie danych bitowych: używając dnaCountRangefunkcji (zdefiniowanej poniżej), przykład: stanie się 4bity 1011 1x1 + 0x1 + 1x1 + 1x1 = 3. Możliwe wartości (dla 4 bitów): [0, 1, 2, 3, 4]
int dnaCountRange(dna_t &d, int start, int chunklen) {
    int score = 0;
    for(int i = 0; i < chunklen; i++) {
        if(d[start + i])
            score ++;
    };  
    return score;
}

Różnice między metodami przechowywania są następujące:

  • Metoda przechowywania dziesiętnego jest podatna na pojedynczą zmianę w DNA. Gdy wartość bitów zmienia się z 1011 na 0011, jej wartość zmienia się z 3 na 2, co jest niewielką zmianą.
  • Metoda dziesiętnego przechowywania jest jednorodna . Każda z możliwych wartości ma taką samą zmianę. Szansa na odczytanie wartości 15 z 4-bitowego bloku pamięci wynosi 1/16 = 6%. Metoda przechowywania smug nie jest jednorodna . Szansa, że ​​4-bitowa seria smug jest mniejsza lub równa 6 to (15-3) / 16 = 81% (wszystkie 16 kombinacji z wyjątkiem 0111,1110,111). Poniżej grafika przedstawiająca kształt rozkładu. Jak widać na niebieskiej strzałce, szansa, że ​​4-bitowa seria będzie mniejsza lub równa 6, wynosi 81%: wizualizacja rozkładu typów pamięci dziesiętnej, smugowej i bitowej dla liczb binarnych o długości 4,5 i 6 bitów

Priorytetyzuj rozwiązania.

Gdy ColorScorePlayer zidentyfikuje dwa ruchy do przodu z identycznymi wynikami, dokonuje się arbitralnego wyboru. IMHO, nigdy nie należy używać funkcji v.rng.rint()funkcji losowych . Zamiast tego powinieneś wykorzystać tę szansę na uzyskanie równych wyników jako haka do oceny rozwiązań dla efektów drugiego rzędu.

Efekt pierwszego rzędu otrzymuje najwyższy priorytet. Jeśli osiągnięte zostaną równe wyniki, pierwszeństwo ma rozwiązanie z priorytetem 2 i tak dalej. Dostosowując parametry rozwiązania, możesz wpłynąć na prawdopodobieństwo wystąpienia równych wyników i w ten sposób zmienić wagę rozwiązań o priorytecie 1 i priorytecie 2.

Realizacja pożądanego zachowania

Dowiedz się, które kolory są bezpieczne:

  • 33% z 16 kolorów jest złych i dlatego, gdy wynik ruchu jest poniżej 63/3, ruch nie będzie dozwolony. Dlatego threshold = 63/3=21, gdzie 63 to maksymalny wynik dla 6 bitów, a 33% = 1/3 (można to zobaczyć na powyższym wykresie).

Jeśli żadne dobre ruchy nie są dostępne, przesuń się w pionie lub w tył:

  • Jeśli nie są dozwolone żadne ruchy do przodu, ruchy pionowe będą porównywane ze sobą w ten sam sposób. Jeśli również nie są dozwolone ruchy pionowe, ruchy do tyłu są uszeregowane. Osiąga się to poprzez weightMovezmienną.

Zobacz, co jest poza:

  • Gdy 2 lub 3 ruchy mają identyczne wyniki, pole 3x3 wokół tych ruchów określi (poprzez x2i y2pętle), która opcja jest najlepsza (poprzez mainSubScorezmienną). Najbardziej odpowiednia kolumna w tym polu 3x3 prowadzi.
coord_t adjustedColorPlayer(dna_t d, view_t v) {
    const int chunklen = 6,threshold = 63/3;
    int xBestScore=0, yBestScore=0;
    long bestScore=-1, weightMove, weightMove2, mainScore;
    for(int x = -1; x <= 1; x++) {
        if (x < 0) weightMove = 1000; // moving backward
        if (x== 0) weightMove = 10000; //moving vertical
        if (x > 0) weightMove = 100000; //moving forward
        for(int y = -1; y <= 1; y++) {
            if(v(x, y) == OUT_OF_BOUNDS || (x==0&&y==0) ) continue;
            mainScore = dnarange(d,v(x,y)*chunklen,chunklen);
            if (mainScore<threshold+1) {
                mainScore =  0; //not a suitable move because too low score
            }else{
                mainScore*= weightMove;
                // when equal score, use sub score by examining 5x5 box to rank moves
                for(int x2 = x-1; x2 <= x+1; x2++){     
                    if (x2 < x) weightMove2 = 1; // moving backward
                    if (x2== x) weightMove2 = 10; //moving vertical
                    if (x2 > x) weightMove2 = 100; //moving forward
                    for(int y2 = x-1; y2 <= y+1; y2++){     
                        if(v(x2, y2) != OUT_OF_BOUNDS){
                            long mainSubScore = dnarange(d,v(x2,y2)*chunklen,chunklen);
                            if (mainSubScore>=threshold+1) mainScore+=mainSubScore*weightMove2;
                        }
                    }
                 }
            }
            if(mainScore > bestScore) {
                bestScore = mainScore;              
                xBestScore = x;
                yBestScore = y;
            }
        }
    }
    return{xBestScore,yBestScore};
}

Wynik: 123 (2 tys. Biegów)

Pierwsze 50 wyników (18 gier zdobyło tylko 1 punkt):

1 10 1 79947 3 1 11 125 7333287 23701 310869 53744 1 2 2 2 2 1 1 57556 2 688438 60 1 2 2636261 26306 1 125369 1 1 1 61895 27 1 36 1 91100 87636 1 2 47497 53 16 1 11 222384 1 1 1

Zidentyfikuj pułapki:

Zbadałem DNA gatunku o najwyższym wyniku, gdy dowolna gra zakończyła się przechowywaniem bitsum4 (więc ocena koloru ma zakres [0,4]):

  • ocena 0: Teleportacja do tyłu, obie ściany, 1x bezpieczne
  • zdobył 1: Pułapka do tyłu (tak nieszkodliwa), Teleportacja do tyłu, 1x bezpieczne
  • punktowany 2: Pułapka naprzód (tak niebezpieczna), 1x bezpieczny
  • punktacja 3: Teleportacja do przodu, 5x bezpieczny
  • ocena 4: Teleportacja do przodu, 1x bezpieczne

Z tego można wywnioskować, że mury i teleporty uzyskują prawidłowy wynik. Pułapki nie są identyfikowane, ponieważ zależą od kierunku i koloru pochodzenia, podczas gdy punktacja odbywa się na podstawie koloru miejsca docelowego. Dlatego istnieje potrzeba przechowywania również danych dotyczących koloru pochodzenia, więc v(0,0). W idealnym świecie chcielibyśmy przechowywać informacje dla 16 kolorów x 8 kierunków x 3 bity = 384 bity.

Niestety dostępnych jest tylko 100 bitów i nie możemy tego wszystkiego użyć, ponieważ potrzebujemy również trochę pamięci na wyjaśnione powyżej rozwiązanie. Dlatego wykonamy 4 kolorowe kosze:

  • 0: kolor 0 - kolor 3,
  • 1: kolor 4 - kolor 7,
  • 2: kolor 8 - kolor 11,
  • 3: kolor 12 - kolor 16

oraz 4 pojemniki kierunku ruchu

  • 0: przesuń w pionie lub do tyłu,
  • 1: przejdź do przodu,
  • 2: idź naprzód,
  • 3: przejdź do przodu w dół

Gdy wynik dziesiętny wynosi 4 lub więcej (100,101,110,111), zakłada się, że pułapka jest powiązana z tą komórką, w wyniku czego ruch ten nie zostanie wybrany, gdy pojawią się równe wyniki. Zatem identyfikacja pułapki jest efektem drugiego rzędu, a „zobacz, co jest poza” będzie rozwiązaniem trzeciego priorytetu.

int dnaLookup2(dna_t &d, int start, int chunklen, int storageMethod) {
    // Definition of storageMethod: 0=decimal, 1=streak, 2=bitsum
    int score = 0, streak = 0;
    for(int i = start; i < start+chunklen; i++) {
        int value = d[i];
        if (storageMethod==0) {
            score = (score << 1) |value;
        }else{
            if (storageMethod==1){
                if(value) score += ++streak; else streak = 0;
            }else{
                if(value) score ++;         
            }
        }
    };  
    return score;
}

coord_t theTrapFighter(dna_t d, view_t v) {
    // Definition of storageMethod: 0=decimal, 1=streak, 2=bitsum
    const int colorMemStorageMethod = 1, colorMemBlockSize = 3;
    const int trapMemStorageMethod = 0, trapMemBlockSize = 3;
    const int trapMemTopThreshold = 4, nDirBins = 4, nColorBins = 4;

    int xBestScore=0, yBestScore=0;
    long bestScore=-1, weightMove, weightMove2, mainScore;
  for(int x = -1; x <= 1; x++) {
        if (x < 0) weightMove = 1000; // moving backward
        if (x== 0) weightMove = 10000; //moving vertical
        if (x > 0) weightMove = 100000; //moving forward
        for(int y = -1; y <= 1; y++) {          
            int color = v(x, y);
            if(color == OUT_OF_BOUNDS || (x==0&&y==0) ) continue;
            mainScore = dnaLookup2(d,color*colorMemBlockSize,
             colorMemBlockSize,colorMemStorageMethod);
            if (mainScore==0) {
                //not a suitable move because too low score
            }else{
                mainScore*= weightMove;
                //lookup trap likelihood
                int directionBin = 0;
                if (nDirBins==3) directionBin = x>0?y+1:-1;
                if (nDirBins==4) directionBin = x>0?y+2:0;
                // put 16 colors in nColorBins bins
                int colorBin = v(0,0)*nColorBins/N_COLORS; 
                colorBin = colorBin>(nColorBins-1)?(nColorBins-1):colorBin;
                if (directionBin >= 0 &&
                 dnaLookup2(
                   d,
                   colorMemBlockSize*16
                    +trapMemBlockSize*(nColorBins*directionBin+colorBin),
                   trapMemBlockSize,
                   trapMemStorageMethod
                 ) >=trapMemTopThreshold){
                  //suspect a trap therefore no sub score is added                  
                 }else{
                    // when equal score, use sub score by examining 5x5 box to rank moves
                    for(int x2 = x-1; x2 <= x+1; x2++){     
                        if (x2 < x) weightMove2 = 1; // moving backward
                        if (x2== x) weightMove2 = 10; //moving vertical
                        if (x2 > x) weightMove2 = 100; //moving forward
                        for(int y2 = x-1; y2 <= y+1; y2++){     
                            int color2 = v(x2, y2);
                            if(color2 != OUT_OF_BOUNDS){
                                mainScore+=weightMove2 * dnaLookup2(d,color2*colorMemBlockSize,
                                 colorMemBlockSize,colorMemStorageMethod);
                            }
                        }
                    }               
                 }
            }
            if(mainScore > bestScore) {
                bestScore = mainScore;              
                xBestScore = x;
                yBestScore = y;
            }
        }
    }
    return{xBestScore,yBestScore};
}

Wynik: 580 (2 tys. Biegów)

Pierwsze 50 wyników (13 gier zdobyło tylko 1 punkt):

28 044 14 189 1 2 265 670 2 275 942 3 122 769 109 183 401 366 61 643 205 949 47 553 138 680 1 107,199 85 666 31 2 29 1 89,519 22 100,908 14 794 1 3 198 300 21 601 14 3 405 278 1 1 1 2 74 647 1 7 201 248 933 1

Błędne założenie dotyczące ściany jest wielokrotnie duplikowane przez noworodków:

Niektóre gatunki niepoprawnie zakładają, że ściany są dobre i starają się do nich cały czas się poruszać, dlatego utkną przed ścianami. Mogą również utknąć w nieskończonej pętli teleporterów. Efekt jest taki sam w obu przypadkach.

Głównym problemem jest to, że po kilkuset iteracjach niektóre geny stają się bardzo dominujące . Jeśli są to „właściwe” geny, możesz uzyskać bardzo wysokie wyniki (> 1 milion punktów). Jeśli są one błędne, utkniesz, ponieważ potrzebujesz różnorodności, aby znaleźć „właściwe” geny.

Walenie kretynów: Rozwiązanie 1: odwrócenie kolorów

Pierwszym rozwiązaniem, które wypróbowałem, była próba wykorzystania części nieużywanej pamięci, która wciąż jest bardzo zróżnicowana. Załóżmy, że przydzielono 84 bity do pamięci kolorów i pamięci wyszukiwania pułapek. Pozostałe 16 bitów będzie bardzo zróżnicowanych. Możemy wypełnić 2 zmienne dziesiętne8, które mają wartości w przedziale [0,255] i są one jednorodne, co oznacza, że ​​każda wartość ma szansę 1/256. Zmienne zostaną wywołane inInversei inReverse.

Jeśli inInversewynosi 255 (szansa 1/256), wówczas odwrócimy interpretację wyników kolorów . Tak więc ściana, którą kretyn uważa za bezpieczny ze względu na wysoki wynik, otrzyma niski wynik, a zatem stanie się złym ruchem. Wadą jest to, że wpłynie to również na geny „praw”, więc uzyskamy mniej bardzo wysokich wyników. Ponadto inInversegatunek ten będzie musiał się rozmnażać, a jego dzieci również otrzymają części dominującego DNA. Najważniejsze jest to, że przywraca różnorodność.

Jeśli inReversewynosi 255 (szansa 1/256), wówczas odwrócimy kolejność pozycji przechowywania wyników kolorów . Więc zanim kolor 0 był zapisywany w bitach 0-3. Teraz kolor 15 zostanie zapisany w tej pozycji. Różnica w inInversepodejściu polega na tym, że inReversecofną pracę wykonaną do tej pory. Wróciliśmy do punktu wyjścia. Stworzyliśmy gatunek, który ma podobne geny, jak w momencie rozpoczęcia gry (z wyjątkiem pamięci znalezienia pułapki)

Poprzez optymalizację badane jest, czy jest mądry, aby używać inInversei inReversew tym samym czasie. Po zakończeniu optymalizacji wynik nie wzrósł. Problem polega na tym, że mamy bardziej zróżnicowaną populację genów, ale wpływa to również na „właściwe DNA”. Potrzebujemy innego rozwiązania.

Morons walczy: Rozwiązanie 2: kod skrótu

Gatunek ma 15 możliwych pozycji początkowych i obecnie istnieje zbyt duża szansa, że ​​pójdzie dokładnie tą samą ścieżką, jeśli zacznie od tej samej pozycji początkowej. Jeśli jest kretynem, który kocha mury, utknie na tej samej ścianie w kółko. Jeśli na szczęście uda mu się dotrzeć do dalekiej ściany, zacznie dominować w puli DNA przy swoich błędnych założeniach. Potrzebujemy, aby jego potomstwo podążyło nieco inną ścieżką (ponieważ dla niego i tak jest już za późno) i nie utknie na dalekiej ścianie, ale na ścianie w pobliżu . Można to osiągnąć, wprowadzając kod skrótu .

Hashcode powinny mieć cel do jednoznacznej identyfikacji i oznakowania aktualną pozycję na planszy. Celem nie jest ustalenie pozycji (x, y), ale udzielenie odpowiedzi na pytania, czy moi przodkowie byli już w tej lokalizacji?

Załóżmy, że masz przed sobą całą planszę i utworzysz jpg każdego możliwego kwadratu 5 na 5 komórek. Otrzymasz (53-5) x (15-5) = 380 zdjęć. Dajmy tym obrazkom numery od 1 do 380. Nasz kod skrótu powinien być postrzegany jako taki identyfikator, z tą różnicą, że nie działa od 1 do 330, ale brakuje IDS, więc np. 563, 3424, 9424, 21245 itp.

unsigned long hashCode=17;
for(int x = -2; x <= 2; x++) {
    for(int y = -2; y <= 2; y++) {
        int color = v(x, y)+2;
        hashCode = hashCode*31+color;
    }
}       

Liczby pierwsze 17i 31są tam, aby zapobiec zniknięciu informacji dodanych na początku pętli. Później więcej o tym, jak zintegrować nasz hashcode z resztą programu.

Pozwala zastąpić mechanizm subcoringowy „patrz, co jest poza” innym mechanizmem subcoringowym. Kiedy dwie lub trzy komórki mają takie same wyniki główne, będzie 50% szans na wybranie górnej, 50% szans na wybranie dolnych komórek i 0% szans na wybranie środkowej. Szansa nie zostanie określona przez generator losowy, ale przez bity z pamięci , ponieważ w ten sposób upewniamy się, że w tej samej sytuacji dokonuje się tego samego wyboru.

W idealnym świecie (gdzie mamy nieskończoną ilość pamięci) obliczylibyśmy unikalny kod skrótu dla naszej obecnej sytuacji, np. 25881, i udaliśmy się do miejsca pamięci 25881 i przeczytaliśmy tam, czy powinniśmy wybrać górną lub dolną komórkę (gdy tam jest równym wynikiem). W ten sposób znaleźlibyśmy się w dokładnie takiej samej sytuacji (kiedy np. Po raz drugi podróżujemy po tablicy i zaczynamy w tej samej pozycji) podejmujemy te same decyzje. Ponieważ nie mamy nieskończonej pamięci, zastosujemy modulo wielkości dostępnej pamięci do kodu mieszającego . Obecny hashcode jest dobry w tym sensie, że rozkład po operacji modulo jest jednorodny.

Kiedy potomstwo podróżuje po tej samej planszy z nieznacznie zmienionym DNA, w większości przypadków (> 99%) podejmuje dokładnie taką samą decyzję. Ale im bardziej się zbliża, tym większa szansa, że ​​jego ścieżka będzie inna niż jego przodkowie. Zatem szansa, że ​​utknie na tej odległej ścianie jest niewielka. Utknął na tej samej pobliskiej ścianie, co jego przodek, jest stosunkowo duży, ale nie jest tak źle, ponieważ nie będzie generował dużo potomstwa. Bez metody hashcode szansa utknięcia na pobliskiej i odległej ścianie jest prawie taka sama

Optymalizacja

Po optymalizacji stwierdzono, że tablica identyfikacji pułapki nie jest potrzebna i wystarczają 2 bity na kolor. Pozostała część pamięci 100-2x16 = 68 bitów służy do przechowywania kodu skrótu. Wygląda na to, że mechanizm kodu skrótu jest w stanie uniknąć pułapek.

Zoptymalizowałem dla 15 parametrów. Ten kod zawierał najlepszy zestaw poprawionych parametrów (jak dotąd):

int dnaLookup(dna_t &d, int start, int chunklen, int storageMethod,int inInverse) {
    // Definition of storageMethod: 0=decimal, 1=streak, 2=bitsum
    int score = 0;
    int streak = 0;
    for(int i = start; i < start+chunklen; i++) {
        int value = d[i];
        if (inInverse) value = (1-d[i]);            
        if (storageMethod==0) {
            score = (score << 1) |value;
        }else{
            if (storageMethod==1){
                if(value) score += ++streak; else streak = 0;
            }else{
                if(value) score ++;         
            }
        }
    };  
    return score;
}

coord_t theFittest(dna_t d, view_t v) {
    // Definition of storageMethod: 0=decimal, 1=streak, 2=bitsum
    const int colorMemStorageMethod = 2, colorMemBlockSize = 2, colorMemZeroThreshold = 0;
    const int useTrapMem = 0, trapMemStorageMethod = -1, trapMemBlockSize = -1;
    const int trapMemTopThreshold = -1, nDirBins = -1, nColorBins = -1;
    const int reorderMemStorageMethod = -1, reorderMemReverseThreshold = -1;
    const int reorderMemInverseThreshold = -1;
    // Definition of hashPrority: -1: no hash, 0:hash when 'look beyond' scores equal,
    // 1: hash replaces 'look beyond', 2: hash replaces 'trap finder' and 'look beyond'
    // 3: hash replaces everything ('color finder', 'trap finder' and 'look beyond')
    const int hashPrority = 2;
    int inReverse = reorderMemReverseThreshold != -1 && 
     (dnaLookup(d,92,8,reorderMemStorageMethod,0) >= reorderMemReverseThreshold);
    int inInverse = reorderMemInverseThreshold != -1 && 
     (dnaLookup(d,84,8,reorderMemStorageMethod,0) >= reorderMemInverseThreshold);
    int trapMemStart=N_COLORS*colorMemBlockSize;
    unsigned long hashCode=17;
    int moveUp=0;
    if (hashPrority>0){
        for(int x = -2; x <= 2; x++) {
            for(int y = -2; y <= 2; y++) {
                int color = v(x, y)+2;
                hashCode = hashCode*31+color;
            }
        }       
        unsigned long hashMemStart=N_COLORS*colorMemBlockSize;
        if (useTrapMem==1 && hashPrority<=1) hashMemStart+=nDirBins*nColorBins*trapMemBlockSize;
        if (hashPrority==3) hashMemStart=0;
        int hashMemPos = hashCode % (DNA_BITS-hashMemStart);
        moveUp = dnaLookup(d,hashMemStart+hashMemPos,1,0,inInverse);
    }

    int xBestScore=0, yBestScore=0;
    long bestScore=-1, weightMove, weightMove2, mainScore;
    for(int x = -1; x <= 1; x++) {
        if (x < 0) weightMove = 1000; // moving backward
        if (x== 0) weightMove = 10000; //moving vertical
        if (x > 0) weightMove = 100000; //moving forward
        for(int y = -1; y <= 1; y++) {          
            int color = v(x, y);
            if (inReverse) color = 15-v(x, y);
            if(color == OUT_OF_BOUNDS || (x==0&&y==0) ) continue;
            //when MoveUp=1 -> give move with highest y most points (hashScore=highest)
            //when MoveUp=0 -> give move with lowest y most points (hashScore=lowest)
            int hashScore = (y+2)*(2*moveUp-1)+4; 
            mainScore = dnaLookup(
              d,
              color*colorMemBlockSize,
              colorMemBlockSize,
              colorMemStorageMethod,
              inInverse
             );
            if (mainScore<colorMemZeroThreshold+1) {
                mainScore =  0; //not a suitable move because too low score
            }else{
                mainScore*= weightMove;
                //lookup trap likelihood
                int directionBin = 0;
                if (nDirBins==3) directionBin = x>0?y+1:-1;
                if (nDirBins==4) directionBin = x>0?y+2:0;
                // put 16 colors in nColorBins bins
                int colorBin = v(0,0)*nColorBins/N_COLORS; 
                if (inReverse) colorBin = (15-v(0,0))*nColorBins/N_COLORS; 
                colorBin = colorBin>(nColorBins-1)?(nColorBins-1):colorBin;
                if (useTrapMem && directionBin >= 0 &&
                 dnaLookup(
                   d,
                   trapMemStart+trapMemBlockSize*(nColorBins*directionBin+colorBin),
                   trapMemBlockSize,
                   trapMemStorageMethod,
                   0
                 )>=trapMemTopThreshold){
                  //suspect a trap therefore no sub score is added                  
                 }else{
                    if (hashPrority>=1){
                        mainScore+=hashScore;
                    } else{
                        // when equal score, use sub score by examining 5x5 box to rank moves
                        for(int x2 = x-1; x2 <= x+1; x2++){     
                            if (x2 < x) weightMove2 = 1; // moving backward
                            if (x2== x) weightMove2 = 10; //moving vertical
                            if (x2 > x) weightMove2 = 100; //moving forward
                            for(int y2 = x-1; y2 <= y+1; y2++){     
                                int color2 = v(x2, y2);
                                if (inReverse) color2 = 15-v(x2, y2);
                                if(color2 != OUT_OF_BOUNDS){
                                    long mainSubScore = dnaLookup(
                                      d,
                                      color2*colorMemBlockSize,
                                      colorMemBlockSize,
                                      colorMemStorageMethod,
                                      inInverse
                                    );
                                    if (mainSubScore>=colorMemZeroThreshold+1){
                                        mainScore+=mainSubScore*weightMove2;
                                    }
                                }
                            }
                        }
                    }               
                 }
            }
            if (hashPrority==2 || (useTrapMem<=0 && hashPrority>=1)) mainScore+=hashScore*10;
            if (hashPrority==3) mainScore=hashScore*weightMove;         

            if(mainScore > bestScore) {
                bestScore = mainScore;              
                xBestScore = x;
                yBestScore = y;
            }
        }
    }
    return{xBestScore,yBestScore};
}   

Wynik: 922 (2 tys. Biegów)

Pierwsze 50 wyników (9 gier zdobyło tylko 1 punkt):

112747 3 1 1 876,965 8 57 214,921 218,707 2 512 937 11 38 389 336,941 1 6 915 2 219,471 74 289 31 116 133,162 1 5 633,066 166 463 515,204 1 86 744 17 362 196 697 1 6 122 126 363 39 3945 1 4 172,168 1 169 329 239 969 399 239 969 399 239 998

To mój pierwszy program w C ++. Jak większość z was ma teraz doświadczenie w analizie gnomów. Chcę podziękować organizatorom, ponieważ bardzo podobała mi się praca nad tym.

Jeśli masz jakieś uwagi, zostaw komentarz poniżej. Przepraszamy za długie teksty.

Ruut
źródło
Uważam, że analiza pułapki jest dość interesująca.
Czy próbowałeś innej funkcji skrótu, na przykład xoring 25 wartości kolorów widzianych jako 12,5 16 bitowych słów i przyjmowanie modulo? Nie jestem przekonany, że zgodność liczb pierwszych daje lepszą jednorodność, ale nie jestem wielkim matematykiem.
Czy zastanawiałeś się również nad dodaniem algorytmu ścieżki? Wydaje się, że jest to ogromny czynnik poprawy niezależnie od genomu, ponieważ ograniczy ruchy do tych, które testują zdolność genomu tylko wzdłuż ścieżek, które znacznie częściej prowadzą do zwycięskiej pozycji.
kuroi, dziękuję za twoją opinię. Nie próbowałem xoringa, ponieważ nie znam się na operacjach binarnych w c ++. Zakładam, że masz na myśli 12,5 8 bitowych słów? Czy używasz Xoring?
Ruut
Możesz spojrzeć na mój kod „twardego wierzącego”, aby zobaczyć, jakiego rodzaju funkcji skrótu używam. Zasadniczo pomijam komórki poza ścieżką i uważam kolory na ścieżce za części o wysokim i niskim rzędzie 16-bitowego słowa. Wszystkie te słowa są kumulowane z XOR w rejestrze, który jest następnie dzielony przez rozmiar tablicy mieszającej. Tak długo, jak maksymalna wartość skrótu (65535) jest znacznie większa niż rozmiar stołu (<100), moduł ma dobrą siłę rozprzestrzeniania. Testowałem go na szerokim zestawie losowo generowanych siatek i wydaje się, że ma dobrą jednorodność.
6

Pathfinder, C ++, wstępny wynik 35,8504 (50 rund)

Całkowity remont! Przeniesiłem mój algorytm do C ++ i trochę go poprawiłem, ale wynik wciąż nie jest zbyt wysoki, prawdopodobnie dlatego, że szczury wciąż walą głową w ściany. Mam dość próbowania tego poprawić, więc na razie pozwolę.


int dnarange(dna_t &d, int start, int len) {
    int res = 0;
    for(int i = start; i < start+len; i++) {
        res = (res << 1) | d[i];
    }
    return res;
}

int dnasum(dna_t &d, int start, int len) {
    int res = 0;
    for(int i = start; i < start+len; i++) {
        res += d[i];
    }
    return res;
}

int dnaweight(dna_t &d, int start) {
    return d[start] + d[start+1] + 2*d[start+2] + 2*d[start+3] + 3*d[start+4];
}

int trap_d [16] = {1,0,1,1,0,1,-1,1,-1,0,-1,-1,0,-1,1,-1}; //immutable
int nhood [10] = {1,0,1,1,1,-1,0,1,0,-1}; //immutable

coord_t pathfinder(dna_t d, view_t v) {
  int is_trap[16] = {0};
  int pos_or_weight[16] = {0};
  int u_weight = dnaweight(d, 80);
  for (int i = 0; i < 16; i++) {
    int status = dnarange(d, 5*i, 2);
    if (status == 1) {
      is_trap[i] = 1;
      pos_or_weight[i] = dnarange(d, 5*i + 2, 3);
    } else {
      pos_or_weight[i] = dnaweight(d, 5*i);
    }
  }
  int w_area[7][4] = {0};
  for (int j = 0; j < 7; j++) {
    w_area[j][3] = u_weight;
  }
  for (int i = 0; i < 3; i++) {
    w_area[0][i] = u_weight;
    w_area[6][i] = u_weight;
  }
  int d_coeff = dnaweight(d, 85);
  for (int i = 0; i < 3; i++) {
    for (int j = 1; j < 6; j++) {
      int p_or_w, color = v(i, j-3);
      if (color != OUT_OF_BOUNDS) {
    p_or_w = pos_or_weight[color];
      } else {
    p_or_w = 1000;
      }
      if (color != OUT_OF_BOUNDS && is_trap[color] && i+trap_d[2*p_or_w] >= 0) {
    w_area[j + trap_d[2*p_or_w + 1]][i + trap_d[2*p_or_w]] += d_coeff;
      } else {
    w_area[j][i] += p_or_w;
      }
    }
  }
  for (int i = 3; i >= 0; i--) {
    for (int j = 0; j < 7; j++) {
      int min_w = 1000;
      for (int k = std::max(0, j-1); k <= std::min(6, j+1); k++) {
    min_w = std::min(min_w, w_area[k][i + 1]);
      }
      w_area[j][i] += min_w;
    }
  }
  int speed = dnasum(d, 90, 5);
  w_area[2][0] += 2 + speed;
  w_area[4][0] += 2 + speed;
  int goal = dnaweight(d, 95);
  int min_w = 10000;
  int sec_w = 10000;
  int min_x, min_y, sec_x, sec_y, w;
  for (int i = 0; i < 5; i++) {
    w = w_area[nhood[2*i + 1] + 3][nhood[2*i]];
    if (w < min_w) {
      sec_w = min_w;
      sec_x = min_x;
      sec_y = min_y;
      min_w = w;
      min_x = nhood[2*i];
      min_y = nhood[2*i + 1];
    } else if (w < sec_w) {
      sec_w = w;
      sec_x = nhood[2*i];
      sec_y = nhood[2*i + 1];
    }
  }
  if (min_w > goal) {
    int r = v.rng.rint(5);
    return {nhood[2*r], nhood[2*r+1]};
  } else if (sec_w <= goal && v.rng.rint(100) < 2*speed) {
    return {sec_x, sec_y};
  }
  return {min_x, min_y};
}

Wyjaśnienie

Ogólną ideą jest sklasyfikowanie każdego koloru jako pułapkę lub nie, a następnie przypisanie kierunków pułapek i ciężarów do pułapek bez pułapek i próba podążenia ścieżką minimalnej masy do prawej granicy siatki wizji.

W pierwszych 80 bitach genomu każdy kolor jest klasyfikowany za pomocą 5 bitów abcde. Jeśli ab = 01kolor jest pułapką i cdekoduje jego kierunek (osiem możliwości). Jeśli ab ≠ 01kolor nie jest pułapką, a jego waga to a + b + 2*(c + d + e).

Następnie inicjalizujemy siatkę 3x7, która reprezentuje pole widzenia szczura po jego prawej stronie, wypełnione „nieznanymi” kolorami. Bity 80-84 kodują wagę nieznanych komórek podobnie jak kolory bez pułapek, a bity 85-89 kodują wspólną wagę pułapek. Wypełniamy siatkę ciężarkami, obliczamy najkrótsze ścieżki i dodajemy dodatkowy ciężar (zakodowany w bitach 90-95) do komórek bezpośrednio powyżej i poniżej szczura, aby zniechęcić do omijania kroków. Bity 95–99 kodują wagę bramkową. Jeśli minimalna waga ścieżki jest poniżej, szczur prawdopodobnie utknął gdzieś i kontynuuje ruch losowo (ale nigdy nie wraca). W przeciwnym razie podąża ścieżką minimalnego ciężaru. Z małym prawdopodobieństwem w zależności od masy zapobiegającej bocznicu, szczur wybiera zamiast tego ścieżkę masy od drugiej do minimalnej. Ma to na celu zapobieganie przywieraniu do ścian (ale wydaje się, że obecnie nie działa zbyt dobrze).

Zgarb
źródło
Uruchomiłem wdrożenie na moim komputerze. Zajęło mi to kilka godzin. Otrzymuje średni wynik 7,848433940863856 punktów. pastebin.com/d50GcwnK
Jakube
@Jakube Wielkie dzięki! To znacznie gorsze niż się spodziewałem, ale teraz, gdy znów patrzę na kod, widzę kilka błędów i innych dziwactw. Spróbuję później przenieść to na C ++, abym mógł to przeanalizować sam.
Zgarb,
5

LookAheadPlayer C ++ ≈ 89,904

Moją pierwotną myślą było poszukiwanie 4 bitów, które pasują do koloru, którego szukałem, i użycie kilku następnych bitów jako wyniku. To był okropny pomysł, prawdopodobnie z powodu mutacji.

Pomyślałem więc o sposobach ochrony przed mutacjami i zwrotnicami, co przypomniało mi o pracy, jaką wykonałem przy dekodowaniu kodu QR. W kodach QR dane są dzielone na bloki i paski, aby uniknąć błędów powodujących zniszczenie zbyt dużej części danej części danych.

Dlatego, podobnie jak ColorScorePlayer, pocięłam DNA na 16 części i wykorzystuję je jako wynik. Jednak wyniki są rozłożone tak, że poszczególne bity każdego wyniku nie są sąsiadujące. Następnie sumuję wynik zarówno aktualnych możliwych ruchów, jak i kolejnych potencjalnych ruchów i wybieram najlepszy ruch do wykonania.

Uwaga: zostało to zakodowane / przetestowane na MinGW. Nie można go skompilować z optymalizacjami ani z wielowątkowością. Nie mam faktycznej instalacji Linuksa ani programu Visual Studio, aby użyć kompilatora, w którym będą działać. Będę testować to szybko jutro rano, ale daj mi znać, jeśli napotkasz jakieś problemy.

// get striped color score, 6 bits per color. should be
// resistant to getting erased by a crossover
void mapColorsBitwise(dna_t &d, int* color_array) {
    for (int i=0; i<N_COLORS; i++) {
        int score = 0;
        for (int j=0; j<6; j++) {
            score = (score<<1) | d[ j*N_COLORS + i ];
        }
        color_array[i] = score;
    }
}

// label for the lookup tables
enum direction_lut {
    UP_RIGHT=0, RIGHT, DOWN_RIGHT
};

// movement coord_t's to correspond to a direction
static const coord_t direction_lut[3] = {
    { 1, -1 }, { 1, 0 }, { 1, 1 }
};

// indexes into the arrays to denote what should be summed
// for each direction.
static const int sum_lut[3][6] = {
    { 3, 4, 8, 8, 9, 14 }, { 9, 13, 13, 14, 14, 19 },
    { 14, 18, 18, 19, 23, 24 }
};

coord_t lookAheadPlayer(dna_t d, view_t v) {
    int scoreArray[25] = { 0 };
    int colorScores[N_COLORS] = { };

    // Get color mapping for this iteration
    mapColorsBitwise(d, colorScores);

    for (int y=-2; y<=2; y++) {
        for (int x=0; x<=2; x++) {
            // Get the scores for our whole field of view
            color_t color = v(x,y);
            if (color != OUT_OF_BOUNDS)
                scoreArray[ (x+2)+((y+2)*5) ] += colorScores[color];
        }
    }

    // get the best move by summing all of the array indices for a particular
    // direction
    int best = RIGHT;
    int bestScore = 0;
    for (int dir=UP_RIGHT; dir<=DOWN_RIGHT; dir++) {
        if (v(direction_lut[dir].x, direction_lut[dir].y) == OUT_OF_BOUNDS)
            continue;

        int score = 0;
        for (int i=0; i<6; i++) {
            score += scoreArray[ sum_lut[dir][i] ];
        }

        if (score > bestScore) {
            bestScore = score;
            best = dir;
        }
    }

    return direction_lut[best];
}
aschmack
źródło
5

SlowAndSteady C ++ (wynik 9,7)

Nie możemy polegać na interpretacji fragmentów genomu jako liczb, ponieważ pojedyncze odwrócenie bitów może mieć radykalnie różne efekty w zależności od jego pozycji. Dlatego po prostu używam 16 6-bitowych segmentów i oceniam je według liczby 1s. Początkowo 111111było dobre i 000000złe, i chociaż nie ma to znaczenia na dłuższą metę (po pełnym rozwinięciu genomu) w początkowej konfiguracji DNA większość segmentów ma 2-4, więc przełączyłem się na używanie 9 - (#1 - 3)^2do punktacji, to pozwala na znacznie większą swobodę ruchu w pierwszych rundach i szybszą ewolucję.

Teraz patrzę tylko na 7 najbliższych sąsiadów, dodam odchylenie kierunku do wyniku koloru i poruszam się losowo w jednym z najwyższych kierunków.

Chociaż sam wynik nie jest bardzo wysoki, moje stworki osiągają linię mety i zdobywają> 1 w 3/4 przypadków.

coord_t SlowAndSteadyPlayer(dna_t d, view_t v) {
    const int chunklen = 6;
    int color_scores[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
    for(int i=0; i<16; i++){ //count ones
        for(int j=0; j<chunklen; j++){
            color_scores[i] += d[i*chunklen + j];
        }
    }

    int moves[7][2] = {
        {-1,1}, {0,1}, {1,1},
                       {1,0},
        {-1,-1},{1,-1},{-1,-1}
    };
    int movescores[7];
    int smax = -1;
    int nmax = 0;
    int best_moves[7];
    for(int m=0; m<7; m++){ //compute the score for each move
        int temp_color = v(moves[m][0], moves[m][1]);
        if(temp_color == OUT_OF_BOUNDS){
            movescores[m] = 0;
            continue;
        }
        int dir_bias[3] = {1,3,6};
        int temp_score = 9-(color_scores[temp_color]-3)*(color_scores[temp_color]-3) + dir_bias[moves[m][0]+1];
        movescores[m] = temp_score;

        if(temp_score > smax) {
            smax = temp_score;
            nmax = 0;
        }
        if(temp_score == smax) best_moves[nmax++] = m;
    }

    int best_chosen = v.rng.rint(nmax);
    return {moves[best_moves[best_chosen]][0], moves[best_moves[best_chosen]][1]};
}

I próbka punktacji na 100 tablicach

Scores: 5 4 13028 1 1 101 2 24 1 21 1 4 2 44 1 1 24 8 2 5 1 13 10 71 2 19528 6 1 69 74587 1 1 3 138 8 4 1 1 17 23 1 2 2 50 7 7 710 6 231 1 4 3 263 4 1 6 7 20 24 11 1 25 1 63 14 1 2 2 1 27 9 7 1 7 31 20 2 17 8 176 3 1 10 13 3 142 1 9 768 64 6837 49 1 9 3 15 32 10 42 8

Średni wynik geometryczny: 9,76557

DenDenDo
źródło
Czy wynik, o którym wspominasz dla jednej planszy przy użyciu standardowego wskaźnika mutacji lub skorygowanej wartości?
trichoplax
„moje zwierzaki osiągają linię mety i zdobywają> 1 w 3/4 przypadków” Chciałbym, aby metryka punktacji wynagrodziła to
Sparr
5

WeightChooser | C # | Wyniki: 220,8262 w 1520 grach

Oblicza wagę możliwego następnego ruchu (niebieski) na podstawie średniej masy możliwych następnych ruchów (żółty)

using ppcggacscontroller;
using System.Linq;
using System;

public class WeightChooser
{
    public static ppcggacscontroller.Program.Coord[] cspcoords = new[] {
            new Program.Coord(1, -1),
            new Program.Coord(1, 0),
            new Program.Coord(1, 1),
        };

    const int dnaBits = 4;

    public static void move(GameLogic.IView v, GameLogic.IGenome g, Random rnd, out int ox, out int oy)
    {
        var gcrds = cspcoords.Where(c => viewVal(v, c) > -1)
            .OrderByDescending(p => getBitsSet(g, viewVal(v, p)))
            .ThenByDescending(gt => weight(v, g, gt));

        Program.Coord nextMove = gcrds.First();
        ox = nextMove.x;
        oy = nextMove.y;
    }

    private static uint getBitsSet(GameLogic.IGenome g, int vVal)
    {
        uint i = g.cutOutInt(dnaBits * vVal, dnaBits);
        i = i - ((i >> 1) & 0x55555555);
        i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
        return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
    }

    private static int viewVal(GameLogic.IView v, Program.Coord c)
    {
        return v[c.x, c.y];
    }

    private static double weight(GameLogic.IView v, GameLogic.IGenome g, Program.Coord toGo)
    {
        double w = 0;

        int y = (toGo.y + v.yd) - 1;
        int i = 0;
        for (; i <= 2; i++)
        {
            int field = v[toGo.x + 1, (y + i) - v.yd];
            if (field > -1)
                w += getBitsSet(g, field);
        }

        return w / i;
    }
}

Scores: 32, 56103, 1361, 3351446, 33027, 23618, 22481, 1172713, 1, 3, 1, 1, 1, 2 88584, 106357, 1, 1232, 1, 1651280, 16690, 1, 1, 23732, 207554, 53, 69424, 1, 1,  79361, 1, 1, 51813, 229624, 25099, 2, 1, 234239, 362531, 1, 1, 19, 7295, 1, 7, 2, 196672, 1654208, 73453, 1, 23082, 1, 8, 5, 1685018, 4, 20, 1, 1, 1, 1, 1, 144 671, 122309, 10, 94752, 100895, 1, 54787, 54315, 252911, 79277, 1159, 241927, 94 347, 1, 318372, 37793, 1, 1, 1345310, 18934, 169700, 1, 1, 3, 186740, 83018, 121 758, 1, 358, 1935741, 88, 1, 1, 1, 1, 7, 21, 51144, 2, 1, 267638, 1, 1, 3, 1, 1,  1, 1, 674080, 47211, 8879, 7, 222766, 67214, 2, 89, 21038, 178463, 92846, 3, 14 0836, 1, 1, 111927, 1, 92165, 1, 192394, 1, 1, 2563722, 1, 42648, 1, 16, 1, 1, 2 85665, 1, 212653, 1, 4, 20513, 3, 135118, 13161, 2, 57, 78355, 3, 3, 44674, 8, 1 , 226472, 1, 1, 31588, 19619, 1, 2931870, 60814, 1, 1, 33867, 60740, 20558, 1, 1 5, 3, 5, 1, 1, 1, 60737, 450636, 468362, 1, 1, 347193, 91248, 551642, 1, 427215,  1, 57859, 17, 15, 66577, 24192, 1, 63560, 6568, 40279, 68216, 23098, 180732, 1,  1, 3041253, 1, 253488, 60535, 1, 1, 150838, 7361, 72855, 290699, 104644, 1, 763 01, 378, 1, 89220, 1, 262257, 2, 2, 1, 117, 105478, 33, 1, 65210, 1, 117588, 1, 1, 24320, 12, 3714568, 81152, 1, 1, 10125, 2, 1, 22, 1, 45201, 1, 1, 10518, 1, 1 , 1, 1, 34, 210021, 1, 1, 1, 65641, 6, 72, 1, 7, 2, 161578, 1, 1, 38378, 1, 4113 741, 1, 34450, 244212, 127660, 1, 256885, 46, 2, 1, 1, 103532, 1, 503965, 114774 , 52450, 124165, 73476, 50250, 1, 3, 3755352, 24928, 1, 1, 51, 11, 1, 210580, 1,  62375, 1, 1, 92745, 341232, 167675, 86, 242, 293710, 454841, 1, 49840, 4456758,  121378, 145323, 74904, 5048, 25459, 1, 57, 116999, 1, 1, 76074, 111447, 95706, 1, 1, 52631, 166756, 2159474, 161216, 1, 2, 3, 11904, 1, 22050, 6, 1, 1, 1, 41, 48908, 6, 80878, 28125, 28, 160516, 1, 4, 1, 8, 1, 1, 7, 362724, 1, 397193, 1, 2 5, 1, 59926, 3, 74548, 2320284, 470189, 1, 108, 1, 1, 16, 1, 496013, 1, 1, 1, 1,  107758, 1, 284144, 146728, 1, 70769, 94215, 1, 1, 9961, 97300, 7, 1, 76263, 1, 27, 294046, 40, 8, 2, 1, 57796, 2, 79800, 1043488, 470547, 1, 1, 1, 6, 69666, 8,  1, 1, 344011, 205325, 3963186, 1141527, 61598, 446029, 1, 1, 1, 1, 625247, 1877 92, 136391, 1, 72519, 1, 141168, 412, 98491, 103995, 297052, 1, 1, 1, 1, 3, 17, 9, 62899, 5, 47810, 254, 26789, 2, 1, 1, 3, 10361, 19615, 40430, 17288, 3, 71831 , 41374, 1, 91317, 409526, 1, 184305, 1, 192552, 3, 3587674, 39, 13, 134500, 41,  42, 672, 559835, 9, 39004, 51452, 1, 1, 12293, 11544, 265766, 8590, 1, 8632, 1,  1, 61849, 35155, 1, 74798, 72773, 1, 89, 37, 4, 4405882, 1, 99, 44397, 5, 4, 6,  1, 1, 1, 515818, 78383, 20, 127829, 1824801, 157, 1, 1, 268561, 19, 2, 230922, 1, 103, 98146, 5029789, 304324, 1, 5, 60516, 1, 139, 28982, 7, 20755, 187083, 1,  1, 143811, 37697, 1, 1, 269819, 83, 1, 202860, 13793, 16438, 113432, 1, 1, 2, 5 134384, 29, 84135, 39035, 2, 125, 1, 30, 129771, 41982, 13548, 61, 1, 2, 1, 82, 102, 2, 105581, 210399, 291204, 3012324, 1, 84763, 1, 1, 442067, 2, 1, 1, 1, 116 , 1, 3, 3, 56, 208807, 1, 2, 1, 14, 29, 31286, 1, 1, 162358, 28856, 46898, 1, 16 2698, 1, 1, 1, 65, 1, 1, 234566, 6, 1, 1, 128, 124, 2167692, 181946, 29, 1, 1, 1 , 1, 17, 162550, 179588, 4, 226480, 28, 1, 158512, 35084, 1, 26160, 17566, 1, 81 826, 2, 33, 1, 1, 11, 1, 230113, 1, 1, 1, 24405, 17, 1, 2, 1, 162365, 2, 1, 1, 8 5225, 1, 15016, 51509, 1, 5, 1, 93, 13, 59, 24548, 1, 3, 2, 2, 1, 64424, 1, 1, 4 , 1, 1, 1, 2, 267115, 139478, 52653, 96225, 1, 1, 35768, 3, 1, 1, 3280017, 8, 80 014, 43095, 112102, 1, 1, 1, 79594, 5, 1, 1, 4, 455714, 19, 15, 1, 233760, 55850 5, 2, 2, 1, 63672, 1, 3732951, 1, 135858, 134256, 452456, 151573, 79057, 638215,  88820, 1, 1, 76517, 13, 314006, 5, 1, 17704, 1, 79589, 1, 18371, 530793, 59020,  1, 1, 1, 4, 1, 1, 1, 71735, 1, 1, 1, 1, 1, 37894, 1, 2, 24054, 1, 8, 26471, 34,  1, 48033, 5, 3, 1, 25, 101, 1, 1, 5, 1, 1, 1, 97521, 1, 682817, 286486, 5, 1472 4, 1, 7805226, 6, 1, 1, 1, 7, 2, 1, 1, 1, 25, 233330, 1, 20899, 3417337, 92793, 23, 80821, 1, 1, 115948, 264191, 3, 79809, 1, 2, 59531, 2, 1, 1, 28684, 97, 1, 2 69433, 98769, 1, 76608, 138124, 1, 1, 325554, 122567, 1, 1, 3, 689604, 4, 85823,  66911, 138091, 169416, 21430, 1, 2, 486654, 108446, 93072, 1, 67907, 4, 1, 1, 5 2260, 67867, 210496, 25157, 1, 1, 1, 5477, 2, 2, 11907, 106, 48404, 1, 1, 1, 787 11, 190304, 112025, 1, 9313, 143055, 40189, 315537, 157581, 70714, 6, 180600, 38 594, 103658, 59444, 7, 31575, 1, 1, 581388, 370430, 1, 114446, 1, 1, 2, 3968, 1,  1, 1, 1, 1, 4523411, 1, 1, 270442, 1, 59, 235631, 3, 110196, 9, 1, 93724, 1, 22 917, 1, 6, 1, 2350266, 1, 1, 20, 4686858, 31, 1, 240180, 10, 470592, 3, 61051, 1 45372, 2831, 64052, 10, 120652, 255971, 479239, 1, 387659, 1, 1, 1, 378379, 7, 3 3218, 55914, 1, 1, 1667456, 6, 2, 74428, 3, 2, 1, 121582, 121274, 19651, 59899, 1, 11, 406670, 137835, 100269, 2, 164361, 98762, 44311, 25817, 178053, 31576, 1,  8, 2539307, 121430, 1, 41001, 1, 4, 1, 116258, 91101, 1, 126857, 1, 8, 49503, 1 , 489979, 12, 500332, 1, 52, 4, 8786, 4, 4878652, 12354, 27480, 89115, 87560, 11 793, 5, 1, 4702325, 301188, 1, 1, 1, 1, 1, 416520, 49357, 230103, 24497, 1, 3, 2 , 57366, 183021, 1, 1, 1, 1, 1, 2, 2, 2546229, 1, 2, 38665, 1, 6903, 1, 89519, 9 5119, 64879, 1, 1, 160380, 474336, 3107, 1, 7, 29099, 28667, 3, 196933, 35979, 1 2924, 7, 1, 99885, 6, 1, 1, 1, 7, 1, 1, 1, 1, 65727, 1, 1, 1, 1, 2108110, 3, 107 811, 23818, 701905, 1, 156034, 32, 1, 29, 143548, 1, 67665, 4612762, 1, 3, 20, 1 , 1, 9, 28543, 1, 1, 1, 30978, 9, 1, 19504, 79412, 15375, 763265, 1, 352373, 193 045, 1, 4570217, 9, 1, 6, 29180, 90030, 1, 1, 1, 1, 1, 93, 1, 100889, 1, 1, 37, 15, 17, 1, 81184, 1, 2, 272831, 1, 137, 1, 9, 42874, 679183, 1, 350027, 12, 1, 2 , 1, 26408, 1, 11182, 1, 30, 139590, 7, 3, 1, 1, 34729, 1, 2, 1, 1, 50343, 66873 , 3891, 1, 148952, 1, 1, 22322, 104176, 1, 3, 20549, 140266, 37827, 30504, 17, 6 8588, 120195, 1, 123353, 2, 64301, 11, 1, 109867, 4, 1, 1, 1, 28671, 1, 50963, 5 4584, 1, 1, 1, 33, 1, 381918, 1, 265823, 4771840, 155179, 314, 134086, 1, 1, 30,  1, 2, 1102665, 18, 132243, 3861, 1, 1, 208906, 60112, 1, 1, 1, 31273, 551, 3490 0, 2, 43606, 1, 1, 1, 1, 5, 2, 88342, 2, 1, 19, 3, 1, 1, 1, 1, 28507, 1, 491467,  1, 1, 22, 1, 1, 1, 1, 9345, 9, 18, 84343, 1, 2, 1, 18, 36816, 1, 1, 513028, 287 88, 5037383, 721932, 170292, 108942, 539115, 1, 575676, 20, 1, 31698, 99797, 205 21, 380986, 1, 1, 14, 2, 1, 201100, 30, 1, 119484, 1, 1, 1, 1, 2214252, 3, 4, 18 179, 9, 4, 542150, 1, 6, 157, 3182099, 4, 1, 1, 6140, 3339847, 498283, 52523, 1,  1, 1, 1, 1, 202054, 263324, 1, 6, 2, 1, 2, 72357, 12, 5, 66, 4, 7368, 1, 30706,  61936, 3945270, 138991, 1, 68247, 1, 1, 30482, 35326, 1, 1, 9, 1, 148, 1, 46985 , 1, 4325093, 1, 1, 2880384, 65173, 1, 56581, 179178, 372369, 56187, 3, 12, 8, 4 00743, 3, 28658, 1, 1, 9, 1, 4, 2, 34357, 1, 42596, 68840, 2, 62638, 158027, 617 34, 71263, 1, 1, 9, 1, 6830309, 3, 1, 1, 157253, 129837, 9, 5008187, 48499, 5981 3, 1, 40320, 233893, 5, 1383, 7732178, 16, 1, 13, 5686145, 84554, 1, 79442, 1, 1 , 256812, 127818, 31, 226113, 1, 4, 1, 1, 4506163, 1, 4, 1, 40176, 19107, 205, 2 7, 1, 448999, 1, 1, 2750, 62723, 1, 12, 1, 1, 79881, 1, 48, 13, 4, 1, 28765, 1, 33, 291330, 30817, 2, 1, 1, 1, 4170949, 16, 1, 1, 118781, 10473, 520797, 1, 8, 1 , 80215, 1, 21759, 5143209, 79141, 40229, 1, 17403, 71680, 1115694, 1, 1, 1, 10,  1, 77149, 382712, 1, 11, 84891, 47633, 1, 2, 39037, 1, 213148, 1607280, 127674,  1, 333207, 1, 78901, 1, 16203, 87580, 1, 1565571, 537902, 53000, 15, 1, 2, 1, 2 13127, 1, 338634, 2469990, 469479, 9519, 51083, 1, 42082, 33179, 1, 1, 32444, 3,  1, 201642, 99724, 377, 1, 2, 1, 36919, 1, 322707, 2, 164765, 82516, 1, 5274643,  1, 36421, 1, 8, 1, 117856, 1, 1, 493342, 1, 36289, 7, 1, 62, 2, 1, 38533, 1, 68 , 45754, 9, 102015, 312941, 1, 99 
Final score is 220.826222910756
Alexander Herold
źródło
5

SZCZURY W AKCJI (nie odpowiedź, ale narzędzie graficzne dla botów C ++)

Od początku tego wyzwania miałem trudności z ustaleniem, co naprawdę stoją szczurom na torze.
W końcu zhakowałem kontroler i napisałem boczne narzędzie, aby uzyskać graficzną reprezentację ścieżki.
W końcu zrobiłem więcej hackowania i dodałem wizualizację możliwych ścieżek DNA danego szczura.

Mapa jest bardzo zagracona i wymaga trochę przyzwyczajenia się, ale zrozumiałem, jak działają moje boty.

Oto przykład:

przykładowy utwór

Prawdopodobnie będziesz musiał powiększyć, aby zobaczyć cokolwiek, więc oto tylko pierwsza połowa:

pół utworu (bez zamierzonej gry słów)

Najpierw spójrzmy na ścieżki szczura. Istnieje jedna ścieżka dla każdej możliwej lokalizacji początkowej (zwykle 15, czasem nieco mniej). Zwykle łączą się, idealnie prowadząc do pojedynczego miejsca zwycięstwa.

Ścieżki są reprezentowane przez duże proste strzałki. Kolor opisuje wynik:

  • zielony: wygrana
  • żółty: nieskończona pętla
  • brązowy: walenie w ścianę
  • czerwony: niefortunny wypadek

W tym przykładzie mamy 12 zwycięskich pozycji początkowych, jedną prowadzącą do nieskończonej pętli i dwie do wyczerpującej śmierci (jak się wydaje, teleportowana w pułapkę).

Nieciągłości ścieżki wynikają z teleportacji, którą można śledzić za pomocą odpowiednich zakrzywionych strzałek.

Teraz kolorowe symbole. Reprezentują znaczenie 16 kolorów (szare przedstawiają to, co widzi szczur).

  • ściana: kwadratowa
  • teleporter: 4 rozgałęziona gwiazda
  • wykrywacz pułapek: mały oktogon

puste kolory są ... no cóż ... puste.

Teleportery mają wychodzące strzałki wskazujące miejsce docelowe.

Detektory pułapek mają również strzałki wskazujące pułapkę, która jest przedstawiona jako czerwone kółko.
W jednym z 9 przypadków pułapka znajduje się w tej samej komórce co jej detektor, w którym to przypadku zobaczysz mały oktogon na czerwonym kółku.

W tym przykładzie jest tak w przypadku jasnożółtej pułapki.
Możesz również zobaczyć fioletowe detektory pułapek skierowane w dół do wskazanej pułapki.

Zauważ, że czasami czerwone koło pułapki będzie ukryte pod ścianą. Oba są śmiertelne, więc wynik jest taki sam w przypadku teleportacji.
Zauważ też, że pułapka może znajdować się na teleporterze, w którym to przypadku teleporter ma pierwszeństwo (tzn. Szczur jest teleportowany przed wpadnięciem w pułapkę, w efekcie neutralizując pułapkę).

Wreszcie, szare symbole reprezentują to, co widzą moje szczury (tj. Znaczenie, jakie ich genom przypisuje kolorom).

  • ściana: kwadratowa
  • wykrywacz pułapek: octogon
  • pułapka: X

Zasadniczo wszystkie komórki siedzące na szarym kwadracie są uważane przez szczura za ściany.
Wielkie X reprezentują komórki uważane za pułapki, a odpowiadające im oktogony wskazują wykrywacz, który je zgłosił.

W tym przykładzie obie ściany są oznaczone jako takie, podobnie jak jasnożółta pułapka (wskazująca rzeczywiście śmiertelną komórkę, więc przedstawienie jej jako ściany jest prawidłowe).
Fioletowy detektor pułapki został zidentyfikowany jako taki (znajduje się na szarym oktogonie), ale lokalizacja pułapki jest niepoprawna (widać, że niektóre czerwone kółka nie mają pod nimi krzyży).

Z 4 teleporterów 2 są uważane za ściany (turkusowe i jasnobrązowe), a 2 jako puste komórki (czerwonawe i żółtawe).

Kilka pustych komórek jest uważanych za detektory pułapek lub ściany. Patrząc uważnie, widać, że te „wadliwe detektory” rzeczywiście zabraniają wejścia do komórek, które wpędzałyby szczura w kłopoty, więc nawet jeśli nie pasują do rzeczywistych kolorów, mają określony cel.

Kod

Cóż, to bałagan, ale działa całkiem dobrze.

Widząc z kodu gracza, dodałem tylko jeden interfejs: funkcję śledzenia używaną do zgłaszania znaczenia danego DNA. W moim przypadku użyłem 3 typów (ściana, wykrywacz pułapek i pusty), ale możesz zasadniczo generować wszystko, co jest związane z kolorem (lub wcale, jeśli nie chcesz grafiki związanej z genomem).

Zhakowałem kontroler, aby wygenerować ogromny ciąg znaków zestawiający opis toru i kolorów z „suchym przebiegiem” DNA szczura ze wszystkich możliwych lokalizacji.

Oznacza to, że wyniki będą naprawdę znaczące tylko wtedy, gdy bot nie użyje losowych wartości. W przeciwnym razie wyświetlane ścieżki będą reprezentować tylko jeden możliwy wynik.

Na koniec wszystkie te ślady są umieszczane w dużym pliku tekstowym, który jest następnie odczytywany przez narzędzie PHP, które generuje dane wyjściowe grafiki.

W bieżącej wersji robię migawkę za każdym razem, gdy szczur umiera po osiągnięciu nowej maksymalnej sprawności (która pokazuje całkiem dobrze progresywne udoskonalanie genomu bez wymagania zbyt wielu migawek), a także ostatnią migawkę na końcu gry (która pokazuje najbardziej udane DNA).

Jeśli ktoś jest zainteresowany, mogę opublikować kod.

Oczywiście działa to tylko dla botów C ++ i będziesz musiał napisać funkcję śledzenia i ewentualnie zmodyfikować kod PHP, jeśli chcesz wyświetlić dane specyficzne dla genomu (szare cyfry w moim przypadku).
Nawet bez informacji specyficznych dla DNA możesz bardzo łatwo zobaczyć ścieżki, którymi podąża twoje DNA na danej mapie.

Dlaczego wynik pośredni?

Przede wszystkim C ++ nie ma przyzwoitej przenośnej biblioteki graficznej, o której można mówić, szczególnie w przypadku MSVC. Nawet jeśli kompilacje Win32 są zwykle dostępne, często przychodzą one na później, a liczba potrzebnych bibliotek zewnętrznych, pakietów i innych potrzebnych uniksów sprawia, że ​​pisanie szybkiej i prostej aplikacji graficznej jest strasznym bólem w części ciała, którego nie pozwala przyzwoitość ja od nazywania.

Rozważyłem użycie Qt (o jedynym środowisku, które sprawia, że ​​przenośne GUI / projektowanie graficzne w C ++ jest prostym i nawet przyjemnym zadaniem, IMHO - prawdopodobnie dlatego, że dodaje system przesyłania wiadomości à la Objective C, którego C ++ bardzo brakuje i robi niesamowitą robotę ograniczania pamięci zarządzanie do najmniejszego minimum), ale wyglądało to na przesadne wykonanie zadania (i każdy, kto chce skorzystać z kodu, musiałby zainstalować duży SDK - chyba nie warte wysiłku).

Nawet przy założeniu przenośnej biblioteki nie ma potrzeby mówić o szybkości (jedna sekunda, aby wygenerować obraz jest w dużej mierze wystarczająca), a dzięki przysłowiowej sztywności i nieodłącznemu bałaganowi C ++ z pewnością nie jest najlepszym narzędziem do tego zadania.

Co więcej, posiadanie pośredniego tekstu wyjściowego zapewnia dużą elastyczność. Gdy już tam są dane, możesz je wykorzystać do innych celów (na przykład analizując wydajność botów).

Dlaczego PHP

Uważam, że język jest niezwykle prosty i elastyczny, bardzo wygodny do tworzenia prototypów. Uczyniłem go moim językiem domowym dla wyzwań kodu, które nie wymagają ekstremalnych osiągów.
Jest to okropny język do gry w golfa, ale golf i tak nigdy nie był moją filiżanką herbaty.

Podejrzewam, że Python lub Ruby byłyby równie przyjemne w użyciu do tego samego celu, ale nigdy nie miałem okazji z nimi zrobić poważnej pracy, a ostatnio pracowałem na stronach internetowych, więc PHP jest.

Nawet jeśli nie znasz języka, modyfikacja kodu w zależności od potrzeb nie powinna być trudna. Tylko nie zapomnij o $s przed zmiennymi, tak jak stare dobre podstawowe dni :).


źródło
1
Czy możesz udostępnić swoje narzędzie? W twojej odpowiedzi nie widzę kodu ani linku.
Franky
5

SkyWalker - Python - ocenia mniej niż 231 w 50 grach

Więc najpierw kod, a potem kilka wyjaśnień. Mam nadzieję, że nic się nie zepsuło podczas kopiowania.

class SkyWalker(Player):
    def __init__(self):
        Player.__init__(self)
        self.coords = [#Coordinate(-1,-1),
                       #Coordinate( 0,-1),
                       Coordinate( 1, 0),
                       Coordinate( 1,-1),
                       #Coordinate(-1, 0),
                       #Coordinate( 0, 0),
                       #Coordinate(-1, 1),
                       #Coordinate( 0, 1),
                       Coordinate( 1, 1)]

        self.n_moves = len(self.coords)

    def visionToMove(self, x, y):
        x = x - 2
        y = y - 2

        return (x, y)

    def trapToMove(self, x, y, offx, offy):
        x = x - 2 + (offx % 3) - 1
        y = y - 2 + (offy % 3) - 1
        return (x, y)

    def isNeighbour(self, x1, y1, x2, y2):
        if (x1 == x2) or (x1+1 == x2) or (x2+1 == x1):
            if (y1 == y2) or (y1+1 == y2) or (y2+1 == y1):
                return True
        return False

    def calcMove(self, donots, never, up):
        forwards = {(1, 0): 0, (1, 1): 0, (1, -1): 0, (0, 1): 10, (0, -1): 10}

        for key in forwards:
            if key in never:
                forwards[key] = 100
            for x in donots:
                if (key[0] == x[0]) and (key[1] == x[1]):
                    forwards[key] = 20

        min_value = min(forwards.itervalues())
        min_keys = [k for k in forwards if forwards[k] == min_value]

        return random.choice(min_keys)

    def turn(self):
        trap1 = self.bit_chunk(0, 4)
        trap1_offsetx = self.bit_chunk(4, 2)
        trap1_offsety = self.bit_chunk(6, 2)
        trap2 = self.bit_chunk(8, 4)
        trap2_offsetx = self.bit_chunk(12, 2)
        trap2_offsety = self.bit_chunk(14, 2)
        wall1 = self.bit_chunk(16, 4)
        wall2 = self.bit_chunk(20, 4)
        tel1 = self.bit_chunk(24, 4)
        tel1_good = self.bit_chunk(28, 3)
        tel2 = self.bit_chunk(31, 4)
        tel2_good = self.bit_chunk(35, 3)
        tel3 = self.bit_chunk(38, 4)
        tel3_good = self.bit_chunk(42, 3)
        tel4 = self.bit_chunk(45, 4)
        tel4_good = self.bit_chunk(49, 3)
        up = self.bit_at(100)

        donots = []
        never = []

        for y in range(0, 5):
            for x in range(0, 5):
                c = self.vision[y][x]
                if (c == -1):
                    never += self.visionToMove(x, y),
                elif (c == trap1):
                    donots += self.trapToMove(x, y, trap1_offsetx, trap1_offsety),
                elif (c == trap2):
                    donots += self.trapToMove(x, y, trap2_offsetx, trap2_offsety),
                elif (c == wall1):
                    donots += self.visionToMove(x, y),
                elif (c == wall2):
                    donots += self.visionToMove(x, y),
                elif (c == tel1):
                    if (tel1_good > 3):
                        donots += self.visionToMove(x, y),
                elif (c == tel2):
                    if (tel2_good > 3):
                        donots += self.visionToMove(x, y),
                elif (c == tel3):
                    if (tel3_good > 3):
                        donots += self.visionToMove(x, y),
                elif (c == tel4):
                    if (tel4_good > 3):
                        donots += self.visionToMove(x, y),

        coord = self.calcMove(donots, never, up)

        return Coordinate(coord[0], coord[1])

Niektóre wyjaśnienia

Moim zdaniem główna różnica polega na tym, że nie koduję każdego koloru. Zamiast tego próbuję zapisać liczbę ważnych kolorów. Moim zdaniem te kolory to pułapki, ściany i teleportery. Próbka nie musi znać koloru dobrej komórki. Dlatego mój genom ma następującą strukturę.

  • 2 x 8 bitów na pułapki, pierwsze 4 bity to numer koloru, a pozostałe 4 to przesunięcie
  • 2 x 4 bity do ścian, tylko kolor
  • 4 x 7 bitów dla teleporterów, znowu 4 bity dla koloru, 3 do wyboru dobrego lub złego

To daje w sumie 52 bitów. Używam jednak tylko pierwszego bitu z 3 decydujących teleporterów (sprawdzam, czy liczba jest większa 3). Dlatego pozostałe 2 mogą zostać usunięte, pozostawiając mi 44 używane bity.

Przy każdej turze sprawdzam każde pole mojej wizji, czy jest to zły kolor (+ poza planszą -1) i dodam go do listy pól, do których okaz nie chce się przenieść. W przypadku pułapki dodaję pole znajdujące się na zapisanym przesunięciu dla tego koloru pułapki.

Na podstawie listy tych złych pól obliczany jest następny ruch. Kolejność preferowanych pól jest następująca:

  1. Naprzód
  2. Góra czy dół
  3. do tyłu w górę lub w dół
  4. wstecz

Jeśli obowiązują dwa pola kategorii, jedno jest wybierane losowo.

Wyniki

Individual scores: [192, 53116, 5, 1649, 49, 2737, 35, 5836, 3, 10173, 4604, 22456, 21331, 445, 419, 2, 1, 90, 25842, 2, 712, 4, 1, 14, 35159, 13, 5938, 670, 78, 455, 45, 18, 6, 20095, 1784, 2, 11, 307853, 58171, 348, 2, 4, 190, 7, 29392, 15, 1158, 24549, 7409, 1]
On average, your bot got 231.34522696 points

Myśli

  • Nie mam pojęcia, czy mam szczęście z 50 biegami, czy też w mojej strategii jest mądrość.

  • Moje biegi nigdy nie wydają się startować i osiągać bardzo wysokie wyniki, ale mają też tendencję do znajdowania przynajmniej kilka razy bramki

  • Niewielka przypadkowość jest dobra, aby nie utknąć w pułapce w pobliżu końca wyścigu

  • Myślę, że nietypowe kolory nigdy nie są złe. Jednak ich wystąpienia mogą być złe, gdy znajdują się na granicy pułapki. Dlatego też oznaczanie koloru jako zły, jeśli nie jest to pułapka, ściana lub zły teleporter, nie ma sensu.

  • Ściany są największymi wrogami

Ulepszenia

Po pierwsze, chociaż będę tęsknił za obserwowaniem, jak czarne kwadraty zbliżają się coraz bardziej do celu, konieczny jest port C ++, aby przeprowadzić więcej testów i uzyskać bardziej znaczący wynik.

Jednym z głównych problemów jest to, że jeśli przed szczurem znajdują się złe komórki (lub te, które okazują złe), łatwo poruszają się w kółko w górę iw dół. W takich przypadkach można to zatrzymać lub zmniejszyć, patrząc na 2 ruchy do przodu, i zapobiec przeniesieniu się na pole, na którym po prostu cofnie się.

Często zajmuje dużo czasu, zanim szczur z dobrymi genami osiąga cel i zaczyna rozprzestrzeniać geny. Może potrzebuję strategii, aby zwiększyć różnorodność w tych przypadkach.

Ponieważ teleportery są trudne do obliczenia, może powinienem podzielić populację na tych, którzy są ryzykowni i zawsze biorą dobre teleportery oraz tych, którzy są bardziej zaniepokojeni i biorą je tylko wtedy, gdy nie ma innego wyboru.

Powinienem jakoś wykorzystać drugą połowę mojego genomu.

Dominik Müller
źródło
Próbuję również przechowywać kolory, ale w końcu doszedłem do wniosku, że to nie działa, ponieważ dostaniesz podwójne. Na przykład jeśli self.bit_chunk(16, 4)i self.bit_chunk(20, 4)masz zarówno wartość 0010, że skutecznie przechowujesz informacje tylko o jednej z dwóch pułapek.
Ruut
Musiałem dodać wcięcie do jednej linii, aby uruchomić to - domyślam się, że zgubiło się podczas kopiowania i wklejania. Dodałem go teraz również do twojego kodu.
trichoplax
Dla każdego, kto chce to uruchomić: Działa w Pythonie 2 i można go uruchomić w Pythonie 3, zmieniając pojedyncze wystąpienie itervaluesna values.
trichoplax
Mam następujące wyniki: [6155, 133, 21, 12194, 8824, 3, 3171, 112, 111425, 3026, 1303, 9130, 2680, 212, 28, 753, 2923, 1, 1, 4140, 107, 1256 , 90, 11, 104, 1538, 63, 917, 8, 1, 709, 11, 304, 212, 2, 43, 5, 4, 206, 8259, 75, 28, 7, 1, 11, 5, 1 , 1244, 1398, 13] Średnia geometryczna 122,9220309940335
trichopaks
Wygląda na to, że musielibyśmy uruchomić o wiele więcej niż 50 gier, aby uzyskać wiarygodny wynik.
trichoplax
3

Python, NeighboursOfNeighbors, wynik = 259,84395 w ponad 100 grach

To jest odmiana w ColorScorePlayer. Co 6 bitów przechowuje wynik jakości dla kwadratu. Gdy bot wykonuje ruch, ocenia każdy z 3 kwadratów do przodu - przekątnej w górę, do przodu i przekątnej w dół. Wynik to jakość kwadratu plus połowa średniej jakości kolejnych 3 kwadratów. To daje botowi pewne spojrzenie w przyszłość, nie przytłaczając jakości pierwszego kwadratu. Algorytm jest podobny do LookAheadPlayer, którego nie widziałem przed napisaniem tego rozwiązania.

class NeighborsOfNeighbors(Player):
  def __init__(self):
    Player.__init__(self)
    self.coords = [ Coordinate( 1, 0),
                    Coordinate( 1,-1),
                    Coordinate( 1, 1)
                    ]

  def turn(self):
    scores=[self.score(c.x,c.y)+0.5*self.adjacentScore(c.x,c.y) if self.vision_at(c.x,c.y)>-1 else None for c in self.coords ]
    max_score = max(scores)
    return random.choice( [c for s,c in zip(scores,self.coords) if s==max_score] )

  def adjacentScore(self,x,y):
    adjacent = [(x+1,y)]
    if self.vision_at(x,y+1)>-1:
      adjacent+=[(x+1,y+1)]
    if self.vision_at(x,y-1)>-1:
      adjacent+=[(x+1,y-1)]
    adjscores=[self.score(a,b) for a,b in adjacent]
    return sum(adjscores)/float(len(adjscores))

  def score(self,x,y):
    return -1 if self.vision_at(x,y) == -1 else self.bit_chunk(6*self.vision_at(x,y),6)
użytkownik2487951
źródło
W jednej linii brakowało wcięcia. Myślę, że zgubił się podczas wklejania. Dodałem go.
trichoplax
Działając w Pythonie 3 narzekał na porównywanie Brak przy obliczaniu maksimum (wyników). Więc zmienił else Nonesię else 0w stosunku do poprzedniego wiersza, aby obliczyć swój wynik. Mam nadzieję, że nie zmieni to twojej logiki (nie wprowadziłem żadnych zmian w twoim kodzie tutaj na SE poza dodaniem zagubionego wcięcia).
trichoplax
Działając w Pythonie 3, otrzymałem następujące wyniki dla tej odpowiedzi: [1, 13085, 360102, 1, 73713, 1, 189, 1, 1, 193613, 34, 195718, 199, 8, 1, 1, 60006, 66453, 2, 2, 53, 425206, 1, 4, 1, 1, 16, 153556, 1, 18134, 35655, 1, 4211684, 2, 1, 26451, 8, 1, 724635, 69242, 38469, 796553, 111340, 1, 25, 40017, 76064, 66478, 209365, 3925393]
trichoplax
Średnia geometryczna 428,3750848244933
trichopaks
2

ROUS (gryzonie o nietypowym rozmiarze), Java, wynik = 0

To powoduje, że otoczenie decyduje, dokąd pójść. Ponieważ kontroler Java nie działa, nie mam na to punktów. Zajedzie to bardzo daleko, jeśli znajdzie kilka teleporterów, aby mu pomóc.To ma tendencję do wyginięcia i awarii sterownika raz na jakiś czas. Wynika to prawdopodobnie z faktu, że jego naturalnym środowiskiem jest Bagno Ognia.

import java.awt.*;
import java.util.Map;

public class ROUS extends Player{

    private static final int NUMBER_OF_GENES = 33;
    private static final int GENE_SIZE = 3;
    private static final Point[] coords = new Point[]{
        new Point(-1, -1),
        new Point(-1, 0),
        new Point(-1, 1),
        new Point(0, -1),
        new Point(0, 1),
        new Point(1, -1),
        new Point(1, 0),
        new Point(1, 1)
    };

    public Point takeTurn(String dna, Map<Point, Integer> vision){
        Point[] table = decode(dna);
        int hash = hash(vision);
        return table[hash];
    }

    private int hash(Map<Point, Integer> surroundings) {
        return Math.abs(surroundings.hashCode()) % NUMBER_OF_GENES;
    }

    private Point[] decode(String dna) {
        Point[] result = new Point[NUMBER_OF_GENES];

        for (int i = 0; i < NUMBER_OF_GENES; i++){
            int p = Integer.parseInt(dna.substring(i * GENE_SIZE, (i + 1) * GENE_SIZE), 2);
            int x;
            int y;

            result[i] = coords[p];
        }
        return result;
    }
}
Numer jeden
źródło
1
Kontroler Java działa teraz.
Martin Ender
3
Na początku myślałem, że składasz hołd starożytnej Rosji, ale jak się wydaje, robił to Reiner.
Minimalna możliwa ocena to 1
trichopaks
@trichoplax ... awaria kontrolera ...
TheNumberOne
Och, rozumiem - więc to zdarza się wystarczająco często, że nie możesz dotrzeć do końca biegu?
trichoplax
2

Szary kolor Lookahead (C ++, ~ 1,35)

Ten średnio nie radzi sobie zbyt dobrze, ale w rzadkich przypadkach działa znakomicie. Niestety, jesteśmy oceniani na podstawie średniej geometrycznej (1,35), a nie na podstawie maksymalnej oceny (20077).

Algorytm działa po prostu za pomocą 4-bitowych szarych kodów, aby zamapować wynik każdego koloru gdzieś od -2 do 2 (z odchyleniem w kierunku zakresu [-1..1]) i oblicza wynik każdego kafelka każdego ruchu i jego kolejnych ruchów . Używa również 2-bitowego szarego kodu, aby określić mnożnik dla samego kafelka, a także współczynnik przesunięcia w przypadku przejścia w prawo. (Szare kody są znacznie mniej podatne na duże skoki z powodu mutacji, chociaż tak naprawdę nie robią żadnych korzyści dla krzyżowania środkowego punktu kodowego ...)

Nie robi też absolutnie nic, by próbować specjalnie obchodzić się z pułapkami, i podejrzewam, że może to być upadek (chociaż nie dodałem żadnych instrumentów do kontrolera, aby przetestować tę teorię).

Dla każdego możliwego ruchu określa wynik, a spośród wszystkich ruchów o najwyższym wyniku wybiera losowo.

coord_t colorTileRanker(dna_t d, view_t v) {
    const int COLOR_OFFSET = 0; // scores for each color (4 bits each)
    const int SELF_MUL_OFFSET = 96; // 2 bits for self-color multiplier
    const int MOVE_MUL_OFFSET = 98; // 2 bits for move-forward multiplier

    static const int gray2[4] = {0, 1, 3, 2};
    static const int gray3[8] = {0, 1, 3, 2, 7, 6, 4, 5};

    // bias factor table
    const int factorTable[4] = {0, 1, 2, 1};

    const int selfMul = factorTable[gray2[dnaRange(d, SELF_MUL_OFFSET, 2)]]*2 + 9;
    const int moveMul = factorTable[gray2[dnaRange(d, MOVE_MUL_OFFSET, 2)]] + 1;

    // scoring table for the color scores
    static const int scoreValue[8] = {0, 1, 2, 3, 4, 3, 2, 1};

    std::vector<coord_t> bestMoves;
    int bestScore = 0;

    for (int x = -1; x <= 1; x++) {
        for (int y = -1; y <= -1; y++) {
            const int color = v(x, y);
            if ((x || y) && (color >= 0)) {
                int score = 0;

                // score for the square itself
                score += selfMul*(scoreValue[gray3[dnaRange(d, COLOR_OFFSET + color*3, 3)]] - 2);

                // score for making forward progress;
                score += moveMul*(x + 1);

                // score for the resulting square's surrounding tiles
                for (int a = -1; a <= 1; a++) {
                    for (int b = -1; b <= 1; b++) {
                        const int color2 = v(x + a, y + b);
                        if (color2 >= 0) {
                            score += scoreValue[gray3[dnaRange(d, COLOR_OFFSET + color2*3, 3)]] - 2;
                        }
                    }
                }

                if (score > bestScore) {
                    bestMoves.clear();
                    bestScore = score;
                }
                if (score >= bestScore) {
                    bestMoves.push_back({x, y});
                }
            }
        }
    }

    if (bestMoves.empty()) {
        return {v.rng.rint(2), v.rng.rint(3) - 1};
    }
    return bestMoves[v.rng.rint(bestMoves.size())];
}

W moim ostatnim biegu uzyskałem wyniki: 1 1 1 1 1 1 1 46 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 20077 1 1 1 2 1 1 1 1 1

Chciałbym móc zdobyć więcej z 20077 i mniej z 1. :)

puszysty
źródło
1
Korzystanie z szarego kodu to poważny pomysł! ;)
matovitch
1
+1 dla kodów Graya. Jednak genom w pełni odporny na mutacje trochę zaszkodzi różnorodności. A Btw wynik 20 000 nie jest nawet bliski maksymalnemu, jaki możesz osiągnąć. Jeśli jakiś szczur ewoluuje w kierunku prowadzenia toru z dowolnej możliwej lokalizacji początkowej, staje się on nieśmiertelny i zyskuje ogromny wynik sprawnościowy. Jego genom szybko dominuje, co prowadzi do populacji prawie 50 000 szczurów i wyniku wynoszącego kilka milionów.
2

C ++, TripleScore, wynik: 100 ~ 400

Po pierwsze, mój wynik różni się znacznie w wielu biegach (głównie z powodu liczby 1).

Rdzeń oblicza wynik 5 kierunków: w górę, w dół, do przodu w górę, do przodu i do przodu w dół. Najpierw oblicza się wynik w górę i w dół, a następnie porównuje wyniki z wartością pozostania na miejscu. Jeśli pozostanie na miejscu jest lepsze niż poruszanie się w górę lub w dół, kierunki te nie zostaną wybrane (więc musi iść do przodu). Ma to zapobiec odbijaniu się (w górę, w dół, w górę, w dół, ...) między 2 punktami.

Teraz punktowane są 3 inne kierunki: do przodu, do przodu i do przodu w dół. Ze wszystkich badanych kierunków zachowane są te z najwyższym wynikiem, a 1 z nich wybierany jest losowo.

Punktacja kierunku: TripleScore oblicza wynik ruchu na podstawie 3 wyników cząstkowych:

  • Wynik koloru miejsca docelowego (zależy od DNA, jak w colorScorePlayer)
  • Wynik przejścia do przodu (zależy od DNA)
  • Maksymalny wynik wykonania ruchu do przodu od celu (pomnożony przez czynnik zapisany w DNA)

Podobnie jak w przypadku innych odpowiedzi, wynik zależy w dużej mierze od liczby zwróconych wyników 1.

#define CHUNKSIZE 5 //We have 20 values so 5 bits/value
#define MAXVALUE 32 //2^CHUNKSIZE
#define AVGVALUE MAXVALUE/2

#define DNASEGMENT(dna, i) dnarange(dna, i*CHUNKSIZE, CHUNKSIZE)
#define DNA_COLOR 0
#define DNA_FORWARD 16
#define DNA_LOOKAHEAD 17

//Get the score for a specific move
int calcscore(dna_t dna, view_t view, int x, int y, bool final){
  if (view(x,y) == OUT_OF_BOUNDS){
    //We cant go there
    return -MAXVALUE;
  }
  //The score of the color
  int s = DNASEGMENT(dna, DNA_COLOR+view(x,y))-AVGVALUE;
  //The score of going forward
  s += x*DNASEGMENT(dna, DNA_FORWARD);

  //Get the children or not
  if (!final){
    int max=-MAXVALUE;
    int v;
    //Get the maximum score of the children
    for (int i=-1; i<2; ++i){
        v = calcscore(dna, view, x+1, y+i, true);
        if (v>max){
            max=v;
        }
    }
    //Apply dna factor to the childs score
    s += (max * DNASEGMENT(dna, DNA_LOOKAHEAD))/AVGVALUE;
  }
  return s;
}

coord_t TripleScore(dna_t dna, view_t view) {
  int maxscore = -100;
  int score;
  coord_t choices[5]; //Maximum 5 possible movements
  int maxchoices = 0;
  int zeroscore = calcscore(dna, view, 0, 0, false);

  //Go over all possible moves and keep a list of the highest scores
  for (int x=0; x<2; ++x){
    for (int y=-1; y<2; ++y){
        if (x | y){
            score = calcscore(dna, view, x, y, false);
            if (score > maxscore){
                maxscore = score;
                choices[0] = {x, y};
                maxchoices = 1;
            }else if (score == maxscore){
                choices[maxchoices++] = {x, y};
            }
        }
    }
    if (!x && maxscore <= zeroscore){
        //I will NOT bounce!
        maxscore = -100;
    }
  }

  return choices[view.rng.rint(maxchoices)];
}
Wouter ibens
źródło
2

Ruby - ProbabilisticScorePlayer

class ProbabilisticScorePlayer < Player
    Here = Vector2D.new( 0, 0)
    Forward = Vector2D.new( 1, 0)
    Right = Vector2D.new( 0, 1)
    Left = Vector2D.new( 0,-1)

    def vision_at(vec2d)
        v = @vision[vec2d.x+2][vec2d.y+2]
        v==-1?nil:v
    end

    def turn
        coords = [Forward]
        [Here,Forward].each{|x|
            [Here,Right,Left].each{|y|
                c = x+y
                if x!=y && vision_at c > -1
                  coords.push c if bit_at(vision_at c)==1
                  coords.push c if bit_at(vision_at(c+Forward)+16)==1
                  coords.push c if bit_at(vision_at(c+Right)+32)==1
                  coords.push c if bit_at(vision_at(c+Left)+48)==1
                  coords.push c if bit_at(vision_at(c+Forward+Right)+64)==1
                  coords.push c if bit_at(vision_at(c+Forward+Left)+80)==1
                end
            }
        }
        coords.sample(random: @rng)
    end
end

Ten wysoce niedeterministyczny szczur oblicza prawdopodobieństwo przejścia na przestrzeń przez jej sąsiedztwo. Pierwsze 16 miejsc w genomie reprezentuje 16 kolorów. 1 w gnieździe oznacza, że ​​kolor jest dobry na nadepnięcie, 0 oznacza zły. Następne 16 to samo dla pola przed celem i tak dalej.

Główną zaletą podejścia probabilistycznego jest to, że utknięcie za ścianą jest prawie niemożliwe. Wadą jest to, że prawie nigdy nie dostaniesz prawie idealnego szczura.

MegaTom
źródło
+1 za oryginalność. Jaki masz wynik?
Jeszcze nigdy tego nie testowałem ...
MegaTom
Czy zapomniałeś podać cwartość początkową? Wydaje się, że nie jest zdefiniowany, gdy używasz go w pierwszym if.
Martin Ender
@ MartinBüttner tak, zapomniałem. Naprawię to teraz.
MegaTom
Nie znam tak dobrze Ruby, ale twój kod nie działa w Ruby 2.1.1. coordsnie jest listą, używasz &&zamiast andi zapomniałeś nawiasów, a nawet po naprawieniu tego wszystkiego nie ograniczasz wartości RNG, więc otrzymujesz pusty kierunek. Czy ten pseudo-kod, czy coś, co ma być uruchamiane z jakimś dialektem Ruby?
2

Java, RunningStar, wynik = 1817.050970291959 w ponad 1000 gier

Ten bot wykorzystuje kodowanie kolorów Run-Bonus techniką StarPlayer .

Aktualizacja: Naprawiono kontroler Java.

Scores: 6, 81533, 1648026, 14, 5, 38841, 1, 76023, 115162, 3355130, 65759, 59, 4, 235023, 1, 1, 1, 3, 2, 1, 1, 14, 50, 1, 306429, 68, 3, 35140, 2, 1, 196719, 162703, 1, 1, 50, 78233, 5, 5, 5209, 1, 2, 60237, 1, 14, 19710, 1528620, 79680, 33441, 58, 1, 4, 45, 105227, 11, 4, 40797, 2, 22594, 9, 2192458, 1954, 294950, 2793185, 4, 1, 1, 112900, 30864, 23839, 19330, 134178, 107920, 5, 122894, 1, 1, 2721770, 8, 175694, 25235, 1, 3109568, 4, 11529, 1, 8766, 319753, 5949, 1, 1856027, 19752, 3, 99071, 67, 198153, 18, 332175, 8, 1524511, 1, 159124, 1, 1917181, 2, 1, 10, 276248, 1, 15, 1, 52, 1159005, 43251, 1, 536150, 75864, 509655, 1126347, 250730, 1548383, 17, 194687, 27301, 2, 1, 207930, 621863, 6065, 443547, 1, 6, 1, 1, 1, 1, 556555, 436634, 25394, 2, 61335, 98076, 1, 190958, 2, 18, 67981, 3, 8, 119447, 1, 1, 1, 19, 28803, 23, 33, 60281, 613151, 1, 65, 20341, 799766, 476273, 105018, 357868, 3, 92325, 2062793, 18, 72097, 30229, 1, 1, 3, 610392, 1, 202149, 887122, 56571, 1, 77788, 61580, 4, 72535, 381846, 148682, 26676, 1, 210, 3556343, 212550, 650316, 33491, 180366, 1, 295685, 46255, 43295, 1006367, 63606, 1, 1, 1, 1, 3094617, 21, 10, 3, 1, 1, 14730, 1585801, 102, 2, 410353, 1570, 1, 17423, 1, 1849366, 5, 1, 357670, 1, 1, 1, 1, 89936, 349048, 15, 7, 6, 2, 121654, 1852897, 19, 1, 103275, 1, 1, 771797, 23, 19, 6700, 1, 135844, 2966847, 3, 2356708, 101515, 1, 17, 1, 996641, 22, 16, 657783, 171744, 9604, 1, 1335166, 1739537, 2365309, 1, 3378711, 11332, 3980, 182951, 609339, 8, 10, 1746504, 61895, 386319, 24216, 331130, 12193, 1, 284, 1, 2, 50369, 38, 8, 1, 1238898, 177435, 124552, 22370, 1418184, 20132, 6, 2, 730842, 1, 1341094, 141638, 534983, 1551260, 31508, 96196, 434312, 3012, 715155, 1, 276172, 214255, 1, 208948, 4, 1631942, 512293, 37, 64474, 1342713, 1, 132634, 13, 2, 61876, 1081704, 160301, 2, 488156, 2414109, 1809831, 5, 74904, 6, 11, 5, 1, 79856, 96, 35421, 229858, 238507, 3838897, 18, 44, 1, 1659126, 9, 33708, 12, 1, 758381, 162742, 256046, 3, 15, 142673, 70953, 58559, 6, 2, 1, 984066, 290404, 1072226, 66415, 4465, 924279, 48133, 319765, 519401, 1, 1, 1201037, 418362, 17022, 68, 213072, 37, 1039025, 1, 2, 6, 4, 45769, 1, 5, 1061838, 54614, 21436, 7149, 1, 1, 1, 35950, 2199045, 1, 379742, 3, 2008330, 238692, 181, 7, 140483, 92278, 214409, 5179081, 1, 1, 334436, 2, 107481, 1142028, 1, 31146, 225284, 1, 14533, 4, 3963305, 173084, 102, 1, 4732, 14, 1, 25, 11032, 224336, 2, 131110, 175764, 81, 5630317, 1, 42, 1, 89532, 621825, 2291593, 210421, 8, 44281, 4, 303126, 2895661, 2672876, 3, 436915, 21025, 1, 4, 49227, 1, 39, 3, 1, 103531, 256423, 2, 1600922, 15, 1, 2, 58933, 1114987, 1, 4, 3, 1, 1544880, 285673, 240, 2, 128, 214387, 3, 1327822, 558121, 5, 2718, 4, 1258135, 7, 37418, 2729691, 1, 346813, 385282, 2, 35674, 513070, 13, 1930635, 117343, 1929415, 52822, 203219, 1, 52407, 1, 1, 1, 3, 2, 37121, 175148, 136893, 2510439, 2140016, 437281, 53089, 40647, 37663, 2579170, 83294, 1597164, 206059, 1, 9, 75843, 773677, 50188, 12, 1, 1067679, 105216, 2452993, 1813467, 3279553, 280025, 121774, 62, 5, 113, 182135, 1, 16, 71853, 4, 557139, 37803, 228249, 6, 32420, 8, 410034, 73889, 1, 2, 96706, 48515, 1, 3, 1314561, 137, 966719, 692314, 80040, 85147, 75291, 1, 1, 30, 38119, 182723, 42267, 3836110, 22, 986685, 2, 37, 1, 3, 26, 43389, 2679689, 1, 1, 57365, 1, 2662599, 2, 72055, 1, 141247, 1, 1, 1122312, 1, 1080672, 4, 266211, 1, 34163, 1490610, 256341, 1, 627753, 32110, 1, 42468, 1, 10746, 1, 9, 1, 46, 1714133, 5, 117, 1, 104340, 218338, 151958, 122407, 211637, 223307, 57018, 74768, 582232, 2, 621279, 4, 1, 11, 196094, 1839877, 167117, 8, 42991, 2199269, 124676, 1, 1, 1, 5, 1, 1, 698083, 1, 76361, 1564154, 67345, 1398411, 9, 11, 105726, 1197879, 1, 2, 62740, 39, 2, 397236, 17057, 267647, 13, 57509, 22954, 1, 12, 747361, 4325650, 21425, 2160603, 144738, 1, 204054, 3113425, 6, 3019210, 30, 3359, 1, 89117, 489245, 1, 218068, 1, 1, 14718, 222722, 1, 1, 216041, 72252, 279874, 183, 89224, 170218, 1549362, 2, 1, 953626, 32, 130355, 30460, 121028, 20, 159273, 5, 2, 30, 1, 76215, 1654742, 2326439, 1, 53836, 1, 6, 4, 72327, 9, 285883, 1, 908254, 698872, 47779, 3, 2293485, 265788, 3766, 1, 1, 83151, 36431, 307577, 256891, 29, 1, 1, 1093544, 145213, 5, 2, 581319, 2911699, 1, 213061, 1359700, 2, 1, 343110, 1, 157592, 1708730, 1, 22703, 32075, 1, 1, 87720, 159221, 2313143, 10, 2266815, 2106917, 1345560, 3146014, 4, 551632, 1066905, 550313, 4069794, 1, 1406178, 38981, 1, 3, 1, 3039372, 241545, 35, 63325, 85804, 1365794, 2, 2143204, 48, 1, 99, 3225633, 7, 4074564, 1023899, 3209940, 2054326, 70880, 2, 1, 284192, 1944519, 84682, 2, 867681, 90022, 378115, 1, 15, 602743, 1337444, 131, 1, 229, 161445, 3, 2, 5591616, 195977, 92415, 637936, 142928, 1, 2310569, 923, 1, 230288, 1300519, 398529, 2233, 100261, 4323269, 81362, 37300, 1, 233775, 32277, 434139, 323797, 19214, 782633, 2881473, 1, 1, 9, 337016, 1, 515612, 44637, 17, 1, 25, 67758, 1737819, 16454, 30613, 692963, 62216, 222062, 344596, 3, 33782, 19, 180441, 23552, 20462, 70740, 10298, 109691, 1, 1729427, 33714, 1770930, 1, 1, 1, 1, 290766, 136688, 688231, 3250223, 30703, 1985963, 527128, 3, 226340, 195576, 30, 1, 3, 1, 793085, 5527, 5, 1, 2188429, 1327399, 5, 6192537, 1445186, 2478313, 2, 16892, 3, 1, 1, 15, 12, 1361157, 4, 1241684, 1, 45008, 1, 505095, 4037314, 14, 8, 1, 16740, 69906, 45, 1, 240949, 3975533, 212705, 2617552, 278884, 1, 24966, 958059, 231886, 22929, 4052071, 51259, 67791, 78739, 1, 165787, 67, 518191, 86923, 437, 1271004, 135941, 244766, 1, 1, 1, 1152745, 1, 3, 406365, 3847357, 476636, 135097, 304368, 8, 1578276, 1, 1, 375, 1, 1, 1298206, 1860743, 2, 35311, 834516, 421428, 2, 66629, 1, 309845, 398756, 33, 907277, 384475, 2267460, 1, 269300, 124525, 34399, 93584, 362186, 811260, 426109, 1, 1009323, 109986, 122181, 1, 1, 3626487, 11452, 1092410, 57233, 6, 2009226, 1, 83333, 4, 1338631, 79114, 2140249, 51813, 1118986, 43514, 1529365, 1, 101, 1, 1,
package game.players;

import java.awt.Point;
import java.util.*;

public class RunningStar extends Player{

    @Override
    public Point takeTurn(String genome, Map<Point, Integer> vision) {
        Map<Integer, Integer> squareCosts = decode(genome);
        Path path = astar(vision, squareCosts);
        return path.get(1);
    }

    private Path astar(Map<Point, Integer> vision, Map<Integer, Integer> squareCosts) {
        Set<Path> closed = new HashSet<>();
        PriorityQueue<Path> open = new PriorityQueue<>();
        open.add(new Path(new Point(0, 0), 0));
        while (!open.isEmpty()){
            Path best = open.remove();
            if (best.head().x == 2 || (best.head().x > 0 && (best.head().y == 2 || best.head().y == -2))){
                return best;
            }
            for (Path path : pathsAround(best, vision, squareCosts)){
                if (!closed.contains(path) && !open.contains(path)){
                    open.add(path);
                }
            }
            closed.add(best);
        }

        Path p = new Path(new Point(0,0), 0);
        return p.add(new Point((int)(random.nextDouble() * 3 - 1), (int)(random.nextDouble() * 3 - 1)), 0);
    }

    private List<Path> pathsAround(Path path, Map<Point, Integer> vision, Map<Integer, Integer> costs) {
        Point head = path.head();
        List<Path> results = new ArrayList<>();
        for (int i = -1; i <= 1; i++){
            for (int j = -1; j <= 1; j++){
                if (i == 0 && j == 0){
                    continue;
                }
                Point p = new Point(head.x + i, head.y + j);
                if (!vision.containsKey(p) || vision.get(p) == -1){
                    continue;
                }
                results.add(path.add(p, costs.get(vision.get(p))));
            }
        }
        return results;
    }

    private Map<Integer, Integer> decode(String genome) {
        int chunkLength = genome.length()/16;
        Map<Integer, Integer> costs = new HashMap<>();
        for (int i = 0; i < 16; i++){
            int runSize = 0;
            int cost = 0;
            for (int j = i * chunkLength; j < (i + 1) * chunkLength; j++){
                switch (genome.charAt(j)){
                    case '0':
                        runSize = 0;
                        break;
                    case '1':
                        cost += ++runSize;
                }
            }
            costs.put(i, cost);
        }
        return costs;
    }

    private class Path implements Comparable<Path>{

        Point head;
        Path parent;
        int length;
        int totalCost;

        private Path(){}

        public Path(Point point, int cost) {
            length = 1;
            totalCost = cost;
            head = point;
            parent = null;
        }

        public Point get(int index) {
            if (index >= length || index < 0){
                throw new IllegalArgumentException(index + "");
            }
            if (index == length - 1){
                return head;
            }
            return parent.get(index);
        }

        public Point head() {
            return head;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            Path path = (Path) o;

            if (!head.equals(path.head)) return false;

            return true;
        }

        @Override
        public int hashCode() {
            return head.hashCode();
        }

        @Override
        public int compareTo(Path o) {
            return totalCost - o.totalCost;

        }

        public Path add(Point point, int cost) {
            Path p = new Path();
            p.head = point;
            p.totalCost = totalCost + cost;
            p.length = length + 1;
            p.parent = this;
            return p;
        }
    }
}
Numer jeden
źródło
2

Skok do przodu, Python 2

Niezbyt przełomowy, ale to moja jedyna próba, która wykonała się dobrze.

class LeapForward(Player):
  def __init__(self):
    Player.__init__(self)
    self.coords = [Coordinate( 1, 0),
                   Coordinate( 1,-1),
                   Coordinate( 1, 1)]
    self.n_moves = len(self.coords)

  def turn(self):
    notOKColors = [self.bit_chunk(4*n,4) for n in range(4,8)]
    notOKMap = [Coordinate(x-2,y-2) for x in range(0,5) for y in range(0,5) if self.vision[y][x] not in notOKColors]
    goTo = [c for c in self.coords if c in notOKMap]
    if not goTo:
      goTo = [Coordinate(1,0)]
    return random.choice(goTo)

Zasadniczo koduje cztery kolory (każdy 4 bity), których należy unikać w genomie. Następnie przechodzi do koloru, którego nie ma na tej liście. Jeśli wszystkie kolory są złe, nadal przeskakuje w nieznane.

plannapus
źródło
Prawdopodobnie powinien to nazwać „RedQueen” :)
plannapus
1

Java - IAmARobotPlayer - Ocena 3,7

Właśnie stworzyłem tego robota-szczura do porównania z innym (jak dotąd niezbyt interesującym) programem, który stworzyłem. Nie osiąga ogólnie dobrych wyników, ale jeśli gdzieś zdobędzie punkty, zdobędzie wiele szczurów. Chodzi o to, że będzie patrzeć tylko na trzy komórki przed sobą, każda komórka jest dobra lub zła. To daje liczbę binarną. Następnie sprawdzi tę liczbę w swoim genomie, weźmie trzy kolejne bity, również skonwertuje je na liczbę i podejmie działanie zapisane pod tym numerem. Więc działa zawsze tak samo, gdy napotyka tę samą sytuację.

package game.players;
import java.awt.*;
import java.util.Map;
public class IAmARobotPlayer extends Player{
    private static final Point[] possibleMoves = {new Point(1,-1), new Point(1,0), new Point(1,1), new Point(0,-1), new Point(0,1), new Point(1,-1), new Point(1,0), new Point(1,1)};
    private int isGood(int pos,Map<Point,Integer> vision, char[] genomeChar){
        int value = vision.get(new Point(1,pos));
        if(value ==-1){
            return 0;
        } else {
            return genomeChar[84+value]-'0';
        }
    }

    @Override
    public Point takeTurn(String genome, Map<Point, Integer> vision) {

        char[] genomeChar = genome.toCharArray();
        int situation = 4*isGood(1,vision,genomeChar)+2*isGood(0,vision,genomeChar)+1*isGood(-1,vision,genomeChar);
        int reaction = 4*(genomeChar[3*situation+0]-'0')+2*(genomeChar[3*situation+1]-'0')+1*(genomeChar[3*situation+2]-'0');
        return possibleMoves[reaction];

    }
}

Wynik:

Individual scores: 1, 1, 332, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 47560, 15457, 1, 
Your final score is 3.7100115087136234
wada
źródło
1

Cautious Specimens - C ++ - ocenia około 2030 na 200 przebiegów

Wykorzystuje część barwną (16 x 4 bity) DNA kodującego Blind Faith, ale pozostawia resztę (36 bitów) DNA całkowicie niewykorzystaną.

Kodowanie koloru to:

  • 10XX - dla bezpiecznych kwadratów;
  • 11XX - dla śmiertelnych kwadratów; i
  • 0000 do 0111 - dla 8 typów kwadratów pułapek.

Gdzie X oznacza nieużywane bity. Biorąc pod uwagę, że tylko 2 z 16 kolorów to pułapki, które wykorzystają wszystkie 4 ich bity (i tylko jeśli pułapka jest przesunięta, co będzie miało miejsce 8 z 9 razy), wówczas zwykle będzie 64 nieużywanych bitów - teoria jest taka, że ​​mutacje, które wpływają na którykolwiek z tych nieużywanych bitów, nie zrujnują genomu, a stabilność jest lepsza niż jakiekolwiek wymyślne rozwiązania, które mogłyby wykorzystać te pozostałe bity.

Próbki wykorzystują to następnie do zaplanowania bezpiecznej trasy w obrębie siatki 7x7 wyśrodkowanej na sobie (5x5 ich widzenie pozwala plus 1 kwadrat z każdej strony, aby umożliwić przesunięcie pułapek), priorytetem jest przesunięcie największej odległości do przodu po 3 ruchach.

Początkowo zacząłem budować w kilku kontrolach, aby upewnić się, że fakt, że kolor, na którym aktualnie stoi okaz, nie jest śmiertelny, odpowiada genomowi i oznacza wszelkie błędne kolory jako kwadraty BEZPIECZNEGO bezpieczeństwa (i ich sąsiednie kwadraty) - ale to dodało znaczące komplikacja zapewniająca niewielki zysk w porównaniu do oznaczania tych kwadratów jako BEZPIECZNYCH i zabijania kilku dodatkowych okazów. Wrócę do tego, jeśli będę miał czas.

#include <initializer_list>
#include <vector>

enum class D { SAFE, LETHAL,TRAP_N, TRAP_NE, TRAP_E, TRAP_SE, TRAP_S, TRAP_SW, TRAP_W, TRAP_NW, UNSURE };
enum class X { SAFE, LETHAL, UNSURE };

inline void checkLocation( color_t color, D (&dna)[16], D check )
{
    if ( color != OUT_OF_BOUNDS && dna[color] == check )
        dna[color] = D::UNSURE;
}

inline void updateMapLocation( X (&map)[7][7], unsigned int x, unsigned int y, const X& safety ){
    if (        ( safety == X::LETHAL && map[x][y] != X::LETHAL )
            || ( safety == X::UNSURE && map[x][y] == X::SAFE ) )
        map[x][y] = safety;
}

inline unsigned int isSafePath( X (&map)[7][7], coord_t p )
{
    return map[p.x][p.y] == X::SAFE ? 1 : 0;
}
inline unsigned int isSafePath(X (&map)[7][7],coord_t p,coord_t q,coord_t r){
    if ( isSafePath( map,p ) )
        if ( isSafePath( map, q ) )
            return isSafePath( map, r );
    return 0;
}

inline unsigned int isSafeEast( X (&map)[7][7], coord_t p )
{
    if ( !isSafePath( map, p ) )
        return 0;
    if ( p.x == 6 )
        return 1;
    return isSafeEast(map,{p.x+1,p.y-1})
            +isSafeEast(map,{p.x+1,p.y+0})
            +isSafeEast(map,{p.x+1,p.y+1});
}

template<typename T> inline T max(T a,T b){return a>=b?a:b;}
template<typename T, typename... A> inline T max(T a,T b,A... c){return max(max(a,b),c...); }

coord_t cautiousSpecimins( dna_t d, view_t v ) {
    X map[7][7] = { { X::SAFE } };
    D dna[16] = { D::UNSURE };
    for ( color_t i = 0; i < 16; i++ )
    {
        if ( d[4*i] == 1 )
        {
            dna[i] = d[4*i + 1] == 1 ? D::LETHAL : D::SAFE;
        }
        else
        {
            switch ( dnarange( d, 4*i + 1, 3 ) )
            {
                case 0: dna[i] = D::TRAP_N; break;
                case 1: dna[i] = D::TRAP_NE; break;
                case 2: dna[i] = D::TRAP_E; break;
                case 3: dna[i] = D::TRAP_SE; break;
                case 4: dna[i] = D::TRAP_S; break;
                case 5: dna[i] = D::TRAP_SW; break;
                case 6: dna[i] = D::TRAP_W; break;
                case 7: dna[i] = D::TRAP_NW; break;
                default: dna[i] = D::UNSURE; break;
            }
        }
    }
    if ( v(-1, 0) != OUT_OF_BOUNDS )
        checkLocation( v( 0, 0), dna, D::LETHAL );

    if ( v(-1, 0) != OUT_OF_BOUNDS )
        for ( unsigned int y = 0; y < 7; ++ y )
            map[2][y] = X::LETHAL;

    if ( v(-2, 0) != OUT_OF_BOUNDS )
        for ( unsigned int x = 0; x < 2; ++x )
            for ( unsigned int y = 0; y < 7; ++ y )
                map[x][y] = X::LETHAL;

    if ( v( 0, 1) == OUT_OF_BOUNDS )
        for ( unsigned int x = 0; x < 7; ++x )
                map[x][4] = X::LETHAL;

    if ( v( 0, 2) == OUT_OF_BOUNDS )
        for ( unsigned int x = 0; x < 7; ++x )
            for ( unsigned int y = 5; y < 7; ++ y )
                map[x][y] = X::LETHAL;

    if ( v( 0,-1) == OUT_OF_BOUNDS )
        for ( unsigned int x = 0; x < 7; ++x )
                map[x][2] = X::LETHAL;

    if ( v( 0,-2) == OUT_OF_BOUNDS )
        for ( unsigned int x = 0; x < 7; ++x )
            for ( unsigned int y = 0; y < 2; ++ y )
                map[x][y] = X::LETHAL;

    checkLocation( v( 1, 1), dna, D::TRAP_SW );
    checkLocation( v( 1, 0), dna, D::TRAP_W  );
    checkLocation( v( 1,-1), dna, D::TRAP_NW );
    checkLocation( v( 0,-1), dna, D::TRAP_N  );
    checkLocation( v(-1,-1), dna, D::TRAP_NE );
    checkLocation( v(-1, 0), dna, D::TRAP_E  );
    checkLocation( v(-1, 1), dna, D::TRAP_SE );
    checkLocation( v( 0, 1), dna, D::TRAP_S  );

    for ( int x = 1; x <= 5; ++x )
    {
        for ( int y = 1; y <= 5; ++y )
        {
            switch( dna[v(x-3,y-3)] )
            {
                case D::LETHAL : updateMapLocation( map, x+0, y+0, X::LETHAL ); break;
                case D::TRAP_N : updateMapLocation( map, x+0, y+1, X::LETHAL ); break;
                case D::TRAP_NE: updateMapLocation( map, x+1, y+1, X::LETHAL ); break;
                case D::TRAP_E : updateMapLocation( map, x+1, y+0, X::LETHAL ); break;
                case D::TRAP_SE: updateMapLocation( map, x+1, y-1, X::LETHAL ); break;
                case D::TRAP_S : updateMapLocation( map, x+0, y-1, X::LETHAL ); break;
                case D::TRAP_SW: updateMapLocation( map, x-1, y-1, X::LETHAL ); break;
                case D::TRAP_W : updateMapLocation( map, x-1, y+0, X::LETHAL ); break;
                case D::TRAP_NW: updateMapLocation( map, x-1, y+1, X::LETHAL ); break;
//              case D::UNSURE : updateMapLocation( map, x+0, y+0, X::SAFE );
//                               updateMapLocation( map, x+0, y+1, X::UNSURE );
//                               updateMapLocation( map, x+1, y+1, X::UNSURE );
//                               updateMapLocation( map, x+1, y+0, X::UNSURE );
//                               updateMapLocation( map, x+1, y-1, X::UNSURE );
//                               updateMapLocation( map, x+0, y-1, X::UNSURE );
//                               updateMapLocation( map, x-1, y-1, X::UNSURE );
//                               updateMapLocation( map, x-1, y+0, X::UNSURE );
//                               updateMapLocation( map, x-1, y+1, X::UNSURE );
//                               break;
                default        : break;
            }           
        }
    }

    unsigned int north = isSafeEast(map,{4,4});
    unsigned int east  = isSafeEast(map,{4,3});
    unsigned int south = isSafeEast(map,{4,2});
    unsigned int mx    = max( north, east, south );
    unsigned int sz;
    std::vector<coord_t> dir;
    if ( mx > 0 )
    {
        if ( north == mx ) dir.push_back( {+1,+1} );
        if ( east  == mx ) dir.push_back( {+1,+0} );
        if ( south == mx ) dir.push_back( {+1,-1} );

        return dir[v.rng.rint(dir.size())];
    }


    north = isSafePath(map,{4,4},{5,5},{5,6})
            + isSafePath(map,{4,4},{4,5},{5,6});
    south = isSafePath(map,{4,2},{5,1},{5,0})
            + isSafePath(map,{4,2},{4,1},{5,0});
    mx = max( north, south );
    if ( mx > 0 )
    {
        if ( north == mx ) dir.push_back( {+1,+1} );
        if ( south == mx ) dir.push_back( {+1,-1} );

        return dir[v.rng.rint(dir.size())];
    }

    north = isSafePath(map,{3,4},{4,5},{5,6});
    south = isSafePath(map,{3,2},{4,1},{5,0});
    mx = max( north, south );
    if ( mx > 0 )
    {
        if ( north == mx ) dir.push_back( {+0,+1} );
        if ( south == mx ) dir.push_back( {+0,-1} );

        return dir[v.rng.rint(dir.size())];
    }

    north = 2*isSafePath(map,{4,4},{4,5},{4,6})
            + 1*isSafePath(map,{4,4},{3,5},{4,6});
    south = 2*isSafePath(map,{4,2},{4,1},{4,0})
            + 1*isSafePath(map,{4,2},{3,1},{4,0});
    mx = max( north, south );
    if ( mx > 0 )
    {
        if ( north == mx ) dir.push_back( {+1,+1} );
        if ( south == mx ) dir.push_back( {+1,-1} );

        return dir[v.rng.rint(dir.size())];
    }

    north = isSafePath(map,{3,4},{4,5},{4,6})
            + isSafePath(map,{3,4},{3,5},{4,6});
    south = isSafePath(map,{3,2},{4,1},{4,0})
            + isSafePath(map,{3,2},{3,1},{4,0});
    mx = max( north, south );
    if ( mx > 0 )
    {
        if ( north == mx ) dir.push_back( {+0,+1} );
        if ( south == mx ) dir.push_back( {+0,-1} );

        return dir[v.rng.rint(dir.size())];
    }

    north = isSafePath(map,{2,4},{3,5},{4,6});
    south = isSafePath(map,{2,2},{3,1},{4,0});
    mx = max( north, south );
    if ( mx > 0 )
    {
        if ( north == mx ) dir.push_back( {-1,+1} );
        if ( south == mx ) dir.push_back( {-1,-1} );

        return dir[v.rng.rint(dir.size())];
    }

    north = isSafePath(map,{3,4},{3,5},{3,6})
            + isSafePath(map,{3,4},{2,5},{3,6});
    south = isSafePath(map,{3,2},{3,1},{3,0})
            + isSafePath(map,{3,2},{2,1},{3,0});
    mx = max( north, south );
    if ( mx > 0 )
    {
        if ( north == mx ) dir.push_back( {+0,+1} );
        if ( south == mx ) dir.push_back( {+0,-1} );

        return dir[v.rng.rint(dir.size())];
    }

    north = isSafePath(map,{2,4},{3,5},{4,6});
    south = isSafePath(map,{2,2},{3,1},{4,0});
    mx = max( north, south );
    if ( mx > 0 )
    {
        if ( north == mx ) dir.push_back( {-1,+1} );
        if ( south == mx ) dir.push_back( {-1,-1} );

        return dir[v.rng.rint(dir.size())];
    }

    return {-1,-1};
}

Przykładowe wyniki:

Scores: 421155 2 129418 71891 90635 1 211 1111987 29745 7 2200750 41793 50500 45 2012072 2 485698 1 110061 1554720 210308 249336 2 1 262110 17 3 19 1719139 23859 45118 3182784 318 2 1 15572 14 2822954 18 11 2 3 15954 1331392 2296280 135015 1 360826 1 692367 4 244775 4814645 3749144 3 1 660000 1 11 3688002 3920202 3428464 123053 1 243520 86 9 6 289576 195966 549120 220918 9 1 43 71046 5213 118177 150678 54639 3 200839 1 3 6 1978584 1514393 119502 1 1 137695 184889 337956 1 1 441405 133902 991 1 4137428 1 1427115 3340977 1 2 1 55559 11 1 94886 30270 1 6 3 69394 264780 6877 47758 128568 1 116672 130539 163747 96253 1 2654354 1 141 58212 1613661 27 9504 1 2474022 843890 1 59 3110814 2353731 150296 313748 2590241 6 5970407 1434171 2 334715 141277 1 56810 2964306 51544 61973 715590 1 106 900384 50948 2 34652 108096 391006 1 2969764 47625 1 24 30481 44 8 1 18 2094036 106461 3080432 75 620651 16 71730 282145 275031 17 1 8 15 121731 18 2 1 1 495868 3252390 6 1 63712 7 3733149 13380 1 1
Geometric mean score: 2030.17

Maksymalny wynik podczas testowania: 8 150 817 zapisanych próbek.

MT0
źródło
Teraz to zrobiłeś ... Chciałem zachować ścieżkę na później, ale nie mogłem zostawić twoich ostrożnych gryzoni bez wyzwań :) Jak się wydaje, ścieżkowanie działa jeszcze lepiej dzięki bardziej wydajnemu kodowaniu. Twój pomysł rozszerzenia obszaru ścieżki do 7x7 również wydaje się obiecujący. Zobaczę, czy mogę tego użyć.
Obecnie robię w tym celu 2000 biegów ... po pierwszych 900 wydaje się, że średnia oscyluje wokół 600, co jest dość dalekie od 2000 roku. Czy mógłbyś również powtórzyć to po swojej stronie, aby sprawdzić, czy 2000 był tylko fuks?
Martin Ender,