อัลกอริทึมสำหรับการเพิ่มประสิทธิภาพของเกมจับคู่กับคิวที่รู้จัก


10

ฉันพยายามเขียน Solver ใน C # .NET สำหรับเกมที่รู้จักในชื่อ Flowerz สำหรับการอ้างอิงของคุณคุณสามารถเล่นได้บน MSN ที่นี่: http://zone.msn.com/gameplayer/gameplayer.aspx?game=flowerz ฉันกำลังเขียนเพื่อความสนุกไม่ใช่เพื่องานประเภทใดหรืองานใด ๆ ที่เกี่ยวข้อง ด้วยเหตุนี้ข้อ จำกัด เพียงอย่างเดียวคือคอมพิวเตอร์ของฉัน (intel i7 core พร้อม RAM 8GB) ไม่จำเป็นต้องวิ่งไปที่อื่นเท่าที่ฉันกังวล

ในระยะสั้นกฎของมันเป็นดังนี้:

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

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

ไม่จำเป็นต้องพูดพื้นที่ค้นหาเพิ่มขึ้นอย่างรวดเร็วยิ่งคิวยิ่งใหญ่ขึ้นดังนั้นแรงเดรัจฉานจึงไม่เกิดขึ้น คิวเริ่มต้นที่ 15 และเติบโตด้วย 5 ทุก ๆ สองหรือสามระดับถ้าฉันจำได้ถูกต้อง และแน่นอนว่าการวางดอกไม้ดอกแรกไว้ที่ (0,0) และดอกที่สอง (0,1) นั้นแตกต่างจากการวางดอกไม้ดอกแรกไว้ที่ (1,0) และดอกไม้ที่สองบน (0,0) โดยเฉพาะอย่างยิ่งเมื่อ ฟิลด์มีประชากรอยู่แล้วด้วยดอกไม้จากรอบก่อนหน้า การตัดสินใจที่ง่ายเช่นนี้สามารถสร้างความแตกต่างในการสร้างมันขึ้นมาหรือไม่

คำถามที่ฉันมีต่อไปนี้:

  • นี่เป็นปัญหาอะไร (คิดว่าพนักงานขายที่เดินทางเป้เครื่องหรือปัญหา combinatorial อื่น ๆ ) การรู้สิ่งนี้จะทำให้ Google-fu ของฉันดีขึ้นกว่าเดิม
  • อัลกอริทึมแบบไหนที่ให้ผลลัพธ์ที่ดีเร็ว

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

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

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

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


ฉันเคยคิดออกว่าพื้นที่ค้นหาของเกมที่ซับซ้อนที่ฉันกำลังทำอยู่นั้นจะเป็น 32Gb ในเวลานั้น (ฉันมีดิสก์ไดรฟ์ 20Mb) ที่จะไม่สามารถทำได้ แต่วันนี้มันเป็นไปได้ใน RAM สำหรับคอมพิวเตอร์บางเครื่อง
Jonathan

ดอกไม้ที่มีสีเดียวหายไปทั้งหมดเมื่อจับคู่หรือไม่ และดอกไม้ที่มีสองสีสามารถจับคู่ชั้นนอกกับสีเดียวของดอกไม้สีเดียวได้หรือไม่? ฉันเข้าใจดังนั้นในข้อหาทั้งสอง แต่เหล่านี้จะไม่เคยระบุไว้อย่างชัดเจนในรายละเอียดของปัญหา ...
สตีเว่น Stadnicki

@StevenStadnicki ขอบคุณ! ฉันได้เพิ่มข้อมูลนั้นไปยังคำถามเดิม
user849924

1
ในฐานะที่เป็นโน้ตเล็ก ๆ โดยบังเอิญมีแนวโน้มที่ปัญหารุ่น 'บูลีน' (มีวิธีการวางดอกไม้ในคิวเพื่อออกจากกระดานว่างเปล่าในตอนท้าย?) เป็นปัญหาที่สมบูรณ์ มันมีความคล้ายคลึงกันอย่างชัดเจนกับปัญหา Clickomania ( erikdemaine.org/clickomania ) ซึ่งเป็นปัญหา NP-complete และปัญหานั้นไม่ยากกว่าปัญหา NP เพราะให้วิธีแก้ปัญหา (ความยาวพหุนาม) ซึ่งง่ายต่อการตรวจสอบโดยใช้การจำลอง ซึ่งหมายความว่าปัญหาการปรับให้เหมาะสมน่าจะอยู่ใน FP ^ NP
Steven Stadnicki

