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

@ -123,13 +123,88 @@ public class GameController {
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) {
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 modelCol = flipCol(guiCol);
@ -226,6 +301,7 @@ public class GameController {
public void updateGuiBoard() {
BoardDTO board = engine.getBoardAsDTO();
gui.updateBoard(board);
gui.setOpeningLabel(engine.getOpeningName());
}
private void switchTimers() {

View File

@ -31,11 +31,12 @@ public class ChessEngine {
private Timer whiteTimer;
private Timer blackTimer;
private Opening detectedOpening = null;
public ChessEngine() {
logging();
board = new Board();
}
public boolean move(MoveDTO move) {
String from = "" + (char)('A' + move.getFromCol()) + (8 - move.getFromRow());
String to = "" + (char)('A' + move.getToCol()) + (8 - move.getToRow());
@ -43,7 +44,7 @@ public class ChessEngine {
if (board.legalMoves().contains(libMove)) {
board.doMove(libMove);
//Replay? Dann abschneiden
// Replay? Dann abschneiden
if (currentMoveIndex < moves.size()) {
logger.info("Replay-Modus: Züge nach " + currentMoveIndex + " werden entfernt.");
moves = new ArrayList<>(moves.subList(0, currentMoveIndex));
@ -51,12 +52,37 @@ public class ChessEngine {
moves.add(libMove);
currentMoveIndex++;
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;
}
logger.warning("Ungültiger Zug: " + libMove);
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) {
logger.info("Hole legale Züge von: " + from);
List<MoveDTO> destinations = new ArrayList<>();
@ -169,6 +195,9 @@ public class ChessEngine {
board.doMove(moves.get(i));
}
currentMoveIndex = idx;
String playedMovesUci = movesToUciString(moves.subList(0, idx));
detectedOpening = Opening.detect(playedMovesUci);
}
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) {
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() {
frame = new JFrame("Creative Mode");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setSize(1600, 1200);
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setUndecorated(false);
frame.setLocationRelativeTo(null);
JPanel mainPanel = new JPanel(new GridBagLayout());

View File

@ -7,9 +7,12 @@ import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.FlowLayout;
import java.awt.Insets;
import java.util.List;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
@ -31,11 +34,17 @@ public class GameGui {
private JLabel whiteTimerLabel;
private JLabel blackTimerLabel;
private JLabel openingLabel;
JButton btnFirst = new JButton("|<");
JButton btnPrev = new JButton("<");
JButton btnNext = new JButton(">");
JButton btnLast = new JButton(">|");
private JButton btnFirst = new JButton("|<");
private JButton btnPrev = new JButton("<");
private JButton btnNext = 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 DARK = new Color(0x778da9);
@ -51,7 +60,8 @@ public class GameGui {
public JFrame mainFrame() {
JFrame frame = new JFrame();
frame.setSize(1600, 1000);
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setUndecorated(false);
frame.setLocationRelativeTo(null);
frame.add(mainPanel());
@ -127,19 +137,58 @@ public class GameGui {
return boardPanel;
}
public JPanel chessPanel(JPanel panel) {
JPanel chessPanel = new JPanel(new GridBagLayout());
GridBagConstraints board = new GridBagConstraints();
public JPanel chessPanel(JPanel boardPanel) {
JPanel chessPanel = new JPanel(new BorderLayout());
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
// --- Eröffnungslabel oben ---
openingLabel = new JLabel("Eröffnung: unbekannt", SwingConstants.CENTER);
openingLabel.setFont(new Font("SansSerif", Font.BOLD, 24));
openingLabel.setForeground(Color.WHITE);
openingLabel.setOpaque(true);
openingLabel.setBackground(new Color(0x283655));
openingLabel.setPreferredSize(new Dimension(800, 50));
chessPanel.add(openingLabel, BorderLayout.NORTH);
// --- Board in ein zentriertes Panel mit fixer Größe ---
JPanel centerPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
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));
@ -147,19 +196,24 @@ public class GameGui {
flipBoardButton.setForeground(Color.WHITE);
flipBoardButton.setFocusPainted(false);
GridBagConstraints btn = new GridBagConstraints();
btn.gridx = 0;
btn.gridy = 1;
btn.weightx = 0.0;
btn.weighty = 0.0;
btn.anchor = GridBagConstraints.SOUTHEAST;
btn.insets = new Insets(10, 0, 0, 0);
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);
chessPanel.add(flipBoardButton, btn);
buttonRow.add(undoButton);
buttonRow.add(resignButton);
buttonRow.add(drawButton);
buttonRow.add(flipBoardButton);
chessPanel.add(buttonRow, BorderLayout.SOUTH);
return chessPanel;
}
public JPanel statsPanel() {
JPanel statsPanel = new JPanel(new BorderLayout());
statsPanel.setBackground(new Color(0x0d1b2a));
@ -270,6 +324,8 @@ public class GameGui {
public JButton getBtnNext() { return btnNext; }
public JButton getBtnLast() { return btnLast; }
public JButton getBtnSave() { return btnSave; }
public JButton getResignButton() { return resignButton; }
public JButton getDrawButton() { return drawButton; }
public void updateBoard(BoardDTO boardDTO) {
PieceDTO[][] board = boardDTO.getBoard();
@ -309,6 +365,13 @@ public class GameGui {
public JLabel getWhiteTimerLabel() { return whiteTimerLabel; }
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) {
JOptionPane.showMessageDialog(null, msg);

View File

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

View File

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