Apa AI Battleship terbaik?


315

Kapal perang!

Kembali pada tahun 2003 (ketika saya berusia 17), saya berkompetisi dalam kompetisi coding AI Battleship . Meskipun saya kehilangan turnamen itu, saya bersenang-senang dan belajar banyak dari itu.

Sekarang, saya ingin menghidupkan kembali kompetisi ini, dalam mencari AI kapal perang terbaik.

Inilah kerangka kerjanya, sekarang di-host di Bitbucket .

Pemenang akan diberikan +450 reputasi! Kompetisi akan diadakan mulai tanggal 17 November 2009 . Tidak ada entri atau pengeditan yang lebih dari nol jam pada tanggal 17 akan diterima. (Waktu Standar Sentral) Kirim entri Anda lebih awal, jadi jangan lewatkan kesempatan Anda!

Untuk menjaga TUJUAN ini , silakan ikuti semangat kompetisi.

Aturan main:

  1. Gim ini dimainkan di kisi 10x10.
  2. Setiap pesaing akan menempatkan masing-masing 5 kapal (dengan panjang 2, 3, 3, 4, 5) pada grid mereka.
  3. Tidak ada kapal yang tumpang tindih, tetapi mungkin berbatasan.
  4. Para pesaing kemudian bergiliran menembakkan satu tembakan ke arah lawan mereka.
    • Variasi pada gim ini memungkinkan menembakkan beberapa tembakan per voli, satu untuk setiap kapal yang selamat.
  5. Lawan akan memberi tahu pesaing jika tembakannya jatuh, mengenai, atau gagal.
  6. Permainan bermain berakhir ketika semua kapal dari satu pemain tenggelam.

Aturan kompetisi:

  1. Semangat kompetisi adalah menemukan algoritma Battleship terbaik.
  2. Apa pun yang dianggap bertentangan dengan semangat kompetisi akan menjadi alasan untuk didiskualifikasi.
  3. Mengganggu lawan bertentangan dengan semangat kompetisi.
  4. Multithreading dapat digunakan di bawah batasan berikut:
    • Tidak lebih dari satu utas mungkin berjalan saat ini bukan giliran Anda. (Meskipun, sejumlah utas mungkin dalam kondisi "Ditangguhkan").
    • Tidak ada utas yang dapat berjalan pada prioritas selain "Normal".
    • Dengan adanya dua batasan di atas, Anda akan dijamin setidaknya 3 inti CPU khusus selama giliran Anda.
  5. Batas waktu CPU 1 detik per game diberikan untuk masing-masing pesaing di utas utama.
  6. Kehabisan waktu menyebabkan kalahnya permainan saat ini.
  7. Pengecualian yang tidak tertangani akan berakibat kehilangan game saat ini.
  8. Akses jaringan dan akses disk diperbolehkan, tetapi Anda mungkin menemukan pembatasan waktu cukup mahal. Namun, beberapa metode pengaturan dan penghancuran telah ditambahkan untuk mengurangi ketegangan waktu.
  9. Kode harus diposting pada stack overflow sebagai jawaban, atau, jika terlalu besar, ditautkan.
  10. Ukuran total maksimum (tidak dikompresi) dari sebuah entri adalah 1 MB.
  11. Secara resmi, .Net 2.0 / 3.5 adalah satu-satunya persyaratan kerangka kerja.
  12. Entri Anda harus mengimplementasikan antarmuka IBattleshipOpponent.

Mencetak:

  1. 51 game terbaik dari 101 game adalah pemenang pertandingan.
  2. Semua pesaing akan bermain bertanding melawan satu sama lain, gaya round-robin.
  3. Setengah terbaik dari pesaing kemudian akan memainkan turnamen eliminasi ganda untuk menentukan pemenang. (Kekuatan terkecil dari dua yang lebih besar dari atau sama dengan setengah, sebenarnya.)
  4. Saya akan menggunakan kerangka kerja TournamentApi untuk turnamen.
  5. Hasilnya akan diposting di sini.
  6. Jika Anda mengirim lebih dari satu entri, hanya entri dengan skor terbaik yang memenuhi syarat untuk dihilangkan dobel.

Semoga berhasil! Selamat bersenang-senang!


EDIT 1:
Terima kasih kepada Freed , yang telah menemukan kesalahan dalam Ship.IsValidfungsinya. Sudah diperbaiki. Silakan unduh versi kerangka kerja yang diperbarui.

EDIT 2:
Karena ada minat yang signifikan dalam mempertahankan statistik ke disk dan semacamnya, saya telah menambahkan beberapa peristiwa pengaturan dan penghancuran yang tidak berwaktu yang seharusnya menyediakan fungsionalitas yang diperlukan. Ini adalah perubahan semi-melanggar . Dengan kata lain: antarmuka telah dimodifikasi untuk menambah fungsi, tetapi tidak diperlukan badan untuk itu. Silakan unduh versi kerangka kerja yang diperbarui.

EDIT 3:
Bug Fix 1: GameWondan GameLosthanya dipanggil jika ada time out.
Bug Fix 2: Jika sebuah mesin menghitung waktu setiap pertandingan, kompetisi tidak akan pernah berakhir.
Silakan unduh versi kerangka kerja yang diperbarui.

