Niedawno natknąłem się na grę 2048 . Łączysz podobne płytki, przesuwając je w jednym z czterech kierunków, aby utworzyć „większe” płytki. Po każdym ruchu, nowa dachówka pojawia się losowo pustej pozycji o wartości albo 2
albo 4
. Gra kończy się, gdy wszystkie pola są wypełnione i nie ma ruchów, które mogłyby scalić kafelki lub utworzysz kafelek o wartości 2048
.
Po pierwsze, muszę osiągnąć dobrze określoną strategię, aby osiągnąć cel. Pomyślałem więc o napisaniu do tego programu.
Mój obecny algorytm:
while (!game_over) {
for each possible move:
count_no_of_merges_for_2-tiles and 4-tiles
choose the move with a large number of merges
}
Co robię to w dowolnym momencie, postaram się połączyć płytki z wartościami 2
i 4
, że to, staram się mieć 2
i 4
płytki, jako minimum, jak to możliwe. Jeśli spróbuję w ten sposób, wszystkie inne kafelki zostaną automatycznie scalone, a strategia wydaje się dobra.
Ale kiedy faktycznie używam tego algorytmu, dostaję tylko około 4000 punktów przed zakończeniem gry. Maksymalna liczba punktów AFAIK to nieco ponad 20 000 punktów, czyli znacznie więcej niż mój obecny wynik. Czy istnieje lepszy algorytm niż powyższy?
źródło
choose the move with large number of merges
co szybko prowadzi do lokalnychOdpowiedzi:
Opracowałem sztuczną inteligencję 2048 za pomocą optymalizacji expectimax , zamiast wyszukiwania minimax używanego przez algorytm @ ovolve. AI po prostu wykonuje maksymalizację dla wszystkich możliwych ruchów, a następnie oczekiwanie na wszystkie możliwe odrodzenia płytek (ważone prawdopodobieństwem płytek, tj. 10% dla 4 i 90% dla 2). O ile mi wiadomo, nie można przycinać optymalizacji expectimax (z wyjątkiem usuwania rozgałęzień, które są niezwykle mało prawdopodobne), dlatego zastosowany algorytm to starannie zoptymalizowane wyszukiwanie metodą brutalnej siły.
Wydajność
AI w swojej domyślnej konfiguracji (maksymalna głębokość wyszukiwania 8) zajmuje od 10 ms do 200 ms, aby wykonać ruch, w zależności od złożoności pozycji planszy. Podczas testowania AI osiąga średnią szybkość ruchu wynoszącą 5-10 ruchów na sekundę w trakcie całej gry. Jeśli głębokość wyszukiwania jest ograniczona do 6 ruchów, sztuczna inteligencja może z łatwością wykonać ponad 20 ruchów na sekundę, co stanowi ciekawe spojrzenie .
Aby ocenić wydajność punktacji AI, uruchomiłem AI 100 razy (podłączony do gry w przeglądarce za pomocą pilota). Dla każdego kafelka przedstawiamy proporcje gier, w których ten kafelek został osiągnięty przynajmniej raz:
Minimalny wynik we wszystkich seriach wynosił 124024; maksymalny osiągnięty wynik to 794076. Mediana wyniku to 387222. AI nigdy nie udało się uzyskać płytki 2048 (więc nigdy nie przegrała gry nawet raz na 100 gier); w rzeczywistości osiągnął płytkę 8192 przynajmniej raz w każdym biegu!
Oto zrzut ekranu z najlepszym biegiem:
Ta gra wymagała 27830 ruchów w ciągu 96 minut, czyli średnio 4,8 ruchów na sekundę.
Realizacja
Moje podejście koduje całą płytkę (16 wpisów) jako pojedynczą 64-bitową liczbę całkowitą (gdzie kafelki to nybbles, tj. 4-bitowe fragmenty). Na komputerze 64-bitowym umożliwia to przejście całej płyty w rejestrze jednego komputera.
Operacje przesunięcia bitów służą do wyodrębnienia poszczególnych wierszy i kolumn. Pojedynczy wiersz lub kolumna jest liczbą 16-bitową, więc tabela o rozmiarze 65536 może kodować transformacje, które działają w jednym wierszu lub kolumnie. Na przykład ruchy są implementowane jako 4 wyszukiwania do wstępnie obliczonej „tabeli efektów ruchu”, która opisuje, w jaki sposób każdy ruch wpływa na pojedynczy wiersz lub kolumnę (na przykład tabela „ruch w prawo” zawiera wpis „1122 -> 0023” opisujący, w jaki sposób wiersz [2,2,4,4] staje się rzędem [0,0,4,8] po przesunięciu w prawo).
Punktacja odbywa się również przy użyciu wyszukiwania w tabeli. Tabele zawierają wyniki heurystyczne obliczone dla wszystkich możliwych wierszy / kolumn, a wynikowy wynik dla tablicy jest po prostu sumą wartości tabeli dla każdego wiersza i kolumny.
Ta reprezentacja planszy, wraz z podejściem do wyszukiwania ruchu i punktacji na stole, pozwala AI na przeszukiwanie ogromnej liczby stanów gry w krótkim czasie (ponad 10 000 000 stanów gry na sekundę na jednym rdzeniu mojego laptopa z połowy 2011 roku).
Samo oczekiwanieimax jest zakodowane jako wyszukiwanie rekurencyjne, które na przemian obejmuje etapy „oczekiwania” (testowanie wszystkich możliwych lokalizacji i wartości odradzania się kafelków i ważenie ich zoptymalizowanych wyników według prawdopodobieństwa każdej możliwości) oraz etapy „maksymalizacji” (testowanie wszystkich możliwych ruchów) i wybierając ten z najlepszym wynikiem). Wyszukiwanie drzewa kończy się, gdy zobaczy poprzednio widzianą pozycję (przy użyciu tabeli transpozycji ), gdy osiągnie zdefiniowany limit głębokości lub osiągnie stan planszy, który jest bardzo mało prawdopodobny (np. Został osiągnięty poprzez uzyskanie 6 „4” płytek w rzędzie od pozycji początkowej). Typowa głębokość wyszukiwania to 4-8 ruchów.
Heurystyka
Do kierowania algorytmu optymalizacji w kierunku korzystnych pozycji stosuje się kilka heurystyk. Precyzyjny wybór heurystyki ma ogromny wpływ na wydajność algorytmu. Różne heurystyki są ważone i łączone w wynik pozycyjny, który określa, jak „dobra” jest dana pozycja tablicy. Wyszukiwanie optymalizacyjne będzie miało na celu maksymalizację średniego wyniku wszystkich możliwych pozycji na planszy. Rzeczywisty wynik, jak pokazuje gra, nie jest wykorzystywany do obliczania wyniku na planszy, ponieważ jest on zbyt ciężki na korzyść łączenia płytek (gdy opóźnione łączenie może przynieść dużą korzyść).
Początkowo użyłem dwóch bardzo prostych heurystyk, dających „bonusy” dla otwartych kwadratów i posiadających duże wartości na krawędzi. Te heurystyki działały całkiem dobrze, często osiągając 16384, ale nigdy nie osiągając 32768.
Petr Morávek (@xificurk) wziął moją sztuczną inteligencję i dodał dwie nowe heurystyki. Pierwsza heurystyka była karą za posiadanie niemonotonicznych wierszy i kolumn, które rosły wraz ze wzrostem rang, zapewniając, że niemonotoniczne rzędy małych liczb nie będą silnie wpływać na wynik, ale niemonotoniczne rzędy dużych liczb znacznie zranią wynik. Druga heurystyka policzyła liczbę potencjalnych połączeń (sąsiadujących równych wartości) oprócz otwartych przestrzeni. Te dwie heurystyki posłużyły do popchnięcia algorytmu w kierunku tablic monotonicznych (łatwiejszych do scalenia) oraz w kierunku pozycji tablic z dużą ilością połączeń (zachęcając go do wyrównania połączeń tam, gdzie to możliwe, aby uzyskać większy efekt).
Ponadto Petr zoptymalizował także heurystyczne wagi, stosując strategię „metaoptymalizacji” (przy użyciu algorytmu zwanego CMA-ES ), gdzie same wagi zostały dostosowane, aby uzyskać najwyższy możliwy średni wynik.
Skutki tych zmian są niezwykle znaczące. Algorytm przeszedł od osiągnięcia kafelka 16384 około 13% czasu do osiągnięcia go w 90% przypadków, a algorytm zaczął osiągać 32768 w 1/3 czasu (podczas gdy stara heurystyka nigdy nie produkowała kafelka 32768) .
Wierzę, że jest jeszcze miejsce na poprawę heurystyki. Ten algorytm zdecydowanie nie jest jeszcze „optymalny”, ale wydaje mi się, że jest już dość blisko.
To, że sztuczna inteligencja osiąga kafelek 32768 w ponad jednej trzeciej swoich gier, jest ogromnym kamieniem milowym; Z zaskoczeniem usłyszę, czy jakikolwiek ludzki gracz osiągnął 32768 w oficjalnej grze (tj. Bez użycia narzędzi takich jak zapisywanie stanu lub cofanie). Myślę, że płytka 65536 jest w zasięgu ręki!
Możesz wypróbować sztuczną inteligencję dla siebie. Kod jest dostępny na https://github.com/nneonneo/2048-ai .
źródło
var value = Math.random() < 0.9 ? 2 : 4;
.Jestem autorem programu AI, o którym wspominali inni w tym wątku. Możesz zobaczyć AI w akcji lub przeczytać źródło .
Obecnie program osiąga około 90% wygranej w javascript w przeglądarce na moim laptopie, biorąc pod uwagę około 100 milisekund czasu myślenia na ruch, więc chociaż nie jest idealny (jeszcze!), Działa całkiem dobrze.
Ponieważ gra jest dyskretną przestrzenią stanów, doskonałą informacją, turową grą, taką jak szachy i warcaby, zastosowałem te same metody, które sprawdziły się w tych grach, a mianowicie wyszukiwanie minimax z przycinaniem alfa-beta . Ponieważ istnieje już wiele informacji na temat tego algorytmu, porozmawiam tylko o dwóch głównych heurystykach, których używam w funkcji oceny statycznej i które formalizują wiele intuicji wyrażonych tutaj przez innych ludzi.
Monotoniczność
Ta heurystyka stara się zapewnić, że wartości płytek zwiększają się lub maleją zarówno w lewo / w prawo, jak i w górę / w dół. Tylko ta heurystyka oddaje intuicję, o której wspominało wielu innych, że płytki o wyższych wartościach powinny być skupione w rogu. Zazwyczaj zapobiega osieroceniu płytek o mniejszej wartości i utrzymuje porządek na planszy, z mniejszymi płytkami kaskadującymi się i wypełniającymi większe płytki.
Oto zrzut ekranu idealnie monotonicznej siatki. Osiągnąłem to, uruchamiając algorytm z ustawioną funkcją eval, aby zignorować inne heurystyki i rozważyć tylko monotoniczność.
Gładkość
Powyższa heurystyka ma tendencję do tworzenia struktur, w których sąsiadujące płytki tracą na wartości, ale oczywiście w celu scalenia sąsiadujące płytki muszą mieć tę samą wartość. Dlatego heurystyka gładkości mierzy tylko różnicę wartości między sąsiadującymi płytkami, próbując zminimalizować tę liczbę.
Komentator Hacker News przedstawił interesującą formalizację tego pomysłu pod względem teorii grafów.
Oto zrzut ekranu idealnie gładkiej siatki, dzięki uprzejmości tego doskonałego widelca do parodii .
Darmowe kafelki
I wreszcie, grozi kara za posiadanie zbyt małej liczby darmowych płytek, ponieważ opcje mogą się szybko skończyć, gdy plansza stanie się zbyt ciasna.
I to wszystko! Przeszukiwanie przestrzeni gry przy jednoczesnej optymalizacji tych kryteriów daje niezwykle dobrą wydajność. Jedną z zalet korzystania z takiego uogólnionego podejścia zamiast z wyraźnie zakodowaną strategią przenoszenia jest to, że algorytm często może znaleźć interesujące i nieoczekiwane rozwiązania. Jeśli patrzysz, jak biegnie, często wykona zaskakujące, ale skuteczne ruchy, na przykład nagle zmienia ścianę lub narożnik, na którym opiera się.
Edytować:
Oto demonstracja siły tego podejścia. Odblokowałem wartości płytek (więc kontynuowałem po osiągnięciu 2048) i oto najlepszy wynik po ośmiu próbach.
Tak, to 4096 obok 2048. =) Oznacza to, że osiągnął nieuchwytną płytkę 2048 trzy razy na tej samej planszy.
źródło
Zainteresowałem się pomysłem sztucznej inteligencji dla tej gry nie zawierającej zakodowanej inteligencji (tj. Bez heurystyki, funkcji oceniania itp.). AI powinna „znać” tylko zasady gry i „rozgryźć” rozgrywkę. Jest to w przeciwieństwie do większości AI (takich jak te w tym wątku), w których gra jest w zasadzie brutalną siłą sterowaną przez funkcję punktacji reprezentującą ludzkie rozumienie gry.
Algorytm AI
Znalazłem prosty, ale zaskakująco dobry algorytm gry: aby określić następny ruch dla danej planszy, AI gra w pamięć za pomocą losowych ruchów, aż gra się skończy. Odbywa się to kilka razy, śledząc końcowy wynik gry. Następnie obliczany jest średni wynik końcowy na ruch początkowy . Początkowy ruch z najwyższym średnim wynikiem końcowym jest wybierany jako następny ruch.
Przy zaledwie 100 przebiegach (tj. W grach pamięciowych) na ruch, AI osiąga płytkę 2048 80% razy, a płytkę 4096 50% razy. Korzystanie z 10000 przebiegów daje kafelek 2048 w 100%, 70% w kafelku 4096 i około 1% w kafelku 8192.
Zobacz to w akcji
Najlepszy osiągnięty wynik pokazano tutaj:
Ciekawym faktem na temat tego algorytmu jest to, że chociaż gry losowe są zaskakująco złe, wybranie najlepszego (lub najmniej złego) ruchu prowadzi do bardzo dobrej gry: Typowa gra AI może osiągnąć 70000 punktów i ostatnie 3000 ruchów, jednak gry losowe w pamięci z dowolnej pozycji dają średnio 340 dodatkowych punktów w około 40 dodatkowych ruchach przed śmiercią. (Możesz to zobaczyć, uruchamiając AI i otwierając konsolę debugowania).
Ten wykres ilustruje ten punkt: niebieska linia pokazuje wynik planszy po każdym ruchu. Czerwona linia pokazuje najlepszy wynik algorytmu w końcowej fazie gry z tej pozycji. Zasadniczo, czerwone wartości „przyciągają” niebieskie wartości w górę w ich kierunku, ponieważ są najlepszym przypuszczeniem algorytmu. Interesujące jest to, że czerwona linia znajduje się nieco powyżej niebieskiej linii w każdym punkcie, ale niebieska linia wciąż rośnie.
Wydaje mi się dość zaskakujące, że algorytm nie musi właściwie przewidywać dobrej gry, aby wybrać ruchy, które go wytwarzają.
Przeszukując później odkryłem, że ten algorytm może zostać sklasyfikowany jako algorytm czystego wyszukiwania drzewa Monte Carlo .
Wdrożenie i linki
Najpierw stworzyłem wersję JavaScript, którą można zobaczyć tutaj w akcji . Ta wersja może uruchomić setki uruchomień w przyzwoitym czasie. Otwórz konsolę, aby uzyskać dodatkowe informacje. ( źródło )
Później, aby się trochę pobawić, skorzystałem z wysoce zoptymalizowanej infrastruktury @nneonneo i zaimplementowałem swoją wersję w C ++. Ta wersja pozwala na maksymalnie 100000 przebiegów na ruch, a nawet 1000000, jeśli masz cierpliwość. Dostarczone instrukcje budowania. Działa w konsoli i ma również zdalne sterowanie do odtwarzania wersji internetowej. ( źródło )
Wyniki
Zaskakujące jest to, że zwiększenie liczby przebiegów nie poprawia drastycznie gry. Wydaje się, że limit tej strategii wynosi około 80000 punktów w przypadku płytki 4096 i wszystkich mniejszych, bardzo zbliżonych do osiągnięcia płytki 8192. Zwiększenie liczby przebiegów ze 100 do 100000 zwiększa szanse na osiągnięcie tego limitu punktów (z 5% do 40%), ale nie do jego przekroczenia.
Uruchomienie 10000 biegów z tymczasowym wzrostem do 1000000 w pobliżu pozycji krytycznych udało się przełamać tę barierę mniej niż 1% razy, osiągając maksymalny wynik 129892 i płytkę 8192.
Ulepszenia
Po wdrożeniu tego algorytmu wypróbowałem wiele ulepszeń, w tym użycie wyników minimalnych lub maksymalnych lub kombinacji wartości minimalnych, maksymalnych i średnich. Próbowałem także użyć głębokości: Zamiast próbować K ruchów na ruch, próbowałem K ruchów na listę ruchów o danej długości (na przykład „góra, góra, lewo”) i wybrałem pierwszy ruch z listy ruchów z najlepszą punktacją.
Później zaimplementowałem drzewo oceniania, które uwzględniało warunkowe prawdopodobieństwo zagrania ruchu po danej liście ruchów.
Jednak żaden z tych pomysłów nie wykazał żadnej rzeczywistej przewagi nad prostym pierwszym pomysłem. Zostawiłem kod dla tych pomysłów skomentowany w kodzie C ++.
Dodałem mechanizm „Głębokiego wyszukiwania”, który tymczasowo zwiększył liczbę przebiegów do 1000000, gdy którykolwiek z przebiegów zdołał przypadkowo dotrzeć do następnego najwyższego kafelka. To zapewniło poprawę czasu.
Chciałbym usłyszeć, czy ktoś ma inne pomysły na ulepszenia, które utrzymują niezależność AI od domeny.
2048 wariantów i klonów
Dla zabawy zaimplementowałem również AI jako bookmarklet , który kontroluje grę. Umożliwia to AI pracę z oryginalną grą i wieloma jej wariantami .
Jest to możliwe ze względu na niezależny od domeny charakter AI. Niektóre warianty są dość wyraźne, na przykład klon heksagonalny.
źródło
EDYCJA: Jest to naiwny algorytm modelujący ludzki świadomy proces myślowy i uzyskuje bardzo słabe wyniki w porównaniu do sztucznej inteligencji, która wyszukuje wszystkie możliwości, ponieważ patrzy tylko o jedną płytkę do przodu. Zostało przesłane wcześnie na osi czasu odpowiedzi.
Udoskonaliłem algorytm i pokonałem grę! Może się nie udać z powodu zwykłego pecha pod koniec (jesteś zmuszony zejść w dół, czego nigdy nie powinieneś robić, a kafelek pojawi się tam, gdzie powinna być najwyższa. Po prostu staraj się zapełnić górny rząd, aby poruszanie się w lewo nie było złamać wzór), ale w gruncie rzeczy ostatecznie masz stałą i ruchomą część do zabawy. To jest twój cel:
To jest model, który wybrałem domyślnie.
Wybrany róg jest dowolny, w zasadzie nigdy nie naciskasz jednego klawisza (zabroniony ruch), a jeśli to zrobisz, naciskasz ponownie przeciwnie i próbujesz go naprawić. W przypadku przyszłych kafelków model zawsze oczekuje, że następny losowy kafelek będzie równy 2 i pojawi się po przeciwnej stronie niż obecny model (podczas gdy pierwszy rząd jest niekompletny, w prawym dolnym rogu, po zakończeniu pierwszego rzędu, w lewym dolnym rogu kąt).
Oto algorytm. Około 80% wygrywa (wydaje się, że zawsze można wygrać dzięki bardziej „profesjonalnym” technikom sztucznej inteligencji, ale nie jestem tego pewien).
Kilka wskazówek na temat brakujących kroków. Tutaj:
Model zmienił się ze względu na szczęście zbliżenia się do oczekiwanego modelu. Model, który stara się osiągnąć AI
I łańcuch, aby się tam dostać, stał się:
O
Stanowią zakazane przestrzenie ...Więc naciska w prawo, następnie w prawo ponownie, a następnie (w prawo lub w górę w zależności od tego, gdzie utworzono 4), a następnie przejdzie do zakończenia łańcucha, dopóki nie uzyska:
Teraz model i łańcuch powracają do:
Drugi wskaźnik, miał pecha i zajęto jego główne miejsce. Prawdopodobnie zawiedzie, ale nadal może to osiągnąć:
Oto model i łańcuch:
Gdy uda mu się dotrzeć do 128, zyskuje cały rząd:
źródło
execute move with best score
jak możesz ocenić najlepszy wynik z możliwych następnych stanów?evaluateResult
tobie, po prostu staraj się zbliżyć do najlepszego możliwego scenariusza.Tutaj kopiuję treść posta na moim blogu
Rozwiązanie, które proponuję, jest bardzo proste i łatwe do wdrożenia. Chociaż osiągnął wynik 131040. Przedstawiono kilka testów wydajności algorytmu.
Algorytm
Heurystyczny algorytm punktacji
Założenie, na którym opiera się mój algorytm, jest dość proste: jeśli chcesz osiągnąć wyższy wynik, tablica musi być utrzymywana tak schludnie, jak to możliwe. W szczególności optymalną konfigurację zapewnia liniowy i monotoniczny malejący porządek wartości płytek. Ta intuicja da ci również górną granicę wartości kafelka: gdzie n jest liczbą kafelków na planszy.
(Istnieje możliwość dotarcia do kafelka 131072, jeśli 4-kafelek jest generowany losowo zamiast 2-kafelka w razie potrzeby)
Dwa możliwe sposoby organizacji tablicy pokazano na następujących obrazach:
Aby wymusić uporządkowanie płytek w monotonicznym porządku malejącym, wynik obliczono jako sumę zlinearyzowanych wartości na planszy pomnożonych przez wartości sekwencji geometrycznej o wspólnym stosunku r <1.
Jednocześnie można ocenić kilka ścieżek liniowych, końcowy wynik będzie maksymalnym wynikiem dowolnej ścieżki.
Reguła decyzyjna
Zaimplementowana reguła decyzyjna nie jest całkiem sprytna, kod w Pythonie jest przedstawiony tutaj:
Implementacja minmax lub Expectiminimax z pewnością poprawi algorytm. Oczywiście bardziej wyrafinowana reguła decyzyjna spowolni algorytm i zajmie to trochę czasu. W najbliższej przyszłości spróbuję implementacji minimax. (bądźcie czujni)
Reper
W przypadku T2 cztery testy na dziesięć generują płytkę 4096 ze średnią oceną 42000
Kod
Kod można znaleźć na GiHub pod następującym linkiem: https://github.com/Nicola17/term2048-AI Opiera się na term2048 i jest napisany w Pythonie. Jak najszybciej zaimplementuję bardziej wydajną wersję w C ++.
źródło
Moja próba wykorzystuje expectimax jak inne rozwiązania powyżej, ale bez bitboardów. Rozwiązanie Nneonneo może sprawdzić 10 milionów ruchów, co jest w przybliżeniu głębokością 4 z 6 pozostałymi płytkami i możliwymi 4 ruchami (2 * 6 * 4) 4 . W moim przypadku odkrywanie tej głębokości trwa zbyt długo, dostosowuję głębokość wyszukiwania expectimax do liczby wolnych płytek:
Punkty na planszach są obliczane na podstawie sumy ważonej kwadratu liczby wolnych płytek i iloczynu punktowego siatki 2D z tym:
co zmusza do układania płytek w sposób malejący w rodzaju węża z lewej górnej płytki.
kod poniżej lub na github :
źródło
cost=1x(number of empty tiles)²+1xdotproduct(snakeWeights,grid)
staramy się zmaksymalizować ten kosztJestem autorem kontrolera 2048, który osiąga lepsze wyniki niż jakikolwiek inny program wymieniony w tym wątku. Wydajna implementacja kontrolera jest dostępna na github . W osobnym repozytorium znajduje się również kod używany do szkolenia funkcji oceny stanu kontrolera. Metodę szkolenia opisano w artykule .
Kontroler wykorzystuje wyszukiwanie expectimax z funkcją oceny stanu wyuczoną od zera (bez wiedzy fachowej człowieka 2048) przez wariant uczenia się różnic czasowych (technika uczenia wzmacniającego). Funkcja stanu-wartości wykorzystuje sieć n-krotną , która jest w zasadzie ważoną funkcją liniową wzorców obserwowanych na płycie. Obejmował ponad 1 miliard ciężarówŁącznie .
Wydajność
Przy 1 ruchach / s: 609104 (średnio 100 gier)
Przy 10 ruchach / s: 589355 (średnio 300 gier)
Przy 3 warstwach ( około 1500 ruchów / s): 511759 (średnio 1000 gier)
Statystyki kafelków dla 10 ruchów / s są następujące:
(Ostatnia linia oznacza posiadanie podanych płytek jednocześnie na planszy).
Dla 3 warstw:
Jednak nigdy nie zauważyłem, aby uzyskał płytkę 65536.
źródło
Wydaje mi się, że znalazłem algorytm, który działa całkiem dobrze, ponieważ często osiągam wyniki powyżej 10000, a mój osobisty najlepszy wynik to około 16000. Moje rozwiązanie nie ma na celu utrzymania największych liczb w rogu, ale utrzymanie go w górnym rzędzie.
Zobacz poniższy kod:
źródło
770.6
, podczas gdy ta się właśnie skończyła396.7
. Czy wiesz, dlaczego tak jest? Myślę, że robi to zbyt wiele wzlotów, nawet jeśli lewo lub prawo połączyłyby się znacznie więcej.Jest już implementacja AI w tej grze tutaj . Fragment README:
W Hacker News toczy się także dyskusja na temat tego algorytmu, który może okazać się przydatny.
źródło
Algorytm
Ocena
Szczegóły oceny
Jest to stała używana jako podstawa i do innych zastosowań, takich jak testowanie.
Więcej przestrzeni uelastycznia stan, mnożymy przez 128 (co jest medianą), ponieważ siatka wypełniona 128 ścianami jest optymalnym stanem niemożliwym.
Tutaj oceniamy twarze, które mają możliwość scalenia, oceniając je wstecz, kafelek 2 staje się wartościowy 2048, podczas gdy kafelek 2048 jest oceniany 2.
Tutaj nadal musimy sprawdzać wartości skumulowane, ale w mniejszym stopniu, który nie zakłóca parametrów elastyczności, więc mamy sumę {x w [4,44]}.
Stan jest bardziej elastyczny, jeśli ma większą swobodę możliwych przejść.
Jest to uproszczone sprawdzenie możliwości połączenia w tym stanie bez uprzedzeń.
Uwaga: Stałe można dostosować.
źródło
constant
? Jeśli wszystko, co robisz, to porównywanie wyników, jak to wpływa na wynik tych porównań?To nie jest bezpośrednia odpowiedź na pytanie OP, to jest więcej rzeczy (eksperymentów), które próbowałem dotychczas rozwiązać ten sam problem i uzyskałem pewne wyniki i mam pewne spostrzeżenia, którymi chcę się podzielić, jestem ciekawy, czy możemy mieć jakieś dalsze spostrzeżenia z tego.
Właśnie wypróbowałem implementację minimax z przycinaniem alfa-beta z odcięciem głębokości drzewa wyszukiwania przy 3 i 5. Próbowałem rozwiązać ten sam problem dla siatki 4x4 jako zadanie projektu dla kursu edX ColumbiaX: CSMM.101x Sztuczna inteligencja ( AI) .
Zastosowałem wypukłą kombinację (wypróbowałem różne wagi heurystyczne) kilku heurystycznych funkcji oceny, głównie z intuicji i z omówionych powyżej:
W moim przypadku odtwarzacz komputerowy jest całkowicie losowy, ale mimo to przyjąłem ustawienia przeciwnika i zaimplementowałem agenta AI jako maksymalnego gracza.
Mam siatkę 4x4 do gry.
Obserwacja:
Jeśli przypiszę zbyt dużą wagę pierwszej funkcji heurystycznej lub drugiej funkcji heurystycznej, oba przypadki, które uzyska gracz AI, są niskie. Grałem z wieloma możliwymi przypisaniami wagi do funkcji heurystycznych i wybrałem wypukłą kombinację, ale bardzo rzadko gracz AI jest w stanie zdobyć 2048. W większości przypadków zatrzymuje się na 1024 lub 512.
Próbowałem również heurystykę narożną, ale z jakiegoś powodu pogarsza to wyniki, a jakaś intuicja, dlaczego?
Ponadto próbowałem zwiększyć wartość graniczną głębokości wyszukiwania z 3 do 5 (nie mogę jej zwiększyć, ponieważ wyszukiwanie miejsca przekracza dozwolony czas nawet przy przycinaniu) i dodałem jeszcze jedną heurystykę, która patrzy na wartości sąsiadujących płytek i daje więcej punktów, jeśli można je połączyć, ale nadal nie jestem w stanie zdobyć 2048.
Myślę, że lepiej będzie użyć Expectimax zamiast minimax, ale nadal chcę rozwiązać ten problem tylko z minimax i uzyskać wysokie wyniki, takie jak 2048 lub 4096. Nie jestem pewien, czy czegoś mi brakuje.
Poniższa animacja pokazuje kilka ostatnich kroków gry rozgrywanej przez agenta AI z graczem komputerowym:
Wszelkie spostrzeżenia będą naprawdę bardzo pomocne, z góry dzięki. (To jest link do mojego postu na blogu do artykułu: https://sandipanweb.wordpress.com/2017/03/06/using-minimax-with-alpha-beta-pruning-and-heuristic-evaluation-to-solve -2048-game-with-computer / i film na youtube: https://www.youtube.com/watch?v=VnVFilfZ0r4 )
Poniższa animacja pokazuje kilka ostatnich etapów rozgrywki, w których agent gracza AI może uzyskać 2048 punktów, tym razem dodając również wartość heurystyczną wartości bezwzględnej:
Poniższe liczby pokazują drzewo gry zbadane przez agenta AI gracza, zakładając, że komputer jest przeciwnikiem tylko na jeden krok:
źródło
Napisałem solver 2048 w Haskell, głównie dlatego, że teraz uczę się tego języka.
Moja implementacja gry nieco różni się od faktycznej gry, ponieważ nowy kafelek to zawsze „2” (zamiast 90% 2 i 10% 4). I że nowy kafelek nie jest losowy, ale zawsze pierwszy dostępny z lewego górnego rogu. Ten wariant jest również znany jako Det 2048 .
W rezultacie ten solver jest deterministyczny.
Użyłem wyczerpującego algorytmu, który faworyzuje puste kafelki. Działa dość szybko na głębokości 1-4, ale na głębokości 5 robi się dość wolno, około 1 sekundy na ruch.
Poniżej znajduje się kod implementujący algorytm rozwiązywania. Siatka jest reprezentowana jako tablica liczb całkowitych o długości 16. Ocenianie odbywa się po prostu przez zliczenie liczby pustych kwadratów.
Myślę, że jest dość udany ze względu na swoją prostotę. Wynik, który osiąga, rozpoczynając od pustej siatki i rozwiązując na głębokości 5, jest następujący:
Kod źródłowy można znaleźć tutaj: https://github.com/popovitsj/2048-haskell
źródło
Ten algorytm nie jest optymalny do wygrania gry, ale jest dość optymalny pod względem wydajności i ilości potrzebnego kodu:
źródło
random from (right, right, right, down, down, up)
nie wszystkie ruchy mają jednakowe prawdopodobieństwo. :)Wiele innych odpowiedzi wykorzystuje sztuczną inteligencję do kosztownego wyszukiwania możliwych przyszłości, heurystyki, uczenia się i tym podobnych. Są imponujące i prawdopodobnie słuszne, ale chciałbym wnieść kolejny pomysł.
Modeluj strategię, z której korzystają dobrzy gracze.
Na przykład:
Czytaj kwadraty w kolejności pokazanej powyżej, aż następna wartość kwadratów będzie większa niż bieżąca. Stwarza to problem próby połączenia kolejnej płytki o tej samej wartości z tym kwadratem.
Aby rozwiązać ten problem, istnieją 2 sposoby poruszania się, które nie zostały pozostawione ani gorzej, a zbadanie obu możliwości może natychmiast ujawnić więcej problemów, tworzy to listę zależności, z których każdy wymaga rozwiązania innego problemu w pierwszej kolejności. Wydaje mi się, że mam ten łańcuch lub, w niektórych przypadkach, drzewo zależności wewnętrznie przy podejmowaniu decyzji o kolejnym ruchu, szczególnie gdy utknęłam.
Kafelek wymaga scalenia z sąsiadem, ale jest zbyt mały: Scal z innym sąsiadem.
Większy kafelek w drodze: Zwiększ wartość mniejszego otaczającego kafelka.
itp...
Całe podejście będzie prawdopodobnie bardziej skomplikowane niż to, ale niewiele bardziej skomplikowane. Może to być mechaniczny brak odczuć, obciążeń, neuronów i głębokich poszukiwań możliwości. Drzewo możliwości po prostu musi być wystarczająco duże, aby w ogóle potrzebować rozgałęzienia.
źródło