Какой лучший броненосец ИИ?


315

Морской бой!

В 2003 году (когда мне было 17 лет) я участвовал в боевом корабле ИИ . Несмотря на то, что я проиграл этот турнир, мне было очень весело и многому научился.

Теперь я хотел бы воскресить это соревнование, в поисках лучшего линкора ИИ.

Вот структура, теперь размещенная на Bitbucket .

Победитель получит +450 репутации! Конкурс будет проводиться с 17 ноября 2009 года. . Записи или изменения, внесенные после нуля 17-го числа, не принимаются. (Центральное стандартное время) Отправляйте свои заявки заранее, чтобы не упустить свой шанс!

Чтобы сохранить эту цель , пожалуйста, следуйте духу конкуренции.

Правила игры:

  1. В игру можно играть по сетке 10х10.
  2. Каждый участник разместит каждое из 5 кораблей (длиной 2, 3, 3, 4, 5) на своей сетке.
  3. Никакие корабли не могут пересекаться, но они могут быть смежными.
  4. Затем участники по очереди стреляют по своему противнику.
    • Вариант игры позволяет делать несколько выстрелов за залп, по одному на каждый выживший корабль.
  5. Оппонент уведомит участника, если выстрел затонул, попал или промахнулся.
  6. Игра заканчивается, когда все корабли одного игрока потоплены.

Правила конкурса:

  1. Дух соревнования - найти лучший алгоритм линкора.
  2. Все, что считается противоречащим духу соревнования, будет основанием для дисквалификации.
  3. Вмешательство противника противоречит духу соревнования.
  4. Многопоточность может использоваться при следующих ограничениях:
    • Не более одного потока может быть запущено, пока не ваша очередь. (Хотя любое количество потоков может находиться в состоянии «приостановлено»).
    • Никакой поток не может работать с приоритетом, отличным от «Нормальный».
    • Учитывая вышеупомянутые два ограничения, вам будет гарантировано как минимум 3 выделенных ядра процессора в течение вашего хода.
  5. Каждому участнику первичного потока назначается ограничение в 1 секунду процессорного времени на игру.
  6. Недостаток времени приводит к потере текущей игры.
  7. Любое необработанное исключение приведет к потере текущей игры.
  8. Доступ к сети и доступ к диску разрешен, но вы можете счесть ограничения по времени довольно запретительными. Тем не менее, несколько методов установки и разрыва были добавлены, чтобы уменьшить временную нагрузку.
  9. Код должен быть размещен в переполнении стека в качестве ответа или, если он слишком большой, связан.
  10. Максимальный общий размер (без сжатия) записи составляет 1 МБ.
  11. Официально .Net 2.0 / 3.5 является единственным требованием к структуре.
  12. Ваша запись должна реализовывать интерфейс IBattleshipOpponent.

Подсчет очков:

  1. Лучшие 51 игра из 101 игры - победитель матча.
  2. Все участники соревнований будут играть друг против друга в стиле круговой игры.
  3. Затем лучшая половина участников сыграет в турнире с двойным выбыванием, чтобы определить победителя. (Наименьшая степень двух, которая больше или равна половине, на самом деле.)
  4. Я буду использовать каркас TournamentApi для турнира.
  5. Результаты будут опубликованы здесь.
  6. Если вы отправите более одной заявки, только ваша лучшая заявка будет иметь право на получение двойного элима.

Удачи! Радоваться, веселиться!


РЕДАКТИРОВАТЬ 1:
Спасибо Freed , который нашел ошибку в Ship.IsValidфункции. Это было исправлено. Пожалуйста, загрузите обновленную версию фреймворка.

РЕДАКТИРОВАТЬ 2:
Поскольку существовал значительный интерес к сохранению статистики на диск и так далее, я добавил несколько несвоевременных событий настройки и демонтажа, которые должны обеспечить требуемую функциональность. Это полуразрушительное изменение . То есть интерфейс был изменен для добавления функций, но для них не требуется никакого тела. Пожалуйста, загрузите обновленную версию фреймворка.