EDIT 4:
Hasil Turnamen:


Jika entri tersebut membutuhkan basis data yang besar, dapatkah ia terhubung ke sana melalui internet? Yaitu. dapatkah entri membuat panggilan layanan web?
Remus Rusanu

apakah ada batasan ukuran pada entri?
Jherico

8
@ Sebelas: Juga, saya berkonsultasi dengan Jeff Atwood hanya untuk melihat apakah itu tepat. Berikut tanggapannya: twitter.com/codinghorror/status/5203185621
John Gietzen

1
Saya juga akan menambahkan bahwa, mengingat komponen acak yang tak terhindarkan untuk 50 game ini tidak akan cukup untuk membedakan secara akurat antara implementasi yang sangat baik. Saya akan berpikir bahwa 501 atau lebih mungkin diperlukan untuk tampilan yang masuk akal yang lebih baik.
ShuggyCoUk

1
Lawan "damai" yang menolak untuk menempatkan kapal menyebabkan persaingan digantung. Tidak yakin seberapa besar Anda peduli tentang orang yang melakukan hal-hal konyol seperti itu. :)
Joe

Jawaban:


56

Saya gerakan kedua untuk melakukan lebih banyak game per pertandingan. Melakukan 50 pertandingan hanyalah membalik koin. Saya perlu melakukan 1000 game untuk mendapatkan perbedaan yang masuk akal antara algoritma pengujian.

Unduh Dreadnought 1.2 .

Strategi:

  • melacak semua posisi yang mungkin untuk kapal yang memiliki> 0 hit. Daftar ini tidak pernah lebih besar dari ~ 30K sehingga dapat disimpan dengan tepat, tidak seperti daftar semua posisi yang mungkin untuk semua kapal (yang sangat besar).

  • Algoritma GetShot memiliki dua bagian, satu yang menghasilkan bidikan acak dan lainnya yang mencoba menyelesaikan menenggelamkan kapal yang sudah menabrak. Kami melakukan bidikan acak jika ada posisi yang memungkinkan (dari daftar di atas) di mana semua kapal yang terkena tenggelam. Kalau tidak, kami mencoba menyelesaikan menenggelamkan kapal dengan memilih lokasi untuk menembak yang menghilangkan posisi paling mungkin (tertimbang).

  • Untuk bidikan acak, hitung lokasi terbaik untuk menembak berdasarkan kemungkinan salah satu kapal yang tidak ditandai tumpang tindih dengan lokasi.

  • Algoritma adaptif yang menempatkan kapal di lokasi di mana lawan secara statistik cenderung menembak.

  • Algoritma adaptif yang lebih suka menembak di lokasi di mana lawan secara statistik lebih cenderung menempatkan kapalnya.

  • tempat kapal kebanyakan tidak saling bersentuhan.


pada mesin uji saya (netbook ULV Celeron) kode ini hilang secara konsisten. Ketika saya membiarkannya mengambil semua waktu ia ingin itu mencambuk Sederhana (tingkat keberhasilan sekitar 90%). Jika Anda sangat bergantung pada spesifikasi mesin yang akan Anda jalankan untuk memukul timimits Anda, Anda mungkin ingin memberi diri Anda ruang gerak ...
ShuggyCoUk

Menarik ... Ini berjalan baik di mesin turnamen. Namun, mesin "sempurna" akan beradaptasi dengan banyak waktu yang telah dihabiskannya.
John Gietzen

35

Inilah entri saya! (Solusi paling naif mungkin)

"Acak 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
Sebenarnya, jawaban ini bagus karena menunjukkan dalam sangat ringkas membentuk API yang Anda akan perlu untuk mengimplementasikan untuk bersaing ... :)
dicroce

1
Kembali ketika saya membangun proyek serupa di kelas Algoritma kuliah saya, saya menggunakan logika acak interlaced dengan beberapa pengambilan keputusan. Terkadang itu bagus!
Nathan Taylor,

2
Ini bisa mencoba menempatkan kapal yang tumpang tindih bukan?

6
Ya, tetapi mesin akan melarang ini. Ini kemudian akan memberitahu AI untuk menempatkan mereka lagi, tapi kali ini, dengan suara yang lebih keras. (Dilihat oleh pop ax \ cmp ax, 1 \ je stern)
John Gietzen

5
Catatan penting untuk siapa pun yang, seperti saya, mengira mereka dapat dengan mudah mengalahkan ini dengan mengingat tembakan yang ditempatkan sebelumnya dan tidak mengulangi. Kerangka kerja akan mengabaikan pengulangan dan memberi Anda kesempatan lain selama total waktu Anda kurang dari batas. Ini miskin menurut pendapat saya, jika seseorang messes up algo mereka mereka harus dihukum ...
ShuggyCoUk

22

Inilah lawan yang harus dilawan orang:

Alih-alih menggunakan strategi geometri tetap, saya pikir akan menarik untuk mencoba memperkirakan probabilitas yang mendasari bahwa setiap ruang tertentu yang belum dijelajahi memiliki kapal.

