最高の戦艦AIとは何ですか?


315

戦艦!

2003年(17歳のとき)に、戦艦AIコーディングコンテストに参加しました。そのトーナメントに負けたとしても、私はたくさんの楽しみを持ち、そこから多くを学びました。

さて、最高の戦艦AIを求めて、この競争を復活させたいと思います。

これが、現在Bitbucketでホストされているフレームワークです。

勝者には+450の評判が与えられます!コンテストは2009年11月17日から開催されます。17日0時以降のエントリーまたは編集は受け付けられません。(中央標準時)エントリーを早く提出して、チャンスを逃さないようにしてください!

この目標を維持するには、コンテストの精神に従ってください。

ゲームのルール:

  1. ゲームは10x10グリッドでプレイされます。
  2. 各競技者は、5隻の船(長さ2、3、3、4、5)をそれぞれグリッドに配置します。
  3. 船を重ねることはできませんが、隣接する場合があります。
  4. 次に、選手は交代で相手にシングルショットを発射します。
    • ゲームのバリエーションでは、ボレーごとに複数のショットを撃つことができます。
  5. ショットが沈んだ、ヒットした、またはミスした場合、対戦相手は競技者に通知します。
  6. いずれかのプレイヤーのすべての船が沈没すると、ゲームプレイは終了します。

コンテストのルール:

  1. 競争の精神は、最高の戦艦アルゴリズムを見つけることです。
  2. コンテストの精神に反するとみなされるものはすべて失格の理由となります。
  3. 対戦相手に干渉することは、競争の精神に反します。
  4. マルチスレッドは、次の制限の下で使用できます。
    • 自分の番でないときは、1つのスレッドしか実行できません。(ただし、任意の数のスレッドが「一時停止」状態になる可能性があります)。
    • 「通常」以外の優先度でスレッドを実行することはできません。
    • 上記の2つの制限がある場合、あなたのターン中、少なくとも3つの専用CPUコアが保証されます。
  5. ゲームごとのCPU時間の1秒の制限は、プライマリスレッドの各競合他社に割り当てられます。
  6. 時間切れになると、現在のゲームが失われます。
  7. 未処理の例外があると、現在のゲームが失われます。
  8. ネットワークアクセスとディスクアクセスは許可されていますが、時間制限がかなり禁止されている場合があります。ただし、時間の負担を軽減するために、いくつかのセットアップとティアダウンの方法が追加されています。
  9. コードはスタックオーバーフローに回答として投稿するか、大きすぎる場合はリンクしてください。
  10. エントリの最大合計サイズ(非圧縮)は1 MBです。
  11. 公式には、.Net 2.0 / 3.5が唯一のフレームワーク要件です。
  12. エントリはIBattleshipOpponentインターフェイスを実装する必要があります。

得点:

  1. 101ゲーム中のベスト51ゲームがマッチの勝者です。
  2. すべての競技者は、ラウンドロビン形式でお互いにマッチしてプレーします。
  3. 競争者の最高の半分は、勝者を決定するためにダブルエリミネーショントーナメントをプレーします。(実際には、半分以上の最小の2の累乗。)
  4. トーナメントにTournamentApiフレームワークを使用します。
  5. 結果はこちらに掲載されます。
  6. 複数のエントリーを送信した場合、ベストスコアのエントリーのみがダブルエリムの対象となります。

幸運を!楽しんで!


編集1:関数にエラーを見つけたFreed
感謝します。修正されました。フレームワークの更新バージョンをダウンロードしてください。Ship.IsValid

編集2:
統計をディスクなどに永続化することに大きな関心があったので、必要な機能を提供するいくつかの非時間設定および破棄イベントを追加しました。これは半破壊的な変更です。つまり、インターフェイスは関数を追加するように変更されていますが、それらに本体は必要ありません。フレームワークの更新バージョンをダウンロードしてください。

EDIT 3:
バグ修正1:GameWonGameLostだけ時間外の場合に呼ばなりました。
バグ修正2:エンジンがすべてのゲームでタイムアウトした場合、競争が終了することはありませんでした。
フレームワークの更新バージョンをダウンロードしてください。

編集4:
トーナメント結果:


エントリに大規模なデータベースが必要な場合、ネット経由で接続できますか?すなわち。エントリはWebサービス呼び出しを行うことができますか?
Remus Rusanu 2009年

エントリーにサイズ制限はありますか?
Jherico、2009年

