En iyi Savaş Gemisi AI nedir?


315

Battleship!

2003 yılında (17 yaşındayken) bir Zırhlı AI kodlama yarışmasında yarıştım . Bu turnuvayı kaybetmiş olmama rağmen çok eğlendim ve çok şey öğrendim.

Şimdi, en iyi AI zırhlısı arayışında, bu rekabeti yeniden canlandırmak istiyorum.

İşte Bitbucket'te barındırılan çerçeve .

Kazanan +450 itibar kazanacak! Yarışma 17 Kasım 2009'da başlayacak . 17. saatte sıfır saatten daha sonraki hiçbir giriş veya düzenleme kabul edilmeyecektir. (Merkezi Standart Saat) Girişlerinizi erkenden gönderin, böylece fırsatınızı kaçırmayın!

Bu HEDEFİ korumak için lütfen yarışmanın ruhunu takip edin.

Oyunun kuralları:

  1. Oyun 10x10 ızgara üzerinde oynanır.
  2. Her yarışmacı, 5 geminin (2, 3, 3, 4, 5 uzunlukta) her birini ızgaralarına yerleştirecektir.
  3. Hiçbir gemi üst üste gelemez, ancak bitişik olabilirler.
  4. Yarışmacılar daha sonra sırayla rakiplerine tek atış yaparlar.
    • Oyundaki bir varyasyon, hayatta kalan her gemi için bir tane olmak üzere voleybolu birden fazla atış yapmanıza izin verir.
  5. Rakip vuruş, batarsa, vurursa veya kaçırırsa rakibe bildirir.
  6. Herhangi bir oyuncunun tüm gemileri batırıldığında oyun sona erer.

Yarışma kuralları:

  1. Yarışmanın ruhu en iyi Savaş Gemisi algoritmasını bulmaktır.
  2. Yarışma ruhuna aykırı olan her şey diskalifiye için zemin olacaktır.
  3. Bir rakibe müdahale etmek yarışmanın ruhuna aykırıdır.
  4. Çoklu iş parçacığı aşağıdaki kısıtlamalar altında kullanılabilir:
    • Sıra sizde değilken birden fazla iş parçacığı çalışıyor olabilir. (Yine de, herhangi bir sayıda iş parçacığı "Askıya Alınmış" durumda olabilir).
    • Hiçbir iş parçacığı "Normal" dışında bir öncelikte çalışamaz.
    • Yukarıdaki iki kısıtlama göz önüne alındığında, dönüşünüz sırasında en az 3 ayrılmış CPU çekirdeği garanti edilecektir.
  5. Birincil iş parçacığında her yarışmacıya oyun başına 1 saniyelik CPU süresi sınırı tahsis edilir.
  6. Zamanın tükenmesi mevcut oyunu kaybetmenize neden olur.
  7. Herhangi bir ele alınmayan istisna mevcut oyunun kaybedilmesine yol açacaktır.
  8. Ağ erişimi ve disk erişimine izin verilir, ancak zaman kısıtlamalarını oldukça engelleyici bulabilirsiniz. Bununla birlikte, zaman gerginliğini hafifletmek için birkaç kurulum ve yırtma yöntemi eklenmiştir.
  9. Kod, yığın taşmasına bir yanıt olarak veya çok büyükse bağlantılı olarak gönderilmelidir.
  10. Bir girişin maksimum toplam boyutu (sıkıştırılmamış) 1 MB'dir.
  11. Resmi olarak, .Net 2.0 / 3.5 tek çerçeve gereksinimidir.
  12. Girişiniz IBattleshipOpponent arayüzünü uygulamalıdır.

puanlama:

  1. 101 oyun arasından en iyi 51 maç kazanır.
  2. Tüm yarışmacılar birbirlerine karşı, robin tarzı eşleşecek şekilde oynayacaklar.
  3. Yarışmacıların en iyi yarısı kazananı belirlemek için çift eleme turnuvası oynayacak. (Aslında ikinin en küçük gücü yarıya eşit veya yarıya eşit.)
  4. Turnuva için TournamentApi çerçevesini kullanacağım .
  5. Sonuçlar burada yayınlanacaktır.
  6. Birden fazla kayıt gönderirseniz, çift eleme için yalnızca en iyi skor kaydınız uygundur.

İyi şanslar! İyi eğlenceler!


DÜZENLEME 1: İşlevde bir hata bulan Freed
sayesinde . Düzeltildi. Lütfen çerçevenin güncellenmiş sürümünü indirin.Ship.IsValid

DÜZENLEME 2:
Diske sürekli istatistikler ve bu gibi önemli bir ilgi olduğu için, gerekli işlevselliği sağlamak gerekir birkaç zamanlanmış olmayan kurulum ve yırtma olayları ekledim. Bu yarı kırıcı bir değişiklik . Yani arayüz, işlev eklemek için değiştirildi, ancak bunlar için gövde gerekli değil. Lütfen çerçevenin güncellenmiş sürümünü indirin.

EDIT 3:
Hata Düzeltme 1: GameWonve GameLostsadece bir mola durumunda çağrıldı.
Hata Düzeltme 2: Bir motor her oyunda zaman aşımına uğradıysa, rekabet asla bitmeyecekti.
Lütfen çerçevenin güncellenmiş sürümünü indirin.

