Merge remote-tracking branch 'origin/Main' into JUnit-Tests

GitBranch
Justin 2025-06-24 00:51:33 +02:00
commit 5770d13cdc
8 changed files with 3714 additions and 40 deletions

File diff suppressed because it is too large Load Diff

View File

@ -122,14 +122,89 @@ public class GameController {
// 3. Board neu zeichnen // 3. Board neu zeichnen
updateGuiBoard(); updateGuiBoard();
}); });
// --- AUFGEBEN-BUTTON ---
gui.getResignButton().addActionListener(e -> {
if (gameOver) return;
int answer = javax.swing.JOptionPane.showConfirmDialog(
null,
"Willst du wirklich aufgeben?",
"Aufgeben",
javax.swing.JOptionPane.YES_NO_OPTION
);
if (answer == javax.swing.JOptionPane.YES_OPTION) {
gameOver = true;
String winner = engine.getCurrentPlayer().equals("WHITE") ? "SCHWARZ" : "WEIß";
gui.displayMessage(winner + " gewinnt durch Aufgabe!");
engine.getWhiteTimer().stop();
engine.getBlackTimer().stop();
askForRestart();
}
});
// --- PATT-/REMIS-BUTTON ---
gui.getDrawButton().addActionListener(e -> {
if (gameOver) return;
int answer = javax.swing.JOptionPane.showConfirmDialog(
null,
"Remis anbieten? (Das Spiel endet sofort unentschieden)",
"Remis",
javax.swing.JOptionPane.YES_NO_OPTION
);
if (answer == javax.swing.JOptionPane.YES_OPTION) {
gameOver = true;
gui.displayMessage("Remis! (durch Einigung)");
engine.getWhiteTimer().stop();
engine.getBlackTimer().stop();
askForRestart();
}
});
gui.getUndoButton().addActionListener(e -> {
// Wer ist am Zug? (Das ist der, der gefragt wird)
String currentPlayer = engine.getCurrentPlayer(); // "WHITE" oder "BLACK"
// Wer möchte zurücknehmen? (Das ist der, der NICHT am Zug ist)
String currentName = currentPlayer.equals("WHITE") ? "Weiß" : "Schwarz";
String previousName = currentPlayer.equals("WHITE") ? "Schwarz" : "Weiß";
int answer = javax.swing.JOptionPane.showConfirmDialog(
null,
currentName + " ist am Zug. " +
previousName + " möchte seinen letzten Zug zurücknehmen.\n" +
currentName + ", erlaubst du das?",
"Zug zurücknehmen?",
javax.swing.JOptionPane.YES_NO_OPTION
);
if (answer == javax.swing.JOptionPane.YES_OPTION) {
engine.undoLastMove();
updateGuiBoard();
gui.updateMoveList(engine.getMoveListStringsGrouped());
gui.setOpeningLabel(engine.getOpeningName());
gui.displayMessage("Der letzte Zug wurde zurückgenommen.");
} else if (answer == javax.swing.JOptionPane.NO_OPTION) {
gui.displayMessage("Das Zurücknehmen wurde abgelehnt.");
}
});
} }
private boolean isAtLatestMove() {
return engine.getCurrentMoveIndex() == engine.getMoveListSize();
}
private void handleClick(int guiRow, int guiCol) { private void handleClick(int guiRow, int guiCol) {
if (gameOver) return; if (gameOver) return;
if (!isAtLatestMove()) {
gui.displayMessage("Du bist im Zug-Archiv und kannst keine Figuren bewegen. Klick auf '>|', um zum letzten Zug zu gehen.");
return;
}
int modelRow = flipRow(guiRow); int modelRow = flipRow(guiRow);
int modelCol = flipCol(guiCol); int modelCol = flipCol(guiCol);
@ -226,6 +301,7 @@ public class GameController {
public void updateGuiBoard() { public void updateGuiBoard() {
BoardDTO board = engine.getBoardAsDTO(); BoardDTO board = engine.getBoardAsDTO();
gui.updateBoard(board); gui.updateBoard(board);
gui.setOpeningLabel(engine.getOpeningName());
} }
private void switchTimers() { private void switchTimers() {

View File

@ -31,11 +31,12 @@ public class ChessEngine {
private Timer whiteTimer; private Timer whiteTimer;
private Timer blackTimer; private Timer blackTimer;
private Opening detectedOpening = null;
public ChessEngine() { public ChessEngine() {
logging(); logging();
board = new Board(); board = new Board();
} }
public boolean move(MoveDTO move) { public boolean move(MoveDTO move) {
String from = "" + (char)('A' + move.getFromCol()) + (8 - move.getFromRow()); String from = "" + (char)('A' + move.getFromCol()) + (8 - move.getFromRow());
String to = "" + (char)('A' + move.getToCol()) + (8 - move.getToRow()); String to = "" + (char)('A' + move.getToCol()) + (8 - move.getToRow());
@ -43,19 +44,44 @@ public class ChessEngine {
if (board.legalMoves().contains(libMove)) { if (board.legalMoves().contains(libMove)) {
board.doMove(libMove); board.doMove(libMove);
//Replay? Dann abschneiden // Replay? Dann abschneiden
if (currentMoveIndex < moves.size()) { if (currentMoveIndex < moves.size()) {
logger.info("Replay-Modus: Züge nach " + currentMoveIndex + " werden entfernt."); logger.info("Replay-Modus: Züge nach " + currentMoveIndex + " werden entfernt.");
moves = new ArrayList<>(moves.subList(0, currentMoveIndex)); moves = new ArrayList<>(moves.subList(0, currentMoveIndex));
} }
moves.add(libMove); moves.add(libMove);
currentMoveIndex++; currentMoveIndex++;
logger.info("Zug erfolgreich durchgeführt: " + libMove); logger.info("Zug erfolgreich durchgeführt: " + libMove);
// Opening-Erkennung nach jedem erfolgreichen Zug
String playedMovesUci = movesToUciString(moves);
detectedOpening = Opening.detect(playedMovesUci);
if (detectedOpening != null) {
logger.info("Aktuelles Opening erkannt: " + detectedOpening.getEco() + " - " + detectedOpening.getName());
// Optional: Speichere das Opening in ein Feld, falls benötigt
}
return true; return true;
} }
logger.warning("Ungültiger Zug: " + libMove); logger.warning("Ungültiger Zug: " + libMove);
return false; return false;
} }
public String getOpeningName() {
if (detectedOpening != null) {
return detectedOpening.getEco() + " - " + detectedOpening.getName();
} else {
return "unbekannt";
}
}
private String movesToUciString(List<Move> moves) {
StringBuilder sb = new StringBuilder();
for (Move m : moves) {
sb.append(m.toString()).append(" ");
}
return sb.toString().trim();
}
public List<MoveDTO> getLegalDestinations(String from) { public List<MoveDTO> getLegalDestinations(String from) {
logger.info("Hole legale Züge von: " + from); logger.info("Hole legale Züge von: " + from);
@ -169,6 +195,9 @@ public class ChessEngine {
board.doMove(moves.get(i)); board.doMove(moves.get(i));
} }
currentMoveIndex = idx; currentMoveIndex = idx;
String playedMovesUci = movesToUciString(moves.subList(0, idx));
detectedOpening = Opening.detect(playedMovesUci);
} }
public int getCurrentMoveIndex() { public int getCurrentMoveIndex() {
@ -355,6 +384,18 @@ public class ChessEngine {
} }
public void undoLastMove() {
if (currentMoveIndex > 0 && moves.size() > 0) {
board.undoMove(); // 1. Brett zurücksetzen
moves.remove(currentMoveIndex - 1); // 2. Zug aus Move-Liste löschen
currentMoveIndex--; // 3. Index anpassen
// 4. Erkennung Opening neu machen!
String playedMovesUci = movesToUciString(moves.subList(0, currentMoveIndex));
detectedOpening = Opening.detect(playedMovesUci);
}
}
public void loadMoves(List<Move> moveList) { public void loadMoves(List<Move> moveList) {
board = new Board(); // Neues leeres Brett board = new Board(); // Neues leeres Brett

View File

@ -0,0 +1,68 @@
package de.hs_mannheim.informatik.chess.model;
import java.util.*;
import java.nio.file.*;
import java.io.*;
public class Opening {
private final String eco;
private final String name;
private final String moves;
public Opening(String eco, String name, String moves) {
this.eco = eco;
this.name = name;
this.moves = moves;
}
public String getEco() { return eco; }
public String getName() { return name; }
public String getMoves() { return moves; }
// Öffnet und cached die Liste aus der gewünschten CSV
private static List<Opening> cachedOpenings = null;
public static List<Opening> allOpenings() {
if (cachedOpenings == null) {
cachedOpenings = new ArrayList<>();
// Passe den Pfad für deinen Benutzernamen an!
String path = "Ressources/all_openings.csv";
try (BufferedReader br = Files.newBufferedReader(Paths.get(path))) {
String line;
while ((line = br.readLine()) != null) {
// CSV-Format: ECO,"Name","Moves"
String[] parts = line.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)", -1);
if (parts.length == 3) {
String eco = parts[0];
String name = parts[1].replaceAll("^\"|\"$", "");
String moves = parts[2].replaceAll("^\"|\"$", "");
cachedOpenings.add(new Opening(eco, name, moves));
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
return cachedOpenings;
}
/**
* Sucht das längste passende Opening für eine gegebene Zugfolge (UCI).
* @param playedMovesUci UCI-Züge als String, z.B. "e2e4 e7e5 g1f3"
* @return bestes Opening oder null
*/
public static Opening detect(String playedMovesUci) {
Opening bestMatch = null;
int bestLength = 0;
for (Opening opening : allOpenings()) {
if (playedMovesUci.startsWith(opening.getMoves())) {
if (opening.getMoves().length() > bestLength) {
bestMatch = opening;
bestLength = opening.getMoves().length();
}
}
}
return bestMatch;
}
}

View File

@ -30,7 +30,8 @@ public class CreativeGui {
public CreativeGui() { public CreativeGui() {
frame = new JFrame("Creative Mode"); frame = new JFrame("Creative Mode");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setSize(1600, 1200); frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setUndecorated(false);
frame.setLocationRelativeTo(null); frame.setLocationRelativeTo(null);
JPanel mainPanel = new JPanel(new GridBagLayout()); JPanel mainPanel = new JPanel(new GridBagLayout());

View File

@ -7,9 +7,12 @@ import java.awt.Font;
import java.awt.GridBagConstraints; import java.awt.GridBagConstraints;
import java.awt.GridBagLayout; import java.awt.GridBagLayout;
import java.awt.GridLayout; import java.awt.GridLayout;
import java.awt.FlowLayout;
import java.awt.Insets; import java.awt.Insets;
import java.util.List; import java.util.List;
import javax.swing.Box;
import javax.swing.BoxLayout; import javax.swing.BoxLayout;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JFrame; import javax.swing.JFrame;
@ -31,11 +34,17 @@ public class GameGui {
private JLabel whiteTimerLabel; private JLabel whiteTimerLabel;
private JLabel blackTimerLabel; private JLabel blackTimerLabel;
private JLabel openingLabel;
JButton btnFirst = new JButton("|<"); private JButton btnFirst = new JButton("|<");
JButton btnPrev = new JButton("<"); private JButton btnPrev = new JButton("<");
JButton btnNext = new JButton(">"); private JButton btnNext = new JButton(">");
JButton btnLast = new JButton(">|"); private JButton btnLast = new JButton(">|");
private JButton resignButton;
private JButton drawButton;
private JButton quickSaveButton;
private JButton undoButton;
Color LIGHT = new Color(0xe0e1dd); Color LIGHT = new Color(0xe0e1dd);
Color DARK = new Color(0x778da9); Color DARK = new Color(0x778da9);
@ -51,7 +60,8 @@ public class GameGui {
public JFrame mainFrame() { public JFrame mainFrame() {
JFrame frame = new JFrame(); JFrame frame = new JFrame();
frame.setSize(1600, 1000); frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setUndecorated(false);
frame.setLocationRelativeTo(null); frame.setLocationRelativeTo(null);
frame.add(mainPanel()); frame.add(mainPanel());
@ -127,38 +137,82 @@ public class GameGui {
return boardPanel; return boardPanel;
} }
public JPanel chessPanel(JPanel panel) { public JPanel chessPanel(JPanel boardPanel) {
JPanel chessPanel = new JPanel(new GridBagLayout()); JPanel chessPanel = new JPanel(new BorderLayout());
GridBagConstraints board = new GridBagConstraints(); chessPanel.setBackground(new Color(0x1b263b));
chessPanel.setBackground(new Color(0x1b263b));
board.gridx = 0;
board.gridy = 0;
board.weightx = 0.7;
board.weighty = 1.0;
board.insets = new Insets(0, 0, 0, 0);
//oben, links, unten, rechts
board.fill = GridBagConstraints.BOTH;
chessPanel.add(panel);
// Button unten rechts
flipBoardButton = new JButton("⇵");
flipBoardButton.setPreferredSize(new Dimension(70, 70));
flipBoardButton.setFont(new Font("SansSerif", Font.BOLD, 40));
flipBoardButton.setBackground(new Color(0x5500ff));
flipBoardButton.setForeground(Color.WHITE);
flipBoardButton.setFocusPainted(false);
GridBagConstraints btn = new GridBagConstraints(); // --- Eröffnungslabel oben ---
btn.gridx = 0; openingLabel = new JLabel("Eröffnung: unbekannt", SwingConstants.CENTER);
btn.gridy = 1; openingLabel.setFont(new Font("SansSerif", Font.BOLD, 24));
btn.weightx = 0.0; openingLabel.setForeground(Color.WHITE);
btn.weighty = 0.0; openingLabel.setOpaque(true);
btn.anchor = GridBagConstraints.SOUTHEAST; openingLabel.setBackground(new Color(0x283655));
btn.insets = new Insets(10, 0, 0, 0); openingLabel.setPreferredSize(new Dimension(800, 50));
chessPanel.add(openingLabel, BorderLayout.NORTH);
chessPanel.add(flipBoardButton, btn); // --- Board in ein zentriertes Panel mit fixer Größe ---
JPanel centerPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
return chessPanel; centerPanel.setOpaque(false);
centerPanel.add(boardPanel);
centerPanel.addComponentListener(new java.awt.event.ComponentAdapter() {
public void componentResized(java.awt.event.ComponentEvent evt) {
int size = Math.min(centerPanel.getWidth(), centerPanel.getHeight());
boardPanel.setPreferredSize(new Dimension(size, size));
boardPanel.revalidate();
}
});
chessPanel.add(centerPanel, BorderLayout.CENTER);
// --- Dummy-Buffer für WEST und EAST ---
chessPanel.add(Box.createRigidArea(new Dimension(40, 0)), BorderLayout.WEST);
chessPanel.add(Box.createRigidArea(new Dimension(40, 0)), BorderLayout.EAST);
// --- Buttonleiste unten bauen ---
JPanel buttonRow = new JPanel();
buttonRow.setOpaque(false);
buttonRow.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.RIGHT, 10, 0));
resignButton = new JButton("🏳");
resignButton.setPreferredSize(new Dimension(70, 70));
resignButton.setFont(new Font("SansSerif", Font.BOLD, 35));
resignButton.setBackground(new Color(0xff0044));
resignButton.setForeground(Color.WHITE);
resignButton.setFocusPainted(false);
drawButton = new JButton("½");
drawButton.setPreferredSize(new Dimension(70, 70));
drawButton.setFont(new Font("SansSerif", Font.BOLD, 35));
drawButton.setBackground(new Color(0x0080ff));
drawButton.setForeground(Color.WHITE);
drawButton.setFocusPainted(false);
flipBoardButton = new JButton("⇵");
flipBoardButton.setPreferredSize(new Dimension(70, 70));
flipBoardButton.setFont(new Font("SansSerif", Font.BOLD, 40));
flipBoardButton.setBackground(new Color(0x5500ff));
flipBoardButton.setForeground(Color.WHITE);
flipBoardButton.setFocusPainted(false);
undoButton = new JButton("↧");
undoButton.setPreferredSize(new Dimension(70, 70));
undoButton.setFont(new Font("SansSerif", Font.BOLD, 40));
undoButton.setBackground(new Color(0xffa500)); // Orange
undoButton.setForeground(Color.WHITE);
undoButton.setFocusPainted(false);
buttonRow.add(undoButton);
buttonRow.add(resignButton);
buttonRow.add(drawButton);
buttonRow.add(flipBoardButton);
chessPanel.add(buttonRow, BorderLayout.SOUTH);
return chessPanel;
} }
public JPanel statsPanel() { public JPanel statsPanel() {
JPanel statsPanel = new JPanel(new BorderLayout()); JPanel statsPanel = new JPanel(new BorderLayout());
@ -270,6 +324,8 @@ public class GameGui {
public JButton getBtnNext() { return btnNext; } public JButton getBtnNext() { return btnNext; }
public JButton getBtnLast() { return btnLast; } public JButton getBtnLast() { return btnLast; }
public JButton getBtnSave() { return btnSave; } public JButton getBtnSave() { return btnSave; }
public JButton getResignButton() { return resignButton; }
public JButton getDrawButton() { return drawButton; }
public void updateBoard(BoardDTO boardDTO) { public void updateBoard(BoardDTO boardDTO) {
PieceDTO[][] board = boardDTO.getBoard(); PieceDTO[][] board = boardDTO.getBoard();
@ -309,6 +365,13 @@ public class GameGui {
public JLabel getWhiteTimerLabel() { return whiteTimerLabel; } public JLabel getWhiteTimerLabel() { return whiteTimerLabel; }
public JLabel getBlackTimerLabel() { return blackTimerLabel; } public JLabel getBlackTimerLabel() { return blackTimerLabel; }
public void setOpeningLabel(String text) {
openingLabel.setText("Eröffnung: " + text);
}
public JButton getUndoButton() {
return undoButton;
}
public void displayMessage(String msg) { public void displayMessage(String msg) {
JOptionPane.showMessageDialog(null, msg); JOptionPane.showMessageDialog(null, msg);

View File

@ -17,7 +17,8 @@ public class MainGui {
frame = new JFrame("Chess - Hauptmenü"); frame = new JFrame("Chess - Hauptmenü");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1600, 1000); frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setUndecorated(false);
frame.setLocationRelativeTo(null); frame.setLocationRelativeTo(null);
//Haupt-Panel mit GridBagLayout //Haupt-Panel mit GridBagLayout

View File

@ -47,7 +47,8 @@ public class PgnGui {
public JFrame mainFrame() { public JFrame mainFrame() {
JFrame frame = new JFrame(); JFrame frame = new JFrame();
frame.setSize(1600, 1000); frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setUndecorated(false);
frame.setLocationRelativeTo(null); frame.setLocationRelativeTo(null);
frame.add(mainPanel()); frame.add(mainPanel());