Mastermind Indirekte Treffer

Hallöle,

ich arbeite gerade an einem kleinen Mastermind Spiel. Die Grafik funktioniert gescheit (Keiner meckert jetzt hier über NullLayout, das ist irrelevant für das Problem…) und auch die direkten Treffer, wenn zwei Farben übereinstimmen werden in den Kontrollfeldern richtig dargestellt. (Schwarze Farbe)

Aber die Rote Farbe, die mir anzeigt, ob eine Farbe zwar verhanden, aber nicht an der richtigen Stelle ist, die macht er nicht richtig.

Mein Programm hat zwei Klassen. In “Farbfeld” definiere ich die kleinen Panel mit den Farben, wo man durch einfaches durchklicken auf die nächste Farbe schaltet:


import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JPanel;

public class Farbfeld extends JPanel {
	
	private int farbe = -1;
	private boolean aktiviert = false;
	
	public Farbfeld() {
		this.addMouseListener(new MouseAdapter() {
			public void mouseClicked(MouseEvent evt) {
				aufMausReagieren();
			}
		});
	}
	
	public void paint(Graphics stift) {
		switch (farbe) {
		case 0:
			stift.setColor(Color.yellow);
			break;
		case 1: 
			stift.setColor(Color.blue);
			break;
		case 2:
			stift.setColor(Color.red);
			break;
		case 3:
			stift.setColor(Color.green);
			break;
		case 4:
			stift.setColor(Color.pink);
			break;
		case 5:
			stift.setColor(Color.cyan);
			break;
		default:
			stift.setColor(Color.gray);
			break;
		}
		stift.fillRect(0,0,this.getWidth(),this.getHeight());
	}
	
	public void nextColor() {
		if(farbe<5) {
			farbe++;
		} else {
			farbe=0;
		}
		this.repaint();
	}

	public int getFarbe() {
		return farbe;
	}
	
	public void aufMausReagieren() {
		if(aktiviert == true) {
			nextColor();
			repaint();
		}
	}
	
	public void setAktiviert(boolean neuerwert) {
		aktiviert = neuerwert;
	}

}```

Der Kern ist die Klasse "Mastermind":
```package mastermind;

