Oyun 2048 için en uygun algoritma nedir?


1920

Geçenlerde 2048 maçında tökezledim . Benzer karoları "büyük" karolar yapmak için dört yönden herhangi birine taşıyarak birleştirirsiniz. Her hareketten sonra, rastgele bir boş konumda 2veya değeri olan yeni bir döşeme görünür 4. Tüm kutular dolduğunda ve fayansları birleştirebilecek hiçbir hareket olmadığında veya değeri olan bir fayans oluşturduğunuzda oyun sona erer 2048.

Birincisi, hedefe ulaşmak için iyi tanımlanmış bir strateji izlemem gerekiyor. Bunun için bir program yazmayı düşündüm.

Mevcut algoritmam:

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
}

Yaptığım şey herhangi bir noktada, fayansları değerlerle birleştirmeye çalışacağım 2ve 4yani, mümkün olduğunca minimum 2ve 4fayans yapmaya çalışacağım . Bu şekilde denersem, diğer tüm karolar otomatik olarak birleşiyordu ve strateji iyi görünüyor.

Ancak, bu algoritmayı gerçekten kullandığımda, oyun sona ermeden sadece 4000 puan kazanıyorum. Maksimum puan AFAIK, şu anki puanımdan çok daha büyük olan 20.000 puandan biraz fazla. Yukarıdakilerden daha iyi bir algoritma var mı?


84
Bu yardımcı olabilir! ov3y.github.io/2048-AI
cegprakash

5
@ nitish712 Bu arada, choose the move with large number of mergeshızlı bir şekilde yerel optima yol açan algoritmanız açgözlü
Khaled.K

21
@ 500-InternalServerError: Alfa-beta oyun ağacı budaması ile bir AI uygulayacak olsaydım , yeni blokların ters yerleştirildiğini varsayardım. Bu en kötü durum varsayımıdır, ancak faydalı olabilir.
Charles

6
Yüksek bir puanı hedeflemek için zamanınız olmadığında eğlenceli bir dikkat dağıtıcı: Mümkün olan en düşük puanı almaya çalışın. Teorik olarak 2s ve 4s dönüşümlüdür.
Mark Hurd

7
Bu sorunun meşruiyeti üzerine tartışma meta'da
Jeroen Vannevel

Yanıtlar:


1266

@ Ovolve algoritması tarafından kullanılan minimax araması yerine, waitimax optimizasyonunu kullanarak bir 2048 AI geliştirdim . AI basitçe tüm olası hareketler üzerinde maksimizasyonu gerçekleştirir, ardından tüm olası kiremit yumurtlamalarından beklenir (fayans olasılığı ile ağırlıklandırılır, yani 2 için% 10 ve 2 için% 90). Bildiğim kadarıyla, waitimax optimizasyonunu (aşırı derecede olası olmayan dalları kaldırmak hariç) budamak mümkün değildir ve bu nedenle kullanılan algoritma dikkatle optimize edilmiş bir kaba kuvvet aramasıdır.

Verim

AI, varsayılan yapılandırmasındaki (maksimum arama derinliği 8), kart konumunun karmaşıklığına bağlı olarak bir hareket gerçekleştirmek için 10ms ila 200ms arasında bir yer alır. Testte AI, tüm oyun boyunca saniyede ortalama 5-10 hamle elde eder. Arama derinliği 6 hareketle sınırlıysa, AI saniyede 20'den fazla hareket gerçekleştirebilir ve bu da ilginç bir izleme sağlar .

AI'nın puan performansını değerlendirmek için, AI'yi 100 kez çalıştırdım (tarayıcı oyununa uzaktan kumanda ile bağlı). Her karo için, bu karonun en az bir kez elde edildiği oyunların oranları:

2048: 100%
4096: 100%
8192: 100%
16384: 94%
32768: 36%

Tüm çalışmalarda minimum puan 124024'tür; elde edilen en yüksek puan 794076'dır. Ortalama puan 387222'dir. AI asla 2048 taşını elde edemedi (böylece 100 oyunda bir kez bile oyunu kaybetmedi); aslında, her koşuda en az bir kez 8192 kiremit elde etti !

İşte en iyi koşu ekran görüntüsü:

32768 kiremit, skor 794076

Bu oyun 96 dakika içinde 27830 hamle veya saniye başına ortalama 4.8 hamle aldı.

uygulama

Yaklaşımım, tüm kartı (16 giriş) tek bir 64 bit tamsayı olarak kodlar (burada karolar nybbles, yani 4 bit parçalar). 64 bitlik bir makinede bu, tüm kartın tek bir makine kaydında aktarılmasını sağlar.

Bit kaydırma işlemleri tek tek satırları ve sütunları ayıklamak için kullanılır. Tek bir satır veya sütun 16 bitlik bir miktardır, bu nedenle 65536 boyutundaki bir tablo, tek bir satır veya sütun üzerinde çalışan dönüşümleri kodlayabilir. Örneğin hareketler, her hareketin tek bir satırı veya sütunu nasıl etkilediğini açıklayan önceden hesaplanmış bir "hareket efekti tablosuna" 4 arama olarak uygulanır (örneğin, "sağa taşı" tablosu, "1122 -> 0023" girişini sıra [2,2,4,4] sağa taşındığında sıra [0,0,4,8] olur).

Puanlama ayrıca tablo araması kullanılarak da yapılır. Tablolar, tüm olası satırlarda / sütunlarda hesaplanan sezgisel puanlar içerir ve bir tahta için sonuçta elde edilen puan, her satır ve sütundaki tablo değerlerinin toplamıdır.

Bu pano temsili, hareket ve puanlama için masa arama yaklaşımı ile birlikte, AI'nın kısa bir süre içinde çok sayıda oyun durumunu aramasına izin verir (2011 ortası dizüstü bilgisayarımın bir çekirdeğinde saniyede 10.000.000'dan fazla oyun durumu).

Waitimax aramasının kendisi, "beklenti" adımları (olası tüm kiremit yumurtlama konumlarını ve değerlerini test etme ve optimize edilmiş puanlarını her olasılığın olasılığına göre ağırlıklandırma) ve "maksimize etme" adımları (olası tüm hareketleri test etme) arasında dönüşümlü olarak yinelenen bir arama olarak kodlanır ve en iyi puana sahip olanı seçme). Ağaç arama, daha önce görülen bir konumu gördüğünde (bir transpozisyon tablosu kullanarak ), önceden tanımlanmış bir derinlik sınırına ulaştığında veya çok olası olmayan bir tahta durumuna ulaştığında (ör. 6 "4" karo elde edilerek sona erer ) başlangıç ​​konumundan başlayarak). Tipik arama derinliği 4-8 harekettir.

Sezgisel

Optimizasyon algoritmasını uygun pozisyonlara yönlendirmek için birkaç buluşsal yöntem kullanılır. Sezgisel seçimin algoritmanın performansı üzerinde büyük bir etkisi vardır. Çeşitli buluşsal yöntemler ağırlıklandırılır ve belirli bir tahta pozisyonunun ne kadar "iyi" olduğunu belirleyen konumsal bir puan olarak birleştirilir. Optimizasyon araştırması, tüm olası tahta pozisyonlarının ortalama puanını en üst düzeye çıkarmayı amaçlayacaktır. Oyun tarafından gösterildiği gibi gerçek puanı, bir değil (büyük bir fayda üretebilir birleştirme Gecikmeli iken) çok ağır fayans birleştirme lehine ağırlıklı olduğundan, tahta puanı hesaplamak için kullanılan.

