Herzlich Willkommen, lieber Gast!
  Sie befinden sich hier:

  Forum » C / C++ (ANSI-Standard) » Schiffe platzieren (NIVEAU:Anfänger/KOMPLEXITÄT:Mittel)

Forum | Hilfe | Team | Links | Impressum | > Suche < | Mitglieder | Registrieren | Einloggen
  Quicklinks: MSDN-Online || STL || clib Reference Grundlagen || Literatur || E-Books || Zubehör || > F.A.Q. < || Downloads   

Autor Thread - Seiten: > 1 < [ 2 ]
000
01.09.2004, 17:37 Uhr
GateKeeper



Hallo.

Meine Frage betrifft das Spiel "Schiffe versenken": Ich habe eine Funktion geschrieben, die für ein beliebig großes Spielfeld alle Schiffe zufällig verteilt. Diese Funktion arbeitet zu langsam.

ausführliche Problembeschreibung:

Wer das Spiel nicht kennt: Es gibt ein quadratisches Spielfeld mit 16x16 Feldern. Darauf müssen zu Beginn des Spiels 11 verschiedene Schiffe mit unterschiedlichen Längen platziert werden. Normalerweise gibt es Schiffslängen von 2 bis 5 Feldern bei einer konstanten Schiffbreite von einem Feld. (4x2F,4x3F,2x4F,1x5F)

Hier die Definition meiner Funktion:

C++:
void SchiffeVerteilen(Schiff* Shiplist, long AnzSchiffe, long Spielfeldgroesse) {
   // Zufallsgenerator initialisieren
   srand(time(0)) ;
    
   for(int i = 0 ; i < AnzSchiffe ; i++) {

      // für jedes Schiff prüfen, ob das Spielfeld oder die Schiffslänge zu klein ist:
      if ((0 < (Shiplist[i].Laenge)) && ((Shiplist[i].Laenge-1) <= Spielfeldgroesse)) {
            
         // solange versuchen, das Schiff zu platzieren, bis es keine Kollision mehr gibt.
         for(int PlatzierungsVersuche = 0 ; (!Shiplist[i].gesetzt) ; PlatzierungsVersuche++ ) {
                
            // Startposition zufällig festlegen:
            Shiplist[i].X = lRnd(0,Spielfeldgroesse - (Shiplist[i].Laenge - 1)) ;
            Shiplist[i].Y = lRnd(0,Spielfeldgroesse - (Shiplist[i].Laenge - 1)) ;
                
            // Ausrichtung (horiz/vert) zufällig festlegen:
            Shiplist[i].deltaX = 0 ; Shiplist[i].deltaY = 0 ;
            if (lRnd(0,2) == 1) Shiplist[i].deltaX = Shiplist[i].Laenge ;
            else Shiplist[i].deltaY = Shiplist[i].Laenge ;

            // Prüfen, ob das Schiff mit dem Rand des Spielfeldes oder einem Schiff kollidiert:
            if ((!SchiffRandKollision(&Shiplist[i],Spielfeldgroesse))
               && (!SchiffSchiffKollision(&Shiplist[i],Shiplist,AnzSchiffe,i))) {
               Shiplist[i].gesetzt = true ;
            }

         }        
      }
   }
}



Meine Funktion macht folgendes:
1. Sie wählt für jedes Schiff zufällig die Koordinaten eines Startpunktes aus
2. Sie wählt für jedes Schiff zufällig die Orientierung (horiz/vert) aus
3. Sie prüft, ob eine Kollision mit dem Rand des Spielfeldes oder einem anderen Schiff vorliegt
4. Wenn keine Kollision vorliegt, wird das Schiff als gesetzt markiert und das nächste Schiff wird platziert.

Die Funktion arbeitet für das 16x16-Felder-Spielfeld in der Regel ohne allzu viele Neuplatzierungsversuche - schnell genug also.

Wenn ich die Spielfeldgroesse aber verringere (um z.B. mit einem 6x6 Felder großen Brett zu spielen) dann kommt es in der Regel zur Endlosschleife. Wahrscheinlich weil nach einigen Fehlsetzungen der Schiffe keine weitere Platzierung mehr möglich ist. Ich habe die Funktion schon dahingehend verändert, dass bei in Relation zur Spielfeldgroesse gesetzten zu hohen Anzahl von Neuplatzierungen alle Schiffe wieder neu gesetzt werden, doch das Problem bestand weiterhin:

Bei kleinen Spielfeldern bleibt die Platzierung ein "Ratespiel" - manchmal muss man ewig vor dem Bildschirm warten, bis eine Lösung gefunden wird.

deshalb meine Frage:

Gibt es irgendeine (mathematische) Lösung um die Platzierungsfrage systematischer anzugehen, sie sozusagen nicht "Schiff-für-Schiff" sondern ganzheitlicher zu lösen - dann lassen sich vielleicht auch irgendwie die aufwändigen Kollisionsfeststellungen vermeiden ?
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
001
01.09.2004, 18:07 Uhr
Windalf
Der wo fast so viele Posts wie FloSoft...
(Operator)


hmm spontan fällt mir ein du könntest so initialisieren das du für jedes schiff eines types erstmal alle möglichen positionen in eine liste packst... jedesmal wenn du ein schiff gesetzt hast killst du aus jeder dieser listen die positionen die nciht mehr möglich sind...

auswürfeln tust du dann quasi welche dieser positionen aus der liste verwendet wird...

als strategie zum setzen auf jeden fall von gross nach klein, weil wenn man die kleinen schiffe zuerst setzt werden die grossen am ende wohl öfter nicht mehr passen...
--
...fleißig wie zwei Weißbrote

Dieser Post wurde am 01.09.2004 um 18:08 Uhr von Windalf editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
002
01.09.2004, 20:51 Uhr
FloSoft
Medialer Over-Flow
(Administrator)


und wenn man dann plötzlich keine möglichen setzungen mehr hat soll er z.b noch 2x probieren, wenn dann nicht, ist das feld einfach zu klein
--
class God : public ChuckNorris { };
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
003
02.09.2004, 10:05 Uhr
stephanw
localhorst


Wenn Du mit 16x16 und den zugehörigen Schiffen gut klarkommst, dann musst Du doch passend zur Verkleinerung des Spielfelds auch die Schiffe und deren Anzahl verkleinern. Auf einem 6x6 Feld wird es nun mal sehr schwer, 11 Schiffe der Länge 5 unterzubringen
--
Reden ist Schweigen und Silber ist Gold.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
004
02.09.2004, 10:15 Uhr
(un)wissender
Niveauwart


Wie wahr, wie wahr.
--
Wer früher stirbt ist länger tot.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
005
02.09.2004, 11:14 Uhr
GateKeeper



@ stephanw, (un)wissender:

Momentan verwende ich den Standardschiffsatz (wie im ersten post aufgeführt 4x2F,4x3F,2x4F,1x5F).

Der besteht aus 4 Schiffen mit 2 Feldern Länge, 4 mit 3 F. Länge, 2 mit 4 F. Länge und 1 mit 5 F. Länge.

Auf einem 6x6 Feld ist die Platzierung dieser Schiffe gerade noch möglich. Meine Funktion hat das auch schon hinbekommen - allerdings äußerst selten.

Ein 5x5 Feld ist allerdings zu klein um alle Schiffe zu setzen.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
006
02.09.2004, 11:38 Uhr
stephanw
localhorst


Ach so sind diese Zahlen zu deuten... na gut. Aber trotzdem: wenn diese die Standardbelegung für das große Feld ist, ist es doch logisch, dass es schwierig ist, diese Belegung auf einem kleinen Feld unterzubringen. Mir fällt auch keine naheliegende "klappt-immer" Lösung ein. Du könntest aber das zufällige Platzieren systematischer wählen. Das Problem ist ja mit dem "Rucksack-Problem" (engl. Knapsack) vergleichbar (--> Google), damit könntest Du ähnliche Lösungen entwickeln (Branch and Bound Verfahren).

Oder Du lässt Dir mit einigem Aufwand z.B. 10 oder 20 Lösungen berechnen und führst dann Variationen ein, indem Du diese horizontal oder/und vertikal spiegelst. Oder die 20 Basis-Lösungen per Hand eingeben. Weitere Variationen könnte man noch durch Vertauschen von Zeilen erreichen, die von keinem senkrechten Schiff überdeckt werden. Analog dazu Spalten-Vertauschungen.
--
Reden ist Schweigen und Silber ist Gold.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
007
02.09.2004, 12:43 Uhr
virtual
Sexiest Bit alive
(Operator)


Hm , also ich fand das ne ganz nette Aufgabe für die Mittagspause, allerdings kann ich nicht behaupten, auf die Probleme gelaufen zu sein, die GateKeeper nannte.

C++:
#include <stdio.h>
#include <stdlib.h>

#define BREITE 6
#define HOEHE 6
#define LAENGE 4

int spielfeld[BREITE][HOEHE];

int teste_konflikt(int x, int y, int laenge, int vertikal) {
    int i;
    for(i=0; i<laenge; ++i) {
        if (vertikal) {
            if (spielfeld[x][y+i]) return 1;
        }
        else {
            if (spielfeld[x+i][y]) return 1;
        }
    }
    return 0;
}

void setze_schiff(int x, int y, int laenge, int vertikal) {
    int i;
    for(i=0; i<laenge; ++i) {
        if (vertikal) {
            spielfeld[x][y+i] = 1;
        }
        else {
            spielfeld[x+i][y] = 1;
        }
    }
}

