Battleship AI ที่ดีที่สุดคืออะไร?


315

เรือรบ!

ย้อนกลับไปในปี 2003 (เมื่อฉันอายุ 17) ฉันเข้าแข่งขันในการแข่งขันการเขียนโปรแกรมBattleship AI แม้ว่าฉันจะแพ้ทัวร์นาเมนต์นั้นฉันก็สนุกและได้เรียนรู้มากมายจากมัน

ตอนนี้ฉันต้องการที่จะฟื้นการแข่งขันครั้งนี้เพื่อค้นหา AI ของเรือรบที่ดีที่สุด

นี่คือกรอบตอนนี้โฮสต์บน Bitbucket

ผู้ชนะจะได้รับรางวัล 450 ชื่อเสียง! การแข่งขันจะจัดขึ้นเริ่มต้นใน17 พฤศจิกายน 2009 จะไม่ยอมรับผลงานหรือการแก้ไขที่ช้ากว่าศูนย์ชั่วโมงในวันที่ 17 (เวลามาตรฐานกลาง) ส่งผลงานก่อนเวลาเพื่อให้คุณไม่พลาดโอกาสของคุณ!

เพื่อรักษาวัตถุประสงค์นี้โปรดปฏิบัติตามจิตวิญญาณของการแข่งขัน

กฎของเกม:

  1. เกมนี้เล่นบนกริด 10x10
  2. ผู้แข่งขันแต่ละคนจะวางแต่ละเรือรบ 5 ลำ (ความยาว 2, 3, 3, 4, 5) บนกริด
  3. ไม่มีเรืออาจทับซ้อนกัน แต่อาจอยู่ติดกัน
  4. จากนั้นคู่แข่งจะทำการยิงนัดเดียวที่คู่ต่อสู้
    • ความแตกต่างในเกมนี้อนุญาตให้ยิงได้หลายนัดต่อการยิงหนึ่งครั้งต่อหนึ่งเรือรบที่รอดชีวิต
  5. ฝ่ายตรงข้ามจะแจ้งให้คู่แข่งทราบหากช็อตนี้ถูกยิง, โดนหรือพลาด
  6. การเล่นเกมจะสิ้นสุดลงเมื่อเรือรบทุกลำของผู้เล่นคนใดคนหนึ่งจมลง

กฎของการแข่งขัน:

  1. จิตวิญญาณของการแข่งขันคือการค้นหาอัลกอริทึมของเรือรบที่ดีที่สุด
  2. สิ่งใดก็ตามที่ถือว่าขัดต่อจิตวิญญาณของการแข่งขันจะเป็นเหตุให้ถูกตัดสิทธิ์
  3. การแทรกแซงคู่ต่อสู้ขัดต่อจิตวิญญาณของการแข่งขัน
  4. การใช้มัลติเธรดอาจใช้ภายใต้ข้อ จำกัด ต่อไปนี้:
    • ไม่สามารถมีเธรดได้มากกว่าหนึ่งเธรดในขณะที่ไม่ใช่ตาคุณ (แม้ว่าจำนวนเธรดใด ๆ อาจอยู่ในสถานะ "ถูกระงับ")
    • ไม่สามารถเรียกใช้เธรดที่ระดับความสำคัญอื่นนอกจาก "ปกติ"
    • ด้วยข้อ จำกัด สองข้อข้างต้นคุณจะได้รับการรับประกันอย่างน้อย 3 คอร์ CPU โดยเฉพาะในระหว่างการเปิด
  5. ขีด จำกัด ของเวลา CPU 1 วินาทีต่อเกมจะถูกกำหนดให้กับคู่แข่งแต่ละรายในเธรดหลัก
  6. หมดเวลาในการสูญเสียเกมปัจจุบัน
  7. ข้อยกเว้นใด ๆ ที่ไม่สามารถจัดการได้จะส่งผลให้สูญเสียเกมปัจจุบัน
  8. อนุญาตการเข้าถึงเครือข่ายและการเข้าถึงดิสก์ แต่คุณอาจพบว่าข้อ จำกัด ด้านเวลานั้นเป็นสิ่งต้องห้ามอย่างเป็นธรรม อย่างไรก็ตามมีการเพิ่มวิธีการตั้งค่าและการฉีกขาดบางอย่างเพื่อบรรเทาความเครียดเวลา
  9. ควรโพสต์รหัสในการล้นสแต็กเป็นคำตอบหรือหากมีขนาดใหญ่เกินไปเชื่อมโยง
  10. ขนาดรวมสูงสุด (ไม่บีบอัด) ของรายการคือ 1 MB
  11. อย่างเป็นทางการ. Net 2.0 / 3.5 เป็นข้อกำหนดของกรอบงานเท่านั้น
  12. ข้อมูลของคุณจะต้องใช้งานอินเทอร์เฟซ IBattleshipOpponent

เกณฑ์การให้คะแนน:

  1. สุดยอด 51 เกมจากทั้งหมด 101 เกมเป็นผู้ชนะในการแข่งขัน
  2. คู่แข่งทั้งหมดจะเล่นแบบจับคู่กันสไตล์กลมแบบโรบิน
  3. ครึ่งที่ดีที่สุดของคู่แข่งจะเล่นเป็นทัวร์นาเมนต์สองครั้งเพื่อตัดสินผู้ชนะ (พลังที่เล็กที่สุดของสองที่มากกว่าหรือเท่ากับครึ่งจริง)
  4. ฉันจะใช้เฟรมเวิร์กTournamentApiสำหรับทัวร์นาเมนต์
  5. ผลลัพธ์จะถูกโพสต์ที่นี่
  6. หากคุณส่งมากกว่าหนึ่งรายการเฉพาะคะแนนที่ดีที่สุดของคุณเท่านั้นที่มีสิทธิ์ได้รับรางวัลสองครั้ง

โชคดี! มีความสุข!


แก้ไข 1:
ขอบคุณFreedที่พบข้อผิดพลาดในShip.IsValidฟังก์ชัน มันได้รับการแก้ไขแล้ว โปรดดาวน์โหลดเฟรมเวิร์กเวอร์ชันที่อัพเดต

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

แก้ไข 3:
แก้ไขข้อผิดพลาด 1: GameWonและGameLostถูกเรียกในกรณีที่หมดเวลาเท่านั้น
แก้ไขข้อผิดพลาดที่ 2: ถ้าเอ็นจิ้นหมดเวลาทุกเกมการแข่งขันจะไม่จบลง
โปรดดาวน์โหลดเฟรมเวิร์กเวอร์ชันที่อัพเดต

แก้ไข 4:
ผลการแข่งขัน:


หากรายการนั้นต้องการฐานข้อมูลขนาดใหญ่สามารถเชื่อมต่อกับมันผ่านเน็ตได้หรือไม่? กล่าวคือ สามารถเข้าใช้บริการทางเว็บได้หรือไม่?
Remus Rusanu

มีการ จำกัด ขนาดของรายการหรือไม่
Jherico

8
@ สตีเว่น: นอกจากนี้ฉันยังปรึกษากับ Jeff Atwood เพื่อดูว่ามันเหมาะสมหรือไม่ นี่คือคำตอบของเขา: twitter.com/codinghorror/status/5203185621
John Gietzen

1
นอกจากนี้ฉันจะเพิ่ม taht เนื่องจากองค์ประกอบสุ่มที่หลีกเลี่ยงไม่ได้สำหรับเกม 50 เกมเหล่านี้จะไม่เพียงพอที่จะแยกแยะระหว่างการใช้งานที่ดีมากอย่างแม่นยำ ฉันคิดว่า 501 หรือมากกว่านั้นอาจจำเป็นสำหรับมุมมองที่สมเหตุสมผลซึ่งดีกว่า
ShuggyCoUk

1
ฝ่ายตรงข้ามที่ "สงบสุข" ที่ไม่ยอมวางเรือทำให้การแข่งขันสิ้นสุดลง ไม่แน่ใจว่าคุณใส่ใจคนที่ทำเรื่องโง่ ๆ แบบนั้นมากแค่ไหน :)
Joe

คำตอบ:


56

ฉันสองการเคลื่อนไหวเพื่อทำเกมมากขึ้นต่อการแข่งขัน การเล่น 50 เกมเป็นเพียงการพลิกเหรียญ ฉันต้องทำ 1,000 เกมเพื่อให้ได้ความแตกต่างที่สมเหตุสมผลระหว่างอัลกอริธึมการทดสอบ

ดาวน์โหลดจต์ 1.2

กลยุทธ์:

  • ติดตามตำแหน่งที่เป็นไปได้ทั้งหมดสำหรับเรือรบที่มี> ฮิต รายการไม่เคยมีขนาดใหญ่กว่า ~ 30K เพื่อให้สามารถรักษาได้อย่างแน่นอนไม่เหมือนกับรายการตำแหน่งที่เป็นไปได้ทั้งหมดสำหรับเรือทุกลำ (ซึ่งมีขนาดใหญ่มาก)

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

  • สำหรับการยิงแบบสุ่มให้คำนวณตำแหน่งที่ดีที่สุดในการยิงตามความน่าจะเป็นของเรือรบลำหนึ่งที่ยังไม่ได้ทำการซ้อนทับตำแหน่ง

  • อัลกอริทึมแบบปรับตัวที่วางเรือในสถานที่ซึ่งคู่ต่อสู้มีโอกาสยิงน้อยกว่า

  • อัลกอริทึมแบบปรับตัวที่ชอบถ่ายภาพในสถานที่ซึ่งฝ่ายตรงข้ามมีแนวโน้มที่จะวางเรือของเขามากกว่า

  • เรือที่ส่วนใหญ่ไม่ได้สัมผัสกัน


บนเครื่องทดสอบของฉัน (เน็ตบุ๊ค ULV Celeron) รหัสนี้จะหมดเวลาอย่างสม่ำเสมอ เมื่อฉันปล่อยให้มันใช้เวลาตลอดเวลามันต้องการแส้ง่าย ๆ (อัตราความสำเร็จประมาณ 90%) หากคุณกำลังอาศัยอย่างหนักในสเปคของเครื่องที่คุณกำลังจะไปทำงานบนที่จะตีคุณ timelimits คุณอาจต้องการที่จะให้ตัวเองห้องเลื้อยบางอย่าง ...
ShuggyCoUk

น่าสนใจ ... มันทำงานได้ดีบนเครื่องทัวร์นาเมนต์ อย่างไรก็ตามเครื่องมือ "สมบูรณ์แบบ" จะปรับให้เข้ากับเวลาที่ใช้ไป
John Gietzen

35

นี่คือรายการของฉัน! (ทางออกที่ไร้เดียงสาที่สุดที่เป็นไปได้)

"สุ่ม 1.1"

namespace Battleship
{
    using System;
    using System.Collections.ObjectModel;
    using System.Drawing;

    public class RandomOpponent : IBattleshipOpponent
    {
        public string Name { get { return "Random"; } }
        public Version Version { get { return this.version; } }

        Random rand = new Random();
        Version version = new Version(1, 1);
        Size gameSize;

        public void NewGame(Size size, TimeSpan timeSpan)
        {
            this.gameSize = size;
        }

        public void PlaceShips(ReadOnlyCollection<Ship> ships)
        {
            foreach (Ship s in ships)
            {
                s.Place(
                    new Point(
                        rand.Next(this.gameSize.Width),
                        rand.Next(this.gameSize.Height)),
                    (ShipOrientation)rand.Next(2));
            }
        }

        public Point GetShot()
        {
            return new Point(
                rand.Next(this.gameSize.Width),
                rand.Next(this.gameSize.Height));
        }

        public void NewMatch(string opponent) { }
        public void OpponentShot(Point shot) { }
        public void ShotHit(Point shot, bool sunk) { }
        public void ShotMiss(Point shot) { }
        public void GameWon() { }
        public void GameLost() { }
        public void MatchOver() { }
    }
}

