最好的战舰AI是什么?


315

战舰!

早在2003年(那时我才17岁),我参加了一个Battleship AI编码竞赛。即使我输掉了比赛,我仍然从中获得很多乐趣并从中学到了很多。

现在,我想复活这场比赛,以寻找最好的战舰AI。

这是框架,现在托管在Bitbucket上

获胜者将获得+450声望!比赛将于2009年11月17日开始。17日晚零时之前不接受任何输入或编辑。(中部标准时间)尽早提交参赛作品,这样您就不会错过任何机会!

为了保持目标,请遵循比赛的精神。

游戏规则:

  1. 游戏在10x10的网格上进行。
  2. 每个参赛者将把5艘船(长度分别为2、3、3、4、5)放置在各自的网格上。
  3. 没有船可以重叠,但它们可以是相邻的。
  4. 然后,竞争对手轮流向对手射击。
    • 游戏的一种变体允许每排射击多发,每艘幸存飞船射击一次。
  5. 如果击球落下,命中或未击中,对手将通知竞争对手。
  6. 任何一名玩家的全部船只沉没时,游戏结束。

比赛规则:

  1. 竞争的精神是找到最佳的战舰算法。
  2. 任何违反比赛精神的行为都将被取消参赛资格。
  3. 干扰对手是违反比赛精神的。
  4. 在以下限制下,可以使用多线程:
    • 轮到您时,最多只能运行一个线程。(但是,任何数量的线程都可以处于“挂起”状态)。
    • 除“正常”外,没有其他线程可以优先运行。
    • 鉴于上述两个限制,在轮到您时,将确保至少有3个专用CPU内核。
  5. 每个游戏在主线程上分配给每个竞争者1秒的CPU时间限制。
  6. 时间用完会导致当前游戏失败。
  7. 任何未处理的异常都将导致当前游戏失败。
  8. 允许进行网络访问和磁盘访问,但是您可能会发现时间限制过高。但是,添加了一些设置和拆卸方法以减轻时间负担。
  9. 应将代码发布在堆栈溢出中作为答案,或者如果链接太大,则将其链接。
  10. 条目的最大总大小(未压缩)为1 MB。
  11. 正式地,.Net 2.0 / 3.5是唯一的框架要求。
  12. 您的条目必须实现IBattleshipOpponent接口。

得分:

  1. 101场比赛中最好的51场比赛是比赛的获胜者。
  2. 所有竞争对手将以循环赛的方式进行比赛。
  3. 然后,最好的一半竞争者将参加双重淘汰赛以确定获胜者。(实际上,两个的最小乘方大于或等于一半。)
  4. 我将在锦标赛中使用TournamentApi框架。
  5. 结果将发布在这里。
  6. 如果您提交了多个条目,则只有得分最高的条目才有资格获得双盲。

祝好运!玩得开心!


编辑1:
感谢Freed,他在Ship.IsValid函数中发现错误。已修复。请下载该框架的更新版本。

编辑2:
由于将统计信息持久保存到磁盘等方面引起了极大的兴趣,我添加了一些非定时的设置和拆除事件,这些事件应提供所需的功能。这是一个突破性的变化。这就是说:接口已被修改以添加功能,但是它们不需要任何主体。请下载该框架的更新版本。

编辑3:
错误修正1:GameWon并且GameLost仅在超时的情况下被调用。
错误修正2:如果每场比赛都有引擎在计时,比赛将永远不会结束。
请下载该框架的更新版本。

编辑4:
比赛结果:


如果条目需要大型数据库,可以通过网络连接到它吗?就是 该条目可以拨打网络服务电话吗?
Remus Rusanu

条目有大小限制吗?
Jherico

8
@Steven:另外,我咨询了Jeff Atwood,看是否合适。下面是他的回答: twitter.com/codinghorror/status/5203185621
约翰Gietzen

1
我还要补充一点,因为这50款游戏中不可避免的随机成分不足以准确地区分非常好的实现。我认为可能需要501或更大的值才能得出合理的结论。
ShuggyCoUk,2009年

1
拒绝放置船只的“和平”对手会导致比赛中止。不知道您对这样的愚蠢人有多在意。:)

Answers:


56

我赞成每场比赛做更多比赛的议案。做50场比赛只是掷硬币。我需要做1000场比赛才能在测试算法之间取得合理的区分。

下载Dreadnought 1.2

