Zum Inhalt springen

Programm oder einfache KI zum automatischem Lösen meines Mastermind Spieles (Fortgeschritten);


Mirko134

Empfohlene Beiträge

Hallo Liebe Programmierfreunde, 

Ich habe ein Mastermind Spiel Programmiert, das man als Nutzer so einfach spielen kann oder auch durch (unter-) Klassen, erstellen und automatisch lösen lassen kann.

Ich habe mich selber bisher an ein paar verschieden Klassen versucht, die das Mastermind Rätsel ohne weitere Hilfe lösen können, jedoch brauchen diese noch relativ viele Versuche dafür, und es existieren Fälle, bei denen sie es auch gar nicht lösen können. Deshalb wollte ich euch mein Hauptprogramm zur Verfügung stellen, damit ihr wenn ihr Lust habt euch ebenfalls an so einer  einfachen "schein KI" zu versuchen.

Natürlich wenn jemand Bock hat und Erfahrung damit hat, kann versuchen eine "KI" zu gestalten, die sich selbst verbessert und von alleine den schnellstmöglichen weg zur Lösung findet... würde mich freuen zu sehen wie so etwas gemacht wird. 

Hier das Mastermind Interface (die Hauptklasse): 

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;

/**
 * Ein Programm, das beim aufruf des Konstruktors ein Mastermind Interface ertellt
 * 
 * @author (Mirko Feldheim) 
 * @version (25.03.2024)
 */
public class MastermindInterface extends JFrame {
    /*------public static------*/
    public static final int SPALTEN = 4;
    public static final int ZEILEN = 10;
    public static final String[] FARB_STRING = {"Red", "Yellow", "Green", "Cyan", "Blue", "Magenta"};
    public static final Color[] COLORS = new Color[]{Color.RED, Color.YELLOW, Color.GREEN,Color.CYAN, Color.BLUE, Color.MAGENTA};

    /* -----privat---- */
    private static int DURCHMESSER = 40; 
    // vom kreisdurchmesser hängt die komplette Spielfeldgroeße ab
    private JPanel mainPanel;
    private JPanel codePanel;
    private JPanel guessPanel;
    private JPanel colorPanel;
    private JButton checkButton;
    private JButton showButton;
    private JPanel checkingPanel;
    
    private int loesungsFeld[][]= new int[ZEILEN][SPALTEN];
    private Color[][] feldFarbe = new Color[ZEILEN][SPALTEN];
    private Color loesungsFarbe[] = new Color[SPALTEN];
    private Color vorschauFarbe[] = new Color[SPALTEN];
    private int versuch=0;
    private boolean geloest=false;
    private int richtig; 
    private int fastRichtig;
    private int falsch;

    private JComboBox<String>[] colorComboBoxes;

    public MastermindInterface() {
        loesungErstellen();
        init(); //erstellen des Interface
    }

    /**
     * Überprüft wie gut die geratenen/ uebergebenen Farben waren, und gibt die Bewertung als Array zurück
     * Zudem wird auch die Zeile und Bewertung eingezeichnet
     * @param Ein FarbArray, dass genau so lang wie die SPALTEN ist;
     * @return An Stelle 0: Anzahl der richtigen Steine ,Stelle 1: Anzahl der fast richtigen 
     */
    public int[] check(Color[] farben){
        Color[] farbenCut= new Color[SPALTEN];
        if(farben.length < SPALTEN){
            return null;
        }else if(farben. length >= SPALTEN){
            for(int i=0; i<SPALTEN; i++){
                farbenCut[i]=farben[i];
            }
        }
        //bis hier nur Sicherung, dass Nullpointerexception entstehen
        for(int i=0; i<SPALTEN; i++){
            feldFarbe[versuch][i]= farbenCut[i];
        }
        geloest=vergleichen(); // vergleichen sorgt dafür dass Richtige und fast richtige berechnet werden
        versuch++;
        repaint();
        final int[] anzahl =new int[]{richtig,fastRichtig};
        return anzahl;
    }
    