52
อันที่จริงคำตอบนี้เป็นสิ่งที่ดีเพราะมันแสดงให้เห็นในรูปแบบรัดกุมมาก API ของคุณจะต้องใช้ในการแข่งขัน ... :)
dicroce

1
ย้อนกลับไปเมื่อฉันสร้างโครงการที่คล้ายกันในชั้นเรียนอัลกอริทึมของฉันฉันใช้ลอจิกแบบสุ่มสอดประสานกับการตัดสินใจบางอย่าง บางครั้งมันก็ดี!
Nathan Taylor

2
สิ่งนี้อาจพยายามวางเรือรบที่ทับซ้อนกันใช่ไหม

6
ใช่ แต่เครื่องยนต์จะไม่อนุญาตสิ่งนี้ จากนั้นจะบอก AI ให้วางอีกครั้ง แต่คราวนี้ด้วยเสียงที่รุนแรงกว่า (เห็นโดยpop ax \ cmp ax, 1 \ je stern)
John Gietzen

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

22

นี่คือคู่ต่อสู้สำหรับผู้เล่น:

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

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

การขยายตัวของรัฐเรือรบที่เป็นไปได้ http://natekohl.net/media/battleship-tree.png

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

สิ่งนี้สามารถมองเห็นเป็นแผนที่ความร้อนซึ่งจุดร้อนมีแนวโน้มที่จะมีเรือ:

แผนที่ความร้อนของความน่าจะเป็นสำหรับตำแหน่งที่ยังไม่ได้สำรวจแต่ละแห่ง http://natekohl.net/media/battleship-probs.png

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

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


จนถึงตอนนี้คุณกำลังเอาชนะโซลูชันเต็มรูปแบบอื่น ๆ เพียง 67.7% ถึง 32.3% :)
จอห์น Gietzen

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

12

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

BoardView ช่วยให้คุณทำงานกับบอร์ดได้อย่างง่ายดาย

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.IO;

namespace Battleship.ShuggyCoUk
{
    public enum Compass
    {
        North,East,South,West
    }

    class Cell<T>
    {
        private readonly BoardView<T> view;
        public readonly int X;
        public readonly int Y;
        public T Data;
        public double Bias { get; set; }

        public Cell(BoardView<T> view, int x, int y) 
        { 
            this.view = view; this.X = x; this.Y = y; this.Bias = 1.0;  
        }

        public Point Location
        {
            get { return new Point(X, Y); }
        }

        public IEnumerable<U> FoldAll<U>(U acc, Func<Cell<T>, U, U> trip)
        {
            return new[] { Compass.North, Compass.East, Compass.South, Compass.West }
                .Select(x => FoldLine(x, acc, trip));
        }

        public U FoldLine<U>(Compass direction, U acc, Func<Cell<T>, U, U> trip)
        {
            var cell = this;
            while (true)
            {
                switch (direction)
                {
                    case Compass.North:
                        cell = cell.North; break;
                    case Compass.East:
                        cell = cell.East; break;
                    case Compass.South:
                        cell = cell.South; break;
                    case Compass.West:
                        cell = cell.West; break;
                }
                if (cell == null)
                    return acc;
                acc = trip(cell, acc);
            }
        }

        public Cell<T> North
        {
            get { return view.SafeLookup(X, Y - 1); }
        }

        public Cell<T> South
        {
            get { return view.SafeLookup(X, Y + 1); }
        }

        public Cell<T> East
        {
            get { return view.SafeLookup(X+1, Y); }
        }

        public Cell<T> West
        {
            get { return view.SafeLookup(X-1, Y); }
        }

        public IEnumerable<Cell<T>> Neighbours()
        {
            if (North != null)
                yield return North;
            if (South != null)
                yield return South;
            if (East != null)
                yield return East;
            if (West != null)
                yield return West;
        }
    }

    class BoardView<T>  : IEnumerable<Cell<T>>
    {
        public readonly Size Size;
        private readonly int Columns;
        private readonly int Rows;

        private Cell<T>[] history;

        public BoardView(Size size)
        {
            this.Size = size;
            Columns = size.Width;
            Rows = size.Height;
            this.history = new Cell<T>[Columns * Rows];
            for (int y = 0; y < Rows; y++)
            {
                for (int x = 0; x < Rows; x++)
                    history[x + y * Columns] = new Cell<T>(this, x, y);
            }
        }

        public T this[int x, int y]
        {
            get { return history[x + y * Columns].Data; }
            set { history[x + y * Columns].Data = value; }
        }

        public T this[Point p]
        {
            get { return history[SafeCalc(p.X, p.Y, true)].Data; }
            set { this.history[SafeCalc(p.X, p.Y, true)].Data = value; }
        }

        private int SafeCalc(int x, int y, bool throwIfIllegal)
        {
            if (x < 0 || y < 0 || x >= Columns || y >= Rows)
            {    if (throwIfIllegal)
                    throw new ArgumentOutOfRangeException("["+x+","+y+"]");
                 else
                    return -1;
            }
            return x + y * Columns;
        }

        public void Set(T data)
        {
            foreach (var cell in this.history)
                cell.Data = data;
        }

        public Cell<T> SafeLookup(int x, int y)
        {
            int index = SafeCalc(x, y, false);
            if (index < 0)
                return null;
            return history[index];
        }

        #region IEnumerable<Cell<T>> Members

        public IEnumerator<Cell<T>> GetEnumerator()
        {
            foreach (var cell in this.history)
                yield return cell;
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        public BoardView<U> Transform<U>(Func<T, U> transform)
        {
            var result = new BoardView<U>(new Size(Columns, Rows));
            for (int y = 0; y < Rows; y++)
            {
                for (int x = 0; x < Columns; x++)
                {
                    result[x,y] = transform(this[x, y]);
                }
            }
            return result;
        }

        public void WriteAsGrid(TextWriter w)
        {
            WriteAsGrid(w, "{0}");
        }

        public void WriteAsGrid(TextWriter w, string format)
        {
            WriteAsGrid(w, x => string.Format(format, x.Data));
        }

        public void WriteAsGrid(TextWriter w, Func<Cell<T>,string> perCell)
        {
            for (int y = 0; y < Rows; y++)
            {
                for (int x = 0; x < Columns; x++)
                {
                    if (x != 0)
                        w.Write(",");
                    w.Write(perCell(this.SafeLookup(x, y)));
                }
                w.WriteLine();
            }
        }

        #endregion
    }
}

ส่วนขยายบางส่วนฟังก์ชันการทำงานซ้ำซ้อนบางอย่างในเฟรมเวิร์กหลัก แต่คุณควรทำจริง ๆ

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Collections.ObjectModel;

namespace Battleship.ShuggyCoUk
{
    public static class Extensions
    {        
        public static bool IsIn(this Point p, Size size)
        {
            return p.X >= 0 && p.Y >= 0 && p.X < size.Width && p.Y < size.Height;
        }

        public static bool IsLegal(this Ship ship,
            IEnumerable<Ship> ships, 
            Size board,
            Point location, 
            ShipOrientation direction)
        {
            var temp = new Ship(ship.Length);
            temp.Place(location, direction);
            if (!temp.GetAllLocations().All(p => p.IsIn(board)))
                return false;
            return ships.Where(s => s.IsPlaced).All(s => !s.ConflictsWith(temp));
        }

        public static bool IsTouching(this Point a, Point b)
        {
            return (a.X == b.X - 1 || a.X == b.X + 1) &&
                (a.Y == b.Y - 1 || a.Y == b.Y + 1);
        }

        public static bool IsTouching(this Ship ship,
            IEnumerable<Ship> ships,
            Point location,
            ShipOrientation direction)
        {
            var temp = new Ship(ship.Length);
            temp.Place(location, direction);
            var occupied = new HashSet<Point>(ships
                .Where(s => s.IsPlaced)
                .SelectMany(s => s.GetAllLocations()));
            if (temp.GetAllLocations().Any(p => occupied.Any(b => b.IsTouching(p))))
                return true;
            return false;
        }

        public static ReadOnlyCollection<Ship> MakeShips(params int[] lengths)
        {
            return new System.Collections.ObjectModel.ReadOnlyCollection<Ship>(
                lengths.Select(l => new Ship(l)).ToList());       
        }

        public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Rand rand)
        {
            T[] elements = source.ToArray();
            // Note i > 0 to avoid final pointless iteration
            for (int i = elements.Length - 1; i > 0; i--)
            {
                // Swap element "i" with a random earlier element it (or itself)
                int swapIndex = rand.Next(i + 1);
                T tmp = elements[i];
                elements[i] = elements[swapIndex];
                elements[swapIndex] = tmp;
            }
            // Lazily yield (avoiding aliasing issues etc)
            foreach (T element in elements)
            {
                yield return element;
            }
        }

        public static T RandomOrDefault<T>(this IEnumerable<T> things, Rand rand)
        {
            int count = things.Count();
            if (count == 0)
                return default(T);
            return things.ElementAt(rand.Next(count));
        }
    }
}

สิ่งที่ฉันท้ายที่สุดใช้มาก

enum OpponentsBoardState
{
    Unknown = 0,
    Miss,
    MustBeEmpty,        
    Hit,
}

การสุ่มตัวอย่าง ปลอดภัย แต่ทดสอบได้มีประโยชน์สำหรับการทดสอบ

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace Battleship.ShuggyCoUk
{
    public class Rand
    {
        Random r;

        public Rand()
        {
            var rand = System.Security.Cryptography.RandomNumberGenerator.Create();
            byte[] b = new byte[4];
            rand.GetBytes(b);
            r = new Random(BitConverter.ToInt32(b, 0));
        }

        public int Next(int maxValue)
        {
            return r.Next(maxValue);
        }

        public double NextDouble(double maxValue)
        {
            return r.NextDouble() * maxValue;
        }

        public T Pick<T>(IEnumerable<T> things)
        {
            return things.ElementAt(Next(things.Count()));
        }

        public T PickBias<T>(Func<T, double> bias, IEnumerable<T> things)
        {
            double d = NextDouble(things.Sum(x => bias(x)));
            foreach (var x in things)
            {
                if (d < bias(x))
                    return x;
                d -= bias(x);                
            }
            throw new InvalidOperationException("fell off the end!");
        }
    }
}

10

ฉันไม่มีเวลาที่จะเขียนอัลกอริธึมเต็มเปี่ยม แต่นี่เป็นความคิด: ถ้าคู่ต่อสู้ของคุณวางเรือแบบสุ่มความน่าจะเป็นของตำแหน่งจะไม่เป็นการกระจายแบบง่ายที่มีศูนย์กลางอยู่ที่ (5.5,5.5) ตัวอย่างเช่นความเป็นไปได้ของตำแหน่งสำหรับเรือรบ (ยาว 5 หน่วย) ในมิติ x อยู่ที่นี่:

x    1 2 3 4 5  6  7 8 9 10
P(x) 2 4 6 8 10 10 8 6 4 2

การคำนวณเดียวกันจะใช้ได้สำหรับ y เรือลำอื่นไม่ได้มีการกระจายชัน แต่การคาดเดาที่ดีที่สุดของคุณยังคงเป็นศูนย์กลาง หลังจากนั้นวิธีการทางคณิตศาสตร์จะค่อยๆฉายเส้นทแยงมุมออกไป (อาจมีความยาวของเรือเฉลี่ย 17/5) ออกจากศูนย์กลาง Ex:

...........
....x.x....
.....x.....
....x.x....
...........

เห็นได้ชัดว่าการสุ่มต้องมีการเพิ่มความคิด แต่ฉันคิดว่าคณิตศาสตร์ล้วนเป็นวิธีที่จะไป


ใช่แน่นอนพวกเขาจะ เครื่องยนต์เก่าของฉันชดเชยสิ่งนั้น
John Gietzen

1
ฉันมาจากช้าแผ่เส้นทแยงมุมจากศูนย์ถือว่าโกง
bzlm