Başlangıçta, açık kareler ve kenarda büyük değerlere sahip olmak için "bonuslar" veren iki çok basit buluşsal yöntem kullandım. Bu sezgisel tarama oldukça iyi performans gösterdi, sıklıkla 16384'e ulaştı, ancak asla 32768'e ulaşmadı.

Petr Morávek (@xificurk) yapay zekamı aldı ve iki yeni buluşsal yöntem ekledi. İlk buluşsal yöntem, sıralar arttıkça artan monotonik olmayan sıralara ve sütunlara sahip olmak için bir cezaydı ve monotonik olmayan küçük sayı sıralarının skoru güçlü bir şekilde etkilememesini sağladı, ancak çok sayıda monotonik olmayan sıraların skoru önemli ölçüde etkilediğini garanti etti. İkinci buluşsal yöntem, açık alanlara ek olarak potansiyel birleşme sayısını (bitişik eşit değerler) saydı. Bu iki sezgisel tarama, algoritmayı monotonik levhalara (birleştirilmesi daha kolay) ve çok sayıda birleştirme ile tahta konumlarına itmeye hizmet etti (daha fazla etki için mümkün olan yerlerde birleştirmeleri hizalamaya teşvik etti).

Ayrıca Petr, sezgisel ağırlıkları mümkün olan en yüksek ortalama puanı elde etmek için ayarlandığı "meta-optimizasyon" stratejisini ( CMA-ES adı verilen bir algoritma kullanarak) kullanarak optimize etti .

Bu değişikliklerin etkisi son derece önemlidir. Algoritma, 16384 karoyu% 13 civarında elde etmeye ve zamanın% 90'ından fazlasına ulaşmaya gitti ve algoritma zamanın 1 / 3'ünde 32768'i elde etmeye başladı (oysa eski sezgisel tarama bir zamanlar 32768 karo üretmedi). .

Sezgisel tarama konusunda hala iyileştirme yapılabileceğine inanıyorum. Bu algoritma kesinlikle "optimal" değil, ama oldukça yaklaşıyor gibi hissediyorum.


AI, oyunlarının üçte birinden 32768 döşemesine ulaşması büyük bir kilometre taşıdır; Herhangi bir insan oyuncunun resmi oyunda 32768'e ulaşıp ulaşmadığını duyduğuma şaşıracağım (yani savestates veya geri alma gibi araçlar kullanmadan). Bence 65536 kiremit ulaşılabilir!

AI'yı kendiniz deneyebilirsiniz. Kodu https://github.com/nneonneo/2048-ai adresinde bulabilirsiniz .


12
@RobL: 2'ler% 90 oranında görünür; 4'ler% 10 oranında görünür. Bu öyle kaynak kodu : var value = Math.random() < 0.9 ? 2 : 4;.
nneonneo

35
Şu anda Cuda'ya hareket ediyor, böylece GPU daha iyi hızlar için işi yapıyor!
nimsson

25
@nneonneo Javascript için emscripten ile kodunuzu taşıdım ve şimdi tarayıcıda oldukça iyi çalışıyor ! Derlemek ve her şeye gerek kalmadan izlemek harika ... Firefox'ta performans oldukça iyi ...
reverse_engineer

7
4x4 ızgaradaki teorik sınır aslında 65536 değil, 131072 IS'dir. Ancak bu, doğru anda 4 almayı gerektirir (yani, her biri 4 .. 65536 ile dolu tüm tahta - 15 alan işgal edilir) ve tahta bunun üzerine kurulmalıdır. böylece gerçekten birleştirebilirsiniz.
Bodo Thiesen

5
: @nneonneo Sen 60 oyun% olarak 32k alma, hatta daha iyi görünüyor bizim AI, kontrol etmek isteyebilirsiniz github.com/aszczepanski/2048
cauchy

1253

Başkalarının bu konuda bahsettiği AI programının yazarıyım. Sen AI görebilirsiniz eylem veya okumak kaynağı .

Şu anda, program, hareket başına yaklaşık 100 milisaniye düşünme süresi verildiğinde, dizüstü bilgisayarımdaki tarayıcıda javascript'te çalışan% 90'lık bir kazanma oranı elde ediyor, bu yüzden mükemmel olmasa da (henüz!) Oldukça iyi bir performans sergiliyor.

Oyun ayrık bir devlet alanı, mükemmel bilgi, satranç ve dama gibi sıra tabanlı bir oyun olduğundan, bu oyunlarda çalıştığı kanıtlanmış aynı yöntemleri kullandım, yani alfa-beta budama ile minimum arama . Bu algoritma hakkında zaten çok fazla bilgi olduğundan, statik değerlendirme işlevinde kullandığım ve diğer insanların burada ifade ettiği sezgilerin çoğunu resmileştiren iki ana buluşsal yöntemden bahsedeceğim .

monotonluk

Bu sezgisel tarama, karoların değerlerinin hem sol / sağ hem de yukarı / aşağı yönlerde artmasını veya azalmasını sağlamaya çalışır. Tek başına bu sezgisel tarama, başkalarının bahsettiği, daha değerli taşların bir köşede kümelenmesi gerektiği sezgisini yakalar. Tipik olarak daha küçük değerli karoların yetim kalmasını önleyecek ve tahtayı çok düzenli tutacak, daha küçük karolar daha büyük karolara dökülüp doldurulacaktır.

İşte mükemmel monotonik bir ızgaranın ekran görüntüsü. Bunu, algoritmayı diğer sezgiselleri dikkate almayacak ve yalnızca tekdüzeliği göz önünde bulunduracak şekilde ayarlanan eval işleviyle çalıştırarak elde ettim.

Mükemmel bir monotonik 2048 kartı

Pürüzsüzlük

Yukarıdaki sezgisel tek başına, bitişik karoların değerinin azaldığı yapılar oluşturma eğilimindedir, ancak elbette birleştirmek için bitişik karoların aynı değerde olması gerekir. Bu nedenle, sezgisel pürüzsüzlük sadece komşu karolar arasındaki değer farkını ölçer ve bu sayıyı en aza indirmeye çalışır.

Hacker News üzerine yorum yapan biri, bu fikrin grafik teorisi açısından ilginç bir resmileştirilmesini sağladı .

İşte bu mükemmel parodi çatalı nezaketle mükemmel pürüzsüz bir ızgaranın ekran görüntüsü .

Mükemmel pürüzsüz 2048 tahta

Ücretsiz Fayans

Ve son olarak, çok az ücretsiz karoya sahip olmak için bir ceza var, çünkü oyun tahtası çok sıkıldığında seçenekler hızla tükenebilir.

Ve bu kadar! Bu kriterleri optimize ederken oyun alanında arama yapmak oldukça iyi bir performans sağlar. Açıkça kodlanmış bir hareket stratejisi yerine böyle genelleştirilmiş bir yaklaşım kullanmanın bir avantajı, algoritmanın genellikle ilginç ve beklenmedik çözümler bulabilmesidir. Çalıştırmasını izlerseniz, hangi duvarın veya köşenin aniden inşa edildiğini değiştirmek gibi genellikle şaşırtıcı ama etkili hareketler yapar.

Düzenle:

