อัลกอริธึมที่เหมาะสมที่สุดสำหรับเกม 2048 คืออะไร?


1920

ฉันได้สะดุดเมื่อเร็ว ๆ นี้เมื่อเกม2048 คุณรวมไพ่ที่คล้ายกันโดยเลื่อนพวกมันไปในทิศทางใดก็ได้ในสี่ทิศทางเพื่อสร้างแผ่น "ใหญ่กว่า" หลังจากที่ย้ายแต่ละกระเบื้องใหม่จะปรากฏขึ้นในตำแหน่งที่ว่างเปล่าสุ่มที่มีค่าเป็นอย่างใดอย่างหนึ่งหรือ2 เกมสิ้นสุดลงเมื่อกล่องทั้งหมดจะเต็มไปและมีการเคลื่อนไหวที่สามารถผสานกระเบื้องไม่มีหรือคุณสร้างกระเบื้องที่มีค่าของ42048

หนึ่งฉันต้องปฏิบัติตามกลยุทธ์ที่กำหนดไว้อย่างดีเพื่อให้บรรลุเป้าหมาย ดังนั้นฉันคิดว่าจะเขียนโปรแกรมสำหรับมัน

อัลกอริทึมปัจจุบันของฉัน:

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
}

สิ่งที่ฉันกำลังทำอยู่คือ ณ จุดใดฉันจะพยายามผสานรวมไพ่กับค่า2และ4นั่นคือฉันพยายามมี2และ4เรียงต่อกันอย่างน้อยที่สุดเท่าที่จะทำได้ ถ้าฉันลองแบบนี้ไพ่อื่น ๆ ทั้งหมดจะถูกรวมเข้าด้วยกันโดยอัตโนมัติและกลยุทธ์ดูดี

แต่เมื่อฉันใช้อัลกอริทึมนี้จริง ๆ ฉันจะได้รับเพียงประมาณ 4000 คะแนนก่อนที่เกมจะยุติ คะแนนสูงสุด AFAIK มากกว่า 20,000 คะแนนเล็กน้อยซึ่งใหญ่กว่าคะแนนปัจจุบันของฉันเล็กน้อย มีอัลกอริทึมที่ดีกว่าข้างต้นหรือไม่?


84
สิ่งนี้อาจช่วยได้! ov3y.github.io/2048-AI
cegprakash

5
@ nitish712 โดยวิธีการอัลกอริทึมของคุณเป็นโลภเพราะคุณได้choose the move with large number of mergesอย่างรวดเร็วซึ่งนำไปสู่ท้องถิ่น
optima

21
@ 500-InternalServerError: ถ้าฉันจะนำ AI ไปใช้กับการตัดทรีเกมแบบ alpha-beta มันจะสมมติว่ามีการวางบล็อกใหม่ในทางตรงข้าม เป็นข้อสมมติฐานที่เลวร้ายที่สุด แต่อาจมีประโยชน์
ชาร์ลส์

6
สิ่งที่ทำให้ไขว้เขวสนุกเมื่อคุณไม่มีเวลาที่จะตั้งเป้าหมายให้ได้คะแนนสูง: พยายามทำแต้มให้ได้ต่ำที่สุด ในทางทฤษฎีมันสลับ 2s และ 4s
Mark Hurd

7
การสนทนาเกี่ยวกับความถูกต้องของคำถามนี้สามารถพบได้ใน meta: meta.stackexchange.com/questions/227266/…
Jeroen Vannevel

คำตอบ:


1266

ฉันพัฒนา AI 2048 โดยใช้การเพิ่มประสิทธิภาพexpectimaxแทนการค้นหาขั้นต่ำที่ใช้โดยอัลกอริทึมของ @ ovolve AI จะทำการเพิ่มประสิทธิภาพให้มากที่สุดในทุกการเคลื่อนไหวที่เป็นไปได้ตามด้วยความคาดหมายของการวางไข่ทั้งหมดที่เป็นไปได้ (ถ่วงน้ำหนักโดยความน่าจะเป็นของไพ่คือ 10% สำหรับ 4 และ 90% สำหรับ 2) เท่าที่ฉันทราบมันเป็นไปไม่ได้ที่จะตัดการเพิ่มประสิทธิภาพที่คาดหวังของแมกซ์แมกซ์ (ยกเว้นการลบสาขาที่ไม่น่าจะเกิดขึ้นอย่างมาก) ดังนั้นอัลกอริทึมที่ใช้นั้นเป็นการค้นหากำลังดุร้ายอย่างระมัดระวัง

ประสิทธิภาพ

AI ในการกำหนดค่าเริ่มต้น (ความลึกการค้นหาสูงสุด 8) จะใช้ทุกที่ตั้งแต่ 10ms ถึง 200ms เพื่อดำเนินการย้ายขึ้นอยู่กับความซับซ้อนของตำแหน่งกระดาน ในการทดสอบ AI จะได้รับอัตราการย้ายเฉลี่ย 5-10 ครั้งต่อวินาทีตลอดทั้งเกม ถ้าลึกของการค้นหาจะถูก จำกัด 6 ย้าย AI ที่สามารถดำเนินการ 20 + ย้ายต่อวินาทีซึ่งทำให้บางดูน่าสนใจ

ในการประเมินประสิทธิภาพคะแนนของ AI ฉันใช้ AI 100 ครั้ง (เชื่อมต่อกับเกมเบราว์เซอร์ผ่านรีโมทคอนโทรล) สำหรับแต่ละไทล์ต่อไปนี้เป็นสัดส่วนของเกมที่ไทล์นั้นได้รับอย่างน้อยหนึ่งครั้ง:

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

คะแนนต่ำสุดของการวิ่งทั้งหมดคือ 124024; คะแนนสูงสุดที่ทำได้คือ 794076 คะแนนเฉลี่ยคือ 387222 AI ไม่เคยพลาดที่จะได้รับไพ่ 2048 (ดังนั้นจึงไม่เคยแพ้เกมแม้แต่ครั้งเดียวใน 100 เกม); ในความเป็นจริงมันประสบความสำเร็จกระเบื้อง8192อย่างน้อยหนึ่งครั้งในทุกการวิ่ง!

นี่คือภาพหน้าจอของการทำงานที่ดีที่สุด:

32768 แผ่น, คะแนน 794076

เกมนี้ใช้เวลา 27830 การเคลื่อนไหวมากกว่า 96 นาทีหรือเฉลี่ย 4.8 การเคลื่อนไหวต่อวินาที

การดำเนินงาน

วิธีการของฉันเข้ารหัสบอร์ดทั้งหมด (16 รายการ) เป็นจำนวนเต็ม 64- บิตเดียว (ที่ไทล์เป็น nybbles, เช่นชิ้น 4 บิต) บนเครื่อง 64 บิตสิ่งนี้จะช่วยให้บอร์ดทั้งหมดผ่านไปในเครื่องเดียว

การดำเนินการบิตกะถูกใช้เพื่อแยกแถวและคอลัมน์แต่ละรายการ แถวหรือคอลัมน์เดียวคือปริมาณ 16 บิตดังนั้นตารางขนาด 65536 สามารถเข้ารหัสการแปลงที่ทำงานบนแถวหรือคอลัมน์เดียว ตัวอย่างเช่นการย้ายถูกนำไปใช้เป็นการค้นหา 4 ครั้งใน "ตารางเอฟเฟกต์การเคลื่อนย้าย" ล่วงหน้าซึ่งจะอธิบายว่าการย้ายแต่ละครั้งมีผลต่อแถวหรือคอลัมน์เดียวอย่างไร (เช่นตาราง "การเลื่อนไปทางขวา") มีรายการ "1122 -> 0023" แถว [2,2,4,4] กลายเป็นแถว [0,0,4,8] เมื่อย้ายไปทางขวา)

การให้คะแนนทำได้โดยใช้การค้นหาตาราง ตารางประกอบด้วยคะแนนฮิวริสติกที่คำนวณได้ในแถว / คอลัมน์ที่เป็นไปได้ทั้งหมดและคะแนนผลลัพธ์สำหรับกระดานเป็นเพียงผลรวมของค่าตารางในแต่ละแถวและคอลัมน์