DÜZENLEME 4:
Turnuva Sonuçları:


Giriş büyük bir veritabanı gerektiriyorsa, ağ üzerinden ona bağlanabilir mi? Yani. giriş web hizmeti aramaları yapabilir mi?
Remus Rusanu

girişlerde bir boyut sınırı var mı?
Jherico

8
@Steven: Ayrıca, uygun olup olmadığını görmek için Jeff Atwood'a danıştım. İşte yanıtı: twitter.com/codinghorror/status/5203185621
John Gietzen

1
Ayrıca, bu 50 oyuna kaçınılmaz rastgele bileşen göz önüne alındığında, çok iyi uygulamalar arasında doğru bir şekilde ayrım yapmak için yeterli olmayacaktır. Makul bir görüş için 501 veya daha fazlasının gerekli olabileceğini düşünürüm.
ShuggyCoUk

1
Gemileri yerleştirmeyi reddeden "huzurlu" bir rakip yarışmanın durmasına neden olur. Böyle aptalca şeyler yapan insanları ne kadar önemsediğinizden emin değilim. :)
Joe

Yanıtlar:


56

Maç başına çok daha fazla oyun yapmak için harekete ikinci kez girdim. 50 oyun yapmak sadece bozuk para çevirmektir. Test algoritmaları arasında makul bir ayrım elde etmek için 1000 oyun yapmam gerekiyordu.

Dreadnought 1.2'yi indirin .

Stratejiler:

  • > 0 isabet alan gemiler için olası tüm pozisyonları takip edin. Liste asla ~ 30K'dan daha büyük olamaz, bu nedenle tüm gemiler için olası tüm pozisyonların (çok büyük) listesinin aksine tam olarak tutulabilir.

  • GetShot algoritması, biri rastgele atışlar üreten diğeri zaten vurulmuş bir gemiyi batırmayı bitirmeye çalışan iki bölümden oluşur. Tüm hit gemilerin battığı olası bir pozisyon (yukarıdaki listeden) varsa rastgele atışlar yapıyoruz. Aksi takdirde, ateş etmek için en olası pozisyonları (ağırlıklı) ortadan kaldıran bir yer seçerek bir gemiyi batırmayı bitirmeye çalışırız.

  • Rastgele atışlar için, konumla örtüşen batık gemilerden birinin olasılığına bağlı olarak ateş etmek için en iyi yeri hesaplayın.

  • gemileri, rakibin istatistiksel olarak atış yapma olasılığının daha düşük olduğu yerlere yerleştiren uyarlamalı algoritma.

  • rakibin gemilerini istatistiksel olarak yerleştirme olasılığının daha yüksek olduğu yerlerde çekim yapmayı tercih eden uyarlamalı algoritma.

  • gemileri çoğunlukla birbirine değmeyecek şekilde yerleştirin.


test makinemde (ULV Celeron netbook) bu kod zaman aşımı ile sürekli olarak kaybediliyor. Her zaman almasına izin verdiğimde, basit (yaklaşık% 90 başarı oranı) kırbaçlıyor. Eğer makinenin özelliklerine çok güveniyorsanız, size zaman kıpırdatmak için
koşacaksınız

İlginç ... Turnuva makinesinde iyi çalışıyor. Bununla birlikte, "mükemmel" bir motor daha önce harcadığı zamana uyum sağlayabilir.
John Gietzen

35

İşte benim girişim! (Mümkün olan en saf çözüm)

"Rastgele 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
Aslında, bu cevap güzel çünkü çok kısa bir formda rekabet etmek için uygulamanız gereken
API'leri gösteriyor

1
Üniversite Algoritmaları sınıfımda benzer bir proje inşa ettiğimde, bazı karar verme süreçleri ile karıştırılmış rastgele mantık kullandım. Bazen iyiydi!
Nathan Taylor

2
Bu üst üste binen gemileri yerleştirmeye çalışabilirdi değil mi?

6
Evet, ancak motor buna izin vermeyecek. Daha sonra AI'ye tekrar yerleştirmelerini söyleyecektir, ancak bu sefer daha sert bir sesle. (Gören pop ax \ cmp ax, 1 \ je stern)
John Gietzen

5
Benim gibi, önceden yerleştirilmiş çekimleri hatırlayarak ve tekrar etmeyerek bunu kolayca yenebileceklerini düşünen herkes için önemli not. Çerçeve, toplam süreniz sınırdan az olduğu sürece tekrarları görmezden gelir ve size bir şans daha verir. Bu benim görüşüme göre kötü, birisi algo berbat eğer cezalandırılmalıdır ...
ShuggyCoUk

22

İşte insanların karşı oynayacağı bir rakip:

Sabit geometriden esinlenen bir strateji kullanmak yerine, keşfedilmemiş herhangi bir alanın bir gemiye sahip olduğu temel olasılıkları tahmin etmenin ilginç olacağını düşündüm .

Bunu doğru yapmak için, mevcut dünya görüşünüze uyan gemilerin tüm olası yapılandırmalarını keşfedecek ve daha sonra bu yapılandırmalara dayalı olasılıkları hesaplayacaksınız. Bir ağacı keşfetmek gibi düşünebilirsiniz:

olası savaş gemilerinin genişlemesi http://natekohl.net/media/battleship-tree.png

O ağacın dünya hakkında bildiklerinizle dolu olan tüm yapraklarını düşündükten sonra (örneğin, gemiler üst üste binemez, tüm isabet alan kareler gemiler vb. Olmalıdır) orada bir gemi oturuyor.

Bu, sıcak noktaların gemi içerme olasılığının daha yüksek olduğu bir ısı haritası olarak görüntülenebilir:

keşfedilmemiş her pozisyon için bir ısı haritası haritası http://natekohl.net/media/battleship-probs.png

Bu Savaş Gemisi yarışmasında sevdiğim bir şey, yukarıdaki ağacın bu tür bir algoritmayı kaba kuvvetle zorlayacak kadar küçük olmasıdır. 5 geminin her biri için ~ 150 olası pozisyon varsa, bu 150 5 = 75 milyar olasılıktır. Ve bu sayı sadece, özellikle de bütün gemileri yok edebiliyorsanız azalır.

Yukarıda bağlandığım rakip tüm ağacı keşfetmiyor; 75 milyar bir saniyeden daha az bir sürede girmeye devam ediyor. Bununla birlikte, birkaç sezgisel tarama yardımıyla bu olasılıkları tahmin etmeye çalışır.


Şimdiye kadar, diğer tam çözümümüzü yaklaşık% 67,7 ila% 32,3 oranında
yeniyorsunuz

2
Kesinlikle bir "olasılık yaklaşımı" nın bir "geometrik yaklaşım" ile nasıl karşılaştırıldığını merak ediyorum. Bu olasılık rakibinin aslında diğer cevaplarda tartışılan geometrik kalıpları takip eden hamleler yaptığını fark ettim. Geometri kullanmak aynı derecede iyi ve çok daha hızlı olabilir. :)
Nate Kohl

12

Tam teşekküllü bir cevap değil ama gerçek cevapları ortak kodla karıştırmanın pek bir anlamı yok. Böylece açık kaynak ruhu içinde bazı uzantılar / genel sınıflar sunuyorum. Bunları kullanırsanız o zaman ad alanını değiştirin veya bir dll içine her şeyi derlemeye çalışacağız lütfen işe yaramaz.

BoardView, açıklamalı bir kartla kolayca çalışmanızı sağlar.

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

Bazı uzantılar, bunların bazıları ana çerçevedeki işlevselliği çoğaltır, ancak gerçekten sizin tarafınızdan yapılmalıdır.

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

Sonunda çok kullandığım bir şey.

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

Rasgeleleştirme. Güvenli ancak test edilebilir, test için yararlıdır.

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

Şu anda tam teşekküllü bir algoritma yazmak için zamanım yok, ancak burada bir düşünce var: Rakibiniz rastgele gemiler yerleştirirse, yerleştirme olasılıkları (5.5,5.5) merkezli basit bir dağıtım olmaz mı? Örneğin, x boyutundaki zırhlı için yerleştirme olanakları (5 adet uzunluğunda) burada:

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

Aynı hesaplamalar y için de geçerlidir. Diğer gemilerin dağıtımları dik olmayacaktı, ama en iyi tahmininiz hala merkez. Bundan sonra, matematiksel yaklaşım merkezden yavaşça köşegenler yayar (belki de ortalama geminin uzunluğu 17/5). Ör:

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

Açıkçası bu fikre bazı rastgeleliklerin eklenmesi gerekecek, ama bence bu tamamen matematiksel olarak ilerlemenin yolu.


Evet, gerçekten de ederlerdi. Eski motorum bunu telafi etti.
John Gietzen

1
Geldiğim yerde, merkezden yavaşça yayılan köşegenler hile olarak kabul edilir .
bzlm

Hile olarak kabul edilirse, oldukça kolay bir önlem var. (X, y) 'den kaçının, burada x = y. :)
ine

5
Sanırım kart saymayı mı düşünüyordu? Bence hile değil.
John Gietzen

10

O kadar sofistike bir şey değil ama benim bulduğum heres. Bu süre zarfında rastgele rakibi% 99.9 yeniyor. Herkes böyle küçük zorlukları varsa ilgi olurdu, iyi eğlenceliydi.

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

Burada az yer kaplayacak ve yine de okunabilir olacak şekilde biraz yoğunlaştırılmıştır.


6

Rekabet Motoru hakkında bazı yorumlar:

NewGame parametreleri:

IBattleshipOpponent :: NewGame oyun öncesi kurulum için tasarlanmışsa ve bir board boyutu alırsa, gemilerin ve ilgili boyutlarının bir listesini de almalıdır. Değişken gemi konfigürasyonlarına izin vermeden değişken pano boyutuna izin vermek mantıklı değildir.

Gemiler mühürlendi:

Gemi sınıfı mührünün mühürlenmesi için hiçbir neden göremiyorum. Diğer temel şeylerin yanı sıra, Gemilerin bir Adı olmasını istiyorum, böylece ("{0} benim battı", ship.Name);. Aklımda başka uzantıları da var, bu yüzden Geminin kalıtımsal olması gerektiğini düşünüyorum.