หากมันถือว่าเป็นการโกงมีวิธีการรับมือที่ค่อนข้างง่าย หลีกเลี่ยง (x, y) โดยที่ x = y :)
INE

5
ฉันคิดว่าเขายิ่งทำให้นับไพ่ ซึ่งในความคิดของฉันไม่ได้โกง
John Gietzen

10

ไม่มีอะไรที่ซับซ้อน แต่เป็นสิ่งที่ฉันคิดขึ้นมา มันเต้นคู่ต่อสู้แบบสุ่ม 99.9% ของเวลา จะสนใจถ้าใครมีความท้าทายเล็กน้อยอื่น ๆ เช่นนี้มันสนุกดี

namespace Battleship
{
    using System;
    using System.Collections.ObjectModel;
    using System.Drawing;
    using System.Collections.Generic;
    using System.Linq;
    public class AgentSmith : IBattleshipOpponent
    {        
        public string Name { get { return "Agent Smith"; } }
        public Version Version { get { return this.version; } }
        private Random rand = new Random();
        private Version version = new Version(2, 1);
        private Size gameSize;
        private enum Direction { Up, Down, Left, Right }
        private int MissCount;
        private Point?[] EndPoints = new Point?[2];
        private LinkedList<Point> HitShots = new LinkedList<Point>();
        private LinkedList<Point> Shots = new LinkedList<Point>();
        private List<Point> PatternShots = new List<Point>();
        private Direction ShotDirection = Direction.Up;
        private void NullOutTarget()
        {
            EndPoints = new Point?[2];
            MissCount = 0;
        }
        private void SetupPattern()
        {
            for (int y = 0; y < gameSize.Height; y++)
                for (int x = 0; x < gameSize.Width; x++)
                    if ((x + y) % 2 == 0) PatternShots.Add(new Point(x, y));
        }
        private bool InvalidShot(Point p)
        {
            bool InvalidShot = (Shots.Where(s => s.X == p.X && s.Y == p.Y).Any());
            if (p.X < 0 | p.Y<0) InvalidShot = true;
            if (p.X >= gameSize.Width | p.Y >= gameSize.Height) InvalidShot = true;
            return InvalidShot;
        }
        private Point FireDirectedShot(Direction? direction, Point p)
        {
            ShotDirection = (Direction)direction;
            switch (ShotDirection)
            {
                case Direction.Up: p.Y--; break;
                case Direction.Down: p.Y++; break;
                case Direction.Left: p.X--; break;
                case Direction.Right: p.X++; break;
            }
            return p;
        }
        private Point FireAroundPoint(Point p)
        {
            if (!InvalidShot(FireDirectedShot(ShotDirection,p)))
                return FireDirectedShot(ShotDirection, p);
            Point testShot = FireDirectedShot(Direction.Left, p);
            if (InvalidShot(testShot)) { testShot = FireDirectedShot(Direction.Right, p); }
            if (InvalidShot(testShot)) { testShot = FireDirectedShot(Direction.Up, p); }
            if (InvalidShot(testShot)) { testShot = FireDirectedShot(Direction.Down, p); }
            return testShot;
        }
        private Point FireRandomShot()
        {
            Point p;
            do
            {
                if (PatternShots.Count > 0)
                    PatternShots.Remove(p = PatternShots[rand.Next(PatternShots.Count)]);
                else do
                    {
                        p = FireAroundPoint(HitShots.First());
                        if (InvalidShot(p)) HitShots.RemoveFirst();
                    } while (InvalidShot(p) & HitShots.Count > 0);
            }
            while (InvalidShot(p));
            return p;
        }
        private Point FireTargettedShot()
        {
            Point p;
            do
            {
                p = FireAroundPoint(new Point(EndPoints[1].Value.X, EndPoints[1].Value.Y));
                if (InvalidShot(p) & EndPoints[1] != EndPoints[0])
                    EndPoints[1] = EndPoints[0];
                else if (InvalidShot(p)) NullOutTarget();
            } while (InvalidShot(p) & EndPoints[1] != null);
            if (InvalidShot(p)) p = FireRandomShot();
            return p;
        }
        private void ResetVars()
        {
            Shots.Clear();
            HitShots.Clear();
            PatternShots.Clear();
            MissCount = 0;
        }
        public void NewGame(Size size, TimeSpan timeSpan)
        {
            gameSize = size;
            ResetVars();
            SetupPattern();
        }
        public void PlaceShips(ReadOnlyCollection<Ship> ships)
        {
            foreach (Ship s in ships)
                s.Place(new Point(rand.Next(this.gameSize.Width), rand.Next(this.gameSize.Height)), (ShipOrientation)rand.Next(2));
        }
        public Point GetShot()
        {
            if (EndPoints[1] != null) Shots.AddLast(FireTargettedShot());
            else Shots.AddLast(FireRandomShot());
            return Shots.Last();
        }
        public void ShotHit(Point shot, bool sunk)
        {            
            HitShots.AddLast(shot);
            MissCount = 0;
            EndPoints[1] = shot;
            if (EndPoints[0] == null) EndPoints[0] = shot;
            if (sunk) NullOutTarget();
        }
        public void ShotMiss(Point shot)
        {
            if (++MissCount == 6) NullOutTarget();
        }
        public void GameWon() { }
        public void GameLost() { }
        public void NewMatch(string opponent) { }
        public void OpponentShot(Point shot) { }
        public void MatchOver() { }
    }
}

ข้นเล็กน้อยเพื่อใช้พื้นที่น้อยที่สุดในที่นี่และยังคงสามารถอ่านได้


6

ความคิดเห็นเกี่ยวกับเครื่องมือการแข่งขัน:

พารามิเตอร์เกมใหม่:

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

เรือถูกปิดผนึก:

ฉันไม่เห็นเหตุผลใด ๆ ว่าเหตุใดคลาสซีลจึงปิดผนึก ในสิ่งพื้นฐานอื่น ๆ ฉันต้องการให้เรือมีชื่อดังนั้นฉันจึงสามารถส่งข้อความเช่น("คุณจม {0}", ship.Name); . ฉันมีส่วนขยายอื่น ๆ ในใจด้วยดังนั้นฉันคิดว่า Ship ควรเป็นสิ่งที่สืบทอดได้

การ จำกัด เวลา:

ในขณะที่การ จำกัด เวลา 1 วินาทีเหมาะสมกับกฎการแข่งขัน BattleshipCompetition ควรมีการตั้งค่าที่ง่ายต่อการเพิกเฉยต่อการละเมิดเวลาเพื่อช่วยในการพัฒนา / แก้ไขจุดบกพร่อง ฉันขอแนะนำให้ตรวจสอบ System.Diagnostics.Process :: UserProcessorTime / Privileged ProcessorTime / TotalProcessorTime เพื่อดูมุมมองที่แม่นยำยิ่งขึ้นว่ามีการใช้เวลาเท่าใด

เรือจม:

API ปัจจุบันแจ้งให้คุณทราบเมื่อคุณจมเรือของฝ่ายตรงข้าม:

ShotHit(Point shot, bool sunk);

แต่ไม่ใช่อันไหนคุณจมเรือ! ฉันคิดว่ามันเป็นส่วนหนึ่งของกฎเรือรบมนุษย์ที่คุณต้องประกาศว่า "คุณจมเรือรบของฉัน!" (หรือเรือพิฆาตหรือย่อย ฯลฯ )

นี่เป็นสิ่งสำคัญโดยเฉพาะอย่างยิ่งเมื่อ AI พยายามล้างเรือที่ชนเข้าหากัน ฉันต้องการขอเปลี่ยนแปลง API เป็น:

ShotHit(Point shot, Ship ship);

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


กรุณาโพสต์ตัวอย่างรหัสถ้าคุณคิดว่าเวลาสามารถทำได้อย่างแม่นยำมากขึ้น ฉันไม่ต้องการเปลี่ยนกฎมากเกินไปในตอนนี้
John Gietzen

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

BUG: @John Gietzen: ฉันได้พิจารณาแล้วว่า PlaceShips ไม่ได้ทำงานหนึ่งครั้งต่อเกม (ตามที่คุณระบุ) หากผู้เล่นวางเรือรบของพวกเขาผิดพลาด (ตามที่ RandomOpponent บ่อยครั้ง) แล้ว PlaceShips จะถูกเรียกซ้ำ ๆ โดยไม่ต้องทำการเรียกตัวเกมใหม่
abelenky

5
ฉันมักจะคิดว่าเป็นกลยุทธ์ในการวางเรือสองลำในรูปแบบ L เพื่อให้คู่ต่อสู้ของฉันคิดว่าพวกเขาจมเรือรบในความเป็นจริงพวกเขาไม่ได้ ฉันไม่เคยรู้สึกว่าคุณต้องประกาศว่าเรือลำใดจม
s, Josh Smeaton

3
@DJ: ฉันทำตามกฎปากกาและกระดาษต้นฉบับ โปรดจำไว้ว่า Hasbro เป็น บริษัท ของเล่นและเกมนี้มีมาก่อน Hasbro
John Gietzen

5

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.IO;
using System.Collections.ObjectModel;

namespace Battleship.ShuggyCoUk
{
    public class Simple : IBattleshipOpponent
    {
        BoardView<OpponentsBoardState> opponentsBoard = new BoardView<OpponentsBoardState>(new Size(10,10));
        Rand rand = new Rand();
        int gridOddEven;
        Size size;

        public string Name { get { return "Simple"; } }

        public Version Version { get { return new Version(2, 1); }}

        public void NewMatch(string opponent) {}

        public void NewGame(System.Drawing.Size size, TimeSpan timeSpan)
        {
            this.size = size;
            this.opponentsBoard = new BoardView<OpponentsBoardState>(size);
            this.gridOddEven = rand.Pick(new[] { 0, 1 });
        }

        public void PlaceShips(System.Collections.ObjectModel.ReadOnlyCollection<Ship> ships)
        {
            BoardView<bool> board = new BoardView<bool>(size);
            var AllOrientations = new[] {
                ShipOrientation.Horizontal,
                ShipOrientation.Vertical };

            foreach (var ship in ships)
            {
                int avoidTouching = 3;
                while (!ship.IsPlaced)
                {
                    var l = rand.Pick(board.Select(c => c.Location));
                    var o = rand.Pick(AllOrientations);
                    if (ship.IsLegal(ships, size, l, o))
                    {
                        if (ship.IsTouching(ships, l, o)&& --avoidTouching > 0)
                            continue;
                        ship.Place(l, o);
                    }
                }
            }
        }
        protected virtual Point PickWhenNoTargets()
        {
            return rand.PickBias(x => x.Bias,
                opponentsBoard
                // nothing 1 in size
                .Where(c => (c.Location.X + c.Location.Y) % 2 == gridOddEven)
                .Where(c => c.Data == OpponentsBoardState.Unknown))
                .Location;
        }

        private int SumLine(Cell<OpponentsBoardState> c, int acc)
        {
            if (acc >= 0)
                return acc;
            if (c.Data == OpponentsBoardState.Hit)
                return acc - 1;
            return -acc;
        }

        public System.Drawing.Point GetShot()
        {
            var targets = opponentsBoard
                .Where(c => c.Data == OpponentsBoardState.Hit)
                .SelectMany(c => c.Neighbours())
                .Where(c => c.Data == OpponentsBoardState.Unknown)
                .ToList();
            if (targets.Count > 1)
            {
                var lines = targets.Where(
                    x => x.FoldAll(-1, SumLine).Select(r => Math.Abs(r) - 1).Max() > 1).ToList();
                if (lines.Count > 0)
                    targets = lines;
            }
            var target = targets.RandomOrDefault(rand);
            if (target == null)
                return PickWhenNoTargets();
            return target.Location;
        }

        public void OpponentShot(System.Drawing.Point shot)
        {
        }

        public void ShotHit(Point shot, bool sunk)
        {
            opponentsBoard[shot] = OpponentsBoardState.Hit;
            Debug(shot, sunk);
        }