การเป็นตัวแทนบอร์ดนี้พร้อมด้วยวิธีการค้นหาแบบตารางสำหรับการเคลื่อนไหวและการให้คะแนนช่วยให้ AI สามารถค้นหาสถานะเกมจำนวนมากได้ในช่วงเวลาสั้น ๆ (มากกว่า 10,000,000 สถานะเกมต่อวินาทีในแล็ปท็อปกลางปี ​​2554 ของฉัน)

การค้นหา expectimax นั้นจะถูกเขียนเป็นการค้นหาแบบเรียกซ้ำซึ่งสลับกันระหว่างขั้นตอน "การคาดหวัง" (การทดสอบตำแหน่งและค่าของการวางไข่ทั้งหมดที่เป็นไปได้และการให้คะแนนคะแนนที่เหมาะสมที่สุดโดยความน่าจะเป็น และเลือกหนึ่งคะแนนที่ดีที่สุด) การค้นหาต้นไม้สิ้นสุดลงเมื่อเห็นตำแหน่งที่เห็นก่อนหน้านี้ (โดยใช้ตารางการขนย้าย ) เมื่อถึงขีดจำกัดความลึกที่กำหนดไว้ล่วงหน้าหรือเมื่อถึงสถานะกระดานที่ไม่น่าเป็นไปได้สูง (เช่นเข้าถึงได้โดยใช้ไพ่ 6 "4" ในแถวจากตำแหน่งเริ่มต้น) ความลึกของการค้นหาทั่วไปคือ 4-8 การเคลื่อนไหว

heuristics

ฮิวริสติกหลายรายการใช้เพื่อกำหนดอัลกอริธึมการปรับให้เหมาะสมไปยังตำแหน่งที่ต้องการ การเลือกฮิวริสติกที่แม่นยำมีผลอย่างมากต่อประสิทธิภาพของอัลกอริทึม ฮิวริสติกแบบต่าง ๆ นั้นมีน้ำหนักและนำมารวมกันเป็นคะแนนตำแหน่งซึ่งกำหนดว่าตำแหน่งคณะกรรมการที่ "ดี" เป็นอย่างไร การค้นหาการปรับให้เหมาะสมนั้นจะมุ่งที่จะเพิ่มคะแนนเฉลี่ยของตำแหน่งกระดานที่เป็นไปได้ทั้งหมด คะแนนจริงตามที่แสดงโดยเกมไม่ได้ใช้ในการคำนวณคะแนนกระดานเนื่องจากมีน้ำหนักมากเกินไปในการสนับสนุนการรวมไพ่ (เมื่อการรวมล่าช้าอาจทำให้เกิดประโยชน์มาก)

ตอนแรกฉันใช้สองฮิวริสติกแบบง่าย ๆ เพื่อให้ "โบนัส" สำหรับช่องสี่เหลี่ยมที่เปิดกว้างและสำหรับค่าขนาดใหญ่บนขอบ ฮิวริสติกเหล่านี้ทำได้ค่อนข้างดีโดยมีจำนวน 16384 ครั้ง แต่ไม่ถึง 32768

Petr Morávek (@xificurk) ใช้ AI ของฉันและเพิ่มฮิวริสติกใหม่สองแบบ ฮิวริสติกแรกคือบทลงโทษสำหรับการมีแถวและคอลัมน์ที่ไม่ใช่แบบโมโนโทนิกซึ่งเพิ่มขึ้นเมื่ออันดับที่เพิ่มขึ้นทำให้มั่นใจได้ว่าแถวที่ไม่ใช่แบบโมโนโทนิกของตัวเลขขนาดเล็กจะไม่ส่งผลกระทบอย่างรุนแรงต่อคะแนน ฮิวริสติกที่สองนับจำนวนการผสานที่เป็นไปได้ (ค่าที่อยู่ติดกันเท่ากับ) นอกเหนือจากการเปิดพื้นที่ ฮิวริสติกทั้งสองนี้ทำหน้าที่ผลักดันอัลกอริธึมไปยังบอร์ดโมโนโทนิก (ซึ่งง่ายต่อการรวม) และต่อตำแหน่งของบอร์ดที่มีการรวมจำนวนมาก (สนับสนุนให้จัดตำแหน่งการรวมที่เป็นไปได้เพื่อผลที่ดีกว่า)

นอกจากนี้ Petr ยังปรับน้ำหนักตุ้มน้ำหนักให้เหมาะสมด้วยกลยุทธ์ "meta-optimization" (ใช้อัลกอริธึมที่เรียกว่าCMA-ES ) ซึ่งมีการปรับน้ำหนักด้วยตนเองเพื่อให้ได้คะแนนเฉลี่ยสูงสุด

ผลของการเปลี่ยนแปลงเหล่านี้มีความสำคัญอย่างยิ่ง อัลกอริธึมเริ่มต้นจากการเข้าถึงไทล์ 16384 ประมาณ 13% ของเวลาเพื่อให้ได้มากกว่า 90% ของเวลาและอัลกอริทึมเริ่มที่จะบรรลุ 32768 ในช่วงเวลา 1/3 ของเวลา (ในขณะที่ฮิวริสติกแบบเก่าไม่เคยสร้างไพ่ 32768) .

ฉันเชื่อว่ายังมีช่องว่างสำหรับการปรับปรุงฮิวริสติก อัลกอริทึมนี้ยังไม่ "ดีที่สุด" อย่างแน่นอน แต่ฉันรู้สึกว่ามันใกล้เข้ามาแล้ว


การที่ AI ได้บรรลุถึงไพ่ 32768 ในหนึ่งในสามของเกมนั้นนับเป็นเหตุการณ์ครั้งสำคัญ ฉันจะแปลกใจที่ได้ยินว่าผู้เล่นคนใดประสบความสำเร็จในเกมอย่างเป็นทางการ 32768 (เช่นโดยไม่ต้องใช้เครื่องมือเช่น savestates หรือเลิกทำ) ฉันคิดว่าแผ่นกระเบื้อง 65536 ใกล้ถึงแล้ว!

คุณสามารถลอง AI ด้วยตัวคุณเอง รหัสที่มีอยู่ในhttps://github.com/nneonneo/2048-ai


12
@RobL: 2 ปรากฏ 90% ของเวลา; 4 ปรากฏ 10% ของเวลา มันอยู่ในรหัสที่มาvar value = Math.random() < 0.9 ? 2 : 4; :
nneonneo

35
กำลังย้ายไปยัง Cuda เพื่อให้ GPU ทำงานได้ดียิ่งขึ้น!
nimsson

25
@nneonneo ฉันเปลี่ยนรหัสของคุณด้วย emscripten เป็น javascript และใช้งานได้ดีในเบราว์เซอร์ตอนนี้! เด็ดที่จะดูโดยไม่จำเป็นต้องรวบรวมและทุกอย่าง ... ใน Firefox ประสิทธิภาพค่อนข้างดี ...
reverse_engineer

7
ข้อ จำกัด ทางทฤษฎีในตาราง 4x4 จริง ๆ แล้วคือ 131072 ไม่ใช่ 65536 อย่างไรก็ตามต้องมีการรับ 4 ในช่วงเวลาที่เหมาะสม (เช่นกระดานทั้งหมดที่เต็มไปด้วย 4 .. 65536 แต่ละครั้ง - 15 เขตข้อมูลครอบครอง) และคณะกรรมการจะต้องตั้งค่าที่ ช่วงเวลาเพื่อให้คุณสามารถรวมกัน
Bodo Thiesen

5
@nneonneo คุณอาจต้องการตรวจสอบ AI ของเราซึ่งดูดียิ่งขึ้นไปถึง 32k ใน 60% ของเกม: github.com/aszczepanski/2048
cauchy

1253

ฉันเป็นผู้เขียนโปรแกรม AI ที่คนอื่นพูดถึงในหัวข้อนี้ คุณสามารถดู AI ในการดำเนินการหรืออ่านแหล่งที่มาแหล่งที่มา

ขณะนี้โปรแกรมประสบความสำเร็จในการชนะ 90% ที่ทำงานบนจาวาสคริปต์ในเบราว์เซอร์บนแล็ปท็อปของฉันโดยใช้เวลาคิดประมาณ 100 มิลลิวินาทีต่อการย้ายดังนั้นในขณะที่มันไม่ได้สมบูรณ์แบบ (ยัง!)