8
@Steven:また、Jeff Atwoodに相談して、それが適切かどうかを確認しました。これが彼の応答です: twitter.com/codinghorror/status/5203185621
John Gietzen '27 / 10/27

1
また、これらの50のゲームに不可避のランダムコンポーネントがあり、非常に優れた実装を正確に区別するのに十分ではないことを考慮して、追加します。どちらが良いかという合理的な見方には501以上が必要かもしれないと思います。
ShuggyCoUk 2009年

1
船を置くことを拒否する「平和な」対戦相手は競争を停止させます。あなたがそのような愚かなことをしている人々をどれほど気にかけているのかわかりません。:)
Joe

回答:


56

試合ごとにもっと多くのゲームをするための動きに私は賛成です。50ゲームを実行すると、コインを投げるだけです。テストアルゴリズムを適切に区別するには、1000ゲームを実行する必要がありました。

Dreadnought 1.2をダウンロードしてください。

戦略:

  • ヒット数が0を超える船のすべての可能な位置を追跡します。すべての船のすべての可能な位置のリスト(非常に大きい)とは異なり、リストは30Kを超えることはないため、正確に保持できます。

  • GetShotアルゴリズムには2つの部分があります。1つはランダムショットを生成し、もう1つはすでにヒットした船の沈没を試みます。ヒットしたすべての船が沈没する可能性がある位置(上記のリストから)がある場合は、ランダムなショットを行います。それ以外の場合は、最も可能性の高い位置(重み付け)を排除する場所を選択して、船の沈下を完了しようとします。

  • ランダムショットの場合は、沈没していない船の1つが位置と重なる可能性に基づいて、撮影に最適な位置を計算します。

  • 対戦相手が統計的に発砲する可能性が低い場所に船を配置する適応アルゴリズム。

  • 対戦相手が統計的に自分の船を配置する可能性が高い場所で撃つことを好む適応アルゴリズム。

  • 主に互いに接触していない船を配置します。


私のテストマシン(ULV Celeronネットブック)では、このコードは常にタイムアウトにより失われます。必要な時間をすべて費やすと、シンプル(約90%の成功率)が実現します。タイム
リミットに達する

興味深い...トーナメントマシンでは問題なく実行されます。ただし、「完全な」エンジンは、すでに費やしていた時間に適応します。
John Gietzen、2009年

35

こちらが私のエントリーです!(可能な限り最も単純な解決策)

「ランダム1.1」

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

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

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

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

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

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

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

52
それは非常に簡潔な... APIを使用して、競争するために実装する必要があると思います:)形成に示しているので、実際に、この答えはいいです
dicroce

1
大学のアルゴリズムクラスで同様のプロジェクトを作成したとき、私はランダムロジックをいくつかの意思決定と組み合わせて使用​​していました。たまに良かった!
Nathan Taylor、

2
これは重なった船を配置しようとすることができませんでしたか?

6
はい、しかしエンジンはこれを許可しません。その後、AIに再度配置するよう指示しますが、今回は、スターナーの声を使用します。(pop ax \ cmp ax, 1 \ je stern
閲覧者

5
私のように、以前に配置されたショットを思い出して繰り返しないことで簡単にこれを打つことができると考えた人にとって重要な注意事項。フレームワークは繰り返しを無視し、合計時間が制限を下回っている限り、もう一度チャンスを与えます。これは私の意見では貧弱であり、誰かが自分のアルゴを台無しにした場合、ペナルティが課せられるべきです...
ShuggyCoUk 2009年

22

対戦相手は次のとおりです。

固定された幾何学にインスパイアされた戦略を使用する代わりに、特定の未探索のスペースが船を保持する根本的な確率推定することを試みることは興味深いと思いました。

これを正しく行うには、現在の世界観に適合する船の可能な構成をすべて探索し、それらの構成に基づいて確率を計算します。あなたはそれを木の探検のように考えることができます:

可能な戦艦状態の拡大http://natekohl.net/media/battleship-tree.png

あなたが世界について知っているものとぶつかるその木のすべての葉を検討した後(たとえば、船はオーバーラップできない、すべてのヒットスクエアは船でなければならないなど)、探査されていない位置ごとに船が発生する頻度を数えて、その可能性を推定できますそこに船が座っています。

これはヒートマップとして視覚化でき、ホットスポットには船が含まれる可能性が高くなります。

未探索の位置ごとの確率のヒートマップhttp://natekohl.net/media/battleship-probs.png

このBattleshipコンテストで私が気に入っている点の1つは、上のツリーがこの種のアルゴリズムをブルートフォースで攻撃できるほど小さいことです。5隻の船それぞれに〜150の可能な位置がある場合、それは150 5 = 75億の可能性です。そして、特に船全体を排除できる場合は、その数は少なくなります。

上記にリンクした相手はツリー全体を探索しません。750億はまだ1秒もかからないほど大きい。ただし、いくつかのヒューリスティックを使用して、これらの確率を推定しようとします。


これまでのところ、あなたは私たちの他の唯一の完全なソリューションを約67.7%から32.3%で打ち負かしています:)
John Gietzen 2009年

