Qual é a melhor IA de navio de guerra?


315

Battleship!

Em 2003 (quando eu tinha 17 anos), participei de uma IA de navio de guerra competição de codificação de . Mesmo tendo perdido o torneio, me diverti muito e aprendi muito com ele.

Agora, eu gostaria de ressuscitar esta competição, em busca da melhor IA de navio de guerra.

Aqui está a estrutura, agora hospedada no Bitbucket .

O vencedor receberá +450 reputação! A competição será realizada a partir de 17 de novembro de 2009 . Nenhuma entrada ou edição posterior a zero hora no dia 17 será aceita. (Horário padrão central) Envie suas entradas com antecedência, para não perder a oportunidade!

Para manter este OBJETIVO , siga o espírito da competição.

Regras do jogo:

  1. O jogo é jogado em uma grade de 10x10.
  2. Cada competidor colocará cada um dos 5 navios (dos comprimentos 2, 3, 3, 4, 5) em sua grade.
  3. Nenhum navio pode se sobrepor, mas pode ser adjacente.
  4. Os competidores então se revezam disparando tiros únicos contra o oponente.
    • Uma variação do jogo permite disparar vários tiros por saraivada, um para cada navio sobrevivente.
  5. O oponente notificará o competidor se o tiro afundar, acertar ou errar.
  6. O jogo termina quando todos os navios de qualquer jogador são afundados.

Regras da competição:

  1. O espírito da competição é encontrar o melhor algoritmo de encouraçado.
  2. Qualquer coisa que seja considerada contrária ao espírito da competição será motivo de desqualificação.
  3. Interferir com um oponente é contra o espírito da competição.
  4. O multithreading pode ser usado sob as seguintes restrições:
    • Não pode haver mais de um segmento em execução enquanto não for a sua vez. (Porém, qualquer número de threads pode estar no estado "Suspenso").
    • Nenhum thread pode ser executado com uma prioridade diferente de "Normal".
    • Dadas as duas restrições acima, você terá a garantia de pelo menos 3 núcleos de CPU dedicados durante o seu turno.
  5. Um limite de 1 segundo de tempo de CPU por jogo é atribuído a cada competidor no encadeamento primário.
  6. Ficar sem tempo resulta na perda do jogo atual.
  7. Qualquer exceção não tratada resultará na perda do jogo atual.
  8. O acesso à rede e ao disco é permitido, mas você pode achar as restrições de tempo bastante proibitivas. No entanto, alguns métodos de configuração e desmontagem foram adicionados para aliviar o tempo gasto.
  9. O código deve ser publicado no estouro da pilha como uma resposta ou, se muito grande, vinculado.
  10. O tamanho total máximo (não compactado) de uma entrada é de 1 MB.
  11. Oficialmente, .Net 2.0 / 3.5 é o único requisito de estrutura.
  12. Sua entrada deve implementar a interface IBattleshipOpponent.

Pontuação:

  1. Os 51 melhores jogos de 101 jogos são os vencedores de uma partida.
  2. Todos os competidores jogarão emparelhados, estilo round-robin.
  3. A melhor metade dos competidores jogará um torneio de eliminação dupla para determinar o vencedor. (A menor potência de duas que é maior ou igual à metade, na verdade.)
  4. Vou usar o TournamentApi estrutura para o torneio.
  5. Os resultados serão publicados aqui.
  6. Se você enviar mais de uma entrada, apenas sua entrada com melhor pontuação será qualificada para o duplo elim.

Boa sorte! Diverta-se!


EDIT 1:
Obrigado a Freed , que encontrou um erro na Ship.IsValidfunção. Foi consertado. Faça o download da versão atualizada do framework.

EDIT 2:
Como houve um interesse significativo nas estatísticas persistentes no disco e outras coisas, adicionei alguns eventos de configuração e desmontagem não programados que devem fornecer a funcionalidade necessária. Esta é uma mudança semi-quebrável . Ou seja: a interface foi modificada para adicionar funções, mas nenhum corpo é necessário para elas. Faça o download da versão atualizada do framework.

EDIT 3:
Bug Fix 1: GameWone GameLostsó estavam sendo chamados no caso de um tempo limite.
Correção de bug 2: Se um mecanismo expirasse todos os jogos, a competição nunca terminaria.
Faça o download da versão atualizada do framework.

EDIT 4:
Resultados do torneio:


Se a entrada exigir um banco de dados grande, ele poderá se conectar a ele pela rede? Ou seja. a entrada pode fazer chamadas de serviço da web?
Remus Rusanu

existe um limite de tamanho nas entradas?
Jherico

8
@ Steven: Além disso, consultei Jeff Atwood apenas para ver se era apropriado. Aqui está sua resposta: twitter.com/codinghorror/status/5203185621
John Gietzen

1
Eu também acrescentaria que, dado o inevitável componente aleatório desses 50 jogos, não será suficiente para distinguir com precisão entre implementações muito boas. Eu pensaria que 501 ou mais podem ser necessários para uma visão razoável sobre qual é melhor.
ShuggyCoUk 01/11/2009

1
Um oponente "pacífico" que se recusa a colocar navios faz a competição travar. Não tenho certeza do quanto você se importa com pessoas fazendo coisas tolas assim. :)
Joe

Respostas:


56

Eu defendo a moção para fazer muito mais jogos por partida. Fazer 50 jogos é apenas jogar uma moeda. Eu precisava fazer 1000 jogos para obter qualquer distinção razoável entre algoritmos de teste.

Faça o download do Dreadnought 1.2 .

Estratégias:

  • acompanhe todas as posições possíveis para navios que tenham> 0 acertos. A lista nunca fica maior que ~ 30K, portanto pode ser mantida exatamente, ao contrário da lista de todas as posições possíveis para todos os navios (que é muito grande).

  • O algoritmo GetShot possui duas partes, uma que gera tiros aleatórios e a outra que tenta terminar de afundar um navio já atingido. Fazemos disparos aleatórios se houver uma posição possível (da lista acima) em que todos os navios atingidos sejam afundados. Caso contrário, tentamos terminar de afundar um navio, escolhendo um local para atirar em que elimine as posições mais possíveis (ponderadas).

  • Para fotos aleatórias, calcule a melhor localização para fotografar com base na probabilidade de um dos navios não afundados sobrepor a localização.

  • algoritmo adaptativo que coloca navios em locais onde o oponente é estatisticamente menos propenso a disparar.

  • algoritmo adaptativo que prefere atirar em locais onde o oponente é estatisticamente mais propenso a colocar seus navios.

  • coloque navios na maior parte não se tocando.


na minha máquina de teste (um netbook ULV Celeron), esse código perde com o tempo limite de forma consistente. Quando deixo que demore o tempo que quiser, ele chicoteia o Simple (aproximadamente 90% de sucesso). Se você está confiando pesadamente sobre a especificação da máquina que você vai estar em execução no a bater-lhe prazos você pode querer dar-se algum espaço de manobra ...
ShuggyCoUk

Interessante ... Funciona bem na máquina do torneio. No entanto, um mecanismo "perfeito" se adaptaria ao tempo que já havia passado.
John Gietzen

35

Aqui está a minha entrada! (A solução mais ingênua possível)

"Aleatório 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
Na verdade, esta resposta é bom porque mostra de uma forma muito concisa formar a API é que você precisa para implementar a competir ... :)
dicroce

1
Quando eu construí um projeto semelhante na classe de Algoritmos da minha faculdade, usei lógica aleatória entrelaçada com algumas tomadas de decisão. Às vezes era bom!
21419 Nathan Taylor

2
Isso poderia tentar colocar navios sobrepostos, não?

6
Sim, mas o mecanismo não permitirá isso. Em seguida, ele diz à AI para colocá-los novamente, mas desta vez com uma voz mais severa. (Visto pop ax \ cmp ax, 1 \ je stern)
John Gietzen

5
Nota importante para quem, como eu, imaginou que poderia superar isso facilmente lembrando-se das fotos colocadas anteriormente e não repetindo. A estrutura ignorará as repetições e lhe dará outra chance, desde que seu tempo total seja menor que o limite. Esta é pobre em minha opinião, se alguém mexe-se a algo que deve ser penalizado ...
ShuggyCoUk

22

Aqui está um oponente para as pessoas jogarem:

Em vez de usar uma estratégia fixa inspirada em geometria, pensei que seria interessante tentar estimar as probabilidades subjacentes de que qualquer espaço inexplorado em particular mantém uma nave.

Para fazer isso corretamente, você exploraria todas as configurações possíveis de navios que se ajustam à sua visão atual do mundo e, em seguida, calcularia as probabilidades com base nessas configurações. Você poderia pensar nisso como explorar uma árvore:

uma expansão de possíveis estados de encouraçado http://natekohl.net/media/battleship-tree.png

Depois de considerar todas as folhas dessa árvore que combinam com o que você sabe sobre o mundo (por exemplo, navios não podem se sobrepor, todos os quadrados atingidos devem ser navios, etc.), você pode contar com que frequência os navios ocorrem em cada posição inexplorada para estimar a probabilidade de que um navio está sentado lá.

Isso pode ser visualizado como um mapa de calor, onde os pontos quentes têm maior probabilidade de conter navios:

um mapa de probabilidades para cada posição inexplorada http://natekohl.net/media/battleship-probs.png

Uma coisa que eu gosto nessa competição de Battleship é que a árvore acima é quase pequena o suficiente para forçar esse tipo de algoritmo. Se houver ~ 150 posições possíveis para cada um dos 5 navios, são 150 5 = 75 bilhões de possibilidades. E esse número só fica menor, especialmente se você puder eliminar navios inteiros.

O oponente ao qual vinculei acima não explora a árvore inteira; 75 bilhões ainda são grandes demais para entrar em menos de um segundo. Ele tenta estimar essas probabilidades, no entanto, com a ajuda de algumas heurísticas.


Até agora, você está batendo a nossa única outra solução completa em cerca de 67,7% para 32,3% :)
John Gietzen

2
Estou definitivamente curioso para ver como uma "abordagem de probabilidade" se compara a uma "abordagem geométrica". Percebi que esse oponente de probabilidade realmente faz movimentos que seguem os padrões geométricos discutidos em outras respostas. Pode ser que o uso da geometria seja tão bom e muito mais rápido. :)
Nate Kohl

12

Não é uma resposta completa, mas parece que não há muito sentido em confundir as respostas reais com códigos comuns. Portanto, apresento algumas extensões / classes gerais no espírito de código aberto. Se você usá-los, altere o espaço para nome ou tentar compilar tudo em uma dll não funcionará.

O BoardView permite que você trabalhe facilmente com um quadro anotado.

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

Algumas extensões, algumas delas duplicam a funcionalidade na estrutura principal, mas devem realmente ser feitas por você.

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

Algo que acabo usando muito.

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

Randomization. Seguro, mas testável, útil para testes.

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

No momento, não tenho tempo para escrever um algoritmo completo, mas aqui está um pensamento: se o seu oponente colocasse navios aleatoriamente, as probabilidades de posicionamento não seriam uma distribuição simples centrada em (5.5,5.5)? Por exemplo, as possibilidades de posicionamento do navio de guerra (5 unidades de comprimento) na dimensão x estão aqui:

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

Os mesmos cálculos seriam válidos para y. Os outros navios não teriam distribuições tão íngremes, mas seu melhor palpite ainda é o centro. Depois disso, a abordagem matemática estaria lentamente irradiando diagonais (talvez com o comprimento da nave média, 17/5) para fora do centro. Ex:

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

Obviamente, alguma aleatoriedade precisaria ser adicionada à ideia, mas acho que puramente matematicamente esse é o caminho a seguir.


Sim, de fato eles fariam. Meu motor antigo compensou isso.
John Gietzen

1
De onde eu venho, irradiar diagonais para fora do centro é considerado trapaça .
Bzlm 30/10/09

Se for considerado trapaça, há uma contramedida bastante fácil. Evite (x, y) onde x = y. :)
ine

5
Eu acho que ele estava fazendo alusão à contagem de cartas? O que, na minha opinião, não é trapaça.
John Gietzen

10

Nada tão sofisticado, mas aqui está o que eu criei. Ele vence o oponente aleatório 99,9% do tempo. Estaria interessado se alguém tiver outros pequenos desafios como este, foi muito divertido.

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

Ligeiramente condensado para ocupar um espaço mínimo aqui e ainda assim ficar legível.


6

Alguns comentários sobre o mecanismo de competição:

Parâmetros NewGame:

Se o IBattleshipOpponent :: NewGame for destinado à configuração pré-jogo e tiver um tamanho de placa, também deverá levar uma lista de navios e seus respectivos tamanhos. Não faz sentido permitir o tamanho variável da placa sem permitir configurações de envio variáveis.

Os navios são selados:

Não vejo nenhuma razão para a classe Ship ser selada. Entre outras coisas básicas, gostaria que os Ships tivessem um Nome, para que eu pudesse enviar mensagens como ("Você afundou meu {0}", ship.Name); . Também tenho outras extensões em mente, então acho que Ship deve ser herdável.

Prazos:

Embora o limite de tempo de 1 segundo faça sentido para uma regra de torneio, ela mexe totalmente com a depuração. A BattleshipCompetition deve ter uma configuração fácil para ignorar violações do tempo para ajudar no desenvolvimento / depuração. Eu também sugeriria investigar System.Diagnostics.Process :: UserProcessorTime / Privileged ProcessorTime / TotalProcessorTime para obter uma visão mais precisa de quanto tempo está sendo usado.

Navios afundados:

A API atual informa quando você afunda o navio de um participante:

ShotHit(Point shot, bool sunk);

mas não qual navio você afundou! Considero que faz parte das regras dos navios de guerra humanos que você deve declarar "Você afundou meu navio de guerra!" (ou destruidor, ou sub, etc).

Isso é especialmente crítico quando uma IA está tentando liberar navios que se chocam. Gostaria de solicitar uma alteração na API para:

ShotHit(Point shot, Ship ship);

Se o navio não for nulo, isso implica que o tiro foi um naufrágio e você sabe qual navio afundou e quanto tempo levou. Se o tiro foi sem afundamento, o navio é nulo e você não tem mais informações.


Poste exemplos de código se achar que o tempo pode ser feito com mais precisão. Eu não quero mudar muito as regras agora.
John Gietzen

Além disso, os tamanhos dos navios são passados ​​durante os PlaceShips (), que são executados exatamente uma vez por jogo e também podem ser usados ​​como uma fase de configuração. Por favor, sinta-se livre para abrir o navio para seu próprio teste, mas pretendo usar o lacrado para o torneio.
John Gietzen

ERRO: @ John Gietzen: Eu determinei que o PlaceShips NÃO é executado exatamente uma vez por jogo (como você afirmou). Se um jogador coloca seus navios incorretamente (como o RandomOpponent costuma fazer), o PlaceShips é chamado repetidamente, sem uma chamada NewGame intermediária.
22415 abelenky

5
Eu sempre considerei uma estratégia colocar dois navios em uma configuração L para fazer meu oponente pensar que eles afundaram um navio de guerra quando na verdade não o fizeram. Eu nunca tive a impressão de que você tinha que declarar qual barco afundou.
21139 Josh Smeaton

3
@ DJ: Eu estou seguindo as regras originais de papel e caneta. Lembre-se que a Hasbro é uma empresa de brinquedos e que este jogo é anterior à Hasbro.
John Gietzen

5

CrossFire atualizado. Sei que não pode competir com Farnsworth ou Dreadnought, mas é muito mais rápido que o último e simples de jogar, caso alguém queira tentar. Isso depende do estado atual das minhas bibliotecas, incluídas aqui para facilitar o uso.

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

É o melhor que pude reunir no meu tempo livre, o que é inexistente. Existem algumas estatísticas de jogo e correspondência em andamento, enquanto eu configuro a função principal para executar um loop e executar continuamente o BattleshipCompetition até pressionar uma tecla.

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

Essa lógica é a mais próxima que eu tive de vencer o Dreadnought, vencendo cerca de 41% dos jogos individuais. (Na verdade, ele venceu uma partida por uma contagem de 52 a 49.) Por incrível que pareça, essa classe não se sai tão bem contra FarnsworthOpponent quanto uma versão anterior que era muito menos avançada.


5

Meu computador está sendo reparado pela dell agora, mas é aqui que eu estava na semana passada:

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
Parabéns pela prata. Você se importa em descrever seu algoritmo em palavras? Seria interessante saber sobre.
Thomas Ahle 28/05

4

Se você é grosseiro forçando sua análise, poderá achar a mecânica do RandomOpponent fornecido altamente ineficiente. Ele se permite selecionar novamente os locais já segmentados e permite que a estrutura o force a repetir até atingir um que ainda não foi tocado ou o tempo limite por movimento expirar.

Esse oponente tem um comportamento semelhante (a distribuição efetiva do posicionamento é a mesma), apenas realiza a verificação de sanidade e consome apenas uma geração de número aleatório por chamada (amortizado)).

Isso usa as classes nas minhas extensões / resposta da biblioteca e eu apenas forneço os principais métodos / estado.

A reprodução aleatória é retirada da resposta de Jon Skeet aqui

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

Não vou poder participar, mas aqui está o algoritmo que eu implementaria se tivesse tempo:

Primeiro, quando detecto um golpe, não persigo o resto do navio imediatamente - construo uma tabela de locais dos navios e descubro se atingi todos os cinco pelo menos uma vez antes de começar a afundá-los completamente. (Observe que esta é uma política ruim para a variante de disparos múltiplos - veja comentários)

  1. Bata no centro (veja a nota final abaixo - 'centro' é apenas uma conveniência para descrição)
  2. Bata no ponto 4 à direita do centro
  3. Bata no ponto 1 abaixo e um à direita do centro
  4. Atinja o ponto quatro à direita do golpe anterior
  5. Continue nesse padrão (deve terminar com linhas diagonais separadas por 3 espaços preenchendo o tabuleiro). Isso deve atingir todos os barcos de 4 e 5 comprimentos e um número estatisticamente grande de 3 e 2 barcos.

  6. Comece a acertar pontos aleatoriamente entre as diagonais, para pegar os barcos de 2 e 3 comprimentos que ainda não foram notados.

Depois de detectar 5 ocorrências, determinaria se as ocorrências estavam em barcos separados. Isso é relativamente fácil, fazendo mais alguns disparos perto de locais onde dois hits estão na mesma linha horizontal ou vertical e estão a 5 locais um do outro (pode haver dois hits no mesmo barco). Se forem barcos separados, continue a afundar todos os navios. Se for encontrado o mesmo barco, continue os padrões de enchimento acima até que todos os 5 barcos estejam localizados.

Este algoritmo é um algoritmo de preenchimento simples. As principais características são que ele não perde tempo afundando navios que conhece quando ainda existem navios desconhecidos e não usa um padrão de preenchimento ineficiente (ou seja, um padrão totalmente aleatório seria um desperdício).

Notas finais:

A) "Centro" é um ponto de partida aleatório no tabuleiro. Isso elimina a principal fraqueza desse algoritmo. B) Embora a descrição indique o desenho de diagonais imediatamente desde o início, o ideal é que o algoritmo simplesmente atire em locais 'aleatórios' que estão ao longo dessas diagonais. Isso ajuda a impedir que o competidor cronometre quanto tempo até que seus navios sejam atingidos por padrões previsíveis.

Isso descreve um algoritmo 'perfeito', pois todos os navios ficam abaixo de (9x9) / 2 + 10 tiros.

No entanto, pode ser melhorado significativamente:

Quando um navio for atingido, identifique seu tamanho antes de fazer as linhas diagonais 'internas'. Você pode ter encontrado a 2 nave, nesse caso as diagonais internas podem ser simplificadas para encontrar as 3 naves de tamanho mais rapidamente.

Identifique os estágios do jogo e aja de acordo. Esse algoritmo pode ser bom até um certo ponto do jogo, mas outros algoritmos podem gerar melhores benefícios como parte do jogo final. Além disso, se o outro jogador estiver muito perto de derrotá-lo, outro algoritmo pode funcionar melhor - por exemplo, um algoritmo de alto risco pode falhar com mais frequência, mas quando funciona, ele funciona rapidamente e você pode derrotar seu oponente que está mais próximo de vencer do que você. .

Identifique o estilo de jogo do competidor - isso pode lhe dar pistas sobre como eles planejam a colocação de navios (por exemplo, é bom que seu próprio algoritmo identifique mais rapidamente como eles colocam seus próprios navios) - se a única ferramenta que você possui é um martelo, tudo parece uma unha)

-Adão


A estratégia de esperar para afundar navios até que todos sejam encontrados depende muito da variação de um tiro por turno. Sob a versão (número de navios sobreviventes) de tiros por turno, é vantajoso afundar navios o mais rápido possível, a fim de diminuir a velocidade do seu oponente.
23411 Jason Owen

4

Minha entrada.

Nada terrivelmente especial e não tive tempo de acrescentar todas as boas idéias que tive.

Mas parece jogar bastante bem. Vamos ver como é a competição:

(coloque isso no arquivo Missouri.cse adicionado ao projeto.)

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

E agora que enviei minha inscrição, algumas estatísticas aproximadas: vs. BP7 44% vencem. / vs. Dreadnought 20% ganha. / vs. Farnsworth 42% vence. Foi um projeto divertido.
22415 abelenky

2

Isso não é minimax. Na verdade, depois de colocar os navios, cada jogador não pode jogar por conta própria, resultando em várias rodadas que ele levou para afundar todos os navios adversários? O que menos revezou vence.

Eu não acho que haja boas estratégias gerais além de afundar navios atingidos e tentar minimizar o número de disparos para cobrir os possíveis outros locais onde os navios possam se esconder.

É claro que pode haver contra-estratégias para qualquer coisa que não seja aleatória. Mas não acho que haja estratégias que sejam boas contra todos os possíveis jogadores.


1
Potencialmente, sim, eles poderiam jogar por conta própria. Não é assim que isso será executado. Ótima idéia, no entanto. Nesta competição, quero que seja possível evitar estatisticamente os chutes do seu oponente.
John Gietzen

2
Entendo. Usando dados de jogos anteriores contra o mesmo oponente, pode-se adaptar a ele?
ziggystar

2

Na verdade, acho que o maior problema do quebra-cabeça é que são essencialmente dois movimentos. Uma jogada é colocar suas naves, a outra é encontrar as naves inimigas (por mais segmentada que possa ser a segunda parte, além de tentar bater um relógio com um fator aleatório, é só 'rodar seu algoritmo'). Não há mecanismo para tentar determinar e depois combater uma estratégia inimiga, que é o que torna as competições similares baseadas em rodadas sucessivas de "tesouras de papel de pedra" bastante interessantes.

Além disso, acho que seria mais legal se você especificasse o jogo como um protocolo de rede e depois fornecesse a estrutura para implementar esse protocolo em C #, em vez de determinar que todas as soluções deveriam ser em C #, mas essa é apenas a minha opinião.

EDIT: Abandono meu argumento inicial, pois não li as regras da competição com cuidado.


Nem todas as soluções devem estar em c #. Posso compilar e vincular um assembly separado. Além disso, você deve ser capaz de combater estatisticamente o seu oponente.
John Gietzen

J #? talvez? Lol, jk. Eu tenho uma estrutura TCP para isso, mas esse torneio precisa ser executado muito rapidamente.
John Gietzen

Por que você presumiria que a comunicação TCP entre dois processos na mesma máquina não seria incrivelmente rápida?
Jherico

@ Jerico: Se eu estivesse usando o TCP, estaria isolando os mecanismos em seus próprios PCs para que eles pudessem usar os recursos da CPU que desejassem.
John Gietzen

Mesmo assim, duas máquinas na mesma LAN poderia facilmente completar um jogo em menos de um segundo com a rede sobrecarga ser mínima
Jherico

2

Eu sempre gostei de começar no meio e me afastar daquele ponto, deixando não mais do que 1 espaço em branco entre outros pontos para explicar esse maldito sub ... o espaço entre os tiros dependia de quais navios afundavam. se o navio B era o último, os tiros só precisavam deixar 4 espaços para minimizar os desperdícios de tiros


1
Então ... eu só preciso ficar longe do meio? :)
darron 27/10/2009

14
Você também precisa ficar longe das arestas, porque um golpe na borda contém mais informações para o seu oponente do que um golpe que não seja na borda. Portanto, você deve colocar todos os seus navios em uma região não-média e não-periférica. A menos que seja isso que eles esperam que você faça.
Jherico

1
Se você começar deixando 3 ou 4 espaços, poderá ter a sorte de acertar o sub de qualquer maneira. Caso contrário, volte e tente preencher as lacunas. Mais em: somethinkodd.com/oddthinking/2009/10/29/battleship-strategy
Oddthinking

18
O navio com dois buracos não é um maldito submarino , é um maldito barco PT . O sub tem três furos. :)
corvo

2

Houve uma competição semelhante realizada pelo Dr. James Heather, da Universidade de Surrey, em nome da Sociedade Britânica de Computação.

Limitações foram impostas aos recursos - ou seja, o tempo máximo do processador por turno, nenhum estado pode ser armazenado entre as jogadas, o tamanho máximo de heap imposto. Para limitar o tempo, a IA poderia enviar uma jogada em qualquer ponto dentro do intervalo de tempo e seria solicitada uma mudança após o término do turno.

Muito interessante - veja mais em: http://www.bcsstudentcontest.com/

Pode lhe dar mais algumas idéias.


2

No momento, a solução é aberta e executada sem nenhuma modificação no monodesenvolvimento no ubuntu 9.10 linux