นับตั้งแต่เกมที่เป็นพื้นที่ต่อเนื่องของรัฐข้อมูลที่สมบูรณ์แบบเกมเทิร์นเบสเช่นหมากรุกหมากฮอสและผมใช้วิธีการเดียวกับที่ได้รับการพิสูจน์แล้วว่าการทำงานในเกมเหล่านั้นคือMinimax ค้นหาที่มีการตัดแต่งกิ่งอัลฟาเบต้า เนื่องจากมีข้อมูลจำนวนมากเกี่ยวกับอัลกอริทึมนั้นอยู่ที่นั่นฉันจะพูดถึงสองหลักการวิเคราะห์พฤติกรรมที่ฉันใช้ในฟังก์ชั่นการประเมินผลแบบคงที่และทำให้เกิดสัญชาติญาณหลายอย่างที่คนอื่น ๆ แสดงออกมาที่นี่

monotonicity

ฮิวริสติกแบบนี้พยายามที่จะทำให้แน่ใจว่าค่าของกระเบื้องนั้นเพิ่มขึ้นหรือลดลงตามทิศทางซ้าย / ขวาและขึ้น / ลง ฮิวริสติกนี้เพียงอย่างเดียวที่จับปรีชาที่คนอื่นพูดถึงกันว่าไพ่ที่มีค่าสูงควรรวมกันเป็นมุม โดยทั่วไปแล้วจะป้องกันไม่ให้แผ่นกระเบื้องที่มีมูลค่าน้อยลงไปจากการถูกกำพร้าและจะทำให้คณะกรรมการจัดระเบียบอย่างมากโดยมีแผ่นกระเบื้องขนาดเล็กเรียงซ้อนกัน

นี่คือภาพหน้าจอของกริดโมโนโซนิกที่สมบูรณ์แบบ ฉันได้รับสิ่งนี้จากการรันอัลกอริทึมด้วยฟังก์ชัน eval ที่ตั้งไว้เพื่อไม่สนใจฮิวริสติกอื่น ๆ และพิจารณาเพียงแค่เสียงเดียว

บอร์ดเดี่ยวที่สมบูรณ์แบบในปี 2048

เรียบเนียน

ฮิวริสติกด้านบนเพียงอย่างเดียวมีแนวโน้มที่จะสร้างโครงสร้างที่แผ่นกระเบื้องที่อยู่ติดกันมีค่าลดลง แต่แน่นอนเพื่อที่จะผสานกระเบื้องที่อยู่ติดกันจะต้องมีค่าเดียวกัน ดังนั้นฮิวริสติกที่ลื่นไหลเพียงแค่วัดความแตกต่างของมูลค่าระหว่างกระเบื้องใกล้เคียงพยายามลดจำนวนนี้ลง

ผู้แสดงความคิดเห็นใน Hacker News ให้ความเป็นระเบียบที่น่าสนใจของแนวคิดนี้ในแง่ของทฤษฎีกราฟ

นี่คือภาพหน้าจอของกริดที่ราบรื่นอย่างสมบูรณ์แบบซึ่งได้รับความอนุเคราะห์จากส้อมล้อเลียนที่ยอดเยี่ยมนี้

บอร์ดที่เรียบเนียนอย่างสมบูรณ์แบบ 2048

กระเบื้องฟรี

และในที่สุดก็มีบทลงโทษสำหรับการมีไทล์ฟรีน้อยเกินไปเนื่องจากตัวเลือกสามารถหมดได้อย่างรวดเร็วเมื่อบอร์ดเกมคับแคบเกินไป

และนั่นมัน! การค้นหาพื้นที่เกมในขณะที่การเพิ่มประสิทธิภาพของเกณฑ์เหล่านี้จะให้ประสิทธิภาพที่ดีอย่างน่าทึ่ง ข้อดีอย่างหนึ่งของการใช้วิธีการทั่วไปเช่นนี้แทนที่จะใช้กลยุทธ์การย้ายแบบเข้ารหัสอย่างชัดเจนคืออัลกอริทึมสามารถค้นหาวิธีแก้ปัญหาที่น่าสนใจและไม่คาดคิดได้บ่อยครั้ง หากคุณดูมันทำงานอยู่บ่อยครั้งมันจะทำให้เกิดการเคลื่อนไหวที่น่าแปลกใจ แต่ก็มีประสิทธิภาพอย่างฉับพลันเช่นการสลับผนังหรือมุมใดมุมหนึ่งที่มันสร้างขึ้น

แก้ไข:

นี่เป็นการสาธิตพลังของวิธีการนี้ ฉันถอดค่ากระเบื้องออก (ดังนั้นมันจะดำเนินต่อไปจนถึง 2048) และนี่คือผลลัพธ์ที่ดีที่สุดหลังจากการทดลองแปดครั้ง

4096

ใช่นั่นคือ 4096 ข้างๆ 2048 =) นั่นหมายความว่ามันสามารถทำกระเบื้อง 2048 ที่เข้าใจยากสามครั้งบนกระดานเดียวกัน


89
คุณสามารถใช้คอมพิวเตอร์วางไทล์ '2' และ '4' เป็น 'คู่ต่อสู้'
Wei Yen

29
@ เหวียนแน่นอน แต่เรื่องมันเป็นปัญหา minmax ไม่ซื่อสัตย์กับตรรกะของเกมเพราะคอมพิวเตอร์วางไพ่แบบสุ่มกับความน่าจะเป็นบางอย่างแทนที่จะตั้งใจทำคะแนนให้น้อยที่สุด
คู

57
แม้ว่า AI จะสุ่มวางกระเบื้องเป้าหมายก็ไม่แพ้ การรับเคราะห์ร้ายเป็นสิ่งเดียวกับที่คู่ต่อสู้เลือกท่าที่แย่ที่สุดสำหรับคุณ ส่วน "ขั้นต่ำ" หมายความว่าคุณพยายามเล่นอย่างระมัดระวังเพื่อให้ไม่มีการเคลื่อนไหวที่น่ากลัวที่คุณอาจได้รับโชคร้าย
FryGuy

196
ฉันมีความคิดที่จะสร้างทางแยกของ 2048 โดยที่คอมพิวเตอร์แทนที่จะวาง 2s และ 4s สุ่มใช้ AI ของคุณเพื่อกำหนดตำแหน่งที่จะวางค่า ผลที่ได้: เป็นไปไม่ได้ที่แท้จริง สามารถลองได้ที่นี่: sztupy.github.io/2048-Hard
SztupY

30
@ SztupY ว้าวนี่มันชั่วร้าย เตือนฉันถึงqntm.org/hatetris Hatetris ซึ่งพยายามวางส่วนที่จะปรับปรุงสถานการณ์ของคุณให้น้อยที่สุด
Patashu

145

ฉันเริ่มให้ความสนใจในแนวคิดของ AI สำหรับเกมนี้ที่ไม่มีความรู้แจ้งแบบตายตัว (เช่นไม่มีฮิวริสติกฟังก์ชั่นให้คะแนน ฯลฯ ) AI ควร"รู้"เฉพาะกฎของเกมและ"เข้าใจ"การเล่นเกม ตรงกันข้ามกับ AIs ส่วนใหญ่ (เหมือนกับที่อยู่ในเธรดนี้) ซึ่งการเล่นเกมนั้นมีกำลังดุร้ายนำโดยฟังก์ชันการให้คะแนนซึ่งแสดงถึงความเข้าใจของมนุษย์ของเกม

อัลกอริทึม AI

ฉันพบอัลกอริทึมการเล่นที่เรียบง่ายและดีอย่างน่าประหลาดใจ: ในการพิจารณาการเคลื่อนที่ครั้งต่อไปสำหรับบอร์ดที่กำหนดไว้ AI จะเล่นเกมในหน่วยความจำโดยใช้การเคลื่อนไหวแบบสุ่มจนกว่าเกมจะจบ ทำได้หลายครั้งในขณะที่ติดตามคะแนนของเกมสุดท้าย จากนั้นคะแนนเฉลี่ยโดยเฉลี่ยต่อการย้ายเริ่มต้นจะถูกคำนวณ การเริ่มต้นที่มีคะแนนเฉลี่ยสูงสุดจะถูกเลือกเป็นการย้ายครั้งต่อไป

ด้วยการวิ่งเพียง 100 ครั้ง (เช่นในเกมหน่วยความจำ) ต่อการเคลื่อนที่ AI จะได้รับไทล์ 2048 ครั้ง 80% ของไทม์สและ 4096 ไทล์ 50% ของจำนวนครั้ง การใช้การวิ่ง 10,000 ครั้งจะได้รับไทล์ 2048 100%, 70% สำหรับไทล์ 4096 และประมาณ 1% สำหรับไทล์ 8192