2
「確率論的アプローチ」が「幾何学的アプローチ」とどのように比較されるのか、私は間違いなく興味があります。この確率の対戦相手が実際に他の回答で説明されている幾何学的パターンに従う動きをすることに気づきました。ジオメトリの使用も同様に優れており、はるかに高速である可能性があります。:)
ネイトコール、

12

完全な答えではありませんが、一般的なコードで実際の答えを混乱させることはほとんどありません。このように、私はオープンソースの精神でいくつかの拡張機能/一般クラスを提示します。これらを使用する場合は、名前空間を変更するか、すべてを1つのdllにコンパイルしようとしても機能しません。

BoardViewを使用すると、注釈付きのボードを簡単に操作できます。

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

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

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

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

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

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

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

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

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

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

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

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

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

        private Cell<T>[] history;

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

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

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

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

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

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

        #region IEnumerable<Cell<T>> Members

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

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

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

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

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

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

        #endregion
    }
}

一部の拡張機能、一部の拡張機能はメインフレームワークの機能を複製しますが、実際に行う必要があります。

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

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

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

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

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

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

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

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

私がよく使うもの。

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

ランダム化。安全ですがテスト可能で、テストに役立ちます。

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

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

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

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

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

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

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

10

現在、本格的なアルゴリズムを作成する時間はありませんが、ここで考えます。対戦相手がランダムに船を配置した場合、配置確率は(5.5,5.5)を中心とした単純な分布ではないでしょうか?たとえば、x次元での戦艦(5ユニット長)の配置の可能性は次のとおりです。

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

同じ計算がyにも有効です。他の船はそれほど急な分布ではないでしょうが、あなたの推測はまだ中心です。その後、数学的アプローチは、対角線を(おそらく平均的な船の長さ17/5で)中心からゆっくりと放射します。例:

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

明らかに、ある程度のランダム性をアイデアに追加する必要がありますが、純粋に数学的にはそれが進むべき道だと思います。


はい、そうです。私の古いエンジンはそれを補いました。
John Gietzen、2009年

1
私がどこから来たのか、対角線を中心からゆっくりと放射することは不正行為と見なされます。
bzlm 2009年

不正行為と考えられる場合、かなり簡単な対策があります。(x、y)は避けてください。:)
INE

5
私は彼がカードを数えることをほのめかしていたと思いますか?私の考えでは、これは不正行為ではありません。
John Gietzen、2009年

10

洗練されたものはありませんが、私が思いついたものがここにあります。それは時間の99.9%のランダムな相手を倒します。このような他の小さな課題があれば興味がありますか、それはとても楽しかったです。

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

わずかに縮小されており、ここで最小のスペースを占め、それでも読み取り可能です。


6

競合エンジンに関するコメント:

NewGameパラメータ:

IBattleshipOpponent :: NewGameがゲーム前のセットアップを目的としており、ボードサイズを取る場合、船とそれぞれのサイズのリストも必要です。船の構成を変えることなく、ボードサイズを変えることはできません。

船は密封されています:

クラス船が封印される理由は何も見当たらない。基本的に、Shipsに名前を付けたいので、次のようなメッセージを出力できます( "You sunk my {0}"、ship.Name); 。他の拡張も考えているので、Shipは継承可能だと思います。

時間制限:

トーナメントルールでは1秒という時間制限は理にかなっていますが、デバッグにはまったく問題があります。BattleshipCompetitionには、開発/デバッグを支援するために時間違反を無視する簡単な設定が必要です。また、System.Diagnostics.Process :: UserProcessorTime / Privileged ProcessorTime / TotalProcessorTimeを調査して、使用されている時間をより正確に把握することをお勧めします。