    /**
     * Zum auszuprobieren wie es aussieht wenn dir richtige loesung in check() uebergeben wird.
     * @return Gibt die Lösung als FarbArray aus 
     */
    public Color[] loesung(){
        return loesungsFarbe;
    }
    
    public int getVersuch(){
        return versuch;
    }
    
    public static Color getColorFromString(String colorName) {
        switch (colorName) {
            case "Red":
                return Color.RED;
            case "Yellow":
                return Color.YELLOW;
            case "Green":
                return Color.GREEN;
            case "Cyan":
                return Color.CYAN;
            case "Blue":
                return Color.BLUE;
            case "Magenta":
                return Color.MAGENTA;
            default:
                return null;
        }
    }
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(MastermindInterface::new);
    }
    /* Bis hier lesen - um die Klasse nutzen zu können (public)
     * --------------------------------------------------------------
     * Alles ab hier ist nur wichtig um zu vertehen wie die Klasse im inneren Funktioniert 
     */
    /**
     * Errechnet wie viele Punkte geanau richtig sind, wie viele and der Falsch stelle sind und wie viele garnicht drauf sind.
     * @ return Gibt true zurück
     */
    private boolean vergleichen(){
        richtig =0; 
        fastRichtig=0;
        falsch=0;
        boolean[] fertig= new boolean[SPALTEN];
        // hier werden erst die genau richtigen aus der weiteren bewertung herausgenommen
        for(int x=0; x<SPALTEN; x++){
            if(feldFarbe[versuch][x] == loesungsFarbe[x]){
                fertig[x]=true;
            }else{
                fertig[x]=false;
            }
        }
        //hier werden die Richtigen, fastRichtigen und falschen errechnet
        for(int i=0; i<SPALTEN; i++){
            if(feldFarbe[versuch][i] == loesungsFarbe[i]){
                richtig++;
            }else{
                boolean fast=false;
                for(int j=0; j<SPALTEN; j++){
                    if(fertig[j]){
                        //System.out.println(i +"|||"+j);
                    }else if((feldFarbe[versuch][i])== (loesungsFarbe[j])){
                        fast=true;
                        fertig[j]=true;
                        break;
                    }
                }
                if(fast){
                    fastRichtig++;
                }else{
                    falsch++;
                }
            }
        }
        //hier wird das Zeichenen der Schwarzen und weißen Bewertungspunkte vorbereitet
        for(int i =0; i<SPALTEN; i++){
            if(i<richtig){
                loesungsFeld[versuch][i]= 2;
            }else if(i<(richtig+fastRichtig)){
                loesungsFeld[versuch][i]= 1;
            }
        }
        if(richtig==SPALTEN){
            JOptionPane.showMessageDialog(MastermindInterface.this, "You won with " + (versuch+1) + "tries");
        }
        return (richtig == SPALTEN);
    }
    
    private void loesungErstellen(){
        for(int i=0; i<SPALTEN; i++){
            Random random = new Random();
            loesungsFarbe[i]= getColorFromString(FARB_STRING[random.nextInt(FARB_STRING.length)]);
        }
    }
    
    /**
     * Das Interface wird ertellt und alles darin befindliche Initialisiert 
     * (kann einfach ignoriert werden)
     */
    private void init(){
        setTitle("Mastermind");
        setSize((DURCHMESSER*3/2)*(SPALTEN +2), (DURCHMESSER*3/2)*(ZEILEN+3));
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        mainPanel = new JPanel(new BorderLayout());
        codePanel = new JPanel(new GridLayout(1, SPALTEN));
        guessPanel = new JPanel(new GridLayout(ZEILEN, SPALTEN));
        checkingPanel = new JPanel(new GridLayout(ZEILEN, SPALTEN));
        colorPanel = new JPanel(new GridLayout(1, SPALTEN + 1));
        checkButton = new JButton("Check");
        showButton= new JButton("Clear");

        colorComboBoxes = new JComboBox[4];
        //Erstellen der SPALTEN
        for (int i = 0; i < SPALTEN; i++) {
            final int ii =i;
            JPanel codeCircle = new JPanel() {
                    @Override
                    protected void paintComponent(Graphics g) {
                        super.paintComponent(g);
                        if(!geloest){
                            g.setColor(Color.BLACK);
                        }else{
                            g.setColor(loesungsFarbe[ii]);
                        }
                        g.fillOval(DURCHMESSER/4, DURCHMESSER/4, DURCHMESSER, DURCHMESSER);
                    }
                };
            codeCircle.setPreferredSize(new Dimension((DURCHMESSER*3)/2, (DURCHMESSER*3)/2));
            codeCircle.setBackground(Color.LIGHT_GRAY);
            codePanel.add(codeCircle);
        }

        for (int i = 0; i < ZEILEN; i++) {
            JPanel guessRow = new JPanel(new GridLayout(1, 4));
            for (int j = 0; j < SPALTEN; j++) {
                final int jj = j;
                final int ii = i;
                JPanel guessCircle = new JPanel() {
                        @Override
                        protected void paintComponent(Graphics g) {
                            super.paintComponent(g);
                            Color color = feldFarbe[ii][jj];
                            if (color != null) {
                                g.setColor(color);
                            }
                            g.fillOval(DURCHMESSER/4, DURCHMESSER/4, DURCHMESSER, DURCHMESSER);
                        }
                    };
                guessCircle.setPreferredSize(new Dimension((DURCHMESSER*3)/2,(DURCHMESSER*3)/2));
                guessCircle.setBackground(Color.LIGHT_GRAY);
                guessRow.add(guessCircle);
            }
            guessPanel.add(guessRow);
        }

        for (int i = 0; i < ZEILEN; i++) {
            JPanel solve = new JPanel(new GridLayout(1, SPALTEN));
            for (int j = 0; j < SPALTEN; j++) {
                final int jj = j;
                final int ii=i;
                loesungsFeld[i][j]=0;
                JPanel solveCircle = new JPanel() {
                        @Override
                        protected void paintComponent(Graphics g) {
                            super.paintComponent(g);
                            if(loesungsFeld[ii][jj]==2){
                                g.setColor(Color.BLACK);
                                g.fillOval(DURCHMESSER/8, DURCHMESSER/8, DURCHMESSER/2, DURCHMESSER/2);
                            }else if(loesungsFeld[ii][jj]==1){
                                g.setColor(Color.WHITE);
                                g.fillOval(DURCHMESSER/8, DURCHMESSER/8, DURCHMESSER/2, DURCHMESSER/2);
                            }else{
                                g.setColor(Color.BLACK);
                                g.drawOval(DURCHMESSER/8, DURCHMESSER/8, DURCHMESSER/2, DURCHMESSER/2); 
                            } 
                        }
                    };
                solveCircle.setPreferredSize(new 
                    Dimension((DURCHMESSER*3)/4,(DURCHMESSER*3)/4));
                solveCircle.setBackground(Color.LIGHT_GRAY);
                solve.add(solveCircle);
            }
            checkingPanel.add(solve);
        }

        for (int i = 0; i < SPALTEN; i++) {
            JComboBox<String> colorComboBox = new JComboBox<>(FARB_STRING);
            colorComboBoxes[i] = colorComboBox;
            colorPanel.add(colorComboBox);
        }

        checkButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    // Code to check the guess
                    for(int i=0; i<SPALTEN; i++){
                        String colorName = (String) colorComboBoxes[i].getSelectedItem();
                        feldFarbe[versuch][i]= getColorFromString(colorName);
                    }
                    geloest=vergleichen();
                    versuch++;
                    repaint();
                }
            });

        for(int i=0; i<SPALTEN; i++){
            final int ii=i;
            colorComboBoxes[i].addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        // Code update color
                        String colorName = (String) colorComboBoxes[ii].getSelectedItem();
                        colorComboBoxes[ii].setBackground(getColorFromString(colorName));
                        repaint();
                    }
                });
        }

        colorPanel.add(checkButton);

        mainPanel.add(codePanel, BorderLayout.NORTH);
        mainPanel.add(guessPanel, BorderLayout.CENTER);
        mainPanel.add(colorPanel, BorderLayout.SOUTH);
        mainPanel.add(checkingPanel, BorderLayout.EAST);

        mainPanel.setBackground(new Color(139, 69, 19));

        add(mainPanel);
        setVisible(true);
    }
}

Und hier ein (einfaches) Beispiel zum Lösen von mir:

import java.awt.Color;
import java.util.*;

/**
 * Ein Programm, das versucht innerhalb der vorgegebenen Versuche, die Lösung zu finden.
 * @author (Mirko Feldheim) 
 * @version (25.03.2024)
 */
public class MasterMind2 extends MastermindInterface
{
    // Instanzvariablen - ersetzen Sie das folgende Beispiel mit Ihren Variablen
    private int versuch;
    private Color[] auswahl;
    private Color[] richtigeFarben = new Color[SPALTEN];
    private int[] bewertung;
    private List<int[]> Bewertung = new ArrayList<>();
    private List<Color[]> Auswahl = new ArrayList<>();
    private Map<Color[],int[]> CombiBewertung = new HashMap<>();
    private int richtige=0;
    /**
     * Konstruktor für Objekte der Klasse MasterMind2
     */
    public MasterMind2()
    {
        auswahl= new Color[SPALTEN];
        bewertung = new int[2];
        // Alle Farben einzeln ausprobieren
        while((versuch=getVersuch())<COLORS.length-1){
            for(int i=0; i<SPALTEN; i++){
                auswahl[i]=COLORS[versuch];
            }
            auswerten();
            for(int i=0; i<(bewertung[0]+bewertung[1]); i++){
                richtigeFarben[richtige]=auswahl[richtige];
                richtige++;
            }
            if(richtige==SPALTEN){
                break;
            }
        }
        //Hier wird eingaespart, dass die letzte Farbe getestet werden muss
        if(richtige<SPALTEN){
            for(int i=richtige; i<SPALTEN; i++){
                richtigeFarben[i]=COLORS[COLORS.length-1];
            }
        }
        for(int i=0; i<auswahl.length; i++){
            auswahl[i]=richtigeFarben[i];
        }
        //die Richtige Reihenfolge für die Farben finden
        int counter=0;
        while(getVersuch()<ZEILEN && bewertung[0]<4){
            if(isPossible(auswahl)){
                auswerten();
            }
            mixUp();
        }
    }
    
    private void setToBestGuess(){
        auswahl=new Color[SPALTEN];
        int best=0;
        for(int z=0; z<Bewertung.size(); z++){
            if(bounty(z)>=best){
                best=bounty(z);
                for(int i=0; i<SPALTEN; i++){
                    auswahl[i]=Auswahl.get(z)[i];
                }
            }
        }
    }
    
    private int bounty(int nummer){
        return 2*(Bewertung.get(nummer)[0]) + 1*(Bewertung.get(nummer)[1]);
    }

    private void mixUp(){
        setToBestGuess();
        Random random= new Random();
        for(int i=0; i<MinimaleTauschversuche();i++){
            while(!tauschen(random.nextInt(SPALTEN), random.nextInt(SPALTEN))){
                //es wird so lange versucht zufällig zeichen zu tauschen, bis sich wirklich was verändert 
            }
        }
    }

    private boolean tauschen(int eins, int zwei){
        Color dasEine = auswahl[eins];
        Color dasAndere = auswahl[zwei];
        if(dasEine!= dasAndere){
            auswahl[zwei] = dasEine;
            auswahl[eins] = dasAndere;
            return true;
        }else{
            return false;
        }
    }
    
    private int MinimaleTauschversuche(){
        int fastRichtig=bewertung[1];
        return (fastRichtig/2)+(fastRichtig%2);
    }

    private boolean gleicheZahlen(int[] eins, int[] zwei){
        for(int i=0; i<eins.length; i++){
            if(eins[i]!=zwei[i]){
                return false;
            }
        }
        return true;
    }

    private boolean gleicheFarben(Color[] eins, Color[] zwei){
        for(int i=0; i<eins.length; i++){
            if(eins[i]!=zwei[i]){
                return false;
            }
        }
        return true;
    }

    private boolean isPossible(Color[] vermutung){
        boolean gleich=true;
        Color[] listenEintrag= new Color[SPALTEN];
        for(int a=0; a<Auswahl.size(); a++){
            listenEintrag=Auswahl.get(a);
            int[] eigenErgebnis = innerCheck(listenEintrag, vermutung);
            int[] combi = CombiBewertung.get(Auswahl.get(a));
            if(!gleicheZahlen(combi,eigenErgebnis)){
                gleich=false;
            }
        }
        return gleich;
    }

    /**
     * An erster Stelle das zu überprüfende, und an zweiter die vermeindliche lösung (die auswahl);
     * Dies ist essentiell, um zu unmögliche versuche zu verhindern
     */
    private int[] innerCheck(Color[] vergleich, Color[] loesung){
        int[] ausgabe=new int[2];
        int richtig=0;
        int fastRichtig=0;
        boolean[] fertig= new boolean[SPALTEN];
        // hier werden erst die genau richtigen aus der weiteren bewertung herausgenommen
        for(int x=0; x<fertig.length; x++){
            if(vergleich[x]==loesung[x]){
                fertig[x]=true;
                ++richtig;
            }else{
                fertig[x]=false;
            }
        }
        //hier werden die fastRichtigen und falschen evaluiert
        for(int v=0; v<vergleich.length; v++){
            if(!fertig[v]){
                boolean knapp=false;
                for(int l=0; l<loesung().length; l++){
                    if(!fertig[l]&& vergleich[v]==loesung[l] && !knapp){
                        knapp=true;
                    }
                }
                if(knapp){
                    ++fastRichtig;
                }
            }
        }
        ausgabe[0]=richtig;
        ausgabe[1]=fastRichtig;
        return ausgabe;
    }

    private void auswerten(){
        bewertung=check(auswahl);
        Bewertung.add(bewertung);
        Auswahl.add(auswahl);
        CombiBewertung.put(auswahl,bewertung);
        auswahl= new Color[SPALTEN];
        bewertung = new int[2];
        for(int i=0; i<auswahl.length; i++){
            auswahl[i]=Auswahl.get(Auswahl.size()-1)[i];
        }
        for(int i=0; i<bewertung.length; i++){
            bewertung[i] = Bewertung.get(Bewertung.size()-1)[i];
        }
        versuch=getVersuch();
    }
}

 

Falls ihr Ergänzungs-, Vereinfachungs- oder Verbesserungsvorschläge habt, gerne her damit und ansonsten viel Spaß und Erfolg dabei das Mastermind Rätsel zu lösen. 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Warum kein GitHub Link? 😁

Was mir aber aufgefallen ist, dass dein Switch-Case ewig viele Returns hat, seit Java 14 gibt es Switch-Case mit Rückgabewert.

Als nächstes die Umsetzung deiner "Farben".

Du benutzt schon eine JComboBox (Zeile 275), dann könntest du da auch direkt Enums als ComboBoxModel hinterlegen und sparst dir den Switch-Case gänzlich.

Das sind so die 2 Sachen, die mir beim ersten, schnellen drüberfliegen in Notepad direkt ins Auge gesprungen sind.

Bearbeitet von Barandorias
Link zu diesem Kommentar
Auf anderen Seiten teilen

Inwiefern ist die Spiellogik von dem JFrame abhängig? Meinst du, weil die Eingabe der Antworten/ Versuche über die JButtons funktioniert, dafür gibt es ja die Methode public int[] check(Color[Spalten]). Oder meinst du, die Zeilen und Spaltenanzahl, die ist ja variabel einstellbar (zumindest nachdem ich das Programm an ein paar kleinen Stellen noch korrigiert habe, wo ich was falsch gemacht hatte) funktioniert. Also wenn du mir konkret eine Beispiel Stelle nennen könntest, an der ein Unittest nicht möglich ist, dann gerne her damit, ich versuche es zu ändern ;)

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 7 Stunden schrieb Barandorias:

Warum kein GitHub Link? 😁

Was mir aber aufgefallen ist, dass dein Switch-Case ewig viele Returns hat, seit Java 14 gibt es Switch-Case mit Rückgabewert.

Als nächstes die Umsetzung deiner "Farben".

Du benutzt schon eine JComboBox (Zeile 275), dann könntest du da auch direkt Enums als ComboBoxModel hinterlegen und sparst dir den Switch-Case gänzlich.

Das sind so die 2 Sachen, die mir beim ersten, schnellen drüberfliegen in Notepad direkt ins Auge gesprungen sind.

Hab nun ein Github Repository erstellt, hier der Link dazu, da findest du aber auch noch andere Spiele, die ich aus Spaß nachprogrammiert habe. :D

https://gitlab.informatik.uni-bremen.de/mirko5/meine_spiele.git

Habe zudem die StringToColor() Methode komplett entfernt/ bin sie umgangen, da dies sowieso überflüssig war, zudem habe ich einen kleinen Fehler korrigiert, weswegen wenn die Spalten und Zeilenanzahl verändert wurden das Programm nicht mehr geladen hat.

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 3 Stunden schrieb Mirko134:

Inwiefern ist die Spiellogik von dem JFrame abhängig?

 

public class MastermindInterface extends JFrame {
  // ...
}

Deine Spiellogik steckt in einem JFrame, da die Klasse von einem JFrame erbt. Also ist auch deine Spiellogik von einem JFrame (und von anderen UI-Elementen) abhängig. Dadurch ist es nun nicht mehr möglich, mittels Unittests die Spiellogik zu testen. Die Spiellogik sollte aber frei von UI-Elementen sein. Du kannst also nicht per Unittests testen, ob dein Spiel auch wirklich funktioniert. Du musst es also immer wieder starten und alle Testszenarien per Hand durchgehen, was mit der Zeit sehr aufwendig wird.

Es sollte daher eine Mastermind-Klasse geben, mit der du das komplette Spiel abbildest und die UI verwendet dann einfach nur diese Klasse und beinhaltet keinerlei Spiellogik.

Bearbeitet von Whiz-zarD
Link zu diesem Kommentar
Auf anderen Seiten teilen

Dein Kommentar

Du kannst jetzt schreiben und Dich später registrieren. Wenn Du ein Konto hast, melde Dich jetzt an, um unter Deinem Benutzernamen zu schreiben.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung wiederherstellen

  Nur 75 Emojis sind erlaubt.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Editor leeren

×   Du kannst Bilder nicht direkt einfügen. Lade Bilder hoch oder lade sie von einer URL.

Fachinformatiker.de, 2024 by SE Internet Services

fidelogo_small.png

Schicke uns eine Nachricht!

Fachinformatiker.de ist die größte IT-Community
rund um Ausbildung, Job, Weiterbildung für IT-Fachkräfte.

Fachinformatiker.de App

Download on the App Store
Get it on Google Play

Kontakt

Hier werben?
Oder sende eine E-Mail an

Social media u. feeds

Jobboard für Fachinformatiker und IT-Fachkräfte

×
×
  • Neu erstellen...