Minesweeper in Java mit BlueJ programmieren

Hallo Zusammen

Ich habe für die Schule diese Programmieraufgabe gefasst. Der Code müsste soweit fertig sein, aber wen ich es testen will, dann kommt immer folgender Fehler:

java.lang.ArrayIndexOutOfBoundsException: 7
at Spielfeld.befüellen(Spielfeld.java:114)
at Spielfeld.(Spielfeld.java:35)
at Spiel.Spiel(Spiel.java:22)

Hier noch mein Code:

import java.util.Random;
import java.util.Scanner;

/**
 * Write a description of class Spielfeld here.
 * 
 * Rietzler Matthias
 * Version 1.0
 */

public class Spielfeld
{
    private int [][] minen;
    private char[][] spielfeld;
    private Random random = new Random();
    private Scanner input = new Scanner(System.in);
    private int i;
    private int j;
    private int Reihe, Spalte;
    private int Leben=3;
    private char Aktion;
    private int m;
    
    
    /**
     * Der Konstruktor started das Spiel.
     */
    public Spielfeld(int i, int m){
        if(i>=5 && i<=10){
            j=i;
            minen = new int [i+2][j+2];
            spielfeld = new char [i+2][j+2];
            minenStarten();
            minenSetzen(m);
            befüellen();
            spielfeldErzeugen();
            
        }else{
            System.out.println("Sie müssen eine Zahl zwischen 5 und 10 wählen");
        }

    }
    
    /**
     * Diese Methode erzeugt das Spielfeld.
     */
    public void spielfeldErzeugen(){
        for(int i=1; i<spielfeld.length+1; i++){
            for(int j=1; j<spielfeld.length+1; j++){
                spielfeld**[j]= '-';
            } 
        }
    }
    
    /**
     * Diese Methode definiert den Bereich in dem die Minen gelegt werden.
     */
    public void minenStarten(){
        for(i=0; i<minen.length; i++){
            for(j=0; j<minen.length; j++){
                minen**[j]= 0;
            } 
        }
    }
    
    /**
     * Die Methode verteilt nach dem Zufallsprinzip Minen auf dem Spielfeld.
     */
    public void minenSetzen(int m){
        if(m>0 && m<(i*j*0.4)){
            boolean raffled;
            int x;
            int Reihe, Spalte;
            for(x=1; x<m+1; x++){
                int a,b;
                do{
                    Reihe = random.nextInt(i);
                    Spalte = random.nextInt(j);
                    if(minen[Reihe][Spalte] ==-1){
                        raffled=true;
                    }else{
                        raffled=false;
                    }
                }while(raffled);
                minen[Reihe][Spalte]=-1;
            }
        }
    }
    
    /**
     * Diese Methode legt die anliegenden Felder frei, sofern das Hauptfeld  ein leeres Feld ist.
     */
    public void oeffneAnliegende(){
        if(spielfeld[Reihe][Spalte]==0){
            for(int d=-1; d<2; d++){
                for(int e=-1; e<2; e++){
                    if((minen[Reihe+d][Spalte+e]!=-1) && (Reihe!=0 && Reihe!=i && Spalte!=0 &&Spalte!=j)){
                        spielfeld[Reihe+d][Spalte+e]=Character.forDigit(minen[Reihe+d][Spalte+e],10);  //forDigit ermöglicht die Umwandlung von int in char
                    }
                }
            }
        }
    }
    
    /**
     * Diese Methode befüllt die Positionen mit den Zahlen.
     */
    public void befüellen(){
        for(int k=1; k<i+1; k++){
            for(int l=1; l<j+1; l++){
                for(int d=-1; d<2; d++){
                    for(int e=-1; e<2; e++){
                        if(minen[k][l]!=-1){
                            if(minen[k+d][l+e]==-1){
                                minen[k][l]++;
                            }
                        }
                    }
                }
            }
        }
    }
    
    /**
     * Diese Methode erstellt das Ausgabenfeld.
     */
    public void anzeigen(){
        System.out.println("                      Spalten");
        System.out.println("Reihen");
        for(int Line = 1 ; Line < j+1 ; Line++){
            System.out.print("       "+Line + " ");
            for(int Column = 0 ; Column < i ; Column++){
                System.out.print("   "+ spielfeld[Reihe][Spalte]);
            }
            
            System.out.println("Bei der Eingabe für 'Aktion' muss für eine Flagge 'f' eingegeben werden.");
        }
    }
    