沈没船:

現在のAPIは、敵の船を沈めたときに通知します。

ShotHit(Point shot, bool sunk);

どの船を沈めたのかではありません!私はあなたが「私の戦艦を沈めた!」と宣言することを要求される人間の戦艦ルールの一部であると考えます。(または駆逐艦、潜水艦など)。

これは、AIが互いにぶつかる船を洗い流そうとしているときに特に重要です。次のAPI変更をリクエストしたい:

ShotHit(Point shot, Ship ship);

がnullでない場合、そのショットは沈没砲撃であり、沈没した船とその長さがわかります。ショットが沈没していないショットの場合、船は空であり、それ以上の情報はありません。


タイミングをより正確に行えると思われる場合は、コードサンプルを投稿してください。今はあまりルールを変えたくない。
John Gietzen、2009年

また、船のサイズは、ゲームごとに1回だけ実行されるPlaceShips()の間に渡され、セットアップフェーズとしても使用できます。自分でテストするために自由に船を開封してください。しかし、私はトーナメントに封印されたものを使用する予定です。
John Gietzen、2009年

バグ:@John Gietzen:PlaceShipsはゲームごとに1回だけ実行されるわけではないと判断しました(ご指摘のとおり)。プレイヤーが(RandomOpponentが頻繁に行うように)船を誤って配置した場合、PlaceShipsは、介入するNewGame呼び出しなしで繰り返し呼び出されます。
abelenky 2009年

5
私は常に、2隻の船をL字型に配置して、実際には沈んでいないのに相手が戦艦を沈めたと思うようにする戦略を常に考えていました。どのボートが沈んだかを宣言しなければならないという印象は決してありませんでした。
ジョシュスミートン

3
@DJ:私は元のペンと紙のルールに従っています。ハスブロはおもちゃの会社であり、このゲームはハスブロよりも古いことを覚えておいてください。
John Gietzen、2009年

5

CrossFireが更新されました。ファーンズワースやドレッドノートと競合できないことは知っていますが、ファーンズワースやドレッドノートよりもはるかに高速で、誰かが試してみたい場合に備えて簡単に操作できます。これは、使いやすくするためにここに含まれている私のライブラリの現在の状態に依存しています。

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

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

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

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

        public void NewMatch(string opponent) {}

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

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

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

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

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

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

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

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

        public const bool DebugEnabled = false;

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

        public void GameWon()
        {
        }

        public void GameLost()
        {
        }

        public void MatchOver()
        {
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

            private Cell<T>[] history;

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

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

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

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

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

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

            #region IEnumerable<Cell<T>> Members

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

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

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

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

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

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

            #endregion
        }

        public class Rand
        {
            Random r;

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

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

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

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

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

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

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

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

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

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

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

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

}


5

これは、私が自由時間にまとめることができる最高のものであり、存在しないものです。キーを押すまでループし、BattleshipCompetitionを継続的に実行するようにメイン関数を設定しているので、いくつかのゲームと試合の集計統計が進行中です。

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

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

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

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

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

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

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

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

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

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

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

        public Point GetShot()
        {
            Point shot;

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

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

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

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

            return shot;
        }

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

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

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

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

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

            return ctr;
        }

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

        public void OpponentShot(Point shot) { }

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

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

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

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

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

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

        public void GameWon()
        {
            wins++;
        }

        public void GameLost()
        {
            losses++;
        }

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

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

            totalWins += wins;
            totalLosses += losses;

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

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

このロジックは、私がドレッドノートを倒す必要があったものに最も近く、個々のゲームの約41%を獲得しています。(実際には52から49のカウントで1つの試合に勝ちました。)奇妙なことに、このクラスはFarnsworthOpponentに対して、はるかに進んでいなかった以前のバージョンほどうまくいきません。


5

私のコンピューターは現在デルによって修理されていますが、これが先週の状況です。

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
シルバーおめでとうございます。アルゴリズムを言葉で説明していただけませんか?知っておくと面白いでしょう。
Thomas Ahle、

4

分析を力ずくで強制している場合は、提供されているRandomOpponentのメカニズムが非常に非効率的であることがわかります。それはそれ自体がすでにターゲットにされた場所を再選択することを可能にし、フレームワークがそれがまだ触れていない場所に到達するか、移動ごとの制限時間が経過するまでそれを繰り返すことを強制します。

