Schiffe versenken

Hallo,

ich versuche gerade Schiffe versenken zu programmieren. Bin dabei auf ein Problem gestoßen und zwar wie ich überprüfe ob ein schiff versunken ist oder nicht.

Nach jedem Klick auf ein 10x10 Spielfeld wird die untenstehend Methode ausgeführt und sollte dann überprüfen ob ein Schiff getroffen oder versunken ist.
Ich hab mir dazu natürlich etwas überlegt und zwar das ich die Positionen der Schiffe in ein Array speicher und dann miteinander vergleiche aber irgendwie steh ich an.

@Override
	public void fieldHit(BattleshipGUI.BattleshipCell p) {
		System.err.println("Hit at " + p);

		/*
		 * Check here if this shot hit a ship and if its sunk now. Set the
		 * correct attributes on p
		 */
		int counter = 0;
		if (ships[p.getXPos()][p.getYPos()] == true) {

			gui.getBattleshipCell(p.getXPos(), p.getYPos()).setHit(true);
			
			for(int k = 0; k < shipPositions.length; k++){
				for (int i = 0; i < shipPositions[k].length; i++) {
					for (int j = 0; j < shipPositions[k]**.length; j++) {
						if(gui.getBattleshipCell(i, j).isHit() == shipPositions[k]**[j] && gui.getBattleshipCell(i, j).isShip() == shipPositions[k]**[j]){
							gui.getBattleshipCell(i, j).setSunk(true);
						}
					}
				}
			}
		}

Ich hoffe man versteht was ich mein und kann mir helfen.

lg

ist gui.getBattleshipCell(p.getXPos(), p.getYPos()) was anderes als p selber, welches ja auch eine BattleshipGUI.BattleshipCell ist?

was ist shipPositions? ist das ein Array der Positionen speziell der k vorhandenen Schiffe oder ist das gesamte Spielfeld k x Array-Länge?

wie du sicher selber weißt musst du feststellen, ob alle Felder eines Schiffes getroffen wurden,
Wege gibt es verschiedene

mein Vorschlag:

  • definiere dir eine Klasse Ship mit einem Array von Cell,
  • jede Cell bekommt auch einen Verweis auf ein Ship, entweder null oder das Ship,
    bisher hast du vielleicht einen boolean, also weißt schon ungefähr wo welche Schiffe sind, was dabei zu setzen ist,
    isShip() prüft nun statt boolean ob Ship-Variable gesetzt ist
  • bei einem Treffer das Ship der Cell holen, alle Cell des Ship prüfen, nur wenn ALLE getroffen sind, dann gesunken,
    da musst du dir sicher noch geeigneten Schleifendurchlauf überlegen,
  • die Speicherung der sunk-Info, falls überhaupt zu speichern, besser in Ship als in Cell, Cell kann bei Bedarf bei ihrem Ship nachfragen

Nein ist nichts anderes.

shipPositions hat jedes schiff ein eigenes 2Dim array und ich wollte jetzt versuchen die schiffe zu vergleichen aber ohne Erfolg…

ship Klasse soll ich nicht erstellen wir haben 3 Methoden in unserer Klasse und die sollen wir ergänzen.

Das Schiffe platzieren alles kein Problem funktioniert einwandfrei. Aber zu überprüfen, ob die Schiffe versunken sind ohne eine neue Klasse zu erstellen …

das kleine Hauptproblem hier genauso:
du musst für jedes Shiff, für jedes k, das gesamte 2D-Unterarray prüfen:
erst wenn ALLE Felder getroffen sind, dann für ALLE auf gesunken setzen,
Einzelprüfung kommt dabei zwar vor, bringt für sich aber keine Aussage ob versunken


ist ein einfaches Problem, aber wichtig dass du es selber löst,
etwas einfaches Beispiel dazu:

definiere in ein Testprogramm ein int-Array mit Werten 3, 4, 5, 3
gib nach Prüfung eine einmalige Aussage mit System.out.println aus: sind alle Elemente < 5, ja oder nein?
und zwar mit einer Schleife festgestellt, kannst du das?
wenn du das schaffst dann bald auch die Prüfung der Schiffsposition

Sorry, aber ich muss an dieser stelle einfach mal Werbung für einen OO-Lösungsansatz machen:

Ich würde die GUI aus [JAPI]CheckBox[/JAPI]en aufbauen.

Dann hätte ich eine Klasse Schiff, die eine Liste von [JAPI]Checkbox[/JAPI]en übergeben bekommt.

Jede [JAPI]CheckBox[/JAPI] iteriert dann in ihrer [JAPI]Action[/JAPI] über alle Schiffe und ruft dort eine Methode auf (die könnte checkTreffer() heißen).
In dieser Methode prüft jedes Schiff, ob alle “seine” [JAPI]CkeckBox[/JAPI]en aktiviert sind. Wenn ja, dann gibt sie true zurück, sonst false.

Diese Methode könnte gleich noch die Farbe der aktivierten [JAPI]CheckBox[/JAPI]en ändern, so dass der Spieler sieht, ob er getroffen hat…

Wenn alle Schiffe true zurückmelden hat der Spieler gewonnen…

Vorteil wäre, dass die Laufzeit des Checks nur von der Anzahl der von Schiffen “belegten” Zellen abhängt. Das sind ja idR. weniger als 10% des Spielfeldes (was aber nur dann relevant wäre, wenn man auf ungewöhnlich großen Feldern spielen wollte…).

bye
TT

@ SlaterB ich hoffe du hast es so gemeint wie ich jetzt umgesetzt habe, ansonst fällt mir dazu nicht mehr viel ein.
Vielleicht bin ich auch schon Fehlerblind sitze an dem Problem schon 2 Tage :frowning:

Hab die Methode jetzt ein bisschen umgeschrieben aber es funktioniert immer noch nicht richtig.
Wenn ich manchmal an einer beliebigen Koordinate ein Schiff treffe werden alle Schiffe als gesunken markiert?

if (ships[p.getXPos()][p.getYPos()] == true) {

			gui.getBattleshipCell(p.getXPos(), p.getYPos()).setHit(true);
			
			for(int k = 0; k < shipPositions.length; k++){
				for (int i = 0; i < shipPositions[k].length; i++) {
					for (int j = 0; j < shipPositions[k]**.length; j++) {
						if(gui.getBattleshipCell(i, j).isShip() == true && gui.getBattleshipCell(i, j).isHit() == false){
							check = false;
							done = true;
							break;
						} else {
							positionk = k;
							check = true;
						}
					}
					if(done) {
						break;
					}
				}
				if(check) {
					
					for(int i = 0; i < shipPositions[positionk].length; i++) {
						for(int j = 0; j < shipPositions[positionk]**.length; j++){
							if(gui.getBattleshipCell(i, j).isShip()) {
								gui.getBattleshipCell(i, j).setSunk(true);
							}
						}
					}
					
				}
			}
		}

Kann hier noch jemand einen Fehler entdecken?

lg

warum hast du nicht zuerst das einfache Beispiel mit Array 3, 4, 5, 3 geübt?
ewige Mysterien falscher Herangehensweisen…,
trotz meiner exklusiver Hilfe, die dich kommerziell leicht 50 Euro kosten würde :wink:

nun gut, du hast zumindest check-Variable, quasi von alleine draufgekommen,
da bin ich zumindest ein wenig froh

der korrekte Weg ist:
in der k-Schleife zu Beginn der Prüfung jedes Schiffes einmalig check auf true setzen,
idealerweise die Variable dort auch erst definieren, muss nicht außerhalb bekannt sein,

dann die Felder des aktuellen Schiffes durchgehen, wenn irgendwo kein Treffer, dann check auf false

→ nach den beiden inneren Schleifen am Ende der aktuellen k-Bearbeitung sagt check aus ob gesunken oder nicht,
wenn gesunken, dann nochmal die Unterschleifen


positionk brauchst du nicht, die ganze Zeit wird an einem k gearbeitet, idealerweise Untermethode,
in deinen Fall wirst du sagen dass nicht erlaubt, bitte…

musst du eigentlich if(gui.getBattleshipCell(i, j).isShip() wirklich prüfen?
sind nicht die Unterelemente unter k alle genau die richtigen Felder?

wobei if(gui.getBattleshipCell(i, j) sicher generell falsch ist,
i und j sind doch immer bei 0 beginnend in den Unterschleifen,
was steht dagegen an shipPositions[k]** bzw. shipPositions[k]**[j] konkret für ein Wert im Array?
benutzt du bisher gar nicht

ist shipPositions[k]** immer ein Zweierarray,
mit shipPositions[k][0] die x-Koordinate, shipPositions[k][1] die y-Koordinate?
dazu noch genauer nachdenken,


Ausgaben mit System.out.println() helfen immer,
gib exakt aus was du machst, ein schönes Log:

  • untersuche nun Schiff k = …, Länge des Unterarrays = …
  • untersuche Punkt k= …, i = …, j= …, Wert = …, Zustand des Feldes = Treffer? ja/nein, check bisher = ja/ nein
  • fertig mit Punkten für k = …, check am Ende = ja/ nein
    usw.
    dann weißt du, was dein Programm macht, und eher auch warum

Hab es abgeändert jedoch versteh ich

positionk brauchst du nicht, die ganze Zeit wird an einem k gearbeitet, idealerweise Untermethode,
in deinen Fall wirst du sagen dass nicht erlaubt, bitte…

musst du eigentlich if(gui.getBattleshipCell(i, j).isShip() wirklich prüfen?
sind nicht die Unterelemente unter k alle genau die richtigen Felder?

wobei if(gui.getBattleshipCell(i, j) sicher generell falsch ist,
i und j sind doch immer bei 0 beginnend in den Unterschleifen,
was steht dagegen an shipPositions[k]** bzw. shipPositions[k]**[j] konkret für ein Wert im Array?
benutzt du bisher gar nicht

ist shipPositions[k]** immer ein Zweierarray,
mit shipPositions[k][0] die x-Koordinate, shipPositions[k][1] die y-Koordinate?
dazu noch genauer nachdenken,

nicht.

musst du eigentlich if(gui.getBattleshipCell(i, j).isShip() wirklich prüfen? Ja ich denke sonst weiß ich ja nicht ob es ein Schiffsteil ist?

wobei if(gui.getBattleshipCell(i, j) sicher generell falsch ist, Versteh ich nicht, es muss doch jede position in der matrix geprüft werden?

was steht dagegen an shipPositions[k]** bzw. shipPositions[k]**[j] konkret für ein Wert im Array? Weiß ich nicht aber k ist nur die referenz auf eine matrix in dem EIN Schiff gespeichert ist… kann auch sein das mein Hirn einfach nicht mehr mag haha

boolean done = false;
		
		if (ships[p.getXPos()][p.getYPos()] == true) {
			 
            gui.getBattleshipCell(p.getXPos(), p.getYPos()).setHit(true);
           
            for(int k = 0; k < shipPositions.length; k++){
            	boolean check = true;
                for (int i = 0; i < shipPositions[k].length; i++) {
                    for (int j = 0; j < shipPositions[k]**.length; j++) {
                        if(gui.getBattleshipCell(i, j).isShip() == true && gui.getBattleshipCell(i, j).isHit() == false){ // hier nicht mit i und j prüfen oder bei der unteren schleife?
                            check = false;
                            done = true;
                            break;
                        }
                    }
                    if(done) {
                        break;
                    }
                }
                if(check) {
                    for(int i = 0; i < shipPositions[k].length; i++) {
                        for(int j = 0; j < shipPositions[k]**.length; j++){
                            if(gui.getBattleshipCell(i, j).isShip()) {
                                gui.getBattleshipCell(i, j).setSunk(true);
                            }
                        }
                    }
                   
                }
            }
        }

Auch wenn’s keiner wissen will:

hier ist meine Lösung.
Fehlt nur noch 'ne Strategie zum zufälligen Verteilen der Schiffe…[SPOILER]```public class Schiffeversenken {
static class Schiff {
List felder = new ArrayList<>();
final String name;

public Schiff(String name) {
    this.name = name;
}

void belegtesFeld(JCheckBox feld) {
    felder.add(feld);
    markiereSchiffFürDemo(feld);
    feld.addActionListener(e -> {
        felder.remove(feld);
        markiereTreffer(feld);
        zeigeTrefferNachricht(feld);
    });
}

boolean istVersenkt() {
    return felder.isEmpty();
}

private void markiereSchiffFürDemo(JCheckBox feld) {
    feld.setBackground(Color.GREEN);
    feld.setForeground(Color.GREEN);
}

private void markiereTreffer(JCheckBox feld) {
    SwingUtilities.invokeLater(() -> {
    feld.setBackground(Color.RED);
    feld.setForeground(Color.RED);
    });
}

private void zeigeTrefferNachricht(JCheckBox feld) {
    StringBuilder trefferNachricht = new StringBuilder(String.format("%s getroffen", name));
    if (felder.isEmpty()) {
    trefferNachricht.append(" und VERSENKT");
    }
    trefferNachricht.append("!");
    JOptionPane.showMessageDialog(feld, new JLabel(trefferNachricht.toString()), "getroffen",
        JOptionPane.INFORMATION_MESSAGE);
}
}

@SuppressWarnings("serial")
static class SpielfeldAction extends AbstractAction {
private final List<Schiff> schiffe;

private SpielfeldAction(List<Schiff> schiffe) {
    this.schiffe = schiffe;
}

@Override
public void actionPerformed(ActionEvent e) {
    JCheckBox jCheckBox = (JCheckBox) e.getSource();
    jCheckBox.setEnabled(false);
    boolean istSpielGewonnen = true;
    for (Schiff schiff : schiffe) {
	istSpielGewonnen &= schiff.istVersenkt();
    }
    if (istSpielGewonnen) {
	JOptionPane.showMessageDialog(jCheckBox, "Du hast gewonnen!", "Spielende",
		JOptionPane.INFORMATION_MESSAGE);
    }
}
}

public static void main(String[] args) {
new Schiffeversenken().start();
}

private void erzeugeGui(JPanel jPanel) {
JFrame spielfeld = new JFrame("Schiffeversenken");
spielfeld.getContentPane().add(jPanel);
spielfeld.pack();
spielfeld.setVisible(true);
}

private Schiff erzeugeSchiffSenkrecht(String name, int startX, int startY, int feldZahl,
    JCheckBox[][] spielfeldModel) {
Schiff schiff = new Schiff(name);
for (int j = 0; j < feldZahl; j++) {
    schiff.belegtesFeld(spielfeldModel[startX][startY + j]);
}
return schiff;
}

private Schiff erzeugeSchiffWaagerecht(String name, int startX, int startY, int feldZahl,
    JCheckBox[][] spielfeldModel) {
Schiff schiff = new Schiff(name);
for (int j = 0; j < feldZahl; j++) {
    schiff.belegtesFeld(spielfeldModel[startX + j][startY]);
}
return schiff;
}

private JPanel erzeugeSpielfeld(final List<Schiff> schiffe, JCheckBox[][] spielfeldModel) {
JPanel jPanel = new JPanel(new GridLayout(0, spielfeldModel.length));
for (int i = 0; i < spielfeldModel.length; i++) {
    for (int j = 0; j < spielfeldModel.length; j++) {
	spielfeldModel**[j] = new JCheckBox(new SpielfeldAction(schiffe));
	spielfeldModel**[j].setHorizontalAlignment(SwingConstants.CENTER);
	jPanel.add(spielfeldModel**[j]);
    }
}
return jPanel;
}

private void start() {
final List<Schiff> schiffe = new ArrayList<>();
JCheckBox[][] spielfeldModel = new JCheckBox[10][10];
JPanel jPanel = erzeugeSpielfeld(schiffe, spielfeldModel);
verteileSchiffe(spielfeldModel, schiffe);

erzeugeGui(jPanel);
}

private void verteileSchiffe(JCheckBox[][] spielfeldModel, final List<Schiff> schiffe) {
schiffe.add(erzeugeSchiffSenkrecht("Zerstörer", 2, 2, 5, spielfeldModel));
schiffe.add(erzeugeSchiffSenkrecht("Fregatte 1", 4, 3, 4, spielfeldModel));
schiffe.add(erzeugeSchiffSenkrecht("Fregatte 2", 6, 4, 4, spielfeldModel));
schiffe.add(erzeugeSchiffWaagerecht("Tropedoboot", 7, 1, 3, spielfeldModel));
schiffe.add(erzeugeSchiffWaagerecht("Landungsboot", 8, 6, 2, spielfeldModel));
schiffe.add(erzeugeSchiffWaagerecht("Schlauchboot 1", 8, 8, 1, spielfeldModel));
schiffe.add(erzeugeSchiffWaagerecht("Schlauchboot 2", 2, 8, 1, spielfeldModel));
}

}```[/SPOILER]

bye
TT

@Timothy_Truckle Danke für deine Antwort kann ich nur leider nicht verwenden, da andere Vorgaben.

für dein eigenes Programm musst du abwarten bis dein Kopf nicht mehr glüht und dann zu den Grundlagen zurückkehren,
schau dir etwa an was für k=0 konkret im Unterarray drinsteht und wie man dafür die Felder abfragt,

getBattleshipCell(i, j) ist dafür nicht hilfreich

mehr schreibe ich diesmal nicht, schon richtig dass zuviel auf einmal alles durcheinander bringt

[OT][quote=Smokie]da andere Vorgaben.[/quote]Du hast also die Vorgabe, in einer OO-Sprache zu programmieren, aber keine Objekte einzusetzten?

Dann grüß mal Deinen Ausbilder von mir, der soll sich hier mal melden…[/OT]

bye
TT

[ot]
Dann pass’ auf, dass der sich nicht hier meldet, und dir vorwirft, du würdest eine Abweichung vom MVC propagieren. In einer Modell-Klasse hat eine CheckBox nix verloren :wink: (noch weniger als eine JCheckBox ;-))
[/ot]

Mir ist noch nicht ganz klar, WAS die gepostete Methode machen soll (habe aber auch nicht den ganzen Thread gelesen). Es geht darum, mit der Methode einen „Schuss“ zu machen UND dabei zu prüfen, ob damit etwas getroffen wurde UND damit das gesamte Schiff versenkt wurde? Etwas mehr code könnte helfen. Alles als einen compilierfähigen Block zusammen kopiert, so dass man es schnell testen kann, wäre am besten, aber … nicht immer machbar.

[OT][quote=Marco13]
Dann pass’ auf, dass der sich nicht hier meldet, und dir vorwirft, du würdest eine Abweichung vom MVC propagieren. In einer Modell-Klasse hat eine CheckBox nix verloren (noch weniger als eine JCheckBox )[/quote]
Also ehrlich gesagt erwarte ich vom Urheber der Aufgabe nicht, dass er das erkennen könnte.[/OT]

bye
TT