1

Você escreveu:

  • Qualquer coisa que seja considerada contrária ao espírito da competição será motivo de desqualificação.
  • Interferir com um oponente é contra o espírito da competição.

por favor defina "contra o espírito da competição" e "interferindo com um oponente"?

Além disso - para simplificar, recomendo que você:

  • proibir o uso de CPU durante o slot da CPU do oponente.
  • desautorize o paralelismo do encadeamento e, em vez disso, forneça mais segundos de CPU em um único encadeamento. Isso simplificará a programação da IA ​​e não prejudicará ninguém que esteja vinculado à CPU / memória.

PS - uma pergunta para os pós-docs do CS à espreita aqui: esse jogo não é solucionável (ou seja, existe uma melhor e única estratégia?). sim, o tamanho da placa e o número de etapas tornam obrigatório o minimax e outros, mas ainda assim eu tenho que me perguntar ... está longe de ir e jogar xadrez com complexidade.


Eu tinha uma reflexão em mente quando disse "Interferir". Não quero que os concorrentes vencam porque eles mexeram em outro motor até a morte.
John Gietzen

8
Eu sugeriria que a espionagem é uma parte importante da guerra moderna, portanto refletir para encontrar os alvos seria o ideal - afinal, era um dos métodos usados ​​durante a segunda guerra mundial ...
Rowland Shaw

Eu tenho uma estrutura para isolar os mecanismos em diferentes PCs, comunicando-me por TCP / IP, tornando o Reflection inútil. No entanto, devido ao meu número estimado de inscrições, isso faria com que a competição levasse um tempo proibitivamente longo.
John Gietzen

6
Eu não sabia que eles tinham Reflexão naquela época!
Markus Nigbur 27/10/09

1

Prevejo que a pessoa que conseguir fazer engenharia reversa do padrão aleatório de sementes e call de seus oponentes vencerá.

Não tenho certeza da probabilidade disso.


Os oponentes têm a opção de usar um CSPRNG.
John Gietzen

Bom argumento, embora eu admita que a engenharia reversa como essa semente esteja além da minha experiência. Eu acho que o aspecto mais vulnerável seria o algoritmo de seleção de padrões de tiro - mas mesmo assim você não ganha necessariamente muito com a quebra, pois não há como mover seus navios depois que o jogo começar.
Triston Attridge 27/10/2009

Quando eu estava me inscrevendo no estágio de pesquisa, escrevemos programas de encouraçados e competimos. Ao definir a semente aleatória, foi exatamente como eu ganhei X) #
P Shved

1
Supondo um algoritmo de posicionamento de navio razoavelmente simples, eu poderia imaginar que, após receber alguns hits em navios diferentes, comece a usar a maior parte do seu turno percorrendo todas as sementes aleatórias possíveis (provavelmente começando com algo próximo do horário atual e avançando / para trás um passo mais ou menos) e ver quais geram canais de navios compatíveis com os hits observados.
Domenic

1

Presumivelmente, também seria possível executar uma série deles com variações no jogo.

Adicionar coisas como um avião em 3D ou ser capaz de mover uma única nave em vez de disparar por um turno provavelmente mudaria bastante o jogo.


2
Existe a variação "salva". Onde você consegue disparar tantos tiros por turno quanto os navios restantes.
John Gietzen

Uma variação interessante também. Lembro-me de reproduzir uma versão em computador que também tinha um avião. Seria disparado aleatoriamente em locais no tabuleiro adversário.
Glenn

outra variação: seja o tamanho da prancha + número de navios.
russau

1

O tempo total de um segundo de jogo é específico da máquina. Uma vez que o segundo valor das operações da CPU será diferente na minha máquina em comparação com a máquina do torneio. Se eu otimizar o algoritmo do Navio de Batalha para utilizar o máximo de tempo de CPU dentro de 1 segundo, ele será executado em uma possível máquina de torneio mais lenta e sempre perderá.

Não tenho certeza de como contornar essa limitação da estrutura, mas ela deve ser abordada.

...

Uma idéia é fazer o que foi feito nesta competição http://www.bcsstudentcontest.com /

E tenha um tempo máximo por turno, em oposição ao tempo total máximo do jogo. Dessa forma, eu poderia limitar os algoritmos a se ajustarem dentro de um tempo de retorno conhecido. Um jogo pode durar de 50 a 600 ou mais turnos, se o algoritmo my gerencia seu tempo total de jogo, pode não dar tempo suficiente para fazer seu melhor trabalho ou pode dar muito tempo e perder. É muito difícil gerenciar o tempo total do jogo dentro do algoritmo Battleship.