この対戦相手は同様の動作をします(効果的な配置の分布は同じです)。それ自体が健全性チェックを実行し、呼び出しごとに1つの乱数生成のみを消費します(償却済み))。

これは私の拡張機能/ライブラリの回答のクラスを使用し、主要なメソッド/状態のみを提供します。

シャッフルはジョン・スキートの答えからここで解除されます

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

参加できなくなりますが、時間がある場合に実装するアルゴリズムは次のとおりです。

まず、ヒットを見つけたらすぐに船の残りの部分を追跡しません。船の位置のテーブルを作成し、完全に沈み始める前に、少なくとも5回すべてヒットしたかどうかを確認します。(これはマルチショットバリアントには不適切なポリシーです。コメントを参照してください)

  1. 中央を押す(下の最後の注意を参照-「中央」は説明のための便宜にすぎません)
  2. 中心の右側にあるスポット4をヒット
  3. スポットを1つ下から中央の1つ右に打つ
  4. 前のヒットの右にあるスポットを4つヒットする
  5. そのパターンで続行します(ボードを埋める3つのスペースで区切られた対角線で終わる必要があります)これは、すべての4と5の長さのボート、および統計的に多数の3と2ボートをヒットする必要があります。

  6. 対角線の間のスポットをランダムに打つことから始めます。これは、まだ気づかれていない2と3の長さのボートをキャッチします。

5つのヒットを検出したら、5つのヒットが別のボートにあるかどうかを判断します。これは、2つのヒットが同じ水平線または垂直線上にあり、互いに5つの位置内にある(同じボートで2つのヒットになる可能性がある)位置の近くでさらに数ショットをショットすることで比較的簡単です。それらが別々のボートである場合、すべての船を沈め続けます。同じボートであることが判明した場合は、5つのボートすべてが見つかるまで上記の充填パターンを続けます。

このアルゴリズムは単純な充填アルゴリズムです。主な機能は、気づいていない船があるときに知っている船を沈める時間を無駄にしないこと、および非効率的な充填パターンを使用しないことです(つまり、完全にランダムなパターンは無駄になります)。

最終メモ:

A)「センター」はボード上のランダムな開始点です。これにより、このアルゴリズムの主な弱点が排除されます。B)説明は最初から対角線を描くことを示していますが、理想的にはアルゴリズムはそれらの対角線に沿った「ランダムな」位置で単に発射するだけです。これは、予測可能なパターンに船が当たるまでの時間を競合他社が計るのを防ぐのに役立ちます。

これは、(9x9)/ 2 + 10ショット未満のすべての船を取得できるという点で「完璧な」アルゴリズムを表しています。

ただし、大幅に改善できます。

船に命中したら、「内部」対角線を描く前にその船のサイズを確認してください。2隻の船を見つけた可能性があります。その場合、内部の対角線を簡略化して、3サイズの船をより迅速に見つけることができます。

ゲームのステージを特定し、それに応じて行動します。このアルゴリズムはゲームの特定の時点までは問題ないかもしれませんが、他のアルゴリズムはエンドゲームの一部としてより良い利益をもたらすかもしれません。また、他のプレーヤーがあなたを倒すのに非常に近い場合、別のアルゴリズムがよりうまく機能する可能性があります-たとえば、ハイリスクアルゴリズムはより頻繁に失敗する可能性がありますが、うまくいくと、あなたよりも勝ちに近い相手に打ち勝つ可能性があります。

競合他社のプレイスタイルを特定します-船の配置を計画する方法についての手掛かりを与える可能性があります(つまり、独自のアルゴリズムが自分の船を配置する方法を最も迅速に識別する可能性は高いです-ハンマーが唯一のツールである場合、すべてが爪のように見えます)

-アダム


すべてが見つかるまで船を沈めるのを待つ戦略は、ターンごとのワンショットのバリエーションに大きく依存します。(生存している船の数)ターンごとのショットバージョンでは、敵を遅くするために、できるだけ早く船を沈めることが有利です。
Jason Owen、

4

私のエントリー。

それほど特別なことは何もありませんでした。自分が持っている優れたアイデアをすべて追加する時間もありませんでした。

しかし、それはかなりうまくいくようです。競争でどのように機能するかを見てみましょう。

(これをファイルに入れMissouri.csてプロジェクトに追加します。)

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

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

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

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

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

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

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

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

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

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

            shotBoard[shot] = Shot.Unresolved;

            return shot;
        }

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

            Board potential = new Board(size);

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

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

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

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

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

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

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