เห็นมันในการกระทำ

คะแนนที่ทำได้ดีที่สุดแสดงไว้ที่นี่:

คะแนนที่ดีที่สุด

ข้อเท็จจริงที่น่าสนใจเกี่ยวกับอัลกอริธึมนี้คือในขณะที่เกมแบบสุ่มเล่นไม่น่าแปลกใจเลยทีเดียวการเลือกการย้ายที่ดีที่สุด (หรือแย่ที่สุด) นำไปสู่การเล่นเกมที่ดีมาก: เกม AI ทั่วไปสามารถเข้าถึง 70000 คะแนน เกมเล่นแบบสุ่มในหน่วยความจำจากตำแหน่งใด ๆ ให้คะแนนเฉลี่ย 340 คะแนนในการย้ายเพิ่มเติม 40 ครั้งก่อนตาย (คุณสามารถเห็นสิ่งนี้ได้ด้วยตัวคุณเองโดยเรียกใช้ AI และเปิดคอนโซลการดีบัก)

กราฟนี้แสดงให้เห็นถึงจุดนี้: เส้นสีน้ำเงินแสดงคะแนนคณะกรรมการหลังจากการย้ายแต่ละครั้ง เส้นสีแดงแสดงคะแนนเกมจบแบบสุ่มที่ดีที่สุดของอัลกอริทึมจากตำแหน่งนั้น ในสาระสำคัญค่าสีแดงคือ "ดึง" ค่าสีฟ้าขึ้นไปทางพวกเขาในขณะที่พวกเขาเป็นเดาที่ดีที่สุดของอัลกอริทึม เป็นที่น่าสนใจที่จะเห็นเส้นสีแดงอยู่เหนือเส้นสีฟ้าเล็กน้อยในแต่ละจุด แต่เส้นสีน้ำเงินยังคงเพิ่มขึ้นเรื่อย ๆ

กราฟคะแนน

ฉันคิดว่ามันค่อนข้างน่าแปลกใจที่อัลกอริทึมไม่จำเป็นต้องมองเห็นการเล่นเกมที่ดีเพื่อเลือกการเคลื่อนไหวที่สร้างขึ้น

การค้นหาในภายหลังฉันพบว่าอัลกอริทึมนี้อาจจัดเป็นPure Monte Carlo Tree Searchอัลกอริทึม

การใช้งานและลิงค์

ครั้งแรกที่ฉันสร้างขึ้นเป็นรุ่น JavaScript ซึ่งสามารถมองเห็นได้ในการดำเนินการที่นี่ รุ่นนี้สามารถวิ่งได้ 100 รอบในเวลาที่เหมาะสม เปิดคอนโซลสำหรับข้อมูลเพิ่มเติม (ที่มา )

ต่อมาเพื่อที่จะเล่นต่อไปอีกฉันใช้ @nneonneo โครงสร้างพื้นฐานที่ได้รับการปรับปรุงให้ดีที่สุดและใช้เวอร์ชันของฉันใน C ++ รุ่นนี้อนุญาตให้ทำงานได้มากถึง 100,000 ครั้งต่อการเคลื่อนไหวและแม้แต่ 100,000 ครั้งหากคุณมีความอดทน ให้คำแนะนำอาคาร มันทำงานในคอนโซลและยังมีการควบคุมระยะไกลในการเล่นเวอร์ชั่นเว็บ (ที่มา )

ผล

น่าแปลกที่การเพิ่มจำนวนการวิ่งนั้นไม่ได้เพิ่มการเล่นเกมอย่างมาก ดูเหมือนว่าจะมีข้อ จำกัด สำหรับกลยุทธ์นี้ที่ประมาณ 80000 จุดด้วยกระเบื้อง 4096 และขนาดเล็กกว่าทั้งหมดใกล้กับกระเบื้อง 8192 ที่ได้รับ การเพิ่มจำนวนการวิ่งจาก 100 เป็น 100000 จะเพิ่มโอกาสที่จะได้คะแนน จำกัด นี้ (จาก 5% เป็น 40%) แต่ไม่เจาะเข้าไป

การวิ่ง 10,000 วิ่งด้วยการเพิ่มขึ้นชั่วคราวเป็น 1000000 ใกล้ตำแหน่งวิกฤติที่มีการจัดการเพื่อทำลายสิ่งกีดขวางนี้น้อยกว่า 1% ของจำนวนครั้งที่ได้คะแนนสูงสุด 129892 และ 8192 ไทล์

ปรับปรุง

หลังจากใช้อัลกอริทึมนี้ฉันพยายามปรับปรุงหลายอย่างรวมถึงการใช้คะแนนขั้นต่ำหรือคะแนนสูงสุดหรือการรวมกันของ min, max และ avg ฉันพยายามใช้ความลึก: แทนที่จะลองใช้ K ต่อการเคลื่อนไหวฉันลองใช้ K การเคลื่อนไหวต่อรายการเคลื่อนไหวมีความยาวที่กำหนด (ตัวอย่างเช่น "ขึ้น, ขึ้น, ซ้าย") และเลือกการย้ายครั้งแรกของรายการคะแนนการย้ายที่ดีที่สุด

ต่อมาฉันได้นำแผนภูมิการให้คะแนนซึ่งคำนึงถึงความน่าจะเป็นแบบมีเงื่อนไขในการเล่นย้ายหลังจากรายการย้ายที่กำหนด

อย่างไรก็ตามไม่มีแนวคิดใดที่แสดงให้เห็นถึงความได้เปรียบใด ๆ จากแนวคิดแรกที่เรียบง่าย ฉันทิ้งรหัสไว้สำหรับแนวคิดเหล่านี้ที่ใส่ความคิดเห็นไว้ในรหัส C ++

ฉันเพิ่มกลไก "การค้นหาลึก" ที่เพิ่มจำนวนการรันชั่วคราวเป็น 1000000 เมื่อการวิ่งใด ๆ ที่จัดการเพื่อเข้าถึงไทล์สูงสุดต่อไปโดยไม่ตั้งใจ นี่เป็นการปรับปรุงเวลา

ฉันสนใจที่จะฟังว่าใครมีแนวคิดการปรับปรุงอื่น ๆ ที่รักษาความเป็นอิสระของโดเมน AI

2048 ตัวแปรและโคลน

เพื่อความสนุกฉันได้ติดตั้ง AI เป็นบุ๊คมาร์คและเชื่อมต่อกับส่วนควบคุมของเกม สิ่งนี้ทำให้ AI สามารถทำงานกับเกมดั้งเดิมและเกมที่หลากหลายได้

สิ่งนี้เป็นไปได้เนื่องจากลักษณะที่เป็นอิสระจากโดเมนของ AI ตัวแปรบางตัวมีความชัดเจนเช่นโคลนหกเหลี่ยม


7
+1 ในฐานะนักเรียน AI ฉันพบว่าสิ่งนี้น่าสนใจจริงๆ จะดูดีกว่านี้ในเวลาว่าง
Isaac

4
มันอัศจรรย์มาก! ฉันใช้เวลาหลายชั่วโมงในการปรับน้ำหนักให้เหมาะสมสำหรับฟังก์ชันฮิวริสติกที่ดีสำหรับ expectimax และฉันใช้สิ่งนี้ใน 3 นาทีและสิ่งนี้ทำให้มันพังอย่างสมบูรณ์
เบรนแดน Annable

8
ใช้ประโยชน์จากการจำลอง Monte Carlo ได้ดี
nneonneo

5
การดูการเล่นนี้กำลังเรียกร้องให้รู้แจ้ง การทำแบบนี้เป็นการวิเคราะห์พฤติกรรมทั้งหมด แต่ก็ใช้งานได้ ขอแสดงความยินดีด้วย!
Stéphane Gourichon

4
โดยไกลทางออกที่น่าสนใจที่สุดที่นี่
shebaw

126

แก้ไข:นี่คืออัลกอริธึมไร้เดียงสาการสร้างแบบจำลองกระบวนการคิดอย่างมีสติของมนุษย์และได้รับผลลัพธ์ที่อ่อนแอมากเมื่อเทียบกับ AI ที่ค้นหาความเป็นไปได้ทั้งหมดเนื่องจากดูเพียงหนึ่งไทล์ข้างหน้า มันถูกส่งมาในช่วงเวลาตอบสนอง