Zaman sınırları:

1 saniyelik zaman sınırı bir turnuva kuralı için anlamlı olsa da, hata ayıklama ile tamamen karışır. Rekabet, geliştirme / hata ayıklamaya yardımcı olmak için zaman ihlallerini göz ardı etmek için kolay bir ayara sahip olmalıdır. Ayrıca ne kadar zaman kullanıldığını daha doğru bir görünüm için System.Diagnostics.Process :: UserProcessorTime / Ayrıcalıklı ProcessorTime / TotalProcessorTime araştırılmasını öneririm.

Batık Gemiler:

Mevcut API, bir rakibin gemisini batırdığınızda sizi bilgilendirir:

ShotHit(Point shot, bool sunk);

ama hangi gemiyi batırdığınızı değil! İnsan savaş gemisi kurallarının "Savaş Gemimi batırdın!" İlan etmeniz gerektiğini düşünüyorum. (veya muhrip veya sub, vb.).

Bu, özellikle bir AI, birbirlerine karşı duran gemileri temizlemeye çalışırken kritik öneme sahiptir. Aşağıdakiler için bir API değişikliği talep etmek istiyorum:

ShotHit(Point shot, Ship ship);

Eğer gemi null değilse , atışın batan bir atış olduğu ve hangi geminin battığını ve ne kadar sürdüğünü biliyorsunuz demektir. Eğer atış batmayan bir atışsa, gemi null olur ve daha fazla bilginiz yoktur.


Zamanlamanın daha doğru yapılabileceğini düşünüyorsanız, lütfen kod örneklerini gönderin. Şu anda kuralları çok fazla değiştirmek istemiyorum.
John Gietzen

Ayrıca, gemi boyutları, oyun başına tam olarak bir kez çalıştırılan ve bir kurulum aşaması olarak da kullanılabilen PlaceShips () sırasında aktarılır. Lütfen kendi testiniz için gemiyi mühürlemekten çekinmeyin, ancak turnuva için mühürlü olanı kullanmayı planlıyorum.
John Gietzen

Hata: @ John Gietzen: PlaceShips'in oyun başına tam olarak bir kez çalıştırılmadığını belirledim (belirttiğiniz gibi). Bir oyuncu gemilerini yanlış yerleştirirse (RandomOpponent'in sıklıkla yaptığı gibi), o zaman araya giren NewGame çağrısı olmadan PlaceShips tekrar tekrar çağrılır.
abelenky

5
İki gemiyi L konfigürasyonuna yerleştirmenin, rakibimin aslında yapmadığı zamanlarda bir savaş gemisi battığını düşünmesini sağlamak için bir strateji olarak gördüm. Hangi teknenin battığını beyan etmek zorunda kaldığınızı hiç düşünmedim.
Josh Smeaton

3
@ DJ: Orijinal kalem ve kağıt kurallarına uyuyorum. Hasbro'nun bir oyuncak şirketi olduğunu ve bu oyunun Hasbro'dan önce geldiğini unutmayın.
John Gietzen

5

CrossFire güncellendi. Farnsworth veya Dreadnought ile rekabet edemeyeceğini biliyorum, ancak ikinci denemeden çok daha hızlı ve herkesin denemek istediği takdirde oynamak basit. Bu, kullanımı kolaylaştırmak için buraya dahil edilen kütüphanelerimin mevcut durumuna dayanır.

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

Bu boş zamanlarımda bir araya getirebileceğim en iyi şeyle ilgilidir, ki bu varolmayanlarla ilgilidir. Bir tuşa basana kadar BattleshipCompetition'ı döngüye sokmak ve sürekli olarak çalıştırmak için ana işlevi ayarladığım için bazı oyun ve maç yetenek istatistikleri devam ediyor.

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

Bu mantık, tek tek oyunların yaklaşık% 41'ini kazanarak Dreadnought'u yenmek zorunda olduğum en yakın mantık. (Aslında 52-49 sayı ile bir maç kazandı.) İşin garibi, bu sınıf FarnsworthOpponent'e karşı çok daha az gelişmiş olan eski bir versiyon olarak da iyi yapmıyor.


5

Bilgisayarım şu anda dell tarafından tamir ediliyor, ancak bu geçen hafta olduğum yer:

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
Gümüş için tebrikler. Algoritmanızı kelimelerle tarif etmeyi düşünür müsünüz? Bilmek ilginç olurdu.
Thomas Ahle

4

Eğer analizinizi zorlamak istiyorsanız, tedarik edilen RandomOpponent'in mekaniğini oldukça verimsiz bulabilirsiniz. Zaten hedeflenen yerleri yeniden seçmesine izin verir ve çerçevenin henüz dokunmadığı bir yere ulaşana veya hareket başına süre bitene kadar tekrarlanmasını sağlar.

Bu rakip benzer bir davranışa sahiptir (etkili yerleşim dağılımı aynıdır) sadece akıl sağlığını kontrol eder ve arama başına sadece bir rastgele sayı üretimi tüketir (amortismana tabi tutulur).