คำตอบ:


9

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

นอกจากนี้ยังมีเกณฑ์เป้าหมายที่บอกให้คุณทราบเมื่อสถานะเป็น "แก้ไข" และค่าใช้จ่ายเส้นทาง - ค่าใช้จ่ายในการก้าวไปสู่รัฐที่กำหนด (เสมอ "1 ย้าย" ในกรณีนี้)

ปริศนาตัวต่อหนึ่งชนิดนี้คือตัวต่อ15ตัว และวิธีการทั่วไปในการแก้ปัญหาคือการค้นหาที่มีข้อมูล - ตัวอย่างเช่นการค้นหาแบบฮิวริสติกแบบคลาสสิกA *และตัวแปร


อย่างไรก็ตามมีปัญหากับวิธีการอย่างรวดเร็วก่อนนี้ อัลกอริทึมเช่น A * ได้รับการออกแบบเพื่อให้เส้นทางที่สั้นที่สุดไปยังเป้าหมาย (ตัวอย่างเช่น: จำนวนการเคลื่อนไหวน้อยที่สุด) ในกรณีของคุณจำนวนของการเคลื่อนไหวได้รับการแก้ไขเสมอ - ไม่มีเส้นทางที่สั้นที่สุด - เพื่อให้การค้นหาการแก้ปัญหาก็จะทำให้คุณเส้นทางไปเกมเสร็จสมบูรณ์

สิ่งที่คุณต้องการคือลำดับของการเคลื่อนไหวที่ให้สถานะเกมที่สมบูรณ์ที่สุด

ดังนั้นสิ่งที่คุณต้องทำคือเปิดปัญหารอบเล็กน้อย แทนที่จะเป็นกระดานเกมที่เป็น "รัฐ" ลำดับของการเคลื่อนไหวจะกลายเป็น "รัฐ" (เช่น: วางรายการในคิวที่ตำแหน่ง "D2, A5, C7, B3, A3, ... ")

ซึ่งหมายความว่าเราไม่สนใจว่ารัฐเหล่านั้นจะถูกสร้างขึ้นอย่างไร คณะกรรมการเองไม่สำคัญจำเป็นต้องประเมินคุณภาพของสถานะที่กำหนดเท่านั้น

สิ่งนี้จะทำให้ปัญหากลายเป็นปัญหาการปรับให้เหมาะสมซึ่งสามารถแก้ไขได้ด้วยอัลกอริธึมการค้นหาในท้องถิ่น (ซึ่งโดยทั่วไปหมายถึงการสร้างรัฐรอบรัฐที่กำหนดและเลือกรัฐที่ดีที่สุดโดยไม่สนใจเส้นทางระหว่างรัฐ)

ปริศนาแม่บทนี้ชนิดเป็นปริศนาแปดควีนส์

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

สำหรับปัญหาของคุณฟังก์ชันวัตถุประสงค์อาจส่งคืนค่าระหว่าง 0 ถึง N สำหรับจำนวนรายการในคิวที่ใช้หมดก่อนถึงสถานะความล้มเหลว (โดยที่ N คือความยาวของคิว) และมิฉะนั้นค่าของ N + M โดยที่ M คือจำนวนของช่องว่างที่เหลืออยู่บนกระดานหลังจากคิวว่างเปล่า เช่นนี้ - ยิ่งค่ายิ่งสูงโซลูชันยิ่งมีความเป็นกลางมากขึ้น

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


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

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