Untuk melakukan ini dengan benar, Anda akan menjelajahi semua kemungkinan konfigurasi kapal yang sesuai dengan pandangan Anda saat ini tentang dunia, dan kemudian menghitung probabilitas berdasarkan pada konfigurasi tersebut. Anda bisa memikirkannya seperti menjelajahi pohon:

perluasan kemungkinan status kapal perang http://natekohl.net/media/battleship-tree.png

Setelah mempertimbangkan semua dedaunan pohon yang sesuai dengan apa yang Anda ketahui tentang dunia (mis. Kapal tidak bisa tumpang tindih, semua kotak yang terkena pasti kapal, dll.) Anda dapat menghitung seberapa sering kapal muncul di setiap posisi yang belum dijelajahi untuk memperkirakan kemungkinan bahwa sebuah kapal duduk di sana.

Ini dapat divisualisasikan sebagai peta panas, di mana hot spot lebih cenderung mengandung kapal:

peta panas probabilitas untuk setiap posisi yang belum dijelajahi http://natekohl.net/media/battleship-probs.png

Satu hal yang saya sukai dari kompetisi Battleship ini adalah bahwa pohon di atas hampir cukup kecil untuk memaksa algoritma semacam ini. Jika ada ~ 150 posisi yang memungkinkan untuk masing-masing dari 5 kapal, itu 150 5 = 75 miliar kemungkinan. Dan jumlah itu semakin kecil, terutama jika Anda dapat menghilangkan seluruh kapal.

Lawan yang saya tautkan di atas tidak menjelajahi seluruh pohon; 75 miliar masih terlalu besar untuk bisa di bawah satu detik. Ia mencoba memperkirakan probabilitas ini, dengan bantuan beberapa heuristik.


Sejauh ini, Anda mengalahkan solusi kami hanya lain penuh oleh sekitar 67,7% untuk 32,3% :)
John Gietzen

2
Saya benar-benar ingin tahu bagaimana "pendekatan probabilitas" dibandingkan dengan "pendekatan geometris". Saya perhatikan bahwa probabilitas lawan ini benar-benar membuat gerakan yang mengikuti pola geometris yang dibahas dalam jawaban lain. Bisa jadi menggunakan geometri sama baiknya, dan jauh lebih cepat. :)
Nate Kohl

12

Bukan jawaban yang lengkap tetapi tampaknya ada sedikit gunanya mengacaukan jawaban nyata dengan kode yang umum. Dengan demikian saya menyajikan beberapa ekstensi / kelas umum dalam semangat open source. Jika Anda menggunakan ini maka silakan ubah namespace atau mencoba untuk mengkompilasi semuanya menjadi satu dll tidak akan berfungsi.

BoardView memungkinkan Anda bekerja dengan mudah menggunakan papan beranotasi.

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
    }
}

Beberapa ekstensi, beberapa fungsi ini duplikat dalam kerangka utama tetapi harus benar-benar dilakukan oleh Anda.

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));
        }
    }
}

Sesuatu yang saya akhirnya gunakan banyak.

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

Pengacakan. Aman tapi bisa diuji, berguna untuk pengujian.

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

Saya tidak punya waktu sekarang untuk menulis algoritma yang lengkap, tetapi inilah pemikiran: jika lawan Anda menempatkan kapal secara acak, bukankah probabilitas penempatannya akan menjadi distribusi sederhana yang berpusat pada (5.5.5.5)? Misalnya, kemungkinan penempatan untuk kapal perang (panjang 5 unit) di dimensi x ada di sini:

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

Perhitungan yang sama akan berlaku untuk y. Kapal-kapal lain tidak akan curam distribusi, tetapi tebakan terbaik Anda masih pusat. Setelah itu, pendekatan matematika akan secara perlahan memancarkan diagonal (mungkin dengan panjang rata-rata kapal, 17/5) di luar pusat. Ex:

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

Jelas beberapa keacakan perlu ditambahkan ke ide, tapi saya pikir itu murni matematis cara untuk pergi.


Ya, memang mereka akan melakukannya. Mesin lama saya memberi kompensasi untuk itu.
John Gietzen

1
Di mana saya berasal, diagonal memancarkan perlahan keluar dari pusat dianggap curang .
bzlm

Jika dianggap curang, ada tindakan pencegahan yang cukup mudah. Hindari (x, y) di mana x = y. :)
ine

5
Saya pikir dia menyinggung penghitungan kartu? Yang, menurut saya, tidak curang.
John Gietzen

10

Tidak ada yang secanggih itu yang ada pada saya. Ini mengalahkan lawan acak 99,9% dari waktu. Akan tertarik jika ada yang punya tantangan kecil lain seperti ini, itu menyenangkan.

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() { }
    }
}

Sedikit terkondensasi untuk mengambil ruang minimal di sini dan masih dapat dibaca.


6

Beberapa komentar tentang Mesin Persaingan:

Parameter NewGame:

Jika IBattleshipOpponent :: NewGame ditujukan untuk pengaturan pra-permainan dan mengambil ukuran papan, itu juga harus mengambil daftar kapal dan ukurannya masing-masing. Tidak masuk akal untuk memungkinkan ukuran papan variabel tanpa memungkinkan untuk konfigurasi kapal variabel.