Bu benim uzantıları / kütüphane cevap sınıfları kullanır ve ben sadece anahtar yöntemler / durum sağlar.

Shuffle Jon Skeet'in cevabından kaldırıldı

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

Katılmayacağım, ama zamanım olsaydı uygulayacağım algoritma:

İlk olarak, bir vuruş tespit ettiğimde, geminin geri kalanını hemen takip etmiyorum - bir gemi yerlerinin tablosu oluşturuyorum ve onları tamamen batırmaya başlamadan önce en az bir kez vurup vurmadığımı anladım. (Bunun çoklu çekim varyantı için kötü bir politika olduğunu unutmayın - yorumlara bakın)

  1. Merkeze vurun (aşağıdaki son nota bakın - 'merkez' sadece açıklama için bir kolaylıktır)
  2. Merkezin sağındaki 4. noktaya basın
  3. Nokta 1'i aşağı ve merkezin sağında bir tane vurun
  4. Önceki isabetin sağındaki dördüncü noktaya ulaş
  5. Bu desende devam edin (tahtayı dolduran 3 boşlukla ayrılmış çapraz çizgilerle sonuçlanmalıdır) Bu, 4 ve 5 uzunluktaki teknelerin hepsine ve istatistiksel olarak çok sayıda 3 ve 2 tekneye çarpmalıdır.

  6. Çaprazlar arasındaki rastgele noktalara isabet etmeye başlayın, bu daha önce fark edilmemiş 2 ve 3 boy tekneleri yakalar.

5 isabet bulduğumda, 5 isabetin ayrı teknelerde olup olmadığını belirleyeceğim. Bu, iki vuruşun aynı yatay veya dikey çizgide olduğu ve birbirinin 5 konumunda olduğu konumların yakınında birkaç atış yaparak nispeten kolaydır (aynı teknede iki vuruş olabilir). Ayrı tekneler ise, tüm gemileri batırmaya devam edin. Aynı tekne oldukları tespit edilirse, yukarıdaki doldurma modellerine 5 tekne de yerleşene kadar devam edin.

Bu algoritma basit bir doldurma algoritmasıdır. Temel özellikleri, hala bilmediği gemiler olduğunda bildiği batıcı gemileri boşa harcamaması ve verimsiz bir doldurma kalıbı kullanmamasıdır (yani, tamamen rastgele bir model israf olacaktır).

Son notlar:

A) "Merkez" kart üzerinde rastgele bir başlangıç ​​noktasıdır. Bu, bu algoritmanın birincil zayıflığını ortadan kaldırır. B) Tanım, başlangıçtan hemen sonra köşegenleri çizmeyi gösterirken, ideal olarak algoritma yalnızca bu köşegenler boyunca yer alan 'rastgele' konumlarda çekim yapar. Bu, yarışmacının gemilerine ne kadar süre vurulacağını tahmin edilebilir kalıplarla zamanlamalarını önlemeye yardımcı olur.

Bu, (9x9) / 2 + 10 atış altındaki tüm gemileri alacağı için 'mükemmel' bir algoritmayı tanımlar.

Bununla birlikte, önemli ölçüde geliştirilebilir:

Bir gemi vurulduktan sonra, 'iç' diyagonal hatları yapmadan önce boyutunu belirleyin. 2 gemiyi bulmuş olabilirsiniz, bu durumda iç köşegenler 3 boyutlu gemileri daha hızlı bulmak için basitleştirilebilir.

Oyundaki aşamaları belirleyin ve buna göre hareket edin. Bu algoritma oyundaki belirli bir noktaya kadar iyi olabilir, ancak diğer algoritmalar oyunun bir parçası olarak daha iyi faydalar sağlayabilir. Ayrıca, diğer oyuncu sizi yenmeye çok yakınsa, başka bir algoritma daha iyi çalışabilir - örneğin yüksek riskli bir algoritma daha sık başarısız olabilir, ancak işe yaradığında hızlı çalışır ve kazanmaya daha yakın olan rakibinizi yenebilir .

Rakibin oyun stilini tanımlayın - size gemi yerleştirmeyi nasıl planladıklarına dair ipuçları verebilir (yani, kendi algoritmalarının kendi gemilerini nasıl yerleştirdiklerini en hızlı şekilde tanımlamaları iyidir - sahip olduğunuz tek araç bir çekiçse, her şey çivi gibi görünüyor)

-Adam


Her şey bulunana kadar gemileri batırmayı bekleme stratejisi büyük ölçüde dönüş başına bir atış varyasyonuna bağlıdır. (Hayatta kalan gemi sayısı) - dönüş başına görüntü sürümü altında, rakibinizi yavaşlatmak için gemileri mümkün olduğunca çabuk batırmak avantajlıdır.
Jason Owen

4

Benim girişim.

Çok özel bir şey yok ve sahip olduğum tüm iyi fikirleri eklemek için zaman bulamadım.

Ama oldukça iyi oynuyor gibi görünüyor. Yarışmada nasıl olduğunu göreceğiz:

(bunu dosyaya koyun Missouri.csve projeye ekleyin.)

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

