web-dev-qa-db-fra.com

Quelle est la meilleure AI de cuirassé?

Bataille navale!

En 2003 (quand j'avais 17 ans), j'ai participé à une compétition de codage Battleship AI . Même si j'ai perdu ce tournoi, je me suis beaucoup amusé et j'ai beaucoup appris de ce tournoi.

Maintenant, je voudrais ressusciter cette compétition, à la recherche du meilleur cuirassé AI.

Voici le framework, maintenant hébergé sur Bitbucket .

Le vainqueur se verra attribuer +450 de réputation! Le concours aura lieu à partir du 17 novembre 2009 . Aucune inscription ou modification postérieure à zéro heure le 17 ne sera acceptée. (Heure normale du Centre) Soumettez vos entrées tôt, pour ne pas manquer votre chance!

Pour conserver cet [~ # ~] objectif [~ # ~] , veuillez suivre l'esprit de la compétition.

Règles du jeu:

  1. Le jeu se joue sur une grille 10x10.
  2. Chaque concurrent placera chacun des 5 navires (de longueur 2, 3, 3, 4, 5) sur sa grille.
  3. Aucun navire ne peut se chevaucher, mais ils peuvent être adjacents.
  4. Les compétiteurs tirent ensuite à tour de rôle sur leur adversaire.
    • Une variante du jeu permet de tirer plusieurs coups par volée, un pour chaque navire survivant.
  5. L'adversaire informera le concurrent si le tir coule, touche ou manque.
  6. Le jeu se termine lorsque tous les navires d'un joueur sont coulés.

Règles du concours:

  1. L'esprit de la compétition est de trouver le meilleur algorithme de cuirassé.
  2. Tout ce qui est jugé contraire à l'esprit de la compétition sera considéré comme un motif de disqualification.
  3. Interférer avec un adversaire est contraire à l'esprit de la compétition.
  4. Le multithreading peut être utilisé avec les restrictions suivantes:
    • Pas plus d'un fil peut être en cours d'exécution alors que ce n'est pas votre tour. (Cependant, un nombre quelconque de threads peuvent être dans un état "suspendu").
    • Aucun thread ne peut être exécuté avec une priorité autre que "Normal".
    • Compte tenu des deux restrictions ci-dessus, vous aurez la garantie d’au moins 3 cœurs de processeur dédiés pendant votre tour.
  5. Une limite de 1 seconde de temps processeur par partie est allouée à chaque concurrent sur le fil principal.
  6. Le fait de manquer de temps entraîne la perte de la partie en cours.
  7. Toute exception non gérée entraînera la perte de la partie en cours.
  8. L'accès réseau et l'accès au disque sont autorisés, mais vous pouvez trouver les restrictions de temps assez prohibitives. Cependant, quelques méthodes de montage et de démontage ont été ajoutées pour atténuer les contraintes de temps.
  9. Le code doit être affiché sur le dépassement de pile en guise de réponse ou, s'il est trop volumineux, lié.
  10. La taille totale maximale (non compressée) d'une entrée est de 1 Mo.
  11. Officiellement, .Net 2.0/3.5 est la seule exigence du cadre.
  12. Votre entrée doit implémenter l'interface IBattleshipOpponent.

Notation:

  1. Les 51 meilleurs jeux sur 101 jeux sont les gagnants d'un match.
  2. Tous les concurrents joueront les uns contre les autres, à tour de rôle.
  3. La meilleure moitié des concurrents disputera ensuite un tournoi en double élimination pour déterminer le vainqueur. (La plus petite puissance de deux qui est supérieure ou égale à la moitié, en fait.)
  4. Je vais utiliser le cadre TournamentApi pour le tournoi.
  5. Les résultats seront affichés ici.
  6. Si vous soumettez plus d'une inscription, seule votre inscription ayant obtenu le meilleur score est éligible pour le double-élim.

Bonne chance! S'amuser!


EDIT 1:
Merci à Freed , qui a trouvé une erreur dans le Ship.IsValid une fonction. Cela a été corrigé. Veuillez télécharger la version mise à jour du framework.

EDIT 2:
Comme la persistance des statistiques sur disque a suscité un vif intérêt, j’ai ajouté quelques événements de configuration et de démontage non chronométrés qui devraient fournir les fonctionnalités requises. Ceci est un changement semi-radical . C'est-à-dire que l'interface a été modifiée pour ajouter des fonctions, mais aucun corps n'est requis pour celles-ci. Veuillez télécharger la version mise à jour du framework.

EDIT 3:
Correctif 1: GameWon et GameLost n'étaient appelés que dans le cas d'une pause.
Correctif 2: Si un moteur retardait chaque match, la compétition ne se terminerait jamais.
Veuillez télécharger la version mise à jour du framework.

EDIT 4:
Résultats du tournoi:

315
John Gietzen

J'appuie la motion pour faire beaucoup plus de jeux par match. Faire 50 jeux, c'est lancer une pièce de monnaie. Je devais faire 1000 jeux pour obtenir une distinction raisonnable entre les algorithmes de test.

Télécharger Dreadnought 1.2 .

Stratégies:

  • garder une trace de toutes les positions possibles pour les navires qui ont> 0 hits. La liste ne dépasse jamais ~ 30K, elle peut donc être conservée exactement, contrairement à la liste de toutes les positions possibles pour tous les navires (qui est très longue).

  • L'algorithme GetShot comprend deux parties, l'une qui génère des tirs aléatoires et l'autre qui tente de finir de couler un navire déjà touché. Nous effectuons des tirs au hasard s’il existe une position possible (parmi la liste ci-dessus) dans laquelle tous les navires touchés sont coulés. Sinon, nous essayons de finir de couler un navire en choisissant un endroit où tirer, ce qui élimine le plus de positions possibles (pondéré).

  • Pour les tirs aléatoires, calculez le meilleur emplacement pour tirer en fonction du risque de chevauchement de l'un des navires non saturés.

  • algorithme adaptatif qui place les navires dans des endroits où l'adversaire est statistiquement moins susceptible de tirer.

  • algorithme adaptatif qui préfère tirer aux endroits où l'adversaire est statistiquement plus susceptible de placer ses navires.

  • placer les navires surtout ne se touchant pas.

56
Keith Randall

Voici mon entrée! (La solution la plus naïve possible)

"Aléatoire 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() { }
    }
}
35
John Gietzen

