Was ist die beste Schlachtschiff-KI?


315

Schlachtschiff!

Im Jahr 2003 (als ich 17 war) nahm ich an einem Battleship AI- Codierungswettbewerb teil. Obwohl ich dieses Turnier verloren habe, hatte ich viel Spaß und habe viel daraus gelernt.

Jetzt möchte ich diesen Wettbewerb auf der Suche nach der besten Schlachtschiff-KI wiederbeleben.

Hier ist das Framework, das jetzt auf Bitbucket gehostet wird .

Der Gewinner erhält einen Ruf von +450! Der Wettbewerb findet ab dem 17. November 2009 statt . Es werden keine Einträge oder Änderungen akzeptiert, die später als null Uhr am 17. sind. (Zentrale Standardzeit) Reichen Sie Ihre Einträge frühzeitig ein, damit Sie Ihre Gelegenheit nicht verpassen!

Um dieses ZIEL zu behalten , folgen Sie bitte dem Geist des Wettbewerbs.

Spielregeln:

  1. Das Spiel wird in einem 10x10-Raster gespielt.
  2. Jeder Teilnehmer wird jedes von 5 Schiffen (mit den Längen 2, 3, 3, 4, 5) in sein Gitter aufnehmen.
  3. Keine Schiffe dürfen sich überlappen, aber sie dürfen benachbart sein.
  4. Die Teilnehmer schießen dann abwechselnd Einzelschüsse auf ihren Gegner.
    • Eine Variation des Spiels ermöglicht das Abfeuern mehrerer Schüsse pro Volleyschuss, einen für jedes überlebende Schiff.
  5. Der Gegner benachrichtigt den Teilnehmer, wenn der Schuss sinkt, trifft oder verfehlt.
  6. Das Spiel endet, wenn alle Schiffe eines Spielers versenkt sind.

Wettbewerbsregeln:

  1. Der Geist des Wettbewerbs ist es, den besten Schlachtschiff-Algorithmus zu finden.
  2. Alles, was gegen den Geist des Wettbewerbs verstößt, wird disqualifiziert.
  3. Sich in einen Gegner einzumischen ist gegen den Geist des Wettbewerbs.
  4. Multithreading kann unter folgenden Einschränkungen verwendet werden:
    • Es darf nicht mehr als ein Thread ausgeführt werden, solange Sie nicht an der Reihe sind. (Es kann sich jedoch eine beliebige Anzahl von Threads in einem "Suspended" -Zustand befinden.)
    • Kein Thread darf mit einer anderen Priorität als "Normal" ausgeführt werden.
    • In Anbetracht der beiden oben genannten Einschränkungen werden Ihnen während Ihres Zuges mindestens 3 dedizierte CPU-Kerne garantiert.
  5. Jedem Konkurrenten im primären Thread wird ein Limit von 1 Sekunde CPU-Zeit pro Spiel zugewiesen.
  6. Wenn die Zeit knapp wird, verliert das aktuelle Spiel.
  7. Jede nicht behandelte Ausnahme führt zum Verlust des aktuellen Spiels.
  8. Netzwerkzugriff und Festplattenzugriff sind zulässig, die zeitlichen Einschränkungen sind jedoch möglicherweise ziemlich unzulässig. Es wurden jedoch einige Auf- und Abbauverfahren hinzugefügt, um die Zeitbelastung zu verringern.
  9. Code sollte als Antwort auf dem Stapelüberlauf veröffentlicht oder, wenn er zu groß ist, verknüpft werden.
  10. Die maximale Gesamtgröße (nicht komprimiert) eines Eintrags beträgt 1 MB.
  11. Offiziell ist .Net 2.0 / 3.5 die einzige Framework-Anforderung.
  12. Ihr Eintrag muss die IBattleshipOpponent-Schnittstelle implementieren.

Wertung:

  1. Die besten 51 von 101 Spielen sind die Gewinner eines Spiels.
  2. Alle Teilnehmer spielen im Round-Robin-Stil gegeneinander.
  3. Die beste Hälfte der Teilnehmer spielt dann ein Doppelausscheidungsturnier, um den Gewinner zu ermitteln. (Kleinste Zweierpotenz, die tatsächlich größer oder gleich der Hälfte ist.)
  4. Ich werde das TournamentApi- Framework für das Turnier verwenden.
  5. Die Ergebnisse werden hier veröffentlicht.
  6. Wenn Sie mehr als einen Eintrag einreichen, ist nur Ihr Eintrag mit der besten Punktzahl für das Double-Elim berechtigt.

Viel Glück! Habe Spaß!


EDIT 1:
Danke an Freed , der einen Fehler in der Ship.IsValidFunktion gefunden hat. Es wurde behoben. Bitte laden Sie die aktualisierte Version des Frameworks herunter.

BEARBEITEN 2:
Da großes Interesse daran besteht, Statistiken auf der Festplatte und dergleichen beizubehalten, habe ich einige nicht zeitgesteuerte Einrichtungs- und Abbauereignisse hinzugefügt, die die erforderliche Funktionalität bereitstellen sollten. Dies ist eine bahnbrechende Veränderung . Das heißt: Die Schnittstelle wurde geändert, um Funktionen hinzuzufügen, für die jedoch kein Body erforderlich ist. Bitte laden Sie die aktualisierte Version des Frameworks herunter.

EDIT 3:
Bug Fix 1: GameWonund GameLostwurden nur im Falle einer Auszeit aufgerufen.
Fehlerbehebung 2: Wenn eine Engine jedes Spiel auslaufen lassen würde, würde der Wettbewerb niemals enden.
Bitte laden Sie die aktualisierte Version des Frameworks herunter.

EDIT 4:
Turnierergebnisse:


Wenn der Eintrag eine große Datenbank erfordert, kann er über das Netz eine Verbindung herstellen? Dh. Kann der Eintrag Webdienstanrufe tätigen?
Remus Rusanu

Gibt es eine Größenbeschränkung für die Einträge?
Jherico

8
@Steven: Außerdem habe ich Jeff Atwood konsultiert, um zu sehen, ob es angemessen ist. Hier ist seine Antwort: twitter.com/codinghorror/status/5203185621
John Gietzen

1
Außerdem würde ich taht hinzufügen, da die unvermeidliche zufällige Komponente dieser 50 Spiele nicht ausreicht, um genau zwischen sehr guten Implementierungen zu unterscheiden. Ich würde denken, dass 501 oder mehr für eine vernünftige Ansicht notwendig sein könnten, die besser ist.
ShuggyCoUk