Eu sugeriria alterar as regras para limitar o tempo de turno e não o tempo total do jogo.

Editar

Se eu escrevi um algoritmo que enumera todos os disparos possíveis e depois os classifica, ele obtém o tiro mais alto do ranking. Levaria muito tempo para gerar todos os disparos possíveis, então eu deixaria o algoritmo rodar por um certo período de tempo e o pararia.

Se houvesse um limite com base em turnos, eu poderia deixar o algoritmo rodar por 0,9 segundos e retornar a foto mais alta do ranking, e estar bem dentro do limite de tempo de turno.

Se eu estiver limitado ao tempo total de jogo de um segundo, será difícil determinar quanto tempo o algoritmo deve ser executado a cada turno. Vou querer maximizar o meu tempo de CPU. Se um jogo durasse 500 rodadas, eu poderia limitar cada turno a 0,002 segundos, mas se um jogo durasse 100 rodadas, eu poderia dar a cada turno 0,01 segundos de tempo de CPU.

Seria impraticável para um algoritmo usar uma pesquisa semi-exaustiva do espaço de tiro para encontrar o melhor tiro com a limitação atual.

O tempo total de 1 segundo está limitando o tipo de algoritmos que podem ser efetivamente usados ​​para competir no jogo.


Isso será executado em um computador Intel Q9550SX quad core, 8 GB de RAM, Vista 64. 1 segundo será um fator limitante?
John Gietzen

Acho que você deve fazer sua IA de encouraçado multithread, então, para calcular o número máximo de disparos por esse intervalo de tempo.
911 Jeff Atwood

o truque é como limitar o intervalo de tempo da curva. Se o limitar em 0,00005 segundos, não posso executar o tempo limite, mas estou limitando significativamente o espaço de pesquisa. Se eu aumentar o limite de tempo de turno, o espaço de pesquisa aumentará, mas corro o risco de ficar sem tempo.
21411 TonyAbell

@TonyAbell: Se é importante ter um limite de tempo com base em turnos, por que não começar com um valor inicial e ajustá-lo de uma rodada para outra? Após cerca de metade das rodadas, você provavelmente encontrará a duração ideal do turno para o oponente que está enfrentando.
22610 kyokley

Você deve acompanhar o tempo restante e limitar a metade do tempo disponível restante.
John Gietzen

1

Estou indo embora aqui não inserindo código real - mas arriscarei algumas observações gerais:

  • Como todas as naves têm pelo menos 2 células de tamanho, você pode usar uma otimização que vi na implementação do jogo no Space Quest V - que só dispara em células alternativas em um padrão de diamante enquanto "procura" um alvo. Isso elimina metade dos quadrados, enquanto ainda garante que você encontrará todos os navios eventualmente.
  • Um padrão de disparo aleatório ao procurar alvos produzirá estatisticamente os melhores resultados em muitos jogos.

1

! [Densidade de Probabilidade] [1] insira a descrição da imagem

! [insira a descrição da imagem aqui] [2]

Eu experimentei comparar os resultados do tiroteio de randon contra uma caça / alvo burro e, finalmente, uma pesquisa sofisticada.

A melhor solução parece ser criar uma função de densidade de probabilidade para a probabilidade de qualquer quadrado individual ser usado pelos navios restantes e apontar com o quadrado com o valor mais alto.

Você pode ver meus resultados aqui, digite a descrição do link aqui


Você poderia, talvez, corrigir sua resposta, especialmente suas imagens e links?
24511 Bart Bart

-2

"Battleship" é o que é conhecido como um problema NP-completo da ciência da computação clássica.

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

(procure Battleship - está lá, em jogos e quebra-cabeças)


4
Que é um quebra-cabeça de navio de guerra ( en.wikipedia.org/wiki/Battleship_(puzzle) ), não um navio de guerra no jogo ( en.wikipedia.org/wiki/Battleship_(game) ).
21430 Jason Berkan

Sim, como Jason afirmou, este é um animal totalmente diferente.
John Gietzen

3
Hehehe. Na próxima tarefa que eu chegar ao trabalho, vou dizer que está completo, e depois almoçar. :
Bork Blatt
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.