        public void ShotMiss(Point shot)
        {
            opponentsBoard[shot] = OpponentsBoardState.Miss;
            Debug(shot, false);
        }

        public const bool DebugEnabled = false;

        public void Debug(Point shot, bool sunk)
        {
            if (!DebugEnabled)
                return;
            opponentsBoard.WriteAsGrid(
                Console.Out,
                x =>
                {
                    string t;
                    switch (x.Data)
                    {
                        case OpponentsBoardState.Unknown:
                            return " ";
                        case OpponentsBoardState.Miss:
                            t = "m";
                            break;
                        case OpponentsBoardState.MustBeEmpty:
                            t = "/";
                            break;
                        case OpponentsBoardState.Hit:
                            t = "x";
                            break;
                        default:
                            t = "?";
                            break;
                    }
                    if (x.Location == shot)
                        t = t.ToUpper();
                    return t;
                });
            if (sunk)
                Console.WriteLine("sunk!");
            Console.ReadLine();
        }

        public void GameWon()
        {
        }

        public void GameLost()
        {
        }

        public void MatchOver()
        {
        }

        #region Library code
        enum OpponentsBoardState
        {
            Unknown = 0,
            Miss,
            MustBeEmpty,
            Hit,
        }

        public enum Compass
        {
            North, East, South, West
        }

        class Cell<T>
        {
            private readonly BoardView<T> view;
            public readonly int X;
            public readonly int Y;
            public T Data;
            public double Bias { get; set; }

            public Cell(BoardView<T> view, int x, int y)
            {
                this.view = view; this.X = x; this.Y = y; this.Bias = 1.0;
            }

            public Point Location
            {
                get { return new Point(X, Y); }
            }

            public IEnumerable<U> FoldAll<U>(U acc, Func<Cell<T>, U, U> trip)
            {
                return new[] { Compass.North, Compass.East, Compass.South, Compass.West }
                    .Select(x => FoldLine(x, acc, trip));
            }

            public U FoldLine<U>(Compass direction, U acc, Func<Cell<T>, U, U> trip)
            {
                var cell = this;
                while (true)
                {
                    switch (direction)
                    {
                        case Compass.North:
                            cell = cell.North; break;
                        case Compass.East:
                            cell = cell.East; break;
                        case Compass.South:
                            cell = cell.South; break;
                        case Compass.West:
                            cell = cell.West; break;
                    }
                    if (cell == null)
                        return acc;
                    acc = trip(cell, acc);
                }
            }

            public Cell<T> North
            {
                get { return view.SafeLookup(X, Y - 1); }
            }

            public Cell<T> South
            {
                get { return view.SafeLookup(X, Y + 1); }
            }

            public Cell<T> East
            {
                get { return view.SafeLookup(X + 1, Y); }
            }

            public Cell<T> West
            {
                get { return view.SafeLookup(X - 1, Y); }
            }

            public IEnumerable<Cell<T>> Neighbours()
            {
                if (North != null)
                    yield return North;
                if (South != null)
                    yield return South;
                if (East != null)
                    yield return East;
                if (West != null)
                    yield return West;
            }
        }

        class BoardView<T> : IEnumerable<Cell<T>>
        {
            public readonly Size Size;
            private readonly int Columns;
            private readonly int Rows;

            private Cell<T>[] history;

            public BoardView(Size size)
            {
                this.Size = size;
                Columns = size.Width;
                Rows = size.Height;
                this.history = new Cell<T>[Columns * Rows];
                for (int y = 0; y < Rows; y++)
                {
                    for (int x = 0; x < Rows; x++)
                        history[x + y * Columns] = new Cell<T>(this, x, y);
                }
            }

            public T this[int x, int y]
            {
                get { return history[x + y * Columns].Data; }
                set { history[x + y * Columns].Data = value; }
            }

            public T this[Point p]
            {
                get { return history[SafeCalc(p.X, p.Y, true)].Data; }
                set { this.history[SafeCalc(p.X, p.Y, true)].Data = value; }
            }

            private int SafeCalc(int x, int y, bool throwIfIllegal)
            {
                if (x < 0 || y < 0 || x >= Columns || y >= Rows)
                {
                    if (throwIfIllegal)
                        throw new ArgumentOutOfRangeException("[" + x + "," + y + "]");
                    else
                        return -1;
                }
                return x + y * Columns;
            }

            public void Set(T data)
            {
                foreach (var cell in this.history)
                    cell.Data = data;
            }

            public Cell<T> SafeLookup(int x, int y)
            {
                int index = SafeCalc(x, y, false);
                if (index < 0)
                    return null;
                return history[index];
            }

            #region IEnumerable<Cell<T>> Members

            public IEnumerator<Cell<T>> GetEnumerator()
            {
                foreach (var cell in this.history)
                    yield return cell;
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return this.GetEnumerator();
            }

            public BoardView<U> Transform<U>(Func<T, U> transform)
            {
                var result = new BoardView<U>(new Size(Columns, Rows));
                for (int y = 0; y < Rows; y++)
                {
                    for (int x = 0; x < Columns; x++)
                    {
                        result[x, y] = transform(this[x, y]);
                    }
                }
                return result;
            }

            public void WriteAsGrid(TextWriter w)
            {
                WriteAsGrid(w, "{0}");
            }

            public void WriteAsGrid(TextWriter w, string format)
            {
                WriteAsGrid(w, x => string.Format(format, x.Data));
            }

            public void WriteAsGrid(TextWriter w, Func<Cell<T>, string> perCell)
            {
                for (int y = 0; y < Rows; y++)
                {
                    for (int x = 0; x < Columns; x++)
                    {
                        if (x != 0)
                            w.Write(",");
                        w.Write(perCell(this.SafeLookup(x, y)));
                    }
                    w.WriteLine();
                }
            }

            #endregion
        }

        public class Rand
        {
            Random r;

            public Rand()
            {
                var rand = System.Security.Cryptography.RandomNumberGenerator.Create();
                byte[] b = new byte[4];
                rand.GetBytes(b);
                r = new Random(BitConverter.ToInt32(b, 0));
            }

            public int Next(int maxValue)
            {
                return r.Next(maxValue);
            }

            public double NextDouble(double maxValue)
            {
                return r.NextDouble() * maxValue;
            }

            public T Pick<T>(IEnumerable<T> things)
            {
                return things.ElementAt(Next(things.Count()));
            }

            public T PickBias<T>(Func<T, double> bias, IEnumerable<T> things)
            {
                double d = NextDouble(things.Sum(x => bias(x)));
                foreach (var x in things)
                {
                    if (d < bias(x))
                        return x;
                    d -= bias(x);
                }
                throw new InvalidOperationException("fell off the end!");
            }
        }
        #endregion
    }

    public static class Extensions
    {
        public static bool IsIn(this Point p, Size size)
        {
            return p.X >= 0 && p.Y >= 0 && p.X < size.Width && p.Y < size.Height;
        }

        public static bool IsLegal(this Ship ship,
            IEnumerable<Ship> ships,
            Size board,
            Point location,
            ShipOrientation direction)
        {
            var temp = new Ship(ship.Length);
            temp.Place(location, direction);
            if (!temp.GetAllLocations().All(p => p.IsIn(board)))
                return false;
            return ships.Where(s => s.IsPlaced).All(s => !s.ConflictsWith(temp));
        }

        public static bool IsTouching(this Point a, Point b)
        {
            return (a.X == b.X - 1 || a.X == b.X + 1) &&
                (a.Y == b.Y - 1 || a.Y == b.Y + 1);
        }

        public static bool IsTouching(this Ship ship,
            IEnumerable<Ship> ships,
            Point location,
            ShipOrientation direction)
        {
            var temp = new Ship(ship.Length);
            temp.Place(location, direction);
            var occupied = new HashSet<Point>(ships
                .Where(s => s.IsPlaced)
                .SelectMany(s => s.GetAllLocations()));
            if (temp.GetAllLocations().Any(p => occupied.Any(b => b.IsTouching(p))))
                return true;
            return false;
        }

        public static ReadOnlyCollection<Ship> MakeShips(params int[] lengths)
        {
            return new System.Collections.ObjectModel.ReadOnlyCollection<Ship>(
                lengths.Select(l => new Ship(l)).ToList());
        }

        public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Battleship.ShuggyCoUk.Simple.Rand rand)
        {
            T[] elements = source.ToArray();
            // Note i > 0 to avoid final pointless iteration
            for (int i = elements.Length - 1; i > 0; i--)
            {
                // Swap element "i" with a random earlier element it (or itself)
                int swapIndex = rand.Next(i + 1);
                T tmp = elements[i];
                elements[i] = elements[swapIndex];
                elements[swapIndex] = tmp;
            }
            // Lazily yield (avoiding aliasing issues etc)
            foreach (T element in elements)
            {
                yield return element;
            }
        }

        public static T RandomOrDefault<T>(this IEnumerable<T> things, Battleship.ShuggyCoUk.Simple.Rand rand)
        {
            int count = things.Count();
            if (count == 0)
                return default(T);
            return things.ElementAt(rand.Next(count));
        }
    }

}


5

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