РЕДАКТИРОВАТЬ 3:
Исправление ошибки 1: GameWonи GameLostбыли вызваны только в случае тайм-аута.
Исправление ошибки 2: если бы двигатель рассчитывал время каждой игры, соревнование никогда бы не закончилось.
Пожалуйста, загрузите обновленную версию фреймворка.

РЕДАКТИРОВАТЬ 4:
Результаты турнира:


Если запись требует большой базы данных, может ли она подключиться к ней через сеть? То есть. может ли запись совершать звонки через веб-сервис?
Ремус Русану

Есть ли ограничение на размер записей?
Джерико

8
@ Стивен: Кроме того, я проконсультировался с Джеффом Этвудом, чтобы узнать, подходит ли это. Вот его ответ: twitter.com/codinghorror/status/5203185621
Джон Гитцен

1
Кроме того, я бы добавил, что, учитывая неизбежность случайного компонента в этих 50 играх, этого будет недостаточно для точного различения очень хороших реализаций. Я думаю, что 501 или больше могут быть необходимы для разумного взгляда на то, что лучше.
ShuggyCoUk

1
«Мирный» противник, который отказывается размещать корабли, приводит к зависанию конкуренции. Не уверен, насколько ты заботишься о людях, которые делают глупости. :)
Джо

Ответы:


56

Я поддерживаю предложение делать больше игр за матч. Выполнение 50 игр - это просто подбрасывание монеты. Мне нужно было сделать 1000 игр, чтобы получить разумное различие между алгоритмами тестирования.

Скачать дредноут 1.2 .

Стратегии:

  • отслеживать все возможные позиции для судов, которые имеют> 0 хитов. Список никогда не становится больше ~ 30K, поэтому его можно сохранить точно, в отличие от списка всех возможных позиций для всех кораблей (который очень большой).

  • Алгоритм GetShot состоит из двух частей, одна из которых генерирует случайные выстрелы, а другая пытается завершить затопление уже пораженного корабля. Мы делаем случайные выстрелы, если есть возможная позиция (из списка выше), в которой потоплены все ударные корабли. В противном случае мы пытаемся завершить затопление корабля, выбрав место для стрельбы, в котором исключены максимально возможные позиции (взвешенные).

  • Для случайных выстрелов рассчитайте наилучшее местоположение для стрельбы, основываясь на вероятности того, что один из непогруженных кораблей перекрывает локацию.

  • адаптивный алгоритм, который размещает корабли в местах, где у противника статистически меньше шансов стрелять.

  • адаптивный алгоритм, который предпочитает стрелять в местах, где у противника статистически больше шансов разместить свои корабли.

  • размещать корабли в основном не касаясь друг друга.


на моей тестовой машине (нетбуке ULV Celeron) этот код постоянно теряет время ожидания. Когда я позволяю этому занимать все время, оно хочет, чтобы это взбило Simple (примерно 90% успеха). Если вы сильно полагаетесь на технические характеристики машины, на которой собираетесь работать, чтобы сократить свои временные ограничения, возможно, вы захотите дать себе немного простора для маневра ...
ShuggyCoUk

Интересно ... Отлично работает на турнирной машине. Однако «идеальный» двигатель приспособился бы к тому, сколько времени он уже провел.
Джон Гитцен

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
Да, но двигатель запретит это. Затем он скажет ИИ разместить их снова, но на этот раз более строгим голосом. (Увиденный pop ax \ cmp ax, 1 \ je stern)
Джон Гитцен

5
Важное замечание для всех, кто, как и я, полагал, что они могут легко победить это, вспомнив ранее сделанные кадры и не повторяя. Фреймворк будет игнорировать повторы и даст вам еще один шанс, если ваше общее время меньше лимита. На мой взгляд, это плохо, если кто-то испортил их алгоритм, его должны оштрафовать ...
ShuggyCoUk

22

Вот противник для людей, чтобы играть против:

Вместо того чтобы использовать фиксированную стратегию, основанную на геометрии, я подумал, что было бы интересно попытаться оценить лежащие в основе вероятности того, что какое-то конкретное неизведанное пространство удерживает корабль.

Чтобы сделать это правильно, вы должны изучить все возможные конфигурации кораблей, которые соответствуют вашему текущему взгляду на мир, а затем вычислить вероятности на основе этих конфигураций. Вы можете думать об этом, как об исследовании дерева:

расширение возможных состояний линкора http://natekohl.net/media/battleship-tree.png

Рассмотрев все листья этого дерева, которые сочетаются с тем, что вы знаете о мире (например, корабли не могут перекрываться, все квадраты попадания должны быть кораблями и т. Д.), Вы можете подсчитать, как часто корабли встречаются в каждой неисследованной позиции, чтобы оценить вероятность того, что там сидит корабль

Это можно представить в виде тепловой карты, где в горячих точках чаще всего находятся корабли:

тепловая карта вероятностей для каждой неисследованной позиции http://natekohl.net/media/battleship-probs.png

Одна вещь, которая мне нравится в этом соревновании «Морской бой», это то, что дерево выше - достаточно маленькое, чтобы переборотить этот алгоритм. Если для каждого из 5 кораблей существует ~ 150 возможных позиций, то это 150 5 = 75 миллиардов возможностей. И это число становится только меньше, особенно если вы можете уничтожить целые корабли.

Оппонент, с которым я связался выше, не исследует все дерево; 75 миллиардов по-прежнему велики, чтобы войти в секунду. Тем не менее, он пытается оценить эти вероятности с помощью нескольких эвристик.


Пока вы бьете наше единственное другое полное решение примерно на 67,7% до 32,3% :)
Джон Гитцен

2
Мне определенно любопытно посмотреть, как «вероятностный подход» сравнивается с «геометрическим подходом». Я заметил, что этот вероятный противник на самом деле делает ходы, которые следуют геометрическим паттернам, обсуждаемым в других ответах. Возможно, что использование геометрии так же хорошо и намного быстрее. :)
Нейт Коля

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)? Например, возможности размещения линкора (длиной 5 единиц) в измерении x здесь:

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 было Имя, чтобы я мог выводить сообщения типа («Ты потопил мой {0}», ship.Name);, Я имею в виду и другие расширения, так что я думаю, что Корабль должен быть наследуемым.

Сроки:

Хотя ограничение по времени в 1 секунду имеет смысл для правила турнира, оно полностью портит отладку. BattleshipCompetition должна иметь простую настройку, чтобы игнорировать нарушения времени, чтобы помочь с разработкой / отладкой. Я бы также предложил изучить System.Diagnostics.Process :: UserProcessorTime / Privileged ProcessorTime / TotalProcessorTime для более точного представления того, сколько времени используется.

Затонувшие корабли:

Текущий API сообщает вам, когда вы потопили корабль противника:

ShotHit(Point shot, bool sunk);

но не который корабль ты потопил! Я считаю это частью правил о человеке-линкоре, который вы должны объявить "Ты потопил мой линкор!" (или эсминец, или саб, и т. д.).

Это особенно важно, когда ИИ пытается сбить корабли, которые сталкиваются друг с другом. Я хочу запросить изменение API на:

ShotHit(Point shot, Ship ship);

Если корабль ненулевой, это означает, что выстрел был тонущим, и вы знаете, какой корабль вы потопили, и как долго это было. Если выстрел был не тонущим, значит корабль пуст, и у вас нет дополнительной информации.


Пожалуйста, опубликуйте примеры кода, если вы считаете, что время может быть сделано более точно. Я не хочу менять правила слишком сильно прямо сейчас.
Джон Гитцен

Кроме того, размеры кораблей передаются во время PlaceShips (), который запускается ровно один раз за игру и может также использоваться в качестве фазы настройки. Пожалуйста, не стесняйтесь распечатать корабль для собственных испытаний, но я планирую использовать запечатанный для турнира.
Джон Гитцен

BUG: @John Gietzen: Я определил, что PlaceShips НЕ запускается ровно один раз за игру (как вы заявили). Если игрок размещает свои корабли неправильно (как это часто делает RandomOpponent), то PlaceShips вызывается повторно, без вмешательства NewGame.
Абеленки

5
Я всегда считал стратегией разместить два корабля в конфигурации L, чтобы заставить моего противника думать, что он потопил линкор, хотя на самом деле это не так. У меня никогда не было впечатления, что ты должен был объявить, какая лодка была потоплена.
Джош Смитон