Voici un adversaire contre lequel les joueurs peuvent jouer:

Au lieu d'utiliser une stratégie inspirée de la géométrie fixe, j'ai pensé qu'il serait intéressant d'essayer d'estimer les probabilités sous-jacentes qu'un espace non exploré particulier détient un navire.

Pour faire cela correctement, vous exploreriez toutes les configurations possibles des navires qui correspondent à votre vision actuelle du monde, puis calculiez des probabilités en fonction de ces configurations. Vous pourriez penser à cela comme explorer un arbre:

ne expansion des états possibles de cuirassés http://natekohl.net/media/battleship-tree.png

Après avoir examiné toutes les feuilles de cet arbre qui concordent avec ce que vous savez sur le monde (par exemple, les navires ne peuvent pas se chevaucher, tous les grands carrés doivent être des navires, etc.) , vous pouvez compter les navires se présentent souvent à chaque position inexplorée pour estimer la probabilité qu’un navire y soit assis.

Cela peut être visualisé comme une carte thermique, où les points chauds sont plus susceptibles de contenir des navires:

ne carte de chaleur des probabilités pour chaque position inexplorée http://natekohl.net/media/battleship-probs.png

Ce que j’aime dans cette compétition Battleship, c’est que l’arbre ci-dessus est presque assez petit pour forcer ce type d’algorithme. S'il y a ~ 150 positions possibles pour chacun des 5 navires, c'est 1505 = 75 milliards de possibilités. Et ce nombre ne fait que diminuer, surtout si vous pouvez éliminer des navires entiers.

L'adversaire que j'ai lié ci-dessus n'explore pas l'arbre entier; 75 milliards est encore trop gros pour entrer en moins d'une seconde. Il tente cependant d’estimer ces probabilités à l’aide de quelques heuristiques.

22
Nate Kohl

Pas une réponse à part entière, mais il semble y avoir peu d’intérêt à encombrer les vraies réponses avec du code commun. Je présente donc quelques extensions/classes générales dans l’esprit open source. Si vous les utilisez, veuillez modifier l'espace de noms ou essayer de tout compiler dans une dll ne fonctionnera pas.

BoardView vous permet de travailler facilement avec un tableau annoté.

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