1
Ein "friedlicher" Gegner, der sich weigert, Schiffe zu platzieren, lässt die Konkurrenz hängen. Ich bin mir nicht sicher, wie sehr Sie sich für Leute interessieren, die solche dummen Dinge tun. :)
Joe

Antworten:


56

Ich stimme dem Antrag zu, viel mehr Spiele pro Spiel zu machen. 50 Spiele zu machen bedeutet nur, eine Münze zu werfen. Ich musste 1000 Spiele machen, um eine vernünftige Unterscheidung zwischen Testalgorithmen zu erhalten.

Laden Sie Dreadnought 1.2 herunter .

Strategien:

  • Behalten Sie alle möglichen Positionen für Schiffe mit> 0 Treffern im Auge. Die Liste wird nie größer als ~ 30K, so dass sie genau gehalten werden kann, im Gegensatz zur Liste aller möglichen Positionen für alle Schiffe (die sehr groß ist).

  • Der GetShot-Algorithmus besteht aus zwei Teilen, von denen einer zufällige Schüsse erzeugt und der andere versucht, ein bereits getroffenes Schiff zu versenken. Wir machen zufällige Schüsse, wenn es eine mögliche Position (aus der obigen Liste) gibt, an der alle getroffenen Schiffe versenkt sind. Andernfalls versuchen wir, ein Schiff zu versenken, indem wir einen Ort zum Schießen auswählen, an dem die größtmöglichen Positionen (gewichtet) eliminiert werden.

  • Berechnen Sie für zufällige Aufnahmen den besten Drehort basierend auf der Wahrscheinlichkeit, dass eines der nicht versenkten Schiffe den Standort überlappt.

  • adaptiver Algorithmus, der Schiffe an Orten platziert, an denen der Gegner statistisch gesehen weniger wahrscheinlich schießt.

  • adaptiver Algorithmus, der lieber an Orten schießt, an denen der Gegner statistisch gesehen eher seine Schiffe platziert.

  • Platziere Schiffe, die sich meistens nicht berühren.


Auf meiner Testmaschine (einem ULV Celeron Netbook) verliert dieser Code durch Timeout konsistent. Wenn ich es die ganze Zeit dauern lasse, peitscht es einfach (ungefähr 90% Erfolgsquote). Wenn Sie sich stark auf die Spezifikation der Maschine verlassen, auf der Sie laufen werden, um Ihre Zeitlimits zu erreichen, möchten Sie sich vielleicht etwas Spielraum geben ...
ShuggyCoUk

Interessant ... Es läuft gut auf dem Turnierautomaten. Ein "perfekter" Motor würde sich jedoch an die Zeit anpassen, die er bereits verbracht hatte.
John Gietzen

35

Hier ist mein Eintrag! (Die naivste mögliche Lösung)

"Random 1.1"

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

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

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

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

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

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

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

52
Eigentlich ist diese Antwort nett, weil sie in einer sehr präzisen Form die APIs zeigt, die Sie implementieren müssen, um am Wettbewerb
teilzunehmen

1
Als ich in meiner College-Algorithmusklasse ein ähnliches Projekt erstellte, verwendete ich zufällige Logik, die mit einigen Entscheidungen verknüpft war. Es war manchmal gut!
Nathan Taylor

2
Dies könnte versuchen, überlappende Schiffe zu platzieren, nicht wahr?

6
Ja, aber der Motor wird dies nicht zulassen. Es wird dann die KI anweisen, sie erneut zu platzieren, diesmal jedoch mit einer strengeren Stimme. (Gesehen von pop ax \ cmp ax, 1 \ je stern)
John Gietzen

5
Wichtiger Hinweis für jeden, der wie ich dachte, er könnte dies leicht übertreffen, indem er sich an die zuvor platzierten Schüsse erinnert und sie nicht wiederholt. Das Framework ignoriert Wiederholungen und gibt Ihnen eine weitere Chance, solange Ihre Gesamtzeit unter dem Limit liegt. Dies ist meiner Meinung nach schlecht, wenn jemand sein Algo
durcheinander bringt,

22

Hier ist ein Gegner, gegen den die Leute spielen können:

Anstatt eine Strategie mit fester Geometrie zu verwenden, hielt ich es für interessant, die zugrunde liegenden Wahrscheinlichkeiten abzuschätzen , mit denen ein bestimmter unerforschter Raum ein Schiff enthält.

Um dies richtig zu machen, würden Sie alle möglichen Konfigurationen von Schiffen untersuchen, die zu Ihrer aktuellen Sicht der Welt passen, und dann Wahrscheinlichkeiten basierend auf diesen Konfigurationen berechnen. Man könnte sich vorstellen, einen Baum zu erkunden:

eine Erweiterung möglicher Schlachtschiffstaaten http://natekohl.net/media/battleship-tree.png

Nachdem Sie alle Blätter dieses Baumes betrachtet haben, die mit dem, was Sie über die Welt wissen, in Einklang stehen (z. B. Schiffe können sich nicht überlappen, alle Trefferfelder müssen Schiffe sein usw.) , können Sie zählen, wie oft Schiffe an jeder unerforschten Position auftreten, um die Wahrscheinlichkeit abzuschätzen, dass Dort sitzt ein Schiff.

Dies kann als Heatmap dargestellt werden, in der Hot Spots eher Schiffe enthalten:

eine Heatmap der Wahrscheinlichkeiten für jede unerforschte Position http://natekohl.net/media/battleship-probs.png

Eine Sache, die ich an diesem Schlachtschiff-Wettbewerb mag, ist, dass der Baum oben fast klein genug ist, um diese Art von Algorithmus brutal zu erzwingen. Wenn es für jedes der 5 Schiffe ~ 150 mögliche Positionen gibt, sind das 150 5 = 75 Milliarden Möglichkeiten. Und diese Zahl wird nur kleiner, besonders wenn Sie ganze Schiffe eliminieren können.

Der Gegner, mit dem ich oben verlinkt habe, erforscht nicht den ganzen Baum. 75 Milliarden sind immer noch zu groß, um in weniger als einer Sekunde hineinzukommen. Es wird jedoch versucht, diese Wahrscheinlichkeiten mithilfe einiger Heuristiken abzuschätzen.


Bisher schlagen Sie unsere einzige andere vollständige Lösung um etwa 67,7% auf 32,3% :)
John Gietzen

2
Ich bin auf jeden Fall gespannt, wie sich ein "Wahrscheinlichkeitsansatz" mit einem "geometrischen Ansatz" vergleichen lässt. Ich habe festgestellt, dass dieser Wahrscheinlichkeitsgegner tatsächlich Bewegungen ausführt, die den in anderen Antworten diskutierten geometrischen Mustern folgen. Es könnte sein, dass die Verwendung von Geometrie genauso gut und viel schneller ist. :)
Nate Kohl