รูปแบบเฉพาะอย่างหนึ่งของสิ่งนี้คืออัลกอริทึมทางพันธุกรรม ( Wikipedia ) ขั้นตอนพื้นฐานสำหรับอัลกอริทึมทางพันธุกรรมคือ:

  1. กำหนดวิธีการแปลงสถานะเป็นสตริงบางชนิด ในกรณีของคุณนี่อาจเป็นสตริงของตัวเลขความยาวคิวจาก 1 ถึง 49 (แสดงถึงตำแหน่งที่เป็นไปได้ทั้งหมดบนกระดานขนาด 7x7 ซึ่งอาจเก็บไว้ที่ 1 ไบต์ต่อครั้ง) (ชิ้นส่วน "โพดำ" ของคุณสามารถแสดงด้วยสองรายการคิวที่ตามมาสำหรับแต่ละขั้นตอนของการย้าย)
  2. สุ่มเลือกพันธุ์ประชากรให้สูงขึ้นเพื่อความน่าจะเป็นรัฐที่มีที่ดีกว่าการออกกำลังกาย ประชากรการผสมพันธุ์ควรมีขนาดเท่ากันกับประชากรดั้งเดิม - คุณสามารถเลือกรัฐจากประชากรเดิมได้หลายครั้ง
  3. จับคู่รัฐในประชากรการผสมพันธุ์ (ครั้งแรกไปกับที่สองที่สามไปที่สี่และอื่น ๆ )
  4. เลือกจุดไขว้แบบสุ่มสำหรับแต่ละคู่ (ตำแหน่งในสตริง)
  5. สร้างลูกหลานสองคนสำหรับแต่ละคู่โดยการแลกเปลี่ยนส่วนของสตริงหลังจุดครอสโอเวอร์
  6. สุ่มกลายพันธุ์แต่ละสถานะของลูกหลาน ตัวอย่างเช่น: สุ่มเลือกเพื่อเปลี่ยนตำแหน่งสุ่มในสตริงเป็นค่าสุ่ม
  7. ทำขั้นตอนนี้ซ้ำกับประชากรใหม่จนกว่าประชากรจะมาบรรจบกันในโซลูชันหนึ่งวิธีหรือมากกว่า (หรือหลังจากหลายรุ่นตามที่กำหนดหรือพบทางออกที่ดีพอ)

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

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

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

เช่นเดียวกับปัญหา AI ที่ไม่สำคัญอื่น ๆ เช่นนี้จะต้องมีการแก้ไขที่สำคัญ

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


สำหรับคำตอบนี้โดยเฉพาะอย่างยิ่งเพื่อให้ได้คำศัพท์ที่ถูกต้องทั้งหมดฉันต้องขุดตำราเรียน AI ของมหาวิทยาลัยของฉัน "ปัญญาประดิษฐ์: แนวทางที่ทันสมัย" โดย Russell และ Norvig ไม่แน่ใจว่ามันเป็น "ดี" (ฉันไม่มีตำรา AI อื่น ๆ เพื่อเปรียบเทียบกับ) แต่ก็ไม่เลว อย่างน้อยมันก็ค่อนข้างใหญ่;)


