Compare commits

...

13 Commits

Author SHA1 Message Date
Justin Muravjev 6f850aa11b Thomas sources for project 2025-06-24 12:10:59 +02:00
Justin Muravjev c9efa6ecda Newer pom.xml 2025-06-24 12:08:39 +02:00
Justin Muravjev 833630abcd Library Quellen 2025-06-24 10:58:45 +02:00
Justin Muravjev 2cf0075c0c Alle Quellen 2025-06-24 10:57:36 +02:00
Justin Muravjev 65c7486a32 Quellen 2025-06-24 10:57:03 +02:00
Justin Muravjev cf870fec57 Merge remote-tracking branch 'origin/Main' into PMD-Korrektur 2025-06-24 10:53:19 +02:00
thomasmuller cc18a20588 pom gefixed 2025-06-24 10:47:00 +02:00
valen 49e88ac03e Korrektur von ChessEngine 2025-06-24 10:26:17 +02:00
thomasmuller cf98d45d6a pom aktualisiert 2025-06-24 10:14:44 +02:00
Justin 32f97aa624 pom rework 2025-06-24 05:50:52 +02:00
Justin fa8f04805f pmd plugin in pom.xml 2025-06-24 05:38:04 +02:00
Justin 59233efcca Last changes for quicksave 2025-06-24 05:31:27 +02:00
Justin aebfb7e38b quicksave feature implemented 2025-06-24 05:04:44 +02:00
9 changed files with 940 additions and 535 deletions

View File

@ -0,0 +1,259 @@
1. Methode toPGN ist vorhanden in Klasse Game.java!!! Mit MoveGenerator werden schon alle legalen züge angezeigt!!!
2. Klassen von GPT zusammengefasst:
https://chatgpt.com/share/684326dc-4ea0-8012-8c10-8b34993140b7
Event:
Die Klasse Event beschreibt ein Schachturnier oder -ereignis mit Name, Ort, Datum, Zeitkontrollen, Rundenstruktur und referenzierten PGN-Daten.
EventType:
EventType beschreibt den strukturellen Typ eines Schachturniers (z.B. KO-System, Rundenturnier oder Analyseveranstaltung).
Game:
Die Klasse Game speichert und verwaltet alle Informationen einer Schachpartie wie Züge, Spieler, Kommentare und Metadaten und erlaubt deren Analyse, Bearbeitung und Ausgabe im PGN-Format.
GameContext:
Die Klasse GameContext stellt alle benötigten Informationen und Methoden bereit, um Rochaden und Schachvarianten (z.B. Chess960) regelkonform zu verwalten und korrekt zu validieren.
GameFactory:
GameFactory stellt statische Hilfsmethoden bereit, um neue Schachobjekte (Spiel, Spieler, Runde, Event) schnell und einheitlich zu erzeugen.
GameMode:
GameMode legt fest, ob eine Partie zwischen Menschen, Maschinen oder gemischt (z.B. Engine vs Mensch) gespielt wird.
GameResult:
GameResult beschreibt, wie eine Schachpartie geendet hat (Sieg, Remis oder noch laufend) und verknüpft dies mit der entsprechenden PGN-Notation.
MoveGeneratorException
MoveGeneratorException signalisiert Fehler beim Generieren von Schachzügen und erlaubt dabei flexible Fehlerbeschreibungen.
MoveList
MoveList verwaltet eine Folge von Schachzügen und erlaubt deren Umwandlung, Validierung und Darstellung in verschiedenen Notationen basierend auf einer Ausgangsstellung.
GameLoader:
Die Klasse GameLoader lädt eine vollständige Schachpartie inklusive aller Metadaten und Züge aus einem PGN-Dateiiterator und wandelt sie in ein Game-Objekt um.
GenericPlayer:
Die Klasse GenericPlayer implementiert das Player-Interface und speichert die wichtigsten Eigenschaften eines Schachspielers wie Name, Elo, Typ (Mensch/Maschine) und Beschreibung.
Mode:
Mode beschreibt den Austragungsort einer Partie: entweder als Präsenzspiel am Brett (OTB) oder über einen Online-Server (ICS).
MovePerTime:
MovePerTime beschreibt eine Zeitkontrolle, bei der eine bestimmte Anzahl an Zügen in einer definierten Zeit (in Millisekunden) gespielt werden muss, z.B. 40/5400 (40 Züge in 90 Minuten).
Player:
Player ist ein Interface, das die grundlegenden Eigenschaften und Methoden eines Schachspielers festlegt, darunter ID, Name, Elo, Typ und eine ausführliche Beschreibung.
PlayerType:
PlayerType unterscheidet, ob ein Spieler ein Mensch (HUMAN) oder eine Engine (ENGINE) ist.
Round:
Round speichert eine bestimmte Runde eines Schachturniers und enthält eine Liste aller Partien (Game), die in dieser Runde gespielt werden.
Termination:
Termination gibt an, warum eine Partie beendet wurde, z.B. regulär, durch Zeitüberschreitung, Regelverstoß oder Abbruch.
TimeControl:
TimeControl speichert verschiedene Zeitregelungen für eine Partie etwa klassische Zeitkontrollen, Bonuszeiten (Inkrement), feste Zugvorgaben, Knoten-/Tiefenlimits (für Engines) und gibt sie als PGN-kompatible Zeichenkette zurück.
TimeControlType:
TimeControlType gibt an, in welchem Modus eine Zeitkontrolle funktioniert, z.B. klassisches Zeitlimit mit Inkrement, Zeit pro Zug, feste Tiefe oder Knotenzahl (für Engines).
VariationType:
VariationType gibt an, welche Schachvariante gespielt wird z.B. Standard-Schach, Chess960 oder Varianten mit speziellen Rochaderegeln oder Figurenmechaniken.
Move:
Move repräsentiert einen einzelnen Schachzug mit Start- und Zielfeld, optionaler Bauernumwandlung sowie SAN-Notation, und kann als Ereignis (BoardEvent) weiterverarbeitet werden.
MoveConversionException:
MoveConversionException signalisiert, dass ein Zug nicht korrekt aus einem Text (z.B. "e2e99" oder "z9z8Q") in ein gültiges Move-Objekt umgewandelt werden konnte.
MoveException:
MoveException signalisiert, dass ein Zug nicht auf dem aktuellen Spielstand ausgeführt werden kann typischerweise, weil er regelwidrig, unmöglich oder logisch inkonsistent ist.
MoveGenerator:
MoveGenerator stellt statische Hilfsmethoden bereit, um aus einem gegebenen Schachbrettzustand alle möglichen Züge (pseudo-legal oder legal) zu erzeugen, einschließlich Spezialzüge wie Rochade oder Bauernumwandlungen.
PgnException
Die Klasse PgnException signalisiert Laufzeitfehler beim Parsen oder Einlesen von PGN-Schachpartien.
PgnHolder
Die Klasse PgnHolder verwaltet das Einlesen, Speichern und Abrufen von Schachpartien aus PGN-Dateien und organisiert dabei Spieler, Events und Spiele zentral in einer leicht zugänglichen Struktur.
PgnIterator
Die Klasse PgnIterator ermöglicht das speicherschonende Durchiterieren von Schachpartien in einer PGN-Datei, indem sie zeilenweise liest und jede Partie einzeln verarbeitet.
PgnLoadListener
Die Schnittstelle PgnLoadListener erlaubt es, auf Fortschritte beim Laden von PGN-Partien zu reagieren, indem sie bei jedem Ladefortschritt benachrichtigt wird.
PgnProperty
Die Klasse PgnProperty modelliert eine einzelne PGN-Eigenschaft (Tag) und stellt Hilfsmethoden bereit, um sie aus einer Textzeile zu erkennen und zu extrahieren.
UnicodePrinter
Die Klasse UnicodePrinter druckt ein Schachbrett-Objekt (Board) als ASCII-Art mit Unicode-Schachsymbolen zeilenweise in die Konsole oder in einen beliebigen Ausgabestrom.
LargeFile
Die Klasse LargeFile erlaubt das effiziente und speicherschonende zeilenweise Lesen großer Textdateien oder Eingabeströme durch Bereitstellung eines einfachen Java-Iterators.
StringUtil
Die Klasse StringUtil stellt nützliche Methoden zur Verfügung, um Zeichenketten effizient zu analysieren, zu verändern und zu übersetzen insbesondere bei der Verarbeitung von Schach-PGN-Daten.
XorShiftRandom
Die Klasse XorShiftRandom implementiert einen leistungsfähigen, einfachen Pseudozufallszahlengenerator auf Basis des Xorshift-Verfahrens, der deterministische long-Werte durch Bitoperationen erzeugt.
Bitboard
Die Klasse Bitboard stellt für jedes Schachfeld und diverse Feldergruppen (Reihen, Linien, Diagonalen) Bitmasken bereit, um Schachpositionen effizient mit Bitoperationen zu verarbeiten.
Board
Die Klasse Board modelliert ein vollständiges Schachbrett inklusive Spielregeln, Figurenpositionen, Zugverlauf und ermöglicht die Validierung, Durchführung und Rücknahme von Schachzügen.
BoardEvent
Das Interface BoardEvent definiert die Struktur für Ereignisse, die beim Ändern des Schachbretts ausgelöst und an Listener weitergegeben werden können.
BoardEventListener
Das Interface BoardEventListener erlaubt es, auf Ereignisse wie Züge oder Statusänderungen des Schachbretts zu reagieren, indem es über die Methode onEvent() benachrichtigt wird.
BoardEventType
Das Enum BoardEventType definiert die Arten von Ereignissen, die auf einem Schachbrett ausgelöst und über Event-Listener verarbeitet werden können etwa beim Ziehen, Zurücknehmen oder Laden.
CastleRight
Das Enum CastleRight beschreibt, ob eine Seite im Schach kurz, lang, beides oder gar nicht rochieren darf.
Constants
Die Klasse Constants bündelt feste, zentrale Informationen und Züge wie die Start-FEN, Rochadepfade und Figur-Notation, um typische Schachsituationen schnell und konsistent abrufen zu können.
DiagonalA1H8
Die Enum DiagonalA1H8 listet alle rechtsschiefen (↘️) Diagonalen des Schachbretts als konstante Werte mit eindeutiger Namensgebung der Begrenzungsfelder auf.
DiagonalH1A8
Die Enum DiagonalH1A8 listet alle linksschiefen (↙️) Diagonalen des Schachbretts auf, wobei jede Diagonale durch ihre Randfelder eindeutig benannt ist.
File
Das Enum File beschreibt die Spalten (AH) des Schachbretts samt zugehöriger Notation und ermöglicht deren einfache Nutzung in Code und Notationen.
MoveBackup
Die Klasse MoveBackup speichert alle relevanten Zustandsinformationen eines Schachbretts vor einem Zug, um diesen später vollständig rückgängig machen zu können.
Piece
Die Klasse Piece repräsentiert jede konkrete Schachfigur mit Farbe und Typ und bietet Methoden zur Erstellung und Notation dieser Figuren.
PieceType
PieceType beschreibt die Typen von Schachfiguren (z.B. Turm, König) und verknüpft sie mit ihrer algebraischen Notation für einfache Identifikation und Verarbeitung.
Rank
Rank beschreibt die horizontalen Reihen des Schachbretts und stellt deren numerische Notation zur Verfügung, um Positionen eindeutig identifizieren zu können.
Side
Side repräsentiert eine der beiden Spielerparteien im Schach Weiß oder Schwarz und ermöglicht den einfachen Wechsel der Seite mit der Methode flip().
Square
Square ist ein mächtiges enum, das jedes Schachfeld eindeutig repräsentiert und dabei zahlreiche Hilfsmethoden zur Brettlogik und Bitboard-Darstellung bereitstellt. Es ist eine der Grundpfeiler des gesamten Schachsystems.

