Compare commits

..

6 Commits

Author SHA1 Message Date
Justin a669a4b2ff Reworked and fixed issues with savePgn feature 2025-06-23 21:20:30 +02:00
Justin 12bea41b1e Timer class reworked 2025-06-23 20:47:28 +02:00
Justin cdc418afb4 New Callback class to open new GameGui after a chess game 2025-06-23 20:47:16 +02:00
Justin 268f5e2398 Implemented timer for testing new chess mode 2025-06-23 19:14:01 +02:00
valen ea2c0066fd Die Klasse wurde beim letzten "Commi and Push" nicht mit genommen 2025-06-23 13:16:52 +02:00
valen ffbdb8b6e0 Aktuelles Spiel kann als pgn an gewünschten Speicherort, mit gewünschten
Namen gespeichert werden.
Header bis auf Datum ist immer Gleich
2025-06-23 13:16:13 +02:00
7 changed files with 384 additions and 29 deletions

View File

@ -8,25 +8,40 @@ import java.util.List;
import javax.swing.BorderFactory;
import com.github.bhlangonijr.chesslib.game.Game;
import de.hs_mannheim.informatik.chess.model.ChessEngine;
import de.hs_mannheim.informatik.chess.model.MoveDTO;
import de.hs_mannheim.informatik.chess.model.PieceDTO;
import de.hs_mannheim.informatik.chess.model.BoardDTO;
import de.hs_mannheim.informatik.chess.view.GameGui;
import de.hs_mannheim.informatik.chess.view.MainGui;
public class GameController {
GameGui gui;
ChessEngine engine;
GameEndCallback callback;
private boolean gameOver = false;
private int selectedRow = -1, selectedCol = -1;
private List<int[]> highlightedFields = new ArrayList<>();
public GameController(GameGui gui, ChessEngine engine) {
public GameController(GameGui gui, ChessEngine engine, GameEndCallback callback) {
this.gui = gui;
this.engine = engine;
this.callback = callback;
engine.initTimers(3, 0);
time();
initListeners();
updateGuiBoard();
}
private void time() {
engine.getWhiteTimer().setOnTick(secs -> gui.updateWhiteTimerLabel(secs));
engine.getBlackTimer().setOnTick(secs -> gui.updateBlackTimerLabel(secs));
engine.getWhiteTimer().setOnTimeout(() -> onTimeout("WHITE"));
engine.getBlackTimer().setOnTimeout(() -> onTimeout("BLACK"));
engine.getWhiteTimer().start();
}
private int flipRow(int row) {
return gui.isFlipped() ? 7 - row : row;
}
@ -68,6 +83,25 @@ public class GameController {
engine.setPositionToMoveIndex(engine.getMoveListSize());
updateGuiBoard();
});
gui.getBtnSave().addActionListener(e -> {
System.out.println("Save-Button wurde geklickt!");
Game currentGame = engine.getCurrentGame();
javax.swing.JFileChooser fileChooser = new javax.swing.JFileChooser();
fileChooser.setDialogTitle("PGN speichern unter...");
int userSelection = fileChooser.showSaveDialog(null);
if (userSelection == javax.swing.JFileChooser.APPROVE_OPTION) {
java.io.File fileToSave = fileChooser.getSelectedFile();
try {
engine.saveAsPgn(currentGame, fileToSave.getParent(), fileToSave.getName());
gui.displayMessage("PGN gespeichert: " + fileToSave.getAbsolutePath());
} catch (Exception ex) {
gui.displayMessage("Fehler beim Speichern: " + ex.getMessage());
}
}
});
gui.getFlipBoardButton().addActionListener(e -> {
@ -93,6 +127,9 @@ public class GameController {
}
private void handleClick(int guiRow, int guiCol) {
if (gameOver) return;
int modelRow = flipRow(guiRow);
int modelCol = flipCol(guiCol);
@ -138,6 +175,9 @@ public class GameController {
}
private void handleMove(MoveDTO move) {
if (gameOver) return;
BoardDTO boardDTO = engine.getBoardAsDTO();
PieceDTO piece = boardDTO.getBoard()[move.getFromRow()][move.getFromCol()];
boolean isPawn = piece != null && piece.getType().equals("PAWN");
@ -169,8 +209,16 @@ public class GameController {
if (engine.isMated()) {
String winner = engine.getCurrentPlayer().equals("WHITE") ? "SCHWARZ" : "WEIß";
gui.displayMessage(winner + " hat gewonnen (Schachmatt)!");
gameOver = true;
askForRestart();
} else if (engine.isStalemate() || engine.isDraw()) {
gui.displayMessage("Remis! (Stalemate oder andere Regel)");
gameOver = true;
askForRestart();
}
if (!engine.isMated() && !engine.isStalemate() && !engine.isDraw()) {
switchTimers();
}
}
@ -179,15 +227,52 @@ public class GameController {
BoardDTO board = engine.getBoardAsDTO();
gui.updateBoard(board);
}
private void switchTimers() {
if (engine.getCurrentPlayer().equals("WHITE")) {
engine.getBlackTimer().stop();
engine.getWhiteTimer().start();
} else {
engine.getWhiteTimer().stop();
engine.getBlackTimer().start();
}
}
// Hilfsmethode, um von Koordinaten (row/col) auf z.B. "E2" zu kommen
private String coordToChessNotation(int modelRow, int modelCol) {
char file = (char)('A' + modelCol);
int rank = 8 - modelRow;
return "" + file + rank;
}
// Timeout-Methode
private void onTimeout(String color) {
if (gameOver) return; // Doppelt hält besser
gameOver = true;
String winner = color.equals("WHITE") ? "SCHWARZ" : "WEIß";
gui.displayMessage(winner + " hat durch Zeit gewonnen!");
engine.getWhiteTimer().stop();
engine.getBlackTimer().stop();
askForRestart();
}
private void askForRestart() {
int answer = javax.swing.JOptionPane.showConfirmDialog(
null,
"Neue Partie starten?",
"Spiel beendet",
javax.swing.JOptionPane.YES_NO_OPTION
);
javax.swing.SwingUtilities.getWindowAncestor(gui.getField(0, 0)).dispose();
if (answer == javax.swing.JOptionPane.YES_OPTION) {
callback.onNewGameRequested();
} else {
callback.onReturnToMenu();
}
}
private void resetFieldBackground(int row, int col) {
Color LIGHT = new Color(0xe0e1dd);
Color DARK = new Color(0x778da9);

View File

@ -0,0 +1,6 @@
package de.hs_mannheim.informatik.chess.controller;
public interface GameEndCallback {
void onNewGameRequested();
void onReturnToMenu();
}

View File

@ -31,7 +31,15 @@ public class MainController {
mainGui.close();
GameGui gameGui = new GameGui();
ChessEngine engine = new ChessEngine();
new GameController(gameGui, engine);
GameEndCallback callback = new GameEndCallback() {
public void onNewGameRequested() {
startNormalMode();
}
public void onReturnToMenu() {
new MainController();
}
};
new GameController(gameGui, engine, callback);
}
private void startCreativeMode() {
@ -40,7 +48,7 @@ public class MainController {
ChessEngine engine = new ChessEngine();
new CreativeController(creativegui, engine);
}
private void startLoadGameMode() {
JFileChooser chooser = new JFileChooser();
int result = chooser.showOpenDialog(null);

View File

@ -14,9 +14,13 @@ import java.util.logging.SimpleFormatter;
import com.github.bhlangonijr.chesslib.Board;
import com.github.bhlangonijr.chesslib.Piece;
import com.github.bhlangonijr.chesslib.Square;
import com.github.bhlangonijr.chesslib.game.Game;
import com.github.bhlangonijr.chesslib.move.Move;
import com.github.bhlangonijr.chesslib.pgn.PgnHolder;
import com.github.bhlangonijr.chesslib.game.*;
import com.github.bhlangonijr.chesslib.move.MoveList;
import java.time.LocalDate;
import com.github.bhlangonijr.chesslib.Side;
public class ChessEngine {
private Board board;
@ -24,6 +28,8 @@ public class ChessEngine {
private static final Logger logger = Logger.getLogger(ChessEngine.class.getName());
private int currentMoveIndex = 0;
private Timer whiteTimer;
private Timer blackTimer;
public ChessEngine() {
logging();
@ -241,15 +247,23 @@ public class ChessEngine {
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) {
String event = game.getRound().getEvent().getName();
String site = game.getRound().getEvent().getSite();
// 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();
String date = game.getRound().getEvent().getStartDate();
String wName = game.getWhitePlayer().getName();
String bName = game.getBlackPlayer().getName();
String result = game.getResult().getDescription();
// 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");
@ -257,29 +271,90 @@ public class ChessEngine {
header.append("[Round \"" + round + "\"]\n");
header.append("[White \"" + wName + "\"]\n");
header.append("[Black \"" + bName + "\"]\n");
header.append("[Result \"" + result + "\"]\n");
header.append("\n");
header.append("[Result \"" + result + "\"]\n\n");
StringBuilder sb = new StringBuilder();
// 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) {
sb.append((i / 2 + 1)).append(". ");
moves.append((i / 2 + 1)).append(". ");
}
sb.append(sanArray[i]).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!
sb.append(result); // Endergebnis muss auch am Ende stehen!
String file = header.toString() + sb.toString();
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 loadMoves(List<Move> moveList) {
board = new Board(); // Neues leeres Brett
@ -294,4 +369,7 @@ public class ChessEngine {
}
public Timer getWhiteTimer() { return whiteTimer; }
public Timer getBlackTimer() { return blackTimer; }
}

View File

@ -0,0 +1,72 @@
package de.hs_mannheim.informatik.chess.model;
import com.github.bhlangonijr.chesslib.game.Player;
import com.github.bhlangonijr.chesslib.game.PlayerType;
public class MyPlayer implements Player {
private String id = "";
private String name;
private int elo = 0;
private PlayerType type = PlayerType.HUMAN;
private String description = "";
private String longDescription = "";
public MyPlayer(String name) {
this.name = name;
}
@Override
public String getId() {
return id;
}
@Override
public void setId(String id) {
this.id = id;
}
@Override
public int getElo() {
return elo;
}
@Override
public void setElo(int elo) {
this.elo = elo;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public PlayerType getType() {
return type;
}
@Override
public void setType(PlayerType type) {
this.type = type;
}
@Override
public String getDescription() {
return description;
}
@Override
public void setDescription(String description) {
this.description = description;
}
@Override
public String getLongDescription() {
return longDescription;
}
}

View File

@ -0,0 +1,61 @@
package de.hs_mannheim.informatik.chess.model;
import java.util.function.Consumer;
public class Timer {
private int secondsLeft;
private javax.swing.Timer swingTimer;
private Runnable onTimeout;
private Consumer<Integer> onTick;
public Timer(int minutes, int seconds) {
this.secondsLeft = minutes * 60 + seconds;
}
public void setOnTimeout(Runnable onTimeout) {
this.onTimeout = onTimeout;
}
public void setOnTick(Consumer<Integer> onTick) {
this.onTick = onTick;
}
public void start() {
if (swingTimer != null && swingTimer.isRunning()) {
swingTimer.stop();
}
swingTimer = new javax.swing.Timer(1000, e -> {
secondsLeft--;
if (onTick != null) {
onTick.accept(secondsLeft);
}
if (secondsLeft <= 0) {
stop();
if (onTimeout != null) {
onTimeout.run();
}
}
});
swingTimer.start();
}
public void stop() {
if (swingTimer != null) {
swingTimer.stop();
}
}
public void reset(int minutes, int seconds) {
stop();
this.secondsLeft = minutes * 60 + seconds;
if (onTick != null) {
onTick.accept(secondsLeft);
}
}
public int getSecondsLeft() {
return secondsLeft;
}
}

View File

@ -27,6 +27,10 @@ public class GameGui {
private JLabel[][] fields = new JLabel[8][8];
private JButton flipBoardButton;
private boolean isFlipped = false;
JButton btnSave = new JButton("💾");
private JLabel whiteTimerLabel;
private JLabel blackTimerLabel;
JButton btnFirst = new JButton("|<");
JButton btnPrev = new JButton("<");
@ -160,6 +164,9 @@ public class GameGui {
JPanel statsPanel = new JPanel(new BorderLayout());
statsPanel.setBackground(new Color(0x0d1b2a));
// Panel für die Timer (NORD)
statsPanel.add(timerPanel(), BorderLayout.NORTH);
// Move-Liste
moveListPanel = new JPanel();
moveListPanel.setLayout(new BoxLayout(moveListPanel, BoxLayout.Y_AXIS));
@ -168,29 +175,48 @@ public class GameGui {
moveListScroll.setPreferredSize(new Dimension(250, 800));
statsPanel.add(moveListScroll, BorderLayout.CENTER);
// Button-Leiste
// Button-Leiste (SÜD)
JPanel buttonPanel = new JPanel();
buttonPanel.setBackground(new Color(0x0d1b2a));
// Grid oder Flow
buttonPanel.setLayout(new GridLayout(1, 4, 10, 0));
// Style (optional)
buttonPanel.setLayout(new GridLayout(1, 5, 10, 0)); // Jetzt 5 statt 4 Spalten!
btnFirst.setBackground(new Color(0x212529)); btnFirst.setForeground(Color.WHITE);
btnPrev.setBackground(new Color(0x212529)); btnPrev.setForeground(Color.WHITE);
btnNext.setBackground(new Color(0x212529)); btnNext.setForeground(Color.WHITE);
btnLast.setBackground(new Color(0x212529)); btnLast.setForeground(Color.WHITE);
// Hinzufügen
btnSave.setBackground(new Color(0x218838)); btnSave.setForeground(Color.WHITE);
buttonPanel.add(btnFirst);
buttonPanel.add(btnPrev);
buttonPanel.add(btnNext);
buttonPanel.add(btnLast);
// Unten ins BorderLayout
buttonPanel.add(btnSave);
statsPanel.add(buttonPanel, BorderLayout.SOUTH);
return statsPanel;
}
private JPanel timerPanel() {
JPanel panel = new JPanel(new GridLayout(2, 1));
panel.setBackground(new Color(0x0d1b2a));
whiteTimerLabel = new JLabel("Weiß: 03:00", SwingConstants.CENTER);
blackTimerLabel = new JLabel("Schwarz: 03:00", SwingConstants.CENTER);
whiteTimerLabel.setFont(new Font("SansSerif", Font.BOLD, 24));
whiteTimerLabel.setForeground(Color.WHITE);
whiteTimerLabel.setBackground(new Color(0x1b263b));
whiteTimerLabel.setOpaque(true);
blackTimerLabel.setFont(new Font("SansSerif", Font.BOLD, 24));
blackTimerLabel.setForeground(Color.WHITE);
blackTimerLabel.setBackground(new Color(0x1b263b));
blackTimerLabel.setOpaque(true);
panel.add(whiteTimerLabel);
panel.add(blackTimerLabel);
return panel;
}
public String showPromotionDialog(String color) {
String[] choices = {"Dame", "Turm", "Springer", "Läufer"};
@ -243,6 +269,7 @@ public class GameGui {
public JButton getBtnPrev() { return btnPrev; }
public JButton getBtnNext() { return btnNext; }
public JButton getBtnLast() { return btnLast; }
public JButton getBtnSave() { return btnSave; }
public void updateBoard(BoardDTO boardDTO) {
PieceDTO[][] board = boardDTO.getBoard();
@ -264,6 +291,24 @@ public class GameGui {
}
}
public void updateWhiteTimerLabel(int secondsLeft) {
whiteTimerLabel.setText("Weiß: " + formatTime(secondsLeft));
}
public void updateBlackTimerLabel(int secondsLeft) {
blackTimerLabel.setText("Schwarz: " + formatTime(secondsLeft));
}
private String formatTime(int seconds) {
int min = seconds / 60;
int sec = seconds % 60;
return String.format("%02d:%02d", min, sec);
}
// Optional: Getter, falls du direkt ran willst (braucht man aber fast nie)
public JLabel getWhiteTimerLabel() { return whiteTimerLabel; }
public JLabel getBlackTimerLabel() { return blackTimerLabel; }
public void displayMessage(String msg) {
JOptionPane.showMessageDialog(null, msg);