namespace Battleship
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Linq;

    public class BP7 : IBattleshipOpponent
    {
        public string Name { get { return "BP7"; } }
        public Version Version { get { return this.version; } }

        Random rand = new Random();
        Version version = new Version(0, 7);
        Size gameSize;
        List<Point> scanShots;
        List<NextShot> nextShots;
        int wins, losses;
        int totalWins = 0;
        int totalLosses = 0;
        int maxWins = 0;
        int maxLosses = 0;
        int matchWins = 0;
        int matchLosses = 0;

        public enum Direction { VERTICAL = -1, UNKNOWN = 0, HORIZONTAL = 1 };
        Direction hitDirection, lastShotDirection;

        enum ShotResult { UNKNOWN, MISS, HIT };
        ShotResult[,] board;

        public struct NextShot
        {
            public Point point;
            public Direction direction;
            public NextShot(Point p, Direction d)
            {
                point = p;
                direction = d;
            }
        }

        public struct ScanShot
        {
            public Point point;
            public int openSpaces;
            public ScanShot(Point p, int o)
            {
                point = p;
                openSpaces = o;
            }
        }

        public void NewGame(Size size, TimeSpan timeSpan)
        {
            this.gameSize = size;
            scanShots = new List<Point>();
            nextShots = new List<NextShot>();
            fillScanShots();
            hitDirection = Direction.UNKNOWN;
            board = new ShotResult[size.Width, size.Height];
        }

        private void fillScanShots()
        {
            int x;
            for (x = 0; x < gameSize.Width - 1; x++)
            {
                scanShots.Add(new Point(x, x));
            }

            if (gameSize.Width == 10)
            {
                for (x = 0; x < 3; x++)
                {
                    scanShots.Add(new Point(9 - x, x));
                    scanShots.Add(new Point(x, 9 - x));
                }
            }
        }

        public void PlaceShips(System.Collections.ObjectModel.ReadOnlyCollection<Ship> ships)
        {
            foreach (Ship s in ships)
            {
                s.Place(
                    new Point(
                        rand.Next(this.gameSize.Width),
                        rand.Next(this.gameSize.Height)),
                    (ShipOrientation)rand.Next(2));
            }
        }

        public Point GetShot()
        {
            Point shot;

            if (this.nextShots.Count > 0)
            {
                if (hitDirection != Direction.UNKNOWN)
                {
                    if (hitDirection == Direction.HORIZONTAL)
                    {
                        this.nextShots = this.nextShots.OrderByDescending(x => x.direction).ToList();
                    }
                    else
                    {
                        this.nextShots = this.nextShots.OrderBy(x => x.direction).ToList();
                    }
                }

                shot = this.nextShots.First().point;
                lastShotDirection = this.nextShots.First().direction;
                this.nextShots.RemoveAt(0);
                return shot;
            }

            List<ScanShot> scanShots = new List<ScanShot>();
            for (int x = 0; x < gameSize.Width; x++)
            {
                for (int y = 0; y < gameSize.Height; y++)
                {
                    if (board[x, y] == ShotResult.UNKNOWN)
                    {
                        scanShots.Add(new ScanShot(new Point(x, y), OpenSpaces(x, y)));
                    }
                }
            }
            scanShots = scanShots.OrderByDescending(x => x.openSpaces).ToList();
            int maxOpenSpaces = scanShots.FirstOrDefault().openSpaces;

            List<ScanShot> scanShots2 = new List<ScanShot>();
            scanShots2 = scanShots.Where(x => x.openSpaces == maxOpenSpaces).ToList();
            shot = scanShots2[rand.Next(scanShots2.Count())].point;

            return shot;
        }

        int OpenSpaces(int x, int y)
        {
            int ctr = 0;
            Point p;

            // spaces to the left
            p = new Point(x - 1, y);
            while (p.X >= 0 && board[p.X, p.Y] == ShotResult.UNKNOWN)
            {
                ctr++;
                p.X--;
            }

            // spaces to the right
            p = new Point(x + 1, y);
            while (p.X < gameSize.Width && board[p.X, p.Y] == ShotResult.UNKNOWN)
            {
                ctr++;
                p.X++;
            }

            // spaces to the top
            p = new Point(x, y - 1);
            while (p.Y >= 0 && board[p.X, p.Y] == ShotResult.UNKNOWN)
            {
                ctr++;
                p.Y--;
            }

            // spaces to the bottom
            p = new Point(x, y + 1);
            while (p.Y < gameSize.Height && board[p.X, p.Y] == ShotResult.UNKNOWN)
            {
                ctr++;
                p.Y++;
            }

            return ctr;
        }

        public void NewMatch(string opponenet)
        {
            wins = 0;
            losses = 0;
        }

        public void OpponentShot(Point shot) { }

        public void ShotHit(Point shot, bool sunk)
        {
            board[shot.X, shot.Y] = ShotResult.HIT;

            if (!sunk)
            {
                hitDirection = lastShotDirection;
                if (shot.X != 0)
                {
                    this.nextShots.Add(new NextShot(new Point(shot.X - 1, shot.Y), Direction.HORIZONTAL));
                }

                if (shot.Y != 0)
                {
                    this.nextShots.Add(new NextShot(new Point(shot.X, shot.Y - 1), Direction.VERTICAL));
                }

                if (shot.X != this.gameSize.Width - 1)
                {
                    this.nextShots.Add(new NextShot(new Point(shot.X + 1, shot.Y), Direction.HORIZONTAL));
                }

                if (shot.Y != this.gameSize.Height - 1)
                {
                    this.nextShots.Add(new NextShot(new Point(shot.X, shot.Y + 1), Direction.VERTICAL));
                }
            }
            else
            {
                hitDirection = Direction.UNKNOWN;
                this.nextShots.Clear();     // so now this works like gangbusters ?!?!?!?!?!?!?!?!?
            }
        }

        public void ShotMiss(Point shot)
        {
            board[shot.X, shot.Y] = ShotResult.MISS;
        }

        public void GameWon()
        {
            wins++;
        }

        public void GameLost()
        {
            losses++;
        }

        public void MatchOver()
        {
            if (wins > maxWins)
            {
                maxWins = wins;
            }

            if (losses > maxLosses)
            {
                maxLosses = losses;
            }

            totalWins += wins;
            totalLosses += losses;

            if (wins >= 51)
            {
                matchWins++;
            }
            else
            {
                matchLosses++;
            }
        }

        public void FinalStats()
        {
            Console.WriteLine("Games won: " + totalWins.ToString());
            Console.WriteLine("Games lost: " + totalLosses.ToString());
            Console.WriteLine("Game winning percentage: " + (totalWins * 1.0 / (totalWins + totalLosses)).ToString("P"));
            Console.WriteLine("Game losing percentage: " + (totalLosses * 1.0 / (totalWins + totalLosses)).ToString("P"));
            Console.WriteLine();
            Console.WriteLine("Matches won: " + matchWins.ToString());
            Console.WriteLine("Matches lost: " + matchLosses.ToString());
            Console.WriteLine("Match winning percentage: " + (matchWins * 1.0 / (matchWins + matchLosses)).ToString("P"));
            Console.WriteLine("Match losing percentage: " + (matchLosses * 1.0 / (matchWins + matchLosses)).ToString("P"));
            Console.WriteLine("Match games won high: " + maxWins.ToString());
            Console.WriteLine("Match games lost high: " + maxLosses.ToString());
            Console.WriteLine();
        }
    }
}

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


5

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

namespace Battleship
{
    using System;
    using System.Collections.ObjectModel;
    using System.Drawing;
    using System.Collections.Generic;
    using System.Linq;

    public class BSKiller4 : OpponentExtended, IBattleshipOpponent
    {
        public string Name { get { return "BSKiller4"; } }
        public Version Version { get { return this.version; } }

        public bool showBoard = false;

        Random rand = new Random();
        Version version = new Version(0, 4);
        Size gameSize;

        List<Point> nextShots;
        Queue<Point> scanShots;

        char[,] board;

        private void printBoard()
        {
            Console.WriteLine();
            for (int y = 0; y < this.gameSize.Height; y++)
            {
                for (int x = 0; x < this.gameSize.Width; x++)
                {
                    Console.Write(this.board[x, y]);
                }
                Console.WriteLine();
            }
            Console.ReadKey();
        }

        public void NewGame(Size size, TimeSpan timeSpan)
        {
            this.gameSize = size;
            board = new char[size.Width, size.Height];
            this.nextShots = new List<Point>();
            this.scanShots = new Queue<Point>();
            fillScanShots();
            initializeBoard();
        }

        private void initializeBoard()
        {
            for (int y = 0; y < this.gameSize.Height; y++)
            {
                for (int x = 0; x < this.gameSize.Width; x++)
                {
                    this.board[x, y] = 'O';
                }
            }
        }

        private void fillScanShots()
        {
            int x, y;
            int num = gameSize.Width * gameSize.Height;
            for (int j = 0; j < 3; j++)
            {
                for (int i = j; i < num; i += 3)
                {
                    x = i % gameSize.Width;
                    y = i / gameSize.Height;
                    scanShots.Enqueue(new Point(x, y));
                }
            }
        }

        public void PlaceShips(ReadOnlyCollection<Ship> ships)
        {
            foreach (Ship s in ships)
            {
                s.Place(new Point(
                        rand.Next(this.gameSize.Width),
                        rand.Next(this.gameSize.Height)),
                        (ShipOrientation)rand.Next(2));
            }
        }

        public Point GetShot()
        {
            if (showBoard) printBoard();
            Point shot;

            shot = findShotRun();
            if (shot.X != -1)
            {
                return shot;
            }

            if (this.nextShots.Count > 0)
            {
                shot = this.nextShots[0];
                this.nextShots.RemoveAt(0);
            }
            else
            {
                shot = this.scanShots.Dequeue();
            }

            return shot;
        }

        public void ShotHit(Point shot, bool sunk)
        {
            this.board[shot.X, shot.Y] = 'H';
            if (!sunk)
            {
                addToNextShots(new Point(shot.X - 1, shot.Y));
                addToNextShots(new Point(shot.X, shot.Y + 1));
                addToNextShots(new Point(shot.X + 1, shot.Y));
                addToNextShots(new Point(shot.X, shot.Y - 1));
            }
            else
            {
                this.nextShots.Clear();
            }
        }



        private Point findShotRun()
        {
            int run_forward_horizontal = 0;
            int run_backward_horizontal = 0;
            int run_forward_vertical = 0;
            int run_backward_vertical = 0;

            List<shotPossibilities> possible = new List<shotPossibilities>(5);

            // this only works if width = height for the board;
            for (int y = 0; y < this.gameSize.Height; y++)
            {
                for (int x = 0; x < this.gameSize.Width; x++)
                {
                    // forward horiz
                    if (this.board[x, y] == 'M')
                    {
                        run_forward_horizontal = 0;
                    }
                    else if (this.board[x, y] == 'O')
                    {
                        if (run_forward_horizontal >= 2)
                        {
                            possible.Add(
                                new shotPossibilities(
                                    run_forward_horizontal,
                                    new Point(x, y),
                                    true));
                        }
                        else
                        {
                            run_forward_horizontal = 0;
                        }
                    }
                    else
                    {
                        run_forward_horizontal++;
                    }

                    // forward vertical
                    if (this.board[y, x] == 'M')
                    {
                        run_forward_vertical = 0;
                    }
                    else if (this.board[y, x] == 'O')
                    {
                        if (run_forward_vertical >= 2)
                        {
                            possible.Add(
                                new shotPossibilities(
                                    run_forward_vertical,
                                    new Point(y, x),
                                    false));
                        }
                        else
                        {
                            run_forward_vertical = 0;
                        }
                    }
                    else
                    {
                        run_forward_vertical++;
                    }


                    // backward horiz
                    if (this.board[this.gameSize.Width - x - 1, y] == 'M')
                    {
                        run_backward_horizontal = 0;
                    }
                    else if (this.board[this.gameSize.Width - x - 1, y] == 'O')
                    {
                        if (run_backward_horizontal >= 2)
                        {
                            possible.Add(
                                new shotPossibilities(
                                    run_backward_horizontal,
                                    new Point(this.gameSize.Width - x - 1, y),
                                    true));
                        }
                        else
                        {
                            run_backward_horizontal = 0;
                        }
                    }
                    else
                    {
                        run_backward_horizontal++;
                    }


                    // backward vertical
                    if (this.board[y, this.gameSize.Height - x - 1] == 'M')
                    {
                        run_backward_vertical = 0;
                    }
                    else if (this.board[y, this.gameSize.Height - x - 1] == 'O')
                    {
                        if (run_backward_vertical >= 2)
                        {
                            possible.Add(
                                new shotPossibilities(
                                    run_backward_vertical,
                                    new Point(y, this.gameSize.Height - x - 1),
                                    false));
                        }
                        else
                        {
                            run_backward_vertical = 0;
                        }
                    }
                    else
                    {
                        run_backward_vertical++;
                    }

                }

                run_forward_horizontal = 0;
                run_backward_horizontal = 0;
                run_forward_vertical = 0;
                run_backward_vertical = 0;
            }
            Point shot;

            if (possible.Count > 0)
            {
                shotPossibilities shotp = possible.OrderByDescending(a => a.run).First();
                //this.nextShots.Clear();
                shot = shotp.shot;
                //if (shotp.isHorizontal)
                //{
                //    this.nextShots.RemoveAll(p => p.X != shot.X);
                //}
                //else
                //{
                //    this.nextShots.RemoveAll(p => p.Y != shot.Y);
                //}
            }
            else
            {
                shot = new Point(-1, -1);
            }

            return shot;
        }

        private void addToNextShots(Point p)
        {
            if (!this.nextShots.Contains(p) &&
                p.X >= 0 &&
                p.X < this.gameSize.Width &&
                p.Y >= 0 &&
                p.Y < this.gameSize.Height)
            {
                if (this.board[p.X, p.Y] == 'O')
                {
                    this.nextShots.Add(p);
                }
            }
        }

        public void GameWon()
        {
            this.GameWins++;
        }

        public void NewMatch(string opponent)
        {
            System.Threading.Thread.Sleep(5);
            this.rand = new Random(System.Environment.TickCount);
        }
        public void OpponentShot(Point shot) { }
        public void ShotMiss(Point shot)
        {
            this.board[shot.X, shot.Y] = 'M';
        }
        public void GameLost()
        {
            if (showBoard) Console.WriteLine("-----Game Over-----");
        }
        public void MatchOver() { }
    }


    public class OpponentExtended
    {
        public int GameWins { get; set; }
        public int MatchWins { get; set; }
        public OpponentExtended() { }
    }