ฉันได้ปรับปรุงอัลกอริทึมและเอาชนะเกม! มันอาจล้มเหลวเนื่องจากโชคร้ายง่าย ๆ ใกล้ถึงจุดสิ้นสุด (คุณถูกบังคับให้ย้ายลงซึ่งคุณไม่ควรทำและไทล์ปรากฏขึ้นในตำแหน่งที่สูงที่สุดของคุณเพียงแค่พยายามทำให้แถวบนสุดเต็ม ทำลายรูปแบบ) แต่โดยพื้นฐานแล้วคุณจะต้องมีชิ้นส่วนคงที่และชิ้นส่วนมือถือเพื่อเล่น นี่คือวัตถุประสงค์ของคุณ:

พร้อมที่จะเสร็จสิ้น

นี่คือรุ่นที่ฉันเลือกตามค่าเริ่มต้น

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

มุมที่เลือกนั้นตามอำเภอใจคุณไม่เคยกดปุ่มใดปุ่มหนึ่ง (เป็นการย้ายที่ต้องห้าม) และถ้าคุณทำเช่นนั้นคุณจะกดตรงกันข้ามอีกครั้งแล้วลองแก้ไข สำหรับไทล์ในอนาคตโมเดลจะคาดหวังให้ไทล์สุ่มถัดไปเป็น 2 และปรากฏที่ด้านตรงข้ามกับโมเดลปัจจุบัน (ในขณะที่แถวแรกไม่สมบูรณ์ที่มุมขวาล่างเมื่อแถวแรกเสร็จสมบูรณ์ที่ด้านล่างซ้าย มุม).

นี่คืออัลกอริทึม ประมาณ 80% ชนะ (ดูเหมือนว่าจะเป็นไปได้เสมอที่จะชนะด้วยเทคนิค AI "มืออาชีพ" มากขึ้นฉันไม่แน่ใจเกี่ยวกับเรื่องนี้ แต่อย่างใด)

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)
  }

ตัวชี้ไม่กี่ขั้นตอนที่หายไป ที่นี่:การเปลี่ยนแปลงรูปแบบ

โมเดลมีการเปลี่ยนแปลงเนื่องจากโชคดีที่ได้อยู่ใกล้กับโมเดลที่คาดหวัง โมเดลที่ AI พยายามทำคือ

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

และโซ่ที่ไปถึงที่นั่นได้กลายเป็น:

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

Oแทนพื้นที่ต้องห้าม ...

ดังนั้นมันจะกดขวาจากนั้นขวาอีกครั้งจากนั้น (ขวาหรือด้านบนขึ้นอยู่กับตำแหน่งที่สร้าง 4) จากนั้นจะดำเนินการต่อให้สมบูรณ์จนกว่าจะได้รับ:

โซ่เสร็จแล้ว

ดังนั้นตอนนี้รูปแบบและโซ่กลับไปที่:

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

ตัวชี้ที่สองมีโชคไม่ดีและจุดหลักถูกใช้แล้ว เป็นไปได้ว่ามันจะล้มเหลว แต่ก็ยังสามารถทำได้:

ป้อนคำอธิบายภาพที่นี่

นี่คือรูปแบบและโซ่:

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

เมื่อมันไปถึง 128 มันจะได้รับทั้งแถวอีกครั้ง:

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

execute move with best scoreคุณจะประเมินคะแนนที่ดีที่สุดจากรัฐต่อไปได้อย่างไร
Khaled.K

ฮิวริสติกมีการกำหนดไว้ในตัวevaluateResultคุณโดยทั่วไปแล้วพยายามที่จะเข้าใกล้สถานการณ์ที่ดีที่สุด
Daren

@Daren ฉันกำลังรอรายละเอียดเฉพาะของคุณ
ashu

@ashu ฉันกำลังทำงานอยู่สถานการณ์ที่ไม่คาดคิดทำให้ฉันไม่มีเวลาทำมันให้เสร็จ ในขณะเดียวกันฉันได้ปรับปรุงอัลกอริทึมและตอนนี้แก้ 75% ของเวลา
Daren

13
สิ่งที่ฉันชอบเกี่ยวกับกลยุทธ์นี้คือฉันสามารถใช้มันได้เมื่อเล่นเกมด้วยตนเองมันทำให้ฉันมีคะแนนมากถึง 37k
Cephalopod

94

ฉันคัดลอกเนื้อหาโพสต์บนบล็อกของฉันที่นี่


โซลูชันที่ฉันเสนอนั้นง่ายมากและใช้งานง่าย แม้ว่าจะได้คะแนนถึง 1,3,340 คะแนนมาตรฐานหลายประการของการแสดงอัลกอริทึมจะถูกนำเสนอ

คะแนน

ขั้นตอนวิธี

อัลกอริทึมการให้คะแนน Heuristic

สมมติฐานของอัลกอริทึมของฉันค่อนข้างง่าย: หากคุณต้องการได้คะแนนสูงกว่าบอร์ดจะต้องเป็นระเบียบที่สุด โดยเฉพาะอย่างยิ่งการตั้งค่าที่เหมาะสมจะได้รับจากลำดับเชิงเส้นและลดลงแบบเรียงลำดับของค่ากระเบื้อง สัญชาตญาณนี้จะทำให้คุณมีขอบเขตสูงสุดสำหรับค่าไทล์sโดยที่ n คือจำนวนไทล์บนกระดาน

(มีความเป็นไปได้ที่จะเข้าถึงไทล์ 131072 หากมีการสร้างแบบสุ่ม 4 ไทล์แทนที่จะเป็น 2 ไทล์เมื่อจำเป็น)

สองวิธีที่เป็นไปได้ในการจัดระเบียบบอร์ดแสดงในภาพต่อไปนี้:

ป้อนคำอธิบายรูปภาพที่นี่

ในการบังคับใช้การเรียงไพ่ในลำดับที่ลดลงแบบ monotonic คะแนน si คำนวณเป็นผลรวมของค่าเชิงเส้นบนกระดานคูณด้วยค่าของลำดับเรขาคณิตด้วยอัตราส่วนทั่วไป r <1

s

s

สามารถประเมินเส้นทางแบบเส้นตรงหลายเส้นทางได้ในครั้งเดียวคะแนนสุดท้ายจะเป็นคะแนนสูงสุดของเส้นทางใด ๆ

กฎการตัดสินใจ

กฎการตัดสินใจที่นำมาใช้นั้นไม่ได้ค่อนข้างฉลาดรหัสใน Python นำเสนอที่นี่:

@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 หรือ Expectiminimax จะช่วยปรับปรุงอัลกอริทึมอย่างแน่นอน เห็นได้ชัดว่ากฎการตัดสินใจที่ซับซ้อนมากขึ้นจะทำให้อัลกอริทึมช้าลงและต้องใช้เวลาสักครู่ในการดำเนินการฉันจะลองใช้งาน minimax ในอนาคตอันใกล้ (คอยติดตาม)

เกณฑ์มาตรฐาน

  • T1 - 121 การทดสอบ - 8 เส้นทางที่แตกต่าง - r = 0.125
  • T2 - 122 การทดสอบ - 8 เส้นทางที่แตกต่าง - r = 0.25
  • T3 - 132 การทดสอบ - 8 เส้นทางที่แตกต่าง - r = 0.5
  • T4 - 211 การทดสอบ - เส้นทางที่แตกต่าง 2 - r = 0.125
  • T5 - 274 การทดสอบ - เส้นทางที่แตกต่าง 2 แบบ - r = 0.25
  • T6 - การทดสอบ 211 ครั้ง - เส้นทางที่ต่างกัน 2 ทาง - r = 0.5

ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่

ในกรณีของ T2 การทดสอบสี่ครั้งในสิบครั้งจะสร้างไทล์ 4096 โดยมีคะแนนเฉลี่ยเท่ากับ s 42000

รหัส

รหัสสามารถพบได้ใน GiHub ที่ลิงค์ต่อไปนี้: https://github.com/Nicola17/term2048-AI มันขึ้นอยู่กับterm2048และมันเขียนใน Python ฉันจะใช้เวอร์ชันที่มีประสิทธิภาพมากขึ้นใน C ++ โดยเร็วที่สุด