Ve şimdi girişimi gönderdim, bazı kaba istatistikler: vs BP7% 44 kazanır. / vs Dreadnought% 20 kazanır. / vs. Farnsworth% 42 kazandı. Eğlenceli bir projeydi.
abelenky

2

Bu minimak değil. Aslında gemileri yerleştirdikten sonra, her oyuncu kendi başına oynayamaz ve her rakibin gemisini batırmak için bir dizi dönüşe neden olur mu? Daha az dönüş alan kişi kazanır.

İsabet gemilerini batırmanın ve gemilerin saklanabileceği kalan olası yerleri kapsamak için atış sayısını en aza indirmeye çalışmanın ötesinde iyi bir genel strateji olduğunu düşünmüyorum.

Tabii ki rastgele olmayan herhangi bir şey için karşı stratejiler olabilir. Ancak olası tüm oyunculara karşı iyi stratejiler olduğunu düşünmüyorum.


1
Potansiyel olarak, evet, kendi başlarına oynayabilirler. Bu böyle yürütülmeyecek. Yine de harika bir fikir. Bu yarışmada, rakibinizin atışlarından istatistiksel olarak kaçınmanın mümkün olmasını istiyorum.
John Gietzen

2
Anlıyorum. Aynı rakibe karşı önceki oyunlardan elde edilen verileri kullanarak ona adapte olabilir misiniz?
ziggystar

2

Aslında, bulmacanın en büyük probleminin aslında iki hareket olması. Bir hamle gemilerinizi yerleştirmek, diğeri düşman gemilerini bulmaktır (ancak ikinci bölümün rastgele bir faktörle bir saati yenmeye çalışmak dışında, sadece 'algoritmanızı çalıştırın'). Bir düşman stratejisini belirlemeye ve buna karşı koymaya çalışacak bir mekanizma yok, bu da birbirini takip eden "taş kağıt makas" turlarına dayalı benzer yarışmaları oldukça ilginç kılıyor.

Ayrıca, oyunu bir ağ protokolü olarak belirttiyseniz ve daha sonra tüm protokollerin C # olması gerektiğini dikte etmek yerine, bu protokolü C #'da uygulamak için çerçeve sağladıysanız daha havalı olacağını düşünüyorum, ama bu sadece benim görüşüm.

EDIT: Rekabet kurallarını yeterince dikkatle okumadığım için başlangıç ​​noktamı iptal ediyorum.


Tüm çözümler C # içinde olmamalıdır. Ayrı bir montajı derleyebilir ve bağlayabilirim. Ayrıca, rakibinize istatistiksel olarak karşı koyabilmelisiniz.
John Gietzen

J #? olabilir? Lol, jk. Bunun için bir TCP çerçevem ​​var, ancak bu turnuvanın çok hızlı çalışması gerekiyor.
John Gietzen

Neden aynı makinedeki iki işlem arasındaki TCP iletişiminin çok hızlı olmayacağını düşünüyorsunuz?
Jherico

@Jherico: TCP kullanıyor olsaydım, istedikleri CPU kaynaklarını kullanabilmeleri için motorları kendi bilgisayarlarında yalıtırdım.
John Gietzen

Yine de, aynı lan üzerindeki iki makine, ağ ek yükü minimum düzeyde olan bir oyunu bir saniyenin altında kolayca tamamlayabilir
Jherico

2

Her zaman ortadan başlamayı ve o noktadan uzaklaşmayı sevdim, o vaftiz altını hesaba katmak için diğer noktalar arasında en fazla 1 boşluk bıraktı ... atışlar arasındaki boşluk hangi gemilerin batırıldığına bağlıydı. B-gemisi son olsaydı, israf edilen atışları en aza indirmek için atışlar sadece 4 boşluk bırakmak zorunda kaldı


1
Yani ... sadece ortadan uzak durmam gerek mi? :)
darron

14
Ayrıca kenarlardan uzak durmanız gerekir, çünkü bir kenar vuruşu rakibiniz için kenar dışı bir vuruştan daha fazla bilgi içerir. Bu yüzden tüm gemilerinizi orta, kenarsız bir bölgeye yerleştirmelisiniz. Onlar buysa sürece bekliyor Yapmanız.
Jherico

1
Eğer 3 veya 4 boşluk bırakarak başlarsanız, yine de alt vurmak için yeterince şanslı olabilirsiniz. Değilse, geri dönün ve boşlukları doldurmayı deneyin. Daha fazla bilgi için: somethinkodd.com/oddthinking/2009/10/29/battleship-strategy
Oddthinking

18
İki delikli gemi lanet olası bir sub değil, lanet olası bir PT botu . Altta üç delik vardır. :)
kuzgun

2

İngiliz Bilgisayar Topluluğu adına Surrey Üniversitesi'nden Dr. James Heather tarafından da benzer bir yarışma gerçekleştirildi.

Kaynaklara sınırlamalar getirildi - yani tur başına maksimum işlemci süresi, hamleler arasında hiçbir durum depolanamadı, maksimum yığın boyutu uygulandı. Zamanı sınırlamak için AI, zaman dilimi içinde herhangi bir noktada bir hamle sunabilir ve dönüşün sona ermesi üzerine bir hamle istenebilir.