    public class shotPossibilities
    {
        public shotPossibilities(int r, Point s, bool h)
        {
            this.run = r;
            this.shot = s;
            this.isHorizontal = h;
        }
        public int run { get; set; }
        public Point shot { get; set; }
        public bool isHorizontal { get; set; }
    }
}

2
ขอแสดงความยินดีกับเงิน คุณคิดที่จะอธิบายอัลกอริทึมของคุณเป็นคำพูดหรือไม่? มันน่าสนใจที่จะรู้เกี่ยวกับ
โทมัส Ahle

4

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

ฝ่ายตรงข้ามนี้มีพฤติกรรมที่คล้ายกัน (การกระจายตำแหน่งที่มีประสิทธิภาพเหมือนกัน) มันแค่ทำการตรวจสอบความมีสติและใช้การสร้างหมายเลขสุ่มเพียงครั้งเดียวต่อการโทรหนึ่งครั้ง (ตัดจำหน่าย)

สิ่งนี้ใช้คลาสในคำตอบส่วนขยาย / ไลบรารีของฉันและฉันจัดหาวิธี / รัฐที่สำคัญเท่านั้น

Shuffle ถูกยกขึ้นจากคำตอบของ Jon Skeet ที่นี่

class WellBehavedRandomOpponent : IBattleShipOpponent
{
    Rand rand = new Rand();
    List<Point> guesses;
    int nextGuess = 0;

    public void PlaceShips(IEnumerable<Ship> ships)
    {
        BoardView<bool> board = new BoardView<bool>(BoardSize);
        var AllOrientations = new[] {
            ShipOrientation.Horizontal,
            ShipOrientation.Vertical };

        foreach (var ship in ships)
        {
            while (!ship.IsPlaced)
            {
                var l = rand.Pick(board.Select(c => c.Location));
                var o = rand.Pick(AllOrientations);
                if (ship.IsLegal(ships, BoardSize, l, o))
                    ship.Place(l, o);
            }
        }
    }

    public void NewGame(Size size, TimeSpan timeSpan)
    {
        var board = new BoardView<bool>(size);
        this.guesses = new List<Point>(
            board.Select(x => x.Location).Shuffle(rand));
        nextGuess = 0;
    }

    public System.Drawing.Point GetShot()
    {
        return guesses[nextGuess++];
    }

    // empty methods left out 
}

4

ฉันจะไม่สามารถมีส่วนร่วมได้ แต่นี่เป็นอัลกอริทึมที่ฉันใช้ถ้ามีเวลา:

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

  1. กดปุ่มกลาง (ดูบันทึกย่อสุดท้ายด้านล่าง - 'ศูนย์กลาง' เป็นเพียงคำอธิบายที่สะดวก)
  2. กดจุดที่ 4 ทางด้านขวาของศูนย์
  3. ตีจุดที่ 1 ลงและหนึ่งทางด้านขวาของศูนย์
  4. กดจุดที่สี่ไปทางขวาของ Hit ก่อนหน้า
  5. ดำเนินการต่อในรูปแบบนั้น (ควรลงท้ายด้วยเส้นทแยงมุมคั่นด้วย 3 ช่องว่างเติมบอร์ด) สิ่งนี้น่าจะชนกับเรือยาว 4 และ 5 ลำและเรือ 3 และ 2 ลำที่มีสถิติขนาดใหญ่

  6. เริ่มต้นการสุ่มกดจุดระหว่าง diagonals นี้จะจับเรือยาว 2 และ 3 ที่ยังไม่ได้สังเกตเห็น

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

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

หมายเหตุสุดท้าย:

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

สิ่งนี้อธิบายอัลกอริทึม 'สมบูรณ์แบบ' ว่าจะให้เรือรบทุกลำเข้ามาใน (9x9) / 2 + 10 นัด

อย่างไรก็ตามสามารถปรับปรุงได้อย่างมีนัยสำคัญ:

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

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

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

อดัม


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

4

รายการของฉัน

ไม่มีอะไรพิเศษเป็นอย่างมากและฉันไม่มีเวลาเพิ่มความคิดที่ดีทั้งหมดที่ฉันมี

แต่ดูเหมือนว่าจะเล่นค่อนข้างดี เราจะดูว่ามันมีการแข่งขันอย่างไร:

(ใส่ไว้ในไฟล์ Missouri.csและเพิ่มในโครงการ)

using System;
using System.Collections.ObjectModel;
using System.Drawing;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;

namespace Battleship
{
    // The Empire of Japan surrendered on the deck of the USS Missouri on Sept. 2, 1945
    public class USSMissouri : IBattleshipOpponent
    {
        public String  Name    { get { return name; } }
        public Version Version { get { return ver;  } }

#region IBattleship Interface
        // IBattleship::NewGame
        public void NewGame(Size gameSize, TimeSpan timeSpan)
        {
            size      = gameSize;
            shotBoard = new ShotBoard(size);
            attackVector = new Stack<Attack>();
        }

        // IBattleship::PlaceShips
        public void PlaceShips(ReadOnlyCollection<Ship> ships)
        {
            HunterBoard board;
            targetBoards = new List<HunterBoard>();
            shotBoard    = new ShotBoard(size);
            foreach (Ship s in ships)
            {
                board = new HunterBoard(this, size, s);
                targetBoards.Add(board);

                // REWRITE: to ensure valid board placement.
                s.Place(
                    new Point(
                        rand.Next(size.Width),
                        rand.Next(size.Height)),
                    (ShipOrientation)rand.Next(2));
            }
        }

        // IBattleship::GetShot
        public Point GetShot()
        {
            Point p = new Point();

            if (attackVector.Count() > 0)
            {
                p = ExtendShot();
                return p;
            }

            // Contemplate a shot at every-single point, and measure how effective it would be.
            Board potential = new Board(size);
            for(p.Y=0; p.Y<size.Height; ++p.Y)
            {
                for(p.X=0; p.X<size.Width; ++p.X)
                {
                    if (shotBoard.ShotAt(p))
                    {
                        potential[p] = 0;
                        continue;
                    }

                    foreach(HunterBoard b in targetBoards)
                    {
                        potential[p] += b.GetWeightAt(p);
                    }
                }
            }

            // Okay, we have the shot potential of the board.
            // Lets pick a weighted-random spot.
            Point shot;
            shot = potential.GetWeightedRandom(rand.NextDouble());

            shotBoard[shot] = Shot.Unresolved;

            return shot;
        }

        public Point ExtendShot()
        {
            // Lets consider North, South, East, and West of the current shot.
            // and measure the potential of each
            Attack attack = attackVector.Peek();

            Board potential = new Board(size);

            Point[] points = attack.GetNextTargets();
            foreach(Point p in points)
            {
                if (shotBoard.ShotAt(p))
                {
                    potential[p] = 0;
                    continue;
                }

                foreach(HunterBoard b in targetBoards)
                {
                    potential[p] += b.GetWeightAt(p);
                }
            }

            Point shot = potential.GetBestShot();
            shotBoard[shot] = Shot.Unresolved;
            return shot;
        }

        // IBattleship::NewMatch
        public void NewMatch(string opponent)
        {
        }
        public void OpponentShot(Point shot)
        {
        }
        public void ShotHit(Point shot, bool sunk)
        {
            shotBoard[shot] = Shot.Hit;

            if (!sunk)
            {
                if (attackVector.Count == 0) // This is a first hit, open an attackVector
                {   
                    attackVector.Push(new Attack(this, shot));
                }
                else
                {
                    attackVector.Peek().AddHit(shot);    // Add a hit to our current attack.
                }
            }

            // What if it is sunk?  Close the top attack, which we've been pursuing.
            if (sunk)
            {
                if (attackVector.Count > 0)
                {
                    attackVector.Pop();
                }
            }
        }
        public void ShotMiss(Point shot)
        {
            shotBoard[shot] = Shot.Miss;

            foreach(HunterBoard b in targetBoards)
            {
                b.ShotMiss(shot);  // Update the potential map.
            }
        }
        public void GameWon()
        {
            Trace.WriteLine  ("I won the game!");
        }
        public void GameLost()
        {
            Trace.WriteLine  ("I lost the game!");
        }
        public void MatchOver()
        {
            Trace.WriteLine("This match is over.");
        }

#endregion 

        public ShotBoard theShotBoard
        {
            get { return shotBoard; }
        }
        public Size theBoardSize
        {
            get { return size; }
        }

        private Random rand = new Random();
        private Version ver = new Version(6, 3); // USS Missouri is BB-63, hence version 6.3
        private String name = "USS Missouri (abelenky@alum.mit.edu)";
        private Size size;
        private List<HunterBoard> targetBoards;
        private ShotBoard shotBoard;
        private Stack<Attack> attackVector;
    }

    // An Attack is the data on the ship we are currently working on sinking.
    // It consists of a set of points, horizontal and vertical, from a central point.
    // And can be extended in any direction.
    public class Attack
    {
        public Attack(USSMissouri root, Point p)
        {
            Player = root;
            hit = p;
            horzExtent = new Extent(p.X, p.X);
            vertExtent = new Extent(p.Y, p.Y);
        }

        public Extent HorizontalExtent
        {
            get { return horzExtent; }
        }
        public Extent VerticalExtent
        {
            get { return vertExtent; }
        }
        public Point  FirstHit
        {
            get { return hit; }
        }

        public void AddHit(Point p)
        {
            if (hit.X == p.X) // New hit in the vertical direction
            {
                vertExtent.Min = Math.Min(vertExtent.Min, p.Y);
                vertExtent.Max = Math.Max(vertExtent.Max, p.Y);
            }
            else if (hit.Y == p.Y)
            {
                horzExtent.Min = Math.Min(horzExtent.Min, p.X);
                horzExtent.Max = Math.Max(horzExtent.Max, p.X);
            }
        }
        public Point[] GetNextTargets() 
        {
            List<Point> bors = new List<Point>();

            Point p;

            p = new Point(hit.X, vertExtent.Min-1);
            while (p.Y >= 0 && Player.theShotBoard[p] == Shot.Hit)
            {
                if (Player.theShotBoard[p] == Shot.Miss)
                {
                    break; // Don't add p to the List 'bors.  
                }
                --p.Y;
            }
            if (p.Y >= 0 && Player.theShotBoard[p] == Shot.None) // Add next-target only if there is no shot here yet.
            {
                bors.Add(p);
            }

            //-------------------

            p = new Point(hit.X, vertExtent.Max+1);
            while (p.Y < Player.theBoardSize.Height && Player.theShotBoard[p] == Shot.Hit)
            {
                if (Player.theShotBoard[p] == Shot.Miss)
                {
                    break; // Don't add p to the List 'bors.  
                }
                ++p.Y;
            }
            if (p.Y < Player.theBoardSize.Height && Player.theShotBoard[p] == Shot.None)
            {
                bors.Add(p);
            }

            //-------------------

            p = new Point(horzExtent.Min-1, hit.Y);
            while (p.X >= 0 && Player.theShotBoard[p] == Shot.Hit)
            {
                if (Player.theShotBoard[p] == Shot.Miss)
                {
                    break; // Don't add p to the List 'bors.  
                }
                --p.X;
            }
            if (p.X >= 0 && Player.theShotBoard[p] == Shot.None)
            {
                bors.Add(p);
            }

            //-------------------

            p = new Point(horzExtent.Max+1, hit.Y);
            while (p.X < Player.theBoardSize.Width && Player.theShotBoard[p] == Shot.Hit)
            {
                if (Player.theShotBoard[p] == Shot.Miss)
                {
                    break; // Don't add p to the List 'bors.  
                }
                ++p.X;
            }
            if (p.X < Player.theBoardSize.Width && Player.theShotBoard[p] == Shot.None)
            {
                bors.Add(p);
            }

            return bors.ToArray();
        }