import java.awt.Color;
import java.awt.Container;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Mastermind {
	
	private JFrame frame1 = new JFrame("Mastermind");
	private JButton buttonpruefen = new JButton("Überprüfen");
	private JPanel jPanel1 = new JPanel();
	private int anzahlreihen = 8;
	private int reihe = 0;
	private Farbfeld[][] spielfeld = new Farbfeld[4][anzahlreihen];
	private JPanel[][] kontrollfeld = new JPanel[4][anzahlreihen];
	private int[] geheimnis = new int[4];
	private int schwarz, rot;
	private int[] farberichtig = new int[4];
	
	public Mastermind() {
		frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame1.setSize(200,380);
		frame1.setResizable(false);
		Container cp = frame1.getContentPane();
		cp.setLayout(null);
		
		buttonpruefen.setBounds(20, 315, 120, 33);
		buttonpruefen.setMargin(new Insets(2, 2, 2, 2));
		buttonpruefen.setVisible(true);
		buttonpruefen.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) {
				buttonpruefe_ActionPerformed(evt);
			}
		});
		cp.add(buttonpruefen);
		
		jPanel1.setBounds(10, 275, 33, 33);
	    jPanel1.setBackground(Color.ORANGE);
	    jPanel1.setVisible(true);
	    
	    int jx = jPanel1.getX();
	    int jy = jPanel1.getY();
	    int jw = jPanel1.getWidth();
	    for(int py=0; py<anzahlreihen; py++) {
	    	for(int px=0; px<4; px++) {
	    		spielfeld[px][py] = new Farbfeld();
	    		spielfeld[px][py].setBounds(jx+px*(jw+5),jy-py*(jw+5),jw,jw);
	    		if(py==0) {
	    			spielfeld[px][py].setAktiviert(true);
	    		}
	    		cp.add(spielfeld[px][py]);
	    	}
	    }
	    for(int py=0; py<anzahlreihen; py++) {
	    	for(int px=0; px<2; px++) {
	    		for(int pz=0; pz<2; pz++) {
	    			kontrollfeld[px+2*pz][py] = new JPanel();
	    			kontrollfeld[px+2*pz][py].setBounds(jx+4*(jw+5)+px*jw/2,jy-py*(jw+5)+pz*jw/2,jw/2-4, jw/2-4);
	    			kontrollfeld[px+2*pz][py].setBackground(Color.LIGHT_GRAY);
	    			cp.add(kontrollfeld[px+2*pz][py]);
	    		}
	    	}
	    }
	    
	    frame1.setLocationRelativeTo(null);
		frame1.setVisible(true);
		
		start();
	}
	
	public void start() {
		Random farbwuerfel = new Random();
		System.out.print("Geheimcode: ");
		for(int n=0;n<4;n++) {
			geheimnis[n] = farbwuerfel.nextInt(6);
			System.out.print(geheimnis[n]);
		}
		System.out.println("");
	}
	
	public void buttonpruefe_ActionPerformed(ActionEvent evt) {
		for(int n=0;n<4;n++) {
			farberichtig[n] = 0;
		}
		schwarz = 0; rot = 0;
		for(int px=0;px<4;px++) {
			spielfeld[px][reihe].setAktiviert(false);
			spielfeld[px][reihe+1].setAktiviert(true);
			for(int z=0;z<4;z++) {
				if(spielfeld[px][reihe].getFarbe() == geheimnis[z]) {
					if(px == z) {
						schwarz++;
						farberichtig[z] = 1;
					} else if(farberichtig[z] != 1) {
						System.out.println("px:"+px+" z:"+z);
						rot++;
					}
				}
			}
		}
		//rot -= schwarz;
		System.out.println("Übereinstimmungen: "+schwarz);
		System.out.println("Indirekt: "+rot);
		for(int a=0;a<schwarz;a++) {
			kontrollfeld[a][reihe].setBackground(Color.black);
		}
		for(int b=schwarz;b<schwarz+rot;b++) {
			//if(b<4) {
				kontrollfeld**[reihe].setBackground(Color.red);
			//}
		}
		reihe++;
	}

	public static void main(String[] args) {
		new Mastermind();
	}
}```

Ich habe einen Spielfeldarray[][], wobei x stets die vier Kästchen nebeneinander sind (0 bis 3) und y die Reihe. Die Reihen zählen von unten nach oben, je nach Spielrunde eben. Es gelten also die Klassischen Mastermindregeln.
Rechts sind die vier Komtrollkästchen dazu.
So sieht das dann aus:


Ich klicke in der aktuellen Reihe jeweils die vier Kästchen meine Farbtipps durch und beim Klick auf Überprüfen rechnet er die Tipps in den Kontrollkästchen aus. Außerdem sperrt er die geratene Reihe vor neuen Klicken ab und schaltet die nächste Reihe frei.


Aber beim Überprüfungsalgorithmus gibt es einen Hacken.
Es gibt die 6 Farben (0 bis 5), die alle in einen geheimnis[]-array gespeichert werden.
Und ansonsten krieg ich die Farbwerte eines Kästchens hiermit heraus:
`spielfeld[px][reihe].getFarbe()`

Ich schalte dann in zwei Forschleifen mit dem Wert px für die Nummer des Kontrollkästchens und z für die Nummern der Farblösungen durch.
Wenn etwas übereinstimmt, dann guckt er ob die Ziffern gleich sind. Wenn ja, dann muss es sich um dieselben Felder handeln und er kann Schwarz ausgeben.

Wenn nicht, dann muss er Rot auswerfen.
Und mit Rot hab ich so meine Probleme.

Folgendes Beispiel:
Die Lösung ist: Rot-Grün-Blau-Rot
Wenn ich jetzt als Spieler rate: Rot-Grün-Rot-Blau, dann müsste er mir zwei Schwarze und zwei Rote anzeigen. Die ersten beiden sind komplett richtig (schwarz) und de letzten beiden sind als farben korrekt, aber an der falschen Stelle (rot).

Jedoch denkt das Programm dann schon einen Schritt weiter und sieht, aha, Rot ist ja zweimal da, und zählt dann zu viel hoch. Obwohl Rot ja ganz vorne schon als richtig galt.

Ich hoffe ich habe das jetzt nicht zu verwirrend beschrieben und man versteht es.

====
Meine erste Lösungsidee war:
Ich habe mir das hier geschrieben: `private int[] farberichtig = new int[4];`
Das heißt, wenn eines der vier Kästchen an der richtigen Stelle bestimmt war, ändere ich den Wert da auf 1, damit das Programm dann in einer If-bedingung merkt, das Feld hab ich aber schon verbraten. Das ändert aber irgendwie nichts.

Ich habe keine Ahnung, was da schief läuft.


Kann mir einer sagen, was ich in dem Algorithmus anders machen müsste?

Bei unbekannten Mastermindregeln, kann man diese hier angucken: https://de.wikipedia.org/wiki/Mastermind_(Spiel)

Viele Grüße
Lukas

Also das Null-Layout ist … … :wink:

… … schon fast das geringere Problem, als die Tatsache, dass da „Model“ und „View“ gemischt sind.

Unabhängig davon ist das Problem, soweit ich das verstanden habe, das, dass er sich nicht merkt, welche Felder er schon verwendet hat. In http://forum.byte-welt.net/java-forum-erste-hilfe-vom-java-welt-kompetenz-zentrum/java-grundlagen-fuer-anfaenger-und-umsteiger-java-se/3724-mastermind-npc.html#post16669 hatte ich das in der Methode „computeResponseWithoutTellingTheComputerPlayerTheRealCombination“ sehr … „pragmatisch“ (d.h. nicht sehr elegant) gelöst. (Die Listen von Characters wären bei dir Listen mit Farben). Falls das nicht hilft (oder auch falls es hilft, aber dir ZU „un-elegant“ erscheint) schau’ ich nochmal, wie man den gegebenen Code ändern müßte, um das gleiche zu erreichen.

Wunderschönen Guten Tag,

ich bin mir dem Konzept der Trennung Model-View-Controller bewusst. Aber bei so kleineren Projekten mit vergleichsweise sehr wenig Code, neige ich oft dazu das aus Faulheit zu missachten. Ich sehe da auch durch und find das so auf einen Blick auch irgendwie übersichtlicher.
Deinen Post hab ich mir angesehen. Ebenfalls eine Möglichkeit der Lösung.
Mein Problem ist jetzt dahingehend, wie ich das einbauen sollte, ohne dabei mein halbes Konzept auseinanderzunehmen.
Bis auf diese Kleinigkeit funktioniert ja alles und ich möchte das ungern, mich selbst ein wenig verwirrend so groß wieder umstellen.

Deshalb hatte ich auf eine Lösung gehofft, die man direkt in meine Variante hinein “friemeln” kann.

Ich danke erstmal für Deine Antwort und schau mal an, wie ich das umsetzen werde.

Schöne Grüße
Lukas

Diese “Faulheit” ist zwar einerseits nachvollziehbar. Andererseits sollte man sich über die Bedeutung im klaren sein. Programme fangen IMMER “klein” und (meistens) “übersichtlich” an. Aber Programme tendieren dazu, größer zu werden, und Objektorientierte Programmierung (und speziell irgendwelche Patterns) zielen ja genau darauf ab, dafür zu sorgen, dass sie trotzdem noch übersichtlich bleiben.

Solche Kritik (oder die Prinzipien, auf denen sie sich begründet) kann noch allgemeiner gesehen werden. Da kommen vieeeele Dinge zusammen, und “gute” Software entsteht IMHO nur (wenn überhaupt ;-)), wenn man diese Dinge berücksichtigt. GANZ offensichtliche und vordergründige Dinge, am geposteten Code:

Variablen-Scopes sollten so klein wie möglich sein. Die Variablen

private int[] farberichtig = new int[4];

werden NUR in einer Methode verwendet. Also sollten sie auch lokal (in dieser Methode) definiert sein.

So ein Schnipsel wie

        for(int px=0;Px<4;Px++) {
            spielfeld[px][reihe].setAktiviert(false);
            spielfeld[px][reihe+1].setAktiviert(true);
            for(int z=0;z<4;z++) {
                if(spielfeld[px][reihe].getFarbe() == geheimnis[z]) {
                    if(px == z) {
                        schwarz++;
                        farberichtig[z] = 1;
                    } else if(farberichtig[z] != 1) {
                        System.out.println("px:"+px+" z:"+z);
                        rot++;
                    }
                }
            }
        }

ist ein schon fast schmerzhafter Verstoß gegen “Separation of Concerns”. Was wird dort gemacht? Ja, die schwarzen werden gezählt, und die roten, und die Aktivierung der nächsten Zeile wird gemacht. Zumindest letzteres sollte in einer Methode ausgelagert werden, die GANZ am Ende der ActionPerformed aufgerufen wird:

        ....
        activateNextRow();
    }
    
    private void activateNextRow()
    {
        // Deactivate current row, activate next row
        for(int px=0;px<4;px++) {
            spielfeld[px][reihe].setAktiviert(false);
            spielfeld[px][reihe+1].setAktiviert(true);
        }
        reihe++;
    }

Aber auch das Zählen der Richtigen/Falschen Felder: Ich frage mich, wie du das bisher getestet hast. Durch das “new Random()” wird ja jedes mal eine neue Konfiguration erstellt. Mit einem “new Random(0)” wäre es wenigstens immer die gleiche. Aber selbst dann ist ein schnelles, gezieltes Testen der fraglichen Funktionalität über das GUI extrem aufwändig und umständlich. Ohne pauschal “Test Driven Development” propagieren zu wollen: Es wäre doch praktisch, wenn man schnell und einfach solche Tests machen könnte wie

// Die ersten beiden Blöcke sind Eingabe und Geheimnis,
// Die letzten beiden Zahlen stehen für "2 Schwarz, 2 Rot"
test(2,1,2,4,   2,1,4,2,   2,2); 

um dann schnell und einfach weitere Problemfälle testen und debuggen zu können:

test(1,1,1,1, 1,1,1,1, 4,0); // 4 mal schwarz
test(1,2,3,4, 4,3,2,1, 0,4); // 4 mal rot
test(1,2,2,2, 2,2,2,2, 3,1); // 3 mal schwarz, 1 mal rot



Ein paar kleine (!), ganz grob in diese Richtung gehende Änderungen sind hier drin, aber... bei solchen Aufräumaktionen kann man natürlich beliebig weit gehen, und das soll KEINE Empfehlung sein, das so zu machen (es gibt sicher elegantere Lösungen), aber vielleicht als GROBER Ansatz....

import java.awt.Color;
import java.awt.Container;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Mastermind {

private JFrame frame1 = new JFrame("Mastermind");
private JButton buttonpruefen = new JButton("Überprüfen");
private JPanel jPanel1 = new JPanel();
private int anzahlreihen = 8;
private int reihe = 0;
private Farbfeld[][] spielfeld = new Farbfeld[4][anzahlreihen];
private JPanel[][] kontrollfeld = new JPanel[4][anzahlreihen];
private int[] geheimnis = new int[4];

public Mastermind() {
    frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame1.setSize(200,380);
    frame1.setResizable(false);
    Container cp = frame1.getContentPane();
    cp.setLayout(null);
   
    buttonpruefen.setBounds(20, 315, 120, 33);
    buttonpruefen.setMargin(new Insets(2, 2, 2, 2));
    buttonpruefen.setVisible(true);
    buttonpruefen.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent evt) {
            buttonpruefe_ActionPerformed(evt);
        }
    });
    cp.add(buttonpruefen);
   
    jPanel1.setBounds(10, 275, 33, 33);
    jPanel1.setBackground(Color.ORANGE);
    jPanel1.setVisible(true);
   
    int jx = jPanel1.getX();
    int jy = jPanel1.getY();
    int jw = jPanel1.getWidth();
    for(int py=0; py<anzahlreihen; py++) {
        for(int px=0; px<4; px++) {
            spielfeld[px][py] = new Farbfeld();
            spielfeld[px][py].setBounds(jx+px*(jw+5),jy-py*(jw+5),jw,jw);
            if(py==0) {
                spielfeld[px][py].setAktiviert(true);
            }
            cp.add(spielfeld[px][py]);
        }
    }
    for(int py=0; py<anzahlreihen; py++) {
        for(int px=0; px<2; px++) {
            for(int pz=0; pz<2; pz++) {
                kontrollfeld[px+2*pz][py] = new JPanel();
                kontrollfeld[px+2*pz][py].setBounds(jx+4*(jw+5)+px*jw/2,jy-py*(jw+5)+pz*jw/2,jw/2-4, jw/2-4);
                kontrollfeld[px+2*pz][py].setBackground(Color.LIGHT_GRAY);
                cp.add(kontrollfeld[px+2*pz][py]);
            }
        }
    }
   
    frame1.setLocationRelativeTo(null);
    frame1.setVisible(true);
   
    start();
}

public void start() {
    Random farbwuerfel = new Random(0);
    System.out.print("Geheimcode: ");
    
    geheimnis[0] = 2;
    geheimnis[1] = 3;
    geheimnis[2] = 1;
    geheimnis[3] = 3;
    
    for(int n=0;n<4;n++) {
        //geheimnis[n] = farbwuerfel.nextInt(6);
        System.out.print(geheimnis[n]);
    }
    System.out.println("");
}

private static int berechneSchwarze(int aktuelleZeile[], int geheimnisKopie[])
{
    int schwarz = 0;
    for(int px=0;px<4;px++) {
        if(aktuelleZeile[px] == geheimnisKopie[px]) {
            schwarz++;
            aktuelleZeile[px] = -1;
            geheimnisKopie[px] = -1;
        }
    }
    return schwarz;
}
private static int berechneRote(int aktuelleZeile[], int geheimnisKopie[])
{
    int rot = 0;
    for(int px=0;px<4;px++) {
        if(aktuelleZeile[px] != -1) {
            for (int i=0; i<4; i++)
            {
                if (geheimnisKopie** == aktuelleZeile[px])
                {
                    rot++;
                    geheimnisKopie** = -1;
                    aktuelleZeile[px] = -1;
                    break;
                }
            }
        }
    }
    return rot;
}

public void buttonpruefe_ActionPerformed(ActionEvent evt) {

    int aktuelleZeile[] = new int[4];
    for(int px=0;px<4;px++) {
        aktuelleZeile[px] = spielfeld[px][reihe].getFarbe();
    }
    int geheimnisKopie[] = geheimnis.clone();
    
    int schwarz = berechneSchwarze(aktuelleZeile, geheimnisKopie); 
    int rot = berechneRote(aktuelleZeile, geheimnisKopie);
    
    System.out.println("Übereinstimmungen: "+schwarz);
    System.out.println("Indirekt: "+rot);
    for(int a=0;a<schwarz;a++) {
        kontrollfeld[a][reihe].setBackground(Color.black);
    }
    for(int b=schwarz;b<schwarz+rot;b++) {
        //if(b<4) {
            kontrollfeld**[reihe].setBackground(Color.red);
        //}
    }
    
    activateNextRow();
}

private void activateNextRow()
{
    // Deactivate current row, activate next row
    for(int px=0;px<4;px++) {
        spielfeld[px][reihe].setAktiviert(false);
        spielfeld[px][reihe+1].setAktiviert(true);
    }
    reihe++;
}

private static void test(int ... v)
{
    int zeile[] = Arrays.copyOfRange(v, 0, 4);
    int geheimnis[] = Arrays.copyOfRange(v, 4, 8);
    int schwarz = berechneSchwarze(zeile, geheimnis); 
    int rot = berechneRote(zeile, geheimnis);
    
    System.out.println(
        "Für "+ Arrays.toString(Arrays.copyOfRange(v, 0, 4))+
        " bei "+Arrays.toString(Arrays.copyOfRange(v, 4, 8))+": s "+schwarz+" r "+rot);
}


public static void main(String[] args) {
    
    test(1,1,2,3,  1,2,1,3);
    
    new Mastermind();
}

}

Wunderschönen Guten Tag,

ich danke Dir vielmals für die Zeit, die Du Dir genommen hast.

Es funktioniert schon einmal, aber ich hätte noch ein paar Fragen dazu und möchte natürlich Deine Einwände nicht offen stehen lassen.
Ich nummeriere es der Einfachheit halber einmal durch:
1)

Variablen-Scopes sollten so klein wie möglich sein. Die Variablen

private int[] farberichtig = new int[4];```
werden NUR in einer Methode verwendet. Also sollten sie auch lokal (in dieser Methode) definiert sein. 