策略:

  • 跟踪命中率大于0的船只的所有可能位置。该列表永远不会超过〜30K,因此可以完全保留,这与所有船只的所有可能位置列表(很大)不同。

  • GetShot算法分为两部分,一部分产生随机射击,另一部分试图完成击沉已经击中的飞船。如果有可能击沉所有命中船只的位置(从上面的列表中),我们将进行随机射击。否则,我们会尝试通过选择要消除最可能位置(加权)的射击位置来完成下沉船只的任务。

  • 对于随机射击,请根据未沉没的飞船之一重叠该位置的可能性来计算最佳射击位置。

  • 自适应算法,将船只放置在对手在统计上不太可能射击的位置。

  • 自适应算法,它倾向于在对手在统计上更有可能放置其船只的位置射击。

  • 放置船只大多不会互相接触。


在我的测试机(ULV Celeron上网本)上,此代码始终因超时丢失。当我不停地花费所有时间时,它希望它变得简单(成功率大约为90%)。如果您严重依赖机器的规格来运行时间限制,则可能需要给自己一些摆动的空间……
ShuggyCoUk

有趣的...它在锦标赛机器上运行良好。但是,“完美”的引擎将适应它已经花费的时间。
John Gietzen

35

这是我的条目!(可能是最幼稚的解决方案)

“随机1.1”

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

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

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

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

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

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

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

52
实际上,这个答案很好,因为它以非常简洁的形式显示了您需要实现竞争的API……:)
dicroce

1
回到我在大学算法班上建立一个类似项目时,我使用了随机逻辑和一些决策。有时候很好!
内森·泰勒

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

我喜欢这个战舰竞赛的一件事是,上面的树几乎很小,足以蛮力地使用这种算法。如果5艘船中的每艘船都有约150个可能的职位,那么150 5 = 750亿个可能性。而且这个数字只会变小,特别是如果您可以消灭整艘船的话。

我上面链接的对手不会探索整棵树;不到一秒,仍有750亿美元的巨额资金。但是,它确实尝试借助一些启发式方法来估计这些概率。


到目前为止,您的性能比我们唯一的其他完整解决方案高出约67.7%至32.3%:)
John Gietzen

2
我绝对很好奇看到“概率方法”与“几何方法”相比。我注意到,这种可能性的对手实际上是按照其他答案中讨论的几何图案进行移动的。可能是因为使用几何图形同样好,而且速度更快。:)
Nate Kohl

12

这不是一个完整的答案,但是用通用的代码使真正的答案杂乱无章。因此,我本着开源的精神介绍了一些扩展/通用类。如果使用这些,则请更改名称空间或尝试将所有内容编译为一个dll无法正常工作。

BoardView使您可以轻松地使用带注释的板。

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

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

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

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

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

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

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

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

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

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

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

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

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

        private Cell<T>[] history;

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

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

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

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

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

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

        #region IEnumerable<Cell<T>> Members

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

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

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

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

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

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

        #endregion
    }
}

一些扩展,其中一些重复了主框架中的功能,但实际上应该由您完成。

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

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

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

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

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

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

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

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

我最终使用了很多东西。

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

随机化。安全但可测试,对测试很有用。

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

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

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

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

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

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

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

10

我现在没有时间编写一个完整的算法,但是有一个想法:如果您的对手随机放置船只,放置概率不是以(5.5,5.5)为中心的简单分布吗?例如,在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....
...........

显然,需要在这个想法上添加一些随机性,但是我认为纯粹从数学上讲,这是要走的路。


是的,的确可以。我的旧发动机弥补了这一点。
约翰·吉森

1
我从哪里来,慢慢辐射对角线出中心被认为是作弊
bzlm

如果被认为是作弊,那么有一个很简单的对策。避免在x = y处使用(x,y)。:)
国家统计局

5
我认为他是在指点卡?我认为,这不是在作弊。
约翰·吉森

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适用于赛前设置并采用局面尺寸,则还应采用船舶及其各自尺寸的列表。如果不考虑可变的船型,那么允许可变的电路板尺寸是没有意义的。

船舶被密封:

我看不到为什么Ship类被密封的任何原因。除其他基本事项外,我希望Ships具有一个Name,因此我可以输出如下消息:(“您沉没了{0}”,ship.Name);。我还考虑了其​​他扩展,因此我认为Ship应该是可继承的。

时间限制:

虽然1秒的时限对于锦标赛规则是有意义的,但它与调试完全搞混了。战舰竞赛应该有一个简单的设置来忽略时间违规,以帮助进行开发/调试。我还建议调查System.Diagnostics.Process :: UserProcessorTime / Privileged ProcessorTime / TotalProcessorTime,以更准确地了解正在使用多少时间。

