การค้นหาเส้นทางที่สั้นที่สุดบนตารางหกเหลี่ยม


14

ฉันกำลังเขียนเกมเทิร์นเบสที่มีองค์ประกอบการจำลอง ภารกิจหนึ่งที่ฉันต้องวางสายในขณะนี้คือการค้นหาเส้นทาง สิ่งที่ฉันต้องการจะทำคือขยับนักผจญภัย AI หนึ่งไทล์ให้ใกล้กับเป้าหมายโดยใช้ x, y และเป้าหมายของเขา x, y

ในการพยายามคิดออกเองฉันสามารถกำหนด 4 ทิศทางไม่มีปัญหาโดยใช้

dx = currentX - targetY
dy = currentY - targetY

แต่ฉันไม่แน่ใจว่าจะกำหนดเส้นทางใดใน 6 ทิศทางที่เป็นเส้นทางที่ "ดีที่สุด" หรือ "สั้นที่สุด"

ตัวอย่างเช่นวิธีการตั้งค่าในปัจจุบันฉันใช้ East, West, NE, NW, SE, SW แต่เพื่อไปที่ไทล์ NE ที่ฉันย้าย East และ NW แทนที่จะเป็นแค่การเคลื่อนย้าย NW

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


5
A * ให้เส้นทางที่สั้นที่สุดแก่คุณโดยไม่คำนึงถึงรูปร่างของกราฟของคุณ (กริดฐานสิบหกรูปแบบอิสระ .. )
Jari Komppa

คำตอบ:


21

คำตอบไม่กี่!

ระบบพิกัดที่ฉันเห็นบ่อยที่สุดสำหรับการสำรวจเส้นทางฐานสิบหกคือระบบที่ผู้เล่นสามารถเคลื่อนที่ในทุกทิศทาง NSEW ปกติรวมถึง NW และ SE จากนั้นคุณเพียงแค่เรนเดอร์ครึ่งตารางแต่ละแถว ตัวอย่างเช่นตำแหน่ง (2,7) ถูกพิจารณาว่าอยู่ติดกับ (1,7), (3,7), (2,6), (2,8) และสถานที่แปลก ๆ : (1,6) และ (3,8) ในขณะเดียวกันหากเราสมมติว่า (2,7) แสดงผลที่กึ่งกลางของหน้าจอ (2,6) จะแสดงผลขึ้นและไปทางขวา (2,8) จะแสดงลงและไปที่ - ด้านซ้าย (1,7) และ (3,7) จะยึดไว้ทางซ้ายและขวาตามลำดับและ (1,6) และ (3,8) จะวางตัวเองบนซ้ายและขวาล่างตามลำดับ

แผนผังของสิ่งที่ฉันหมายถึง:

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

หากคุณทำเช่นนี้การหาเส้นทางที่สั้นที่สุดนั้นไม่ใช่เรื่องยาก - เดินทางไปยังระยะทาง NW / SE สูงสุดที่คุณสามารถทำได้โดยไม่ต้องมองข้ามเป้าหมายของคุณไปตามแกนแกนคาร์ดินัลจากนั้นเดินทางไปตามแกนนั้นไปยังเป้าหมาย

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


ขอบคุณสำหรับลิงก์ไปยังอัลกอริทึมการค้นหา A * วิธีเดียวที่ฉันสามารถจินตนาการได้ว่าการสำรวจ nsew และ nw / se เป็น hex ที่เอียง ซึ่งดูแปลก ๆ ในหัวของฉัน คุณสามารถเชื่อมโยงฉันกับตัวอย่างนี้ได้หรือไม่?
Timothy Mayes

4
ฉันกำลังบอกว่าภาพที่เรนเดอร์ของคุณไม่จำเป็นต้องมีความคล้ายคลึงกับโครงสร้างภายในมากนัก ฉันแนะนำว่าภายในคุณใช้ NSEW และ NW / SE แต่คุณแสดงมันต่อผู้ใช้ราวกับว่ามันเป็นตาราง การแนบไดอะแกรมอธิบายกับคำตอบเดิม :)
ZorbaTHut

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

2
@PandaPajama: jagged ทำงานได้ดีกว่าสำหรับการจัดเก็บแผนที่สี่เหลี่ยมอย่างมีประสิทธิภาพ คุณสามารถทำให้พิกัดที่ไม่เป็นรอยหยักทำงานได้ดีกับเคล็ดลับนี้
amitp