Das ganze war noch eine Frühe Beta. Ich hatte anfangs damit gerechnet, es in mehr als einer Methode zu verwenden und dann später vergessen, es dann nur lokal zu nutzen. Vielen Dank für den Hinweis!

So ein Schnipsel wie
for(int px=0;Px<4;Px++) { spielfeld[px][reihe].setAktiviert(false); spielfeld[px][reihe+1].setAktiviert(true); for(int z=0;z<4;z++) { if(spielfeld[px][reihe].getFarbe() == geheimnis[z]) { if(px == z) { schwarz++; farberichtig[z] = 1; } else if(farberichtig[z] != 1) { System.out.println("px:"+px+" z:"+z); rot++; } } } }
ist ein schon fast schmerzhafter Verstoß gegen „Separation of Concerns“. Was wird dort gemacht? Ja, die schwarzen werden gezählt, und die roten, und die Aktivierung der nächsten Zeile wird gemacht. Zumindest letzteres sollte in einer Methode ausgelagert werden, die GANZ am Ende der ActionPerformed aufgerufen wird:

        activateNextRow();
    }
   
    private void activateNextRow()
    {
        // Deactivate current row, activate next row
        for(int px=0;Px<4;Px++) {
            spielfeld[px][reihe].setAktiviert(false);
            spielfeld[px][reihe+1].setAktiviert(true);
        }
        reihe++;
    }```

Okay okay, auch hier kann ich zustimmen. Ich hatte das anfangs ein wenig pragmatisch ineinander geschachtelt. Wäre mir möglicherweise noch aufgefallen, wenn ich über den Code rübergegangen wäre. Aber nun gut, ich danke vielmals auch für diesen Tipp! :wink:

Aber auch das Zählen der Richtigen/Falschen Felder: Ich frage mich, wie du das bisher getestet hast. Durch das „new Random()“ wird ja jedes mal eine neue Konfiguration erstellt. Mit einem „new Random(0)“ wäre es wenigstens immer die gleiche. Aber selbst dann ist ein schnelles, gezieltes Testen der fraglichen Funktionalität über das GUI extrem aufwändig und umständlich.

Hier muss einmal nachhaken. Was ist denn der Unterschied zwischen new Random(0) und new Random() ? Also was bringt mir das für einen Vorteil?

Was die Testmethode angeht, okay okay, das hätte man so machen können. Ich hab das jetzt immer so gemacht, dass ich das Programm eingeschaltet und mich selbst durchgeklickt habe. Umständlicher, aber es hat auch funktioniert.

Nun zum Endcode:
Es sieht nun so aus:


import java.awt.Color;
import java.awt.Container;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

public class Mastermind {
	
	private JFrame frame1 = new JFrame("Mastermind");
	private JButton buttonpruefen = new JButton("Überprüfen");
	private JPanel jPanel1 = new JPanel();
	private static int anzahlreihen = 8;
	private static int reihe = 0;
	private static Farbfeld[][] spielfeld = new Farbfeld[4][anzahlreihen];
	private JPanel[][] kontrollfeld = new JPanel[4][anzahlreihen];
	private static int[] geheimnis = new int[4];
	
	public Mastermind() {
		frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame1.setSize(200,380);
		frame1.setResizable(false);
		Container cp = frame1.getContentPane();
		cp.setLayout(null);
		
		buttonpruefen.setBounds(20, 315, 120, 33);
		buttonpruefen.setMargin(new Insets(2, 2, 2, 2));
		buttonpruefen.setVisible(true);
		buttonpruefen.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) {
				buttonpruefe_ActionPerformed(evt);
			}
		});
		cp.add(buttonpruefen);
		
		jPanel1.setBounds(10, 275, 33, 33);
	    jPanel1.setBackground(Color.ORANGE);
	    jPanel1.setVisible(true);
	    
	    int jx = jPanel1.getX();
	    int jy = jPanel1.getY();
	    int jw = jPanel1.getWidth();
	    for(int py=0; py<anzahlreihen; py++) {
	    	for(int px=0; px<4; px++) {
	    		spielfeld[px][py] = new Farbfeld();
	    		spielfeld[px][py].setBounds(jx+px*(jw+5),jy-py*(jw+5),jw,jw);
	    		if(py==0) {
	    			spielfeld[px][py].setAktiviert(true);
	    		}
	    		cp.add(spielfeld[px][py]);
	    	}
	    }
	    for(int py=0; py<anzahlreihen; py++) {
	    	for(int px=0; px<2; px++) {
	    		for(int pz=0; pz<2; pz++) {
	    			kontrollfeld[px+2*pz][py] = new JPanel();
	    			kontrollfeld[px+2*pz][py].setBounds(jx+4*(jw+5)+px*jw/2,jy-py*(jw+5)+pz*jw/2,jw/2-4, jw/2-4);
	    			kontrollfeld[px+2*pz][py].setBackground(Color.LIGHT_GRAY);
	    			cp.add(kontrollfeld[px+2*pz][py]);
	    		}
	    	}
	    }
	    
	    frame1.setLocationRelativeTo(null);
		frame1.setVisible(true);
		
		start();
	}
	
	public void start() {
		Random farbwuerfel = new Random();
		System.out.print("Geheimcode: ");
		
		for(int n=0;n<4;n++) {
			geheimnis[n] = farbwuerfel.nextInt(6);
			System.out.print(geheimnis[n]);
		}
		System.out.println("");
	}
	
	private static int rechnerSchwarz(int aktuelleZeile[], int geheimnisKopie[]) {
        int schwarz = 0;
        for(int px=0;px<4;px++) {
            if(aktuelleZeile[px] == geheimnisKopie[px]) {
                schwarz++;
                aktuelleZeile[px] = -1;
                geheimnisKopie[px] = -1;
            }
        }
        return schwarz;
    }
    private static int rechnerRot(int aktuelleZeile[], int geheimnisKopie[]) {
        int rot = 0;
        for(int px=0;px<4;px++) {
            if(aktuelleZeile[px] != -1) {
                for (int i=0; i<4; i++) {
                    if (geheimnisKopie** == aktuelleZeile[px]) {
                        rot++;
                        geheimnisKopie** = -1;
                        aktuelleZeile[px] = -1;
                        break;
                    }
                }
            }
        }
        return rot;
    }
	
	public void buttonpruefe_ActionPerformed(ActionEvent evt) {
		int aktuelleZeile[] = new int[4];
        for(int px=0;px<4;px++) {
            aktuelleZeile[px] = spielfeld[px][reihe].getFarbe();
        }
        int geheimnisKopie[] = geheimnis.clone();
       
        int schwarz = rechnerSchwarz(aktuelleZeile, geheimnisKopie);
        int rot = rechnerRot(aktuelleZeile, geheimnisKopie);
       
        System.out.println("Übereinstimmungen: "+schwarz);
        System.out.println("Indirekt: "+rot);
        for(int a=0;a<schwarz;a++) {
            kontrollfeld[a][reihe].setBackground(Color.black);
        }
        for(int b=schwarz;b<schwarz+rot;b++) {
        	kontrollfeld**[reihe].setBackground(Color.red);
        }
        if(schwarz == 4) {
        	if(reihe == 0) {
        		JOptionPane.showMessageDialog(null, "Du gewinnst nach nur einer Runde!
Ein wahrer Glückstreffer!", "Gewonnen!", JOptionPane.PLAIN_MESSAGE);
        		neustart();
        	} else {
        		JOptionPane.showMessageDialog(null, "Du gewinnst das Spiel nach "+(reihe+1)+" Runden!", "Gewonnen!", JOptionPane.PLAIN_MESSAGE);
        		neustart();
        	}	
        }
        if(reihe == 7) {
        	JOptionPane.showMessageDialog(null, "Du hast es nach 8 Runden nicht geschafft
die Farbkombination zu erraten. Schade!", "Verloren!", JOptionPane.PLAIN_MESSAGE);
        	neustart();
        }
        neuespielrunde();
	}
	
	private void neuespielrunde() {
        for(int px=0;px<4;px++) {
            spielfeld[px][reihe].setAktiviert(false);
            spielfeld[px][reihe+1].setAktiviert(true);
        }
        reihe++;
    }
	
	private void neustart() {
		int dialogneustart = JOptionPane.showConfirmDialog(null, "Möchtest Du eine neue Runde starten?", "Neue Runde?", JOptionPane.YES_NO_OPTION);
        if(dialogneustart == 0) {
        	start(); //?
        } else {
        	System.exit(0);
        }
	}

	public static void main(String[] args) {
		new Mastermind();
	}
}```