12

Keine vollwertige Antwort, aber es scheint wenig Sinn zu machen, die wirklichen Antworten mit dem üblichen Code zu überladen. Ich präsentiere daher einige Erweiterungen / allgemeine Klassen im Geiste von Open Source. Wenn Sie diese verwenden, ändern Sie bitte den Namespace oder der Versuch, alles in eine DLL zu kompilieren, funktioniert nicht.

Mit BoardView können Sie problemlos mit einem kommentierten Board arbeiten.

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

Einige Erweiterungen, einige davon duplizieren die Funktionalität im Hauptframework, sollten aber wirklich von Ihnen durchgeführt werden.

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

Etwas, das ich am Ende oft benutze.

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

Randomisierung. Sicher, aber testbar, nützlich zum Testen.

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

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

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

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

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

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

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

10

Ich habe momentan nicht die Zeit, einen vollwertigen Algorithmus zu schreiben, aber hier ist ein Gedanke: Wenn Ihr Gegner Schiffe zufällig platziert, wären die Platzierungswahrscheinlichkeiten dann nicht eine einfache Verteilung, die auf (5.5.5.5) zentriert ist? Zum Beispiel sind die Platzierungsmöglichkeiten für das Schlachtschiff (5 Einheiten lang) in der x-Dimension hier:

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

Die gleichen Berechnungen wären für y gültig. Die anderen Schiffe hätten nicht so viele Verteilungen, aber Ihre beste Vermutung ist immer noch das Zentrum. Danach würde der mathematische Ansatz langsam Diagonalen (möglicherweise mit der Länge eines durchschnittlichen Schiffes von 17/5) aus der Mitte ausstrahlen. Ex:

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

Natürlich müsste der Idee eine gewisse Zufälligkeit hinzugefügt werden, aber ich denke, dass dies rein mathematisch der richtige Weg ist.


Ja, in der Tat würden sie. Mein alter Motor hat das kompensiert.
John Gietzen

1
Woher ich komme, gilt das langsame Ausstrahlen von Diagonalen aus der Mitte als Betrug .
Bzlm

Wenn es als Betrug angesehen wird, gibt es eine ziemlich einfache Gegenmaßnahme. Vermeiden Sie (x, y) mit x = y. :)
ine

5
Ich denke, er spielte auf das Kartenzählen an? Was meiner Meinung nach nicht schummelt.
John Gietzen

10

Nichts so raffiniertes, aber hier ist, was ich mir ausgedacht habe. Es schlägt den zufälligen Gegner in 99,9% der Fälle. Würde mich interessieren, wenn jemand andere kleine Herausforderungen wie diese hat, es hat viel Spaß gemacht.

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

Leicht verdichtet, um hier nur wenig Platz zu beanspruchen und trotzdem lesbar zu sein.


6

Einige Kommentare zur Competition Engine:

NewGame-Parameter:

Wenn IBattleshipOpponent :: NewGame für die Einrichtung vor dem Spiel vorgesehen ist und eine Brettgröße hat, sollte es auch eine Liste der Schiffe und ihrer jeweiligen Größe enthalten. Es macht keinen Sinn, eine variable Boardgröße zuzulassen, ohne variable Schiffskonfigurationen zuzulassen.

Schiffe sind versiegelt:

Ich sehe keinen Grund, warum das Klassenschiff versiegelt ist. Unter anderem möchte ich, dass Schiffe einen Namen haben, damit ich Nachrichten wie ("Du hast meine {0} versenkt", ship.Name) ausgeben kann; . Ich habe auch andere Erweiterungen im Sinn, daher denke ich, dass Ship vererbbar sein sollte.

Zeitbegrenzungen:

Während das Zeitlimit von 1 Sekunde für eine Turnierregel sinnvoll ist, ist das Debuggen völlig durcheinander. BattleshipCompetition sollte eine einfache Einstellung haben, um Zeitverletzungen zu ignorieren und die Entwicklung / das Debuggen zu unterstützen. Ich würde auch vorschlagen, System.Diagnostics.Process :: UserProcessorTime / Privileged ProcessorTime / TotalProcessorTime zu untersuchen, um eine genauere Ansicht darüber zu erhalten, wie viel Zeit verwendet wird.

Versunkene Schiffe:

Die aktuelle API informiert Sie, wenn Sie das Schiff eines Gegners versenkt haben:

ShotHit(Point shot, bool sunk);

aber nicht welches Schiff du versenkt hast! Ich betrachte es als Teil der Regeln für das menschliche Schlachtschiff, dass Sie erklären müssen: "Sie haben mein Schlachtschiff versenkt!" (oder Zerstörer oder U-Boot usw.).

Dies ist besonders wichtig, wenn eine KI versucht, Schiffe auszuspülen, die gegeneinander stoßen. Ich möchte eine API-Änderung anfordern für:

ShotHit(Point shot, Ship ship);

Wenn das Schiff nicht null ist, bedeutet dies, dass der Schuss ein Sinkschuss war und Sie wissen, welches Schiff Sie versenkt haben und wie lange es gedauert hat. Wenn der Schuss ein nicht sinkender Schuss war, ist das Schiff null und Sie haben keine weiteren Informationen.


Bitte senden Sie Code-Beispiele, wenn Sie der Meinung sind, dass das Timing genauer durchgeführt werden kann. Ich möchte die Regeln jetzt nicht zu sehr ändern.
John Gietzen

Außerdem werden die Schiffsgrößen während der PlaceShips () übergeben, die genau einmal pro Spiel ausgeführt werden und auch als Einrichtungsphase verwendet werden können. Bitte zögern Sie nicht, das Schiff für Ihre eigenen Tests zu entsiegeln, aber ich plane, das versiegelte für das Turnier zu verwenden.
John Gietzen

BUG: @John Gietzen: Ich habe festgestellt, dass PlaceShips NICHT genau einmal pro Spiel ausgeführt wird (wie Sie angegeben haben). Wenn ein Spieler seine Schiffe falsch platziert (wie es die RandomOpponent häufig tut), wird PlaceShips wiederholt aufgerufen, ohne dass ein NewGame-Aufruf erfolgt.
Abelenky

5
Ich habe es immer als Strategie angesehen, zwei Schiffe in einer L-Konfiguration zu platzieren, damit mein Gegner glaubt, sie hätten ein Schlachtschiff versenkt, obwohl dies nicht der Fall war. Ich hatte nie den Eindruck, dass Sie angeben mussten, welches Boot versenkt wurde.
Josh Smeaton

