From 09ad9aa452c92320d40efddf249cdeb68414b3ba Mon Sep 17 00:00:00 2001 From: Matias <3020772@stud.hs-mannheim.de> Date: Tue, 11 Feb 2025 14:57:44 +0100 Subject: [PATCH] =?UTF-8?q?Kleine=20=C3=84nderungen=20an=20der=20GUI=20+?= =?UTF-8?q?=20Implementierung=20der=20AI=20mittels=20Minmax=20Algorithmus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Semesterprojekt/src/Anzeige/GUI.java | 219 ++++++++------- Semesterprojekt/src/Logik/TicTacToe.java | 335 ++++++++++++++++------- Semesterprojekt/src/icon.jpg | Bin 0 -> 7192 bytes 3 files changed, 354 insertions(+), 200 deletions(-) create mode 100644 Semesterprojekt/src/icon.jpg diff --git a/Semesterprojekt/src/Anzeige/GUI.java b/Semesterprojekt/src/Anzeige/GUI.java index 50ab5d3..46db7ae 100644 --- a/Semesterprojekt/src/Anzeige/GUI.java +++ b/Semesterprojekt/src/Anzeige/GUI.java @@ -9,7 +9,9 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; +import java.util.Random; +import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; @@ -17,165 +19,188 @@ import javax.swing.SwingUtilities; import Logik.TicTacToe; -public class GUI extends JFrame{ - +public class GUI extends JFrame { + private TicTacToe ttt; private ArrayList buttons = new ArrayList<>(); private JButton neuesSpiel = new JButton(); private JButton konsole = new JButton(); - private boolean spielEnde = false; + private JButton modus = new JButton(); public GUI(TicTacToe ttt) { - + this.ttt = ttt; - + this.setTitle("TicTacToe"); - this.setSize(500,600); + this.setSize(500, 600); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setLocationRelativeTo(null); this.setLayout(new BorderLayout()); - + + ImageIcon img = new ImageIcon(getClass().getResource("/icon.jpg")); + if (img.getIconWidth() == -1) { + System.out.println("Bild konnte nicht geladen werden!"); + } else { + this.setIconImage(img.getImage()); + } + fuelleArrayList(); - + JPanel jp2 = new JPanel(); - jp2.setLayout(new BorderLayout()); - jp2.setPreferredSize(new Dimension(500,150)); - + jp2.setLayout(new GridLayout(3,1)); + jp2.setPreferredSize(new Dimension(500, 150)); + neuesSpiel.setFocusPainted(false); - neuesSpiel.setFont(new Font("Arial",Font.PLAIN, 30)); - neuesSpiel.setBackground(new Color(65,65,65)); + neuesSpiel.setFont(new Font("Arial", Font.PLAIN, 30)); + neuesSpiel.setBackground(new Color(65, 65, 65)); neuesSpiel.setForeground(Color.WHITE); neuesSpiel.setText("Neues Spiel beginnnen?"); - neuesSpiel.setPreferredSize(new Dimension(500,75)); + neuesSpiel.setPreferredSize(new Dimension(500, 75)); neuesSpiel.setBorderPainted(false); neuesSpiel.addActionListener(new ActionListener() { - - @Override - public void actionPerformed(ActionEvent e) { - - ttt.setCounter(0); - ttt.setAktuellerSpieler('X'); - spielEnde = false; - ttt.fuelleTreeMap(); - - - for(JButton b: buttons) { - - b.setForeground(Color.WHITE); - b.setText(""); - - } - + @Override + public void actionPerformed(ActionEvent e) { + + ttt.resetGame(); + + resetGui(); + revalidate(); repaint(); - } + } }); + + jp2.add(neuesSpiel); + + modus.setFocusPainted(false); + modus.setFont(new Font("Arial", Font.PLAIN, 30)); + modus.setBackground(new Color(90, 90, 90)); + modus.setForeground(Color.WHITE); - jp2.add(neuesSpiel, BorderLayout.NORTH); + if(ttt.getModus() == 1) modus.setText("Modus wechseln? (Player vs. Player aktiv)"); + else if(ttt.getModus() == 2) modus.setText("Modus wechseln? (Player vs. AI aktiv)"); + + modus.setPreferredSize(new Dimension(500, 75)); + modus.setBorderPainted(false); + modus.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + + if (ttt.getModus() == 2) { + modus.setText("Modus wechseln? (Player vs. Player aktiv)"); + ttt.setModus(1); + } else if (ttt.getModus() == 1) { + modus.setText("Modus wechseln? (Player vs. AI aktiv)"); + ttt.setModus(2); + } + + for (JButton b : buttons) { + + b.setForeground(Color.WHITE); + b.setText(""); + + } + + ttt.resetGame(); + + revalidate(); + repaint(); + } + }); + + jp2.add(modus); konsole.setFocusPainted(false); - konsole.setFont(new Font("Arial",Font.PLAIN, 30)); - konsole.setBackground(new Color(90,90,90)); + konsole.setFont(new Font("Arial", Font.PLAIN, 30)); + konsole.setBackground(new Color(65, 65, 65)); konsole.setForeground(Color.WHITE); konsole.setText("Zurück zur Konsole?"); - konsole.setPreferredSize(new Dimension(500,75)); + konsole.setPreferredSize(new Dimension(500, 75)); konsole.setBorderPainted(false); konsole.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - + try { - + dispose(); ttt.ermittleEingabeAnfang(); - + } catch (Exception e1) { - + System.out.println("Eingabe von GUI zur Konsole konnte nicht aufgerufen werden"); } } - + }); - - jp2.add(konsole, BorderLayout.SOUTH); - - + + jp2.add(konsole); + this.add(jp2, BorderLayout.SOUTH); this.setVisible(true); - + } - + private void fuelleArrayList() { - - JPanel jp = new JPanel(new GridLayout(3,3)); - - for(int i = 0; i < 9; i++) { - + + JPanel jp = new JPanel(new GridLayout(3, 3)); + + for (int i = 0; i < 9; i++) { + JButton b = new JButton(); - - //System.out.println("Button "+i+" wurde erstellt."); - + b.setFocusPainted(false); - b.setFont(new Font("Arial",Font.PLAIN, 70)); - - if(i%2 == 0) { - b.setBackground(new Color(90,90,90)); - - }else { - b.setBackground(new Color(65,65,65)); + b.setFont(new Font("Arial", Font.PLAIN, 70)); + + if (i % 2 == 0) { + b.setBackground(new Color(90, 90, 90)); + + } else { + b.setBackground(new Color(65, 65, 65)); } - + b.setForeground(Color.WHITE); b.setBorderPainted(false); - + b.addActionListener(new ActionListener() { - - @Override - public void actionPerformed(ActionEvent e) { - SwingUtilities.invokeLater(() -> { - - actionListenerInhalt(b); - - }); - } + + @Override + public void actionPerformed(ActionEvent e) { + SwingUtilities.invokeLater(() -> { + + ttt.actionListenerInhalt(b); + //System.out.println("Button "+buttons.indexOf(b)+" wurde gedrückt!"); + + }); + } }); - + b.setVisible(true); - + buttons.add(b); jp.add(b); this.add(jp); } } - private void actionListenerInhalt(JButton b) { + public void resetGui(){ - int index = buttons.indexOf(b); - if (!ttt.pruefeBelegtesFeld(index) && !spielEnde) { - ttt.belegteFelder.put(index, ttt.getAktuellerSpieler()); - b.setText("" + ttt.getAktuellerSpieler()); + for (JButton b : buttons) { - if (ttt.getCounter() >= 4 && ttt.pruefeSieg()) { - spielEnde = true; - int[] gewinnerIndexe = ttt.getGewinnerIndexe(); + b.setForeground(Color.WHITE); + b.setText(""); - for (int i = 0; i < 3; i++) { - buttons.get(gewinnerIndexe[i]).setForeground(Color.RED); - buttons.get(gewinnerIndexe[i]).setText("" + ttt.getAktuellerSpieler()); - } - } + } + } - ttt.setCounter(ttt.getCounter() + 1); + public ArrayList getButtons() { + return buttons; + } - if (ttt.getAktuellerSpieler() == 'X') { - ttt.setAktuellerSpieler('O'); - } else { - ttt.setAktuellerSpieler('X'); - } - - repaint(); - } + public void setButtons(ArrayList buttons) { + this.buttons = buttons; } } diff --git a/Semesterprojekt/src/Logik/TicTacToe.java b/Semesterprojekt/src/Logik/TicTacToe.java index fdeb311..6dc4e04 100644 --- a/Semesterprojekt/src/Logik/TicTacToe.java +++ b/Semesterprojekt/src/Logik/TicTacToe.java @@ -1,73 +1,115 @@ package Logik; +import java.awt.Color; +import java.util.ArrayList; +import java.util.InputMismatchException; +import java.util.Random; import java.util.Scanner; import java.util.TreeMap; +import javax.swing.JButton; + import Anzeige.GUI; public class TicTacToe { - - public TreeMap belegteFelder = new TreeMap<>(); - - private int counter, ausgewaehltesFeld; + private GUI gui; - private char aktuellerSpieler; + + private TreeMap belegteFelder = new TreeMap<>(); private int[] gewinnerIndexe = new int[3]; - + + private int counter, ausgewaehltesFeld, modus; + private char aktuellerSpieler; + private boolean aiIstDran; + public TicTacToe() throws Exception { - + ermittleEingabeAnfang(); } - + public void ermittleEingabeAnfang() throws Exception { - - //setzt praktisch werte zurück - fuelleTreeMap(); - aktuellerSpieler = 'X'; - counter = 0; - + + resetGame(); + Scanner s = new Scanner(System.in); - System.out.println("\nWollen Sie mit einer GUI('gui' eingeben) oder mit der Konsole('konsole' eingeben) spielen?\nZum beenden einfach 'ende' eingeben."); + System.out.println( + "\nWollen Sie mit einer GUI('gui' eingeben) oder mit der Konsole('konsole' eingeben) spielen?\nZum beenden einfach 'ende' eingeben."); System.out.print(">>"); String befehl = s.nextLine(); - + if (befehl.equalsIgnoreCase("gui")) { - System.out.println("Um ein Feld für ein Zug auszuwählen muss dieses Feld angeklickt werden..."); + ermittleModus(); - gui = new GUI(this); + if (modus == 1 || modus == 2) { + + if (modus == 2) + System.out.println("Sie sind X die AI ist O."); + System.out.println("Um ein Feld für ein Zug auszuwählen muss dieses Feld angeklickt werden..."); + + gui = new GUI(this); + + } } else if (befehl.equalsIgnoreCase("konsole")) { - + + ermittleModus(); + + System.out.println( + "Die Felder sind von 0 bis 8 durchnummerriert.\nUm ein Feld für ein Zug auszuwählen muss die Zahl des Feldes eingegeben werden..."); gibAktuellenStandAus(); - - System.out.println("Die Felder sind von 0 bis 8 durchnummerriert.\nUm ein Feld für ein Zug auszuwählen muss die Zahl des Feldes eingegeben werden..."); for (int i = 0; i < 9; i++) { erfasseEingabe(); gibAktuellenStandAus(); - - if (i >= 4 && pruefeSieg()) break; - if (aktuellerSpieler == 'X')aktuellerSpieler = 'O'; - else aktuellerSpieler = 'X'; + if (i >= 4 && werHatGewonnen(belegteFelder) != null) + break; + + wechselAktuellerSpieler(); } + if (werHatGewonnen(belegteFelder) == 'D') + System.out.println("Unentschieden!"); + else + System.out.println(aktuellerSpieler + " hat gewonnen!"); + ermittleEingabeAnfang(); - - }else if(befehl.equalsIgnoreCase("ende")) { + + } else if (befehl.equalsIgnoreCase("ende")) { System.out.println("Spiel wird beendet."); System.exit(0); - }else { + } else { System.out.println("Eingabe ungültig!\nVersuchen Sie es erneut."); ermittleEingabeAnfang(); } } - public void fuelleTreeMap() { + private void ermittleModus() { + + Scanner s = new Scanner(System.in); + + System.out.println( + "Geben Sie die jeweilige Zahl für den Spielmodus ein: (Player vs. Player -> 1), (Player vs. AI -> 2)"); + System.out.print(">>"); + + try { + modus = s.nextInt(); + + if (modus < 1 || modus > 3) { + throw new InputMismatchException(); + } + } catch (InputMismatchException e) { + System.out.println("Es wurde keine gültige Eingabe getätigt!"); + ermittleModus(); + } + + } + + private void fuelleTreeMap() { for (int i = 0; i < 9; i++) { belegteFelder.put(i, (char) (48 + i)); } @@ -76,8 +118,9 @@ public class TicTacToe { private void gibAktuellenStandAus() { System.out.println("\n-------------"); - for (int i = 0; i < 9; i+=3) { - System.out.printf("|%2s |%2s |%2s |", belegteFelder.get(i), belegteFelder.get(i+1), belegteFelder.get(i+2)); + for (int i = 0; i < 9; i += 3) { + System.out.printf("|%2s |%2s |%2s |", belegteFelder.get(i), belegteFelder.get(i + 1), + belegteFelder.get(i + 2)); System.out.println("\n-------------"); } System.out.println(); @@ -90,103 +133,189 @@ public class TicTacToe { System.out.print("\nSpieler " + aktuellerSpieler + " ist am Zug!\nGeben Sie eine Feldnummer ein:\n>>"); try { - ausgewaehltesFeld = s.nextInt(); + + if (modus == 1 || modus == 2 && aktuellerSpieler == 'X') + + ausgewaehltesFeld = s.nextInt(); + + if (modus == 2 && aiIstDran || modus == 3) { + + ausgewaehltesFeld = makeTurn(); + + } + System.out.println("Die Eingabe: " + ausgewaehltesFeld); - if(pruefeBelegtesFeld(ausgewaehltesFeld)) throw new Exception(); + if (pruefeBelegtesFeld(ausgewaehltesFeld)) + throw new Exception(); + } catch (Exception e) { System.out.println("Es wurde keine passende Eingabe getätigt,\nversuchen Sie es erneut..."); - erfasseEingabe(); + if (modus == 1 || modus == 2 && aktuellerSpieler == 'X') + erfasseEingabe(); } } - - public synchronized boolean pruefeBelegtesFeld(int index) { - - System.out.println("Zu pruefender Index: "+index); + + private int makeTurn() { + int bestMove = -1; + int bestScore = Integer.MIN_VALUE; // Maximierung für AI (O) + + for (int i = 0; i < 9; i++) { + if (belegteFelder.get(i) != 'X' && belegteFelder.get(i) != 'O') { + belegteFelder.put(i, 'O'); // Temporär setzen + int moveScore = minimax(new TreeMap<>(belegteFelder), false); + belegteFelder.put(i, (char) ('0' + i)); // Zug zurücksetzen + + if (moveScore > bestScore) { + bestScore = moveScore; + bestMove = i; + } + } + } + return bestMove; + } + + private int minimax(TreeMap board, boolean isMaximizing) { + + Character winner = werHatGewonnen(board); + + if (winner != null) { + if (winner == 'X') + return -1; + if (winner == 'O') + return 1; + if (winner == 'D') + return 0; + } + + if (isMaximizing) { + int bestScore = Integer.MIN_VALUE; + for (int i = 0; i < 9; i++) { + if (board.get(i) != 'X' && board.get(i) != 'O') { + board.put(i, 'O'); + int score = minimax(board, false); + board.put(i, (char) ('0' + i)); // Zug zurücksetzen + bestScore = Math.max(score, bestScore); + } + } + return bestScore; + + } else { + int bestScore = Integer.MAX_VALUE; + for (int i = 0; i < 9; i++) { + if (board.get(i) != 'X' && board.get(i) != 'O') { + board.put(i, 'X'); + int score = minimax(board, true); + board.put(i, (char) ('0' + i)); // Zug zurücksetzen + bestScore = Math.min(score, bestScore); + } + } + return bestScore; + } + } + + private boolean pruefeBelegtesFeld(int index) { + + // System.out.println("Zu pruefender Index: " + index+"| Inhalt: + // "+belegteFelder.get(index)); if (belegteFelder.get(index) == 'X' || belegteFelder.get(index) == 'O') { System.out.println("Feld " + index + " schon belegt, machen Sie eine erneute Eingabe..."); return true; + } else { + belegteFelder.put(index, aktuellerSpieler); } - - belegteFelder.put(index, aktuellerSpieler); + + System.out.println("Feld " + index + " wurde mit " + aktuellerSpieler + " belegt"); return false; - + } - public boolean pruefeSieg() { + private Character werHatGewonnen(TreeMap board) { - if ((belegteFelder.get(0) == aktuellerSpieler) && (belegteFelder.get(1) == aktuellerSpieler) && (belegteFelder.get(2) == aktuellerSpieler)) { - System.out.println("Spieler " + aktuellerSpieler + " hat drei '" + aktuellerSpieler+ "' in der ersten Waagerechte und hat somit gewonnen!"); - gewinnerIndexe[0] = 0; - gewinnerIndexe[1] = 1; - gewinnerIndexe[2] = 2; - return true; - } else if ((belegteFelder.get(3) == aktuellerSpieler) && (belegteFelder.get(4) == aktuellerSpieler) && (belegteFelder.get(5) == aktuellerSpieler)) { - System.out.println("Spieler " + aktuellerSpieler + " hat drei '" + aktuellerSpieler+ "' in der zweiten Waagerechte und hat somit gewonnen!"); - gewinnerIndexe[0] = 3; - gewinnerIndexe[1] = 4; - gewinnerIndexe[2] = 5; - return true; - } else if ((belegteFelder.get(6) == aktuellerSpieler) && (belegteFelder.get(7) == aktuellerSpieler) && (belegteFelder.get(8) == aktuellerSpieler)) { - System.out.println("Spieler " + aktuellerSpieler + " hat drei '" + aktuellerSpieler+ "' in der dritten Waagerechte und hat somit gewonnen!"); - gewinnerIndexe[0] = 6; - gewinnerIndexe[1] = 7; - gewinnerIndexe[2] = 8; - return true; - } else if ((belegteFelder.get(0) == aktuellerSpieler) && (belegteFelder.get(3) == aktuellerSpieler) && (belegteFelder.get(6) == aktuellerSpieler)) { - System.out.println("Spieler " + aktuellerSpieler + " hat drei '" + aktuellerSpieler+ "' in der ersten Senkrechte und hat somit gewonnen!"); - gewinnerIndexe[0] = 0; - gewinnerIndexe[1] = 3; - gewinnerIndexe[2] = 6; - return true; - } else if ((belegteFelder.get(1) == aktuellerSpieler) && (belegteFelder.get(4) == aktuellerSpieler) && (belegteFelder.get(7) == aktuellerSpieler)) { - System.out.println("Spieler " + aktuellerSpieler + " hat drei '" + aktuellerSpieler+ "' in der zweiten Senkrechte und hat somit gewonnen!"); - gewinnerIndexe[0] = 1; - gewinnerIndexe[1] = 4; - gewinnerIndexe[2] = 7; - return true; - } else if ((belegteFelder.get(2) == aktuellerSpieler) && (belegteFelder.get(5) == aktuellerSpieler) && (belegteFelder.get(8) == aktuellerSpieler)) { - System.out.println("Spieler " + aktuellerSpieler + " hat drei '" + aktuellerSpieler+ "' in der dritten Senkrechte und hat somit gewonnen!"); - gewinnerIndexe[0] = 2; - gewinnerIndexe[1] = 5; - gewinnerIndexe[2] = 8; - return true; - } else if ((belegteFelder.get(0) == aktuellerSpieler) && (belegteFelder.get(4) == aktuellerSpieler) && (belegteFelder.get(8) == aktuellerSpieler)) { - System.out.println("Spieler " + aktuellerSpieler + " hat drei '" + aktuellerSpieler+ "' in der ersten Diagonale und hat somit gewonnen!"); - gewinnerIndexe[0] = 0; - gewinnerIndexe[1] = 4; - gewinnerIndexe[2] = 8; - return true; - } else if ((belegteFelder.get(2) == aktuellerSpieler) && (belegteFelder.get(4) == aktuellerSpieler) && (belegteFelder.get(6) == aktuellerSpieler)) { - System.out.println("Spieler " + aktuellerSpieler + " hat drei '" + aktuellerSpieler+ "' in der zweiten Diagonale und hat somit gewonnen!"); - gewinnerIndexe[0] = 2; - gewinnerIndexe[1] = 4; - gewinnerIndexe[2] = 6; - return true; + int[][] gewinnKombinationen = { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 7, 8 }, // Reihen + { 0, 3, 6 }, { 1, 4, 7 }, { 2, 5, 8 }, // Spalten + { 0, 4, 8 }, { 2, 4, 6 } // Diagonalen + }; + + for (int[] kombi : gewinnKombinationen) { + if (board.get(kombi[0]) == board.get(kombi[1]) && board.get(kombi[1]) == board.get(kombi[2]) + && (board.get(kombi[0]) == 'X' || board.get(kombi[0]) == 'O')) { + gewinnerIndexe = kombi; + return board.get(kombi[0]); // Gibt 'X' oder 'O' zurück + } } - return false; + + // Prüfen, ob noch leere Felder existieren + for (int i = 0; i < 9; i++) { + if (board.get(i) != 'X' && board.get(i) != 'O') { + return null; // Es gibt noch leere Felder → Spiel geht weiter + } + } + + return 'D'; } - public void setAktuellerSpieler(char aktuellerSpieler) { - this.aktuellerSpieler = aktuellerSpieler; + public void actionListenerInhalt(JButton b) { + + int index = gui.getButtons().indexOf(b); + + if (werHatGewonnen(belegteFelder) == null && !pruefeBelegtesFeld(index)) { + + b.setText("" + aktuellerSpieler); + + if (counter >= 4 && werHatGewonnen(belegteFelder) != null) { + + if (werHatGewonnen(belegteFelder) != 'D') { + for (int i = 0; i < 3; i++) { + gui.getButtons().get(gewinnerIndexe[i]).setForeground(Color.RED); + gui.getButtons().get(gewinnerIndexe[i]).setText("" + aktuellerSpieler); + } + } + + return; + + } + + counter++; + wechselAktuellerSpieler(); + + if (modus == 2 && aiIstDran) { + actionListenerInhalt(gui.getButtons().get(makeTurn())); + } + } } - public char getAktuellerSpieler() { - return aktuellerSpieler; + private void wechselAktuellerSpieler() { + + if (aktuellerSpieler == 'X') { + aktuellerSpieler = 'O'; + if (modus == 2) + aiIstDran = true; + } else { + aktuellerSpieler = 'X'; + if (modus == 2) + aiIstDran = false; + } } - public TreeMap getBelegteFelder() { - return belegteFelder; + public void resetGame() { + + counter = 0; + aktuellerSpieler = 'X'; + aiIstDran = false; + + fuelleTreeMap(); + } - public int[] getGewinnerIndexe() { - return gewinnerIndexe; + public int getModus() { + return modus; } - public int getCounter() { - return counter; + public void setModus(int modus) { + this.modus = modus; } - public void setCounter(int counter) { - this.counter = counter; + public boolean isAiIstDran() { + return aiIstDran; } } diff --git a/Semesterprojekt/src/icon.jpg b/Semesterprojekt/src/icon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..27beb7130941e972a3202eaf95073eaf10bbd14e GIT binary patch literal 7192 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 TrT3@DAX$!9K}~|k literal 0 HcmV?d00001