#endregion 

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

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

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

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

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

            Point p;

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

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

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

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

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

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

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

            return bors.ToArray();
        }

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

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

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

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

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

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

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

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

            return new Point(x,y);
        }

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

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

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

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

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

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

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

            return output;
        }

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

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

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

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

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

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

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

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

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

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

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

        public void ShotHit(Point p)
        {
        }

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

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

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

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

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

            return potential;
        }

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

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

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

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

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

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

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

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

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

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

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

そして、私は私のエントリーを提出したので、いくつかの大まかな統計:対BP7 44%勝利。/ vs.ドレッドノート20%勝利。/ファーンズワース対42%の勝利。楽しいプロジェクトでした。
abelenky 2009年

2

これはミニマックスではありません。実際に船を配置した後、各プレーヤーが単独でプレイすることはできません。その結果、すべての対戦相手の船を沈めるのに数ターンかかることになりますか?ターン数が少ない方が勝ちです。

ヒットした船を沈め、ショットの数を最小限にして船が隠れている可能性のある残りの場所をカバーする以外に、一般的な戦略はないと思います。

もちろん、ランダムではないものには対抗戦略があるかもしれません。しかし、私はすべての可能なプレイヤーに対して良い戦略があるとは思いません。


1
潜在的に、はい、彼らは自分で遊ぶことができました。これは、これが実行される方法ではありません。素晴らしいアイデアですが。今回の大会では、統計的に相手のショットを避けられるようにしたい。
John Gietzen、2009年

2
そうですか。同じ対戦相手に対する以前のゲームのデータを使用すると、彼に適応できる可能性がありますか?
ziggystar 2009年

2

実際、パズルの最大の問題は、本質的に2つの動きがあることだと思います。1つの動きはあなたの船を配置することであり、もう1つは敵の船を見つけることです(ただし、ランダムな要素で時計を倒そうとすることを除けば、2番目の部分はセグメント化されていますが、「アルゴリズムを実行する」)。敵の戦略を決定して対抗しようとするメカニズムはありません。このため、「じゃんけん」の連続ラウンドに基づいた同様の競争は非常に興味深いものです。

また、ゲームをネットワークプロトコルとして指定し、そのプロトコルをC#で実装するためのフレームワークを提供すると、すべてのソリューションがC#である必要があると指示するよりも、クールだと思いますが、それは私の意見です。

編集:競技規則を注意深く読んでいないため、最初のポイントを取り消しました。


すべてのソリューションがC#である必要はありません。別のアセンブリをコンパイルしてリンクインできます。また、統計的に対戦相手に対抗できるはずです。
John Gietzen、2009年

J#?多分?笑、jk。私はこれのためのTCPフレームワークを持っていますが、このトーナメントは非常に迅速に実行する必要があります。
John Gietzen、2009年

同じマシン上の2つのプロセス間のTCP通信が非常に高速ではないと想定するのはなぜですか?
Jherico、2009年

@Jherico:TCPを使用していた場合、自分のPCのエンジンを分離して、必要なCPUリソースを使用できるようにします。
John Gietzen、2009年

そうであっても、同じLAN上の2台のマシンが簡単にネットワークのオーバーヘッドが最小限であることを第二の下でゲームを完了でき
Jherico

2

私はいつも真ん中から始めて、その1点から離れてらせん状に曲がり、他の点の間に空白を1つだけ残して、そのゴッドダムサブを説明するのが好きでした。ショット間のスペースは、沈んだ船に依存していました。B船が最後だった場合、ショットは無駄なショットを最小限に抑えるために間に4つのスペースを残すだけで済みました。


1
だから...私は真ん中から離れる必要がありますか?:)
ダロン

14
エッジヒットには、非エッジヒットよりも対戦相手の情報が多く含まれるため、エッジから離れる必要もあります。ですから、すべての船を非中央、非エッジ領域に配置する必要があります。それが彼らがあなたにすることを期待していることでない限り。
Jherico、2009年

1
3つまたは4つのスペースを残して開始する場合、とにかくサブをヒットするのに十分幸運かもしれません。そうでない場合は、戻ってギャップを埋めてみてください。詳細:somethinkodd.com/oddthinking/2009/10/29/battleship-strategy
Oddthinking