3
@DJ: Ich halte mich an die ursprünglichen Regeln für Stift und Papier. Denken Sie daran, dass Hasbro eine Spielzeugfirma ist und dass dieses Spiel älter ist als Hasbro.
John Gietzen

5

CrossFire aktualisiert. Ich weiß, dass es nicht mit Farnsworth oder Dreadnought konkurrieren kann, aber es ist viel schneller als letzteres und einfach zu spielen, falls jemand es versuchen möchte. Dies hängt vom aktuellen Status meiner Bibliotheken ab, die hier enthalten sind, um die Verwendung zu vereinfachen.

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

Dies ist ungefähr das Beste, was ich in meiner Freizeit zusammenstellen konnte, was so gut wie nicht existiert. Es gibt einige Spiel- und Match-Zählstatistiken, während ich die Hauptfunktion so einrichte, dass die BattleshipCompetition wiederholt und kontinuierlich ausgeführt wird, bis ich eine Taste drücke.

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

Diese Logik kam Dreadnought am nächsten und gewann ungefähr 41% der einzelnen Spiele. (Es hat tatsächlich ein Match mit einer Anzahl von 52 bis 49 gewonnen.) Seltsamerweise ist diese Klasse gegen FarnsworthOpponent nicht so gut wie eine frühere Version, die viel weniger fortgeschritten war.


5

Mein Computer wird gerade von Dell repariert, aber hier war ich letzte Woche:

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

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

        public bool showBoard = false;

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

        List<Point> nextShots;
        Queue<Point> scanShots;

        char[,] board;

        private void printBoard()
        {
            Console.WriteLine();
            for (int y = 0; y < this.gameSize.Height; y++)
            {
                for (int x = 0; x < this.gameSize.Width; x++)
                {
                    Console.Write(this.board[x, y]);
                }
                Console.WriteLine();
            }
            Console.ReadKey();
        }

        public void NewGame(Size size, TimeSpan timeSpan)
        {
            this.gameSize = size;
            board = new char[size.Width, size.Height];
            this.nextShots = new List<Point>();
            this.scanShots = new Queue<Point>();
            fillScanShots();
            initializeBoard();
        }

        private void initializeBoard()
        {
            for (int y = 0; y < this.gameSize.Height; y++)
            {
                for (int x = 0; x < this.gameSize.Width; x++)
                {
                    this.board[x, y] = 'O';
                }
            }
        }

        private void fillScanShots()
        {
            int x, y;
            int num = gameSize.Width * gameSize.Height;
            for (int j = 0; j < 3; j++)
            {
                for (int i = j; i < num; i += 3)
                {
                    x = i % gameSize.Width;
                    y = i / gameSize.Height;
                    scanShots.Enqueue(new Point(x, y));
                }
            }
        }

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

        public Point GetShot()
        {
            if (showBoard) printBoard();
            Point shot;

            shot = findShotRun();
            if (shot.X != -1)
            {
                return shot;
            }

            if (this.nextShots.Count > 0)
            {
                shot = this.nextShots[0];
                this.nextShots.RemoveAt(0);
            }
            else
            {
                shot = this.scanShots.Dequeue();
            }

            return shot;
        }

        public void ShotHit(Point shot, bool sunk)
        {
            this.board[shot.X, shot.Y] = 'H';
            if (!sunk)
            {
                addToNextShots(new Point(shot.X - 1, shot.Y));
                addToNextShots(new Point(shot.X, shot.Y + 1));
                addToNextShots(new Point(shot.X + 1, shot.Y));
                addToNextShots(new Point(shot.X, shot.Y - 1));
            }
            else
            {
                this.nextShots.Clear();
            }
        }



        private Point findShotRun()
        {
            int run_forward_horizontal = 0;
            int run_backward_horizontal = 0;
            int run_forward_vertical = 0;
            int run_backward_vertical = 0;

            List<shotPossibilities> possible = new List<shotPossibilities>(5);

            // this only works if width = height for the board;
            for (int y = 0; y < this.gameSize.Height; y++)
            {
                for (int x = 0; x < this.gameSize.Width; x++)
                {
                    // forward horiz
                    if (this.board[x, y] == 'M')
                    {
                        run_forward_horizontal = 0;
                    }
                    else if (this.board[x, y] == 'O')
                    {
                        if (run_forward_horizontal >= 2)
                        {
                            possible.Add(
                                new shotPossibilities(
                                    run_forward_horizontal,
                                    new Point(x, y),
                                    true));
                        }
                        else
                        {
                            run_forward_horizontal = 0;
                        }
                    }
                    else
                    {
                        run_forward_horizontal++;
                    }

                    // forward vertical
                    if (this.board[y, x] == 'M')
                    {
                        run_forward_vertical = 0;
                    }
                    else if (this.board[y, x] == 'O')
                    {
                        if (run_forward_vertical >= 2)
                        {
                            possible.Add(
                                new shotPossibilities(
                                    run_forward_vertical,
                                    new Point(y, x),
                                    false));
                        }
                        else
                        {
                            run_forward_vertical = 0;
                        }
                    }
                    else
                    {
                        run_forward_vertical++;
                    }


                    // backward horiz
                    if (this.board[this.gameSize.Width - x - 1, y] == 'M')
                    {
                        run_backward_horizontal = 0;
                    }
                    else if (this.board[this.gameSize.Width - x - 1, y] == 'O')
                    {
                        if (run_backward_horizontal >= 2)
                        {
                            possible.Add(
                                new shotPossibilities(
                                    run_backward_horizontal,
                                    new Point(this.gameSize.Width - x - 1, y),
                                    true));
                        }
                        else
                        {
                            run_backward_horizontal = 0;
                        }
                    }
                    else
                    {
                        run_backward_horizontal++;
                    }


                    // backward vertical
                    if (this.board[y, this.gameSize.Height - x - 1] == 'M')
                    {
                        run_backward_vertical = 0;
                    }
                    else if (this.board[y, this.gameSize.Height - x - 1] == 'O')
                    {
                        if (run_backward_vertical >= 2)
                        {
                            possible.Add(
                                new shotPossibilities(
                                    run_backward_vertical,
                                    new Point(y, this.gameSize.Height - x - 1),
                                    false));
                        }
                        else
                        {
                            run_backward_vertical = 0;
                        }
                    }
                    else
                    {
                        run_backward_vertical++;
                    }

                }

                run_forward_horizontal = 0;
                run_backward_horizontal = 0;
                run_forward_vertical = 0;
                run_backward_vertical = 0;
            }
            Point shot;

            if (possible.Count > 0)
            {
                shotPossibilities shotp = possible.OrderByDescending(a => a.run).First();
                //this.nextShots.Clear();
                shot = shotp.shot;
                //if (shotp.isHorizontal)
                //{
                //    this.nextShots.RemoveAll(p => p.X != shot.X);
                //}
                //else
                //{
                //    this.nextShots.RemoveAll(p => p.Y != shot.Y);
                //}
            }
            else
            {
                shot = new Point(-1, -1);
            }

            return shot;
        }

        private void addToNextShots(Point p)
        {
            if (!this.nextShots.Contains(p) &&
                p.X >= 0 &&
                p.X < this.gameSize.Width &&
                p.Y >= 0 &&
                p.Y < this.gameSize.Height)
            {
                if (this.board[p.X, p.Y] == 'O')
                {
                    this.nextShots.Add(p);
                }
            }
        }

        public void GameWon()
        {
            this.GameWins++;
        }

        public void NewMatch(string opponent)
        {
            System.Threading.Thread.Sleep(5);
            this.rand = new Random(System.Environment.TickCount);
        }
        public void OpponentShot(Point shot) { }
        public void ShotMiss(Point shot)
        {
            this.board[shot.X, shot.Y] = 'M';
        }
        public void GameLost()
        {
            if (showBoard) Console.WriteLine("-----Game Over-----");
        }
        public void MatchOver() { }
    }


    public class OpponentExtended
    {
        public int GameWins { get; set; }
        public int MatchWins { get; set; }
        public OpponentExtended() { }
    }

    public class shotPossibilities
    {
        public shotPossibilities(int r, Point s, bool h)
        {
            this.run = r;
            this.shot = s;
            this.isHorizontal = h;
        }
        public int run { get; set; }
        public Point shot { get; set; }
        public bool isHorizontal { get; set; }
    }
}

