บทความนี้จะวิเคราะห์ระบบ AI ของเกมงูโดยอิงจากโปรเจกต์ GitHub: dhillyon/snake-ai ซึ่งใช้ Dijkstra Algorithm ในการหาทางจากหัวงูไปยังแอปเปิ้ล โดยมีการจัดการสถานการณ์ต่าง ๆ อย่างชาญฉลาด เช่น การหาเส้นทางที่ปลอดภัยที่สุดเมื่อไม่สามารถไปหาแอปเปิ้ลได้ทันที


หัวข้อที่ 1: บทนำเกี่ยวกับเกมงูและ AI

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

AI ในโปรเจกต์นี้ใช้ Dijkstra Algorithm เพื่อหาทางที่สั้นที่สุดจากหัวงูไปยังแอปเปิ้ล โดยจะอัปเดตเส้นทางใหม่ทุกครั้งที่เกมรันในแต่ละเฟรม


หัวข้อที่ 2: รายละเอียดเกี่ยวกับ AI Code ว่าทำงานอย่างไร

ในแต่ละเฟรมของเกม (หรือเรียกว่า tick) จะมีขั้นตอนการทำงานของ AI ดังนี้:

  1. ค้นหาเส้นทางที่สั้นที่สุด จากหัวงูไปยังแอปเปิ้ล โดยใช้ Dijkstra Algorithm

    • ถ้ามีเส้นทางไปได้ AI จะใช้เส้นทางนั้นทันที
    • ถ้าไม่มีทางไป (เช่น แอปเปิ้ลอยู่ในพื้นที่ที่ถูกล้อมโดยลำตัวงู) ระบบจะเลือก "เส้นทางที่ยาวที่สุดที่ยังปลอดภัย" เพื่อให้งูมีชีวิตอยู่ให้นานที่สุด จนกว่าจะมีเส้นทางใหม่เปิดออก
  2. จากเส้นทางที่ได้ (ไม่ว่าจะสั้นที่สุดหรือยาวที่สุด) จะนำตำแหน่งของ 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. จากนั้นงูจะเคลื่อนที่ไป 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 Pathfinding Animation
แอนิเมชันแสดงการค้นหาเส้นทางแบบ 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 แบบผสมผสานในอนาคตได้