ไม่เลวภาพประกอบของคุณทำให้ฉันมีความคิดในการใช้เวกเตอร์ผสานเพื่อประเมิน
Khaled.K

สวัสดี. คุณแน่ใจหรือไม่ว่าคำแนะนำที่ให้ไว้ในหน้า GitHub นำไปใช้กับโครงการของคุณ? ฉันต้องการทดลองใช้ แต่สิ่งเหล่านั้นดูเหมือนจะเป็นคำแนะนำสำหรับเกมที่เล่นได้ดั้งเดิมไม่ใช่ AI autorun คุณสามารถอัปเดตสิ่งเหล่านั้นได้ไหม ขอบคุณ
JD Gamboa

41

ความพยายามของฉันใช้ expectimax เหมือนกับโซลูชันอื่น ๆ ด้านบน แต่ไม่มี bitboards วิธีการแก้ปัญหาของ Nneonneo สามารถตรวจสอบ 10millions ของการเคลื่อนไหวซึ่งมีประมาณความลึก 4 มี 6 กระเบื้องซ้ายและ 4 ย้ายเป็นไปได้ (2 * 6 * 4) 4 ในกรณีของฉันความลึกนี้ใช้เวลานานเกินไปในการสำรวจฉันปรับความลึกของการค้นหาimaximaxตามจำนวนไทล์ฟรีที่เหลืออยู่:

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

คะแนนของบอร์ดคำนวณจากผลรวมถ่วงน้ำหนักของสี่เหลี่ยมของจำนวนไทล์ฟรีและผลคูณของจุดในตาราง 2D ด้วยวิธีนี้

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

ซึ่งบังคับให้จัดเรียงไพ่เรียงจากมากไปน้อยในงูเรียงจากซ้ายบนกระเบื้อง

รหัสด้านล่างหรือบนGitHub :

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
ไม่แน่ใจว่าทำไมถึงไม่มี upvotes มากกว่านี้ มันมีประสิทธิภาพจริงๆสำหรับความเรียบง่าย
David Greydanus

ขอขอบคุณคำตอบที่ล่าช้าและมันทำงานได้ไม่ดีนัก (เกือบตลอดเวลาใน [1024, 8192]) ฟังก์ชั่นค่าใช้จ่าย / สถิติจำเป็นต้องใช้งานมากกว่านี้
caub

คุณน้ำหนักช่องว่างว่างเปล่าอย่างไร
David Greydanus

1
เป็นเพียงcost=1x(number of empty tiles)²+1xdotproduct(snakeWeights,grid)และเราพยายามที่จะเพิ่มค่าใช้จ่ายนี้สูงสุด
caub

ขอบคุณ @ Robusto ฉันควรปรับปรุงรหัสบางวันมันสามารถทำให้ง่ายขึ้น
caub

38

ฉันเป็นผู้เขียนคอนโทรลเลอร์ 2048 ที่ทำคะแนนได้ดีกว่าโปรแกรมอื่น ๆ ที่กล่าวถึงในหัวข้อนี้ การดำเนินงานที่มีประสิทธิภาพของการควบคุมที่มีอยู่ในGitHub ในrepo ที่แยกต่างหากนอกจากนี้ยังมีรหัสที่ใช้สำหรับการฝึกอบรมฟังก์ชั่นการประเมินสถานะของตัวควบคุม วิธีการฝึกอบรมที่มีการอธิบายไว้ในกระดาษ

คอนโทรลเลอร์ใช้การค้นหาแบบคาดหวังโดยใช้ฟังก์ชันการประเมินสถานะที่เรียนรู้ตั้งแต่เริ่มต้น (ไม่มีความเชี่ยวชาญของมนุษย์ 2048) โดยการเรียนรู้ที่แตกต่างชั่วคราว (เทคนิคการเรียนรู้เสริม) ฟังก์ชัน state-value ใช้เครือข่าย n-tupleซึ่งโดยทั่วไปแล้วจะเป็นฟังก์ชันเชิงเส้นแบบถ่วงน้ำหนักของรูปแบบที่สังเกตเห็นบนกระดาน มันเกี่ยวข้องกับน้ำหนักมากกว่า1 พันล้านทั้งหมด

ประสิทธิภาพ

ที่ 1 ย้าย / s: 609104 (เฉลี่ย 100 เกม)

เมื่อเคลื่อนไหว 10 ครั้ง / วินาที: 589355 (เฉลี่ย 300 เกม)

ที่ระดับ 3 ชั้น (แคลิฟอร์เนียได้ 1,500 ครั้ง / s): 511759 (1,000 เกมเฉลี่ย)

สถิติไทล์สำหรับ 10 การเคลื่อนไหว / วินาทีมีดังนี้:

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

(บรรทัดสุดท้ายหมายถึงการมีกระเบื้องที่กำหนดในเวลาเดียวกันบนกระดาน)

สำหรับ 3 ชั้น:

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

อย่างไรก็ตามฉันไม่เคยสังเกตว่ามันได้รับกระเบื้อง 65536


4
ผลลัพธ์ที่น่าประทับใจ อย่างไรก็ตามคุณสามารถอัปเดตคำตอบเพื่ออธิบายได้ (โดยสังเขปในแง่ง่าย ... ฉันแน่ใจว่ารายละเอียดทั้งหมดยาวเกินกว่าจะโพสต์ที่นี่) โปรแกรมของคุณประสบความสำเร็จได้อย่างไร ในคำอธิบายคร่าวๆของอัลกอริทึมการเรียนรู้ทำงานอย่างไร
Cedric Mamo

27

ฉันคิดว่าฉันพบอัลกอริทึมที่ใช้งานได้ดีเพราะฉันมักจะทำคะแนนได้มากกว่า 10,000 คะแนนส่วนบุคคลของฉันดีที่สุดคือประมาณ 16000 คำตอบของฉันไม่ได้มุ่งหวังที่จะรักษาหมายเลขที่ใหญ่ที่สุดไว้ที่มุมหนึ่ง

โปรดดูรหัสด้านล่าง:

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 เกมทดสอบกับกลยุทธ์วงจรเล็ก ๆ น้อย ๆ "ขึ้น, ขวา, ขึ้น, ซ้าย, ... " (และลงถ้ามันต้อง) กลยุทธ์วงจรเสร็จ "คะแนนเฉลี่ยกระเบื้อง" ของในขณะนี้มีเพียง770.6 396.7คุณมีเดาว่าทำไมอาจเป็น ฉันคิดว่ามันอัพมากเกินไปแม้ว่าจะซ้ายหรือขวาก็จะรวมกันมากขึ้น
โทมัส Ahle

1
กระเบื้องมีแนวโน้มที่จะสแต็คในรูปแบบที่เข้ากันไม่ได้หากพวกเขาจะไม่เปลี่ยนในหลายทิศทาง โดยทั่วไปแล้วการใช้กลยุทธ์แบบวนรอบจะส่งผลให้กระเบื้องมีขนาดใหญ่ขึ้นในศูนย์ซึ่งทำให้การหลบหลีกแคบกว่าเดิมมาก
bcdan

25

มีอยู่แล้วการดำเนินการ AI สำหรับเกมนี้ที่นี่ ข้อความที่ตัดตอนมาจาก README:

อัลกอริทึมคือการค้นหาความลึกที่ลึกขึ้นซ้ำครั้งแรกในอัลฟาเบต้า ฟังก์ชั่นการประเมินผลพยายามที่จะรักษาแถวและคอลัมน์แบบโมโนโทนิก (ทั้งการลดหรือการเพิ่ม) ในขณะที่ลดจำนวนของไพ่บนกริด

นอกจากนี้ยังมีการอภิปรายเกี่ยวกับHacker Newsเกี่ยวกับอัลกอริทึมนี้ที่คุณอาจพบว่ามีประโยชน์


4
นี่ควรเป็นคำตอบที่ดี แต่ควรเพิ่มรายละเอียดเพิ่มเติมเกี่ยวกับการนำไปใช้งาน: เช่นวิธีการกำหนดรูปแบบของบอร์ดเกม (เช่นกราฟ) การเพิ่มประสิทธิภาพที่ใช้ (min-max ความแตกต่างระหว่างไพ่) เป็นต้น
Alceu Costa

