Schiffe versenken
Zehn mal zehn Felder, fünf Schiffe, ein Gegner mit Verstand — wer zuerst die ganze Flotte trifft, gewinnt.
Zwei Spielfelder, zehn mal zehn Felder groß, auf jedem eine versteckte Flotte. Abwechselnd wird auf ein Feld geschossen — Treffer oder Wasser. Wer zuerst alle gegnerischen Schiffe versenkt hat, gewinnt.
Die Flotte besteht hier aus fünf Schiffen: einem Schlachtschiff mit fünf Feldern, einem Kreuzer mit vier, zwei Zerstörern mit je drei und einem U-Boot mit zwei — zusammen siebzehn Felder. Das ist die im englischsprachigen Raum übliche Aufstellung; die klassische deutsche Fassung kennt zehn kleinere Schiffe. Schiffe liegen waagerecht oder senkrecht und dürfen sich nicht berühren, auch nicht über Eck.
Oben seht ihr das gegnerische Feld, auf das ihr schießt; unten euer eigenes mit euren Schiffen und den Schüssen des Gegners. Mit „Flotte neu würfeln" ordnet ihr eure Schiffe vor Spielbeginn anders an.
Der Rechner schießt nicht blind. Solange er nichts getroffen hat, sucht er in einem Schachbrettmuster — weil das kleinste Schiff zwei Felder belegt, kann er so kein Schiff übersehen und braucht trotzdem nur die Hälfte der Schüsse. Hat er getroffen, geht er in den Jagdmodus und beschießt gezielt die Nachbarfelder, bis das Schiff versenkt ist. Das ist ungefähr die Strategie, die auch gute menschliche Spieler verwenden.
Verwandtes im Archiv: Minesweeper ist das andere große Spiel des logischen Suchens auf einem Raster, Mastermind verlangt ähnliches Schlussfolgern und Vier gewinnt ist der Klassiker mit offenen Karten.
Gegnerisches Feld — hier schießt ihr hin:
Euer Feld:
So funktioniert das Script
Zwei Aufgaben sind hier interessant: Schiffe so zu verteilen, dass sie sich nicht berühren, und einen Gegner zu bauen, der klüger schießt als der Zufall.
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
…
if (brett.belegt[stelle(nx, ny)] !== -1) return false;
}
}
Die Regel „Schiffe dürfen sich nicht berühren" wird geprüft, indem für jedes geplante Feld auch die acht Nachbarn angeschaut werden. Die doppelte Schleife von −1 bis +1 durchläuft genau diese neun Felder einschließlich des Feldes selbst — eine sehr gebräuchliche Kurzform, die man auch in Minesweeper zum Zählen der Minen ringsum findet.
let versuche = 0;
while (versuche < 2000) {
versuche++;
…
if (!passt(brett, x, y, schiff.laenge, waagerecht)) continue;
…
break;
}
Verteilt wird durch Ausprobieren: Ort und Richtung würfeln, prüfen, bei Nichtgefallen neu würfeln. Das ist unelegant und trotzdem die richtige Wahl — ein Verfahren, das gezielt gültige Plätze berechnet, wäre um ein Vielfaches komplizierter, und auf einem so leeren Brett ist meist schon der erste oder zweite Versuch erfolgreich. Der Zähler ist die Notbremse: Ohne ihn liefe die Schleife bei einem übervollen Brett endlos.
const muster = offen(eigen).filter(function (nummer) {
const x = nummer % GROESSE;
const y = Math.floor(nummer / GROESSE);
return (x + y) % 2 === 0;
});
Das ist der klügste Teil des Gegners. Statt zufällig irgendwohin zu schießen, beschießt er nur Felder, auf denen Zeile plus Spalte gerade ist — ein Schachbrettmuster. Der Gedanke: Jedes Schiff belegt mindestens zwei benachbarte Felder, und jedes Paar benachbarter Felder enthält immer genau ein schwarzes und ein weißes. Wer nur die schwarzen beschießt, findet also garantiert jedes Schiff — mit halb so vielen Schüssen wie beim flächendeckenden Absuchen.
nachbarn(ziel).forEach(function (n) {
if (!eigen.beschossen[n]) jagd.push(n);
});
Nach einem Treffer wechselt der Gegner in den Jagdmodus: Die vier Nachbarfelder kommen auf eine Liste offener Ziele, die vor jedem weiteren Zufallsschuss abgearbeitet wird. Weil pop das zuletzt hinzugefügte Feld zurückgibt, verfolgt er eine gefundene Spur zusammenhängend, statt zwischen mehreren Treffern hin- und herzuspringen. Ist das Schiff versenkt, wird die Liste geleert.
brett.belegt = new Array(GROESSE * GROESSE).fill(-1);
Das Brett merkt sich in einer flachen Liste, welche Schiffsnummer auf welchem Feld liegt; -1 steht für Wasser. Getrennt davon gibt es eine zweite Liste, welche Felder beschossen wurden. Diese Trennung von „was liegt dort" und „was weiß der Gegner" ist der Kern des Spiels — und der Grund, warum dasselbe Brett einmal verdeckt und einmal offen gezeichnet werden kann.
knopf.disabled = !verdeckt || vorbei || brett.beschossen[nummer];
Dieselbe Zeichenfunktion erzeugt beide Felder. Das eigene ist vollständig gesperrt, das gegnerische nur dort, wo schon geschossen wurde. So gibt es nur eine Stelle im Code, an der ein Spielfeld entsteht — und damit nur eine Stelle, an der ein Darstellungsfehler stecken kann.
Zum Anpassen: Für die klassische deutsche Flotte tauscht ihr die Liste am Anfang gegen ein Schlachtschiff, zwei Kreuzer, drei Zerstörer und vier U-Boote aus. Wollt ihr die Schiffe selbst platzieren statt zu würfeln, braucht ihr einen zweiten Spielzustand vor dem eigentlichen Spiel — die Prüffunktion passt lässt sich dafür unverändert weiterverwenden.
Script für die eigene Homepage
Kopiert euch den kompletten Code und fügt ihn an der Stelle eurer Seite ein, an der das Script erscheinen soll. Er läuft ohne weitere Dateien und ohne fremde Server.
Mehr aus der Kategorie Spiele
-
ASCII Minesweeper
-
Asteroids
-
Ausweichen
-
Ballspiel
-
Bennys Quiz
-
Blackjack
-
Blu Mania
-
BreakOut
-
Buchstaben zählen
-
Buchstabenpuzzle
-
Checkbox Spiel
-
Concentration
-
Der längste Drücker
-
Die Türme von Hanoi
-
Einmaleins Trainer
-
Escape
-
Galgenmännchen
-
Galgenmännchen 2
-
Game Over Game
-
Glücks Klick
-
Glücksbuttons
-
Ich sehe was
-
Kniffel
-
Labyrinth
-
Liebesrechner
-
Lights Out
-
Lights Out 2
-
Mastermind
-
Minesweeper
-
Mühle