        private Point hit; 
        private Extent horzExtent;
        private Extent vertExtent;
        private USSMissouri Player;
    }

    public struct Extent
    {
        public Extent(Int32 min, Int32 max)
        {
            Min = min;
            Max = max;
        }
        public Int32 Min;
        public Int32 Max;
    }

    public class Board  // The potential-Board, which measures the full potential of each square.
    {
        // A Board is the status of many things.
        public Board(Size boardsize)
        {
            size = boardsize;
            grid = new int[size.Width , size.Height];
            Array.Clear(grid,0,size.Width*size.Height);
        }

        public int this[int c,int r]
        {
            get { return grid[c,r];  }
            set { grid[c,r] = value; }
        }
        public int this[Point p]
        {
            get { return grid[p.X, p.Y];  }
            set { grid[p.X, p.Y] = value; }
        }

        public Point GetWeightedRandom(double r)
        {
            Int32 sum = 0;
            foreach(Int32 i in grid)
            {
                sum += i;
            }

            Int32 index = (Int32)(r*sum);

            Int32 x=0, y=0;
            for(y=0; y<size.Height; ++y)
            {
                for(x=0; x<size.Width; ++x)
                {
                    if (grid[x,y] == 0) continue; // Skip any zero-cells
                    index -= grid[x,y];
                    if (index < 0) break;
                }
                if (index < 0) break;
            }

            if (x == 10 || y == 10)
                throw new Exception("WTF");

            return new Point(x,y);
        }

        public Point GetBestShot()
        {
            int max=grid[0,0];
            for(int y=0; y<size.Height; ++y)
            {
                for (int x=0; x<size.Width; ++x)
                {
                    max = (grid[x,y] > max)? grid[x,y] : max;
                }
            }

            for(int y=0; y<size.Height; ++y)
            {
                for (int x=0; x<size.Width; ++x)
                {
                    if (grid[x,y] == max)
                    {
                        return new Point(x,y);
                    }
                }
            }
            return new Point(0,0);
        }

        public bool IsZero()
        {
            foreach(Int32 p in grid)
            {
                if (p > 0)
                {
                    return false;
                }
            }
            return true;
        }

        public override String ToString()
        {
            String output = "";
            String horzDiv = "   +----+----+----+----+----+----+----+----+----+----+\n";
            String disp;
            int x,y;

            output += "      A    B    C    D    E    F    G    H    I    J    \n" + horzDiv;

            for(y=0; y<size.Height; ++y)
            {
                output += String.Format("{0} ", y+1).PadLeft(3);
                for(x=0; x<size.Width; ++x)
                {
                    switch(grid[x,y])
                    {
                        case (int)Shot.None:       disp = "";  break;
                        case (int)Shot.Hit:        disp = "#"; break;
                        case (int)Shot.Miss:       disp = "."; break;
                        case (int)Shot.Unresolved: disp = "?"; break;
                        default:                   disp = "!"; break;
                    }

                    output += String.Format("| {0} ", disp.PadLeft(2));
                }
                output += "|\n" + horzDiv;
            }

            return output;
        }

        protected Int32[,] grid;
        protected Size     size;
    }

    public class HunterBoard
    {
        public HunterBoard(USSMissouri root, Size boardsize, Ship target)
        {
            size = boardsize;
            grid = new int[size.Width , size.Height];
            Array.Clear(grid,0,size.Width*size.Height);

            Player = root;
            Target = target;
            Initialize();
        }

        public void Initialize()
        {
            int x, y, i;

            for(y=0; y<size.Height; ++y)
            {
                for(x=0; x<size.Width - Target.Length+1; ++x)
                {
                    for(i=0; i<Target.Length; ++i)
                    {
                        grid[x+i,y]++;
                    }
                }
            }

            for(y=0; y<size.Height-Target.Length+1; ++y)
            {
                for(x=0; x<size.Width; ++x)
                {
                    for(i=0; i<Target.Length; ++i)
                    {
                        grid[x,y+i]++;
                    }
                }
            }
        }

        public int this[int c,int r]
        {
            get { return grid[c,r];  }
            set { grid[c,r] = value; }
        }
        public int this[Point p]
        {
            get { return grid[p.X, p.Y];  }
            set { grid[p.X, p.Y] = value; }
        }

        public void ShotMiss(Point p)
        {
            int x,y;
            int min, max;

            min = Math.Max(p.X-Target.Length+1, 0);
            max = Math.Min(p.X, size.Width-Target.Length);
            for(x=min; x<=max; ++x)
            {
                DecrementRow(p.Y, x, x+Target.Length-1);
            }

            min = Math.Max(p.Y-Target.Length+1, 0);
            max = Math.Min(p.Y, size.Height-Target.Length);
            for(y=min; y<=max; ++y)
            {
                DecrementColumn(p.X, y, y+Target.Length-1);
            } 

            grid[p.X, p.Y] = 0;
        }

        public void ShotHit(Point p)
        {
        }

        public override String ToString()
        {
            String output = String.Format("Target size is {0}\n", Target.Length);
            String horzDiv = "   +----+----+----+----+----+----+----+----+----+----+\n";
            int x,y;

            output += "      A    B    C    D    E    F    G    H    I    J    \n" + horzDiv;
            for(y=0; y<size.Height; ++y)
            {
                output += String.Format("{0} ", y+1).PadLeft(3);
                for(x=0; x<size.Width; ++x)
                {
                    output += String.Format("| {0} ", grid[x,y].ToString().PadLeft(2));
                }
                output += "|\n" + horzDiv;
            }
            return output;
        }

        // If we shoot at point P, how does that affect the potential of the board?
        public Int32 GetWeightAt(Point p)
        {
            int x,y;
            int potential = 0;
            int min, max;

            min = Math.Max(p.X-Target.Length+1, 0);
            max = Math.Min(p.X, size.Width-Target.Length);
            for(x=min; x<=max; ++x)
            {
                if (Player.theShotBoard.isMissInRow(p.Y, x, x+Target.Length-1) == false)
                {
                    ++potential;
                }
            }

            min = Math.Max(p.Y-Target.Length+1, 0);
            max = Math.Min(p.Y, size.Height-Target.Length);
            for(y=min; y<=max; ++y)
            {
                if (Player.theShotBoard.isMissInColumn(p.X, y, y+Target.Length-1) == false)
                {
                    ++potential;
                }
            } 

            return potential;
        }

        public void DecrementRow(int row, int rangeA, int rangeB)
        {
            int x;
            for(x=rangeA; x<=rangeB; ++x)
            {
                grid[x,row] = (grid[x,row]==0)? 0 : grid[x,row]-1;
            }
        }
        public void DecrementColumn(int col, int rangeA, int rangeB)
        {
            int y;
            for(y=rangeA; y<=rangeB; ++y)
            {
                grid[col,y] = (grid[col,y]==0)? 0 : grid[col,y]-1;
            }
        }

        private Ship Target = null;
        private USSMissouri Player;
        private Int32[,] grid;
        private Size     size;
    }

    public enum Shot
    {
        None = 0,
        Hit = 1,
        Miss = 2,
        Unresolved = 3
    };

    public class ShotBoard
    {
        public ShotBoard(Size boardsize)
        {
            size = boardsize;
            grid = new Shot[size.Width , size.Height];

            for(int y=0; y<size.Height; ++y)
            {
                for(int x=0; x<size.Width; ++x)
                {
                    grid[x,y] = Shot.None;
                }
            }
        }

        public Shot this[int c,int r]
        {
            get { return grid[c,r];  }
            set { grid[c,r] = value; }
        }
        public Shot this[Point p]
        {
            get { return grid[p.X, p.Y];  }
            set { grid[p.X, p.Y] = value; }
        }

        public override String ToString()
        {
            String output = "";
            String horzDiv = "   +----+----+----+----+----+----+----+----+----+----+\n";
            String disp;
            int x,y;

            output += "      A    B    C    D    E    F    G    H    I    J    \n" + horzDiv;

            for(y=0; y<size.Height; ++y)
            {
                output += String.Format("{0} ", y+1).PadLeft(3);
                for(x=0; x<size.Width; ++x)
                {
                    switch(grid[x,y])
                    {
                        case Shot.None:       disp = "";  break;
                        case Shot.Hit:        disp = "#"; break;
                        case Shot.Miss:       disp = "."; break;
                        case Shot.Unresolved: disp = "?"; break;
                        default:              disp = "!"; break;
                    }

                    output += String.Format("| {0} ", disp.PadLeft(2));
                }
                output += "|\n" + horzDiv;
            }
            return output;
        }

        // Functions to find shots on the board, at a specific point, or in a row or column, within a range
        public bool ShotAt(Point p)
        {
            return !(this[p]==Shot.None);
        }
        public bool isMissInColumn(int col, int rangeA, int rangeB)
        {
            for(int y=rangeA; y<=rangeB; ++y)
            {
                if (grid[col,y] == Shot.Miss)
                {
                    return true;
                }
            }
            return false;
        }
        public bool isMissInRow(int row, int rangeA, int rangeB)
        {
            for(int x=rangeA; x<=rangeB; ++x)
            {
                if (grid[x,row] == Shot.Miss)
                {
                    return true;
                }
            }
            return false;
        }
        protected Shot[,] grid;
        protected Size     size;
    }
}

และตอนนี้ฉันได้ส่งผลงานของฉันสถิติคร่าวๆ: vs. BP7 44% ชนะ / เทียบกับจต์ต้องชนะ 20% / vs. Farnsworth ชนะ 42% มันเป็นโครงการที่สนุก
abelenky

2

นี่ไม่ใช่มินิแมกซ์ ที่จริงหลังจากวางเรือแล้วผู้เล่นแต่ละคนไม่สามารถเล่นด้วยตัวเองได้ทำให้เกิดการหมุนหลายครั้งทำให้เขาต้องจมเรือฝ่ายตรงข้ามทุกลำ? คนที่ชนะน้อยลง

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

แน่นอนว่าอาจมีกลยุทธ์เคาน์เตอร์สำหรับสิ่งที่ไม่สุ่ม แต่ฉันไม่คิดว่ามีกลยุทธ์ที่ดีต่อผู้เล่นที่เป็นไปได้ทั้งหมด


1
อาจเป็นไปได้ว่าพวกเขาสามารถเล่นได้ด้วยตัวเอง นั่นไม่ใช่วิธีการนี้จะถูกเรียกใช้ ความคิดที่ดีแม้ว่า ในการแข่งขันครั้งนี้ฉันต้องการให้มันเป็นไปได้ที่จะหลีกเลี่ยงสถิติการยิงของคู่ต่อสู้ของคุณ
John Gietzen

2
ฉันเห็น. การใช้ข้อมูลจากเกมก่อนหน้านี้กับคู่ต่อสู้คนเดียวกันอาจทำให้เขาปรับตัวได้หรือไม่?
ziggystar

2

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

นอกจากนี้ฉันคิดว่ามันจะเจ๋งกว่าถ้าคุณระบุเกมเป็นโปรโตคอลเครือข่ายแล้วกำหนดกรอบการใช้โปรโตคอลนั้นใน C # แทนที่จะบอกว่าโซลูชันทั้งหมดควรเป็น C # แต่นั่นเป็นเพียงความคิดของฉัน

แก้ไข: ฉันยกเลิกจุดเริ่มต้นของฉันเนื่องจากฉันไม่ได้อ่านกฎการแข่งขันอย่างรอบคอบเพียงพอ


ไม่ใช่ทุกโซลูชั่นที่จะต้องอยู่ใน C # ฉันสามารถรวบรวมและลิงก์ในชุดประกอบแยกกันได้ นอกจากนี้คุณควรจะสามารถตอบโต้สถิติของฝ่ายตรงข้ามได้
John Gietzen

J #? อาจจะ? ฮ่า ๆ jk ฉันมีโครงร่าง TCP สำหรับสิ่งนี้ แต่การแข่งขันครั้งนี้ต้องดำเนินการอย่างรวดเร็ว
John Gietzen