1
สำหรับผู้อ่านในอนาคต: โปรแกรมนี้เป็นโปรแกรมเดียวกันที่ผู้เขียน (ovolve) อธิบายไว้ในคำตอบที่อยู่บนสุดที่นี่ คำตอบนี้และอื่น ๆ ที่กล่าวถึงโปรแกรมของ ovolve ในการสนทนานี้ทำให้ ovolve ปรากฏขึ้นและเขียนวิธีการทำงานของอัลกอริธึม คำตอบนั้นมีคะแนน 1200
MultiplyByZer0

23

ขั้นตอนวิธี

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

    choose the maximum evaluation
}

การประเมินผล

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)

รายละเอียดการประเมินผล

128 (Constant)

นี่เป็นค่าคงที่ที่ใช้เป็นฐานและสำหรับการใช้งานอื่น ๆ เช่นการทดสอบ

+ (Number of Spaces x 128)

ช่องว่างมากขึ้นทำให้รัฐมีความยืดหยุ่นมากขึ้นเราคูณด้วย 128 (ซึ่งเป็นค่ามัธยฐาน) เนื่องจากตารางที่เต็มไปด้วยใบหน้าทั้ง 128 นั้นเป็นสถานะที่เป็นไปไม่ได้ที่ดีที่สุด

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

ที่นี่เราประเมินใบหน้าที่มีความเป็นไปได้ที่จะผสานโดยการประเมินย้อนหลังกระเบื้อง 2 กลายเป็นค่า 2048 ในขณะที่กระเบื้อง 2048 ถูกประเมิน 2

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

ที่นี่เรายังต้องตรวจสอบค่าที่ซ้อนกัน แต่ด้วยวิธีที่น้อยกว่าซึ่งไม่รบกวนพารามิเตอร์ความยืดหยุ่นดังนั้นเราจึงมีจำนวน {x ใน [4,44]}

+ (Number of possible next moves x 256)

รัฐมีความยืดหยุ่นมากขึ้นหากมีอิสระในการเปลี่ยนแปลงที่เป็นไปได้มากขึ้น

+ (Number of aligned values x 2)

นี่เป็นการตรวจสอบแบบง่าย ๆ เกี่ยวกับความเป็นไปได้ที่จะมีการรวมภายในสถานะนั้นโดยไม่ต้องมองล่วงหน้า

หมายเหตุ: ค่าคงที่สามารถปรับได้ ..


2
ฉันจะแก้ไขในภายหลังเพื่อเพิ่มรหัสสด @ nitish712
Khaled.K

9
win% ของอัลกอริทึมนี้คืออะไร
cegprakash

ทำไมคุณต้องการconstant? หากสิ่งที่คุณทำคือการเปรียบเทียบคะแนนสิ่งที่มีผลต่อผลลัพธ์ของการเปรียบเทียบเหล่านั้นอย่างไร
bcdan

@bcdan แก้ปัญหา (aka เปรียบเทียบคะแนน) ขึ้นอยู่กับการเปรียบเทียบค่าที่คาดหวังของรัฐในอนาคตเช่นเดียวกับวิธีการวิเคราะห์พฤติกรรมการทำงานหมากรุกยกเว้นนี้เป็นแก้ปัญหาเชิงเส้นเนื่องจากเราไม่ได้สร้างต้นไม้ที่จะรู้ว่าสิ่งที่ดีที่สุดย้ายไม่มีข้อความต่อไป
Khaled.K

12

นี่ไม่ใช่คำตอบที่ตรงกับคำถามของ OP นี่คือสิ่งเพิ่มเติม (การทดลอง) ฉันพยายามแก้ไขปัญหาเดียวกันและได้รับผลลัพธ์บางอย่างและมีข้อสังเกตบางอย่างที่ฉันต้องการแบ่งปันฉันอยากรู้ว่าเราสามารถมี ข้อมูลเชิงลึกเพิ่มเติมจากสิ่งนี้

ฉันเพิ่งลองใช้งาน minimax ด้วยการตัดเลเยอร์อัลฟาเบต้าด้วยการตัดทอนความลึกต้นไม้ค้นหาที่ 3 และ 5 ฉันพยายามแก้ไขปัญหาเดียวกันสำหรับตาราง 4x4 เป็นงานที่มอบหมายโครงการสำหรับหลักสูตร edX ColumbiaX: CSMM.101x ปัญญาประดิษฐ์ ( AI)

ฉันใช้การรวมแบบนูน (ลองตุ้มน้ำหนักแบบฮิวริสติกที่แตกต่างกัน) ของฟังก์ชั่นการประเมินแบบฮิวริสติกโดยส่วนใหญ่มาจากสัญชาติญาณและจากที่กล่าวถึงข้างต้น:

  1. monotonicity
  2. มีพื้นที่ว่าง

ในกรณีของฉันผู้เล่นคอมพิวเตอร์นั้นสุ่มโดยสมบูรณ์ แต่ฉันยังคงสันนิษฐานการตั้งค่าของฝ่ายตรงข้ามและใช้ตัวแทนผู้เล่น AI เป็นผู้เล่นสูงสุด

ฉันมี 4x4 กริดสำหรับเล่นเกม

สังเกต:

หากฉันกำหนดน้ำหนักมากเกินไปให้กับฟังก์ชันฮิวริสติกแรกหรือฟังก์ชันฮิวริสติกที่สองทั้งสองกรณีคะแนนที่ผู้เล่น AI จะได้รับจะต่ำ ฉันเล่นด้วยการกำหนดน้ำหนักที่เป็นไปได้มากมายในฟังก์ชั่นการวิเคราะห์พฤติกรรมและใช้การผสมผสานที่นูน แต่ไม่ค่อยมีผู้เล่น AI ที่สามารถทำคะแนน 2048 ได้บ่อยครั้งที่มันหยุดที่ 1024 หรือ 512

ฉันลองทำมุมฮิวริสติกด้วยเหตุผลบางอย่างทำให้ผลลัพธ์แย่ลงสัญชาตญาณทำไม?

นอกจากนี้ฉันพยายามเพิ่มความลึกของการค้นหาจาก 3 เป็น 5 (ฉันไม่สามารถเพิ่มได้มากขึ้นเนื่องจากการค้นหาพื้นที่นั้นเกินเวลาที่อนุญาตแม้จะมีการตัดแต่งกิ่ง) และเพิ่ม heuristic อีกหนึ่งที่ดูค่าของแผ่นกระเบื้องที่อยู่ติดกันและให้ คะแนนเพิ่มเติมถ้าพวกเขาสามารถผสาน แต่ฉันยังไม่สามารถรับ 2048

ฉันคิดว่ามันจะดีกว่าถ้าใช้ Expectimax แทน minimax แต่ก็ยังต้องการแก้ปัญหานี้ด้วย minimax เท่านั้นและรับคะแนนสูงเช่น 2048 หรือ 4096 ฉันไม่แน่ใจว่าฉันหายอะไรไปหรือเปล่า

ภาพเคลื่อนไหวด้านล่างแสดงขั้นตอนสุดท้ายของเกมที่เล่นโดยเอเจนต์ AI กับเครื่องเล่นคอมพิวเตอร์:

ป้อนคำอธิบายรูปภาพที่นี่

ข้อมูลเชิงลึกใด ๆ จะเป็นประโยชน์อย่างมากขอบคุณล่วงหน้า (นี่คือลิงค์ของโพสต์บล็อกของฉันสำหรับบทความ: https://sandipanweb.wordpress.com/2017/03/06/using-minimax-with-alpha-beta-pruning-and-heuristic-evaluation-to-solve -2048 เกมกับคอมพิวเตอร์ /และวิดีโอ youtube: https://www.youtube.com/watch?v=VnVFilfZ0r4 )

ภาพเคลื่อนไหวต่อไปนี้แสดงขั้นตอนสุดท้ายของเกมที่เล่นโดยที่ตัวแทนผู้เล่น AI จะได้รับ 2048 คะแนนในครั้งนี้เป็นการเพิ่มการวิเคราะห์ด้วยค่าสัมบูรณ์ด้วย:

ป้อนคำอธิบายรูปภาพที่นี่

รูปต่อไปนี้แสดงแผนผังเกมที่ผู้เล่น AI ทำหน้าที่สำรวจว่าคอมพิวเตอร์เป็นปฏิปักษ์ในขั้นตอนเดียว:

ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่


9

ฉันเขียนตัวแก้ปัญหา 2048 ใน Haskell ส่วนใหญ่เป็นเพราะฉันเรียนรู้ภาษานี้ในขณะนี้

การติดตั้งเกมของฉันแตกต่างจากเกมจริงเล็กน้อยโดยที่ไทล์ใหม่จะเป็น '2' เสมอ (มากกว่า 90% 2 และ 10% 4) และว่าไทล์ใหม่นั้นจะไม่สุ่ม แต่มักจะเป็นไทล์แรกที่มีให้จากซ้ายบน ตัวแปรนี้ยังเป็นที่รู้จักกันเดชอุดม 2048

ดังนั้นตัวแก้ปัญหานี้จึงกำหนดขึ้นได้

ฉันใช้อัลกอริทึมครบถ้วนสมบูรณ์ที่โปรดปรานกระเบื้องว่าง มันทำงานได้ค่อนข้างเร็วสำหรับความลึก 1-4 แต่ในระดับความลึก 5 มันจะค่อนข้างช้าที่ประมาณ 1 วินาทีต่อการเคลื่อนไหว

ด้านล่างเป็นรหัสที่ใช้อัลกอริทึมการแก้ปัญหา ตารางถูกแสดงเป็นอาร์เรย์ความยาว 16 ตัวของจำนวนเต็ม และการให้คะแนนทำได้ง่ายๆโดยการนับจำนวนช่องว่าง

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] ]