2
Herzlichen Glückwunsch zum Silber. Haben Sie etwas dagegen, Ihren Algorithmus in Worten zu beschreiben? Es wäre interessant zu wissen.
Thomas Ahle

4

Wenn Sie Ihre Analyse brutal erzwingen, ist die Mechanik der mitgelieferten RandomOpponent möglicherweise äußerst ineffizient. Es erlaubt sich, bereits zielgerichtete Orte erneut auszuwählen, und lässt das Framework die Wiederholung erzwingen, bis es einen trifft, den es noch nicht berührt hat, oder das Zeitlimit pro Zug abläuft.

Dieser Gegner hat ein ähnliches Verhalten (die effektive Platzierungsverteilung ist dieselbe). Er führt lediglich die Überprüfung der geistigen Gesundheit selbst durch und verbraucht nur eine Zufallszahlengenerierung pro Anruf (amortisiert).

Dies verwendet die Klassen in meiner Erweiterung / Bibliotheksantwort und ich gebe nur die Schlüsselmethoden / den Schlüsselstatus an.

Shuffle wird hier von Jon Skeets Antwort gestrichen

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

Ich werde nicht teilnehmen können, aber hier ist der Algorithmus, den ich implementieren würde, wenn ich Zeit hätte:

Erstens, wenn ich einen Treffer entdecke, verfolge ich den Rest des Schiffes nicht sofort - ich erstelle eine Tabelle mit Schiffsstandorten und finde heraus, ob ich alle fünf mindestens einmal getroffen habe, bevor ich anfange, sie vollständig zu versenken. (Beachten Sie, dass dies eine schlechte Richtlinie für die Mehrfachschussvariante ist - siehe Kommentare)

  1. Treffen Sie die Mitte (siehe letzte Anmerkung unten - 'Mitte' ist nur eine Annehmlichkeit für die Beschreibung)
  2. Treffen Sie die Stelle 4 rechts von der Mitte
  3. Schlagen Sie die Stelle 1 nach unten und eine rechts von der Mitte
  4. Treffen Sie die Stelle vier rechts vom vorherigen Treffer
  5. Fahren Sie in diesem Muster fort (sollte mit diagonalen Linien enden, die durch 3 Leerzeichen getrennt sind, die das Brett füllen). Dies sollte alle Boote mit 4 und 5 Längen und eine statistisch große Anzahl von Booten mit 3 und 2 Längen treffen.

  6. Fangen Sie an, zufällig Punkte zwischen den Diagonalen zu treffen, um die Boote mit 2 und 3 Längen zu fangen, die noch nicht bemerkt wurden.

Sobald ich 5 Treffer festgestellt habe, würde ich feststellen, ob sich die 5 Treffer auf separaten Booten befinden. Dies ist relativ einfach, indem Sie einige weitere Schüsse in der Nähe von Orten machen, an denen sich zwei Treffer auf derselben horizontalen oder vertikalen Linie befinden und sich innerhalb von 5 Orten voneinander befinden (möglicherweise zwei Treffer auf demselben Boot). Wenn es sich um separate Boote handelt, versenken Sie weiterhin alle Schiffe. Wenn sich herausstellt, dass es sich um dasselbe Boot handelt, fahren Sie mit den obigen Füllmustern fort, bis alle 5 Boote gefunden sind.

Dieser Algorithmus ist ein einfacher Füllalgorithmus. Die Hauptmerkmale sind, dass es keine Zeit damit verschwendet, Schiffe zu versenken, von denen es weiß, wenn es noch Schiffe gibt, von denen es nichts weiß, und dass es kein ineffizientes Füllmuster verwendet (dh ein vollständig zufälliges Muster wäre verschwenderisch).

Schlussbemerkungen:

A) "Zentrum" ist ein zufälliger Startpunkt auf dem Brett. Dies beseitigt die Hauptschwäche dieses Algorithmus. B) Während die Beschreibung angibt, Diagonalen unmittelbar von Anfang an zu zeichnen, schießt der Algorithmus im Idealfall nur an "zufälligen" Stellen entlang dieser Diagonalen. Dies hilft zu verhindern, dass der Konkurrent zeitlich festlegt, wie lange es dauert, bis seine Schiffe von vorhersehbaren Mustern getroffen werden.

Dies beschreibt einen 'perfekten' Algorithmus dahingehend, dass alle Schiffe unter (9x9) / 2 + 10 Schüsse abgefeuert werden.

Es kann jedoch erheblich verbessert werden:

Sobald ein Schiff getroffen wurde, identifizieren Sie seine Größe, bevor Sie die 'internen' diagonalen Linien ausführen. Möglicherweise haben Sie die 2 Schiffe gefunden. In diesem Fall können die internen Diagonalen vereinfacht werden, um die 3 Schiffe schneller zu finden.

Identifizieren Sie Phasen im Spiel und handeln Sie entsprechend. Dieser Algorithmus kann bis zu einem bestimmten Punkt im Spiel gut sein, aber andere Algorithmen können als Teil des Endspiels bessere Vorteile bringen. Wenn der andere Spieler kurz davor steht, Sie zu besiegen, funktioniert möglicherweise ein anderer Algorithmus besser - beispielsweise schlägt ein Algorithmus mit hohem Risiko häufiger fehl, aber wenn er funktioniert, funktioniert er schnell und Sie können Ihren Gegner schlagen, der näher am Gewinnen ist als Sie .

Identifizieren Sie den Spielstil des Konkurrenten - es kann Ihnen Hinweise geben, wie er die Schiffsplatzierung plant (dh die Chancen stehen gut, dass sein eigener Algorithmus am schnellsten erkennt, wie er seine eigenen Schiffe platziert - wenn das einzige Werkzeug, das Sie haben, ein Hammer ist, alles sieht aus wie ein Nagel)

-Adam


Die Strategie, darauf zu warten, dass Schiffe versenkt werden, bis alle gefunden sind, hängt stark von der One-Shot-per-Turn-Variante ab. Bei der Version (Anzahl der überlebenden Schiffe) Schüsse pro Runde ist es vorteilhaft, Schiffe so schnell wie möglich zu versenken, um den Gegner zu verlangsamen.
Jason Owen

4

Mein Eintrag.

Nichts schrecklich Besonderes, und ich hatte keine Zeit, all die guten Ideen hinzuzufügen, die ich hatte.

Aber es scheint ziemlich gut zu spielen. Wir werden sehen, wie es im Wettbewerb läuft:

(Legen Sie dies in eine Datei Missouri.csund fügen Sie es dem Projekt hinzu.)

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

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

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

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

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

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

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

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

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

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

            shotBoard[shot] = Shot.Unresolved;

            return shot;
        }

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

            Board potential = new Board(size);

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

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

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

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

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

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

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

#endregion 

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

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

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

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

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

            Point p;

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

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

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

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

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

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

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

            return bors.ToArray();
        }

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

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

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

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

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

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

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

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

            return new Point(x,y);
        }

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

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

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

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

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

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

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

            return output;
        }

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

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

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

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

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

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

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

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

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

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

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

        public void ShotHit(Point p)
        {
        }

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

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

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

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

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

            return potential;
        }

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

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

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

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

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

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

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

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

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

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

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

Und jetzt, wo ich meinen Beitrag eingereicht habe, einige grobe Statistiken: vs. BP7 44% gewinnt. / vs. Dreadnought 20% gewinnt. / vs. Farnsworth 42% gewinnt. Es war ein lustiges Projekt.
Abelenky

2

Dies ist kein Minimax. Kann nicht jeder Spieler nach dem Platzieren der Schiffe alleine spielen, was zu einer Reihe von Runden führte, die er brauchte, um jedes gegnerische Schiff zu versenken? Derjenige, der weniger Runden brauchte, gewinnt.

Ich glaube nicht, dass es gute allgemeine Strategien gibt, außer getroffene Schiffe zu versenken und zu versuchen, die Anzahl der Schüsse zu minimieren, um die verbleibenden möglichen Orte abzudecken, an denen sich Schiffe verstecken könnten.

Natürlich könnte es Gegenstrategien für alles geben, was nicht zufällig ist. Aber ich denke nicht, dass es Strategien gibt, die gegen alle möglichen Spieler gut sind.


1
Möglicherweise könnten sie alleine spielen. So wird das nicht laufen. Tolle Idee. In diesem Wettbewerb möchte ich, dass es möglich ist, die Schüsse Ihres Gegners statistisch zu vermeiden.
John Gietzen

2
Aha. Wenn man Daten aus früheren Spielen gegen denselben Gegner verwendet, kann man sich möglicherweise an ihn anpassen?
Ziggystar

2

Eigentlich denke ich, dass das größte Problem mit dem Puzzle ist, dass es im Wesentlichen zwei Züge ist. Ein Zug platziert Ihre Schiffe, der andere findet die feindlichen Schiffe (wie segmentiert der zweite Teil auch sein mag, abgesehen davon, dass Sie versuchen, eine Uhr mit einem Zufallsfaktor zu schlagen, ist es einfach "Führen Sie Ihren Algorithmus aus"). Es gibt keinen Mechanismus, um zu versuchen, eine feindliche Strategie zu bestimmen und dann zu kontern, was ähnliche Wettbewerbe, die auf aufeinanderfolgenden Runden von "Steinpapierscheren" basieren, ziemlich interessant macht.

Ich denke auch, dass es cooler wäre, wenn Sie das Spiel als Netzwerkprotokoll angeben und dann das Framework für die Implementierung dieses Protokolls in C # bereitstellen würden, anstatt zu diktieren, dass alle Lösungen C # sein sollten, aber das ist nur meine Meinung.

EDIT: Ich hebe meinen Ausgangspunkt auf, da ich die Wettbewerbsregeln nicht sorgfältig genug gelesen habe.


Nicht alle Lösungen müssen in C # sein. Ich kann eine separate Assembly kompilieren und verknüpfen. Außerdem sollten Sie in der Lage sein, Ihren Gegner statistisch zu kontern.
John Gietzen

J #? könnte sein? Lol, jk. Ich habe ein TCP-Framework dafür, aber dieses Turnier muss sehr schnell laufen.
John Gietzen

Warum sollten Sie davon ausgehen, dass die TCP-Kommunikation zwischen zwei Prozessen auf demselben Computer nicht besonders schnell ist?
Jherico

@Jherico: Wenn ich TCP verwenden würde, würde ich die Engines auf ihren eigenen PCs isolieren, damit sie alle gewünschten CPU-Ressourcen verwenden können.
John Gietzen

Trotzdem könnten zwei Maschinen auf demselben LAN ein Spiel problemlos in
weniger als

2

Ich habe es immer gemocht, in der Mitte zu beginnen und mich von diesem einen Punkt wegzudrehen, wobei nicht mehr als ein Leerzeichen zwischen anderen Punkten übrig blieb, um dieses verdammte U-Boot zu erklären. Der Abstand zwischen den Schüssen war davon abhängig, welche Schiffe versenkt wurden. Wenn das B-Schiff das letzte war, mussten die Schüsse nur 4 Felder dazwischen lassen, um verschwendete Schüsse zu minimieren