ทำไมคุณถึงคิดว่าการสื่อสาร TCP ระหว่างสองกระบวนการในเครื่องเดียวกันจะไม่เร็วอย่างเห็นได้ชัด
Jherico

@Jherico: ถ้าฉันใช้ TCP ฉันจะแยกเครื่องยนต์ออกจากพีซีของตัวเองเพื่อที่พวกเขาจะได้ใช้ทรัพยากร CPU ที่พวกเขาต้องการ
John Gietzen

ดังนั้นแม้สองเครื่องใน LAN เดียวกันได้อย่างง่ายดายสามารถจบเกมในภายใต้สองกับเครือข่ายค่าใช้จ่ายในการเป็นน้อย
Jherico

2

ฉันชอบเริ่มต้นตรงกลางและวนห่างจากจุดหนึ่งทำให้ไม่ต้องเว้นที่ว่างมากกว่า 1 จุดระหว่างจุดอื่นใดเพื่ออธิบายว่าย่อย goddam นั้น ... ช่องว่างระหว่างนัดขึ้นอยู่กับว่าเรือลำใดจม ถ้า B-ship เป็นครั้งสุดท้ายการยิงจะต้องเว้นช่องว่าง 4 ช่องระหว่างนั้นเพื่อลดการยิงที่สูญเปล่า


1
ดังนั้น ... ฉันแค่ต้องอยู่ห่างจากกลาง? :)
darron

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

1
หากคุณเริ่มต้นด้วยการเว้นช่องว่าง 3 หรือ 4 ช่องคุณอาจโชคดีพอที่จะกดปุ่มย่อยได้ ถ้าไม่กลับไปลองเติมในช่องว่าง เพิ่มเติมได้ที่: somethinkodd.com/oddthinking/2009/10/29/battleship-strategy
Oddthinking

18
เรือที่มีสองหลุมไม่ได้เป็นเหี้ยย่อยมันเป็นเหี้ยเรือ PT ย่อยมีสามหลุม :)
กา

2

มีการแข่งขันคล้ายกันที่ดำเนินการโดย Dr James Heather แห่งมหาวิทยาลัย Surrey ในนามของ British Computer Society

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

น่าสนใจมาก - ดูเพิ่มเติมที่: http://www.bcsstudentcontest.com/

อาจให้ความคิดเพิ่มเติมแก่คุณ


2

เนื่องจากเป็นวิธีการแก้ปัญหาที่เปิดและทำงานโดยไม่มีการดัดแปลงใน monodevelop ใน ubuntu 9.10 linux


1

คุณเขียน:

  • สิ่งใดก็ตามที่ถือว่าขัดต่อจิตวิญญาณของการแข่งขันจะเป็นเหตุให้ถูกตัดสิทธิ์
  • การแทรกแซงคู่ต่อสู้ขัดต่อจิตวิญญาณของการแข่งขัน

โปรดระบุ "กับจิตวิญญาณของการแข่งขัน" และ "รบกวนคู่ต่อสู้"?

นอกจากนี้ - เพื่อให้ง่ายขึ้นฉันขอแนะนำให้คุณ:

  • ไม่อนุญาตให้ใช้ CPU เลยระหว่างช่องเสียบ CPU ของคู่ต่อสู้
  • ไม่อนุญาตให้ขนานกันของเธรดและให้ CPU วินาทีมากขึ้นในเธรดเดียว สิ่งนี้จะทำให้การโปรแกรมของ AI ง่ายขึ้นและจะไม่ทำร้ายใครก็ตามที่มี CPU / memory-bound

ป.ล. - คำถามสำหรับ CS post-docs ที่ซุ่มซ่อนที่นี่: เกมนี้ไม่สามารถแก้ไขได้ (เช่นมีกลยุทธ์เดียวที่ดีที่สุดใช่ไหม) ใช่ขนาดบอร์ดและจำนวนก้าวทำให้มินิแมกซ์และคณะต้องทำ แต่ฉันยังต้องสงสัย ... มันไกลจากโกและหมากรุกในความซับซ้อน


ฉันคิดไตร่ตรองเมื่อฉันพูดว่า "รบกวน" ฉันไม่ต้องการให้คู่แข่งชนะเพราะพวกเขาทำให้เครื่องยนต์อื่นสั่นสะเทือน
John Gietzen

8
ผมขอแนะนำให้หน่วยสืบราชการลับที่เป็นส่วนสำคัญของการทำสงครามที่ทันสมัยเพื่อสะท้อนให้เห็นถึงการหาเป้าหมายจะเหมาะ - หลังจากทั้งหมดมันเป็นหนึ่งในวิธีการที่ใช้ในช่วงสงครามโลกครั้งที่สอง ...
Rowland ชอว์

ฉันมีกรอบในการแยกเอนจินลงบนพีซีต่าง ๆ สื่อสารผ่าน TCP / IP แสดงผล Reflection ไร้ค่า อย่างไรก็ตามเนื่องจากจำนวนที่คาดไว้ของฉันทำให้การแข่งขันใช้เวลานานมาก
John Gietzen

6
ฉันไม่รู้ว่าพวกเขามีการสะท้อนกลับมาแล้ว!
Markus Nigbur

1

ฉันทำนายว่าคนที่จัดการเพื่อพลิกกลับฝ่ายการสุ่มเมล็ดและรูปแบบการโทรของฝ่ายตรงข้ามจะเป็นฝ่ายชนะ

ไม่แน่ใจว่าเป็นไปได้อย่างไร


ฝ่ายตรงข้ามมีตัวเลือกในการใช้ CSPRNG
John Gietzen

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

เมื่อฉันสมัครเข้าฝึกงานวิจัยเราเขียนโปรแกรมเรือรบและเข้าแข่งขัน โดยการตั้งค่าเมล็ดสุ่มเป็นวิธีที่ฉันได้รับรางวัล X)
28390 P Shved

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

1

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

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


2
มีรูปแบบ "salvo" ที่ซึ่งคุณจะได้รับการยิงเป็นจำนวนมากในแต่ละเทิร์นเมื่อคุณมีเรือรบที่เหลืออยู่
John Gietzen

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

อีกรูปแบบ: เป็นขนาดของบอร์ด + จำนวนเรือรบ
russau

1

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

ฉันไม่แน่ใจว่าจะหลีกเลี่ยงข้อ จำกัด ของกรอบนี้ได้อย่างไร แต่ควรได้รับการแก้ไข

...

แนวคิดหนึ่งคือการทำสิ่งที่ทำในการแข่งขันครั้งนี้http://www.bcsstudentcontest.com /

และมีเวลาสูงสุดต่อรอบเมื่อเทียบกับเวลารวมของเกมสูงสุด วิธีนี้ฉันสามารถ จำกัด อัลกอริทึมให้พอดีภายในเวลาที่รู้ เกมอาจใช้เวลา 50 ถึง 600+ รอบหากอัลกอริทึมของฉันจัดการกับเกมทั้งหมดอาจไม่ให้เวลามากพอที่จะทำงานได้ดีที่สุดหรืออาจเสียเวลาและเสียเวลามากเกินไป มันยากมากในการจัดการเวลาของเกมทั้งหมดภายในอัลกอริทึม Battleship

ฉันขอแนะนำให้เปลี่ยนกฎเพื่อ จำกัด เวลาเปิดไม่รวมเวลาเล่นเกม

แก้ไข

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

หากมีการ จำกัด การเลี้ยวฉันสามารถปล่อยให้อัลกอริธึมทำงานได้ 0.9 วินาทีและกลับไปสู่การจัดอันดับสูงสุดและทำได้ดีภายในเวลา จำกัด การเลี้ยว

หากฉัน จำกัด เวลาเล่นเกมทั้งหมดเพียงหนึ่งวินาทีมันจะยากที่จะกำหนดระยะเวลาที่อัลกอริทึมควรทำงานในแต่ละเทิร์น ฉันจะต้องการเวลา CPU สูงสุดของฉัน หากเกมนาน 500 รอบฉันสามารถ จำกัด แต่ละเทิร์นเป็น 0.002 วินาที แต่ถ้าเกมนาน 100 รอบฉันสามารถให้เทิร์นซีพียู 0.01 วินาทีในแต่ละรอบ

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

เวลารวมของเกม 1 วินาที จำกัด ประเภทของอัลกอริทึมที่สามารถใช้ในการแข่งขันในเกมได้อย่างมีประสิทธิภาพ


สิ่งนี้จะทำงานใน Intel Q9550SX quad core, 8 GB ram, Vista 64 machine 1 วินาทีจะเป็นปัจจัย จำกัด หรือไม่
John Gietzen

ฉันเดาว่าคุณควรสร้างเรือรบ AI แบบมัลติเธรดของคุณเพื่อคำนวณจำนวน # ของการยิงสูงสุดต่อช่วงเวลานั้น
เจฟฟ์แอด

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

@TonyAbell: ถ้าเป็นเรื่องสำคัญที่จะต้องมีการ จำกัด เวลาในการเทิร์นเบสทำไมไม่เริ่มต้นด้วยค่าเริ่มต้นจากนั้นปรับจากรอบเป็นรอบ? หลังจากผ่านไปครึ่งรอบคุณจะพบว่ามีความยาวเทิร์นที่เหมาะสมที่สุดสำหรับคู่ต่อสู้ที่คุณเผชิญหน้า
kyokley

คุณควรติดตามเวลาที่เหลืออยู่และ จำกัด ไว้ที่ 1/2 ของเวลาที่เหลือ
John Gietzen

1

ฉันกำลังออกจากที่นี่โดยไม่ใส่รหัสจริง - แต่ฉันจะทำให้เกิดข้อสังเกตทั่วไป:

  • เนื่องจากเรือทุกลำมีขนาดอย่างน้อย 2 เซลล์คุณสามารถใช้การเพิ่มประสิทธิภาพที่ฉันเห็นในการนำเกมมาใช้ใน Space Quest V - ซึ่งจะทำการยิงเฉพาะเซลล์อื่นในรูปแบบเพชรในขณะที่ "ค้นหา" เป้าหมาย วิธีนี้จะกำจัดสี่เหลี่ยมครึ่งหนึ่งในขณะที่ยังรับประกันว่าคุณจะพบเรือทั้งหมดในที่สุด
  • รูปแบบการยิงแบบสุ่มเมื่อค้นหาเป้าหมายจะให้ผลลัพธ์ที่ดีที่สุดในหลาย ๆ เกม

1

! [ความหนาแน่นน่าจะเป็น] [1] ป้อนคำอธิบายภาพของเธอ

! [ใส่คำอธิบายภาพที่นี่] [2]

ฉันทดลองกับการเปรียบเทียบผลลัพธ์ของการยิงแรนด์เทียบกับการไล่ล่า / เป้าหมายโง่และในที่สุดก็เป็นการค้นหาที่ซับซ้อน

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

คุณสามารถเห็นผลลัพธ์ของฉันที่นี่ป้อนคำอธิบายลิงก์ที่นี่


คุณสามารถแก้ไขคำตอบของคุณโดยเฉพาะภาพและลิงค์ของคุณได้ไหม?
บาร์ต

-2

"เรือประจัญบาน" เป็นสิ่งที่รู้จักกันในชื่อวิทยาศาสตร์คอมพิวเตอร์ปัญหา NP-complete

http://en.wikipedia.org/wiki/List_of_NP-complete_problems

(มองหา Battleship - อยู่ที่นั่นภายใต้เกมและปริศนา)


4
ซึ่งเป็นปริศนาเรือรบ ( en.wikipedia.org/wiki/Battleship_(puzzle) ) ไม่ใช่ Battleship the game ( en.wikipedia.org/wiki/Battleship_(game )
เจสัน Berkan

ใช่ตามที่เจสันกล่าวว่านี่เป็นสัตว์ที่แตกต่างอย่างสิ้นเชิง
John Gietzen

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