ฉันคิดว่ามันค่อนข้างประสบความสำเร็จสำหรับความเรียบง่าย ผลลัพธ์ที่ได้เมื่อเริ่มต้นด้วยกริดว่างเปล่าและการแก้ปัญหาที่ความลึก 5 คือ:

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

Game Over

สามารถดูซอร์สโค้ดได้ที่นี่: https://github.com/popovitsj/2048-haskell


ลองขยายด้วยกฎจริง มันเป็นความท้าทายที่ดีในการเรียนรู้เกี่ยวกับเครื่องกำเนิดไฟฟ้าแบบสุ่มของ Haskell!
โทมัส Ahle

ฉันรู้สึกผิดหวังมากกับ Haskell ที่พยายามทำเช่นนั้น แต่ฉันอาจจะลองอีกครั้ง! ฉันพบว่าเกมดังกล่าวง่ายขึ้นมากโดยไม่มีการสุ่มเลือก
wvdz

ฉันไม่แน่ใจว่าคุณสามารถหาวิธีรับ 16k หรือ 32k ได้เสมอ อย่างไรก็ตามการสุ่มในแฮสเค็ลล์นั้นไม่เลวเลยคุณแค่ต้องการวิธีที่จะผ่าน 'เมล็ด' ทำอย่างชัดเจนหรือด้วยการสุ่ม monad
โทมัส Ahle

การปรับอัลกอริทึมเพื่อให้ถึง 16k / 32k เสมอสำหรับเกมที่ไม่ใช่แบบสุ่มอาจเป็นอีกหนึ่งความท้าทายที่น่าสนใจ ...
wvdz

คุณพูดถูกมันยากกว่าที่ฉันคิด ฉันจัดการเพื่อหาลำดับนี้: [ขึ้น, ซ้าย, ซ้าย, ขึ้น, ซ้าย, ลง, ซ้าย] ซึ่งชนะเกมเสมอ แต่มันไม่ได้สูงกว่า 2048 (ในกรณีที่ไม่มีการเคลื่อนไหวทางกฎหมายอัลกอริทึมรอบจะเลือก รายการถัดไปตามลำดับตามเข็มนาฬิกา)
Thomas Ahle

6

อัลกอริทึมนี้ไม่เหมาะสำหรับการชนะเกม แต่มันค่อนข้างดีที่สุดในแง่ของประสิทธิภาพและจำนวนรหัสที่ต้องการ:

  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) เคลื่อนไหวบางอย่างนั้นไม่น่าจะเท่ากัน :)
Daren

3
ที่จริงแล้วถ้าคุณยังใหม่กับเกมมันช่วยได้จริง ๆ เพียงแค่ใช้ 3 ปุ่มโดยทั่วไปแล้วสิ่งที่อัลกอริทึมนี้ทำ จึงไม่เลวเท่าที่เห็นตั้งแต่แรกเห็น
ตัวเลข

5
ใช่มันขึ้นอยู่กับการสังเกตของตัวเองกับเกม จนกว่าคุณจะต้องใช้ทิศทางที่ 4 เกมจะแก้ปัญหาได้จริงโดยไม่ต้องสังเกตอะไร "AI" นี้ควรจะได้รับ 512/1024 โดยไม่ตรวจสอบค่าที่แน่นอนของบล็อกใด ๆ
API-Beast

3
AI ที่เหมาะสมจะพยายามหลีกเลี่ยงการเข้าสู่สถานะที่สามารถเคลื่อนที่ไปในทิศทางเดียวได้โดยเสียค่าใช้จ่ายทั้งหมด
API-Beast

3
การใช้เพียง 3 ทิศทางจริง ๆ แล้วเป็นกลยุทธ์ที่ดีมาก! มันทำให้ฉันเกือบถึงปี 2048 ที่เล่นเกมด้วยตนเอง หากคุณรวมสิ่งนี้เข้ากับกลยุทธ์อื่น ๆ ในการตัดสินใจเลือกระหว่าง 3 ท่าที่เหลืออยู่มันอาจทรงพลังมาก ไม่ต้องพูดถึงว่าการลดตัวเลือก 3 มีผลกระทบอย่างมากต่อประสิทธิภาพ
wvdz

4

คำตอบอื่น ๆ อีกมากมายใช้ AI กับการค้นหาฟิวเจอร์สฮิวริสติกการเรียนรู้และสิ่งเหล่านี้ สิ่งเหล่านี้น่าประทับใจและอาจเป็นวิธีที่ถูกต้อง แต่ฉันต้องการมีส่วนร่วมในความคิดอื่น

วางโมเดลกลยุทธ์ที่ผู้เล่นเกมใช้

ตัวอย่างเช่น:

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

อ่านสี่เหลี่ยมตามลำดับที่แสดงด้านบนจนกว่าค่าสี่เหลี่ยมถัดไปจะมากกว่าค่าในปัจจุบัน สิ่งนี้นำเสนอปัญหาของการพยายามรวมไทล์อื่นที่มีค่าเดียวกันเข้ากับสแควร์นี้

เพื่อแก้ไขปัญหานี้พวกเขามี 2 วิธีในการย้ายที่ไม่เหลือหรือแย่กว่านั้นและการตรวจสอบความเป็นไปได้ทั้งสองอย่างอาจเปิดเผยปัญหาเพิ่มเติมทันทีซึ่งเป็นรายการของการพึ่งพาซึ่งกันและกันแต่ละปัญหาที่ต้องแก้ไขปัญหาอื่นก่อน ฉันคิดว่าฉันมีห่วงโซ่นี้หรือในบางกรณีต้นไม้พึ่งพาอาศัยภายในเมื่อตัดสินใจย้ายของฉันต่อไปโดยเฉพาะอย่างยิ่งเมื่อติดอยู่


ไทล์ต้องการรวมกับเพื่อนบ้าน แต่เล็กเกินไป: รวมเพื่อนบ้านอื่นเข้าด้วยกัน

ทางที่ใหญ่ขึ้นในทางที่: เพิ่มมูลค่าของรอบ ๆ ตัวที่เล็กลง

ฯลฯ ...


วิธีการทั้งหมดจะมีความซับซ้อนมากกว่านี้ แต่ไม่ซับซ้อนมากขึ้น มันอาจเป็นกลไกที่ทำให้รู้สึกขาดคะแนนน้ำหนักเซลล์ประสาทและการค้นหาความเป็นไปได้อย่างลึกซึ้ง ต้นไม้แห่งความเป็นไปได้ยังต้องมีขนาดใหญ่พอที่จะต้องแตกกิ่งก้านสาขาได้เลย


5
คุณกำลังอธิบายการค้นหาในท้องถิ่นด้วยการวิเคราะห์พฤติกรรม นั่นจะทำให้คุณติดอยู่ดังนั้นคุณต้องวางแผนล่วงหน้าสำหรับการเคลื่อนไหวครั้งต่อไป ในทางกลับกันจะนำคุณไปสู่การค้นหาและให้คะแนนโซลูชันเช่นกัน (เพื่อตัดสินใจ) ดังนั้นนี่จึงไม่แตกต่างจากโซลูชันที่นำเสนออื่น ๆ
runDOSrun
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.