2
@PandaPajama มีเคล็ดลับที่น่าสนใจอีกข้อหนึ่งที่คุณสามารถใช้ได้ - คุณสามารถใช้การแทนค่าแบบไม่หยักสำหรับพิกัดแล้วสรุปการสำรองข้อมูลการจัดเก็บข้อมูลของคุณที่อยู่เบื้องหลังสิ่งที่ใช้วิธี "jagged" ฉันได้พบว่าระบบพิกัดของ non-jagged นั้นง่ายกว่าที่จะจัดการ แต่แน่นอนว่าเมื่อมันถูกแยกออกไปแบ็กเอนด์สามารถทำทุกอย่างที่มันต้องการเพื่อทำให้สิ่งต่าง ๆ มีประสิทธิภาพ :)
ZorbaTHut

5

ฉันเพิ่งโพสต์ไลบรารีของ hex-grid utilites บน CodePlex.com ที่นี่: https://hexgridutilities.codeplex.com/ ห้องสมุดมีการค้นหาเส้นทาง (ใช้ A- * a la Eric Lippert) และรวมถึงประโยชน์สำหรับการแปลงอัตโนมัติระหว่าง jagged (ผู้ใช้ที่เรียกว่า) ลูกน้องและพิกัดที่ไม่ใช่ jagged (เรียกว่า Canonical) อัลกอริธึมการค้นพบพา ธ ทำให้ต้นทุนขั้นตอนสำหรับแต่ละโหนดแตกต่างกันทั้งในรายการฐานสิบหกและด้านฐานสิบหกที่ผ่านการสำรวจ (แม้ว่าตัวอย่างที่มีให้นั้นง่ายกว่า) นอกจากนี้ยังมีการจัดมุมมองแบบยกระดับโดยใช้การคัดเลือกภาพเงา [แก้ไข: ลบคำ]

นี่คือตัวอย่างโค้ดที่แปลงได้อย่างง่ายดายระหว่างระบบพิกัด hex-grid สามระบบ:

static readonly IntMatrix2D MatrixUserToCanon = new IntMatrix2D(2,1, 0,2, 0,0, 2);
IntVector2D VectorCanon {
  get { return !isCanonNull ? vectorCanon : VectorUser * MatrixUserToCanon / 2; }
  set { vectorCanon = value;  isUserNull = isCustomNull = true; }
} IntVector2D vectorCanon;
bool isCanonNull;

static readonly IntMatrix2D MatrixCanonToUser  = new IntMatrix2D(2,-1, 0,2, 0,1, 2);    
IntVector2D VectorUser {
  get { return !isUserNull  ? vectorUser 
             : !isCanonNull ? VectorCanon  * MatrixCanonToUser / 2
                            : VectorCustom * MatrixCustomToUser / 2; }
  set { vectorUser  = value;  isCustomNull = isCanonNull = true; }
} IntVector2D vectorUser;
bool isUserNull;

static IntMatrix2D MatrixCustomToUser = new IntMatrix2D(2,0, 0,-2, 0,(2*Height)-1, 2);
static IntMatrix2D MatrixUserToCustom = new IntMatrix2D(2,0, 0,-2, 0,(2*Height)-1, 2);
IntVector2D VectorCustom {
  get { return !isCustomNull ? vectorCustom : VectorUser * MatrixUserToCustom / 2; }
  set { vectorCustom  = value;  isCanonNull = isUserNull = true; }
} IntVector2D vectorCustom;
bool isCustomNull;

IntMatrix2D และ IntVector2D คือการใช้งานจำนวนเต็ม [เหมือนกัน: homogeneous] ของ affine2D กราฟิกเวกเตอร์และเมทริกซ์ การหารสุดท้ายด้วย 2 บนแอปพลิเคชันเวกเตอร์คือการทำให้เวกเตอร์เป็นปกติอีกครั้ง สิ่งนี้อาจถูกฝังอยู่ในการใช้งาน IntMatrix2D แต่เหตุผลที่อาร์กิวเมนต์ที่ 7 สำหรับตัวสร้าง IntMatrix2D นั้นไม่ชัดเจน สังเกตการรวมกันของการแคชและการประเมินผลที่ขี้เกียจของสูตรที่ไม่หมุนเวียน