18
2つの穴のある船はおかしな潜水艦ではなく、おかしなPTボートです。サブには3つの穴があります。:)
レイヴン

2

イギリスコンピュータ学会に代わってサリー大学のジェームズヘザー博士が運営する同様のコンテストがありました。

リソースに制限が課されました。つまり、1ターンあたりの最大プロセッサー時間、移動間で状態を保存できず、最大ヒープサイズが課されました。時間を制限するために、AIはタイムスロット内の任意の時点で移動を送信でき、ターンの終了時に移動を要求されます。

非常に興味深い-詳細については、http//www.bcsstudentcontest.com/を参照してください

あなたにいくつかのより多くのアイデアを与えるかもしれません。



1

あなたが書いた:

  • コンテストの精神に反するとみなされるものはすべて失格の理由となります。
  • 対戦相手に干渉することは、競争の精神に反します。

「競争の精神に対して」と「対戦相手に干渉すること」を定義してください。

また、簡単にするために、次のことをお勧めします。

  • 対戦相手のCPUスロット中はCPUをまったく使用できないようにします。
  • スレッドの並列処理を禁止し、代わりに1つのスレッドでより多くのCPU秒を与えます。これにより、AIのプログラミングが簡素化され、CPU /メモリに縛られている人を傷つけることはありません。

PS-ここに潜むCSポストドキュメンテーションへの質問:このゲームは解決可能ではありません(つまり、単一の最良の戦略はありますか?)。はい、ボードのサイズとステップ数により、ミニマックスなどが必須になりますが、それでも疑問に思う必要があります。


「干渉する」と言ったときのことを思い出しました。他のエンジンをいじって死ぬので、競争相手に勝ってほしくない。
John Gietzen、2009年

8
スパイ活動は現代の戦争の重要な部分であることをお勧めします。したがって、ターゲットを見つけるために反映することは理想的です。結局のところ、それは第二次世界大戦中に使用された方法の1つでした...
Rowland Shaw

エンジンをさまざまなPCに分離し、TCP / IPを介して通信し、Reflectionの価値をなくすためのフレームワークがあります。しかし、私の推定エントリー数により、これは競争に非常に長い時間がかかるようになります。
John Gietzen、2009年

6
彼らが当時Reflectionを持っているとは知りませんでした!
Markus Nigbur、2009年

1

相手のランダムシードとコールパターンをリバースエンジニアリングできた人が勝つと予測します。

それがどれほど可能性が高いかわかりません。


対戦相手は、CSPRNGを使用するオプションがあります。
John Gietzen、2009年

良い点ですが、そのようなシードをリバースエンジニアリングすることは、とにかく私の専門知識を超えています。最も脆弱な側面は火のパターン選択アルゴリズムであろうと思いますが、ゲームが始まったら船を動かすことができないので、それを解くことから必ずしも多くを得ることはできません。
Triston Attridge、2009年

私が研究インターンシップに応募したとき、私たちは戦艦プログラムを書いて競争しました。)ランダムシードを、私はXを獲得した正確にどのようにして設定することで
P Shved

1
かなり単純な船配置アルゴリズムを想定すると、さまざまな船で数回ヒットした後、すべての可能なランダムシードをループするターンの大部分を使用し始めることができると思います(おそらく、現在の時間の近くのどこかから始まり、前進します/後方に1ステップほど)、観測されたヒットと互換性のある船の配置を生成するものを確認します。
Domenic

1

また、ゲームのバリエーションでこれらのシリーズを実行することも可能でしょう。

3D飛行機のようなものを追加したり、ターンのシュートの代わりに1隻の船を動かせるようにしたりすると、ゲームはかなり変わります。


2
「サルボ」バリエーションがあります。あなたが残っている船を持っているのと同じくらい多くのショットをターンごとに撃つことができる場所。
John Gietzen、2009年

面白いバリエーションも。飛行機を搭載したコンピューター版をプレイしたことを覚えているようです。それは反対側のボードの場所でランダムに発砲します。
Glenn、

別のバリエーション:ボードのサイズ+船の数。
russau 2009年

1

1秒の合計ゲーム時間はマシン固有です。私のマシンでは、1秒に1回のCPUオペレーションがトーナメントマシンとは異なります。1秒以内に最大のCPU時間を使用するようにバトルシップアルゴリズムを最適化すると、低速のトーナメントマシンで実行され、常に失われます。

フレームワークのこの制限を回避する方法はわかりませんが、対処する必要があります。

