บทความนี้จะวิเคราะห์ระบบ AI ของเกมงูโดยอิงจากโปรเจกต์ GitHub: dhillyon/snake-ai ซึ่งใช้ Dijkstra Algorithm ในการหาทางจากหัวงูไปยังแอปเปิ้ล โดยมีการจัดการสถานการณ์ต่าง ๆ อย่างชาญฉลาด เช่น การหาเส้นทางที่ปลอดภัยที่สุดเมื่อไม่สามารถไปหาแอปเปิ้ลได้ทันที
หัวข้อที่ 1: บทนำเกี่ยวกับเกมงูและ AI
เกมงู (Snake) เป็นเกมคลาสสิกที่ผู้เล่นควบคุมงูให้กินแอปเปิ้ลและยาวขึ้นเรื่อย ๆ โดยไม่ชนกับตัวเองหรือตีกรอบจอ ในโปรเจกต์นี้ AI จะควบคุมงูโดยอัตโนมัติ เพื่อให้มันสามารถอยู่รอดและกินแอปเปิ้ลได้มากที่สุด
AI ในโปรเจกต์นี้ใช้ Dijkstra Algorithm เพื่อหาทางที่สั้นที่สุดจากหัวงูไปยังแอปเปิ้ล โดยจะอัปเดตเส้นทางใหม่ทุกครั้งที่เกมรันในแต่ละเฟรม
หัวข้อที่ 2: รายละเอียดเกี่ยวกับ AI Code ว่าทำงานอย่างไร
ในแต่ละเฟรมของเกม (หรือเรียกว่า tick) จะมีขั้นตอนการทำงานของ AI ดังนี้:
-
ค้นหาเส้นทางที่สั้นที่สุด จากหัวงูไปยังแอปเปิ้ล โดยใช้ Dijkstra Algorithm
- ถ้ามีเส้นทางไปได้ AI จะใช้เส้นทางนั้นทันที
- ถ้าไม่มีทางไป (เช่น แอปเปิ้ลอยู่ในพื้นที่ที่ถูกล้อมโดยลำตัวงู) ระบบจะเลือก "เส้นทางที่ยาวที่สุดที่ยังปลอดภัย" เพื่อให้งูมีชีวิตอยู่ให้นานที่สุด จนกว่าจะมีเส้นทางใหม่เปิดออก
จากเส้นทางที่ได้ (ไม่ว่าจะสั้นที่สุดหรือยาวที่สุด) จะนำตำแหน่งของ cell แรกในเส้นทางนั้นมาคำนวณทิศทางใหม่ที่งูควรเคลื่อนที่ โดยใช้สูตร:
snake_dir = (path[0][0] - snake[0][0], path[0][1] - snake[0][1])
ซึ่ง:
-
path[0]
คือพิกัดจุดถัดไปที่งูควรไป -
snake[0]
คือพิกัดหัวงูปัจจุบัน -
snake_dir
จะกลายเป็นเวกเตอร์ทิศทาง เช่น (0, 1), (-1, 0) เป็นต้น
- จากนั้นงูจะเคลื่อนที่ไป 1 ช่องตามทิศทางที่คำนวณได้จาก
snake_dir
การคำนวณใหม่นี้จะเกิดขึ้นทุกเฟรม ทำให้ AI ตอบสนองต่อสถานการณ์ในเกมแบบเรียลไทม์
หัวข้อที่ 3: อธิบายโค้ดว่าทำงานอย่างไร รับแล้วจะได้ผลอย่างไร
ตัวอย่างฟังก์ชันสำคัญใน AI คือ move_snake()
ซึ่งจะรับผลลัพธ์จาก Dijkstra แล้วนำไปหาทิศทาง:
def move_snake():
path = get_path_to_apple()
if not path:
path = get_longest_safe_path()
if path:
snake_dir = (path[0][0] - snake[0][0], path[0][1] - snake[0][1])
move(snake_dir)
คำอธิบายผลลัพธ์:
- หากมีเส้นทางไปหาแอปเปิ้ล
get_path_to_apple()
จะคืนลิสต์ของตำแหน่ง และงูจะเคลื่อนไปตำแหน่งถัดไปทันที - หากไม่มีทางไป ระบบจะเลือกเส้นทางที่ยาวและปลอดภัยที่สุดแทนผ่าน
get_longest_safe_path()
- เมื่อได้เส้นทางแล้ว จะคำนวณเวกเตอร์ระหว่างตำแหน่งหัวงูและตำแหน่งเป้าหมายถัดไป แล้วใช้ฟังก์ชัน
move()
เพื่อย้ายงูตามทิศทางนั้น
ตัวอย่างผลลัพธ์ที่เกิดขึ้น:
- งูสามารถไปหาแอปเปิ้ลได้โดยไม่ชนตัวเอง
- เมื่อไม่มีทางไป AI จะวนในพื้นที่ว่างได้นานขึ้นจนทางใหม่เปิด
- ทำให้งูตายช้าลงและทำคะแนนได้สูงขึ้น
หัวข้อที่ 4: ภาพประกอบและตัวอย่างผลลัพธ์
ภาพแสดงตัวอย่างเส้นทางที่ AI คำนวณได้:
- ✅ กรณีมีทางตรงไปหาแอปเปิ้ล (เส้นสีเขียว): งูจะเคลื่อนที่โดยตรงไปยังแอปเปิ้ลทันที
- ⚠️ กรณีไม่มีทางไป (เส้นสีฟ้า): งูจะเคลื่อนที่วนในพื้นที่ว่างเพื่อถ่วงเวลา
![ตัวอย่างเส้นทาง AI Snake]
ภาพจาก GitHub dhillyon/snake-ai แสดงตัวอย่างการคำนวณเส้นทางสำเร็จ
แอนิเมชันแสดงการค้นหาเส้นทางแบบ Dijkstra: สำรวจทุกเส้นทางอย่างรอบคอบ
หัวข้อที่ 5: ทดลองปรับ AI จาก Dijkstra เป็น Greedy Search
เพื่อศึกษาความแตกต่างของวิธีการ เราได้ทดลองเปลี่ยน AI ที่ใช้ Dijkstra เป็น Greedy Search โดยเลือก node ที่อยู่ใกล้แอปเปิ้ลมากที่สุดในแต่ละรอบ โดยไม่สนใจน้ำหนักรวมของเส้นทางทั้งหมด
แนวทางที่เปลี่ยน:
- ใช้ Heuristic ที่คำนวณ Manhattan distance ระหว่างจุดปัจจุบันกับแอปเปิ้ล เช่น:
def manhattan(p1, p2):
return abs(p1[0] - p2[0]) + abs(p1[1] - p2[1])
- แต่ละรอบจะเลือก node ที่ใกล้แอปเปิ้ลที่สุดตาม heuristic นี้เท่านั้น
ข้อสังเกตจากผลลัพธ์:
- ✅ เร็วกว่า Dijkstra เพราะไม่ต้องพิจารณาเส้นทางทั้งหมด
- 🔻 บางครั้งเลือกเส้นทางที่สั้นแต่ตัน ทำให้งูติดกับดัก
- ⚠️ ไม่สามารถรับมือกับกรณีต้อง "อ้อม" ไปทางที่ปลอดภัยกว่าได้ดีเท่า Dijkstra
ตัวอย่างภาพเปรียบเทียบ:
หัวข้อที่ 6: สรุป
AI ในโปรเจกต์ snake-ai
นี้ใช้ Dijkstra Algorithm อย่างชาญฉลาดในการควบคุมงูให้เคลื่อนที่หาแอปเปิ้ล และเมื่อไม่สามารถหาแอปเปิ้ลได้ ก็ยังสามารถวางแผนเส้นทางที่ปลอดภัยที่สุดเพื่อความอยู่รอด
เมื่อลองปรับมาใช้ Greedy Search พบว่าทำงานได้เร็วขึ้น แต่ขาดความแม่นยำและความยืดหยุ่นในการหลีกเลี่ยงเส้นทางอันตราย เป็นตัวอย่างที่ดีของการเปรียบเทียบ algorithm คลาสสิกต่าง ๆ และสามารถใช้ต่อยอดในการออกแบบ AI แบบผสมผสานในอนาคตได้