Certaines extensions, certaines de celles-ci dupliquent des fonctionnalités dans le cadre principal, mais doivent être réalisées par vous-même.

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

Quelque chose que je finis par utiliser beaucoup.

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

Randomisation. Sécurisé mais testable, utile pour les tests.

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!");
        }
    }
}
12
ShuggyCoUk

Je n'ai pas le temps d'écrire un algorithme à part entière, mais voici une pensée: si votre adversaire place des navires au hasard, les probabilités de placement ne seraient-elles pas une simple distribution centrée à (5.5.5.5)? Par exemple, les possibilités de placement du cuirassé (5 unités) dans la dimension x sont les suivantes:

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

Les mêmes calculs seraient valables pour y. Les autres navires n’auraient pas une distribution aussi importante, mais votre meilleure hypothèse reste le centre. Après cela, l’approche mathématique émettrait lentement des diagonales (peut-être avec la longueur du navire moyen, 17/5) hors du centre. Ex:

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

De toute évidence, il faudrait ajouter un peu de hasard à l’idée, mais je pense que c’est purement mathématique, c’est la voie à suivre.

10
ine

Rien d'aussi sophistiqué que ce que j'ai imaginé. Il bat l'adversaire au hasard 99,9% du temps. Serait intéressé si quelqu'un a d'autres petits défis comme celui-ci, c'était bien amusant.

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

Légèrement condensé pour occuper un minimum de place ici tout en restant lisible.

10
Gavin

Quelques commentaires sur le moteur de compétition:

paramètres NewGame:

Si IBattleshipOpponent :: NewGame est destiné à la configuration avant le jeu et prend une taille de tableau, il doit également afficher une liste des navires et de leurs tailles respectives. Cela n’a aucun sens de permettre une taille de carte variable sans tenir compte de configurations de navire variables.

Les navires sont scellés:

Je ne vois aucune raison pour laquelle la classe Ship est scellée. Entre autres choses de base, j'aimerais que Ships ait un nom pour pouvoir envoyer des messages tels que ("Vous avez coulé mon {0}", ship.Name); . J'ai aussi d'autres extensions en tête, alors je pense que Ship devrait être héritable.

délais:

Bien que la limite de temps d'une seconde ait du sens pour une règle de tournoi, elle gâche totalement le débogage. BattleshipCompetition devrait avoir un paramètre facile pour ignorer les violations de temps afin d'aider au développement/débogage. Je suggérerais également d’enquêter sur System.Diagnostics.Process :: UserProcessorTime/Privileged ProcessorTime/TotalProcessorTime pour obtenir une vue plus précise du temps utilisé.

Navires Coulés:

L'API actuelle vous informe lorsque vous avez coulé le navire d'un oppenent:

ShotHit(Point shot, bool sunk);

mais pas qui vous expédiez coulé! Je considère comme faisant partie des règles du cuirassé humain que vous devez déclarer "Vous avez coulé mon cuirassé!" (ou destroyer, ou sous, etc.).

Cela est particulièrement critique lorsqu'une IA tente de débusquer des navires qui se font face. J'aimerais demander un changement d'API pour:

ShotHit(Point shot, Ship ship);

Si le navire est non nul, cela signifie que le tir était un tir au ras du sol, et vous savez quel navire vous avez coulé et combien de temps il a été. Si le tir était un tir non-sombrant, le navire est nul et vous n'avez aucune autre information.

6
abelenky

CrossFire mis à jour. Je sais qu'il ne peut rivaliser avec Farnsworth ou Dreadnought, mais il est beaucoup plus rapide que ce dernier et simple à jouer au cas où quelqu'un voudrait essayer. Cela dépend de l'état actuel de mes bibliothèques, inclus ici pour le rendre facile à utiliser.

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
ShuggyCoUk

Dell est en train de réparer mon ordinateur, mais voici où je me trouvais la semaine dernière:

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; }
    }
}
5
John Boker

C’est à peu près tout ce que je pourrais faire de mieux pendant mon temps libre, ce qui est à peu près inexistant. Il y a des statistiques de pointage et de match en cours, alors que je configure la fonction principale pour qu'elle exécute en boucle et exécute continuellement le BattleshipCompetition jusqu'à ce que j'appuie sur une touche.

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