ฉันพบว่ามีปัญหากับครอสโอเวอร์เช่นกัน: เป็นไปได้มากที่เด็กมีรายการวางเกินกว่าที่มีอยู่ในคิว (ชนิดที่ขาด GA สำหรับ TSP: เขาอาจไปเที่ยวเมืองสองครั้งหรือมากกว่านั้น (หรือไม่เลย!) หลังจาก ครอสโอเวอร์อาจจะเป็นครอสโอเวอร์ที่ได้รับคำสั่ง ( permutationcity.co.uk/projects/mutants/tsp.html ) สามารถใช้งานได้โดยเฉพาะอย่างยิ่งเมื่อคุณทำลำดับการเคลื่อนย้ายสถานะ
user849924

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

สถานะความล้มเหลวคือเมื่อคุณไม่มีตัวเลือกเพิ่มเติมในการวางการเคลื่อนไหวเช่นเมื่อคุณไม่มีที่ว่างและไม่มีการจับคู่เกิดขึ้นกับการย้ายนั้น คล้ายกับสิ่งที่คุณพูด: คุณต้องวางมันในตำแหน่งที่ถูกครอบครองอยู่แล้ว (แต่มันก็เป็นจริงเมื่อไม่มีที่ที่จะเริ่มต้นด้วย) ครอสโอเวอร์ที่ฉันโพสต์น่าสนใจ โครโมโซม A มีรายการอยู่ใน A1, B1, ... , G1, A2, B2 และ C2 และโครโมโซม B บน G7 ... A7, G6, F6 และ E6 เลือก randoms สองสามตัวจาก A และเก็บดัชนีไว้ เลือกส่วนประกอบของ A จาก B และเก็บดัชนีและผสานสำหรับเด็ก
user849924

'ปัญหา' กับครอสโอเวอร์นี้คืออนุญาตให้มีการเคลื่อนไหวหลายจุดในจุดเดียวกัน แต่นั่นควรจะสามารถแก้ไขได้อย่างง่ายดายด้วยบางสิ่งที่คล้ายกับ SimulateAutomaticChanges จากทางออกของ Stefan K: ใช้การเคลื่อนย้าย / สถานะของเด็กกับสถานะพื้นฐาน ) ไม่สามารถทำได้ (เพราะคุณต้องวางดอกไม้ในจุดที่ถูกครอบครอง) จากนั้นเด็กจะไม่ถูกต้องและเราจะต้องผสมพันธุ์อีกครั้ง นี่คือสิ่งที่เงื่อนไขความล้มเหลวของคุณปรากฏขึ้น ตอนนี้ฉันเข้าใจแล้ว : D
user849924

ฉันยอมรับสิ่งนี้เป็นคำตอบด้วยเหตุผลสองประการ ครั้งแรก: คุณให้ฉันความคิดที่ฉันต้องการเพื่อให้ GA ทำงานสำหรับปัญหานี้ ประการที่สอง: คุณเป็นคนแรก ; p
user849924

2

การจำแนกประเภท

คำตอบนั้นไม่ใช่เรื่องง่าย ทฤษฎีเกมมีการจำแนกประเภทสำหรับเกม แต่ดูเหมือนจะไม่มีความชัดเจน 1: 1-match สำหรับเกมนั้นกับทฤษฎีพิเศษ มันเป็นรูปแบบพิเศษของปัญหา combinatorial

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

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

Wikipedia ให้คำแนะนำบางประการเกี่ยวกับการจัดหมวดหมู่ที่นี่: http://en.wikipedia.org/wiki/Game_theory#Types_of_games