    /**
     * Kontrolliert die gewählte Position auf Minen.
     */
    public int gewaehltePosition(int Reihe, int Spalte){
        return minen[Reihe][Spalte];
    }
    
    /**
     * Diese Methode deckt die Felder auf oder markiert sie mit eine Flagge.
     */
    public boolean positionAufdecken(){
        do{
            System.out.println("Aktion: ");
            Aktion = (char)input.nextInt();
            System.out.println("Reihe: ");
            Reihe = input.nextInt()-1;
            System.out.println("Spalte: ");
            Spalte = input.nextInt()-1;
            if(Aktion=='f'){
                spielfeld**[j]='x';
            }
            if(spielfeld[Reihe][Spalte] != '-' && Reihe<i+1 && Reihe>0 && Spalte>0 && Spalte<j+1){
                System.out.println("Diese Feld ist schon aufgedeckt.");
            }
            if(Reihe<1 || Reihe>i || Spalte<1 || Spalte>j){
                System.out.println("Wähle eine Zahl zwischen 1 und " + 1);
            }
        }while(Reihe<1 || Reihe>i || Spalte<1 || Spalte>j || spielfeld[Reihe][Spalte] !='-');
        if(gewaehltePosition(Reihe, Spalte)==-1){
            Leben--;
        }
        if(Leben!=0){
            return true;
        }else{
            return false;
        }
    }
    
    /**
     * Mit dieser Methode werden alle Minen im Spiel aufgedeckt bei einer Niederlage.
     */
    public void alleMinen(){
        for(int e=1; e<i+1; e++){
            for(int f=1; f<j+1; f++){
                if(minen[e][f] ==-1){
                    spielfeld[e][f]='*';
                }
            }
        }
        anzeigen();
    }
    
    /**
     * Diese Methode überprüft, ob das Spiel bereits gewonnen ist oder schon verloren.
     */
    public boolean sieg(){
        int count=0;
        for(int g=1; g<i+1; g++){
            for(int h=1; h<j+1; h++){
                if(spielfeld[g][h]=='x'){
                    count++;
                }
            }
        }
        if(count==m){
            return true;
        }else{
            return false;
        }
    }
    
}
public class Spiel
{
    private Spielfeld spielfeld;
    private boolean ende=false;
    private boolean sieg=false;

    /**
     * Constructor for objects of class Spiel
     */
    public static void main(String[] args){
        new Spiel();
    }

    public void Spiel(int i, int m){
        spielfeld = new Spielfeld(i, m);
        spielen(spielfeld); 
    }

    /**
     * Diese Bereitet das Spiel vor.
     */
    public void spielen(Spielfeld spielfeld){
        do{
            spielfeld.anzeigen();
            ende = spielfeld.positionAufdecken();
            if(!ende){
                spielfeld.oeffneAnliegende();
                ende=spielfeld.sieg();
            }
        }while(!ende);

        if(spielfeld.sieg()){
            System.out.println("Glückwunsch, Sie haben meine Spiel geschaft.");
            spielfeld.alleMinen();
        }else{
            System.out.println("Pech gehabt! Nach 3 Minen stirbt jeder ;)");
            spielfeld.alleMinen();
        }
    }

}

Bin über jede Hilfe dankbar.

Die betroffene Zeile:

if(minen[k][l]!=-1){

Zunächst würde ich folgendes tun:

if(minen
   [k]
   [l]
   !=-1
){

Um die genaue position des fehlers zu bestimmen. Ist hier ein bisschen übrtrieben, es geht nur um k oder l oder beides.
Jetzt heisst es

java.lang.ArrayIndexOutOfBoundsException: 7

Das heisst das es den index 7 in dem array nicht gibt. (x oder y wirst du ja wie oben herausfinden)
(Wahrscheinlich weisst du das alles ja, aber trozdem)

Also hat das array nur die positionen 0, 1, 2, 3, 4, 5, 6, aber nicht 7.
Versuch mal zum Beispiel ein mal weniger durch die schleife zu gehen. Was ich nicht verstehe:

wieso l<j+1; ? was ist j, wieso + 1?

Ich hab deinen Vorschlag mal umgesetzt und anschliessend mit folgendem Code getestet.

public void befüellen(){
        for(int k=1; k<i; k++){
            for(int l=1; l<j; l++){
                for(int d=-1; d<2; d++){
                    for(int e=-1; e<2; e++){
                        if(minen
                        [k]
                        [l]
                        !=-1){
                            if(minen
                            [k+d]
                            [l+e]
                            ==-1){
                                minen[k][l]++;
                            }
                        }
                    }
                }
            }
        }
    }

In dieser schreibweise meldet er den gleichen Fehler und markiert die gelb geschribene Zeile.
Leider werde ich aus dieser Markierung nicht wirklich schlau.

Das +1 hab ich geschriben, weil nach meiner überlegung sonst nur bei 1 durchgang fehlt.

Bsp i =5 -> das nur bis 4 gezählt wird, also nur 4 mal die Schlaufe abgehandelt wird.

Dein k geht bis zur array-grenze, beim letzten durchlauf von d ist da = 1 und du greifst auf [k+1] zu: Peng :slight_smile:

Du musst bei den 2 inneren Schleifen überprüfen, dass du nicht aus dem Array läufst, oder die 2 äußeren schleifen bis k<i-1 und l<j-1 laufen lassen.

Übrigens fängt ein Array bei 0 an zu zählen, nicht bei 1…

Gruß

Übrigens fängt ein Array bei 0 an zu zählen, nicht bei 1…

genau deswegen auch der denkfehler mit dem +1.
Mach das doch mal weg. Das mit dem Code umbauen war einfach nur um zu sehen an welcher stelle genau er scheitert.
Wenn du zum beispiel ein 26D array hast, (XD) Und in dieser Zeile ne exception bekommst:

verruecktesarray[a]**[d][e][f][g][h]**[j][k][l][m][n][o][p][q][r][s][t][u][v][w][y][z].length;```

da kannst du wohl kaum auf anhieb sagen wo genau es nun scheitert…

Mit dem Aufsplitten in eine Zeile pro Index gewinnst du aber auch nichts, der Fehler wird immer beim Start der Expression geworfen, nicht in der Zeile mit dem Indexzugriff.
Besser einen Debugger, Logausgaben oder einen schlauen Blick auf die Schleifen (AIOOB findet man meistens sehr schnell) zur Fehlersuche verwenden.

Gruß

Vielen Dank für eure Hilfe. Das Problem mit dem OutOfBounds habe ich nun gelöst.

Leider will der ganze Rest noch nicht so richtig funktionieren. Ich hatte das Problem, dass es nach dem 1. mal Zahlen eingeben schon das Spiel beendete und alle Minen aufdeckte. Ich habe nun ein paar Sachen kontrolliert und geändert. Leider werden nun die Felder nicht mehr geöffnet.

Ich vermute den Fehler in diesen Zeilen:

        do{
            spielfeld.anzeigen();
            ende = spielfeld.eingabePosition();
            if(!ende){
                spielfeld.oeffneAnliegende();
                ende=spielfeld.sieg();
            }
        }while(!ende);

        if(sieg==true){
            System.out.println("Glückwunsch, Sie haben meine Spiel geschafft.");
            spielfeld.alleMinen();
        }else{
            System.out.println("Pech gehabt! Nach 3 Minen stirbt jeder ;)");
            spielfeld.alleMinen();
        }
    }```

```public void oeffneAnliegende(){
        if(spielfeld[Reihe][Spalte]==0){
            for(int d=-1; d<2; d++){
                for(int e=-1; e<2; e++){
                    if((minen[Reihe+d][Spalte+e]!=-1) && (Reihe!=0 && Reihe!=i+1 && Spalte!=0 &&Spalte!=j+1)){
                        spielfeld[Reihe+d][Spalte+e]=Character.forDigit(minen[Reihe+d][Spalte+e],10);  //forDigit ermöglicht die Umwandlung von int in char
                    }
                }
            }
        }
    }```

```public boolean sieg(){
        int count=0;
        for(int g=1; g<i; g++){
            for(int h=1; h<j; h++){
                if(spielfeld[g][h]=='-'){
                    count++;
                }
            }
        }
        if(count==m){
            return true;
        }else{
            return false;
        }
    }```

Das macht es nur unleserlich und unübersichtlich, und hilft wie firephonix sagt rein garnix, da der Fehler in Zeile 2 auftritt, also zu Begin der Anweisung.

Laut deiner Überschrift verwendest du BlueJ, das ist schon ein Fehler, nimm lieber eine anständige IDE wie z.B. Eclipse, Netbeans, InteliJ.

Wo und mit was ist dein m in deiner sieg() Funktion gesetzt?

Wie sieht deine Funktion eingabePosition() aus?

Leider muss ich BlueJ verwenden, da wir im Unterricht mit dem dazugehörigen Buch arbeiten.

m sind die Anzahl Minen, die im Spiel verteilt werden. Diese werden beim Start des Spiels gewählt.

Soweit funktioniert es inzwischen auchdas aufdecken, nur sobald ich nun die letzte Reihe und die Letzte Spalte aufdecken möchte kommt wieder der OutOfBounds Fehler.

Java Code:

[ol]
[li]/[/li]
[li]
** * Diese Methode überprüft die Eingabe auf Ihre Korrektheit(im Feld). Ausserdem überprüft Sie das gewählte Feld auf eine Mine.[/li]
[li]
/**[/li]
[li] public boolean eingabePosition(){[/li]
[li] do{[/li]
[li] [/li]
[li] System.out.println("Reihe: ");[/li]
[li] Reihe = input.nextInt();[/li]
[li] System.out.println("Spalte: ");[/li]
[li] Spalte = input.nextInt();[/li]
[li] [/li]
[li] if((spielfeld[Reihe][Spalte] != ‘-’) && ((Reihe<i+1 && Reihe>0) && (Spalte>0 && Spalte<j+1))){[/li]
[li] System.out.println(“Diese Feld ist schon aufgedeckt.”);[/li]
[li] }[/li]
[li] if(Reihe<1 || Reihe>i || Spalte<1 || Spalte>j){[/li]
[li] System.out.println("Wähle eine Zahl zwischen 1 und " + i);[/li]
[li] }[/li]
[li] [/li]
[li] }while((Reihe<1 || Reihe>i || Spalte<1 || Spalte>j) || (spielfeld[Reihe][Spalte] !=’-’));[/li]
[li] if(gewaehltePosition(Reihe, Spalte)==-1){[/li]
[li] spielfeld[Reihe][Spalte]=’
’;[/li]
[li] Leben–;[/li]
[li] }[/li]
[li] if(Leben==0){[/li]
[li] return true;[/li]
[li] }else{[/li]
[li] return false;[/li]
[li] }[/li]
[li] }[/li]
[/ol]

Wie groß ist das Array spielfeld? Welche Werte gibst Du für die letzte Spalte/Reihe ein?
Dir ist bewusst das der Index eines Arrays der Länge n von 0 bis n-1 läuft.

Das Array ist zwischen 5x5 und 10x10 gross.
Ja das ist mir bewusst. Deshalb war meine Idee, bei der Eingabe der Feldlänge anschliessend noch +2 zu rechnen damit ich um das gesamte Feld noch eine zusätzliche Reihe habe.
Dies funktioniert nun soweit. Mein jetziges Problem besteht noch darin, dass das Programm auch die letzte Reihe und Spalte mit Minen befüllt. An den Stellen die ich für richtig gehalten habe um den Bereich einzuschränken, ruft diese -1 leider eine outOfBoungs-Fehlermeldung anschliessend hervor.

Der Sinn dahinter erschließt sich mir jetzt überhaupt nicht?? Wieso +2? um „aussen rum“ noch einen Rahmen zu haben? Was hast Du/Dein Programm davon?

Grundsätzlich wäre es besser die Reihenfolge im ersten if umzustellen, dann könnte keine AOOBException auftreten (zumindest in der Schleife). Das mit dem +2 würde ich sein lassen und wenn das Spielfeld immer quadratisch ist könnte man die Variable j entfallen lassen.

[QUOTE=Unregistered]Das macht es nur unleserlich und unübersichtlich, und hilft wie firephonix sagt rein garnix, da der Fehler in Zeile 2 auftritt, also zu Begin der Anweisung.
[/QUOTE]

Folgender Code:


public class Test{
	public static void main(String[]args){ 
		int a[][][][][] = new int[5][5][5][5][5];
		System.out.println(a
				[0]
				[0]
				[0]
				[0]
				[10]);
	}
}

erzeugt folgende Exception:

Exception in thread “main” java.lang.ArrayIndexOutOfBoundsException: 10
at pack1.Test.main(Test.java:11)

und dieser Code:


public class Test{
	public static void main(String[]args){ 
		int a[][][][][] = new int[5][5][5][5][5];
		System.out.println(a
				[0]
				[0]
				[10]
				[0]
				[0]);
	}
}

erzeugt diese Exception:

Exception in thread “main” java.lang.ArrayIndexOutOfBoundsException: 10
at pack1.Test.main(Test.java:9)

Unübersichtlich finde ich es trotzdem.
Was für eine IDE verwendest du? Denn z.B. IntelliJ macht das nicht so und meines Wissens Eclipse auch nicht.

Ich benutze eclipse. Hat es vielleicht damit was zu tun? Und was heisst unübersichtlich, es ist ja nur einmal kurz um den fehler zu finden. Debk ja nicht ich schreibe alles so :smiley:

Wie gesagt, ich habe sonst bis auf in meinem jetzigem Projekt immer Eclipse verwendet und wusste das nicht, jetzt IntelliJ und jedenfalls bei anderen Funktionen mit mehrzeiligen Parametern oder so, zeigt er es nicht so an.
Zum Fehler finden, würde ich den Code nicht so umformatieren, sondern eher den Debuger benutzen. Und ich bin davon ausgegangen, du würdest es so schreiben und da finde ich es für ein Array schon übersichtlich. Für Parameter in einer Funktion / Methode, finde ich die Schreibweise untereinander in Ordnung.

hm. :slight_smile:

ich hab das Spiel soweit zum laufen gebracht. das einzige problem im moment ist, dass mein counter nicht richtig funktioniert um das Spiel zu beenden. Meine Idee ist das er die Anzahl geöffneten Felder zu den Minen addiert und mit den Anzahl Feldern vergleicht. Leider funktionier das nicht so richtig.

das sind die betroffenen code-stellen:

     * Diese Methode legt die anliegenden Felder frei, sofern das Hauptfeld  ein leeres Feld ist.
     */
    public void oeffneAnliegende(){
        spielfeld[Reihe][Spalte]=Character.forDigit(minen[Reihe][Spalte],10);
        if(spielfeld[Reihe][Spalte]=='0'){
            count++;
            for(int d=-1; d<2; d++){
                for(int e=-1; e<2; e++){
                    if((minen[Reihe+d][Spalte+e]!=-1 && (Reihe!=0 && Reihe!=i && Spalte!=0 &&Spalte!=j))){
                        if(spielfeld[Reihe+d][Spalte+e]=='-'){
                            count++;
                        }
                        spielfeld[Reihe+d][Spalte+e]=Character.forDigit(minen[Reihe+d][Spalte+e],10);  //forDigit ermöglicht die Umwandlung von int in char
                        }
                }
            }
        }
    }```

```/**
     * Diese Methode überprüft, ob das Spiel erfolgreich beendet wurde.
     */
    public boolean sieg(){

        if(count + m == s){
            return true;
        }else{
            return false;
        }
    }```

Guck doch bei jedem klick einfach ob alle felder entweder auf oder"geflaggt" sind, wenn ja dann ob die flaggen richtig gesetzt wurden.

Sorry, dein Code ist leider recht unverständlich und unübersichtlich, das ich garnicht genau weiß, was er machen soll.

Zum einen nehme sprechende Namen. Weil was ist m oder s oder count in deiner sieg Funktion? Warum verwendest du immer globale Variablen? Das ist in manchen Fällen Fehleranfällig.