沉船:

当您击沉了对手的飞船时,当前的API会通知您:

ShotHit(Point shot, bool sunk);

但不是船沉没你!我认为这是人类战舰规则的一部分,要求您声明“您击沉了我的战舰!” (或驱逐舰或潜艇等)。

当AI试图冲撞彼此抵靠的船只时,这一点尤其重要。我想将API更改为:

ShotHit(Point shot, Ship ship);

如果不为空,则表示该射击是下沉的,您知道您沉没了哪艘船,以及沉没了多长时间。如果该射击是非下沉射击,则ship为空,并且您没有更多信息。


如果您认为计时可以更准确地进行,请张贴代码示例。我现在不想过多更改规则。
约翰·吉岑

另外,船只尺寸在PlaceShips()期间传递,而PlaceShips()每个游戏仅运行一次,也可以用作设置阶段。请随时为您的测试打开船密封,但我计划在比赛中使用密封的船。
约翰·吉岑

BUG:@John Gietzen:我确定PlaceShips并非每个游戏都运行一次(如您所述)。如果玩家错误地放置了自己的飞船(就像RandomOpponent通常所做的那样),则会重复调用PlaceShips,而不会介入NewGame。
abelenky

5
我一直认为将两艘船配置成L形的策略是让我的对手以为他们沉没了一艘战舰,而实际上却没有。我从没有想过要宣布那艘船沉没了。
乔什·史密顿

3
@DJ:我要遵循原始的纸笔规则。请记住,孩之宝是一家玩具公司,该游戏早于孩之宝。
John Gietzen

5

CrossFire已更新。我知道它无法与Farnsworth或Dreadnought竞争,但它比后者快得多,并且易于操作,以防有人尝试。这取决于我的库的当前状态,为了易于使用,此处将其包含在此处。

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

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

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

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

        public void NewMatch(string opponent) {}

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

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

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

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

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

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

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

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

        public const bool DebugEnabled = false;

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

        public void GameWon()
        {
        }

        public void GameLost()
        {
        }

        public void MatchOver()
        {
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

            private Cell<T>[] history;

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

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

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

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

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

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

            #region IEnumerable<Cell<T>> Members

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

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

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

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

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

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

            #endregion
        }

        public class Rand
        {
            Random r;

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

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

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

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

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

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

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

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

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

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

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

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

}


5

这是我在空闲时间可以汇集的最好的东西,这几乎是不存在的。当我设置主要功能来循环并连续运行BattleshipCompetition直到按下某个键时,游戏和比赛统计数据仍在进行中。

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

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

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

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

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

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

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

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

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

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

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

        public Point GetShot()
        {
            Point shot;

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

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

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

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

            return shot;
        }

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

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

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

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

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

            return ctr;
        }

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

        public void OpponentShot(Point shot) { }

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

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

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

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

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

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

        public void GameWon()
        {
            wins++;
        }

        public void GameLost()
        {
            losses++;
        }

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

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

            totalWins += wins;
            totalLosses += losses;

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

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

这种逻辑是我击败无畏战舰最接近的逻辑,赢得了约41%的个人比赛。(实际上它以52:49的比分赢得了一场比赛。)奇怪的是,这门课对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
恭喜白银。您介意用语言描述算法吗?知道这将很有趣。
托马斯·阿勒

4

如果您蛮横地进行分析,则可能会发现所提供的RandomOpponent的机制效率很低。它允许自己重新选择已经定位的位置,并让框架强制其重复执行,直到达到尚未触及的位置,或者每次移动的时间限制到期为止。

该对手的行为类似(有效的位置分配是相同的),它只是进行健全性检查,每次调用仅消耗一个随机数(摊销)。

这使用了扩展名/库答案中的类,并且我仅提供了关键的方法/状态。

乔恩·斯凯特(Jon Skeet)的回答取消了洗牌

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

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

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

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

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

    // empty methods left out 
}

4

我将无法参与,但是如果有时间,这是我要实现的算法:

首先,当我发现撞击时,我不会立即追击其余的飞船-我会建立一个舰船位置表,并弄清是否在完全下沉之前至少击中了全部五个船。(请注意,这对于多发镜头变体而言是错误的政策-请参见评论)

  1. 居中(请参阅下面的最后说明-“居中”只是为了方便描述)
  2. 击中中心右侧的第4点
  3. 击中点1,然后在中心右侧
  4. 击中上一个命中右侧的第四个点
  5. 继续按照该模式进行操作(应该以对角线隔开3个空格来填充木板)。这应该击中所有4和5个长度的船,以及统计上数量众多的3和2个船。

  6. 开始随机击中对角线之间的斑点,这将抓住尚未注意到的2号和3号长艇。