1
Also ... muss ich mich nur von der Mitte fernhalten? :)
Darron

14
Sie müssen sich auch von den Kanten fernhalten, da ein Kantentreffer mehr Informationen für Ihren Gegner enthält als ein Nichtkantentreffer. Sie sollten also alle Ihre Schiffe in einer nicht mittleren, nicht kantigen Region platzieren. Es sei denn, sie erwarten , dass Sie das tun.
Jherico

1
Wenn Sie zunächst 3 oder 4 Felder verlassen, haben Sie möglicherweise das Glück, das U-Boot trotzdem zu treffen. Wenn nicht, gehen Sie zurück und versuchen Sie, die Lücken zu füllen. Mehr unter: somethinkodd.com/oddthinking/2009/10/29/battleship-strategy
Oddthinking

18
Das Schiff mit zwei Löchern ist kein verdammtes U- Boot , es ist ein verdammtes PT-Boot . Das U-Boot hat drei Löcher. :)
Rabe

2

Es gab einen ähnlichen Wettbewerb, der von Dr. James Heather von der University of Surrey im Auftrag der British Computer Society durchgeführt wurde.

Die Ressourcen wurden eingeschränkt - nämlich die maximale Prozessorzeit pro Runde, zwischen den Zügen konnte kein Status gespeichert werden, die maximale Heap-Größe wurde festgelegt. Um die Zeit zu begrenzen, könnte die KI zu jedem Zeitpunkt innerhalb des Zeitfensters einen Zug einreichen und würde nach Beendigung des Zuges um einen Zug gebeten.

Sehr interessant - siehe mehr unter: http://www.bcsstudentcontest.com/

Könnte Ihnen weitere Ideen geben.


2

So wie es ist, wird die Lösung geöffnet und ohne Änderung in Monodevelop unter Ubuntu 9.10 Linux ausgeführt


1

Sie schrieben:

  • Alles, was gegen den Geist des Wettbewerbs verstößt, wird disqualifiziert.
  • Sich in einen Gegner einzumischen ist gegen den Geist des Wettbewerbs.

Bitte definieren Sie "gegen den Geist des Wettbewerbs" und "Eingriffe in einen Gegner".

Außerdem - zur Vereinfachung empfehle ich Ihnen:

  • Während des CPU-Slots des Gegners darf die CPU überhaupt nicht verwendet werden.
  • Thread-Parallelität nicht zulassen und stattdessen mehr CPU-Sekunden für einen einzelnen Thread angeben. Dies vereinfacht die Programmierung der KI und verletzt ohnehin niemanden, der an CPU / Speicher gebunden ist.

PS - eine Frage für die hier lauernden CS-Post-Docs: Ist dieses Spiel nicht lösbar (dh gibt es eine einzige, beste Strategie?). Ja, die Größe des Boards und die Anzahl der Schritte machen Minimax et al. obligatorisch, aber ich muss mich trotzdem fragen ... es ist weit entfernt von Go und Schach in seiner Komplexität.


Ich dachte nach, als ich "Interfering" sagte. Ich möchte nicht, dass Konkurrenten gewinnen, weil sie einen anderen Motor zu Tode gedreht haben.
John Gietzen

8
Ich würde vorschlagen, dass Spionage ein wichtiger Bestandteil der modernen Kriegsführung ist, daher wäre es ideal, über das Finden der Ziele nachzudenken - schließlich war es eine der Methoden, die während des Zweiten Weltkriegs angewendet wurden ...
Rowland Shaw,

Ich habe ein Framework, um die Engines auf verschiedenen PCs zu isolieren, über TCP / IP zu kommunizieren und Reflection wertlos zu machen. Aufgrund meiner geschätzten Anzahl von Einsendungen würde dies jedoch dazu führen, dass der Wettbewerb unerschwinglich lange dauert.
John Gietzen

6
Ich wusste damals nicht, dass sie Reflection hatten!
Markus Nigbur

1

Ich gehe davon aus, dass die Person, die es schafft, das zufällige Seed- und Call-Muster ihres Gegners rückzuentwickeln, gewinnen wird.

Ich bin mir nicht sicher, wie wahrscheinlich das ist.


Die Gegner haben die Möglichkeit, ein CSPRNG zu verwenden.
John Gietzen

Guter Punkt, obwohl ich zugebe, dass das Reverse Engineering eines solchen Samens sowieso über mein Fachwissen hinausgeht. Ich denke, der verwundbarste Aspekt wäre der Algorithmus zur Auswahl des Feuermusters - aber selbst dann würden Sie nicht unbedingt viel davon profitieren, wenn Sie ihn brechen, da Sie Ihre Schiffe nach dem Start des Spiels nicht mehr bewegen können.
Triston Attridge

Als ich mich für ein Forschungspraktikum bewarb, haben wir Schlachtschiffprogramme geschrieben und uns beworben. Durch das Setzen von zufälligen Samen war genau, wie ich X)
P Shved

1
Unter der Annahme eines relativ einfachen Schiffsplatzierungsalgorithmus würde ich mir vorstellen, dass man nach ein paar Treffern auf verschiedenen Schiffen den größten Teil seines Zuges damit verbringen könnte, alle möglichen zufälligen Samen zu durchlaufen (wahrscheinlich beginnend irgendwo in der Nähe der aktuellen Zeit und vorwärts zu gehen /) einen Schritt zurück oder so) und zu sehen, welche Schiffsplatzierungen erzeugen, die mit beobachteten Treffern kompatibel sind.
Domenic

1

Vermutlich wäre es auch möglich, eine Reihe davon mit Variationen des Spiels auszuführen.

Das Hinzufügen von Dingen wie einem 3D-Flugzeug oder die Möglichkeit, ein einzelnes Schiff zu bewegen, anstatt für eine Runde zu schießen, würde das Spiel wahrscheinlich ein gutes Stück verändern.


2
Es gibt die "Salve" -Variante. Wo Sie so viele Schüsse pro Runde schießen können, wie noch Schiffe übrig sind.
John Gietzen

Eine interessante Variante. Ich erinnere mich an eine Computerversion, die auch ein Flugzeug hatte. Es würde zufällig an Stellen auf dem gegnerischen Brett feuern.
Glenn

eine andere Variation: sei die Größe des Brettes + Anzahl der Schiffe.
Russau

1