void spielfeld_initialisieren() {
    int laenge;
    for(laenge=LAENGE; laenge>0; --laenge) {
        int a;
        for(a=0; a<LAENGE+2-laenge; ++a) {
            int x;
            int y;
            int vertikal;
            do {
                vertikal = rand()%2;
                if (vertikal) {
                    x = rand()%BREITE;
                    y = rand()%(HOEHE+1-laenge);
                }
                else {
                    x = rand()%(BREITE+1-laenge);
                    y = rand()%HOEHE;
                }
            } while(teste_konflikt(x, y, laenge, vertikal));
            setze_schiff(x,y,laenge, vertikal);
        }
    }
}

void ausgeben() {
    int x,y;
    for(y=0; y<HOEHE; ++y) {
        for(x=0; x<BREITE; ++x) {
            putchar(spielfeld[x][y]? '#':' ');
        }
        puts("");
    }
}

int main() {
    srand(time(NULL));
    spielfeld_initialisieren();
    ausgeben();
}


Drei Anmerkungen
1. Ist wirklich nur auf das wesentliche Reduziert und nicht schön.
2. Ich fange mit den großen Schiffen an, weil es logiscerweise komplizierter ist, große Schiffe in ein Feld zu setzen, welches bereits kleine Schiffe enthält.
3. Man sollte beachten, daß ein Schiff nur in einem Teilbereich des Spielfeldes einen Anfangspunkt haben kann Ein Schiff mit der Länge L auf einen Spielfeld der Größe B*H kann nur folgende Anfangspunkte haben:
a) Bei Vertikaler ausrichtung: 0/0 .. B/H-L+1
b) Bei Horizontaler ausrichtung:0/0 .. B-L+1/H
Befolgt man dies, kann man einerseits die möglichen Anfangsposition grade für große Schiffe deutlich reduzieren; andererseits kann man sich im Programmcode die Abfrage sparen, ob ein Schiff aus dem Spielfeld herausragt.
--
Gruß, virtual
Quote of the Month
Ich eß' nur was ein Gesicht hat (Creme 21)

Dieser Post wurde am 02.09.2004 um 12:45 Uhr von virtual editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
008
02.09.2004, 22:35 Uhr
GateKeeper



@ virtual:

Ok. Die Lösung ist wirklich gut. Durch die Platzierungsstrategie, die auch Windalf schon vorgeschlagen hat, ist sogar erreicht, dass es zu keiner Situation kommt, in der ein Schiff neu platziert werden muss. Sprich, diese Lösung produziert wirklich immer ein brauchbares Ergebnis.

Eine Sache gefällt mir persönlich nicht: die Verwendung eines Arrays zur Spielfeldrepräsentation:

1.Man verliert damit doch die Möglichkeit eines unbegrenzten Spielfeldes.

2.Wenn man eine 3(bzw. mehr)-dimensionale Variante möchte, muss man sowohl die Schiffdefinition als auch die Spielfelddefinition anpassen. Bei meiner Variante genügt die Anpassung der Schiffsdefinition.

3.(s.1)Der Speicherbedarf nimmt enorm zu, wenn das Spielfeld vergrößert wird. (3D-Variante)Evtl. wird sehr viel Speicher für redundante Information (leere Spielfelder verschwendet).

Deine Variante ist wird allerdings meiner ursprünglichen Problemstellung wesentlich besser gerecht. Sie ist - denke ich - auch maschinennäher.

Danke sehr für diese ausführliche Lösung.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
009
02.09.2004, 23:30 Uhr
virtual
Sexiest Bit alive
(Operator)


@GateKeeper
Wie gedenkst Du denn letztlich das Spiel abzuspeichern? - Also wenn ich Schiffeversenken auf dem Papier spiele, dann kreuze ich in der Regel auch an, wo ich bereits hingetippt habe. Bei einem Durchschnittlichen Spiel haben dann 80-100% aller Spielfelder irgendeine Information (Schiff/Versenktes Schiff/Daneben geschossen). Von daher halte ich die Array variante für nicht ganz verkehrt, weil wenn Dein Rechner eine ähnliche Quote hat wie ein Mensch, dann ist ein Array grade am Ende des Spiels wahrscheinlich kompakter+platzsparender, als irgendwelche komplizierten Strukturen. Von der fehleranfälligen Programmierung mal ganz abgesehen. Keep it Simple.
--
Gruß, virtual
Quote of the Month
Ich eß' nur was ein Gesicht hat (Creme 21)
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
Seiten: > 1 < [ 2 ]     [ C / C++ (ANSI-Standard) ]  


ThWBoard 2.73 FloSoft-Edition
© by Paul Baecher & Felix Gonschorek (www.thwboard.de)

Anpassungen des Forums
© by Flo-Soft (www.flo-soft.de)

Sie sind Besucher: