[C#] 최고의 전함 AI는 무엇입니까?

전함!

2003 년 (17 살 때)에 전함 AI 코딩 경쟁에 참가했습니다. 토너먼트를 잃어 버렸지 만 많은 재미가 있었고 많은 것을 배웠습니다.

이제 최고의 전함 AI를 찾아이 경쟁을 부활시키고 싶습니다.

다음은 현재 Bitbucket에서 호스팅되는 프레임 워크 입니다.

우승자는 +450의 평판을 얻습니다! 대회는 2009 년 11 월 17 일에 시작됩니다 . 17 일 0시 이후의 항목이나 편집 내용은 허용되지 않습니다. (중부 표준시) 응모작을 일찍 제출하여 기회를 놓치지 마십시오!

목표 를 유지하려면 경쟁의 정신을 따르십시오.

게임의 규칙:

  1. 게임은 10×10 그리드에서 재생됩니다.
  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. 항목의 최대 총 크기 (비 압축)는 1MB입니다.
  11. 공식적으로 .Net 2.0 / 3.5는 유일한 프레임 워크 요구 사항입니다.
  12. 항목은 IBattleshipOpponent 인터페이스를 구현해야합니다.

채점 :

  1. 101 개 게임 중 최고의 51 개 게임이 경기의 승자입니다.
  2. 모든 선수는 라운드 로빈 스타일로 서로 경기를합니다.
  3. 그런 다음 경쟁사 중 최고 절반이 이중 제거 토너먼트를 플레이하여 승자를 결정합니다. (실제로 절반보다 크거나 같은 2의 가장 작은 거듭 제곱)
  4. 토너먼트에 TournamentApi 프레임 워크를 사용할 것 입니다.
  5. 결과가 여기에 게시됩니다.
  6. 두 개 이상의 출품작을 제출하면 최고 점수를받은 출품작 만 이중 심사를받을 수 있습니다.

행운을 빕니다! 즐기세요!


편집 1 : 함수 에서 오류를 발견 한 Freed 에게
감사드립니다 . 수정되었습니다. 업데이트 된 버전의 프레임 워크를 다운로드하십시오.Ship.IsValid

편집 2 :
디스크에 통계를 유지하는 데 상당한 관심이 있었기 때문에 필자는 필요한 기능을 제공 해야하는 시간이 설정되지 않은 설정 및 해제 이벤트를 몇 가지 추가했습니다. 이것은 반 파괴적인 변화 입니다. 즉, 인터페이스가 기능을 추가하도록 수정되었지만 본문이 필요하지 않습니다. 업데이트 된 버전의 프레임 워크를 다운로드하십시오.

편집 3 :
버그 수정 1 : GameWonGameLost한 번에 아웃의 경우 전화 받고 있었다.
버그 수정 2 : 엔진이 모든 게임의 시간을 초과했다면 경쟁은 끝나지 않을 것입니다.
업데이트 된 버전의 프레임 워크를 다운로드하십시오.

편집 4 :
토너먼트 결과 :



답변

경기당 더 많은 게임을하기 위해 모션을 두 번째로합니다. 50 게임을하는 것은 동전을 뒤집는 것입니다. 테스트 알고리즘을 합리적으로 구별하려면 1000 게임을해야했습니다.

Dreadnought 1.2를 다운로드하십시오 .

전략 :

  • 적중 수가 0보다 큰 선박의 모든 가능한 위치를 추적하십시오. 목록은 ~ 30K보다 커지지 않으므로 모든 선박의 가능한 모든 위치 목록과 달리 정확하게 유지할 수 있습니다 (매우 큽니다).

  • GetShot 알고리즘에는 두 가지 부분이 있습니다. 하나는 임의 샷을 생성하고 다른 하나는 이미 적중 한 선박의 침몰을 시도합니다. 모든 적중 함이 침몰 할 수있는 위치 (위 목록에서)가있을 경우 무작위 샷을합니다. 그렇지 않으면, 우리는 가능한 가장 많은 위치 (가중치)를 제거 할 수있는 촬영 장소를 선택하여 선박 침몰을 완료하려고합니다.

  • 무작위 샷의 경우, 위치가 겹치지 않은 배송되지 않은 선박 중 하나의 가능성을 기반으로 가장 적합한 위치를 계산하십시오.

  • 적을 통계적으로 발사하기 어려운 위치에 선박을 배치하는 적응 알고리즘.

  • 상대가 통계적으로 배를 배치 할 가능성이 더 높은 위치에서 촬영하는 것을 선호하는 적응 알고리즘.

  • 배는 주로 서로 접촉하지 않습니다.


답변

여기 내 항목이 있습니다! (가장 순진한 해결책)

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


답변

사람들이 상대하는 상대는 다음과 같습니다.

고정 지오메트리에서 영감을 얻은 전략을 사용하는 대신, 특정 미 탐사 공간이 배를 보유하고있는 근본적인 확률추정하는 것이 흥미로울 것이라고 생각했습니다 .

이를 위해 현재의 세계관에 맞는 가능한 모든 선박 구성을 탐색 한 다음 해당 구성을 기반으로 확률을 계산합니다. 나무를 탐험하는 것처럼 생각할 수 있습니다.

가능한 전함 상태의 확장 http://natekohl.net/media/battleship-tree.png

세계에 대해 알고있는 것과 같은 나무의 모든 잎을 고려한 후 (예를 들어, 선박이 겹칠 수없고, 적중 한 모든 정사각형은 선박 등이어야 함) 각 미 탐사 위치에서 선박이 얼마나 자주 발생하는지 계산하여 그 가능성을 추정 할 수 있습니다. 배가 앉아있다

이것은 핫스팟이 선박을 포함 할 가능성이 높은 히트 맵으로 시각화 할 수 있습니다.

각각의 미 탐사 위치에 대한 확률 열지도 http://natekohl.net/media/battleship-probs.png

이 전함 경쟁에서 내가 좋아하는 것 중 하나는 위의 트리가 이런 종류의 알고리즘을 무차별화할 수있을 정도로 작다는 것입니다. 5 대의 선박 각각에 대해 ~ 150 개의 가능한 위치가 있다면, 그것은 150 5 = 750 억 가능성입니다. 그리고 그 숫자는 특히 배 전체를 제거 할 수 있다면 더 작아집니다.

위에 링크 된 상대는 전체 트리를 탐색하지 않습니다. 750 억은 여전히 ​​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!");
        }
    }
}


답변

지금은 본격적인 알고리즘을 작성할 시간이 없지만 생각은 다음과 같습니다. 상대방이 무작위로 배를 배치하면 배치 확률이 (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....
...........

분명히 임의의 무작위성이 아이디어에 추가되어야 할 것이지만, 나는 순수하게 수학적으로 그것이 갈 길이라고 생각합니다.


답변

그다지 세련된 것은 없지만 여기에 내가 생각해 낸 것입니다. 그것은 무작위 상대의 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() { }
    }
}

여기에 최소한의 공간을 차지하고 읽을 수 있도록 약간 압축되었습니다.


답변

경쟁 엔진에 대한 의견 :

새로운 게임 파라미터 :

IBattleshipOpponent :: NewGame이 사전 게임 설정 용이며 보드 크기를 사용하는 경우, 선박 및 해당 크기 목록도 가져와야합니다. 가변 선박 구성을 허용하지 않고 가변 보드 크기를 허용하는 것은 의미가 없습니다.

선박은 밀봉되어 있습니다 :

클래스 함선이 봉인 된 이유는 없습니다. 다른 기본 사항 중에서 Ships에 이름이 있기를 원하므로 ( “You sunk my {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은 null이며 추가 정보가 없습니다.