Cette logique est la plus proche de laquelle j'ai dû battre Dreadnought, remportant environ 41% des matchs individuels. (Il a en fait remporté un match par 52 à 49). Curieusement, cette classe ne réussit pas aussi bien contre FarnsworthOpponent qu’une version antérieure beaucoup moins avancée.

5
BP.

Mon entrée.

Rien de très spécial, et je n'ai pas eu le temps d'ajouter toutes les bonnes idées que j'avais.

Mais il semble jouer assez bien. Nous verrons comment ça se passe en compétition:

(mettre ceci dans le fichier Missouri.cs et ajouté au projet.)

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 ([email protected])";
        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;
    }
}
4
abelenky

Je ne vais pas pouvoir participer, mais voici l'algorithme que je mettrais en place si j'avais le temps:

Premièrement, lorsque je détecte un impact, je ne poursuis pas le reste du vaisseau immédiatement. Je construis un tableau des emplacements des vaisseaux et je détermine si j'ai touché les cinq au moins une fois avant de commencer à les couler complètement. (Notez qu'il s'agit d'une mauvaise politique pour la variante à prises multiples - voir les commentaires.)

  1. Touchez le centre (voir la note finale ci-dessous - "centre" est simplement une commodité pour la description)
  2. Frappez l'endroit 4 à la droite du centre
  3. Frapper l'endroit 1 vers le bas et un à la droite du centre
  4. Frappez le point quatre à la droite du coup précédent
  5. Continuez dans cette configuration (devrait se terminer par des lignes diagonales séparées par 3 espaces remplissant la planche). Cela devrait toucher tous les bateaux de 4 et 5 longueurs, et un nombre statistiquement important de 3 et 2 bateaux.

  6. Commencez par frapper au hasard entre les diagonales et attrapez les bateaux de 2 et 3 longueurs qui n’ont pas déjà été remarqués.

Une fois que j'ai détecté 5 coups sûrs, je déterminerais s'ils sont sur des bateaux séparés. Ceci est relativement facile en effectuant quelques coups de plus près des emplacements où deux hits sont sur la même ligne horizontale ou verticale et se trouvent à 5 emplacements l'un de l'autre (il peut s'agir de deux hits sur le même bateau). Si ce sont des bateaux séparés, continuez à couler tous les navires. S'il s'avère que c'est le même bateau, continuez les schémas de remplissage ci-dessus jusqu'à ce que les 5 bateaux soient localisés.

Cet algorithme est un algorithme de remplissage simple. La principale caractéristique est que le système ne perd pas de temps à couler des navires dont il a connaissance lorsqu'il ignore encore certains navires, et qu'il n'utilise pas un schéma de remplissage inefficace (un schéma totalement aléatoire constituerait un gaspillage).

Notes finales:

A) "Centre" est un point de départ aléatoire sur le tableau. Cela élimine la principale faiblesse de cet algorithme. B) Bien que la description indique que les diagonales sont dessinées immédiatement dès le début, l’algorithme tire simplement sur des emplacements "aléatoires" situés le long de ces diagonales. Cela permet d’empêcher le concurrent de déterminer le temps qu’il faudra avant que ses navires soient touchés par des tendances prévisibles.

Ceci décrit un algorithme "parfait" dans le sens où il obtiendra tous les vaisseaux sous (9x9)/2 + 10 coups.

Cependant, cela peut être amélioré de manière significative:

Une fois qu'un navire est touché, identifiez sa taille avant de faire les lignes diagonales "internes". Vous avez peut-être trouvé le navire 2, auquel cas les diagonales internes peuvent être simplifiées pour trouver les navires de taille 3 plus rapidement.

Identifiez les étapes du jeu et agissez en conséquence. Cet algorithme peut être utile jusqu’à un certain point du jeu, mais d’autres algorithmes peuvent offrir de meilleurs avantages dans le cadre de la phase finale. De plus, si l'autre joueur est sur le point de vous vaincre, un autre algorithme pourrait fonctionner mieux - par exemple, un algorithme à haut risque pourrait échouer plus souvent, mais si cela fonctionne, il fonctionne rapidement et vous pouvez battre votre adversaire qui est plus proche de gagner que vous. .

Identifiez le style de jeu du compétiteur - cela peut vous donner des indices sur la manière dont il planifie le placement des navires (c.-à-d., Il est probable que leur propre algorithme identifie plus rapidement la manière dont ils placent leurs propres navires - si le seul outil dont vous disposez est un marteau, tout ressemble à un clou)