一旦我检测到5个打击,就将确定5个打击是否在不同的船上。通过在两个命中点位于同一条水平或垂直线上并且彼此位于5个位置之内(可能是同一条船上的两个命中点)附近进行多次射击,这相对容易一些。如果它们是分开的船,则继续下沉所有船。如果发现它们是同一条船,则继续上面的填充方式,直到找到所有5条船。

该算法是一种简单的填充算法。关键特征在于,它不会浪费时间,即使知道的船只仍然沉没,也不会浪费时间,而且不会使用效率低下的填充模式(即完全随机的模式会很浪费)。

最后说明:

A)“中心”是板上的随机起点。这消除了该算法的主要缺点。B)尽管描述表明从一开始就绘制对角线,但理想情况下,算法仅在沿那些对角线的“随机”位置射击。这有助于防止竞争者确定自己的船只被可预测模式击中多长时间。

这描述了一种“完美”的算法,它将使所有飞船的射击次数均低于(9x9)/ 2 + 10。

但是,它可以大大改善:

一旦击中船只,在做“内部”对角线之前确定其大小。您可能已经找到了2艘船,在这种情况下,可以简化内部对角线以更快地找到3艘船。

确定游戏中的各个阶段并采取相应的措施。这个算法在游戏中的某个阶段可能是好的,但是其他算法可能会在最终游戏中产生更好的收益。另外,如果另一位玩家非常接近击败您,那么另一种算法可能会更好-例如,高风险算法可能会更频繁地失败,但是当它起作用时,它会很快起作用,并且您可能击败比您更接近胜利的对手。

确定竞争对手的比赛方式-可能会为您提供有关他们如何计划船只布置的线索(即,很有可能他们自己的算法可以最快地识别他们如何放置自己的船只-如果您拥有的唯一工具是锤子,一切看起来像钉子)

-亚当


等待沉没直到发现所有船只的策略在很大程度上取决于每转一枪的变化。在(幸存的船只数量)每圈射击数量的情况下,尽快击沉船只以减慢对手的速度是有利的。
杰森·欧文

4

我的条目。

没什么特别的,我没有时间添加我所有的好主意。

但这似乎表现不错。我们将看看它在竞争中的表现:

(将此放入文件Missouri.cs并添加到项目中。)

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

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

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

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

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

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

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

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

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

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

            shotBoard[shot] = Shot.Unresolved;

            return shot;
        }

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

            Board potential = new Board(size);

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

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

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

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

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

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

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

#endregion 

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

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

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

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

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

            Point p;

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

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

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

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

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

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

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

            return bors.ToArray();
        }

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

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

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

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

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

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

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

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

            return new Point(x,y);
        }

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

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

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

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

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

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

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

            return output;
        }

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

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

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

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

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

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

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

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

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

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

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

        public void ShotHit(Point p)
        {
        }

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

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

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

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

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

            return potential;
        }

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

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

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

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

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

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

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

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

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

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

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

现在,我已经提交了参赛作品,一些粗略的统计数据:vs. BP7 44%获胜。/ vs.无畏战胜20%。/对阵Farnsworth 42%的胜利。这是一个有趣的项目。
abelenky

2

这不是最小最大值。实际上,在放置了船只之后,每个玩家都不能独自玩游戏,从而导致他需要花费很多回合才能击沉所有对手船只吗?转弯次数少的人获胜。

我认为除了击沉重创船只并试图减少投篮数量以掩盖船只可能隐藏的其余地方之外,没有任何好的通用策略。

当然,对于任何不是随机的东西,都有可能采取对策。但是我不认为有什么策略可以对付所有可能的参与者。


1
可能,是的,他们可以自己玩。这不是将如何运行。不过好主意。在这项比赛中,我希望能从统计角度上避免对手的出手。
John Gietzen

2
我知道了。使用以前对同一个对手比赛的数据可能会适应他吗?
ziggystar,

2

实际上,我认为这个难题的最大问题是它的两个动作。一招是放置您的船只,另一招是找到敌方船只(但是,除试图以随机因素击败时钟外,第二部分可能是分段的,只是“运行您的算法”)。没有机制可以确定并反制敌人的策略,这使得基于“剪刀石头布”的连续回合进行的类似比赛变得非常有趣。