3
@DJ: я иду по оригинальным правилам с ручкой и бумагой. Помните, что Hasbro - это компания по производству игрушек, и что эта игра предшествует Hasbro.
Джон Гитцен

5

Обновлен CrossFire. Я знаю, что он не может конкурировать с Фарнсвортом или Дредноутом, но намного быстрее, чем последний, и с ним легко играть, если кто-то захочет попробовать. Это зависит от текущего состояния моих библиотек, включенных здесь, чтобы упростить его использование.

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

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

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

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

        public void NewMatch(string opponent) {}

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

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

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

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

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

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

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

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

        public const bool DebugEnabled = false;

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

        public void GameWon()
        {
        }

        public void GameLost()
        {
        }

        public void MatchOver()
        {
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

            private Cell<T>[] history;

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

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

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

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

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

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

            #region IEnumerable<Cell<T>> Members

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

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

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

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

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

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

            #endregion
        }

        public class Rand
        {
            Random r;

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

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

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

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

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

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

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

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

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

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

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

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

}


5

Это лучшее из того, что я мог собрать в свободное время, которого почти нет. Идет подсчет статистики по играм и матчам, так как я настроил основную функцию на цикл и непрерывно запускаю BattleshipCompetition, пока не нажму клавишу.

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

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

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

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

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

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

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

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

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

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

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

        public Point GetShot()
        {
            Point shot;

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

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

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

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

            return shot;
        }

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

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

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

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

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

            return ctr;
        }

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

        public void OpponentShot(Point shot) { }

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

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

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

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

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

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

        public void GameWon()
        {
            wins++;
        }

        public void GameLost()
        {
            losses++;
        }

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

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

            totalWins += wins;
            totalLosses += losses;

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

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

Эта логика является наиболее близкой к тому, что мне приходилось обыгрывать Дредноут, выигрывая около 41% отдельных игр. (Фактически он выиграл один матч со счетом от 52 до 49.) Как ни странно, этот класс не так хорошо справляется с FarnsworthOpponent, как более ранняя версия, которая была намного менее продвинутой.


5

Мой компьютер сейчас ремонтируется dell, но это то, где я был на прошлой неделе:

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 крайне неэффективным. Он позволяет себе повторно выбирать уже целевые местоположения и позволяет каркасу заставлять его повторяться, пока он не достигнет того, чего еще не коснулся, или пока не истечет временное ограничение на ход.

Этот оппонент ведет себя аналогично (эффективное распределение размещения такое же), он просто выполняет проверку работоспособности и потребляет только одно генерирование случайных чисел за звонок (амортизируется)).

Это использует классы в моем расширении / ответе библиотеки, и я только предоставляю ключевые методы / состояние.

Shuffle снят с ответа Джона Скита здесь

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 лодок не будут найдены.

Этот алгоритм является простым алгоритмом заполнения. Ключевая особенность заключается в том, что он не тратит время на затопление судов, о которых он знает, когда есть еще корабли, о которых он не знает, и не использует неэффективный шаблон заполнения (т. Е. Полностью случайный шаблон будет расточительным).

Финальные заметки:

А) «Центр» - это случайная отправная точка на доске. Это устраняет основную слабость этого алгоритма. Б) Хотя описание указывает на рисование диагоналей непосредственно с самого начала, в идеале алгоритм просто стреляет в «случайные» места, которые расположены вдоль этих диагоналей. Это помогает предотвратить выбор участником времени, по истечении которого его корабли будут поражены предсказуемыми схемами.

Это описывает «идеальный» алгоритм в том смысле, что все корабли получат меньше (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;
    }
}

И теперь, когда я представил свою заявку, некоторые приблизительные показатели: против BP7 44% побед. / против дредноута 20% побед. / против Фарнсворта 42% побед. Это был веселый проект.
Абеленки

2

Это не минимакс. На самом деле, после размещения кораблей, не может ли каждый игрок играть сам по себе, в результате чего ему понадобилось несколько ходов, чтобы потопить каждый корабль противника? Тот, который занимал меньше ходов, побеждает.