Kapal disegel:

Saya tidak melihat alasan mengapa Kapal kelas disegel. Di antara hal-hal dasar lainnya, saya ingin Kapal memiliki Nama, jadi saya dapat menampilkan pesan seperti ("Anda hang my {0}", ship.Name); . Saya juga memiliki ekstensi lain, jadi saya pikir Ship harus dapat diwariskan.

Batas Waktu:

Sementara batas waktu 1 detik masuk akal untuk aturan turnamen, itu benar-benar mengacaukan dengan debugging. BattleshipCompetition harus memiliki pengaturan yang mudah untuk mengabaikan pelanggaran waktu untuk membantu pengembangan / debugging. Saya juga menyarankan menyelidiki System.Diagnostics.Process :: UserProcessorTime / Privileged ProcessorTime / TotalProcessorTime untuk tampilan yang lebih akurat tentang berapa banyak waktu yang digunakan.

Kapal Tenggelam:

API saat ini memberi tahu Anda ketika Anda telah menenggelamkan kapal oppenent:

ShotHit(Point shot, bool sunk);

tetapi tidak yang mengirim Anda tenggelam! Saya menganggapnya sebagai bagian dari aturan Battleship manusia yang harus Anda nyatakan "Kau menenggelamkan Battleship-ku!" (atau perusak, atau sub, dll).

Ini sangat penting ketika AI mencoba untuk menerbangkan kapal yang saling bertabrakan. Saya ingin meminta perubahan API ke:

ShotHit(Point shot, Ship ship);

Jika kapal bukan nol, itu menyiratkan bahwa tembakan itu merupakan tembakan yang tenggelam, dan Anda tahu kapal mana yang Anda tenggelam, dan berapa lama itu. Jika tembakan itu merupakan tembakan yang tidak tenggelam, maka kapal adalah nol, dan Anda tidak memiliki informasi lebih lanjut.


Silakan kirim sampel kode jika Anda berpikir bahwa waktu dapat dilakukan dengan lebih akurat. Saya tidak ingin mengubah peraturan terlalu banyak sekarang.
John Gietzen

Selain itu, ukuran kapal dilewatkan selama PlaceShips () yang dijalankan tepat sekali per game dan juga dapat digunakan sebagai fase pengaturan. Silakan membuka segel kapal untuk pengujian Anda sendiri, tetapi saya berencana menggunakan yang tersegel untuk turnamen.
John Gietzen

BUG: @John Gietzen: Saya telah menentukan bahwa PlaceShips TIDAK berjalan tepat sekali per game (seperti yang Anda nyatakan). Jika seorang pemain menempatkan kapal mereka secara tidak benar (seperti yang dilakukan RandomOpponent), maka PlaceShips dipanggil berulang kali, tanpa campur tangan NewGame.
abelenky

5
Saya selalu menganggap itu sebagai strategi untuk menempatkan dua kapal dalam konfigurasi L untuk membuat lawan saya berpikir mereka telah menenggelamkan kapal perang padahal sebenarnya tidak. Saya tidak pernah berada di bawah kesan Anda harus menyatakan perahu mana yang tenggelam.
Josh Smeaton

3
@ DJ: Saya akan mengikuti aturan pena-dan-kertas yang asli. Ingatlah bahwa Hasbro adalah perusahaan mainan dan game ini ada sebelum Hasbro.
John Gietzen

5

CrossFire diperbarui. Saya tahu itu tidak dapat bersaing dengan Farnsworth atau Dreadnought tetapi jauh lebih cepat daripada yang terakhir dan mudah dimainkan jika ada yang ingin mencoba. Ini bergantung pada kondisi perpustakaan saya saat ini, termasuk di sini untuk membuatnya mudah digunakan.

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

Ini adalah tentang yang terbaik yang bisa saya kumpulkan di waktu senggang saya, yaitu tentang tidak ada. Ada beberapa statistik penghitungan pertandingan dan pertandingan yang terjadi, ketika saya mengatur fungsi utama untuk mengulang dan terus menjalankan Kompetisi Battleship hingga saya menekan tombol.

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();
        }
    }
}

Logika ini adalah yang paling dekat yang harus saya kalahkan dari Dreadnought, memenangkan sekitar 41% dari masing-masing game. (Ini benar-benar memenangkan satu pertandingan dengan hitungan 52-49.) Anehnya, kelas ini tidak begitu baik melawan FarnsworthOpponent karena versi sebelumnya yang jauh lebih maju.


5

Komputer saya sedang diperbaiki oleh dell sekarang, tapi di sinilah saya pada minggu lalu:

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
Selamat atas peraknya. Apakah Anda keberatan menjelaskan algoritme Anda dengan kata-kata? Akan menarik untuk diketahui.
Thomas Ahle

4

Jika Anda dengan kasar memaksa analisis Anda, maka Anda mungkin menemukan mekanisme dari RandomOpponent yang disediakan sangat tidak efisien. Ini memungkinkan dirinya untuk memilih kembali lokasi yang sudah ditargetkan dan membiarkan kerangka kerja memaksanya untuk mengulang sampai menyentuh yang belum disentuh atau timelimit per gerakan berakhir.