Çok ilginç - daha fazla bilgi için: http://www.bcsstudentcontest.com/

Size biraz daha fikir verebilir.


2

Olduğu gibi, çözüm ubuntu 9.10 linux'da monodevelop'ta değişiklik olmadan açılır ve çalışır


1

Sen yazdın:

  • Yarışma ruhuna aykırı olan her şey diskalifiye için zemin olacaktır.
  • Bir rakibe müdahale etmek yarışmanın ruhuna aykırıdır.

Lütfen "yarışma ruhuna karşı" ve "rakibe müdahale" yi tanımlayın.

Ayrıca - basitleştirmek için şunları tavsiye ederim:

  • rakibin CPU yuvası sırasında hiç CPU kullanarak izin vermeyin.
  • iş parçacığı paralelliğine izin vermeyin ve bunun yerine tek bir iş parçacığında daha fazla CPU saniye verin. Bu, AI'nın programlanmasını basitleştirecek ve zaten CPU / hafızaya bağlı olan kimseye zarar vermeyecektir.

PS - burada gizlenen CS post-dokümanlar için bir soru: Bu oyun çözülebilir değil (yani, tek bir en iyi strateji var mı?). evet, tahta boyutu ve adım sayısı minimax et al zorunlu kılar, ama yine de merak ediyorum ... Go ve satranç karmaşık değil.


"Müdahale" dediğimde aklımda bir yansıma vardı. Rakiplerin kazanmasını istemiyorum çünkü başka bir motoru ölüme döndürüyorlardı.
John Gietzen

8
Casusluğun modern savaşın önemli bir parçası olduğunu öneririm, bu yüzden hedefleri bulmanın yansıtılması ideal olacaktır - sonuçta, ikinci dünya savaşı sırasında kullanılan yöntemlerden biriydi ...
Rowland Shaw

Motorları farklı PC'lere yalıtmak, TCP / IP üzerinden iletişim kurmak, Yansımayı değersiz kılmak için bir çerçevem ​​var. Ancak, tahmini giriş sayımdan dolayı, bu yarışmanın yasaklanması uzun sürecektir.
John Gietzen

6
O zamanlar Yansıma olduğunu bilmiyordum!
Markus Nigbur

1

Rakiplerine rastgele tohum ve çağrı modelini tersine çevirmeyi başaran kişinin kazanacağını tahmin ediyorum.

Bunun ne kadar muhtemel olduğundan emin değilim.


Rakipler CSPRNG kullanma seçeneğine sahiptir.
John Gietzen

İyi bir nokta, ancak böyle bir tohumun tersine mühendisliklerin uzmanlığımın ötesinde olduğunu itiraf etmeliyim. Sanırım en savunmasız durum ateş paterni seçim algoritması olacaktı - ama o zaman bile, oyun başladıktan sonra gemilerinizi hareket ettirmenin hiçbir yolu olmadığından, onu kırmaktan çok fazla kazanamazsınız.
Triston Attridge

Araştırma stajı için başvururken, zırhlı programları yazdık ve yarıştık. Rastgele tohum ayarlayarak tam olarak X kazandım)
P Shved

1
Oldukça basit bir gemi yerleştirme algoritması varsayarsak, birinin farklı gemilerde birkaç vuruş yaptıktan sonra, olası tüm rastgele tohumlarda (muhtemelen şimdiki zamanın yakınında bir yerden başlayıp ileri / bir adım kadar geriye) ve hangilerinin gözlemlenen isabetlerle uyumlu gemi yerleşimleri oluşturduğunu görmek.
Domenic

1

Muhtemelen, oyundaki varyasyonlarla bunlardan bir dizi çalıştırmak da mümkün olacaktır.

Bir 3d uçak gibi şeyleri eklemek veya bir tur için ateş etmek yerine tek bir gemiyi hareket ettirmek, oyunu adil bir şekilde değiştirebilir.


2
"Salvo" varyasyonu var. Her turda kalan geminiz kadar atış yaptığınız yer.
John Gietzen

İlginç bir varyasyon da. Uçağa sahip bir bilgisayar versiyonunu oynadığımı hatırlıyorum. Rakip tahtadaki yerlerde rastgele ateş ederdi.
Glenn

başka bir varyasyon: geminin büyüklüğü + gemi sayısı.
russau

1

Bir saniyelik toplam oyun süresi makineye özeldir. Bir kez ikinci değer CPU işlemleri, makinemde turnuva makinesine göre farklı olacaktır. Savaş Gemisi algoritmasını en fazla CPU süresini 1 saniye içinde kullanacak şekilde optimize edersem, o zaman daha yavaş bir turnuva makinesinde çalıştırılır, her zaman kaybedilir.

Çerçevenin bu sınırlamasını nasıl aşacağınızdan emin değilim, ancak ele alınmalıdır.

...

Bir fikir, bu yarışmada ne yapıldığını yapmaktır http://www.bcsstudentcontest.com /

Ve maksimum toplam oyun süresinin aksine, tur başına maksimum süreye sahip olun. Bu şekilde algoritmaları biliyorum dönüş zamanına sığacak şekilde sınırlayabilirim. Bir oyun 50 ila 600+ tur sürebilir, algoritmam toplam oyun süresini yönetirse, en iyi işini yapmak için yeterli zaman vermeyebilir veya çok fazla zaman verebilir ve kaybedebilir. Toplam oyun süresini Savaş Gemisi algoritması içinde yönetmek çok zordur.