View File

@ -0,0 +1,21 @@
Quellen
Thomas Müller | 3021788
Timer https://chatgpt.com/c/6857ff1e-7ff4-8006-aba3-60d3a2dd59b4
Valentin Weller | 3019075
Junit: https://chatgpt.com/share/6859a713-4b98-8012-8cb2-a9f3c7fb37c8
QuickSave: https://chatgpt.com/share/6859a73f-e750-8012-a168-f9fa7c5487bd
PGNs: https://chatgpt.com/share/6859a8f1-d3c4-8012-98f4-6ae4b057be73
https://chatgpt.com/share/6859a95a-cca8-8012-bbbe-4e4b4517d5d9
Eröffnungserkennung: https://github.com/hell-sh/CompactChessOpenings/blob/master/src/sh/hell/compactchess/game/Opening.java
Quellen
Justin Muravjev 3014931
ChatGPT:
https://chatgpt.com/share/685a60b4-dad0-8012-86a8-ca9a7e6eb76b

View File

@ -10,13 +10,12 @@
<version>0.0.1-SNAPSHOT</version> <version>0.0.1-SNAPSHOT</version>
<name>schach</name> <name>schach</name>
<description>A simple schach.</description> <description>A simple schach project</description>
<url>http://www.example.com</url> <url>http://www.example.com</url>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>21</maven.compiler.source> <maven.compiler.release>17</maven.compiler.release>
<maven.compiler.target>21</maven.compiler.target>
</properties> </properties>
<repositories> <repositories>
@ -27,29 +26,31 @@
</repositories> </repositories>
<dependencies> <dependencies>
<!-- Schachlib (bhlangonijr) --> <!-- Schachlib -->
<dependency> <dependency>
<groupId>com.github.bhlangonijr</groupId> <groupId>com.github.bhlangonijr</groupId>
<artifactId>chesslib</artifactId> <artifactId>chesslib</artifactId>
<version>1.3.4</version> <version>1.3.4</version>
</dependency> </dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
<!-- Mockito damit man Controller und Gui besser testen kann-->
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>mockito-core</artifactId> <artifactId>junit-jupiter</artifactId>
<version>5.11.0</version> <version>5.10.2</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> <!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build> <build>
<pluginManagement> <pluginManagement>
<plugins> <plugins>
<plugin> <plugin>
@ -89,14 +90,60 @@
<version>3.1.2</version> <version>3.1.2</version>
</plugin> </plugin>
</plugins> </plugins>
</pluginManagement> </pluginManagement>
<plugins>
<!-- Java Compiler Plugin -->
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>17</release>
</configuration>
</plugin>
<!-- PMD Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.21.0</version>
<configuration>
<!-- Set to 17 until PMD supports 21 -->
<targetJdk>17</targetJdk>
<failOnViolation>false</failOnViolation>
<printFailingErrors>true</printFailingErrors>
<linkXRef>false</linkXRef>
<rulesets>
<ruleset>rulesets/java/quickstart.xml</ruleset>
<ruleset>rulesets/java/basic.xml</ruleset>
<ruleset>rulesets/java/braces.xml</ruleset>
<ruleset>rulesets/java/unusedcode.xml</ruleset>
<ruleset>rulesets/java/imports.xml</ruleset>
</rulesets>
</configuration>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Surefire Plugin for tests -->
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.3.0</version>
</plugin>
</plugins>
</build> </build>
<reporting> <reporting>
<plugins> <plugins>
<plugin> <plugin>
<artifactId>maven-project-info-reports-plugin</artifactId> <artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.6.1</version>
</plugin> </plugin>
</plugins> </plugins>
</reporting> </reporting>
</project> </project>