Lawan ini memiliki perilaku yang sama (distribusi penempatan efektif adalah sama) itu hanya memeriksa kewarasan itu sendiri dan hanya mengkonsumsi satu generasi nomor acak per panggilan (diamortisasi)).

Ini menggunakan kelas-kelas di ekstensi / jawaban perpustakaan saya dan saya hanya menyediakan metode kunci / negara.

Shuffle diangkat dari jawaban Jon Skeet di sini

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

Saya tidak akan dapat berpartisipasi, tetapi inilah algoritma yang akan saya terapkan jika saya punya waktu:

Pertama, ketika saya mendeteksi hit, saya tidak mengejar sisa kapal dengan segera - saya membuat tabel lokasi kapal dan mencari tahu apakah saya telah memukul kelima setidaknya satu kali sebelum mulai menenggelamkannya sepenuhnya. (Perhatikan bahwa ini adalah kebijakan buruk untuk varian multi pemotretan - lihat komentar)

  1. Masuk ke pusat (lihat catatan akhir di bawah - 'pusat' hanyalah kemudahan untuk deskripsi)
  2. Tekan tempat 4 di sebelah kanan tengah
  3. Tekan tempat 1 ke bawah dan satu ke kanan pusat
  4. Tekan tempat empat di sebelah kanan hit sebelumnya
  5. Lanjutkan dalam pola itu (harus berakhir dengan garis-garis diagonal yang dipisahkan oleh 3 ruang yang mengisi papan) Ini harus mengenai semua 4 dan 5 kapal panjang, dan sejumlah besar 3 dan 2 kapal secara statistik.

  6. Mulai memukul tempat secara acak di antara diagonal, ini akan menangkap kapal panjang 2 dan 3 yang belum diketahui.

Setelah saya mendeteksi 5 hit, saya akan menentukan apakah 5 hit itu berada di kapal terpisah. Ini relatif mudah dengan membuat beberapa tembakan lagi di dekat lokasi di mana dua klik berada pada garis horizontal atau vertikal yang sama dan berada dalam 5 lokasi satu sama lain (mungkin dua pukulan di kapal yang sama). Jika mereka adalah kapal yang terpisah maka terus menenggelamkan semua kapal. Jika mereka ditemukan kapal yang sama, lanjutkan pola pengisian di atas sampai semua 5 kapal berada.

Algoritma ini adalah algoritma pengisian sederhana. Fitur utamanya adalah tidak membuang waktu untuk menenggelamkan kapal yang dikenalnya ketika masih ada kapal yang tidak disadarinya, dan tidak menggunakan pola pengisian yang tidak efisien (yaitu, pola acak penuh akan boros).

Catatan akhir:

A) "Tengah" adalah titik awal acak di papan tulis. Ini menghilangkan kelemahan utama dari algoritma ini. B) Sementara deskripsi menunjukkan menggambar diagonal segera dari awal, idealnya algoritma hanya menembak di lokasi 'acak' yang ada di sepanjang diagonal tersebut. Ini membantu mencegah pesaing menentukan waktu berapa lama sampai kapal mereka dihantam oleh pola yang dapat diprediksi.

Ini menggambarkan algoritme 'sempurna' karena akan mendapatkan semua kapal di bawah (9x9) / 2 + 10 tembakan.

Namun, dapat ditingkatkan secara signifikan:

Setelah kapal tertabrak, identifikasi ukurannya sebelum melakukan garis diagonal 'internal'. Anda mungkin telah menemukan 2 kapal, dalam hal ini diagonal internal dapat disederhanakan untuk menemukan 3 kapal berukuran lebih cepat.

Identifikasi tahapan-tahapan dalam permainan dan lakukan tindakan yang sesuai. Algoritme ini mungkin bagus hingga titik tertentu dalam permainan, tetapi algoritma lain dapat menghasilkan manfaat yang lebih baik sebagai bagian dari permainan akhir. Juga, jika pemain lain sangat dekat untuk mengalahkan Anda, algoritma lain mungkin bekerja lebih baik - misalnya algoritma berisiko tinggi mungkin gagal lebih sering, tetapi ketika berhasil ia bekerja dengan cepat dan Anda dapat mengalahkan lawan Anda yang lebih dekat untuk menang daripada Anda .

Identifikasi gaya permainan pesaing - ini dapat memberi Anda petunjuk tentang bagaimana mereka merencanakan penempatan kapal (yaitu, kemungkinan bagus bahwa algoritma mereka sendiri paling cepat mengidentifikasi bagaimana mereka menempatkan kapal mereka sendiri - jika satu-satunya alat yang Anda miliki adalah palu, semuanya terlihat seperti paku)

-Adam


Strategi menunggu untuk menenggelamkan kapal sampai semuanya ditemukan sangat bergantung pada variasi satu tembakan per giliran. Di bawah (jumlah kapal yang bertahan) versi -shots-per-turn, menguntungkan untuk menenggelamkan kapal secepat mungkin sehingga memperlambat lawan Anda.
Jason Owen

4

Entri saya

Tidak ada yang sangat istimewa, dan saya tidak punya waktu untuk menambahkan semua ide bagus yang saya miliki.