ฉันจะจัดหมวดหมู่เป็น "ปัญหาการควบคุมที่เหมาะสมแบบไม่ต่อเนื่องเวลา" ( http://en.wikipedia.org/wiki/Optimal_control ) แต่ฉันไม่คิดว่าสิ่งนี้จะช่วยคุณได้

อัลกอริทึม

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

จากนั้นคุณต้องการเพียงส่วนต่อไปนี้:

  1. รูปแบบของสถานะของเกมที่เก็บข้อมูลทั้งหมดของเกม (เช่นสถานะของบอร์ด / แผนที่, คิว, ย้ายหมายเลข / ตำแหน่งในคิว)
  2. ตัวสร้างการเคลื่อนย้ายซึ่งให้การเคลื่อนไหวที่ถูกต้องทั้งหมดสำหรับสถานะเกมที่กำหนด
  3. ฟังก์ชั่น "do move" และ "undo move" ซึ่งใช้ / เลิกทำการย้าย (ถูกต้อง) ที่กำหนดไปยังสถานะเกม ในขณะที่ฟังก์ชั่น "do move" ควรเก็บ "เลิกทำข้อมูล" สำหรับฟังก์ชั่น "เลิกทำ" การคัดลอกสถานะเกมและการแก้ไขในแต่ละการวนซ้ำจะทำให้การค้นหาช้าลงอย่างมาก! ลองอย่างน้อยที่สุดเพื่อเก็บสถานะไว้ในสแต็ก (= ตัวแปรท้องถิ่นไม่มีการจัดสรรแบบไดนามิกโดยใช้ "ใหม่")
  4. ฟังก์ชั่นการประเมินผลซึ่งให้คะแนนเทียบเคียงสำหรับแต่ละสถานะของเกม
  5. ฟังก์ชั่นการค้นหา

นี่คือการดำเนินการอ้างอิงที่ไม่สมบูรณ์สำหรับการค้นหาเชิงลึกครั้งแรก:

public class Item
{
    // TODO... represents queue items (FLOWER, SHOVEL, BUTTERFLY)
}

public class Field
{
    // TODO... represents field on the board (EMPTY or FLOWER)
}

public class Modification {
    int x, y;
    Field originalValue, newValue;

    public Modification(int x, int y, Field originalValue, newValue) {
        this.x = x;
        this.y = y;
        this.originalValue = originalValue;
        this.newValue = newValue;
    }

    public void Do(GameState state) {
        state.board[x,y] = newValue;
    }

    public void Undo(GameState state) {
        state.board[x,y] = originalValue;
    }
}

class Move : ICompareable {

    // score; from evaluation function
    public int score; 

    // List of modifications to do/undo to execute the move or to undo it
    Modification[] modifications;

    // Information for later knowing, what "control" action has been chosen
    public int x, y;   // target field chosen
    public int x2, y2; // secondary target field chosen (e.g. if moving a field)


    public Move(GameState state, Modification[] modifications, int score, int x, int y, int x2 = -1, int y2 = -1) {
        this.modifications = modifications;
        this.score = score;
        this.x = x;
        this.y = y;
        this.x2 = x2;
        this.y2 = y2;
    }

    public int CompareTo(Move other)
    {
        return other.score - this.score; // less than 0, if "this" precededs "other"...
    }

    public virtual void Do(GameState state)
    {
        foreach(Modification m in modifications) m.Do(state);
        state.queueindex++;
    }

    public virtual void Undo(GameState state)
    {
        --state.queueindex;
        for (int i = m.length - 1; i >= 0; --i) m.Undo(state); // undo modification in reversed order
    }
}

class GameState {
    public Item[] queue;
    public Field[][] board;
    public int queueindex;

    public GameState(Field[][] board, Item[] queue) {
        this.board = board;
        this.queue = queue;
        this.queueindex = 0;
    }

    private int Evaluate()
    {
        int value = 0;
        // TODO: Calculate some reasonable value for the game state...

        return value;
    }

    private List<Modification> SimulateAutomaticChanges(ref int score) {
        List<Modification> modifications = new List<Modification>();
        // TODO: estimate all "remove" flowers or recoler them according to game rules 
        // and store all changes into modifications...
        if (modifications.Count() > 0) {
            foreach(Modification modification in modifications) modification.Do(this);

            // Recursively call this function, for cases of chain reactions...
            List<Modification> moreModifications = SimulateAutomaticChanges();

            foreach(Modification modification in modifications) modification.Undo(this);

            // Add recursively generated moves...
            modifications.AddRange(moreModifications);
        } else {
            score = Evaluate();
        }

        return modifications;
    }

    // Helper function for move generator...
    private void MoveListAdd(List<Move> movelist, List<Modifications> modifications, int x, int y, int x2 = -1, int y2 = -1) {
        foreach(Modification modification in modifications) modification.Do(this);

        int score;
        List<Modification> autoChanges = SimulateAutomaticChanges(score);

        foreach(Modification modification in modifications) modification.Undo(this);

        modifications.AddRange(autoChanges);

        movelist.Add(new Move(this, modifications, score, x, y, x2, y2));
    }


    private List<Move> getValidMoves() {
        List<Move> movelist = new List<Move>();
        Item nextItem = queue[queueindex];
        const int MAX = board.length * board[0].length + 2;

        if (nextItem.ItemType == Item.SHOVEL)
        {

            for (int x = 0; x < board.length; ++x)
            {
                for (int y = 0; y < board[x].length; ++y)
                {
                    // TODO: Check if valid, else "continue;"

                    for (int x2 = 0; x2 < board.length; ++x2)
                    {
                        for(int y2 = 0; y2 < board[x].length; ++y2) {
                            List<Modifications> modifications = new List<Modifications>();

                            Item fromItem = board[x][y];
                            Item toItem = board[x2][y2];
                            modifications.Add(new Modification(x, y, fromItem, Item.NONE));
                            modifications.Add(new Modification(x2, y2, toItem, fromItem));

                            MoveListAdd(movelist, modifications, x, y, x2, y2);
                        }
                    }
                }
            }

        } else {

            for (int x = 0; x < board.length; ++x)
            {
                for (int y = 0; y < board[x].length; ++y)
                {
                    // TODO: check if nextItem may be applied here... if not "continue;"

                    List<Modifications> modifications = new List<Modifications>();
                    if (nextItem.ItemType == Item.FLOWER) {
                        // TODO: generate modifications for putting flower at x,y
                    } else {
                        // TODO: generate modifications for putting butterfly "nextItem" at x,y
                    }

                    MoveListAdd(movelist, modifications, x, y);
                }
            }
        }

        // Sort movelist...
        movelist.Sort();

        return movelist;
    }


    public List<Move> Search()
    {
        List<Move> validmoves = getValidMoves();

        foreach(Move move in validmoves) {
            move.Do(this);
            List<Move> solution = Search();
            if (solution != null)
            {
                solution.Prepend(move);
                return solution;
            }
            move.Undo(this);
        }

        // return "null" as no solution was found in this branch...
        // this will also happen if validmoves == empty (e.g. lost game)
        return null;
    }
}

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

หากนี่ช้าเกินไปคุณสามารถลองใช้วิธีการบางอย่างของเกมสองคนเป็น HashTables เพื่อที่คุณจะต้องคำนวณคีย์แฮ (ซ้ำ) สำหรับแต่ละสถานะของเกมที่คุณประเมินและทำเครื่องหมายสถานะที่ไม่นำไปสู่การแก้ปัญหา เช่นทุกครั้งก่อนที่เมธอด Search () จะส่งคืน "null" จะต้องสร้างรายการ HashTable และเมื่อเข้าสู่การค้นหา () คุณจะต้องตรวจสอบว่าสถานะนี้ผ่านไปแล้วหรือยังโดยที่ไม่มีผลลัพธ์ที่เป็นบวก ตรวจสอบต่อไป. สำหรับสิ่งนี้คุณจะต้องมีตารางแฮชขนาดใหญ่และต้องยอมรับ "การชนกันของแฮช" ซึ่งอาจทำให้คุณไม่พบโซลูชันที่มีอยู่ แต่ไม่น่าเป็นไปได้มากหากฟังก์ชันแฮชของคุณดีพอและตารางของคุณคือ ใหญ่พอ (เป็นความเสี่ยงของความเสี่ยงที่คำนวณได้)

ฉันคิดว่าไม่มีอัลกอริธึมอื่นที่จะแก้ปัญหานี้ (ตามที่คุณอธิบาย) มีประสิทธิภาพมากกว่าสมมติว่าฟังก์ชั่นการประเมินของคุณเหมาะสมที่สุด ...


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

คำตอบนี้ทำให้ฉันมีความคิดสำหรับรูปแบบและวิธีการทำงานกับกฎของเกมดังนั้นฉันจะโหวตมัน ขอบคุณสำหรับข้อมูลของคุณ!
user849924

@ user849924: ใช่แน่นอนฟังก์ชั่นการประเมินผลต้องคำนวณการประเมิน "คุณค่า" สำหรับสิ่งนั้น ยิ่งสถานะของเกมในปัจจุบันยิ่งแย่ลง (ใกล้ถึงการสูญเสีย) ยิ่งมูลค่าการประเมินกลับมาแย่ลงเท่าใด การประเมินที่ง่ายที่สุดคือส่งคืนจำนวนฟิลด์ว่าง คุณสามารถปรับปรุงได้โดยเพิ่ม 0.1 สำหรับแต่ละดอกที่วางถัดจากดอกไม้ที่มีสีคล้ายกัน ในการตรวจสอบการทำงานของคุณเลือกสถานะเกมแบบสุ่มคำนวณค่าและเปรียบเทียบ หากคุณคิดว่าสถานะ A ดีกว่าสถานะ B คะแนนก่อนหน้า A ควรจะดีกว่าคะแนนสำหรับ B
SDwarfs
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.