Es funktioniert auch sehr gut.
Ich frage an dieser Stelle einmal kurz:
Wozu hast Du das Geheimnis eigentlich geklont und den Klon später verändert? Das verstehe ich im Code noch nicht so ganz.

2. Ich habe jetzt noch am Ende ein Dialogfenster eingebaut, das dem User mitteilt, wann er gewonnen oder verloren hat. Sonst hätte man nach 8 Runden auch noch eine unschöne OutOfBoundsException kassiert.
Mich würde das hier noch interessieren:
```private void neustart() {
		int dialogneustart = JOptionPane.showConfirmDialog(null, "Möchtest Du eine neue Runde starten?", "Neue Runde?", JOptionPane.YES_NO_OPTION);
        if(dialogneustart == 0) {
        	start(); //?
        } else {
        	System.exit(0);
        }
	}```

Wenn ich auf die neue Methode start() aufrufe, dann muss ich ja noch die ganzen Werte neu zurücksetzen und den Spieler wieder in Reihe 0 starten lassen.
Geht das auch einfacher? Also ich meine das in etwa so:
`new Mastermind();` startet ein komplett neues Spiel und Fenster und legt es über das alte. Mit `System.exit(0);` könnte ich das aktuelle zumachen, aber das dahinter, das alte eben nicht.