Toplam oyun süresini değil dönüş süresini sınırlamak için kuralları değiştirmenizi öneririm.

Düzenle

Mümkün olan tüm çekimleri numaralandıran ve sonra onları sıralayan bir algoritma yazsaydım, en yüksek dereceli çekimi alır. Tüm olası çekimleri oluşturmak çok uzun sürecek, bu yüzden algoritmanın belirli bir süre çalışmasına izin verip durduracağım.

Sıra tabanlı bir limit olsaydı, algoritmanın 0.9 saniye çalışmasına izin verebilir ve en yüksek sıradaki atışa geri dönebilirim ve dönüş süresi sınırına uyuyorum.

Bir saniyelik toplam oyun süresi ile sınırlıysam, algoritmanın her turda ne kadar süre çalışması gerektiğini belirlemek zor olacaktır. CPU zamanımı maksimuma çıkarmak isteyeceğim. Bir oyun 500 tur sürerse, her dönüşü 0.002 saniye ile sınırlayabilirim, ancak bir oyun 100 tur sürerse, her turda 0.01 saniye CPU süresi verebilirim.

Bir algoritmanın, mevcut sınırlama ile en iyi çekimi bulmak için atış alanının yarı kapsamlı bir aramasını kullanması pratik değildir.

1 saniyelik toplam oyun süresi, oyunda rekabet etmek için etkili bir şekilde kullanılabilecek algoritma türlerini sınırlandırmaktadır.


Bu bir Intel Q9550SX dört çekirdekli, 8 GB ram, Vista 64 makinesinde çalıştırılacak. 1 saniye sınırlayıcı bir faktör mü olacak?
John Gietzen

Sanırım o zaman aralığı başına maksimum atış sayısını hesaplamak için savaş gemisi AI'nızı çok iş parçacıklı yapmalısınız.
Jeff Atwood

hile, dönüş zaman aralığının nasıl sınırlanacağıdır. Eğer 0.00005 saniyeyi sınırlarsam, zaman sınırını aşmaktan güvende olurum, ancak arama alanını önemli ölçüde sınırlandırıyorum. Dönüş süresi sınırını artırırsam, arama alanı artar ancak zamanım tükenme riskiyle karşı karşıya kalırım.
TonyAbell

@TonyAbell: Sıra tabanlı bir zaman sınırına sahip olmanız önemliyse, neden bir başlangıç ​​değeri ile başlamıyorsunuz ve daha sonra onu turdan raya ayarlamıyorsunuz? Yaklaşık yarım turdan sonra muhtemelen karşılaştığınız rakip için en uygun dönüş uzunluğunu bulmuş olacaksınız.
kyokley

Kalan sürenizi takip etmeli ve kalan kullanılabilir sürenin 1 / 2'si ile sınırlamalısınız.
John Gietzen

1

Buraya gerçek kodu girmeden başa çıkıyorum - ama bazı genel gözlemleri tehlikeye atacağım:

  • Tüm gemiler en az 2 hücre boyutunda olduğundan, Space Quest V'de oyunun bir uygulamasında gördüğüm bir optimizasyonu kullanabilirsiniz - bu, bir hedef "ararken" sadece elmas desenindeki alternatif hücrelere ateş eder. Bu, tüm gemileri sonunda bulacağınızı garanti ederken, karelerin yarısını ortadan kaldırır.
  • Hedef ararken rastgele atış şekli birçok oyunda istatistiksel olarak en iyi sonuçları verir.

1

! [Olasılık Yoğunluğu] [1] resim açıklamasını girin

! [resim açıklamasını buraya girin] [2]

Randon çekiminin aptal bir av / hedef ile nihayet sofistike bir aramanın sonuçlarını karşılaştırmayı denedim.

En iyi çözüm, kalan gemiler tarafından herhangi bir karenin kullanılma olasılığı için bir olasılık yoğunluk fonksiyonu oluşturmak ve en yüksek değere sahip kareyi hedeflemek gibi görünmektedir.

Sonuçlarımı burada görebilirsiniz link açıklamasını buraya girin


Cevabınızı ve özellikle resimlerinizi ve bağlantınızı düzeltebilir misiniz?
Bart

-2

"Savaş gemisi" klasik bir bilgisayar bilimi NP-tam sorun olarak bilinen şeydir.

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

(Savaş Gemisi'ni arayın - orada, oyunların ve bulmacaların altında)


4
Hangi bir savaş gemisi bulmaca ( en.wikipedia.org/wiki/Battleship_(puzzle) ), oyun Battleship değil ( en.wikipedia.org/wiki/Battleship_(game) ).
Jason Berkan

Evet, Jason'ın belirttiği gibi, bu tamamen farklı bir hayvan.
John Gietzen

3
Hehehe. Bir sonraki görevim İşe aldığım NP tam olduğunu söyleyeceğim, sonra uzun bir öğle yemeği alacağım. :-)
Bork Blatt
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.