另外,我认为如果将游戏指定为网络协议,然后提供框架以C#实现该协议,而不是规定所有解决方案都应为C#,那会更酷。但这只是我的观点。

编辑:我取消了我的初衷,因为我没有足够仔细地阅读比赛规则。


并非所有解决方案都必须使用C#。我可以编译并链接到单独的程序集中。另外,您应该能够从统计上反击对手。
约翰·吉岑

J#?也许?哈哈,jk。我确实有一个TCP框架,但是这个锦标赛需要非常快速地运行。
约翰·吉岑

您为什么要假设同一台计算机上两个进程之间的TCP通信速度不会那么快?
Jherico

@Jherico:如果我使用的是TCP,我将在自己的PC上隔离引擎,以便它们可以使用所需的任何CPU资源。
约翰·吉岑

即便如此,同一局域网上的两台计算机也可以轻松地在一秒钟内完成游戏,而网络开销却很小
Jherico

2

我一直喜欢从中间开始并逐渐远离该点,而在其他任何点之间都留有不超过1个空白,以解释该死的子弹...射击之间的间隔取决于沉没的船只。如果B舰是最后一艘,则炮弹之间仅需留出4个空格,以减少炮弹浪费


1
所以...我只需要远离中间位置?:)
darron

14
您还需要远离边缘,因为与非边缘命中相比,边缘命中为您的对手提供的信息更多。因此,您应该将所有船只放置在非中间,非边缘区域。除非那是他们所期望的
Jherico

1
如果从3或4个空格开始,无论如何您可能很幸运可以击中潜艇。如果不是,请返回并尝试填补空白。更多在:somethinkodd.com/oddthinking/2009/10/29/battleship-strategy
Oddthinking

18
有两个孔的船不是该死的潜艇,它是该死的PT船。潜水艇有三个孔。:)
乌鸦

2

萨里大学的James Heather博士代表英国计算机学会举办了一场类似的比赛。

资源受到限制-即每转的最大处理器时间,两次移动之间无法存储任何状态,施加的最大堆大小。为了限制时间,AI可以在时隙内的任何时间提交移动,并且在转弯结束时会要求移动。

非常有趣-有关更多信息,请访问:http//www.bcsstudentcontest.com/

可能会给您更多的想法。


2

就这样,该解决方案将在ubuntu 9.10 linux中的monodevelop中打开且无需修改即可运行


1

你写了:

  • 任何违反比赛精神的行为都将被取消参赛资格。
  • 干扰对手是违反比赛精神的。

请定义“反对比赛的精神”和“干扰对手”?

另外-为简化起见,我建议您:

  • 禁止在对手的CPU插槽中完全使用CPU。
  • 禁止线程并行,而是在单个线程上分配更多的CPU时间。这将简化AI的编程,也不会伤害任何受CPU /内存限制的人。

PS-潜伏在CS后文档中的一个问题:这个游戏是否可以解决(即,有没有一个最佳的策略?)。是的,电路板的尺寸和步骤数使minimax等成为必需,但是我仍然想知道...它的复杂性远非围棋和国际象棋。


当我说“干扰”时,我想到了反思。我不希望竞争对手获胜,因为他们将另一台发动机绞死了。
约翰·吉岑

8
我建议间谍活动是现代战争的重要组成部分,因此进行反思以找到目标将是理想的-毕竟,这是第二次世界大战期间使用的方法之一……
罗兰·肖2009年

我有一个框架,用于将引擎隔离到不同的PC上,通过TCP / IP进行通信,从而使Reflection毫无价值。但是,由于我的参赛人数估计很高,这会使比赛耗时过长。
约翰·吉岑

6
我当时不知道他们有反思!
Markus Nigbur,2009年

1

我预测,能够对对手的随机种子和通话方式进行反向工程的人将获胜。

不知道那有多大可能。


对手可以选择使用CSPRNG。
约翰·吉岑

好点,尽管我承认进行反向工程这样的种子无论如何我都不擅长。我猜想最脆弱的方面是射击模式选择算法-但是即使那样,您也不一定会从破坏它中获益,因为一旦游戏开始,您将无法移动飞船。
Triston Attridge

当我申请研究实习时,我们编写了战舰计划并参加了比赛。通过设置随机种子恰好是我赢得X的方式)
P已