Namun sepertinya bermain cukup baik. Kita akan lihat bagaimana ini terjadi dalam kompetisi:

(letakkan ini di file Missouri.csdan ditambahkan ke proyek.)

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;
    }
}

Dan sekarang setelah saya mengirimkan entri saya, beberapa statistik kasar: vs BP7, 44% menang. / vs. Dreadnought 20% menang. / vs. Farnsworth menang 42%. Itu adalah proyek yang menyenangkan.
abelenky

2

Ini bukan minimax. Sebenarnya setelah menempatkan kapal, tidak bisakah masing-masing pemain bermain sendiri, menghasilkan sejumlah putaran yang dibutuhkannya untuk menenggelamkan setiap kapal lawan? Yang kurang bergantian menang.

Saya tidak berpikir bahwa ada strategi umum yang baik selain menenggelamkan kapal yang menabrak dan mencoba untuk meminimalkan jumlah tembakan untuk menutupi tempat yang tersisa di mana kapal mungkin bersembunyi.

Tentu saja mungkin ada strategi kontra untuk apa pun yang tidak acak. Tapi saya tidak berpikir bahwa ada strategi yang baik terhadap semua pemain yang mungkin.


1
Berpotensi, ya, mereka bisa bermain sendiri. Itu bukan bagaimana ini akan dijalankan. Tapi ide bagus. Dalam kompetisi ini, saya ingin dimungkinkan untuk secara statistik menghindari tembakan lawan Anda.
John Gietzen

2
Saya melihat. Menggunakan data dari game sebelumnya melawan lawan yang sama yang mungkin bisa beradaptasi dengannya?
ziggystar

2

Sebenarnya, saya pikir masalah terbesar dengan teka-teki adalah bahwa pada dasarnya dua gerakan. Satu langkah adalah menempatkan kapal Anda, yang lain adalah menemukan kapal musuh (namun tersegmentasi bagian kedua itu mungkin, selain mencoba untuk mengalahkan jam dengan faktor acak, itu hanya 'jalankan algoritma Anda'). Tidak ada mekanisme untuk mencoba menentukan dan kemudian melawan strategi musuh, yang membuat kompetisi serupa berdasarkan putaran "gunting kertas batu" cukup menarik.

Juga, saya pikir akan lebih keren jika Anda menentukan game sebagai protokol jaringan dan kemudian menyediakan kerangka kerja untuk mengimplementasikan protokol itu dalam C #, daripada mendikte bahwa semua solusi harus C #, tapi itu hanya pendapat saya.

EDIT: Saya membatalkan poin awal saya, karena saya tidak membaca aturan kompetisi dengan cukup hati-hati.


Tidak semua solusi harus dalam C #. Saya dapat mengkompilasi dan menautkan dalam perakitan terpisah. Selain itu, Anda harus dapat melawan lawan secara statistik.
John Gietzen

J #? mungkin? Lol, jk. Saya memang memiliki kerangka TCP untuk ini, tetapi turnamen ini harus berjalan sangat cepat.
John Gietzen

Mengapa Anda berasumsi bahwa komunikasi TCP antara dua proses pada mesin yang sama tidak akan sangat cepat?
Jherico

@ Jherico: Jika saya menggunakan TCP, saya akan mengisolasi mesin pada PC mereka sendiri sehingga mereka dapat menggunakan sumber daya CPU yang mereka inginkan.
John Gietzen

Meski begitu, dua mesin di lan yang sama dapat dengan mudah menyelesaikan permainan di bawah satu detik dengan overhead jaringan yang minimal
Jherico

2

Saya selalu suka memulai di tengah dan berputar dari satu titik sehingga tidak lebih dari 1 ruang kosong di antara titik-titik lain untuk menjelaskan sub sialan itu ... ruang antara tembakan tergantung pada kapal mana yang tenggelam. jika kapal-B adalah yang terakhir, tembakan hanya harus menyisakan 4 ruang di antara untuk meminimalkan tembakan yang sia-sia


1
Jadi ... Aku hanya perlu menjauh dari tengah? :)
darron

14
Anda juga harus menjauhi tepi, karena pukulan tepi berisi lebih banyak informasi untuk lawan Anda daripada pukulan non-tepi. Jadi, Anda harus menempatkan semua kapal Anda di wilayah non-menengah, non-tepi. Kecuali kalau itulah yang mereka harapkan Anda lakukan.
Jherico

1
Jika Anda mulai dengan menyisakan 3 atau 4 spasi, Anda mungkin cukup beruntung untuk mencapai sub. Jika tidak, kembali dan coba isi celahnya. Lebih lanjut di: somethinkodd.com/oddthinking/2009/10/29/battleship-strategy
Oddthinking

18
Kapal dengan dua lubang bukan kapal selam , itu kapal PT sialan . Kapal selam memiliki tiga lubang. :)
gagak

2

Ada kompetisi serupa yang dijalankan oleh Dr James Heather dari The University of Surrey atas nama British Computer Society.

Keterbatasan ditempatkan pada sumber daya - yaitu waktu prosesor maksimum per putaran, tidak ada status yang dapat disimpan di antara gerakan, ukuran tumpukan maksimum yang dikenakan. Untuk membatasi waktu, AI dapat mengirimkan langkah pada titik mana pun dalam slot waktu dan akan diminta untuk pindah setelah penghentian belokan.