เมทริกซ์เหล่านี้ใช้สำหรับกรณี:

  • Hex Hex แนวตั้ง;
  • แหล่งกำเนิดในมุมบนซ้ายสำหรับพิกัด Canonical & ผู้ใช้, ซ้ายล่างสำหรับพิกัดที่กำหนดเอง;
  • แกน Y ในแนวตั้งลง
  • แกน X เป็นรูปสี่เหลี่ยมผืนผ้าตามแนวนอน และ
  • Canonical X-axis ไปทางตะวันออกเฉียงเหนือ (เช่นขึ้นและไปทางขวาที่ 120 องศา CCW จากแกน Y)

รหัสห้องสมุดที่กล่าวถึงข้างต้นมีกลไกที่สง่างามเหมือนกันสำหรับการหยิบ hex (เช่นการระบุเลขฐานสิบหกที่เลือกด้วยการคลิกเมาส์)

ในพิกัดของ Canonical พิกัด 6 ทิศทางของเวกเตอร์คือ (1,0), (0,1), (1,1) และค่า inverses ของพวกมันสำหรับรูปหกเหลี่ยมทั้งหมดโดยไม่ต้องมีสมมาตรของ coordintes ขรุขระ


ว้าว! สุทธิหนึ่งคะแนนสำหรับการโพสต์ไลบรารีของรหัสการทำงานพร้อมด้วยตัวอย่างและเอกสารประกอบที่ตอบคำถาม / ปัญหาที่โพสต์โดย OP
Pieter Geerkens

5
ในขณะที่ฉันไม่ใช่ downvoter (และมารยาทโดยทั่วไปแนะนำให้แสดงความคิดเห็นอธิบาย downvote) แต่ฉันสงสัยว่า downvote นั้นเป็นเพราะ (ก) โพสต์ดังกล่าวทำให้เกิดการโฆษณาและ (b) วางคำตอบจำนวนมากไว้ที่อื่น ด้านข้างของลิงค์มักจะขมวดคิ้วเนื่องจากลิงก์มีแนวโน้มที่จะเน่าและไซต์ SE พยายามที่จะมีตัวเองอยู่ ข้อมูลที่ให้ไว้ที่นี่เป็นสิ่งที่น่าสนใจ แต่ไม่ตอบคำถามของผู้ใช้และข้อมูลเดียวที่สามารถตอบคำถามนั้นอยู่ที่อีกด้านหนึ่งของลิงก์
Steven Stadnicki

จุดที่ดี; ขอขอบคุณ. ฉันได้ขยายการโพสต์ด้วยข้อความที่ตัดตอนมาซึ่งตอบคำถามเกี่ยวกับวิธีการรักษาพิกัดกริดเลขฐานสิบหกที่มีประสิทธิภาพได้อย่างมีประสิทธิภาพ ไลบรารีโค้ดที่โพสต์นั้นเป็นฟรีแวร์
Pieter Geerkens

อ๊ะ! การหารด้วย 2จะทำงานเฉพาะสำหรับจำนวนเต็มบวก (ขอบคุณอีกครั้ง K&R.) มันควรจะถูกแทนที่ด้วยการโทรไปที่วิธี Normalize () ใน IntVector2D:
Pieter Geerkens

public IntVector2D Normalize() { if (Z==1) return this; else { var x = (X >= 0) ? X : X - Z; var y = (Y >= 0) ? Y : Y - Z; return new IntVector2D(x/Z, y/Z); } }
Pieter Geerkens

0

นี่เป็นปัญหาที่ได้รับการแก้ไขโดยมีวรรณคดีมากมายที่ต้องสำรอง ที่ดีที่สุดของทรัพยากรที่ผมรู้ก็คือแดงหยดเกมส์: https://www.redblobgames.com/grids/hexagons/

โดยย่อเหตุผลที่เป็นไปได้มากที่สุดคือคุณเริ่มต้นด้วยระบบพิกัดที่ไม่ถูกต้อง การใช้ระบบพิกัด Cube ที่ใช้อัลกอริทึม A * นั้นค่อนข้างง่าย ดูการสาธิตสดได้ที่ลิงค์ด้านบน

หากคุณต้องการใช้ระบบอื่นจริงๆให้แปลงเป็นและเมื่อจำเป็น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.