1
假设采用合理简单的舰船放置算法,我想人们可以在对不同舰船造成两次打击后,开始使用大部分的回合来遍历所有可能的随机种子(可能从当前时间附近开始,然后向前移动/向后移动大约一个步骤),并查看哪个产生与观察到的命中兼容的船只位置。
多梅尼克2009年

1

据推测,还可以在游戏中运行一系列变化的游戏。

添加诸如3D飞机之类的功能,或者能够移动一艘飞船而不是射击一个转弯,可能会稍微改变游戏。


2
有“ salvo”变体。在这里,您每轮可以射击的数量与剩余的船只数量相同。
约翰·吉岑

一个有趣的变化。我似乎还记得玩过一架也有飞机的计算机版本。它会在对面板上的任意位置射击。
格伦

另一个变化是:木板的大小+船数。
russau,2009年

1

一秒钟的游戏时间是特定于机器的。与锦标赛机器相比,我的机器上每秒一次的CPU操作价值将有所不同。如果我优化战舰算法以在1秒内利用最多的CPU时间,那么它将在可能较慢的锦标赛机器上运行,它将永远丢失。

我不确定如何解决框架的这种局限性,但是应该解决它。

...

一种想法是做这次比赛中所做的事情http://www.bcsstudentcontest.com /

并且每回合最多要有一个时间,而不是最长的总游戏时间。这样,我可以将算法限制为适合已知的转弯时间。如果我的算法可以管理游戏的总时间,那么游戏可能会持续50到600多个回合,它可能没有足够的时间来发挥其最佳性能,或者可能会浪费太多时间而导致失败。在战舰算法中很难管理总游戏时间。

我建议更改规则以限制回合时间而不是总比赛时间。

编辑

如果我编写了一种算法,该算法枚举了所有可能的镜头,然后对它们进行排名,则采用最高排名的镜头。生成所有可能的镜头将花费很长时间,因此我将算法运行一定的时间然后将其停止。

如果存在基于转弯的限制,我可以让算法运行0.9秒并返回最高排名的镜头,并且可以忍受转弯时间限制。

如果我限于一秒钟的总游戏时间,将很难确定算法每回合应运行多长时间。我将要最大化我的CPU时间。如果一场游戏持续了500回合,我可以将每回合限制为0.002秒,但是如果一场游戏持续了100回合,我可以为每回合提供0.01秒的CPU时间。

对于算法而言,使用半详尽搜索镜头空间以在当前限制下找到最佳镜头是不切实际的。

1秒的总游戏时间限制了可有效用于比赛的算法类型。


这将在Intel Q9550SX四核,8 GB内存,Vista 64计算机上运行。1秒会成为限制因素吗?
约翰·吉岑

我想您应该使您的战舰AI多线程,然后,计算每个时间间隔的最大射击次数。
杰夫·阿特伍德

诀窍是如何限制转弯时间间隔。如果我将其限制为0.00005秒,则可以避免超过时间限制,但是我将大大限制搜索空间。如果增加转弯时间限制,则搜索空间会增加,但会冒时间用尽的风险。
TonyAbell

@TonyAbell:如果有一个基于转弯的时间限制很重要,为什么不从一个初始值开始然后逐个调整呢?在大约半轮之后,您很可能已经找到了面对您的对手的最佳转弯长度。
kyokley

您应该跟踪剩余时间,并将其限制为剩余可用时间的1/2。
约翰·吉岑

1

我在这里通过不输入实际代码来解决问题-但会危害一些一般性观察:

  • 由于所有飞船的大小至少为2个单元,因此您可以使用我在Space Quest V的游戏实现中看到的优化-仅在“寻找”目标时以菱形模式向备用单元射击。这消除了一半的正方形,同时仍然保证您最终会找到所有的船。
  • 在寻找目标时,随机射击会在许多游戏中统计出最佳结果。

1

![概率密度] [1]输入图像描述她

![在此处输入图片描述] [2]

我尝试过比较randon射击和哑巴狩猎/目标的结果,最后比较复杂的搜索。

最好的解决方案似乎是为剩余的船只使用任何单个正方形的可能性创建一个概率密度函数,并以具有最高值的正方形为目标。

您可以在此处查看我的结果,在此处输入链接描述


您能否确定答案,尤其是图像和链接?
巴特

-2

“战斗力”是所谓的经典计算机科学NP完全问题。

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

(寻找战舰-它在那里,在游戏和谜题中)



是的,正如杰森所说,这是完全不同的动物。
约翰·吉岑

3
呵呵呵 下次上班时,我要说是NP完成,然后吃一顿长饭。:-)
Bork Blatt
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.