Sangat menarik - lihat lebih lanjut di: http://www.bcsstudentcontest.com/

Mungkin memberi Anda lebih banyak ide.


2

Karena itu, solusinya terbuka dan berjalan tanpa modifikasi di monodevelop di ubuntu 9.10 linux


1

Kau menulis:

  • Apa pun yang dianggap bertentangan dengan semangat kompetisi akan menjadi alasan untuk didiskualifikasi.
  • Mengganggu lawan bertentangan dengan semangat kompetisi.

tolong definisikan "melawan semangat kompetisi" dan "mengganggu lawan"?

Juga - untuk mempermudah, saya sarankan Anda:

  • larang menggunakan CPU sama sekali selama slot CPU lawan.
  • larang paralelisme utas dan sebagai gantinya berikan lebih banyak CPU detik pada satu utas. Ini akan menyederhanakan pemrograman AI dan tidak akan melukai siapa pun yang terikat CPU / memori.

PS - pertanyaan untuk post-doc CS yang bersembunyi di sini: bukankah game ini dapat dipecahkan (yaitu apakah ada strategi tunggal terbaik?). ya, ukuran papan dan jumlah langkah membuat minimax et al wajib, tapi tetap saya harus bertanya-tanya ... itu jauh dari Go dan catur dalam kompleksitas.


Saya memiliki refleksi dalam pikiran ketika saya berkata "Mengganggu". Saya tidak ingin pesaing menang karena mereka menggigit mesin lain sampai mati.
John Gietzen

8
Saya menyarankan spionase adalah bagian penting dari peperangan modern, jadi mencerminkan untuk menemukan target akan ideal - bagaimanapun juga, itu adalah salah satu metode yang digunakan selama perang dunia kedua ...
Rowland Shaw

Saya memiliki kerangka kerja untuk mengisolasi mesin ke PC yang berbeda, berkomunikasi melalui TCP / IP, membuat Reflection tidak berharga. Namun, karena perkiraan jumlah entri saya, ini akan membuat kompetisi memakan waktu lama.
John Gietzen

6
Saya tidak tahu mereka memiliki Refleksi saat itu!
Markus Nigbur

1

Saya memperkirakan bahwa orang yang berhasil merekayasa balik benih acak dan pola panggilan lawan akan menang.

Tidak yakin seberapa besar kemungkinannya.


Lawan memiliki opsi untuk menggunakan CSPRNG.
John Gietzen

Poin bagus, meskipun saya akui bahwa rekayasa balik benih seperti itu di luar keahlian saya. Saya kira aspek yang paling rentan adalah algoritma pemilihan pola api - tetapi bahkan kemudian Anda tidak akan mendapatkan banyak manfaat dari melanggar itu, karena tidak ada cara Anda bisa memindahkan kapal Anda setelah permainan dimulai.
Triston Attridge

Ketika saya melamar magang penelitian, kami menulis program kapal perang dan bersaing. Dengan menetapkan benih acak adalah persis bagaimana saya menang X)
P Shved

1
Dengan asumsi algoritma penempatan kapal yang cukup sederhana, saya akan membayangkan orang bisa, setelah mendapatkan beberapa hit di kapal yang berbeda, mulai menggunakan sebagian besar putarannya melalui semua benih acak yang mungkin (mungkin dimulai dengan suatu tempat di dekat waktu saat ini dan bergerak maju / mundur satu langkah atau lebih) dan lihat mana yang menghasilkan penempatan kapal yang kompatibel dengan hit yang diamati.
Domenic

1

Mungkin juga, dimungkinkan untuk menjalankan serangkaian ini dengan variasi pada gim.

Menambahkan hal-hal seperti pesawat 3d atau mampu memindahkan satu kapal alih-alih menembak mungkin akan mengubah permainan sedikit adil.


2
Ada variasi "salvo". Di mana Anda bisa menembakkan sebanyak mungkin tembakan per giliran karena Anda memiliki kapal yang tersisa.
John Gietzen

Variasi yang menarik juga. Saya ingat bermain versi komputer yang memiliki pesawat juga. Itu akan secara acak menembak lokasi di papan lawan.
Glenn

variasi lain: menjadi ukuran papan + jumlah kapal.
russau

1

Total waktu permainan satu detik adalah spesifik mesin. Sekali nilai kedua operasi CPU akan berbeda pada mesin saya dibandingkan dengan mesin turnamen. Jika saya mengoptimalkan algoritma Battle Ship untuk memanfaatkan waktu CPU paling banyak dalam 1 detik, maka itu dijalankan pada mesin turnamen yang mungkin lebih lambat, itu akan selalu hilang.

Saya tidak yakin bagaimana mengatasi batasan kerangka kerja ini, tetapi harus diatasi.

...

Satu ide adalah melakukan apa yang dilakukan dalam kompetisi ini http://www.bcsstudentcontest.com /