-Adam

4
Adam Davis

Si vous forcez brutalement votre analyse, vous constaterez peut-être que la mécanique de RandomOpponent fourni est très inefficace. Il se permet de resélectionner des emplacements déjà ciblés et laisse le cadre le forcer à le répéter jusqu'à ce qu'il atteigne un emplacement qu'il n'a pas encore touché ou que le délai imparti par déplacement expire.

Cet adversaire a un comportement similaire (la distribution de placement effective est la même), il ne fait que vérifier son intégrité et ne consomme qu'une génération de nombre aléatoire par appel (amorti)).

Ceci utilise les classes de ma réponse extensions/library et je ne fournis que les méthodes/états clés.

Shuffle est levé de réponse de Jon Skeet ici

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
ShuggyCoUk

En fait, je pense que le plus gros problème du puzzle est qu’il s’agit essentiellement de deux mouvements. Un mouvement consiste à placer vos navires, l'autre à trouver les navires ennemis (aussi segmenté que puisse être la seconde partie, en plus d'essayer de battre une horloge avec un facteur aléatoire, il s'agit simplement de "lancer votre algorithme"). Il n’existe aucun mécanisme permettant de déterminer puis de contrer une stratégie ennemie, ce qui rend les compétitions similaires basées sur des séries successives de "ciseaux à papier de roche" plutôt intéressantes.

De plus, je pense qu'il serait plus cool de spécifier le jeu en tant que protocole réseau, puis de fournir le cadre pour implémenter ce protocole en C #, plutôt que de dicter que toutes les solutions doivent être en C #, mais ce n'est que mon opinion.

EDIT: J'annule mon point de départ, car je n'ai pas lu assez attentivement les règles du concours.

2
Jherico

J'ai toujours aimé commencer au milieu et s'éloigner de ce point, ne laissant qu'un espace vide entre les autres points pour rendre compte de ce maudit sous-marin ... l'espace entre les tirs dépendait du navire coulé. si le navire B était le dernier, les tirs ne devaient laisser que 4 espaces entre eux pour minimiser les tirs perdus

2
CheeseConQueso

Ce n'est pas minimax. En fait, après avoir placé les navires, chaque joueur ne peut-il pas jouer seul, ce qui lui a pris plusieurs tours pour couler tous les navires adverses? Celui qui a pris moins de tours gagne.

Je ne pense pas qu'il existe de bonnes stratégies générales allant au-delà du naufrage des navires et de la minimisation du nombre de tirs afin de couvrir les derniers endroits où les navires pourraient se cacher.

Bien sûr, il pourrait y avoir des contre-stratégies pour tout ce qui n'est pas aléatoire. Mais je ne pense pas qu’il existe des stratégies efficaces contre tous les joueurs possibles.

2
ziggystar

En l'état, la solution s'ouvre et fonctionne sans modification dans monodevelop sous Ubuntu 9.10 Linux

2
John Boker

Le Dr James Heather de l'Université de Surrey a organisé un concours similaire au nom de la British Computer Society.

Les ressources étaient limitées: temps processeur maximal par tour, aucun état ne pouvait être stocké entre les déplacements, taille de segment maximale imposée. Pour limiter le temps, l'IA pourrait soumettre un mouvement à n'importe quel moment de la plage horaire et serait priée de le faire à la fin du tour.

Très intéressant - voir plus à: http://www.bcsstudentcontest.com/

Pourrait vous donner quelques idées supplémentaires.

2
Tom Duckering

Tu as écrit:

  • Tout ce qui est jugé contraire à l'esprit de la compétition sera considéré comme un motif de disqualification.
  • Interférer avec un adversaire est contraire à l'esprit de la compétition.

veuillez définir "contre l’esprit de la compétition" et "interférer avec un adversaire"?

Aussi - pour simplifier, je vous recommande:

  • n'autorise aucune utilisation de la CPU pendant la fente de la CPU de l'adversaire.
  • interdit le parallélisme des threads et donne à la place plus de secondes CPU sur un seul thread. Cela simplifiera la programmation de l'intelligence artificielle et n'endommagera de toute façon personne liée au processeur/à la mémoire.