Я не думаю, что есть какие-то хорошие общие стратегии, помимо затонувших хитовых кораблей и попыток минимизировать количество выстрелов, чтобы покрыть оставшиеся возможные места, где корабли могут спрятаться.

Конечно, могут быть контрстратегии для всего, что не случайно. Но я не думаю, что есть стратегии, которые хороши против всех возможных игроков.


1
Потенциально, да, они могли бы играть самостоятельно. Это не так, как это будет выполняться. Отличная идея. В этом соревновании я хочу, чтобы была возможность статистически избегать ударов противника.
Джон Гитцен

2
Понимаю. Используя данные предыдущих игр против одного и того же противника, можно было бы приспособиться к нему?
зиггистар

2

На самом деле, я думаю, что самая большая проблема головоломки состоит в том, что она состоит из двух ходов. Одним ходом является размещение ваших кораблей, другим - обнаружение вражеских кораблей (однако сегментировано, что вторая часть может быть, кроме попытки побить часы со случайным фактором, это просто «запустить свой алгоритм»). Там нет механизма, чтобы попытаться определить и затем противостоять вражеской стратегии, что делает подобные соревнования, основанные на последовательных раундах «ножницы из бумаги для камней», довольно интересными.

Кроме того, я думаю, что было бы лучше, если бы вы указали игру в качестве сетевого протокола, а затем предоставили платформу для реализации этого протокола в C #, а не указали, что все решения должны быть на C #, но это только мое мнение.

РЕДАКТИРОВАТЬ: я отменяю свою начальную точку, так как я недостаточно внимательно прочитал правила конкурса.


Не все решения должны быть в C #. Я могу скомпилировать и связать в отдельную сборку. Кроме того, вы должны быть в состоянии статистически противостоять противнику.
Джон Гитцен

J #? может быть? Lol, JK. У меня есть для этого TCP-фреймворк, но этот турнир должен пройти очень быстро.
Джон Гитцен

Почему вы предполагаете, что связь TCP между двумя процессами на одной машине не будет невероятно быстрой?
Джерико

@Jherico: Если бы я использовал TCP, я бы изолировал движки на их собственных ПК, чтобы они могли использовать любые ресурсы ЦП, которые они хотели.
Джон Гитцен

Несмотря на это, две машины на одной локальной сети могут легко завершить игру менее чем за секунду, при этом
нагрузка

2

Мне всегда нравилось начинать с середины и отходить от этой одной точки, оставляя не более 1 пустого пространства между любыми другими точками, чтобы объяснить эту чертову субмарину ... пространство между выстрелами зависело от того, какие корабли были потоплены. если B-корабль был последним, выстрелы должны были оставить только 4 пробела между ними, чтобы минимизировать потерянные выстрелы


1
Итак ... Мне просто нужно держаться подальше от середины? :)
Даррон

14
Вам также нужно держаться подальше от краев, потому что удар по краю содержит больше информации для вашего противника, чем удар без края. Таким образом, вы должны разместить все свои корабли в не среднем, не краевом регионе. Если только этого не ожидают от вас.
Джерико

1
Если вы начнете с оставления 3 или 4 пробелов, вам, возможно, повезет, так или иначе ударив по субмарине Если нет, вернитесь и попробуйте заполнить пробелы. Больше на: somethinkodd.com/oddthinking/2009/10/29/battleship-strategy
Нечетное мышление

18
Судно с двумя отверстиями не проклятый к югу , это проклятая PT лодки . Саб имеет три отверстия. :)
Ворон

2

Аналогичный конкурс проводился доктором Джеймсом Хизером из Университета Суррея от имени Британского компьютерного общества.

Были наложены ограничения на ресурсы - максимальное время процессора за ход, состояние не может быть сохранено между ходами, максимальный размер кучи. Чтобы ограничить время, ИИ может подать ход в любой точке временного интервала, и ему будет предложено сделать ход после завершения хода.

Очень интересно - смотрите больше на: http://www.bcsstudentcontest.com/

Может дать вам еще несколько идей.


2

На самом деле, решение открывается и запускается без изменений в monodevelop в Ubuntu 9.10 Linux.


1

Вы написали:

  • Все, что считается противоречащим духу соревнования, будет основанием для дисквалификации.
  • Вмешательство противника противоречит духу соревнования.