Dan memiliki waktu maksimum per giliran sebagai lawan dari total waktu permainan maksimum. Dengan cara ini saya bisa membatasi algoritma agar sesuai dalam waktu yang tepat. Sebuah game mungkin bertahan 50 hingga 600+, jika algoritma saya mengelola total waktu permainannya, itu mungkin tidak memberikan cukup waktu untuk melakukan pekerjaan terbaiknya atau itu bisa memberi terlalu banyak waktu dan kalah. Sangat sulit untuk mengatur waktu total game dalam algoritma Battleship.

Saya akan menyarankan mengubah aturan untuk membatasi waktu belok bukan waktu permainan total.

Edit

Jika saya menulis algoritma yang menyebutkan semua kemungkinan pemotretan dan kemudian memeringkatnya, maka ambil pemotretan dengan peringkat tertinggi. Butuh terlalu lama untuk menghasilkan semua kemungkinan pemotretan, jadi saya akan membiarkan algoritme berjalan untuk jangka waktu tertentu kemudian menghentikannya.

Jika ada batas berbasis giliran, saya bisa membiarkan algoritme berjalan selama 0,9 detik dan mengembalikan bidikan peringkat tertinggi, dan baik-baik saja dengan batas waktu belokan.

Jika saya terbatas pada total waktu permainan satu detik, akan sulit untuk menentukan berapa lama algoritma harus berjalan untuk setiap belokan. Saya ingin memaksimalkan waktu CPU saya. Jika sebuah game berlangsung 500 putaran saya dapat membatasi setiap putaran menjadi 0,002 detik, tetapi jika sebuah game berlangsung 100 putaran saya bisa memberikan setiap putaran 0,01 detik waktu CPU.

Alangkah tidak praktis bagi suatu algoritma untuk menggunakan pencarian ruang tembakan semi-lengkap untuk menemukan bidikan terbaik dengan batasan saat ini.

Total waktu permainan 1 detik membatasi jenis algoritme yang dapat digunakan secara efektif untuk bersaing dalam permainan.


Ini akan dijalankan pada prosesor Intel Q9550SX quad core, ram 8 GB, Vista 64. Apakah 1 detik akan menjadi faktor pembatas?
John Gietzen

Saya kira Anda harus membuat AI kapal perang Anda multithreaded, kemudian, untuk menghitung jumlah foto maksimum per interval waktu itu.
Jeff Atwood

Kuncinya adalah bagaimana membatasi interval waktu belokan. Jika saya membatasinya 0,00005 detik saya aman dari menjalankan melebihi batas waktu, tetapi saya secara signifikan membatasi ruang pencarian Jika saya meningkatkan batas waktu belok, ruang pencarian meningkat tetapi saya berisiko kehabisan waktu.
TonyAbell

@TonyAbell: Jika penting untuk memiliki batas waktu berdasarkan giliran, mengapa tidak memulai dengan nilai awal dan kemudian menyesuaikannya dari putaran ke putaran? Setelah sekitar setengah putaran Anda kemungkinan besar telah menemukan panjang putaran optimal untuk lawan yang Anda hadapi.
Kyokley

Anda harus melacak sisa waktu Anda, dan membatasi hingga 1/2 dari waktu yang tersedia tersisa.
John Gietzen

1

Saya melakukannya di sini dengan tidak memasukkan kode aktual - tetapi saya akan membahayakan beberapa pengamatan umum:

  • Karena semua kapal setidaknya berukuran 2 sel, Anda dapat menggunakan pengoptimalan yang saya lihat pada implementasi game di Space Quest V - yang hanya menembakkan sel-sel alternatif dalam pola berlian saat sedang "mencari" target. Ini menghilangkan setengah dari kotak, sambil tetap menjamin bahwa Anda akan menemukan semua kapal pada akhirnya.
  • Pola penembakan acak ketika mencari target secara statistik akan menghasilkan hasil terbaik di banyak pertandingan.

1

! [Density Probabilitas] [1] masukkan deskripsi gambarnya

! [masukkan deskripsi gambar di sini] [2]

Saya bereksperimen dengan membandingkan hasil penembakan randon vs berburu / target bodoh dan akhirnya pencarian yang canggih.

Solusi terbaik tampaknya adalah untuk membuat fungsi kepadatan probabilitas untuk seberapa besar kemungkinan setiap kotak digunakan oleh kapal yang tersisa, dan bertujuan dengan kotak dengan nilai tertinggi.

Anda dapat melihat hasil saya di sini, masukkan deskripsi tautan di sini


Bisakah Anda memperbaiki jawaban Anda dan terutama gambar serta tautan Anda?
Bart

-2

"Battleship" adalah apa yang dikenal sebagai masalah NP-complete ilmu komputer klasik.

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

(cari Battleship - ada di sana, di bawah permainan dan teka-teki)


4
Yang merupakan teka-teki kapal perang ( en.wikipedia.org/wiki/Battleship_(puzzle) ), bukan Battleship the game ( en.wikipedia.org/wiki/Battleship_(game) ).
Jason Berkan

Ya, seperti yang dikatakan Jason, ini adalah binatang yang sama sekali berbeda.
John Gietzen

3
Hehehe. Tugas berikutnya yang saya dapatkan di kantor saya akan mengatakan itu NP-lengkap, kemudian makan siang yang panjang. :-)
Bork Blatt
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.