PS - Une question à poser aux post-doctorants CS qui se cache ici: ce jeu ne peut-il pas être résolu (c’est-à-dire, existe-t-il une stratégie unique et optimale?). oui, la taille de la planche et le nombre de marches obligent minimax et autres à être obligatoires, mais je dois quand même me demander ... c'est loin d'être un jeu d'échecs et de complexité.

1
Shachar

Il serait vraisemblablement possible d’en exécuter une série avec des variantes du jeu.

Ajouter des choses comme un avion en 3D ou être capable de déplacer un seul navire au lieu de tirer pendant un tour changerait probablement le jeu un peu.

1
Glenn

Le temps de jeu total d'une seconde est spécifique à la machine. Une seconde d'opération du processeur sera différente sur ma machine par rapport à la machine du tournoi. Si j'optimise l'algorithme Battle Ship pour qu'il utilise le plus de temps processeur en une seconde, il est exécuté sur une machine de tournoi éventuellement plus lente, il perdra toujours.

Je ne sais pas comment contourner cette limitation du cadre, mais il faut y remédier.

...

Une idée est de faire ce qui a été fait dans cette compétition http://www.bcsstudentcontest.com /

Et avoir un temps maximum par tour par opposition au temps total maximum de jeu. De cette façon, je pouvais limiter les algorithmes pour qu'ils tiennent dans un délai de rotation connu. Un jeu peut durer plus de 50 à 600 tours. Si l’algorithme my gère son temps de jeu total, il risque de ne pas lui laisser le temps nécessaire pour faire de son mieux, ou de perdre trop de temps. Il est très difficile de gérer le temps total de jeu dans l'algorithme Battleship.

Je suggérerais de changer les règles pour limiter le temps de tour et non le temps total de jeu.

Modifier

Si j'ai écrit un algorithme qui énumère tous les plans possibles, puis les classe, prend le plan le plus élevé. Cela prendrait trop de temps pour générer tous les plans possibles, alors je laisserais l'algorithme fonctionner pendant un certain temps puis l'arrêterais.

S'il y avait une limite basée sur les virages, je pouvais laisser l'algorithme s'exécuter pendant 0,9 seconde et renvoyer le tir le plus élevé, tout en respectant la limite de temps de virage.

Si je suis limité au temps de jeu total d'une seconde, il sera difficile de déterminer la durée pendant laquelle l'algorithme doit s'exécuter à chaque tour. Je voudrai maximiser mon temps de calcul. Si une partie durait 500 tours, je pouvais limiter chaque tour à 0,002 seconde, mais si une partie durait 100 tours, je pouvais attribuer à chaque tour 0,01 seconde de temps CPU.

Il serait peu pratique pour un algorithme d’utiliser une recherche semi-exhaustive de l’espace de tir pour trouver le meilleur tir avec la limitation actuelle.

Le temps total de jeu d'une seconde limite le type d'algorithmes pouvant être utilisés efficacement pour rivaliser dans le jeu.

1
TonyAbell

! [Densité de probabilité] [1] enter image description her

! [entrez la description de l'image ici] [2]

J'ai expérimenté en comparant les résultats du tir au rand avec une chasse/cible stupide et finalement une recherche sophistiquée.

La meilleure solution semble être de créer une fonction de densité de probabilité pour déterminer la probabilité d'utilisation d'un carré individuel par les navires restants, et de viser avec le carré ayant la valeur la plus élevée.

Vous pouvez voir mes résultats ici entrer la description du lien ici

1
Nick Berry

Je m'arrête ici en n'introduisant pas le code réel - mais je vais hasarder quelques observations générales:

  • Étant donné que tous les navires ont au moins 2 cellules, vous pouvez utiliser une optimisation que j'ai constatée dans une implémentation du jeu dans Space Quest V - qui ne tire que sur des cellules en alternance dans un motif en losange pendant qu'elle "recherche" une cible. Cela élimine la moitié des carrés, tout en garantissant que vous retrouverez tous les navires à terme.
  • Un schéma de tir aléatoire lors de la recherche de cibles donnera statistiquement les meilleurs résultats sur de nombreux jeux.
1
Bork Blatt

Je prédis que la personne qui parvient à faire de l'ingénierie inverse de ses adversaires au départ et au modèle d'appel gagnera.

Je ne sais pas quelle est la probabilité que cela se produise.

1
Triston Attridge