Kann man das irgendwie realisieren? Also das ist jetzt eine generelle Frage, die mich bei meinen Programmen generell aufregt. Ja klar kann ich auch einfach die Variable zurücksetzen und Start drücken, aber mich würde interessieren, ob man das besser lösen kann.


Schöne Grüße
Lukas :)

Umgekehrt wird ein Schuh daraus. Egal in welchem Kontext du die Variable später erwartest, zuerst legst du sie normalerweise im minimal möglichen Scope ab. Wenn du sie aus irgend einem Grunde in einem größeren Sichtbarkeitsbereich brauchst, dann refaktorisiert man eben schnell (in IDEA wäre das beispielsweise nur ein Druck auf Strg+Alt+F).

Das machen wir hier in diesem Forum eben so :smiley:
(Ich zumindest, manchmal… wenn man Zeit hat, die man sich „nehmen“ kann ;))

Hier muss einmal nachhaken. Was ist denn der Unterschied zwischen new Random(0) und new Random() ? Also was bringt mir das für einen Vorteil?

Eigentlich ein Fall für die JavaDoc, aber kurz: Der Wert, der da übergeben wird, ist der „random seed“. Wenn man den Generator mit new Random(0) (oder irgendeinem anderen festen Wert) initialisiert, wird er nach jeder Erstellung immer wieder die gleiche Folge von Zufallszahlen liefern. Ein new Random() erzeugt einen Generator, der quasi bei jedem Programmstart eine andere Zahlenfolge liefert (Er macht dann intern dann GROB sowas wie new Random(System.currentTimeMillis()) - d.h. die gelieferte Zahlenfolge hängt von der Systemzeit ab.

Zumindest zum Testen ist ein new Random(0) meistens praktischer (und es gibt auch Fälle, wo man das IMMER verwenden kann - so ein Spiel ist eine Ausnahme, weil es blöd wäre, wenn es sich nach jedem Start gleich verhalten würde)

Ich frage an dieser Stelle einmal kurz:
Wozu hast Du das Geheimnis eigentlich geklont und den Klon später verändert? Das verstehe ich im Code noch nicht so ganz.

Hmja, das finde ich auch nicht „schön“, war aber die „pragmatischste“ Lösung, bei der nicht so viel geändert werden mußte. (Mit ein paar List<Integer> könnte man es einen Tick eleganter machen, aber das gibt sich nicht sooo viel). Das entscheidende ist, dass der „geheimnis“-Array während der „rechnerSchwarz“ und „rechnerRot“-Methoden geändert werden muss: Die Einträge werden auf „-1“ gesetzt, damit man die Treffer, die schon bei „schwarz“ gezählt wurden, nicht bei „rot“ nochmal zählt. Man will aber den eigentlichen „Spielzustand“ nicht ändern. D.h. das ursprüngliche „geheimnis“ soll ja erhalten bleiben, und nicht durch diese Test-Methoden „kaputt gemacht“ werden. Deswegen werde dort Kopien übergeben. (Die aber aufeinander aufbauen - d.h. man MUSS erst Schwarz und dann Rot berechnen … ja, nicht so schön, aber ein first shot :expressionless: )

Mich würde das hier noch interessieren:

Geht das auch einfacher? Also ich meine das in etwa so:
new Mastermind(); startet ein komplett neues Spiel und Fenster und legt es über das alte.

Kann man das irgendwie realisieren? Also das ist jetzt eine generelle Frage, die mich bei meinen Programmen generell aufregt. Ja klar kann ich auch einfach die Variable zurücksetzen und Start drücken, aber mich würde interessieren, ob man das besser lösen kann.

Hmja, das ist so ein Punkt, wo ein „Modell“ praktisch wäre: Man würde NUR das Modell zurücksetzen, was vermutlich schon mit einer Methode im Stil von

void reset() {
    guessesList.clear();
    secret = generateNewSecret();

    notifyViewAboutTheUpdate();
}

getan wäre. Dadurch, dass so viel von dem Spielzustand aber in der View „codiert“ ist, wäre das etwas frickelicker.

Die Möglichkeit, beim Bestätigen des Dialogs sowas zu machen wie

if(dialogneustart == 0) {
    frame1.dispose();
    new Mastermind();
} else {
  ....
}

besteht zwar theoretisch, aber das wäre ziemlicher Bogus (wie wenn man sich ein neues Auto kauft, weil der Tank leer ist (bei Tintenstrahldruckern IST das zwar schon fast so, aber das würde mein Beispiel kaputtmachen :D)).

Die zweitschlechteste Lösung wäre, das neu zu machen, was im Moment im Konstruktor gemacht wird, nämlich die ganzen Felder und Kontrollfelder (neu) erstellen, nachdem man die alten aus der ContentPane entfernt hat.

Einen Tick sinnvoller wäre, „händisch“ durch den ganzen Felder-Array zu gehen, und auf jeden Feld eine (noch zu erstellende) „reset“-Methode aufzurufen.

In allen Fällen wird die Frage relevant, WAS genau (d.h. welche Variablenn im einzelnen) den Spielzustand beschreibt (der „farberichtig“-Array z.B. nicht, deswegen konnte er auch wegfallen - aber der übrige Zustand steckt eben jetzt noch in den GUI-Elementen…)

[/QUOTE][QUOTE]
Hallöle und Danke für Eure Antworten,

cmrudolph, ich danke für diesen Ratschlag. :slight_smile: So eine Eclipsetastenkombination kenne ich nicht, aber ist ja auch nicht schwer, das mal eben selbst zu machen. :wink:

marco13:
Zum Random: Ich habs ausprobiert und mich gewundert warum bei (0) immer das gleiche herauskam. :wink: Deshalb die Frage. Vielen Dank für die Erklärung.

Zum Klon: Okay, verstanden. Ich muss zugeben, jetzt wenn ich weiß woher das kommt, find ich das sinnvoll und würde es nicht groß anders machen.

Zum Neustart des Programms:
Okay, das scheint etwas kompliziert zu sein.

Deshalb habe ich es jetzt auf primitivstem Wege wie immer gemacht.
Eine reset-Methode bei der alles auf Anfang gesetzt wird, wurde erstellt. :slight_smile:

Das sieht so aus:

		for(int py=0; py<anzahlreihen; py++) {
	    	for(int px=0; px<4; px++) {
	    		spielfeld[px][py].setBackground(Color.darkGray);
	    		spielfeld[px][py].setAktiviert(false);
	    		spielfeld[px][py].setFarbe(6);
	    	}
	    }
	    for(int py=0; py<anzahlreihen; py++) {
	    	for(int px=0; px<2; px++) {
	    		for(int pz=0; pz<2; pz++) {
	    			kontrollfeld[px+2*pz][py].setBackground(Color.lightGray);
	    		}
	    	}
	    }
	    
	    reihe = 0;
	    for(int px=0;px<4;px++) {
	    	spielfeld[px][0].setAktiviert(true);
	    }
	    
		
		start();
	}```

Ich setze die Kontrollfelder und die Spielfelder zurück, setze die Reihe auf 0 zurück und aktiviere ausschließlich die erste Reihe neu. Danach wird die Methode start wieder aufgerufen.

Aber es läuft absolut falsch.
Manchmal bleiben Reste des vergangenen Spiels übrig.
Und noch viel seltsamer: Er startet dann immer in der Zweiten Zeile (Spieltechnisch reihe=1).
Obwohl haargenau das hier da steht:
```for(int px=0;px<4;px++) {
	    	spielfeld[px][0].setAktiviert(true);
	    }```
Die dort stehende 0 ist doch aber keine 1 oder was?

Ansonsten lässt sich das Spiel dann ganz normal spielen. Aber man steigt eben erst eine Zeile später ein.

Sieht jemand, wo da mein Fehler in der Materie ist?
Der Rest ist der gleiche Quellcode wie vorher.

```package mastermind;