...

1つのアイデアは、このコンテストで行われたことを行うことですhttp://www.bcsstudentcontest.com /

また、最大合計ゲーム時間とは対照的に、1ターンあたりの最大時間があります。このようにして、既知のターン時間内に収まるようにアルゴリズムを制限できます。ゲームは50〜600ターン以上続く場合があります。私のアルゴリズムがゲームの合計時間を管理している場合、最高の仕事をするのに十分な時間が与えられないか、時間がかかりすぎて負ける可能性があります。戦艦アルゴリズム内で合計ゲーム時間を管理することは非常に困難です。

ルールを変更して、総ゲーム時間ではなくターン時間を制限することをお勧めします。

編集する

可能なショットをすべて列挙してランク付けするアルゴリズムを作成した場合は、最も高いランクのショットを取ります。可能なすべてのショットを生成するには時間がかかりすぎるため、アルゴリズムを一定時間実行してから停止します。

ターンベースの制限があった場合、アルゴリズムを0.9秒間実行させて最高のランキングショットを返し、ターン時間の制限に十分耐えることができます。

合計ゲーム時間を1秒に制限すると、各ターンでアルゴリズムを実行する時間を決定するのが難しくなります。CPU時間を最大にしたいと思います。ゲームが500ラウンド続くと、各ターンを0.002秒に制限できますが、ゲームが100ラウンド続くと、各ターンに0.01秒のCPU時間を与えることができます。

アルゴリズムがショットスペースの半網羅的な検索を使用して、現在の制限で最良のショットを見つけることは非現実的です。

合計1秒のゲーム時間は、ゲームでの競争に効果的に使用できるアルゴリズムのタイプを制限しています。


これは、Intel Q9550SXクアッドコア、8 GB RAM、Vista 64マシンで実行されます。1秒が制限要因になりますか?
John Gietzen、2009年

戦艦AIをマルチスレッド化して、その時間間隔あたりの最大ショット数を計算する必要があると思います。
Jeff Atwood、

トリックは、ターン時間間隔を制限する方法です。0.00005秒に制限すると、制限時間を超えても安全ですが、検索スペースが大幅に制限されます。ターンタイム制限を増やすと、検索スペースは増えますが、時間切れになるリスクがあります。
TonyAbell 2009年

@TonyAbell:ターンベースの制限時間を設けることが重要な場合は、初期値から始めて、ラウンドごとに調整してみませんか?約半分のラウンドの後、あなたはおそらくあなたが直面している対戦相手に最適なターンの長さを見つけたでしょう。
kyokley 2009年

残り時間を追跡し、残り時間の半分に制限する必要があります。
John Gietzen、2009年

1

私は実際のコードを入れないことでここに手を出しています-しかし、いくつかの一般的な観察を危険にさらすでしょう:

  • すべての船のサイズは少なくとも2セルなので、スペースクエストVでのゲームの実装で見た最適化を使用できます。これは、ターゲットを「求めている」間、ダイヤモンドパターンの代替セルでのみ発砲します。これにより、正方形の半分が削除されますが、最終的にすべての船を見つけることが保証されます。
  • ターゲットを探すときのランダムな発砲パターンは、統計的に多くのゲームで最高の結果をもたらします。

1

![確率密度] [1]画像の説明を入力してください

![ここに画像の説明を入力] [2]

私は、ランドン射撃の結果とダムハント/ターゲットの結果を比較する実験を行い、最後に高度な検索を行いました。

最良の解決策は、残りの船が個々の正方形を使用する可能性について確率密度関数を作成し、最も高い値の正方形を狙うことです。

ここ私の結果を見ることができますここにリンクの説明を入力してください


おそらくあなたの答え、特にあなたの画像とリンクを修正できますか?
Bart

-2

「戦艦」は、古典的なコンピュータサイエンスのNP完全問題として知られているものです。

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

(戦艦を探してください-それはゲームとパズルの下にあります)


4
どの戦艦パズル(あるen.wikipedia.org/wiki/Battleship_(puzzle) )ではなく、戦艦ゲーム(en.wikipedia.org/wiki/Battleship_(game) )。
Jason Berkan、

ええ、ジェイソンが言ったように、これは完全に異なる動物です。
John Gietzen、2009年

3
へへへ。次の仕事で私はそれがNP完了だと言って、長い昼食を取ります。:-)
Bork Blatt
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.