diff --git a/schach/src/main/java/de/hs_mannheim/informatik/chess/controller/CreativeController.java b/schach/src/main/java/de/hs_mannheim/informatik/chess/controller/CreativeController.java new file mode 100644 index 0000000..9d961e1 --- /dev/null +++ b/schach/src/main/java/de/hs_mannheim/informatik/chess/controller/CreativeController.java @@ -0,0 +1,169 @@ +package de.hs_mannheim.informatik.chess.controller; + +import de.hs_mannheim.informatik.chess.view.CreativeGui; +import de.hs_mannheim.informatik.chess.model.ChessEngine; +import de.hs_mannheim.informatik.chess.model.PieceDTO; + +import javax.swing.*; +import java.awt.event.MouseListener; +import java.util.HashMap; +import java.util.Map; + +public class CreativeController { + private CreativeGui gui; + private ChessEngine engine; + + // Hilfsmap für Unicode zu Chesslib Piece FEN-Zeichen + private static final HashMap PIECE_TO_FEN = createPieceToFenMap(); + + private static HashMap createPieceToFenMap() { + HashMap map = new HashMap(); + map.put("BLACK_KING", 'k'); + map.put("BLACK_QUEEN", 'q'); + map.put("BLACK_ROOK", 'r'); + map.put("BLACK_BISHOP", 'b'); + map.put("BLACK_KNIGHT", 'n'); + map.put("BLACK_PAWN", 'p'); + map.put("WHITE_KING", 'K'); + map.put("WHITE_QUEEN", 'Q'); + map.put("WHITE_ROOK", 'R'); + map.put("WHITE_BISHOP", 'B'); + map.put("WHITE_KNIGHT", 'N'); + map.put("WHITE_PAWN", 'P'); + return map; + } + + public CreativeController(CreativeGui gui, ChessEngine engine) { + this.gui = gui; + this.engine = engine; + + setupFieldListeners(); + setupFenUpdateListener(); + gui.setFenText("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); + applyFenToBoard(); + } + + private void setupFieldListeners() { + JLabel[][] fields = gui.getFields(); + for (int row = 0; row < 8; row++) { + for (int col = 0; col < 8; col++) { + final int r = row, c = col; + for (MouseListener l : fields[r][c].getMouseListeners()) { + fields[r][c].removeMouseListener(l); + } + fields[r][c].addMouseListener(new java.awt.event.MouseAdapter() { + public void mousePressed(java.awt.event.MouseEvent evt) { + fieldClicked(r, c); + } + }); + } + } + gui.getFlipBoardButton().addActionListener(e -> { + applyFenToBoard(); + gui.setFlipped(!gui.isFlipped()); + updateGuiFromEngine(); // Board wird neu angezeigt, ggf. invertiert + }); + } + + private void fieldClicked(int row, int col) { + String selectedPiece = gui.getSelectedPiece(); + if (selectedPiece == null) return; + if ("ERASER".equals(selectedPiece)) { + gui.getFields()[row][col].setText(""); + } else { + gui.getFields()[row][col].setText(CreativeGui.UNICODE_MAP.get(selectedPiece)); + } + // Jetzt FEN synchronisieren: + gui.setFenText(boardToFen()); + } + + + private void setupFenUpdateListener() { + //Getter für das Feld in deiner Gui: + JTextField fenField = gui.getFenField(); + if (fenField != null) { + fenField.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + applyFenToBoard(); + } + }); + } + + JButton updateBtn = gui.getUpdateButton(); + if (updateBtn != null) { + updateBtn.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + applyFenToBoard(); + } + }); + } + } + + private void applyFenToBoard() { + String fen = gui.getFenText(); + try { + engine.setPositionFromFEN(fen); + updateGuiFromEngine(); + } catch (Exception ex) { + JOptionPane.showMessageDialog(null, "Ungültiges FEN: " + ex.getMessage()); + } + } + + // Board aus der GUI zu FEN String + private String boardToFen() { + JLabel[][] fields = gui.getFields(); + StringBuilder fen = new StringBuilder(); + for (int row = 0; row < 8; row++) { + int empty = 0; + for (int col = 0; col < 8; col++) { + String symbol = fields[row][col].getText(); + String pieceKey = null; + for (Map.Entry entry : CreativeGui.UNICODE_MAP.entrySet()) { + if (entry.getValue().equals(symbol)) { + pieceKey = entry.getKey(); + break; + } + } + if (pieceKey == null || "ERASER".equals(pieceKey) || symbol.trim().isEmpty()) { + empty++; + } else { + if (empty > 0) { + fen.append(empty); + empty = 0; + } + Character fenChar = PIECE_TO_FEN.get(pieceKey); + if (fenChar != null) fen.append(fenChar); + } + } + if (empty > 0) fen.append(empty); + if (row < 7) fen.append('/'); + } + fen.append(" w - - 0 1"); + return fen.toString(); + } + + private void updateGuiFromEngine() { + PieceDTO[][] board = engine.getBoardAsDTO().getBoard(); + JLabel[][] fields = gui.getFields(); + + boolean flipped = gui.isFlipped(); // NEU + + for (int row = 0; row < 8; row++) { + for (int col = 0; col < 8; col++) { + int displayRow = flipped ? 7 - row : row; + int displayCol = flipped ? 7 - col : col; + PieceDTO p = board[row][col]; + if (p == null) { + fields[displayRow][displayCol].setText(""); + } else { + for (Map.Entry entry : CreativeGui.UNICODE_MAP.entrySet()) { + if (entry.getKey().startsWith(p.getColor()) && entry.getKey().endsWith(p.getType())) { + fields[displayRow][displayCol].setText(entry.getValue()); + break; + } + } + } + } + } + } +} diff --git a/schach/src/main/java/de/hs_mannheim/informatik/chess/controller/Controller.java b/schach/src/main/java/de/hs_mannheim/informatik/chess/controller/GameController.java similarity index 50% rename from schach/src/main/java/de/hs_mannheim/informatik/chess/controller/Controller.java rename to schach/src/main/java/de/hs_mannheim/informatik/chess/controller/GameController.java index 87e3886..f3b0f7b 100644 --- a/schach/src/main/java/de/hs_mannheim/informatik/chess/controller/Controller.java +++ b/schach/src/main/java/de/hs_mannheim/informatik/chess/controller/GameController.java @@ -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 Controller { +public class GameController { GameGui gui; ChessEngine engine; + GameEndCallback callback; + + private boolean gameOver = false; private int selectedRow = -1, selectedCol = -1; private List highlightedFields = new ArrayList<>(); - public Controller(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 Controller { 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 Controller { } private void handleClick(int guiRow, int guiCol) { + + if (gameOver) return; + int modelRow = flipRow(guiRow); int modelCol = flipCol(guiCol); @@ -125,7 +162,7 @@ public class Controller { for (int[] xy : highlightedFields) { resetFieldBackground(xy[0], xy[1]); } - highlightedFields.clear(); + highlightedFields.clear(); int oldGuiRow = gui.isFlipped() ? 7 - selectedRow : selectedRow; int oldGuiCol = gui.isFlipped() ? 7 - selectedCol : selectedCol; gui.getField(oldGuiRow, oldGuiCol).setBorder(null); @@ -137,43 +174,113 @@ public class Controller { } } - public void handleMove(MoveDTO move) { - if (engine.move(move)) { - updateGuiBoard(); - - //Züge in der MoveList aktualisieren - gui.updateMoveList(engine.getMoveListStringsGrouped()); - - //Spielstatus prüfen - if (engine.isMated()) { - String winner = engine.getCurrentPlayer().equals("WHITE") ? "SCHWARZ" : "WEIß"; - gui.displayMessage(winner + " hat gewonnen (Schachmatt)!"); - } else if (engine.isStalemate() || engine.isDraw()) { - gui.displayMessage("Remis! (Stalemate oder andere Regel)"); + 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"); + boolean isWhitePromotion = isPawn && piece.getColor().equals("WHITE") && move.getToRow() == 0; + boolean isBlackPromotion = isPawn && piece.getColor().equals("BLACK") && move.getToRow() == 7; + + boolean success = false; + + if (isWhitePromotion || isBlackPromotion) { + String color = piece.getColor().equals("WHITE") ? "Weiß" : "Schwarz"; + String promotion = gui.showPromotionDialog(color); + success = engine.moveWithPromotion(move, promotion); + if (!success) { + gui.displayMessage("Ungültiger Promotionszug!"); + return; } } else { - gui.displayMessage("Ungültiger Zug!"); + success = engine.move(move); + if (!success) { + gui.displayMessage("Ungültiger Zug!"); + return; + } + } + + updateGuiBoard(); + gui.updateMoveList(engine.getMoveListStringsGrouped()); + + // ---- HIER ist die Matt/Patt/Remis-Prüfung ---- + 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(); } } + public void updateGuiBoard() { 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; } + - - private void resetFieldBackground(int row, int col) { - if ((row + col) % 2 == 0) { - gui.getField(row, col).setBackground(new Color(0x778da9)); + // 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 { - gui.getField(row, col).setBackground(new Color(0xe0e1dd)); + callback.onReturnToMenu(); } } + + private void resetFieldBackground(int row, int col) { + Color LIGHT = new Color(0xe0e1dd); + Color DARK = new Color(0x778da9); + if ((row + col) % 2 == 0) { + gui.getField(row, col).setBackground(LIGHT); + } else { + gui.getField(row, col).setBackground(DARK); + } + } + } diff --git a/schach/src/main/java/de/hs_mannheim/informatik/chess/controller/GameEndCallback.java b/schach/src/main/java/de/hs_mannheim/informatik/chess/controller/GameEndCallback.java new file mode 100644 index 0000000..daed890 --- /dev/null +++ b/schach/src/main/java/de/hs_mannheim/informatik/chess/controller/GameEndCallback.java @@ -0,0 +1,6 @@ +package de.hs_mannheim.informatik.chess.controller; + +public interface GameEndCallback { + void onNewGameRequested(); + void onReturnToMenu(); +} diff --git a/schach/src/main/java/de/hs_mannheim/informatik/chess/controller/MainController.java b/schach/src/main/java/de/hs_mannheim/informatik/chess/controller/MainController.java new file mode 100644 index 0000000..5607a08 --- /dev/null +++ b/schach/src/main/java/de/hs_mannheim/informatik/chess/controller/MainController.java @@ -0,0 +1,72 @@ +package de.hs_mannheim.informatik.chess.controller; + +import de.hs_mannheim.informatik.chess.view.MainGui; +import de.hs_mannheim.informatik.chess.view.PgnGui; +import de.hs_mannheim.informatik.chess.view.PgnSelectionGui; +import de.hs_mannheim.informatik.chess.view.CreativeGui; +import de.hs_mannheim.informatik.chess.view.GameGui; + +import java.io.IOException; +import java.util.List; + +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; + +import com.github.bhlangonijr.chesslib.game.Game; + +import de.hs_mannheim.informatik.chess.model.ChessEngine; + +public class MainController { + private MainGui mainGui; + + public MainController() { + mainGui = new MainGui(); + + mainGui.setNormalModeListener(e -> startNormalMode()); + mainGui.setCreativeModeListener(e -> startCreativeMode()); + mainGui.setLoadGameListener(e -> startLoadGameMode()); + } + + private void startNormalMode() { + mainGui.close(); + GameGui gameGui = new GameGui(); + ChessEngine engine = new ChessEngine(); + GameEndCallback callback = new GameEndCallback() { + public void onNewGameRequested() { + startNormalMode(); + } + public void onReturnToMenu() { + new MainController(); + } + }; + new GameController(gameGui, engine, callback); + } + + private void startCreativeMode() { + mainGui.close(); + CreativeGui creativegui = new CreativeGui(); + ChessEngine engine = new ChessEngine(); + new CreativeController(creativegui, engine); + } + + private void startLoadGameMode() { + JFileChooser chooser = new JFileChooser(); + int result = chooser.showOpenDialog(null); + if (result == JFileChooser.APPROVE_OPTION) { + String path = chooser.getSelectedFile().getAbsolutePath(); + ChessEngine engine = new ChessEngine(); + try { + List games = engine.loadGamesFromPgn(path); + // Jetzt Auswahl-GUI öffnen! + new PgnSelectionGui(games, selectedGame -> { + // Callback wenn User eins ausgewählt hat! + PgnGui pgnGui = new PgnGui(); + engine.loadMoves(selectedGame.getHalfMoves()); + new PgnController(pgnGui, engine); + }); + } catch (IOException ex) { + JOptionPane.showMessageDialog(null, "Fehler beim Laden der PGN-Datei:\n" + ex.getMessage()); + } + } + } +} \ No newline at end of file diff --git a/schach/src/main/java/de/hs_mannheim/informatik/chess/controller/PgnController.java b/schach/src/main/java/de/hs_mannheim/informatik/chess/controller/PgnController.java new file mode 100644 index 0000000..fd59baa --- /dev/null +++ b/schach/src/main/java/de/hs_mannheim/informatik/chess/controller/PgnController.java @@ -0,0 +1,82 @@ +package de.hs_mannheim.informatik.chess.controller; + +import java.awt.Color; + +import de.hs_mannheim.informatik.chess.model.ChessEngine; +import de.hs_mannheim.informatik.chess.model.BoardDTO; +import de.hs_mannheim.informatik.chess.view.PgnGui; + +public class PgnController { + PgnGui gui; + ChessEngine engine; + + + public PgnController(PgnGui pgngui, ChessEngine engine) { + this.gui = pgngui; + this.engine = engine; + initListeners(); + updateGuiBoard(); + } + + private void initListeners() { + // Erster Zug + gui.getBtnFirst().addActionListener(e -> { + engine.setPositionToMoveIndex(0); + updateGuiBoard(); + }); + // Ein Zug zurück + gui.getBtnPrev().addActionListener(e -> { + int idx = Math.max(0, engine.getCurrentMoveIndex() - 1); + engine.setPositionToMoveIndex(idx); + updateGuiBoard(); + }); + // Ein Zug vor + gui.getBtnNext().addActionListener(e -> { + int idx = Math.min(engine.getMoveListSize(), engine.getCurrentMoveIndex() + 1); + engine.setPositionToMoveIndex(idx); + updateGuiBoard(); + }); + // Letzter Zug + gui.getBtnLast().addActionListener(e -> { + engine.setPositionToMoveIndex(engine.getMoveListSize()); + updateGuiBoard(); + }); + + + gui.getFlipBoardButton().addActionListener(e -> { + //ALLE Highlights und Borders zurücksetzen + for (int row = 0; row < 8; row++) { + for (int col = 0; col < 8; col++) { + resetFieldBackground(row, col); + gui.getField(row, col).setBorder(null); + } + } + + // 2. Flip-Zustand ändern + gui.setFlipped(!gui.isFlipped()); + + // 3. Board neu zeichnen + updateGuiBoard(); + }); + + + } + + public void updateGuiBoard() { + BoardDTO board = engine.getBoardAsDTO(); + gui.updateBoard(board); + gui.updateMoveList(engine.getMoveListStringsGrouped()); + } + + private void resetFieldBackground(int row, int col) { + Color LIGHT = new Color(0xe0e1dd); + Color DARK = new Color(0x778da9); + if ((row + col) % 2 == 0) { + gui.getField(row, col).setBackground(LIGHT); + } else { + gui.getField(row, col).setBackground(DARK); + } + } + +} + diff --git a/schach/src/main/java/de/hs_mannheim/informatik/chess/main/Main.java b/schach/src/main/java/de/hs_mannheim/informatik/chess/main/Main.java index 94d5753..392bca0 100644 --- a/schach/src/main/java/de/hs_mannheim/informatik/chess/main/Main.java +++ b/schach/src/main/java/de/hs_mannheim/informatik/chess/main/Main.java @@ -1,19 +1,12 @@ package de.hs_mannheim.informatik.chess.main; -import de.hs_mannheim.informatik.chess.controller.Controller; -import de.hs_mannheim.informatik.chess.model.ChessEngine; -import de.hs_mannheim.informatik.chess.view.GameGui; -import de.hs_mannheim.informatik.chess.view.MainGui; +import de.hs_mannheim.informatik.chess.controller.MainController; + public class Main{ public static void main( String[] args ){ - new MainGui(() -> { - //Wenn "Normal Modus" gedrückt wird - GameGui gui = new GameGui(); - ChessEngine engine = new ChessEngine(); - new Controller(gui, engine); - }); + new MainController(); } } diff --git a/schach/src/main/java/de/hs_mannheim/informatik/chess/model/ChessEngine.java b/schach/src/main/java/de/hs_mannheim/informatik/chess/model/ChessEngine.java index 8f0141c..89320fa 100644 --- a/schach/src/main/java/de/hs_mannheim/informatik/chess/model/ChessEngine.java +++ b/schach/src/main/java/de/hs_mannheim/informatik/chess/model/ChessEngine.java @@ -1,4 +1,8 @@ package de.hs_mannheim.informatik.chess.model; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.logging.ConsoleHandler; @@ -11,6 +15,12 @@ import com.github.bhlangonijr.chesslib.Board; import com.github.bhlangonijr.chesslib.Piece; import com.github.bhlangonijr.chesslib.Square; 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; @@ -18,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(); @@ -87,6 +99,37 @@ public class ChessEngine { 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! + 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; + } + + 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++; + logger.info("Promotionszug durchgeführt: " + libMove); + return true; + } + logger.warning("Ungültiger Promotionszug: " + libMove); + return false; + } + public BoardDTO getBoardAsDTO() { logger.info("Erstelle DTO-Abbild des Boards"); PieceDTO[][] dtoBoard = new PieceDTO[8][8]; @@ -146,6 +189,11 @@ public class ChessEngine { return new PieceDTO(type, color, symbol); } + public void setPositionFromFEN(String fen) { + board.loadFromFen(fen); + } + + public boolean isMated() { boolean mated = board.isMated(); logger.info("isMated() = " + mated); @@ -186,10 +234,142 @@ public class ChessEngine { logger.info("ChessEngine wurde initialisiert."); } + + public List loadGamesFromPgn(String path) throws IOException { + + PgnHolder pgnHolder = new PgnHolder(path); + try { + pgnHolder.loadPgn(); + } catch (Exception e) { + e.printStackTrace(); + } + List games = pgnHolder.getGames(); + return games; + } - public void setPositionFromFEN(String fen) { - board.loadFromFen(fen); + 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 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 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; } } diff --git a/schach/src/main/java/de/hs_mannheim/informatik/chess/model/MyPlayer.java b/schach/src/main/java/de/hs_mannheim/informatik/chess/model/MyPlayer.java new file mode 100644 index 0000000..7681ff9 --- /dev/null +++ b/schach/src/main/java/de/hs_mannheim/informatik/chess/model/MyPlayer.java @@ -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; + } +} diff --git a/schach/src/main/java/de/hs_mannheim/informatik/chess/model/Timer.java b/schach/src/main/java/de/hs_mannheim/informatik/chess/model/Timer.java new file mode 100644 index 0000000..19441d6 --- /dev/null +++ b/schach/src/main/java/de/hs_mannheim/informatik/chess/model/Timer.java @@ -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 onTick; + + + public Timer(int minutes, int seconds) { + this.secondsLeft = minutes * 60 + seconds; + } + + public void setOnTimeout(Runnable onTimeout) { + this.onTimeout = onTimeout; + } + + public void setOnTick(Consumer 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; + } +} + + diff --git a/schach/src/main/java/de/hs_mannheim/informatik/chess/view/CreativeGui.java b/schach/src/main/java/de/hs_mannheim/informatik/chess/view/CreativeGui.java new file mode 100644 index 0000000..4f146b9 --- /dev/null +++ b/schach/src/main/java/de/hs_mannheim/informatik/chess/view/CreativeGui.java @@ -0,0 +1,177 @@ +package de.hs_mannheim.informatik.chess.view; + +import javax.swing.*; +import java.awt.*; +import java.util.HashMap; + +public class CreativeGui { + private boolean isFlipped = false; + + private JFrame frame; + private JLabel[][] fields = new JLabel[8][8]; + private String selectedPiece = null; + private JTextField fenField; + private JButton updateBtn; + private JButton flipBoardButton; + + public static final HashMap UNICODE_MAP = new HashMap() {{ + put("BLACK_KING", "\u265A"); put("BLACK_QUEEN", "\u265B"); + put("BLACK_ROOK", "\u265C"); put("BLACK_BISHOP", "\u265D"); + put("BLACK_KNIGHT", "\u265E"); put("BLACK_PAWN", "\u265F"); + put("WHITE_KING", "\u2654"); put("WHITE_QUEEN", "\u2655"); + put("WHITE_ROOK", "\u2656"); put("WHITE_BISHOP", "\u2657"); + put("WHITE_KNIGHT", "\u2658"); put("WHITE_PAWN", "\u2659"); + put("ERASER", "\u2716"); + }}; + + private final Color LIGHT = new Color(0xe0e1dd); + private final Color DARK = new Color(0x778da9); + + public CreativeGui() { + frame = new JFrame("Creative Mode"); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + frame.setSize(1600, 1200); + frame.setLocationRelativeTo(null); + + JPanel mainPanel = new JPanel(new GridBagLayout()); + mainPanel.setBackground(LIGHT); + GridBagConstraints gbc = new GridBagConstraints(); + + // LINKS: chessPanel (Board+Toolbars) + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 0.6; + gbc.weighty = 1.0; + gbc.insets = new Insets(5, 5, 5, 0); + gbc.fill = GridBagConstraints.BOTH; + mainPanel.add(chessPanel(), gbc); + + // RECHTS: FEN & Optionen + gbc.gridx = 1; + gbc.gridy = 0; + gbc.weightx = 0.4; + gbc.weighty = 1.0; + gbc.insets = new Insets(5, 0, 5, 5); + gbc.fill = GridBagConstraints.BOTH; + mainPanel.add(fenPanel(), gbc); + + frame.setContentPane(mainPanel); + frame.setVisible(true); + } + + private JPanel toolbarPanel(boolean white) { + JPanel panel = new JPanel(); + panel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + panel.setBackground(new Color(0x1b263b)); + String prefix = white ? "WHITE_" : "BLACK_"; + String[] pieces = {"KING", "QUEEN", "ROOK", "BISHOP", "KNIGHT", "PAWN"}; + for (String type : pieces) { + JButton btn = new JButton(UNICODE_MAP.get(prefix + type)); + btn.setFont(new Font("Serif", Font.BOLD, 40)); + btn.setFocusPainted(false); + btn.setPreferredSize(new Dimension(70, 70)); + btn.setBackground(white ? LIGHT : DARK); + btn.setBorder(BorderFactory.createEmptyBorder(20, 0, 20, 0)); + String key = prefix + type; + btn.addActionListener(e -> selectedPiece = key); + panel.add(btn); + } + JButton del = new JButton(UNICODE_MAP.get("ERASER")); + del.setFont(new Font("Serif", Font.BOLD, 36)); + del.setBackground(Color.PINK); + del.setPreferredSize(new Dimension(70, 70)); + del.setFocusPainted(false); + del.addActionListener(e -> selectedPiece = "ERASER"); + panel.add(del); + + return panel; + } + + private JPanel boardPanel() { + JPanel boardPanel = new JPanel(new GridLayout(8, 8)); + boardPanel.setPreferredSize(new Dimension(800, 800)); + for (int row = 0; row < 8; row++) { + for (int col = 0; col < 8; col++) { + JLabel label = new JLabel("", SwingConstants.CENTER); + label.setOpaque(true); + label.setFont(new Font("Serif", Font.BOLD, 70)); + label.setBackground((row + col) % 2 == 0 ? LIGHT : DARK); + fields[row][col] = label; + boardPanel.add(label); + } + } + return boardPanel; + } + + private JPanel chessPanel() { + JPanel chessPanel = new JPanel(new GridBagLayout()); + chessPanel.setBackground(new Color(0x1b263b)); + GridBagConstraints gbc = new GridBagConstraints(); + + // Toolbar oben + gbc.gridx = 0; + gbc.gridy = 0; + gbc.fill = GridBagConstraints.HORIZONTAL; + chessPanel.add(toolbarPanel(false), gbc); + + // Board + gbc.gridx = 0; + gbc.gridy = 1; + gbc.fill = GridBagConstraints.BOTH; + chessPanel.add(boardPanel(), gbc); + + // Toolbar unten + gbc.gridx = 0; + gbc.gridy = 2; + gbc.fill = GridBagConstraints.HORIZONTAL; + chessPanel.add(toolbarPanel(true), gbc); + + // Drehknopf + 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(); + btn.gridx = 0; + btn.gridy = 2; + btn.weightx = 0.0; + btn.weighty = 0.0; + btn.anchor = GridBagConstraints.EAST; + btn.insets = new Insets(5, 0, 0, 0); + chessPanel.add(flipBoardButton, btn); + + return chessPanel; + } + + private JPanel fenPanel() { + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + panel.setBackground(new Color(0xe0e1dd)); + panel.setPreferredSize(new Dimension(420, 40)); + panel.add(new JLabel("FEN:")); + fenField = new JTextField(""); + fenField.setFont(new Font("Monospaced", Font.PLAIN, 22)); + fenField.setMaximumSize(new Dimension(600, 50)); + panel.add(fenField); + updateBtn = new JButton("Update Board"); + updateBtn.setAlignmentX(Component.CENTER_ALIGNMENT); + panel.add(updateBtn); + return panel; + } + + // Für Controller + public JLabel[][] getFields() { return fields; } + public String getFenText() { return fenField.getText(); } + public void setFenText(String fen) { fenField.setText(fen); } + public JTextField getFenField() { return fenField; } + public JButton getUpdateButton() { return updateBtn; } + public void setSelectedPiece(String piece) { selectedPiece = piece; } + public String getSelectedPiece() { return selectedPiece; } + + public boolean isFlipped() { return isFlipped; } + public void setFlipped(boolean f) { isFlipped = f; } + public JButton getFlipBoardButton() { return flipBoardButton; } +} diff --git a/schach/src/main/java/de/hs_mannheim/informatik/chess/view/GameGui.java b/schach/src/main/java/de/hs_mannheim/informatik/chess/view/GameGui.java index 76f4b97..6574fde 100644 --- a/schach/src/main/java/de/hs_mannheim/informatik/chess/view/GameGui.java +++ b/schach/src/main/java/de/hs_mannheim/informatik/chess/view/GameGui.java @@ -27,12 +27,19 @@ 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("<"); JButton btnNext = new JButton(">"); JButton btnLast = new JButton(">|"); + Color LIGHT = new Color(0xe0e1dd); + Color DARK = new Color(0x778da9); + private JPanel moveListPanel; private JScrollPane moveListScroll; @@ -54,20 +61,21 @@ public class GameGui { } private void initFields() { - for (int row = 0; row < 8; row++) { - for (int col = 0; col < 8; col++) { - JLabel label = new JLabel("", SwingConstants.CENTER); - label.setOpaque(true); - label.setFont(new Font("Serif", Font.BOLD, 40)); - if ((row + col) % 2 == 0) { - label.setBackground(new Color(0x778da9)); - } else { - label.setBackground(new Color(0xe0e1dd)); - } - fields[row][col] = label; - } - } - } + for (int row = 0; row < 8; row++) { + for (int col = 0; col < 8; col++) { + JLabel label = new JLabel("", SwingConstants.CENTER); + label.setOpaque(true); + label.setFont(new Font("Serif", Font.BOLD, 40)); + // Richtige Schachfärbung: + if ((row + col) % 2 == 0) { + label.setBackground(LIGHT); // a1 ist jetzt hell! + } else { + label.setBackground(DARK); + } + fields[row][col] = label; + } + } + } public JPanel mainPanel() { JPanel mainPanel = new JPanel(new GridBagLayout()); @@ -97,29 +105,27 @@ public class GameGui { } public JPanel boardPanel() { - JPanel boardPanel = new JPanel(new GridLayout(8, 8)); - boardPanel.setPreferredSize(new Dimension(800, 800)); + JPanel boardPanel = new JPanel(new GridLayout(8, 8)); + boardPanel.setPreferredSize(new Dimension(800, 800)); - for (int row = 0; row < 8; row++) { - - for (int col = 0; col < 8; col++) { - - JLabel label = new JLabel("", SwingConstants.CENTER); - label.setOpaque(true); - label.setFont(new Font("Serif", Font.BOLD, 70)); - if ((row + col) % 2 == 0) { - label.setBackground(new Color(0x778da9)); - } else { - label.setBackground(new Color(0xe0e1dd)); - } - fields[row][col] = label; // <-- Save the label - boardPanel.add(label); - - } - } - boardPanel.setBackground(new Color(0x1b263b)); - return boardPanel; - } + for (int row = 0; row < 8; row++) { + for (int col = 0; col < 8; col++) { + JLabel label = new JLabel("", SwingConstants.CENTER); + label.setOpaque(true); + label.setFont(new Font("Serif", Font.BOLD, 70)); + // Richtige Schachfärbung: + if ((row + col) % 2 == 0) { + label.setBackground(LIGHT); + } else { + label.setBackground(DARK); + } + fields[row][col] = label; + boardPanel.add(label); + } + } + boardPanel.setBackground(new Color(0x1b263b)); + return boardPanel; + } public JPanel chessPanel(JPanel panel) { JPanel chessPanel = new JPanel(new GridBagLayout()); @@ -158,7 +164,10 @@ public class GameGui { JPanel statsPanel = new JPanel(new BorderLayout()); statsPanel.setBackground(new Color(0x0d1b2a)); - // Move-Liste (scrollbar, wie gehabt) + // Panel für die Timer (NORD) + statsPanel.add(timerPanel(), BorderLayout.NORTH); + + // Move-Liste moveListPanel = new JPanel(); moveListPanel.setLayout(new BoxLayout(moveListPanel, BoxLayout.Y_AXIS)); moveListPanel.setBackground(new Color(0x0d1b2a)); @@ -166,30 +175,67 @@ 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 – je nach Geschmack - 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"}; + String choice = (String) JOptionPane.showInputDialog( + null, + color + "er Bauer wird umgewandelt – wähle Figur:", + "Bauernumwandlung", + JOptionPane.PLAIN_MESSAGE, + null, + choices, + choices[0]); + if (choice == null) return "QUEEN"; + switch (choice) { + case "Turm": return "ROOK"; + case "Springer": return "KNIGHT"; + case "Läufer": return "BISHOP"; + default: return "QUEEN"; + } + } public void updateMoveList(List moves) { moveListPanel.removeAll(); @@ -223,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(); @@ -244,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); diff --git a/schach/src/main/java/de/hs_mannheim/informatik/chess/view/MainGui.java b/schach/src/main/java/de/hs_mannheim/informatik/chess/view/MainGui.java index f770a47..74aef54 100644 --- a/schach/src/main/java/de/hs_mannheim/informatik/chess/view/MainGui.java +++ b/schach/src/main/java/de/hs_mannheim/informatik/chess/view/MainGui.java @@ -1,15 +1,19 @@ package de.hs_mannheim.informatik.chess.view; import java.awt.*; +import java.awt.event.ActionListener; + import javax.swing.*; public class MainGui { private JFrame frame; - private Runnable onStartGame; - public MainGui(Runnable onStartGame) { - this.onStartGame = onStartGame; + JButton btnMode1 = new JButton("Normal Mode"); + JButton btnCreative = new JButton("Creative Mode"); + JButton btnLoadGame = new JButton("Load Game (PGN)"); + + public MainGui() { frame = new JFrame("Chess - Hauptmenü"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); @@ -38,44 +42,38 @@ public class MainGui { gbc.ipady = 15; mainPanel.add(Box.createRigidArea(new Dimension(0, 20)), gbc); - //Buttons - JButton btnMode1 = new JButton("Normal Mode"); - JButton btnMode2 = new JButton("Mode 2 (coming soon)"); - JButton btnMode3 = new JButton("Mode 3 (coming soon)"); - JButton btnCreative = new JButton("Creative Mode"); - JButton btnLoadGame = new JButton("Load Game (PGN)"); - styleButton(btnMode1); - styleButton(btnMode2); - styleButton(btnMode3); styleButton(btnCreative); styleButton(btnLoadGame); - gbc.gridy = 2; gbc.ipady = 25; + gbc.gridy = 1; gbc.ipady = 25; mainPanel.add(btnMode1, gbc); - - gbc.gridy = 3; - mainPanel.add(btnMode2, gbc); - - gbc.gridy = 4; - mainPanel.add(btnMode3, gbc); - gbc.gridy = 5; + gbc.gridy = 2; mainPanel.add(btnCreative, gbc); - gbc.gridy = 6; + gbc.gridy = 3; mainPanel.add(btnLoadGame, gbc); frame.add(mainPanel); frame.setVisible(true); - //Button ActionListener für "Normal Modus" - btnMode1.addActionListener(e -> { - frame.dispose(); // Hauptmenü schließen - onStartGame.run(); // **Ruft den Callback auf** - }); + } + // --- Methoden für Controller zum Setzen der Listener --- + public void setNormalModeListener(ActionListener l) { + btnMode1.addActionListener(l); + } + public void setCreativeModeListener(ActionListener l) { + btnCreative.addActionListener(l); + } + public void setLoadGameListener(ActionListener l) { + btnLoadGame.addActionListener(l); } + public void close() { + frame.dispose(); + } + // Helper für Button Styling private void styleButton(JButton btn) { btn.setFont(new Font("Serif", Font.BOLD, 30)); diff --git a/schach/src/main/java/de/hs_mannheim/informatik/chess/view/PgnGui.java b/schach/src/main/java/de/hs_mannheim/informatik/chess/view/PgnGui.java new file mode 100644 index 0000000..b01b9c6 --- /dev/null +++ b/schach/src/main/java/de/hs_mannheim/informatik/chess/view/PgnGui.java @@ -0,0 +1,273 @@ +package de.hs_mannheim.informatik.chess.view; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.awt.Insets; +import java.util.List; + +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.SwingConstants; + +import de.hs_mannheim.informatik.chess.model.BoardDTO; +import de.hs_mannheim.informatik.chess.model.PieceDTO; + +public class PgnGui { + + private JLabel[][] fields = new JLabel[8][8]; + private JButton flipBoardButton; + private boolean isFlipped = false; + + JButton btnFirst = new JButton("|<"); + JButton btnPrev = new JButton("<"); + JButton btnNext = new JButton(">"); + JButton btnLast = new JButton(">|"); + + Color LIGHT = new Color(0xe0e1dd); + Color DARK = new Color(0x778da9); + + private JPanel moveListPanel; + private JScrollPane moveListScroll; + + public PgnGui(){ + initFields(); + mainFrame(); + } + + + public JFrame mainFrame() { + JFrame frame = new JFrame(); + frame.setSize(1600, 1000); + frame.setLocationRelativeTo(null); + frame.add(mainPanel()); + + frame.setDefaultCloseOperation(2); + frame.setVisible(true); + return frame; + } + + private void initFields() { + for (int row = 0; row < 8; row++) { + for (int col = 0; col < 8; col++) { + JLabel label = new JLabel("", SwingConstants.CENTER); + label.setOpaque(true); + label.setFont(new Font("Serif", Font.BOLD, 40)); + // Richtige Schachfärbung: + if ((row + col) % 2 == 0) { + label.setBackground(LIGHT); // a1 ist jetzt hell! + } else { + label.setBackground(DARK); + } + fields[row][col] = label; + } + } + } + + public JPanel mainPanel() { + JPanel mainPanel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + mainPanel.setBackground(new Color(0xe0e1dd)); + // Links (Schach) + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 0.6; + gbc.weighty = 1.0; + gbc.insets = new Insets(5, 5, 5, 0); + //oben, links, unten, rechts + gbc.fill = GridBagConstraints.BOTH; + mainPanel.add(chessPanel(boardPanel()), gbc); + + // Rechts (Stats) + gbc.gridx = 1; + gbc.gridy = 0; + gbc.weightx = 0.4; + gbc.weighty = 1.0; + gbc.insets = new Insets(5, 0, 5, 5); + //oben, links, unten, rechts + gbc.fill = GridBagConstraints.BOTH; + mainPanel.add(statsPanel(), gbc); + + return mainPanel; + } + + public JPanel boardPanel() { + JPanel boardPanel = new JPanel(new GridLayout(8, 8)); + boardPanel.setPreferredSize(new Dimension(800, 800)); + + for (int row = 0; row < 8; row++) { + for (int col = 0; col < 8; col++) { + JLabel label = new JLabel("", SwingConstants.CENTER); + label.setOpaque(true); + label.setFont(new Font("Serif", Font.BOLD, 70)); + // Richtige Schachfärbung: + if ((row + col) % 2 == 0) { + label.setBackground(LIGHT); + } else { + label.setBackground(DARK); + } + fields[row][col] = label; + boardPanel.add(label); + } + } + boardPanel.setBackground(new Color(0x1b263b)); + return boardPanel; + } + + public JPanel chessPanel(JPanel panel) { + JPanel chessPanel = new JPanel(new GridBagLayout()); + GridBagConstraints board = new GridBagConstraints(); + 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(); + 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); + + chessPanel.add(flipBoardButton, btn); + + return chessPanel; + } + + public JPanel statsPanel() { + JPanel statsPanel = new JPanel(new BorderLayout()); + statsPanel.setBackground(new Color(0x0d1b2a)); + + // Move-Liste + moveListPanel = new JPanel(); + moveListPanel.setLayout(new BoxLayout(moveListPanel, BoxLayout.Y_AXIS)); + moveListPanel.setBackground(new Color(0x0d1b2a)); + moveListScroll = new JScrollPane(moveListPanel); + moveListScroll.setPreferredSize(new Dimension(250, 800)); + statsPanel.add(moveListScroll, BorderLayout.CENTER); + + // Button-Leiste + JPanel buttonPanel = new JPanel(); + buttonPanel.setBackground(new Color(0x0d1b2a)); + // Grid oder Flow + buttonPanel.setLayout(new GridLayout(1, 4, 10, 0)); + + // Style (optional) + 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 + buttonPanel.add(btnFirst); + buttonPanel.add(btnPrev); + buttonPanel.add(btnNext); + buttonPanel.add(btnLast); + + // Unten ins BorderLayout + statsPanel.add(buttonPanel, BorderLayout.SOUTH); + + return statsPanel; + } + + public String showPromotionDialog(String color) { + String[] choices = {"Dame", "Turm", "Springer", "Läufer"}; + String choice = (String) JOptionPane.showInputDialog( + null, + color + "er Bauer wird umgewandelt – wähle Figur:", + "Bauernumwandlung", + JOptionPane.PLAIN_MESSAGE, + null, + choices, + choices[0]); + if (choice == null) return "QUEEN"; + switch (choice) { + case "Turm": return "ROOK"; + case "Springer": return "KNIGHT"; + case "Läufer": return "BISHOP"; + default: return "QUEEN"; + } + } + + public void updateMoveList(List moves) { + moveListPanel.removeAll(); + for (String move : moves) { + JLabel moveLabel = new JLabel(move); + moveLabel.setForeground(Color.WHITE); + moveLabel.setFont(new Font("SansSerif", Font.PLAIN, 18)); + moveListPanel.add(moveLabel); + } + moveListPanel.revalidate(); + moveListPanel.repaint(); + } + + public JLabel getField(int row, int col) { + return fields[row][col]; + } + + public JButton getFlipBoardButton() { + return flipBoardButton; + } + + public boolean isFlipped() { + return isFlipped; + } + + public void setFlipped(boolean flipped) { + this.isFlipped = flipped; + } + + public JButton getBtnFirst() { return btnFirst; } + public JButton getBtnPrev() { return btnPrev; } + public JButton getBtnNext() { return btnNext; } + public JButton getBtnLast() { return btnLast; } + + public void updateBoard(BoardDTO boardDTO) { + PieceDTO[][] board = boardDTO.getBoard(); + if (!isFlipped) { + for (int row = 0; row < 8; row++) { + for (int col = 0; col < 8; col++) { + PieceDTO piece = board[row][col]; + fields[row][col].setText(piece != null ? piece.getUnicodeSymbol() : ""); + } + } + } else { + // Invertiere Zeilen und Spalten + for (int row = 0; row < 8; row++) { + for (int col = 0; col < 8; col++) { + PieceDTO piece = board[7 - row][7 - col]; + fields[row][col].setText(piece != null ? piece.getUnicodeSymbol() : ""); + } + } + } + } + + + public void displayMessage(String msg) { + JOptionPane.showMessageDialog(null, msg); + } + +} + diff --git a/schach/src/main/java/de/hs_mannheim/informatik/chess/view/PgnSelectionGui.java b/schach/src/main/java/de/hs_mannheim/informatik/chess/view/PgnSelectionGui.java new file mode 100644 index 0000000..8bc50fe --- /dev/null +++ b/schach/src/main/java/de/hs_mannheim/informatik/chess/view/PgnSelectionGui.java @@ -0,0 +1,39 @@ +package de.hs_mannheim.informatik.chess.view; + +import java.util.List; +import java.util.function.Consumer; + +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JScrollPane; + +import com.github.bhlangonijr.chesslib.game.Game; + +public class PgnSelectionGui extends JFrame { + public PgnSelectionGui(List games, Consumer onGameSelected) { + setTitle("Geladene Partien"); + setSize(600, 800); + setLocationRelativeTo(null); + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + JScrollPane scrollPane = new JScrollPane(panel); + + int index = 1; + for (Game game : games) { + String white = game.getWhitePlayer().getName(); + String black = game.getBlackPlayer().getName(); + String title = "Spiel " + index++ + ": " + white + " vs. " + black; + JButton btn = new JButton(title); + btn.addActionListener(e -> { + dispose(); + onGameSelected.accept(game); + }); + panel.add(btn); + } + add(scrollPane); + setVisible(true); + } +} + diff --git a/schach/src/test/java/de/hs_mannheim/informatik/chess/test/ControllerTest.java b/schach/src/test/java/de/hs_mannheim/informatik/chess/test/ControllerTest.java index 36b2b34..0c9fbf7 100644 --- a/schach/src/test/java/de/hs_mannheim/informatik/chess/test/ControllerTest.java +++ b/schach/src/test/java/de/hs_mannheim/informatik/chess/test/ControllerTest.java @@ -2,7 +2,7 @@ package de.hs_mannheim.informatik.chess.test; import static org.junit.jupiter.api.Assertions.*; -import de.hs_mannheim.informatik.chess.controller.Controller; +import de.hs_mannheim.informatik.chess.controller.GameController; import de.hs_mannheim.informatik.chess.model.*; import de.hs_mannheim.informatik.chess.view.GameGui; @@ -20,9 +20,10 @@ import javax.swing.JLabel; public class ControllerTest { - private Controller controller; + private GameController controller; private ChessEngine engineMock; private GameGui guiMock; + private GameEndCallback callback; @@ -43,7 +44,7 @@ public class ControllerTest { when(guiMock.getFlipBoardButton()).thenReturn(new javax.swing.JButton()); // Jetzt ist der Controller sicher - controller = new Controller(guiMock, engineMock); + controller = new GameController(guiMock, engineMock, callback); } @Test //Testet dass keine Fehlermeldung bei legalen Move kommt