İşte bu yaklaşımın gücünün bir gösterimi. Karo değerlerini açtım (bu yüzden 2048'e ulaştıktan sonra devam etti) ve burada sekiz denemeden sonra en iyi sonuç.

4096

Evet, bu 2048'in yanında 4096. =) Bu, aynı tahtada üç kez zor 2048 döşemesini elde ettiği anlamına geliyor.


89
Bilgisayara '2' ve '4' taşlarını 'rakip' olarak yerleştirerek davranabilirsiniz.
Wei Yen

29
@WeiYen Tabii, ama bir minmax problemi olarak düşünmek oyun mantığına sadık değildir, çünkü bilgisayar kasıtlı olarak skoru en aza indirmek yerine fayansları belirli olasılıklarla rastgele yerleştiriyor.
koo

57
AI karoları rastgele yerleştirse de amaç kaybetmemek. Şanssız olmak, rakibin sizin için en kötü hamleyi seçmesiyle aynı şeydir. "Min" kısmı, şanssızlık yaşayabileceğiniz hiçbir kötü hamle olmaması için, muhafazakar bir şekilde oynamaya çalıştığınız anlamına gelir.
FryGuy

196
2048 çatal oluşturmak için bir fikir vardı, nerede bilgisayar yerine 2s ve 4s yerine AI koymak değerleri belirlemek için nerede. Sonuç: tamamen imkansızlık. Burada denenebilir: sztupy.github.io/2048-Hard
SztupY

30
@SztupY Vay canına, bu şeytani. Durumunuzu en az iyileştirecek parçayı da yerleştirmeye çalışan qntm.org/hatetris Hatetris'i hatırlatıyor .
14'te Patashu

145

Sabit kodlu zeka (yani buluşsal yöntem, puanlama işlevi vb.) İçermeyen bu oyun için yapay zeka fikriyle ilgilenmeye başladım . AI sadece oyun kurallarını “bilmeli” ve oyunu “anlamalı” . Bu, oyunun temel olarak insanın oyunla ilgili anlayışını temsil eden bir puanlama işlevinin yönlendirdiği kaba kuvvet olduğu çoğu yapay zekanın (bu konudaki olanlar gibi) aksine.

AI Algoritması

Basit ama şaşırtıcı derecede iyi bir oyun algoritması buldum: Belirli bir tahta için bir sonraki hamleyi belirlemek için AI, oyun bitene kadar oyunu rastgele hamleler kullanarak hafızada oynar . Bu, oyun sonu skorunu takip ederken birkaç kez yapılır. Daha sonra başlangıç ​​hareketi başına ortalama bitiş puanı hesaplanır. En yüksek ortalama bitiş puanına sahip başlangıç ​​hareketi bir sonraki hareket olarak seçilir.

Her hareket için sadece 100 koşu (yani hafıza oyunlarında), AI 2048 kutucuğunu zamanların% 80'ine ve 4096 kutucuğuna zamanların% 50'sine ulaşır. 10000 işlem kullanarak 2048 döşemesi% 100, 4096 döşemesi için% 70 ve 8192 döşemesi için yaklaşık% 1 elde edilir.

Hareket halindeyken görün

Elde edilen en iyi puan burada gösterilir:

en iyi puan

Bu algoritma hakkında ilginç bir gerçek, rastgele oyunların şaşırtıcı derecede kötü olmasına rağmen, en iyi (veya en az kötü) hareketi seçmenin çok iyi bir oyun oynamasına yol açmasıdır: Tipik bir AI oyunu 70000 puana ve son 3000 hamleye ulaşabilir, ancak Herhangi bir pozisyondaki hafıza içi rastgele oyun oyunları, ölmeden önce yaklaşık 40 ekstra hamlede ortalama 340 ek puan verir. (AI'yi çalıştırarak ve hata ayıklama konsolunu açarak bunu kendiniz görebilirsiniz.)

Bu grafik bu noktayı göstermektedir: Mavi çizgi her hareketten sonra tahta skorunu gösterir. Kırmızı çizgi, algoritmanın bu konumdan rastgele en iyi oyun sonu skorunu gösterir. Özünde, kırmızı değerler algoritmanın en iyi tahminde olduğu için mavi değerleri onlara doğru "çekiyor". Kırmızı çizginin her noktada mavi çizginin biraz üstünde olduğunu görmek ilginç, ancak mavi çizgi gittikçe artmaya devam ediyor.

puanlama grafiği

Algoritmanın, onu üreten hamleleri seçmek için iyi bir oyun oynamayı öngörmesi gerekmediğini oldukça şaşırtıcı buluyorum.

Daha sonra arama yaparak bu algoritmanın bir Saf Monte Carlo Ağacı Arama algoritması olarak sınıflandırılabileceğini buldum .

Uygulama ve Linkler

İlk olarak burada çalışırken görülebilecek bir JavaScript sürümü oluşturdum . Bu sürüm, 100'lerin koşularını uygun zamanda çalıştırabilir. Ek bilgi için konsolu açın. ( kaynak )

Daha sonra, biraz daha oynamak için @nneonneo yüksek düzeyde optimize edilmiş altyapı kullandım ve versiyonumu C ++ ile uyguladım. Bu sürüm, hamle başına 100000 ve hatta sabrınız varsa 1000000'e kadar çalışmayı sağlar. Bina talimatları sağlandı. Konsolda çalışır ve web sürümünü oynatmak için uzaktan kumandaya sahiptir. ( kaynak )

Sonuçlar

Şaşırtıcı bir şekilde, koşu sayısını arttırmak oyun oyununu önemli ölçüde iyileştirmez. Bu stratejinin 4096 kiremit ve 8192 kiremitin elde edilmesine çok yakın olan daha küçük olanlarla yaklaşık 80000 noktada bir sınırı olduğu görülmektedir. Koşu sayısının 100'den 100000'e çıkarılması , bu puan sınırına (% 5'ten% 40'a) ulaşma olasılığını arttırır, ancak bu puanı aşmaz.

Kritik pozisyonların yakınında 1000000'e geçici bir artışla 10000 koşmak, bu bariyeri% 1'den daha az kırmayı başardı ve maksimum puan 129892 ve 8192 kiremit elde etti.

İyileştirmeler

Bu algoritmayı uyguladıktan sonra, min veya maks puanları veya min, max ve avg kombinasyonlarını kullanmak gibi birçok iyileştirmeyi denedim. Ayrıca derinliği kullanarak çalıştı: Yerine Hamle başına K ishal çalışmakla, ben hamle başına K hamle çalıştı listenin (örneğin "yukarı, yukarı, sol") belirli bir uzunlukta ve en iyi skor hareket listesinin ilk hamleyi seçilmesi.

Daha sonra, belirli bir hareket listesinden sonra bir hamle oynayabilmenin koşullu olasılığını dikkate alan bir puanlama ağacı uyguladım.

Ancak, bu fikirlerin hiçbiri basit ilk fikre göre gerçek bir avantaj göstermedi. Bu fikirler için kod bıraktım C ++ kodunda yorumladı.

Herhangi bir çalışma yanlışlıkla bir sonraki en yüksek döşemeye ulaşmayı başardığında, çalışma sayısını geçici olarak 1000000'e çıkaran bir "Derin Arama" mekanizması ekledim. Bu bir zaman iyileştirmesi sundu.

AI'nın alandan bağımsızlığını koruyan başka iyileştirme fikirleri olup olmadığını duymak isterim.

2048 Varyantları ve Klonları

Sadece eğlenmek için , AI'yi oyunun kontrollerine bağlayan bir yer imi olarak da uyguladım . Bu, AI'nın orijinal oyun ve birçok varyantı ile çalışmasına izin verir .

Bu, AI'nin alandan bağımsız doğası nedeniyle mümkündür. Altıgen klon gibi bazı varyantlar oldukça farklıdır.


7
+1. Bir AI öğrencisi olarak bunu gerçekten ilginç buldum. Boş zamanlarında buna daha iyi bakacağız.
Isaac

4
Bu harika! Sadece waitimax için iyi bir sezgisel işlev için ağırlıkları optimize ederek saatler geçirdim ve bunu 3 dakika içinde uyguladım ve bu tamamen çökertti.
Brendan Annable

8
Monte Carlo simülasyonunun güzel kullanımı.
nneonneo

5
Bu oyunu izlemek bir aydınlanma çağrısı yapıyor. Bu, tüm sezgiselleri patlatır, ancak işe yarar. Tebrikler!
Stéphane Gourichon

4
Şimdiye kadar, burada en ilginç çözüm.
shebaw

126

EDIT: Bu, insan bilinçli düşünce sürecini modelleyerek saf bir algoritma olduğunu ve sadece bir karo ileriye baktığı için tüm olasılıkları araştıran yapay zeka ile karşılaştırıldığında çok zayıf sonuçlar alır. Yanıt zaman çizelgesinde erken gönderilmişti.

Algoritmayı geliştirdim ve oyunu yendim! Sonuna yakın basit kötü şans nedeniyle başarısız olabilir (aşağı hareket etmek zorunda kalırsınız, asla yapmamanız gerekir ve en yüksek seviyenizin olması gereken yerde bir karo görünür. ancak temel olarak sabit bir parça ve oynatılacak bir mobil parçaya sahip olursunuz. Hedefiniz bu:

Bitirmeye hazır

Bu, varsayılan olarak seçtiğim model.

1024 512 256 128
  8   16  32  64
  4   2   x   x
  x   x   x   x

Seçilen köşe keyfi, temelde hiçbir zaman bir tuşa (yasak hareket) basmazsınız ve yaparsanız, tam tersine tekrar basar ve düzeltmeye çalışırsınız. Gelecekteki döşemeler için, model her zaman bir sonraki rastgele döşemenin 2 olmasını bekler ve mevcut modelin karşı tarafında görünür (ilk satır eksikken, ilk satır tamamlandığında, sol altta, ilk satır tamamlandığında köşe).

İşte algoritma. Yaklaşık% 80 kazanır (daha "profesyonel" AI teknikleri ile kazanmak her zaman mümkün görünüyor, yine de bundan emin değilim.)

initiateModel();

while(!game_over)
{    
    checkCornerChosen(); // Unimplemented, but it might be an improvement to change the reference point

    for each 3 possible move:
        evaluateResult()
    execute move with best score
    if no move is available, execute forbidden move and undo, recalculateModel()
 }

 evaluateResult() {
     calculatesBestCurrentModel()
     calculates distance to chosen model
     stores result
 }

 calculateBestCurrentModel() {
      (according to the current highest tile acheived and their distribution)
  }

Eksik adımlar üzerinde birkaç işaretçi. Buraya:model değişikliği

Beklenen modele daha yakın olma şansı nedeniyle model değişti. AI'nın elde etmeye çalıştığı model

 512 256 128  x
  X   X   x   x
  X   X   x   x
  x   x   x   x

Ve oraya ulaşma zinciri haline geldi:

 512 256  64  O
  8   16  32  O
  4   x   x   x
  x   x   x   x

OYasak alanlarda temsil ...

Böylece sağa, sonra tekrar sağa basacak, sonra (4'ün yaratıldığı yere bağlı olarak sağ veya üst), o zamana kadar zinciri tamamlamaya devam edecek:

Zincir tamamlandı

Şimdi model ve zincir geri döndü:

 512 256 128  64
  4   8  16   32
  X   X   x   x
  x   x   x   x

İkinci işaretçi, kötü şansa sahipti ve ana noktası alındı. Başarısız olması muhtemeldir, ancak yine de başarabilir:

Resim açıklamasını buraya girin

İşte model ve zincir:

  O 1024 512 256
  O   O   O  128
  8  16   32  64
  4   x   x   x

128'e ulaşmayı başardığında tekrar bütün bir sıra kazanılır:

  O 1024 512 256
  x   x  128 128
  x   x   x   x
  x   x   x   x

execute move with best scoreolası gelecek durumlardan en iyi puanı nasıl değerlendirebilirsiniz?
Khaled.K

sezgisel olarak tanımlanır evaluateResulttemelde mümkün olan en iyi senaryoya en yakın almaya çalışın.
Daren

@Daren Ayrıntılı bilgi için bekliyorum
ashu

@ashu Üzerinde çalışıyorum, beklenmedik durumlar beni bitirmek için zaman bırakmadı. Bu arada algoritmayı geliştirdim ve şimdi zamanın% 75'ini çözüyor.
Daren

13
Bu strateji hakkında gerçekten sevdiğim şey, oyunu manuel olarak oynarken kullanabileceğim, beni 37k puana kadar yükseltti.
Cephalopod

94

Buraya blogumdaki bir gönderinin içeriğini kopyalarım


Önerdiğim çözüm çok basit ve uygulanması kolaydır. Her ne kadar 131040 puanına ulaştı. Algoritma performanslarının çeşitli kriterleri sunulmuştur.

Puan

Algoritma

Sezgisel puanlama algoritması

Algoritmamın dayandığı varsayım oldukça basittir: daha yüksek bir puan elde etmek istiyorsanız, tahta mümkün olduğunca düzenli tutulmalıdır. Özellikle, optimal kurulum karo değerlerinin doğrusal ve monotonik azalan düzeniyle verilir. Bu sezgi ayrıca bir döşeme değeri için üst sınırı verecektir: sburada n, tahtadaki döşeme sayısıdır.

(Gerektiğinde 2 kiremit yerine 4 kiremit rastgele oluşturulursa, 131072 döşemeye ulaşma olasılığı vardır)

Tahtayı düzenlemenin iki olası yolu aşağıdaki resimlerde gösterilmiştir:

resim açıklamasını buraya girin

Fayansların monotonik azalan bir sırada uygulanmasını sağlamak için, puan si, tahtadaki doğrusallaştırılmış değerlerin toplamı olarak hesaplanır ve ortak oran r <1 olan bir geometrik sekansın değerleriyle çarpılır.

s

s

Birkaç doğrusal yol bir kerede değerlendirilebilir, nihai puan herhangi bir yolun maksimum puanı olacaktır.

Karar kuralı

Uygulanan karar kuralı oldukça akıllı değil, Python'daki kod burada sunuluyor:

@staticmethod
def nextMove(board,recursion_depth=3):
    m,s = AI.nextMoveRecur(board,recursion_depth,recursion_depth)
    return m

@staticmethod
def nextMoveRecur(board,depth,maxDepth,base=0.9):
    bestScore = -1.
    bestMove = 0
    for m in range(1,5):
        if(board.validMove(m)):
            newBoard = copy.deepcopy(board)
            newBoard.move(m,add_tile=True)

            score = AI.evaluate(newBoard)
            if depth != 0:
                my_m,my_s = AI.nextMoveRecur(newBoard,depth-1,maxDepth)
                score += my_s*pow(base,maxDepth-depth+1)

            if(score > bestScore):
                bestMove = m
                bestScore = score
    return (bestMove,bestScore);

Minmax veya Expectiminimax uygulaması algoritmayı kesinlikle geliştirecektir. Açıkçası daha karmaşık bir karar kuralı algoritmayı yavaşlatacak ve uygulanması biraz zaman alacak ve yakın gelecekte bir minimax uygulaması deneyeceğim. (bizi izlemeye devam edin)

Karşılaştırma

  • T1 - 121 testleri - 8 farklı yol - r = 0.125
  • T2 - 122 testleri - 8 farklı yol - r = 0.25
  • T3 - 132 testleri - 8 farklı yol - r = 0.5
  • T4 - 211 testleri - 2 farklı yol - r = 0.125
  • T5 - 274 testleri - 2 farklı yol - r = 0.25
  • T6 - 211 testleri - 2 farklı yol - r = 0.5

resim açıklamasını buraya girin resim açıklamasını buraya girin resim açıklamasını buraya girin resim açıklamasını buraya girin

T2 durumunda, onda dört test ortalama s42000 puanla 4096 kiremit üretir.

kod

Kod aşağıdaki bağlantıda GiHub bulunabilir: https://github.com/Nicola17/term2048-AI Bu dayanmaktadır term2048 ve Python ile yazılmış. En kısa sürede C ++ 'da daha verimli bir sürüm uygulayacağım.


Fena değil, illüstrasyonunuz birleştirme vektörlerini değerlendirmeye almakla ilgili bana bir fikir verdi
Khaled.K

Merhaba. Github sayfasında verilen talimatların projeniz için geçerli olduğundan emin misiniz? Denemek istiyorum ama AI autorun için değil, orijinal oynanabilir oyunun talimatları gibi görünüyor. Bunları güncelleyebilir misiniz? Teşekkürler.
JD Gamboa

41

Denemem, yukarıdaki diğer çözümler gibi waitimax kullanıyor, ancak bitboard'ları kullanmıyor. Nneonneo'nun çözümü, yaklaşık 6 karo ve 4 hamle (2 * 6 * 4) 4 ile yaklaşık 4 derinlik olan 10 milyon hareketi kontrol edebilir . Benim durumumda, bu derinliğin keşfedilmesi çok uzun sürüyor, waitimax aramasının derinliğini kalan serbest döşeme sayısına göre ayarlıyorum:

depth = free > 7 ? 1 : (free > 4 ? 2 : 3)

Kurulların puanları, serbest fayans sayısının karesinin ağırlıklı 2D toplamı ve 2D ızgarasının nokta çarpımı ile şu şekilde hesaplanır:

[[10,8,7,6.5],
 [.5,.7,1,3],
 [-.5,-1.5,-1.8,-2],
 [-3.8,-3.7,-3.5,-3]]

fayansları sol üst karodan bir tür yılanta inen şekilde düzenlemeye zorlar.

aşağıda veya github üzerinde kod :

var n = 4,
	M = new MatrixTransform(n);

var ai = {weights: [1, 1], depth: 1}; // depth=1 by default, but we adjust it on every prediction according to the number of free tiles

var snake= [[10,8,7,6.5],
            [.5,.7,1,3],
            [-.5,-1.5,-1.8,-2],
            [-3.8,-3.7,-3.5,-3]]
snake=snake.map(function(a){return a.map(Math.exp)})

initialize(ai)

function run(ai) {
	var p;
	while ((p = predict(ai)) != null) {
		move(p, ai);
	}
	//console.log(ai.grid , maxValue(ai.grid))
	ai.maxValue = maxValue(ai.grid)
	console.log(ai)
}

function initialize(ai) {
	ai.grid = [];
	for (var i = 0; i < n; i++) {
		ai.grid[i] = []
		for (var j = 0; j < n; j++) {
			ai.grid[i][j] = 0;
		}
	}
	rand(ai.grid)
	rand(ai.grid)
	ai.steps = 0;
}

function move(p, ai) { //0:up, 1:right, 2:down, 3:left
	var newgrid = mv(p, ai.grid);
	if (!equal(newgrid, ai.grid)) {
		//console.log(stats(newgrid, ai.grid))
		ai.grid = newgrid;
		try {
			rand(ai.grid)
			ai.steps++;
		} catch (e) {
			console.log('no room', e)
		}
	}
}

function predict(ai) {
	var free = freeCells(ai.grid);
	ai.depth = free > 7 ? 1 : (free > 4 ? 2 : 3);
	var root = {path: [],prob: 1,grid: ai.grid,children: []};
	var x = expandMove(root, ai)
	//console.log("number of leaves", x)
	//console.log("number of leaves2", countLeaves(root))
	if (!root.children.length) return null
	var values = root.children.map(expectimax);
	var mx = max(values);
	return root.children[mx[1]].path[0]

}

function countLeaves(node) {
	var x = 0;
	if (!node.children.length) return 1;
	for (var n of node.children)
		x += countLeaves(n);
	return x;
}

function expectimax(node) {
	if (!node.children.length) {
		return node.score
	} else {
		var values = node.children.map(expectimax);
		if (node.prob) { //we are at a max node
			return Math.max.apply(null, values)
		} else { // we are at a random node
			var avg = 0;
			for (var i = 0; i < values.length; i++)
				avg += node.children[i].prob * values[i]
			return avg / (values.length / 2)
		}
	}
}

function expandRandom(node, ai) {
	var x = 0;
	for (var i = 0; i < node.grid.length; i++)
		for (var j = 0; j < node.grid.length; j++)
			if (!node.grid[i][j]) {
				var grid2 = M.copy(node.grid),
					grid4 = M.copy(node.grid);
				grid2[i][j] = 2;
				grid4[i][j] = 4;
				var child2 = {grid: grid2,prob: .9,path: node.path,children: []};
				var child4 = {grid: grid4,prob: .1,path: node.path,children: []}
				node.children.push(child2)
				node.children.push(child4)
				x += expandMove(child2, ai)
				x += expandMove(child4, ai)
			}
	return x;
}

function expandMove(node, ai) { // node={grid,path,score}
	var isLeaf = true,
		x = 0;
	if (node.path.length < ai.depth) {
		for (var move of[0, 1, 2, 3]) {
			var grid = mv(move, node.grid);
			if (!equal(grid, node.grid)) {
				isLeaf = false;
				var child = {grid: grid,path: node.path.concat([move]),children: []}
				node.children.push(child)
				x += expandRandom(child, ai)
			}
		}
	}
	if (isLeaf) node.score = dot(ai.weights, stats(node.grid))
	return isLeaf ? 1 : x;
}



var cells = []
var table = document.querySelector("table");
for (var i = 0; i < n; i++) {
	var tr = document.createElement("tr");
	cells[i] = [];
	for (var j = 0; j < n; j++) {
		cells[i][j] = document.createElement("td");
		tr.appendChild(cells[i][j])
	}
	table.appendChild(tr);
}

function updateUI(ai) {
	cells.forEach(function(a, i) {
		a.forEach(function(el, j) {
			el.innerHTML = ai.grid[i][j] || ''
		})
	});
}


updateUI(ai);
updateHint(predict(ai));

function runAI() {
	var p = predict(ai);
	if (p != null && ai.running) {
		move(p, ai);
		updateUI(ai);
		updateHint(p);
		requestAnimationFrame(runAI);
	}
}
runai.onclick = function() {
	if (!ai.running) {
		this.innerHTML = 'stop AI';
		ai.running = true;
		runAI();
	} else {
		this.innerHTML = 'run AI';
		ai.running = false;
		updateHint(predict(ai));
	}
}


function updateHint(dir) {
	hintvalue.innerHTML = ['↑', '→', '↓', '←'][dir] || '';
}

document.addEventListener("keydown", function(event) {
	if (!event.target.matches('.r *')) return;
	event.preventDefault(); // avoid scrolling
	if (event.which in map) {
		move(map[event.which], ai)
		console.log(stats(ai.grid))
		updateUI(ai);
		updateHint(predict(ai));
	}
})
var map = {
	38: 0, // Up
	39: 1, // Right
	40: 2, // Down
	37: 3, // Left
};
init.onclick = function() {
	initialize(ai);
	updateUI(ai);
	updateHint(predict(ai));
}


function stats(grid, previousGrid) {

	var free = freeCells(grid);

	var c = dot2(grid, snake);

	return [c, free * free];
}

function dist2(a, b) { //squared 2D distance
	return Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2)
}

function dot(a, b) {
	var r = 0;
	for (var i = 0; i < a.length; i++)
		r += a[i] * b[i];
	return r
}

function dot2(a, b) {
	var r = 0;
	for (var i = 0; i < a.length; i++)
		for (var j = 0; j < a[0].length; j++)
			r += a[i][j] * b[i][j]
	return r;
}

function product(a) {
	return a.reduce(function(v, x) {
		return v * x
	}, 1)
}

function maxValue(grid) {
	return Math.max.apply(null, grid.map(function(a) {
		return Math.max.apply(null, a)
	}));
}

function freeCells(grid) {
	return grid.reduce(function(v, a) {
		return v + a.reduce(function(t, x) {
			return t + (x == 0)
		}, 0)
	}, 0)
}

function max(arr) { // return [value, index] of the max
	var m = [-Infinity, null];
	for (var i = 0; i < arr.length; i++) {
		if (arr[i] > m[0]) m = [arr[i], i];
	}
	return m
}

function min(arr) { // return [value, index] of the min
	var m = [Infinity, null];
	for (var i = 0; i < arr.length; i++) {
		if (arr[i] < m[0]) m = [arr[i], i];
	}
	return m
}

function maxScore(nodes) {
	var min = {
		score: -Infinity,
		path: []
	};
	for (var node of nodes) {
		if (node.score > min.score) min = node;
	}
	return min;
}


function mv(k, grid) {
	var tgrid = M.itransform(k, grid);
	for (var i = 0; i < tgrid.length; i++) {
		var a = tgrid[i];
		for (var j = 0, jj = 0; j < a.length; j++)
			if (a[j]) a[jj++] = (j < a.length - 1 && a[j] == a[j + 1]) ? 2 * a[j++] : a[j]
		for (; jj < a.length; jj++)
			a[jj] = 0;
	}
	return M.transform(k, tgrid);
}

function rand(grid) {
	var r = Math.floor(Math.random() * freeCells(grid)),
		_r = 0;
	for (var i = 0; i < grid.length; i++) {
		for (var j = 0; j < grid.length; j++) {
			if (!grid[i][j]) {
				if (_r == r) {
					grid[i][j] = Math.random() < .9 ? 2 : 4
				}
				_r++;
			}
		}
	}
}

function equal(grid1, grid2) {
	for (var i = 0; i < grid1.length; i++)
		for (var j = 0; j < grid1.length; j++)
			if (grid1[i][j] != grid2[i][j]) return false;
	return true;
}

function conv44valid(a, b) {
	var r = 0;
	for (var i = 0; i < 4; i++)
		for (var j = 0; j < 4; j++)
			r += a[i][j] * b[3 - i][3 - j]
	return r
}

function MatrixTransform(n) {
	var g = [],
		ig = [];
	for (var i = 0; i < n; i++) {
		g[i] = [];
		ig[i] = [];
		for (var j = 0; j < n; j++) {
			g[i][j] = [[j, i],[i, n-1-j],[j, n-1-i],[i, j]]; // transformation matrix in the 4 directions g[i][j] = [up, right, down, left]
			ig[i][j] = [[j, i],[i, n-1-j],[n-1-j, i],[i, j]]; // the inverse tranformations
		}
	}
	this.transform = function(k, grid) {
		return this.transformer(k, grid, g)
	}
	this.itransform = function(k, grid) { // inverse transform
		return this.transformer(k, grid, ig)
	}
	this.transformer = function(k, grid, mat) {
		var newgrid = [];
		for (var i = 0; i < grid.length; i++) {
			newgrid[i] = [];
			for (var j = 0; j < grid.length; j++)
				newgrid[i][j] = grid[mat[i][j][k][0]][mat[i][j][k][1]];
		}
		return newgrid;
	}
	this.copy = function(grid) {
		return this.transform(3, grid)
	}
}
body {
	font-family: Arial;
}
table, th, td {
	border: 1px solid black;
	margin: 0 auto;
	border-collapse: collapse;
}
td {
	width: 35px;
	height: 35px;
	text-align: center;
}
button {
	margin: 2px;
	padding: 3px 15px;
	color: rgba(0,0,0,.9);
}
.r {
	display: flex;
	align-items: center;
	justify-content: center;
	margin: .2em;
	position: relative;
}
#hintvalue {
	font-size: 1.4em;
	padding: 2px 8px;
	display: inline-flex;
	justify-content: center;
	width: 30px;
}
<table title="press arrow keys"></table>
<div class="r">
    <button id=init>init</button>
    <button id=runai>run AI</button>
    <span id="hintvalue" title="Best predicted move to do, use your arrow keys" tabindex="-1"></span>
</div>


3
Bunun neden daha fazla oyu olmadığından emin değilim. Basitliği için gerçekten etkilidir.
David Greydanus

Teşekkürler, geç cevap ve gerçekten iyi performans göstermiyor (neredeyse her zaman [1024, 8192] 'de), maliyet / istatistik işlevi daha fazla çalışmaya ihtiyaç duyar
caub

Boş alanları nasıl ağırlıklandırdınız?
David Greydanus

1
Basitçe cost=1x(number of empty tiles)²+1xdotproduct(snakeWeights,grid)ve bu maliyeti en üst düzeye çıkarmaya çalışıyoruz
caub

teşekkürler @Robusto, kodu bir gün geliştirmeliyim, basitleştirilebilir
19'da caub

38

Bu iş parçacığında bahsedilen diğer programlardan daha iyi puan alan 2048 denetleyicinin yazarıyım. Kontrolörün etkin bir uygulaması github'da mevcuttur . Gelen ayrı repo da kontrolörün devlet değerlendirme fonksiyonunu eğitimi için kullanılan kod yoktur. Eğitim yöntemi makalede açıklanmıştır .

Kontrolör, tempoima fark öğrenme (bir takviye öğrenme tekniği) çeşidi tarafından sıfırdan (insan 2048 uzmanlığı olmadan) öğrenilen bir durum değerlendirme fonksiyonu ile waitimax aramasını kullanır . Durum-değer fonksiyonu , temel olarak kartta gözlenen örüntülerin ağırlıklı bir doğrusal fonksiyonu olan bir n-tuple ağı kullanır . Toplamda 1 milyardan fazla ağırlık içeriyordu .

Verim

1 hamle / sn: 609104 (ortalama 100 oyun)

10 hamle / sn: 589355 (ortalama 300 oyun)

3 katlı (yaklaşık 1500 hamle / sn): 511759 (ortalama 1000 oyun)

10 hamle / s için karo istatistikleri aşağıdaki gibidir:

2048: 100%
4096: 100%
8192: 100%
16384: 97%
32768: 64%
32768,16384,8192,4096: 10%

(Son satır, verilen döşemeleri tahtada aynı anda bulundurmak anlamına gelir).

3 katlı için:

2048: 100%
4096: 100%
8192: 100%
16384: 96%
32768: 54%
32768,16384,8192,4096: 8%

Ancak, 65536 kiremit elde ettiğini hiç gözlemlemedim.


4
Oldukça etkileyici sonuç. Ancak, cevabınızı programınızı nasıl başardığını açıklamak için (kabaca basit terimlerle ... Burada ayrıntıları göndermek için çok uzun olacağından eminim) muhtemelen cevabı güncelleyebilir misiniz? Öğrenme algoritmasının nasıl çalıştığına dair kabaca bir açıklamada olduğu gibi?
Cedric Mamo

27

Sanırım oldukça iyi çalışan bir algoritma buldum, genellikle 10000'in üzerinde puanlara ulaşıyorum, kişisel en iyim 16000 civarında. Benim çözümüm en büyük sayıları köşede tutmayı değil, üst sırada tutmayı amaçlıyor.

Lütfen aşağıdaki koda bakın:

while( !game_over ) {
    move_direction=up;
    if( !move_is_possible(up) ) {
        if( move_is_possible(right) && move_is_possible(left) ){
            if( number_of_empty_cells_after_moves(left,up) > number_of_empty_cells_after_moves(right,up) ) 
                move_direction = left;
            else
                move_direction = right;
        } else if ( move_is_possible(left) ){
            move_direction = left;
        } else if ( move_is_possible(right) ){
            move_direction = right;
        } else {
            move_direction = down;
        }
    }
    do_move(move_direction);
}

5
100.000 oyun, "yukarı, sağ, yukarı, sol, ..." (ve gerekirse aşağı) önemsiz döngüsel stratejisine karşı test ettim. Döngüsel strateji "ortalama bir çini skoru" nu bitirirken, bu strateji daha 770.6yeni oldu 396.7. Bunun neden olabileceğini tahmin ediyor musunuz? Çok fazla yükseliş olduğunu düşünüyorum, sol veya sağ çok daha fazla birleştiğinde bile.
Thomas Ahle

1
Fayanslar, birden fazla yöne kaydırılmamışlarsa, uyumsuz şekillerde istifleme eğilimindedirler. Genel olarak, döngüsel bir stratejinin kullanılması, merkezdeki daha büyük fayanslarla sonuçlanacak ve bu da manevrayı çok daha sıkışık hale getirecektir.
bcdan

25

Burada bu oyun için zaten bir AI uygulaması var . README'den alıntı:

Algoritma yinelemeli derinlik ilk alfa-beta araştırmasıdır. Değerlendirme işlevi, ızgaradaki döşemelerin sayısını en aza indirirken satırları ve sütunları monotonik tutmaya çalışır (hepsi azalır veya artar).

Hacker News hakkında bu algoritma hakkında yararlı bulabileceğiniz bir tartışma var .


4
Bu en iyi yanıt olmalı, ancak uygulama hakkında daha fazla ayrıntı eklemek güzel olurdu: örneğin oyun tahtasının nasıl modellendiğini (grafik olarak), kullanılan optimizasyonu (fayanslar arasındaki fark min-max) vb.
Alceu Costa

1
Gelecekteki okuyucular için: Bu, yazarı (ovolve) tarafından buradaki en üstteki ikinci cevapta açıklanan aynı programdır . Bu cevap ve bu tartışmadaki ovolve programının diğer sözleri, ovolve'nin ortaya çıkmasını ve algoritmasının nasıl çalıştığını yazmasını sağladı; bu cevabın 1200 puanı var.
MultiplyByZer0

23

Algoritma

while(!game_over)
{
    for each possible move:
        evaluate next state

    choose the maximum evaluation
}

Değerlendirme

Evaluation =
    128 (Constant)
    + (Number of Spaces x 128)
    + Sum of faces adjacent to a space { (1/face) x 4096 }
    + Sum of other faces { log(face) x 4 }
    + (Number of possible next moves x 256)
    + (Number of aligned values x 2)

Değerlendirme Detayları

128 (Constant)

Bu, bir taban hattı olarak ve test gibi diğer kullanımlar için kullanılan bir sabittir.

+ (Number of Spaces x 128)

Daha fazla alan durumu daha esnek hale getirir, 128 yüzle dolu bir ızgara optimal bir imkansız durum olduğundan, 128 ile (medyan) çoğalırız.

+ Sum of faces adjacent to a space { (1/face) x 4096 }

Burada, birleştirme imkanına sahip yüzler değerlendirilir, geriye dönük olarak değerlendirilerek, kiremit 2 değeri 2048, kiremit 2048 ise 2 olarak değerlendirilir.

+ Sum of other faces { log(face) x 4 }

Burada hala yığılmış değerleri kontrol etmemiz gerekiyor, ancak esneklik parametrelerini kesintiye uğratmayan daha az bir şekilde, bu nedenle {4,44]} içindeki {x toplamına sahibiz.

+ (Number of possible next moves x 256)

Bir devlet, olası geçiş özgürlüğü daha fazla ise daha esnektir.

+ (Number of aligned values x 2)

Bu, ileriye bakmadan bu durumda birleşme olasılığının basitleştirilmiş bir kontrolüdür.

Not: Sabitler değiştirilebilir.


2
@ Nitish712
Khaled.K

9
Bu algoritmanın% kazancı nedir?
cegprakash

Neden ihtiyacınız var constant? Yaptığınız tek şey puanları karşılaştırmaksa, bu karşılaştırmaların sonucunu nasıl etkiler?
bcdan

@bcdan sezgisel (karşılaştırma-skor olarak da bilinir), satranç sezgisel yönteminin nasıl çalıştığına benzer şekilde, gelecekteki durumun beklenen değerini karşılaştırmaya bağlıdır, ancak bu, sonraki en iyi N hamleyi bilmek için bir ağaç inşa etmediğimiz için, doğrusal bir sezgisel taramadır
Khaled.K

12

Bu OP'nin sorusuna doğrudan bir cevap değil, bu şimdiye kadar aynı sorunu çözmek için denediğim şeyler (deneyler) ve bazı sonuçlar elde ettim ve paylaşmak istediğim bazı gözlemlerim var, merak edip edemeyeceğimizi merak ediyorum bundan daha fazla bilgi edinebilirsiniz.

Minimax uygulamamı 3 ve 5'te arama ağacı derinliği kesme ile alfa-beta budama ile denedim. 4X ızgara için aynı sorunu edX kursu için bir proje ödevi olarak çözmeye çalıştım ColumbiaX: CSMM.101x Yapay Zeka ( AI) .

Başlıca sezgiden ve yukarıda tartışılanlardan birkaç sezgisel değerlendirme fonksiyonunun dışbükey kombinasyonunu (farklı sezgisel ağırlıkları denedim) uyguladım:

  1. monotonluk
  2. Boş Alan Mevcut

Benim durumumda, bilgisayar oynatıcı tamamen rastgele, ama yine de rakip ayarları varsaydım ve maksimum oyuncu olarak AI oynatıcı ajanını uyguladım.

Oyunu oynamak için 4x4 ızgaram var.

Gözlem:

İlk buluşsal fonksiyona veya ikinci buluşsal fonksiyona çok fazla ağırlık atarsam, her iki durumda da AI oyuncunun aldığı puanlar düşük olur. Sezgisel işlevlere olası birçok ağırlık ataması ile oynadım ve dışbükey bir kombinasyon aldım, ancak çok nadiren AI oyuncusu 2048'i puanlayabilir. Çoğu zaman ya 1024 veya 512'de durur.

Köşe buluşsal yöntemini de denedim, ancak bazı nedenlerden dolayı sonuçları daha da kötüleştiriyor, neden herhangi bir sezgi?

Ayrıca, arama derinliği kesimini 3'ten 5'e yükseltmeye çalıştım (budamanın budama ile bile izin verilen süreyi aştığından daha fazla artıramıyorum) ve bitişik karoların değerlerine bakan ve veren bir sezgisel daha ekledim birleştirilebiliyorsa daha fazla puan, ancak yine de 2048'i alamıyorum.

Minimax yerine Expectimax kullanmak daha iyi olacak, ancak yine de bu sorunu sadece minimax ile çözmek ve 2048 veya 4096 gibi yüksek puanlar almak istiyorum. Hiçbir şey eksik olup olmadığımdan emin değilim.

Aşağıdaki animasyon, AI aracısı tarafından bilgisayar oynatıcıyla oynanan oyunun son birkaç adımını göstermektedir:

resim açıklamasını buraya girin

Herhangi bir içgörü gerçekten çok yararlı olacaktır, şimdiden teşekkürler. (Bu yazı için blog yazımın bağlantısı: https://sandipanweb.wordpress.com/2017/03/06/using-minimax-with-alpha-beta-pruning-and-heuristic-evaluation-to-solve -2048-bilgisayar-oyunu- ve youtube videosu: https://www.youtube.com/watch?v=VnVFilfZ0r4 )

Aşağıdaki animasyon, AI oyuncu ajanının 2048 skoru alabileceği oyunun son birkaç adımını gösteriyor, bu sefer de sezgisel mutlak değer ekliyor:

resim açıklamasını buraya girin

Aşağıdaki şekiller , oyuncunun AI aracısı tarafından araştırılan ve yalnızca tek bir adım için bilgisayarı düşman olarak kabul eden oyun ağacını göstermektedir :

resim açıklamasını buraya girin resim açıklamasını buraya girin resim açıklamasını buraya girin resim açıklamasını buraya girin resim açıklamasını buraya girin resim açıklamasını buraya girin


9

Haskell'de 2048 çözücü yazdım, çünkü şu anda bu dili öğreniyorum.

Oyunun uygulanması benim gerçek oyundan biraz farklı, çünkü yeni bir karo her zaman bir '2' (% 90 2 ve% 10 4 yerine). Ve yeni karo rastgele değil, her zaman sol üstteki ilk kullanılabilir karo. Bu varyant Det 2048 olarak da bilinir .

Sonuç olarak, bu çözücü deterministiktir.

Boş fayansları tercih eden kapsamlı bir algoritma kullandım. 1-4 derinliği için oldukça hızlı bir şekilde çalışır, ancak 5 derinliğinde hareket başına yaklaşık 1 saniyede oldukça yavaşlar.

Çözme algoritmasını uygulayan kod aşağıdadır. Izgara, 16 uzunluklu bir Tamsayı dizisi olarak temsil edilir. Ve puanlama sadece boş karelerin sayılmasıyla yapılır.

bestMove :: Int -> [Int] -> Int
bestMove depth grid = maxTuple [ (gridValue depth (takeTurn x grid), x) | x <- [0..3], takeTurn x grid /= [] ]

gridValue :: Int -> [Int] -> Int
gridValue _ [] = -1
gridValue 0 grid = length $ filter (==0) grid  -- <= SCORING
gridValue depth grid = maxInList [ gridValue (depth-1) (takeTurn x grid) | x <- [0..3] ]

Sadeliği için oldukça başarılı olduğunu düşünüyorum. Boş bir ızgara ile başlayıp 5 derinliğinde çözerken ulaştığı sonuç:

Move 4006
[2,64,16,4]
[16,4096,128,512]
[2048,64,1024,16]
[2,4,16,2]

Game Over

Kaynak kodu burada bulunabilir: https://github.com/popovitsj/2048-haskell


Gerçek kurallarla genişletmeye çalışın. Haskell'in rastgele jeneratörünü öğrenmek için iyi bir meydan okuma!
Thomas Ahle

Haskell bunu yapmaya çalışırken çok sinirli oldum, ama muhtemelen ikinci kez deneyeceğim! Oyunun randomizasyon olmadan oldukça kolaylaştığını gördüm.
wvdz

Randomizasyon olmadan her zaman 16k veya 32k elde etmenin bir yolunu bulacağınızdan eminim. Haskell'deki randomizasyon o kadar da kötü değil, sadece `` tohumun '' etrafından geçmenin bir yoluna ihtiyacınız var. Açıkça veya Rastgele monad ile yapın.
Thomas Ahle

Algoritmayı, rastgele olmayan bir oyun için her zaman 16k /
32k'ye ulaşacak şekilde rafine etmek

Haklısın, düşündüğümden daha zor. Bu diziyi bulmayı başardım: [UP, LEFT, LEFT, UP, LEFT, DOWN, LEFT] her zaman oyunu kazanır, ancak 2048'in üzerine çıkmaz. (Yasal bir hareket olmaması durumunda, döngü algoritması sadece seçer bir sonraki saat yönünde)
Thomas Ahle

6

Bu algoritma oyunu kazanmak için uygun değildir, ancak performans ve gereken kod miktarı açısından oldukça uygundur:

  if(can move neither right, up or down)
    direction = left
  else
  {
    do
    {
      direction = random from (right, down, up)
    }
    while(can not move in "direction")
  }

10
random from (right, right, right, down, down, up) tüm hareketlerin eşit olasılığa sahip olmadığını söylerseniz daha iyi çalışır . :)
Daren

3
Aslında, oyunda tamamen yeniyseniz, temelde bu algoritmanın yaptığı gibi sadece 3 anahtarın kullanılmasına gerçekten yardımcı olur. Yani ilk bakışta göründüğü kadar kötü değil.
Basamak

5
Evet, oyunla ilgili kendi gözlemime dayanıyor. 4. yönü kullanmak zorunda kalana kadar, oyun herhangi bir gözlem olmadan pratik olarak kendini çözecektir. Bu "AI" herhangi bir bloğun kesin değerini kontrol etmeden 512/1024'e ulaşabilmelidir.
API-Beast

3
Uygun bir AI, ne pahasına olursa olsun sadece bir yöne gidebileceği bir duruma gelmekten kaçınmaya çalışacaktır.
API-Beast

3
Sadece 3 yön kullanmak aslında çok iyi bir stratejidir! Beni 2048'e kadar oynamıştı. Bunu, kalan 3 hamle arasında karar vermek için diğer stratejilerle birleştirirseniz, çok güçlü olabilir. Seçimi 3'e düşürmenin performans üzerinde büyük bir etkisi olduğunu belirtmiyoruz.
wvdz

4

Diğer cevapların birçoğu yapay zekayı olası gelecek, sezgisel tarama, öğrenme vb. Bunlar etkileyici ve muhtemelen doğru yol, ama başka bir fikre katkıda bulunmak istiyorum.

Oyunun iyi oyuncularının kullandığı strateji türünü modelleyin.

Örneğin:

13 14 15 16
12 11 10  9
 5  6  7  8
 4  3  2  1

Bir sonraki kareler değeri geçerli olandan büyük olana kadar kareleri yukarıda gösterilen sırayla okuyun. Bu, aynı değere sahip başka bir döşemeyi bu kareye birleştirmeye çalışmanın sorununu ortaya koyuyor.

Bu sorunu çözmek için, bırakılmayan veya daha kötüsü olmayan hareket etmenin 2 yolu vardır ve her iki olasılığı da incelemek derhal daha fazla problem ortaya çıkarabilir, bu her biri önce başka bir sorunun çözülmesini gerektiren bir bağımlılık listesi oluşturur. Sanırım bu zinciri veya bazı durumlarda bir sonraki hamleye karar verirken, özellikle de sıkışmışken, içten içe bağımlılık ağacım var.


Döşemenin komşu ile birleşmesi gerekiyor, ancak çok küçük: Başka bir komşuyu bununla birleştir.

Yolda daha büyük döşeme: Daha küçük bir çevre döşemesinin değerini artırın.

vb...


Bütün yaklaşım muhtemelen bundan daha karmaşık ancak çok daha karmaşık olmayacaktır. Puanlar, ağırlıklar, nöronlar ve derin olasılıklar arayışı içinde olmayan bu mekanik olabilir. Olasılıklar ağacının nadiren herhangi bir dallamaya ihtiyaç duyacak kadar büyük olması gerekir.


5
Sezgisel yöntemlerle yerel bir aramayı tanımlıyorsunuz. Bu sizi sıkıştıracak, bu yüzden sonraki hamleleri önceden planlamanız gerekiyor. Bu da sizi (karar vermek için) çözümlerin araştırılmasına ve puanlanmasına götürür. Yani bu gerçekten sunulan diğer çözümlerden farklı değil.
runDOSrun
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.