import java.awt.Color;
import java.awt.Container;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

public class Mastermind {
	
	private JFrame frame1 = new JFrame("Mastermind");
	private JButton buttonpruefen = new JButton("Überprüfen");
	private JPanel jPanel1 = new JPanel();
	private static int anzahlreihen = 8;
	private static int reihe = 0;
	private static Farbfeld[][] spielfeld = new Farbfeld[4][anzahlreihen];
	private JPanel[][] kontrollfeld = new JPanel[4][anzahlreihen];
	private static int[] geheimnis = new int[4];
	
	public Mastermind() {
		frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame1.setSize(200,380);
		frame1.setResizable(false);
		Container cp = frame1.getContentPane();
		cp.setLayout(null);
		
		buttonpruefen.setBounds(20, 315, 120, 33);
		buttonpruefen.setMargin(new Insets(2, 2, 2, 2));
		buttonpruefen.setVisible(true);
		buttonpruefen.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) {
				buttonpruefe_ActionPerformed(evt);
			}
		});
		cp.add(buttonpruefen);
		
		jPanel1.setBounds(10, 275, 33, 33);
	    jPanel1.setBackground(Color.ORANGE);
	    jPanel1.setVisible(true);
	    
	    int jx = jPanel1.getX();
	    int jy = jPanel1.getY();
	    int jw = jPanel1.getWidth();
	    for(int py=0; py<anzahlreihen; py++) {
	    	for(int px=0; px<4; px++) {
	    		spielfeld[px][py] = new Farbfeld();
	    		spielfeld[px][py].setBounds(jx+px*(jw+5),jy-py*(jw+5),jw,jw);
	    		if(py==0) {
	    			spielfeld[px][py].setAktiviert(true);
	    		}
	    		cp.add(spielfeld[px][py]);
	    	}
	    }
	    for(int py=0; py<anzahlreihen; py++) {
	    	for(int px=0; px<2; px++) {
	    		for(int pz=0; pz<2; pz++) {
	    			kontrollfeld[px+2*pz][py] = new JPanel();
	    			kontrollfeld[px+2*pz][py].setBounds(jx+4*(jw+5)+px*jw/2,jy-py*(jw+5)+pz*jw/2,jw/2-4, jw/2-4);
	    			kontrollfeld[px+2*pz][py].setBackground(Color.LIGHT_GRAY);
	    			cp.add(kontrollfeld[px+2*pz][py]);
	    		}
	    	}
	    }
	    
	    frame1.setLocationRelativeTo(null);
		frame1.setVisible(true);
		
		start();
	}
	
	public void start() {
		Random farbwuerfel = new Random();
		System.out.print("Geheimcode: ");
		
		for(int n=0;n<4;n++) {
			geheimnis[n] = farbwuerfel.nextInt(6);
			System.out.print(geheimnis[n]);
		}
		System.out.println("");
	}
	
	private static int rechnerSchwarz(int aktuelleZeile[], int geheimnisKopie[]) {
        int schwarz = 0;
        for(int px=0;px<4;px++) {
            if(aktuelleZeile[px] == geheimnisKopie[px]) {
                schwarz++;
                aktuelleZeile[px] = -1;
                geheimnisKopie[px] = -1;
            }
        }
        return schwarz;
    }
    private static int rechnerRot(int aktuelleZeile[], int geheimnisKopie[]) {
        int rot = 0;
        for(int px=0;px<4;px++) {
            if(aktuelleZeile[px] != -1) {
                for (int i=0; i<4; i++) {
                    if (geheimnisKopie** == aktuelleZeile[px]) {
                        rot++;
                        geheimnisKopie** = -1;
                        aktuelleZeile[px] = -1;
                        break;
                    }
                }
            }
        }
        return rot;
    }
	
	public void buttonpruefe_ActionPerformed(ActionEvent evt) {
		int aktuelleZeile[] = new int[4];
        for(int px=0;px<4;px++) {
            aktuelleZeile[px] = spielfeld[px][reihe].getFarbe();
        }
        int geheimnisKopie[] = geheimnis.clone();
        int schwarz = rechnerSchwarz(aktuelleZeile, geheimnisKopie);
        int rot = rechnerRot(aktuelleZeile, geheimnisKopie);
       
        System.out.println("Übereinstimmungen: "+schwarz);
        System.out.println("Indirekt: "+rot);
        for(int a=0;a<schwarz;a++) {
            kontrollfeld[a][reihe].setBackground(Color.black);
        }
        for(int b=schwarz;b<schwarz+rot;b++) {
        	kontrollfeld**[reihe].setBackground(Color.red);
        }
        if(schwarz == 4) {
        	if(reihe == 0) {
        		JOptionPane.showMessageDialog(null, "Du gewinnst nach nur einer Runde!
Ein wahrer Glückstreffer!", "Gewonnen!", JOptionPane.PLAIN_MESSAGE);
        		neustart();
        	} else {
        		JOptionPane.showMessageDialog(null, "Du gewinnst das Spiel nach "+(reihe+1)+" Runden!", "Gewonnen!", JOptionPane.PLAIN_MESSAGE);
        		neustart();
        	}	
        }
        if(reihe == 7) {
        	JOptionPane.showMessageDialog(null, "Du hast es nach 8 Runden nicht geschafft
die Farbkombination zu erraten. Schade!", "Verloren!", JOptionPane.PLAIN_MESSAGE);
        	neustart();
        }
        neuespielrunde();
	}
	
	private void neuespielrunde() {
        for(int px=0;px<4;px++) {
            spielfeld[px][reihe].setAktiviert(false);
            spielfeld[px][reihe+1].setAktiviert(true);
        }
        reihe++;
    }
	
	private void reset() {
		for(int py=0; py<anzahlreihen; py++) {
	    	for(int px=0; px<4; px++) {
	    		spielfeld[px][py].setBackground(Color.darkGray);
	    		spielfeld[px][py].setAktiviert(false);
	    		spielfeld[px][py].setFarbe(6);
	    	}
	    }
	    for(int py=0; py<anzahlreihen; py++) {
	    	for(int px=0; px<2; px++) {
	    		for(int pz=0; pz<2; pz++) {
	    			kontrollfeld[px+2*pz][py].setBackground(Color.lightGray);
	    		}
	    	}
	    }
	    
	    reihe = 0;
	    for(int px=0;px<4;px++) {
	    	spielfeld[px][0].setAktiviert(true);
	    }
	    
		
		start();
	}
	
	private void neustart() {
		int dialogneustart = JOptionPane.showConfirmDialog(null, "Möchtest Du eine neue Runde starten?", "Neue Runde?", JOptionPane.YES_NO_OPTION);
        if(dialogneustart == 0) {
        	reset();
        } else {
        	System.exit(0);
        }
	}

	public static void main(String[] args) {
		new Mastermind();
	}
}```
Schöne Grüße
Lukas

Edit:
Es funktioniert, wenn ich den Quelltext hier so umschreibe:
```if(schwarz == 4) {
        	if(reihe == 0) {
        		JOptionPane.showMessageDialog(null, "Du gewinnst nach nur einer Runde!
Ein wahrer Glückstreffer!", "Gewonnen!", JOptionPane.PLAIN_MESSAGE);
        		neustart();
        	} else {
        		JOptionPane.showMessageDialog(null, "Du gewinnst das Spiel nach "+(reihe+1)+" Runden!", "Gewonnen!", JOptionPane.PLAIN_MESSAGE);
        		neustart();
        	}	
        } else if(reihe == 7) {
        	JOptionPane.showMessageDialog(null, "Du hast es nach 8 Runden nicht geschafft
die Farbkombination zu erraten. Schade!", "Verloren!", JOptionPane.PLAIN_MESSAGE);
        	neustart();
        } else {
        	neuespielrunde();
        }```

Das neuespielrunde kommt in ein else, das stand bisher einzeln dahinter und hat dann die Methode mit dem weiterschalten von reihe++ aktiviert. 
Nun geht es.
Aber ich verstehe nicht, wieso das nötig ist.
Weil dadurch dass in den if-bedingungen schon auf eine Methode verwiesen wurde, dachte ich, wird man eh aus der Methode rausgeleitet und der Aufruf dieses Code kommt gar nicht mehr?

Und zweitens:
```for(int px=0;px<4;px++) {
	    	spielfeld[px][0].setAktiviert(true);
	    }```
Das hier ist eine Eineindeutige Zuordnung, die haargenau Reihe 0 aktiviert und nichts anderes. Wieso ist das dann da abhängig von dieser Sache?

Aber nun ist es erledigt.

Anmerkungen in meinem Code sind immer Willkommen. ;)

Hmja. Lass dich von diesem Effekt nicht einlullen. Wenn man irgendwas sieht (insbesondere, wenn man irgendwas sieht, was „gut“ ist - und das mit den Klonen ist NICHT „gut“, sondern nur eine schnelle Lösung, bei der nicht viel geändert werden mußte), dann neigt man dazu, zu denken: „Joa, das hätte ich genauso gemacht“. Es gehört viel Disziplin dazu, nicht die nahe liegende Frage zu verdrängen: „Und warum HAST du es nicht so gemcht?“.

In diesem Fall kam mir noch eine Alternative in den Sinn, mit der man sich das Klonen sparen könnte. Die Alternative wäre, NICHT Änderungen in einem geklonten Array vorzunehmen, sondern den Original-Array zu übergeben (und diesen dann NICHT zu verändern), und die Information, welche Felder schon gezählt wurden, mit einem „boolean[]“ Array abzubilden. Aber auch das wäre nicht besonders hübsch. Man müßte sich mal genauer, systematischer (auf schon fast Mathematisch-Theoretischer Ebene) überlegen, wie man diese „Antwort“ (rot/schwarz) am elegantesten berechnen könnte - und sich dann ggf. sogar eine eigene kleine Klasse dafür zu erstellen, die ohne rohe Arrays auskommt. Aber das würde jetzt wohl zu weit führen.

Aber es läuft absolut falsch.
Manchmal bleiben Reste des vergangenen Spiels übrig.
Und noch viel seltsamer: Er startet dann immer in der Zweiten Zeile (Spieltechnisch reihe=1).
Obwohl haargenau das hier da steht:

	    	spielfeld[px][0].setAktiviert(true);
	    }```
Die dort stehende 0 ist doch aber keine 1 oder was?

Da hab ich jetzt auch dreimal drüberschauen müssen… bis ich genau DAS bemerkt habe:

Edit:
Es funktioniert, wenn ich den Quelltext hier so umschreibe:

        	if(reihe == 0) {
        		JOptionPane.showMessageDialog(null, "Du gewinnst nach nur einer Runde!
Ein wahrer Glückstreffer!", "Gewonnen!", JOptionPane.PLAIN_MESSAGE);
        		neustart();
        	} else {
        		JOptionPane.showMessageDialog(null, "Du gewinnst das Spiel nach "+(reihe+1)+" Runden!", "Gewonnen!", JOptionPane.PLAIN_MESSAGE);
        		neustart();
        	}	
        } else if(reihe == 7) {
        	JOptionPane.showMessageDialog(null, "Du hast es nach 8 Runden nicht geschafft
die Farbkombination zu erraten. Schade!", "Verloren!", JOptionPane.PLAIN_MESSAGE);
        	neustart();
        } else {
        	neuespielrunde();
        }```

Das neuespielrunde kommt in ein else, das stand bisher einzeln dahinter und hat dann die Methode mit dem weiterschalten von reihe++ aktiviert.
Nun geht es.
Aber ich verstehe nicht, wieso das nötig ist.
Weil dadurch dass in den if-bedingungen schon auf eine Methode verwiesen wurde, dachte ich, wird man eh aus der Methode rausgeleitet und der Aufruf dieses Code kommt gar nicht mehr?

Ja, er hüpft an dieser Stelle in die „neustart“-Methode - aber irgendwann kommt er aus dieser Methode auch wieder raus, und ruft dann (also direkt nach dem Neustart) eben „neuespielrunde“ auf…

Und zweitens:

	    	spielfeld[px][0].setAktiviert(true);
	    }```
Das hier ist eine Eineindeutige Zuordnung, die haargenau Reihe 0 aktiviert und nichts anderes. Wieso ist das dann da abhängig von dieser Sache?

Ja, das macht er zuerst. Und danach ruft er irgendwann „neuespielrunde“ auf, wodurch genau die nächste Zeile aktiviert wird.

Anmerkungen in meinem Code sind immer Willkommen. :wink:

Mach das ‚static‘ weg

Warum hast du

    private static int anzahlreihen = 8;
    private static int reihe = 0;
    private static Farbfeld[][] spielfeld = new Farbfeld[4][anzahlreihen];
    private static int[] geheimnis = new int[4];

die static gemacht? Nimm das „static“ einfach raus.

Ansonsten könnte man viele, viele Verbesserungsvorschläge machen, große und kleine,… es ist schwer, da ein Ende zu finden. (Ist das eine Hausaufgabe oder so?)

Hallöle und Danke für Deine Antwort und Zeit dafür,

ich schaue später nochmal hinüber, ob ich das klonen besser machen kann. Aber diese „schnellen Lösungen“ neigen immer dazu Einzug in meine Person zu halten. :wink:

Vielen Dank für das Feedback zu meinem Neustartproblem. Klappt ja nun recht gut.

Das Static hab ich entfernt, danke. Ich hatte zwischenzeitlich Methoden direkt von public static main aus aufgerufen, weshalb Eclipse verlangt habe, nur statische Variablen in dieser statischen Methode aufzurufen. Mein Fehler. Ist wie das Problem von vorhin mit den Methodenübergreifenden Variablen. Hab ich einfach vergessen, als es nicht mehr relevant war es zurückzuändern.

Ansonsten könnte man viele, viele Verbesserungsvorschläge machen, große und kleine,… es ist schwer, da ein Ende zu finden. (Ist das eine Hausaufgabe oder so?)

Jein. Also nicht direkt.
Ich habe vorher Informatik zu studieren und programmiere deshalb ab und an in Java, Python und Swift, um meine Kenntnisse dahingehend zu verbessern.

Unser Informatiklehrer hat im Schulinternen Netzwerk einen Haufen Projektideen in einem Ordner gesammelt, für Leute die Interesse daran haben, so als Projektansätze.
Mastermind war einfach nur ein Beispiel. Hab mich dieses Wochenende an seine Bearbeitung gemacht.
Im Unterricht war das nie behandelt. Also es stammt aus der Schule aber ist keine Hausaufgabe. :wink:

Und es lief bis auf das Zählen der Roten und den Neustart auch recht gut.
Wenn ich mir manchmal so angucke, was meine ersten Supportthemen hier waren… (kann man die löschen? :smiley: )

Schöne Grüße
Lukas

Ein Problem hätte ich noch:
Manchmal setzt er nicht alles auf einmal zurück. Hier zum Beispiel ein neu gestartetes Spiel:

Und wenn ich dann aber einen der Buttons anklicke, zum Beispiel den Blauen da unten, dann ist alles weg und man kann normal spielen.
Quellcodetechnisch kann da nichts groß falsch sein.
Ist da irgendwas, was Java nicht leiden kann, was ich noch ändern sollte? Tritt auch nur sporadisch auf.

Das mit dem “Teilweise nicht richtig anzezeigt” hattest du schon angedeutet - es trat bei mir nicht auf, aber ein Kandidat wäre, dass man am Ende der “reset”-Methode vielleicht nochmal ein “repaint()” aufrufen müßte. Es könnte auch damit zusammenhängen, dass in der “Farbfeld”-Klasse “paint” überschrieben ist. Eigentlich sollte dort NICHT paint(…), sondern

protected void paintComponent(Graphics g)
{
    super.paintComponent(g);
    // Rest wie vorher in paint(...)
]

überschrieben sein (einer der vieeelen (“kleinen”?) Dinge, die man verbessern könnte ;-))

Hallöle,

ein repaint() hat es gebracht. Läuft nun. Bzw. der Fehler trat nicht mehr auf. Man kann ja leider nur beweisen, dass er auftritt und nicht, dass er nicht mehr auftritt, da letztes auch nach einer endlichen Anzahl an Versuchen bedeuten kann, dass es im Versuch danach doch wieder passiert. :wink:

Was das andere angeht:
So hab ich das in der Schule gelernt. Informatikunterricht muss ja auch nicht kompetent sein. :smiley:
Meinst Du, ich sollte das so hier machen:

		super.paintComponent(stift);
		switch (farbe) {
		case 0:
			stift.setColor(Color.yellow);
			break;
		case 1: 
			stift.setColor(Color.blue);
			break;
		case 2:
			stift.setColor(Color.red);
			break;
		case 3:
			stift.setColor(Color.green);
			break;
		case 4:
			stift.setColor(Color.pink);
			break;
		case 5:
			stift.setColor(Color.cyan);
			break;
		default:
			stift.setColor(Color.gray);
			break;
		}
		stift.fillRect(0,0,this.getWidth(),this.getHeight());
	}```

Gruß
Lukas

Ohne mir den Rest des Codes angesehen zu haben: dieser Abschnitt “stinkt” (im Sinne von bad smell).
Wenn farbe bereits vom Typ Color wäre, könntest du direkt stift.setColor(farbe) aufrufen und würdest dir das hässliche switch sparen.

@FranzFerdinand Ja @cmrudolph Der „int“ ist hier eine Zustandsbeschreibung, von einem von 7 Zuständen. Das mit „Color“ auf 256^3 Zustände zu erweitern, wäre nicht so günstig. Eine „enum“ könnte man aber durchaus in Betracht ziehen :wink:

private final static Color[] FARBEN = new Color[]{ Color.yellow, Color.blue, Color.red, Color.green, Color.pink, Color.cyan };
public void paintComponent(Graphics stift) {
  super.paintComponent(stift);
  if(farbe >=0 && farbe < FARBEN.length) {
    stift.setColor(FARBEN[farbe]);
  } else {
    stift.setColor(Color.gray);
  }
  stift.fillRect(0,0,this.getWidth(),this.getHeight());
}```

Dies wäre eine andere Möglichkeit so ein switch zu umgehen.

Wie man 's nimmt. Der int hat aber nicht nur 7, sondern 256^4 und damit 256x so viele Zustände wie Color :wink:
Ein Enum mit einer Eigenschaft „Color“ wäre aber sicherlich die deutlich bessere Alternative. Die Farbe als Teil des Steines zu betrachten ist denke ich legitim, denn das ist es, was in der realen Welt den Unterschied zwischen den Steinen ausmacht und somit nicht nur Teil der Darstellung.

War ein Tippfehler: Die Color hat auch 256^4 :stuck_out_tongue_winking_eye: (auch wenn man die Colors, bei denen Alpha=0 ist, nicht mehr unterscheiden kann :D)

Aus dem Bauch heraus, und bei pragmatisch-vordergründiger Trennung von View und Model, wäre eine „Farbe“ im Modell so gesehen unpassend, weil das ja (übertrieben abstrakt-tehoretisch) nur „ein Zeichen eines Alphabets“ ist, und die Frage, ob „1“ nun mit einem Grünen oder Gelben Rechteck dargestellt wird, Sache der View ist, aber… das führt wohl gerade etwas zu weit :wink:

Nein, das führt eigentlich nicht zu weit. Color hat im Model nichts zu suchen. Das gehört zwingendst in die View.

In Swing ist es java.awt.Color.RED
In SWT hat man sowas wie, Display.getCurrent().getSystemColor(SWT.COLOR_RED);
Sollte man dann zu einem HTML-Client übergehen, dann bekommt man #F00

Und dafür jedes mal das Model erweitern wird ungeschickt.

Aber YAGNI, zumindest hier. Zum Üben und Lernen wäre es allerdings gerade passend, sowas richtig zu trennen und einzuordnen.

Ich glaube, wenn das ganze auf SWT umgestellt werden soll, ist die Farbe das geringste Problem :wink: aber ansonsten ist das wohl nicht falsch.

Ok, eine Klasse aus dem awt-Paket zu nehmen ist im Modell wirklich nicht angebracht. Nichtsdestotrotz sehe ich die “Farbe” als so etwas essentielles bei der Modellierung eines Spielsteines, dass sie auf jeden Fall dazu gehört. Und das nicht nur in Form von “Farbe 1”, “Farbe 2”, …, sondern durchaus explizit ausmodelliert.
Dabei landet man dann wieder beim Enum und müsste diese wieder in einen zum GUI-Framework passenden Farbwert übersetzen. Dafür böte sich dann eine EnumMap an.