пожалуйста, определите «против духа соревнования» и «вмешиваться в противника»?

Также - чтобы упростить, я рекомендую вам:

  • вообще запрещать использование CPU во время слота CPU противника.
  • запретить параллелизм потоков и вместо этого дать больше процессорных секунд в одном потоке. Это упростит программирование ИИ и не повредит никому, кто все равно связан с процессором / памятью.

PS - вопрос для постдоков Документа CS, скрывающийся здесь: разве эта игра не разрешима (то есть есть ли единственная, лучшая стратегия?). да, размер доски и количество шагов делают минимакс и др. обязательными, но все же я должен задаться вопросом ... это далеко от Го и шахмат по сложности.


Я подумал, когда сказал «вмешательство». Я не хочу, чтобы конкуренты побеждали, потому что они загубили другой двигатель до смерти.
Джон Гитцен

8
Я бы предположил, что шпионаж - важная часть современной войны, поэтому размышления о поиске целей были бы идеальными - в конце концов, это был один из методов, использованных во время второй мировой войны ...
Роуланд Шоу,

У меня есть структура для изоляции движков на разных ПК, общение по TCP / IP, что делает Reflection бесполезным. Тем не менее, из-за моего предполагаемого количества заявок, конкурс может занять слишком много времени.
Джон Гитцен

6
Я не знал, что у них было Отражение тогда!
Маркус Нигбур

1

Я предсказываю, что человек, которому удастся провести обратный инжиниринг случайных семян и паттернов своих противников, победит.

Не уверен, насколько это вероятно.


У оппонентов есть возможность использовать CSPRNG.
Джон Гитцен

Хороший вопрос, хотя я признаю, что реверс-инжиниринг такого семени в любом случае мне не под силу. Я полагаю, что наиболее уязвимым аспектом был бы алгоритм выбора огневого паттерна, но даже в этом случае вы не сильно выиграете от его разрушения, так как вы не сможете перемещать свои корабли после начала игры.
Тристон Этридж

Когда я подал заявку на стажировку, мы написали боевые программы и соревновались. Путем установки случайного начального числа было именно то, как я выиграл X)
P Shved

1
Предполагая достаточно простой алгоритм размещения корабля, я мог бы предположить, что после нескольких попаданий на разные корабли можно было бы начать использовать большую часть своего хода, просматривая все возможные случайные начальные числа (вероятно, начиная с текущего времени и двигаясь вперед / на один шаг назад или около того) и выяснение того, какие из них создают места размещения кораблей, совместимые с наблюдаемыми попаданиями.
Доменик

1

Также, возможно, можно было бы запустить серию из них с различными вариантами игры.

Добавление таких вещей, как трехмерный самолет или возможность перемещать один корабль вместо стрельбы за ход, вероятно, изменит игру.


2
Есть вариант "залп". Где вы можете сделать столько выстрелов за ход, сколько у вас осталось кораблей.
Джон Гитцен

Интересный вариант, а также. Кажется, я вспоминаю, как играл в компьютерную версию с самолетом. Он будет случайным образом стрелять по локациям на противоположной доске.
Гленн

другой вариант: быть размером доски + количество кораблей.
Руссау

1

Одна вторая общая время игры в зависит от машины. Однажды вторая ценность операций ЦП будет отличаться на моей машине по сравнению с турнирной машиной. Если я оптимизирую алгоритм Боевого Корабля для использования большей части процессорного времени в течение 1 секунды, то он запускается на более медленной турнирной машине, и он всегда будет проигрывать.

Я не уверен, как обойти это ограничение структуры, но это должно быть устранено.

...

Одна идея состоит в том, чтобы сделать то, что было сделано в этом соревновании http://www.bcsstudentcontest.com /

И иметь максимальное время за ход, а не максимальное общее время игры. Таким образом, я мог бы ограничить алгоритмы в соответствии с известным временем поворота. Игра может длиться от 50 до 600+ ходов, если мой алгоритм управляет общим игровым временем, он может не дать достаточно времени, чтобы выполнить свою лучшую работу, или он может дать слишком много времени и проиграть. Очень сложно управлять общим временем игры в алгоритме линкора.