View File

@ -7,6 +7,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
import javax.swing.JOptionPane;
import com.github.bhlangonijr.chesslib.game.Game; import com.github.bhlangonijr.chesslib.game.Game;
@ -24,8 +25,10 @@ public class GameController {
GameEndCallback callback; GameEndCallback callback;
private boolean gameOver = false; private boolean gameOver = false;
private int selectedRow = -1, selectedCol = -1; private int selectedRow = -1;
private int selectedCol = -1;
private List<int[]> highlightedFields = new ArrayList<>(); private List<int[]> highlightedFields = new ArrayList<>();
private boolean gameWasResignedOrDrawn = false;
private GameMode gameMode; private GameMode gameMode;
public GameController(GameGui gui, ChessEngine engine, GameEndCallback callback, GameMode gameMode) { public GameController(GameGui gui, ChessEngine engine, GameEndCallback callback, GameMode gameMode) {
@ -33,8 +36,9 @@ public class GameController {
this.gameMode = gameMode; this.gameMode = gameMode;
if (gameMode != null) { if (gameMode != null) {
engine.initTimers(gameMode.minutes, gameMode.incrementSeconds); engine.initTimers(gameMode.minutes, gameMode.incrementSeconds);
time(); setupAndStartTimers();
} }
addWindowCloseListener();
} }
// Für Creative/PGN-Mode (ohne Zeit) // Für Creative/PGN-Mode (ohne Zeit)
@ -43,17 +47,36 @@ public class GameController {
this.engine = engine; this.engine = engine;
this.callback = callback; this.callback = callback;
this.gameMode = null; this.gameMode = null;
addWindowCloseListener();
// KEINE Timer initialisieren // KEINE Timer initialisieren
initListeners(); initListeners();
updateGuiBoard(); updateGuiBoard();
} }
private void time() {
engine.getWhiteTimer().setOnTick(secs -> gui.updateWhiteTimerLabel(secs)); private void setupAndStartTimers() {
engine.getBlackTimer().setOnTick(secs -> gui.updateBlackTimerLabel(secs)); if (engine.getWhiteTimer() != null) {
engine.getWhiteTimer().setOnTimeout(() -> onTimeout("WHITE")); engine.getWhiteTimer().setOnTick(secs -> gui.updateWhiteTimerLabel(secs));
engine.getBlackTimer().setOnTimeout(() -> onTimeout("BLACK")); engine.getWhiteTimer().setOnTimeout(() -> onTimeout("WHITE"));
engine.getWhiteTimer().start(); engine.getWhiteTimer().stop(); // <-- WICHTIG!
}
if (engine.getBlackTimer() != null) {
engine.getBlackTimer().setOnTick(secs -> gui.updateBlackTimerLabel(secs));
engine.getBlackTimer().setOnTimeout(() -> onTimeout("BLACK"));
engine.getBlackTimer().stop(); // <-- WICHTIG!
}
// Timer-Labels initial setzen
if (engine.getWhiteTimer() != null) gui.updateWhiteTimerLabel(engine.getWhiteTimer().getSecondsLeft());
if (engine.getBlackTimer() != null) gui.updateBlackTimerLabel(engine.getBlackTimer().getSecondsLeft());
// Aktuellen Spieler-Timer starten:
if (engine.getCurrentPlayer().equals("WHITE")) {
engine.getWhiteTimer().start();
} else {
engine.getBlackTimer().start();
}
} }
private int flipRow(int row) { private int flipRow(int row) {
return gui.isFlipped() ? 7 - row : row; return gui.isFlipped() ? 7 - row : row;
} }
@ -61,6 +84,20 @@ public class GameController {
return gui.isFlipped() ? 7 - col : col; return gui.isFlipped() ? 7 - col : col;
} }
private void addWindowCloseListener() {
if (gui.getFrame() != null) {
gui.getFrame().addWindowListener(new java.awt.event.WindowAdapter() {
@Override
public void windowClosing(java.awt.event.WindowEvent e) {
if (!gameOver) {
engine.quicksave();
MainController.engineRAM = engine;
}
new MainController();
}
});
}
}
private void initListeners() { private void initListeners() {
for (int row = 0; row < 8; row++) { for (int row = 0; row < 8; row++) {
for (int col = 0; col < 8; col++) { for (int col = 0; col < 8; col++) {
@ -138,14 +175,15 @@ public class GameController {
// --- AUFGEBEN-BUTTON --- // --- AUFGEBEN-BUTTON ---
gui.getResignButton().addActionListener(e -> { gui.getResignButton().addActionListener(e -> {
if (gameOver) return; if (gameOver) return;
int answer = javax.swing.JOptionPane.showConfirmDialog( int answer = JOptionPane.showConfirmDialog(
null, null,
"Willst du wirklich aufgeben?", "Willst du wirklich aufgeben?",
"Aufgeben", "Aufgeben",
javax.swing.JOptionPane.YES_NO_OPTION JOptionPane.YES_NO_OPTION
); );
if (answer == javax.swing.JOptionPane.YES_OPTION) { if (answer == JOptionPane.YES_OPTION) {
gameOver = true; gameOver = true;
gameWasResignedOrDrawn = true; // <<<<<<
String winner = engine.getCurrentPlayer().equals("WHITE") ? "SCHWARZ" : "WEIß"; String winner = engine.getCurrentPlayer().equals("WHITE") ? "SCHWARZ" : "WEIß";
gui.displayMessage(winner + " gewinnt durch Aufgabe!"); gui.displayMessage(winner + " gewinnt durch Aufgabe!");
if (engine.getWhiteTimer() != null) engine.getWhiteTimer().stop(); if (engine.getWhiteTimer() != null) engine.getWhiteTimer().stop();
@ -156,20 +194,22 @@ public class GameController {
gui.getDrawButton().addActionListener(e -> { gui.getDrawButton().addActionListener(e -> {
if (gameOver) return; if (gameOver) return;
int answer = javax.swing.JOptionPane.showConfirmDialog( int answer = JOptionPane.showConfirmDialog(
null, null,
"Remis anbieten? (Das Spiel endet sofort unentschieden)", "Remis anbieten? (Das Spiel endet sofort unentschieden)",
"Remis", "Remis",
javax.swing.JOptionPane.YES_NO_OPTION JOptionPane.YES_NO_OPTION
); );
if (answer == javax.swing.JOptionPane.YES_OPTION) { if (answer == JOptionPane.YES_OPTION) {
gameOver = true; gameOver = true;
gameWasResignedOrDrawn = true; // <<<<<<
gui.displayMessage("Remis! (durch Einigung)"); gui.displayMessage("Remis! (durch Einigung)");
if (engine.getWhiteTimer() != null) engine.getWhiteTimer().stop(); if (engine.getWhiteTimer() != null) engine.getWhiteTimer().stop();
if (engine.getBlackTimer() != null) engine.getBlackTimer().stop(); if (engine.getBlackTimer() != null) engine.getBlackTimer().stop();
askForRestart(); askForRestart();
} }
}); });
gui.getUndoButton().addActionListener(e -> { gui.getUndoButton().addActionListener(e -> {
// Wer ist am Zug? (Das ist der, der gefragt wird) // Wer ist am Zug? (Das ist der, der gefragt wird)
@ -346,7 +386,7 @@ public class GameController {
return "" + file + rank; return "" + file + rank;
} }
// Timeout-Methode // Timeout-Methode
private void onTimeout(String color) { private void onTimeout(String color) {
if (gameOver) return; // Doppelt hält besser if (gameOver) return; // Doppelt hält besser
@ -359,19 +399,31 @@ public class GameController {
} }
private void askForRestart() { private void askForRestart() {
int answer = javax.swing.JOptionPane.showConfirmDialog( int answer = JOptionPane.showConfirmDialog(
null, null,
"Neue Partie starten?", "Neue Partie starten?",
"Spiel beendet", "Spiel beendet",
javax.swing.JOptionPane.YES_NO_OPTION JOptionPane.YES_NO_OPTION
); );
javax.swing.SwingUtilities.getWindowAncestor(gui.getField(0, 0)).dispose(); javax.swing.SwingUtilities.getWindowAncestor(gui.getField(0, 0)).dispose();
if (answer == javax.swing.JOptionPane.YES_OPTION) { if (answer == JOptionPane.YES_OPTION) {
engine.clearQuicksave();
MainController.engineRAM = null;
callback.onNewGameRequested(); callback.onNewGameRequested();
} else { } else {
// HIER: QuickSave **NUR** falls das Spiel NICHT durch Aufgabe/Remis/Patt beendet wurde!
if (!gameWasResignedOrDrawn) {
engine.quicksave();
MainController.engineRAM = engine;
} else {
engine.clearQuicksave();
MainController.engineRAM = null;
}
callback.onReturnToMenu(); callback.onReturnToMenu();
} }
gameWasResignedOrDrawn = false; // Reset für nächstes Spiel
} }
private void resetFieldBackground(int row, int col) { private void resetFieldBackground(int row, int col) {
Color LIGHT = new Color(0xe0e1dd); Color LIGHT = new Color(0xe0e1dd);

View File

@ -20,6 +20,7 @@ import de.hs_mannheim.informatik.chess.model.GameMode;
public class MainController { public class MainController {
private MainGui mainGui; private MainGui mainGui;
static ChessEngine engineRAM = null;
public MainController() { public MainController() {
mainGui = new MainGui(); mainGui = new MainGui();
@ -30,20 +31,54 @@ public class MainController {
} }
private void startNormalMode() { private void startNormalMode() {
// Prüfe, ob es im RAM ein Quicksave gibt:
GameMode mode = GameModeSelector.selectGameMode(); if (engineRAM != null && engineRAM.quickload()) {
int choice = JOptionPane.showConfirmDialog(
null,
"Letzte Partie fortsetzen?",
"Quicksave gefunden",
JOptionPane.YES_NO_OPTION
);
if (choice == JOptionPane.YES_OPTION) {
GameGui gameGui = new GameGui();
GameEndCallback callback = new GameEndCallback() {
public void onNewGameRequested() { startNormalMode(); }
public void onReturnToMenu() { new MainController(); }
};
new GameController(gameGui, engineRAM, callback); // KEIN initTimers, KEIN neuer Engine!
// --- Timerlabels aktualisieren
gameGui.updateWhiteTimerLabel(engineRAM.getWhiteTimer().getSecondsLeft());
gameGui.updateBlackTimerLabel(engineRAM.getBlackTimer().getSecondsLeft());
// --- Timer des aktuellen Spielers starten
if (engineRAM.getCurrentPlayer().equals("WHITE")) {
engineRAM.getWhiteTimer().start();
} else {
engineRAM.getBlackTimer().start();
}
mainGui.close();
return; // Fertig!
} else {
engineRAM.clearQuicksave();
engineRAM = null;
}
}
// Neues Spiel normal starten:
GameMode mode = GameModeSelector.selectGameMode();
if (mode == null) return;
mainGui.close(); mainGui.close();
GameGui gameGui = new GameGui();
ChessEngine engine = new ChessEngine(mode); ChessEngine engine = new ChessEngine(mode);
engineRAM = engine; // Für spätere Quicksaves merken!
GameGui gameGui = new GameGui();
GameEndCallback callback = new GameEndCallback() { GameEndCallback callback = new GameEndCallback() {
public void onNewGameRequested() { public void onNewGameRequested() {
startNormalMode(); startNormalMode();
} }
public void onReturnToMenu() { public void onReturnToMenu() {
new MainController(); new MainController();
} }
}; };
new GameController(gameGui, engine, callback,mode); new GameController(gameGui, engine, callback, mode);
} }
private void startCreativeMode() { private void startCreativeMode() {

View File

@ -1,8 +1,10 @@
package de.hs_mannheim.informatik.chess.model; package de.hs_mannheim.informatik.chess.model;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.ConsoleHandler; import java.util.logging.ConsoleHandler;
@ -18,422 +20,505 @@ import com.github.bhlangonijr.chesslib.move.Move;
import com.github.bhlangonijr.chesslib.pgn.PgnHolder; import com.github.bhlangonijr.chesslib.pgn.PgnHolder;
import com.github.bhlangonijr.chesslib.game.*; import com.github.bhlangonijr.chesslib.game.*;
import com.github.bhlangonijr.chesslib.move.MoveList; import com.github.bhlangonijr.chesslib.move.MoveList;
import java.time.LocalDate;
import com.github.bhlangonijr.chesslib.Side; import com.github.bhlangonijr.chesslib.Side;
public class ChessEngine { public class ChessEngine {
private Board board; private Board board;
private List<Move> moves = new ArrayList<>(); private List<Move> moves = new ArrayList<>();
private String initialFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; private String initialFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
private static final Logger logger = Logger.getLogger(ChessEngine.class.getName()); private static final Logger logger = Logger.getLogger(ChessEngine.class.getName());
private int quicksaveWhiteTimeLeft = -1;
private int quicksaveBlackTimeLeft = -1;
private int currentMoveIndex = 0; private int currentMoveIndex = 0;
private Timer whiteTimer; private Timer whiteTimer;
private Timer blackTimer; private Timer blackTimer;
private final GameMode mode; private final GameMode mode;
private String quicksaveFen = null;
private List<Move> quicksaveMoves = null;
private int quicksaveMoveIndex = 0;
private Opening detectedOpening = null; private Opening detectedOpening = null;
public ChessEngine() { public ChessEngine() {
this.mode = null; this.mode = null;
logging();
board = new Board();
}
public ChessEngine(GameMode mode) {
this.mode = mode;
whiteTimer = new Timer(mode.minutes, mode.seconds);
blackTimer = new Timer(mode.minutes, mode.seconds);
logging(); logging();
board = new Board(); 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());
Move libMove = new Move(Square.valueOf(from), Square.valueOf(to));
if (board.legalMoves().contains(libMove)) {
board.doMove(libMove);
// Replay? Dann abschneiden public ChessEngine(GameMode mode) {
if (currentMoveIndex < moves.size()) { this.mode = mode;
logger.info("Replay-Modus: Züge nach " + currentMoveIndex + " werden entfernt."); whiteTimer = new Timer(mode.minutes, mode.seconds);
moves = new ArrayList<>(moves.subList(0, currentMoveIndex)); blackTimer = new Timer(mode.minutes, mode.seconds);
} logging();
moves.add(libMove); board = new Board();
currentMoveIndex++; }
logger.info("Zug erfolgreich durchgeführt: " + libMove);
// Opening-Erkennung nach jedem erfolgreichen Zug public boolean move(MoveDTO move) {
String playedMovesUci = movesToUciString(moves); String from = "" + (char) ('A' + move.getFromCol()) + (8 - move.getFromRow());
detectedOpening = Opening.detect(playedMovesUci); String to = "" + (char) ('A' + move.getToCol()) + (8 - move.getToRow());
Move libMove = new Move(Square.valueOf(from), Square.valueOf(to));
if (board.legalMoves().contains(libMove)) {
board.doMove(libMove);
if (detectedOpening != null) { if (currentMoveIndex < moves.size()) {
logger.info("Aktuelles Opening erkannt: " + detectedOpening.getEco() + " - " + detectedOpening.getName()); if (logger.isLoggable(Level.INFO)) {
// Optional: Speichere das Opening in ein Feld, falls benötigt logger.info("Replay-Modus: Züge nach " + currentMoveIndex + " werden entfernt.");
} }
return true; moves = new ArrayList<>(moves.subList(0, currentMoveIndex));
} }
logger.warning("Ungültiger Zug: " + libMove); moves.add(libMove);
return false; currentMoveIndex++;
} if (logger.isLoggable(Level.INFO)) {
logger.info("Zug erfolgreich durchgeführt: " + libMove);
}
public String getOpeningName() { String playedMovesUci = movesToUciString(moves);
if (detectedOpening != null) { detectedOpening = Opening.detect(playedMovesUci);
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<>();
try {
Square fromSq = Square.valueOf(from.toUpperCase());
for (Move move : board.legalMoves()) {
if (move.getFrom().equals(fromSq)) {
int fromRow = 8 - fromSq.getRank().ordinal() - 1;
int fromCol = fromSq.getFile().ordinal();
int toRow = 8 - move.getTo().getRank().ordinal() - 1;
int toCol = move.getTo().getFile().ordinal();
destinations.add(new MoveDTO(fromRow, fromCol, toRow, toCol));
}
}
logger.info("Es wurden " + destinations.size() + " Ziele gefunden.");
} catch (Exception e) {
logger.severe("Fehler beim Holen der legalen Ziele: " + e.getMessage());
}
return destinations;
}
public List<String> getMoveListStringsGrouped() {
logger.info("Gruppiere Züge für Anzeige.");
List<String> result = new ArrayList<>();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < moves.size(); i++) {
if (i % 2 == 0) sb.append((i/2 + 1) + ". ");
sb.append(moves.get(i).toString()).append(" ");
if (i % 2 == 1 || i == moves.size() - 1) {
result.add(sb.toString().trim());
sb = new StringBuilder();
}
}
return result;
}
public PieceDTO getPieceAt(String square) {
logger.info("Hole Figur an Feld: " + square);
Piece piece = board.getPiece(Square.valueOf(square.toUpperCase()));
return convertPieceToDTO(piece);
}
public boolean moveWithPromotion(MoveDTO move, String promotionPiece) {
String from = "" + (char)('A' + move.getFromCol()) + (8 - move.getFromRow());
String to = "" + (char)('A' + move.getToCol()) + (8 - move.getToRow());
// Die Farbe bestimmen! if (detectedOpening != null && logger.isLoggable(Level.INFO)) {
boolean isWhite = (8 - move.getFromRow()) < (8 - move.getToRow()); logger.info("Aktuelles Opening erkannt: " + detectedOpening.getEco() + " - " + detectedOpening.getName());
Piece promotion; }
switch (promotionPiece) { return true;
case "ROOK": promotion = isWhite ? Piece.WHITE_ROOK : Piece.BLACK_ROOK; break; }
case "KNIGHT": promotion = isWhite ? Piece.WHITE_KNIGHT : Piece.BLACK_KNIGHT; break; if (logger.isLoggable(Level.WARNING)) {
case "BISHOP": promotion = isWhite ? Piece.WHITE_BISHOP : Piece.BLACK_BISHOP; break; logger.warning("Ungültiger Zug: " + libMove);
default: promotion = isWhite ? Piece.WHITE_QUEEN : Piece.BLACK_QUEEN; }
} return false;
}
Move libMove = new Move(Square.valueOf(from), Square.valueOf(to), promotion); public String getOpeningName() {
if (detectedOpening != null) {
return detectedOpening.getEco() + " - " + detectedOpening.getName();
} else {
return "unbekannt";
}
}
if (board.legalMoves().contains(libMove)) { private String movesToUciString(List<Move> moves) {
board.doMove(libMove); StringBuilder sb = new StringBuilder();
for (Move m : moves) {
sb.append(m.toString()).append(" ");
}
return sb.toString().trim();
}
if (currentMoveIndex < moves.size()) { public List<MoveDTO> getLegalDestinations(String from) {
moves = new ArrayList<>(moves.subList(0, currentMoveIndex)); if (logger.isLoggable(Level.INFO)) {
} logger.info("Hole legale Züge von: " + from);
moves.add(libMove); }
currentMoveIndex++; List<MoveDTO> destinations = new ArrayList<>();
logger.info("Promotionszug durchgeführt: " + libMove); try {
return true; Square fromSq = Square.valueOf(from.toUpperCase(java.util.Locale.ROOT));
} for (Move move : board.legalMoves()) {
logger.warning("Ungültiger Promotionszug: " + libMove); if (move.getFrom().equals(fromSq)) {
return false; int fromRow = 8 - fromSq.getRank().ordinal() - 1;
} int fromCol = fromSq.getFile().ordinal();
int toRow = 8 - move.getTo().getRank().ordinal() - 1;
public BoardDTO getBoardAsDTO() { int toCol = move.getTo().getFile().ordinal();
logger.info("Erstelle DTO-Abbild des Boards"); destinations.add(new MoveDTO(fromRow, fromCol, toRow, toCol));
PieceDTO[][] dtoBoard = new PieceDTO[8][8]; }
for (int rank = 8; rank >= 1; rank--) { }
for (int file = 0; file < 8; file++) { if (logger.isLoggable(Level.INFO)) {
Square square = Square.valueOf("" + (char)('A' + file) + rank); logger.info("Es wurden " + destinations.size() + " Ziele gefunden.");
Piece piece = board.getPiece(square); }
dtoBoard[8-rank][file] = convertPieceToDTO(piece); } catch (Exception e) {
} if (logger.isLoggable(Level.SEVERE)) {
} logger.severe("Fehler beim Holen der legalen Ziele: " + e.getMessage());
return new BoardDTO(dtoBoard); }
} }
return destinations;
private String pieceToUnicode(Piece piece) { }
switch (piece) {
case WHITE_KING: return "♔";
case WHITE_QUEEN: return "♕";
case WHITE_ROOK: return "♖";
case WHITE_BISHOP: return "♗";
case WHITE_KNIGHT: return "♘";
case WHITE_PAWN: return "♙";
case BLACK_KING: return "♚";
case BLACK_QUEEN: return "♛";
case BLACK_ROOK: return "♜";
case BLACK_BISHOP: return "♝";
case BLACK_KNIGHT: return "♞";
case BLACK_PAWN: return "♟";
default: return " ";
}
}
public void setPositionToMoveIndex(int idx) {
logger.info("Setze Board auf Zug-Index: " + idx);
board = new Board();
board.loadFromFen(initialFen); // Statt new Board() -> initialFen!
for (int i = 0; i < idx; i++) {
board.doMove(moves.get(i));
}
currentMoveIndex = idx;
String playedMovesUci = movesToUciString(moves.subList(0, idx));
detectedOpening = Opening.detect(playedMovesUci);
}
public int getCurrentMoveIndex() {
logger.info("Hole aktuellen Zug-Index: " + currentMoveIndex);
return currentMoveIndex;
}
public int getMoveListSize() {
logger.info("Hole Anzahl gespielter Züge: " + moves.size());
return moves.size();
}
private PieceDTO convertPieceToDTO(Piece piece) {
if (piece == null || piece.equals(Piece.NONE)) return null;
String color = piece.name().startsWith("WHITE") ? "WHITE" : "BLACK";
String type = piece.name().substring(piece.name().indexOf('_') + 1); // "PAWN", "KING"...
String symbol = pieceToUnicode(piece);
return new PieceDTO(type, color, symbol);
}
public void setPositionFromFEN(String fen) {
board.loadFromFen(fen);
initialFen = fen;
}
public List<String> getMoveListStringsGrouped() {
public boolean isMated() { if (logger.isLoggable(Level.INFO)) {
boolean mated = board.isMated(); logger.info("Gruppiere Züge für Anzeige.");
logger.info("isMated() = " + mated); }
return mated; List<String> result = new ArrayList<>();
} StringBuilder sb = new StringBuilder();
for (int i = 0; i < moves.size(); i++) {
public boolean isStalemate() { if (i % 2 == 0) {
boolean stale = board.isStaleMate(); sb.append((i / 2 + 1)).append(". ");
logger.info("isStalemate() = " + stale); }
return stale; sb.append(moves.get(i).toString()).append(" ");
} if (i % 2 == 1 || i == moves.size() - 1) {
result.add(sb.toString().trim());
public boolean isDraw() { sb = new StringBuilder();
boolean draw = board.isDraw(); }
logger.info("isDraw() = " + draw); }
return draw; return result;
} }
public String getCurrentPlayer() {
String player = board.getSideToMove().toString();
logger.info("Am Zug: " + player);
return player;
}
public void logging() {
// Eigener Handler nur für diese Klasse
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.ALL);
handler.setFormatter(new SimpleFormatter() {
@Override
public synchronized String format(LogRecord lr) {
return String.format("[%s] %s%n%n", lr.getLevel().getLocalizedName(), lr.getMessage());
}
});
logger.setUseParentHandlers(false);
logger.addHandler(handler);
logger.info("ChessEngine wurde initialisiert.");
}
public List<Game> loadGamesFromPgn(String path) throws IOException {
PgnHolder pgnHolder = new PgnHolder(path); public void quicksave() {
try { this.quicksaveFen = board.getFen();
this.quicksaveMoves = new ArrayList<>(moves);
this.quicksaveMoveIndex = currentMoveIndex;
if (whiteTimer != null) {
quicksaveWhiteTimeLeft = whiteTimer.getSecondsLeft();
}
if (blackTimer != null) {
quicksaveBlackTimeLeft = blackTimer.getSecondsLeft();
}
}
public boolean quickload() {
if (quicksaveFen == null) {
return false;
}
board = new Board();
board.loadFromFen(quicksaveFen);
moves = new ArrayList<>(quicksaveMoves);
currentMoveIndex = quicksaveMoveIndex;
if (whiteTimer != null && quicksaveWhiteTimeLeft != -1) {
whiteTimer.setSecondsLeft(quicksaveWhiteTimeLeft);
}
if (blackTimer != null && quicksaveBlackTimeLeft != -1) {
blackTimer.setSecondsLeft(quicksaveBlackTimeLeft);
}
return true;
}
public void clearQuicksave() {
quicksaveFen = null;
quicksaveMoves = null;
quicksaveMoveIndex = 0;
}
public PieceDTO getPieceAt(String square) {
if (logger.isLoggable(Level.INFO)) {
logger.info("Hole Figur an Feld: " + square);
}
Piece piece = board.getPiece(Square.valueOf(square.toUpperCase(java.util.Locale.ROOT)));
return convertPieceToDTO(piece);
}
public boolean moveWithPromotion(MoveDTO move, String promotionPiece) {
String from = "" + (char) ('A' + move.getFromCol()) + (8 - move.getFromRow());
String to = "" + (char) ('A' + move.getToCol()) + (8 - move.getToRow());
boolean isWhite = (8 - move.getFromRow()) < (8 - move.getToRow());
Piece promotion;
switch (promotionPiece) {
case "ROOK":
promotion = isWhite ? Piece.WHITE_ROOK : Piece.BLACK_ROOK;
break;
case "KNIGHT":
promotion = isWhite ? Piece.WHITE_KNIGHT : Piece.BLACK_KNIGHT;
break;
case "BISHOP":
promotion = isWhite ? Piece.WHITE_BISHOP : Piece.BLACK_BISHOP;
break;
default:
promotion = isWhite ? Piece.WHITE_QUEEN : Piece.BLACK_QUEEN;
break;
}
Move libMove = new Move(Square.valueOf(from), Square.valueOf(to), promotion);
if (board.legalMoves().contains(libMove)) {
board.doMove(libMove);
if (currentMoveIndex < moves.size()) {
moves = new ArrayList<>(moves.subList(0, currentMoveIndex));
}
moves.add(libMove);
currentMoveIndex++;
if (logger.isLoggable(Level.INFO)) {
logger.info("Promotionszug durchgeführt: " + libMove);
}
return true;
}
if (logger.isLoggable(Level.WARNING)) {
logger.warning("Ungültiger Promotionszug: " + libMove);
}
return false;
}
public BoardDTO getBoardAsDTO() {
if (logger.isLoggable(Level.INFO)) {
logger.info("Erstelle DTO-Abbild des Boards");
}
PieceDTO[][] dtoBoard = new PieceDTO[8][8];
for (int rank = 8; rank >= 1; rank--) {
for (int file = 0; file < 8; file++) {
Square square = Square.valueOf("" + (char) ('A' + file) + rank);
Piece piece = board.getPiece(square);
dtoBoard[8 - rank][file] = convertPieceToDTO(piece);
}
}
return new BoardDTO(dtoBoard);
}
private String pieceToUnicode(Piece piece) {
switch (piece) {
case WHITE_KING:
return "♔";
case WHITE_QUEEN:
return "♕";
case WHITE_ROOK:
return "♖";
case WHITE_BISHOP:
return "♗";
case WHITE_KNIGHT:
return "♘";
case WHITE_PAWN:
return "♙";
case BLACK_KING:
return "♚";
case BLACK_QUEEN:
return "♛";
case BLACK_ROOK:
return "♜";
case BLACK_BISHOP:
return "♝";
case BLACK_KNIGHT:
return "♞";
case BLACK_PAWN:
return "♟";
default:
return " ";
}
}
public void setPositionToMoveIndex(int idx) {
if (logger.isLoggable(Level.INFO)) {
logger.info("Setze Board auf Zug-Index: " + idx);
}
board = new Board();
board.loadFromFen(initialFen);
for (int i = 0; i < idx; i++) {
board.doMove(moves.get(i));
}
currentMoveIndex = idx;
String playedMovesUci = movesToUciString(moves.subList(0, idx));
detectedOpening = Opening.detect(playedMovesUci);
}
public int getCurrentMoveIndex() {
if (logger.isLoggable(Level.INFO)) {
logger.info("Hole aktuellen Zug-Index: " + currentMoveIndex);
}
return currentMoveIndex;
}
public int getMoveListSize() {
if (logger.isLoggable(Level.INFO)) {
logger.info("Hole Anzahl gespielter Züge: " + moves.size());
}
return moves.size();
}
private PieceDTO convertPieceToDTO(Piece piece) {
if (piece == null || piece.equals(Piece.NONE)) {
return null;
}
String color = piece.name().startsWith("WHITE") ? "WHITE" : "BLACK";
String type = piece.name().substring(piece.name().indexOf('_') + 1);
String symbol = pieceToUnicode(piece);
return new PieceDTO(type, color, symbol);
}
public void setPositionFromFEN(String fen) {
board.loadFromFen(fen);
initialFen = fen;
}
public boolean isMated() {
boolean mated = board.isMated();
if (logger.isLoggable(Level.INFO)) {
logger.info("isMated() = " + mated);
}
return mated;
}
public boolean isStalemate() {
boolean stale = board.isStaleMate();
if (logger.isLoggable(Level.INFO)) {
logger.info("isStalemate() = " + stale);
}
return stale;
}
public boolean isDraw() {
boolean draw = board.isDraw();
if (logger.isLoggable(Level.INFO)) {
logger.info("isDraw() = " + draw);
}
return draw;
}
public String getCurrentPlayer() {
String player = board.getSideToMove().toString();
if (logger.isLoggable(Level.INFO)) {
logger.info("Am Zug: " + player);
}
return player;
}
public void logging() {
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.ALL);
handler.setFormatter(new SimpleFormatter() {
@Override
public synchronized String format(LogRecord lr) {
return String.format("[%s] %s%n%n", lr.getLevel().getLocalizedName(), lr.getMessage());
}
});
logger.setUseParentHandlers(false);
logger.addHandler(handler);
if (logger.isLoggable(Level.INFO)) {
logger.info("ChessEngine wurde initialisiert.");
}
}
public List<Game> loadGamesFromPgn(String path) throws IOException {
PgnHolder pgnHolder = new PgnHolder(path);
try {
pgnHolder.loadPgn(); pgnHolder.loadPgn();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
List<Game> games = pgnHolder.getGames(); return pgnHolder.getGames();
return games;
}
public void initTimers(int min, int sec) {
whiteTimer = new Timer(min, sec);
blackTimer = new Timer(min, sec);
}
public void saveAsPgn(Game game, String path, String dateiname) {
// Sicher alle Strings holen (nie null)
String event = safe(game.getRound().getEvent().getName());
String site = safe(game.getRound().getEvent().getSite());
String round = "" + game.getRound().getNumber();
// Datum für PGN-Format (YYYY.MM.DD)
String date = safe(game.getRound().getEvent().getStartDate()).replace("-", ".");
String wName = safe(game.getWhitePlayer().getName());
String bName = safe(game.getBlackPlayer().getName());
String result = safe(game.getResult().getDescription());
// PGN-Header zusammenbauen
StringBuilder header = new StringBuilder();
header.append("[Event \"" + event + "\"]\n");
header.append("[Site \"" + site + "\"]\n");
header.append("[Date \"" + date + "\"]\n");
header.append("[Round \"" + round + "\"]\n");
header.append("[White \"" + wName + "\"]\n");
header.append("[Black \"" + bName + "\"]\n");
header.append("[Result \"" + result + "\"]\n\n");
// Züge als SAN holen
StringBuilder moves = new StringBuilder();
String[] sanArray = game.getHalfMoves().toSanArray();
for (int i = 0; i < sanArray.length; i++) {
if (i % 2 == 0) {
moves.append((i / 2 + 1)).append(". ");
}
moves.append(sanArray[i]).append(" ");
// Optional: Zeilenumbruch für Lesbarkeit
// if (i > 0 && i % 8 == 0) moves.append("\n");
}
moves.append(result); // Ergebnis am Ende!
String file = header + moves.toString();
// Datei schreiben
try {
Files.writeString(Path.of(path, dateiname), file, StandardCharsets.UTF_8);
} catch (IOException e) {
e.printStackTrace();
}
}
// Hilfsfunktion für Null-Sicherheit
private String safe(String s) {
return s == null ? "?" : s;
}
public Game getCurrentGame() {
return getCurrentGame(this.board, this.moves, this.currentMoveIndex);
}
public Game getCurrentGame(Board board, java.util.List<Move> moves, int currentMoveIndex) {
// Event und Turnierdaten setzen
Event event = new Event();
event.setName("Generated Game");
event.setSite("Local");
event.setStartDate(LocalDate.now().toString()); // Format: yyyy-MM-dd
// Runde anlegen
Round round = new Round(event);
round.setNumber(1);
// Spiel initialisieren
Game game = new Game("1", round); // "1" ist die Game-ID
// Spieler setzen (deine MyPlayer-Klasse)
game.setWhitePlayer(new MyPlayer("White"));
game.setBlackPlayer(new MyPlayer("Black"));
// Ergebnis setzen
if (board.isMated()) {
game.setResult(board.getSideToMove() == Side.WHITE ? GameResult.BLACK_WON : GameResult.WHITE_WON);
} else if (board.isStaleMate() || board.isDraw()) {
game.setResult(GameResult.DRAW);
} else {
game.setResult(GameResult.ONGOING);
}
// Züge übernehmen
MoveList moveList = new MoveList();
for (Move move : moves) {
moveList.add(move);
}
game.setHalfMoves(moveList);
// Position auf aktuellen Zug setzen (letzter gespielter Halbzug)
if (currentMoveIndex > 0 && currentMoveIndex <= moveList.size()) {
game.setPosition(currentMoveIndex - 1);
} else {
game.setPosition(moveList.size() - 1);
}
// FEN setzen: JETZT das aktuelle Board-FEN verwenden!
game.setBoard(new Board());
game.getBoard().loadFromFen(board.getFen());
return game;
}
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
moves.clear();
currentMoveIndex = 0;
for (Move move : moveList) {
board.doMove(move);
moves.add(move);
currentMoveIndex++;
}
}
public Timer getWhiteTimer() { return whiteTimer; }
public Timer getBlackTimer() { return blackTimer; }
public GameMode getGameMode() {
// TODO Auto-generated method stub
return mode;
} }
public void initTimers(int min, int sec) {
if (whiteTimer == null) {
whiteTimer = new Timer(min, sec);
} else if (quicksaveWhiteTimeLeft != -1) {
whiteTimer = new Timer(min, sec);
whiteTimer.setSecondsLeft(quicksaveWhiteTimeLeft);
} else {
whiteTimer = new Timer(min, sec);
}
if (blackTimer == null) {
blackTimer = new Timer(min, sec);
} else if (quicksaveBlackTimeLeft != -1) {
blackTimer = new Timer(min, sec);
blackTimer.setSecondsLeft(quicksaveBlackTimeLeft);
} else {
blackTimer = new Timer(min, sec);
}
}
public void saveAsPgn(Game game, String path, String dateiname) {
String event = safe(game.getRound().getEvent().getName());
String site = safe(game.getRound().getEvent().getSite());
String round = "" + game.getRound().getNumber();
String date = safe(game.getRound().getEvent().getStartDate()).replace("-", ".");
String wName = safe(game.getWhitePlayer().getName());
String bName = safe(game.getBlackPlayer().getName());
String result = safe(game.getResult().getDescription());
StringBuilder header = new StringBuilder();
header.append("[Event \"" + event + "\"]\n");
header.append("[Site \"" + site + "\"]\n");
header.append("[Date \"" + date + "\"]\n");
header.append("[Round \"" + round + "\"]\n");
header.append("[White \"" + wName + "\"]\n");
header.append("[Black \"" + bName + "\"]\n");
header.append("[Result \"" + result + "\"]\n\n");
StringBuilder movesSb = new StringBuilder();
String[] sanArray = game.getHalfMoves().toSanArray();
for (int i = 0; i < sanArray.length; i++) {
if (i % 2 == 0) {
movesSb.append((i / 2 + 1)).append(". ");
}
movesSb.append(sanArray[i]).append(" ");
}
movesSb.append(result);
String file = header + movesSb.toString();
try {
Files.writeString(Path.of(path, dateiname), file, StandardCharsets.UTF_8);
} catch (IOException e) {
e.printStackTrace();
}
}
private String safe(String s) {
return s == null ? "?" : s;
}
public Game getCurrentGame() {
return getCurrentGame(this.board, this.moves, this.currentMoveIndex);
}
public Game getCurrentGame(Board board, List<Move> moves, int currentMoveIndex) {
Event event = new Event();
event.setName("Generated Game");
event.setSite("Local");
event.setStartDate(LocalDate.now().toString());
Round round = new Round(event);
round.setNumber(1);
Game game = new Game("1", round);
game.setWhitePlayer(new MyPlayer("White"));
game.setBlackPlayer(new MyPlayer("Black"));
if (board.isMated()) {
game.setResult(board.getSideToMove() == Side.WHITE ? GameResult.BLACK_WON : GameResult.WHITE_WON);
} else if (board.isStaleMate() || board.isDraw()) {
game.setResult(GameResult.DRAW);
} else {
game.setResult(GameResult.ONGOING);
}
MoveList moveList = new MoveList();
for (Move move : moves) {
moveList.add(move);
}
game.setHalfMoves(moveList);
if (currentMoveIndex > 0 && currentMoveIndex <= moveList.size()) {
game.setPosition(currentMoveIndex - 1);
} else {
game.setPosition(moveList.size() - 1);
}
game.setBoard(new Board());
game.getBoard().loadFromFen(board.getFen());
return game;
}
public void undoLastMove() {
if (currentMoveIndex > 0 && !moves.isEmpty()) {
board.undoMove();
moves.remove(currentMoveIndex - 1);
currentMoveIndex--;
String playedMovesUci = movesToUciString(moves.subList(0, currentMoveIndex));
detectedOpening = Opening.detect(playedMovesUci);
}
}
public void loadMoves(List<Move> moveList) {
board = new Board();
moves.clear();
currentMoveIndex = 0;
for (Move move : moveList) {
board.doMove(move);
moves.add(move);
currentMoveIndex++;
}
}
public Timer getWhiteTimer() {
return whiteTimer;
}
public Timer getBlackTimer() {
return blackTimer;
}
public GameMode getGameMode() {
return mode;
}
} }

View File

@ -57,6 +57,9 @@ public class Timer {
return secondsLeft; return secondsLeft;
} }
public void setSecondsLeft(int secondsLeft) {
this.secondsLeft = secondsLeft;
}
public void addSeconds(int seconds) { public void addSeconds(int seconds) {
this.secondsLeft += seconds; this.secondsLeft += seconds;
} }

View File

@ -26,6 +26,7 @@ import de.hs_mannheim.informatik.chess.model.BoardDTO;
import de.hs_mannheim.informatik.chess.model.PieceDTO; import de.hs_mannheim.informatik.chess.model.PieceDTO;
public class GameGui { public class GameGui {
JFrame frame;
private JLabel[][] fields = new JLabel[8][8]; private JLabel[][] fields = new JLabel[8][8];
private JButton flipBoardButton; private JButton flipBoardButton;
@ -43,7 +44,6 @@ public class GameGui {
private JButton resignButton; private JButton resignButton;
private JButton drawButton; private JButton drawButton;
private JButton quickSaveButton;
private JButton undoButton; private JButton undoButton;
Color LIGHT = new Color(0xe0e1dd); Color LIGHT = new Color(0xe0e1dd);
@ -59,13 +59,14 @@ public class GameGui {
public JFrame mainFrame() { public JFrame mainFrame() {
JFrame frame = new JFrame(); frame = new JFrame();
frame.setExtendedState(JFrame.MAXIMIZED_BOTH); frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setUndecorated(false); frame.setUndecorated(false);
frame.setLocationRelativeTo(null); frame.setLocationRelativeTo(null);
frame.add(mainPanel()); frame.add(mainPanel());
frame.setDefaultCloseOperation(2); frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.setVisible(true); frame.setVisible(true);
return frame; return frame;
} }
@ -378,4 +379,8 @@ public class GameGui {
JOptionPane.showMessageDialog(null, msg); JOptionPane.showMessageDialog(null, msg);
} }
public JFrame getFrame() {
return frame;
}
} }

View File

@ -1,102 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.hs-mannheim.informatik.schach</groupId>
<artifactId>schach</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>schach</name>
<description>A simple schach.</description>
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<!-- Schachlib (bhlangonijr) -->
<dependency>
<groupId>com.github.bhlangonijr</groupId>
<artifactId>chesslib</artifactId>
<version>1.3.4</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
<!-- Mockito damit man Controller und Gui besser testen kann-->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.4.0</version>
</plugin>
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.12.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.6.1</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.3.0</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>3.1.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.1.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
<reporting>
<plugins>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
</plugin>
</plugins>
</reporting>
</project>