Die Gesamtspielzeit von einer Sekunde ist maschinenspezifisch. Der CPU-Betrieb im Wert von einer Sekunde ist auf meinem Computer anders als auf dem Turniercomputer. Wenn ich den Battle Ship-Algorithmus so optimiere, dass innerhalb von 1 Sekunde die meiste CPU-Zeit verbraucht wird, wird er auf einem möglicherweise langsameren Turniercomputer ausgeführt und verliert immer.

Ich bin nicht sicher, wie ich diese Einschränkung des Frameworks umgehen soll, aber es sollte angegangen werden.

...

Eine Idee ist, das zu tun, was in diesem Wettbewerb getan wurde. Http://www.bcsstudentcontest.com /

Und haben Sie eine maximale Zeit pro Spielzug im Gegensatz zur maximalen Gesamtspielzeit. Auf diese Weise konnte ich die Algorithmen so einschränken, dass sie in eine bekannte Bearbeitungszeit passen. Ein Spiel kann 50 bis 600 Runden dauern. Wenn der my-Algorithmus seine gesamte Spielzeit verwaltet, gibt er möglicherweise nicht genügend Zeit, um seinen besten Job zu erledigen, oder er gibt zu viel Zeit und verliert. Es ist sehr schwierig, die gesamte Spielzeit innerhalb des Schlachtschiff-Algorithmus zu verwalten.

Ich würde vorschlagen, die Regeln zu ändern, um die Zugzeit und nicht die Gesamtspielzeit zu begrenzen.

Bearbeiten

Wenn ich einen Algorithmus geschrieben habe, der alle möglichen Aufnahmen auflistet und sie dann ordnet, dann mache ich die höchste Aufnahme. Es würde zu lange dauern, um alle möglichen Aufnahmen zu generieren, also würde ich den Algorithmus für eine bestimmte Zeit laufen lassen und ihn dann stoppen.

Wenn es ein rundenbasiertes Limit gäbe, könnte ich den Algorithmus 0,9 Sekunden lang laufen lassen und den Schuss mit dem höchsten Rang zurückgeben und das Rundenzeitlimit gut einhalten.

Wenn ich auf eine Gesamtspielzeit von einer Sekunde beschränkt bin, ist es schwierig zu bestimmen, wie lange der Algorithmus für jede Runde ausgeführt werden soll. Ich möchte meine CPU-Zeit maximieren. Wenn ein Spiel 500 Runden dauerte, konnte ich jede Runde auf 0,002 Sekunden beschränken, aber wenn ein Spiel 100 Runden dauerte, konnte ich jeder Runde 0,01 Sekunden CPU-Zeit geben.

Es wäre für einen Algorithmus unpraktisch, eine halb erschöpfende Suche im Schussraum zu verwenden, um den besten Schuss mit der aktuellen Begrenzung zu finden.

Die Gesamtspielzeit von 1 Sekunde begrenzt die Art der Algorithmen, die effektiv verwendet werden können, um im Spiel zu konkurrieren.


Dies wird auf einem Intel Q9550SX Quad Core, 8 GB RAM, Vista 64 Computer ausgeführt. Wird 1 Sekunde ein begrenzender Faktor sein?
John Gietzen

Ich denke, Sie sollten Ihre Schlachtschiff-KI multithreaded machen, um die maximale Anzahl von Schüssen pro Zeitintervall zu berechnen.
Jeff Atwood

Der Trick besteht darin, das Drehzeitintervall zu begrenzen. Wenn ich es auf 0,00005 Sekunden beschränke, kann ich das Zeitlimit nicht überschreiten, aber ich schränke den Suchraum erheblich ein. Wenn ich das Zeitlimit für die Drehung erhöhe, wird der Suchraum vergrößert, aber ich riskiere, dass mir die Zeit ausgeht.
TonyAbell

@TonyAbell: Wenn es wichtig ist, ein rundenbasiertes Zeitlimit zu haben, warum nicht mit einem Anfangswert beginnen und ihn dann von Runde zu Runde anpassen? Nach ungefähr der Hälfte der Runden haben Sie höchstwahrscheinlich die optimale Zuglänge für den Gegner gefunden, dem Sie gegenüberstehen.
Kyokley

Sie sollten Ihre verbleibende Zeit im Auge behalten und sie auf die Hälfte der verfügbaren verbleibenden Zeit beschränken.
John Gietzen

1

Ich werde hier aufhören, indem ich keinen tatsächlichen Code eingebe - aber ich werde einige allgemeine Beobachtungen riskieren:

  • Da alle Schiffe mindestens 2 Zellen groß sind, können Sie eine Optimierung verwenden, die ich bei einer Implementierung des Spiels in Space Quest V gesehen habe - die nur auf alternative Zellen in einem Rautenmuster feuert, während sie ein Ziel "sucht". Dies eliminiert die Hälfte der Quadrate und garantiert gleichzeitig, dass Sie schließlich alle Schiffe finden.
  • Ein zufälliges Schussmuster bei der Suche nach Zielen liefert statistisch die besten Ergebnisse über viele Spiele.

1

! [Wahrscheinlichkeitsdichte] [1] Bildbeschreibung eingeben

! [Bildbeschreibung hier eingeben] [2]

Ich experimentierte mit dem Vergleich der Ergebnisse des Randon-Schießens mit einer dummen Jagd / einem dummen Ziel und schließlich mit einer ausgeklügelten Suche.

Die beste Lösung scheint darin zu bestehen, eine Wahrscheinlichkeitsdichtefunktion für die Wahrscheinlichkeit zu erstellen, mit der ein einzelnes Quadrat von den verbleibenden Schiffen verwendet wird, und mit dem Quadrat mit dem höchsten Wert zu zielen.

Sie können meine Ergebnisse hier sehen. Geben Sie hier die Linkbeschreibung ein


Könnten Sie vielleicht Ihre Antwort und insbesondere Ihre Bilder und Links korrigieren?
Bart

-2

"Schlachtschiff" ist ein sogenanntes klassisches NP-vollständiges Problem der Informatik.

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

(Suche nach Schlachtschiff - es ist da, unter Spielen und Rätseln)


4
Welches ist ein Schlachtschiff-Puzzle ( en.wikipedia.org/wiki/Battleship_(puzzle) ), nicht Schlachtschiff das Spiel ( en.wikipedia.org/wiki/Battleship_(game) ).
Jason Berkan

Ja, wie Jason sagte, ist dies ein ganz anderes Tier.
John Gietzen

3
Hehehe. Nächste Aufgabe, die ich bei der Arbeit bekomme Ich werde sagen, dass sie NP-abgeschlossen ist, und dann ein langes Mittagessen einnehmen. :-)
Bork Blatt
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.