Я бы предложил изменить правила, чтобы ограничить время хода, а не общее время игры.

редактировать

Если я написал алгоритм, который перечисляет все возможные кадры, а затем ранжирует их, то делает снимок с самым высоким рейтингом. Создание всех возможных снимков заняло бы слишком много времени, поэтому я позволил бы алгоритму работать некоторое время, а затем остановил его.

Если бы был предел на основе хода, я мог бы позволить алгоритму работать в течение 0,9 секунды и возвращать выстрел с наивысшим рейтингом и хорошо выдерживать ограничение времени хода.

Если я ограничусь общим игровым временем в одну секунду, будет трудно определить, как долго должен работать алгоритм для каждого хода. Я хочу максимально использовать свое процессорное время. Если игра длилась 500 раундов, я мог бы ограничить каждый ход до 0,002 секунды, но если игра длилась 100 раундов, я мог бы дать каждому ходу 0,01 секунды процессорного времени.

Для алгоритма было бы нецелесообразно использовать полуисчерпывающий поиск пространства выстрелов, чтобы найти лучший выстрел с текущим ограничением.

Общее время игры в 1 секунду ограничивает тип алгоритмов, которые можно эффективно использовать для соревнования в игре.


Он будет работать на четырехъядерном процессоре Intel Q9550SX, оперативной памяти 8 ГБ и Vista 64. Будет ли 1 секунда ограничивающим фактором?
Джон Гитцен

Я полагаю, вам следует сделать многопоточный AI своего линкора таким образом, чтобы рассчитать максимальное количество выстрелов за этот промежуток времени.
Джефф Этвуд

Хитрость в том, как ограничить интервал поворота. Если я ограничу его 0,00005 секунд, я не смогу работать в течение указанного времени, но я значительно ограничу пространство поиска. Если я увеличу лимит времени поворота, пространство поиска увеличится, но я рискую не хватить времени.
TonyAbell

@TonyAbell: Если важно иметь ограничение по времени на основе хода, почему бы не начать с начального значения, а затем изменять его от раунда к раунду? Примерно после половины раундов вы, скорее всего, найдете оптимальную длину поворота для противника, с которым вы сталкиваетесь.
Кёкли

Вы должны отслеживать оставшееся время и ограничивать его до 1/2 доступного оставшегося времени.
Джон Гитцен

1

Я решил, не вставляя фактический код, - но я рискну некоторыми общими наблюдениями:

  • Поскольку все корабли имеют размер не менее 2 ячеек, вы можете использовать оптимизацию, которую я видел в реализации игры в Space Quest V - которая стреляет только по чередующимся ячейкам в виде ромба, пока она «ищет» цель. Это устраняет половину квадратов, в то же время гарантируя, что в конечном итоге вы найдете все корабли.
  • Случайная стрельба при поиске целей статистически даст лучшие результаты во многих играх.

1

! [Вероятность плотности] [1] введите описание изображения ее

! [введите описание изображения здесь] [2]

Я экспериментировал со сравнением результатов рандоновой стрельбы с тупой охотой / целью и, наконец, сложным поиском.

Лучшее решение, по-видимому, состоит в том, чтобы создать функцию плотности вероятности для того, насколько вероятно, что любой отдельный квадрат используется оставшимися кораблями, и стремиться к квадрату с наибольшим значением.

Вы можете увидеть мои результаты здесь, введите ссылку здесь


Не могли бы вы исправить свой ответ и особенно ваши изображения и ссылки?
Барт

-2

«Морской бой» - это то, что известно как классическая компьютерная наука - полная проблема.

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

(ищите линкор - он там, под играми и головоломками)


4
Это головоломка для линкора ( en.wikipedia.org/wiki/Battleship_(puzzle) ), а не линкор в игре ( en.wikipedia.org/wiki/Battleship_(game) ).
Джейсон Беркан

Да, как сказал Джейсон, это совершенно другое животное.
Джон Гитцен

3
Hehehe. Следующее задание, которое я получаю на работе, я собираюсь сказать, что оно завершено NP, затем возьмите длинный обед. :-)
Борк Блатт
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.