diff --git a/src/main/java/de/tilosp/chess/Main.java b/src/main/java/de/tilosp/chess/Main.java index a7102c0..365d2e6 100644 --- a/src/main/java/de/tilosp/chess/Main.java +++ b/src/main/java/de/tilosp/chess/Main.java @@ -1,8 +1,11 @@ package de.tilosp.chess; +import de.tilosp.chess.gui.NewGameGUI; + public class Main { public static void main(String[] args) { - + // Open NewGameGUI Window + new NewGameGUI().setVisible(true); } } diff --git a/src/main/java/de/tilosp/chess/gui/ChessboardGUI.java b/src/main/java/de/tilosp/chess/gui/ChessboardGUI.java new file mode 100644 index 0000000..800ce41 --- /dev/null +++ b/src/main/java/de/tilosp/chess/gui/ChessboardGUI.java @@ -0,0 +1,253 @@ +package de.tilosp.chess.gui; + +import de.tilosp.chess.icon.Icons; +import de.tilosp.chess.lib.ChessPiece; +import de.tilosp.chess.lib.ChessPieceType; +import de.tilosp.chess.lib.Chessboard; +import de.tilosp.chess.player.LocalPlayer; +import de.tilosp.chess.player.Player; + +import javax.swing.*; +import javax.swing.border.BevelBorder; +import javax.swing.border.Border; +import java.awt.*; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; + +public class ChessboardGUI extends GUI { + + private static final Border BORDER_INSERTS = BorderFactory.createEmptyBorder(5, 5, 5, 5); + private static final Border BORDER_BEVEL_RAISED = BorderFactory.createBevelBorder(BevelBorder.RAISED); + private static final Border BORDER_BEVEL_LOWERED = BorderFactory.createBevelBorder(BevelBorder.LOWERED); + private static final Border BORDER_SIDE_PANEL = BorderFactory.createCompoundBorder(BORDER_BEVEL_RAISED, BORDER_BEVEL_LOWERED); + + private static final Color COLOR_1 = new Color(255, 206, 158); + private static final Color COLOR_1_B = new Color(255, 222, 174); + private static final Color COLOR_2 = new Color(209, 139, 71); + private static final Color COLOR_2_B = new Color(225, 155, 87); + private static final Color COLOR_SELECTED = new Color(209, 82, 60); + private static final Color COLOR_SELECTED_B = new Color(225, 98, 76); + private static final Color COLOR_SHOW = new Color(183, 78, 55); + private static final Color COLOR_SHOW_B = new Color(199, 94, 71); + private static final Color COLOR_PROMOTION = Color.WHITE; + private static final Color COLOR_PROMOTION_D = Color.WHITE.darker(); + + private static final Font FONT_LABEL = new Font(null, 0, 75); + + private HoverButton[][] boardButtons; + private HoverButton[] promotionButtons; + private JLabel[] topLabels; + private JLabel[] leftLabels; + private JLabel topLabel; + + private final Player[] players; + private Chessboard chessboard; + private int[] selected; + + public ChessboardGUI(Player player1, Player player2) { + super(); + players = new Player[] { player1, player2 }; + } + + @Override + void initGUI() { + setTitle("Chess"); + panel.setLayout(new BorderLayout()); + JPanel chessboardMPanel = new JPanel(); + panel.add(chessboardMPanel, BorderLayout.CENTER); + + chessboard = new Chessboard(); + + // initialise chessboard + JPanel chessboardPanel = new JPanel(new GridLayout(9, 9)); + chessboardMPanel.add(chessboardPanel); + chessboardMPanel.addComponentListener(new ComponentAdapter() { + + @Override + public void componentResized(ComponentEvent e) { + int size = Math.min(chessboardMPanel.getWidth(), chessboardMPanel.getHeight()); + chessboardPanel.setPreferredSize(new Dimension(size, size)); + chessboardMPanel.revalidate(); + } + }); + + boardButtons = new HoverButton[8][8]; + promotionButtons = new HoverButton[4]; + topLabels = new JLabel[8]; + leftLabels = new JLabel[8]; + + chessboardPanel.add(new JLabel()); + for (int i = 0; i < 8; i++) + chessboardPanel.add(topLabels[i] = newLabel(Character.toString((char) (0x61 + i)))); + for (int y = 0; y < 8; y++) { + chessboardPanel.add(leftLabels[y] = newLabel(Integer.toString(8 - y))); + for (int x = 0; x < 8; x++) { + HoverButton button = new HoverButton(); + button.setBorder(null); + chessboardPanel.add(boardButtons[x][y] = button); + } + } + + // initialise side panel + JPanel sidePanelO = new JPanel(new BorderLayout()); + panel.add(sidePanelO, BorderLayout.LINE_END); + sidePanelO.setBorder(BORDER_INSERTS); + + JPanel sidePanel = new JPanel(new BorderLayout()); + sidePanelO.add(sidePanel, BorderLayout.CENTER); + sidePanel.setBorder(BORDER_SIDE_PANEL); + + + sidePanel.add(Box.createVerticalGlue(), BorderLayout.CENTER); + topLabel = new JLabel(); + topLabel.setFont(FONT_LABEL); + topLabel.setBorder(BORDER_INSERTS); + + sidePanel.add(topLabel, BorderLayout.PAGE_START); + + // add promotion buttons + JPanel promotionPanel = new JPanel(new GridLayout(2, 2)); + promotionPanel.setBorder(BORDER_INSERTS); + for (int i = 0; i < 4; i++) { + HoverButton button = new HoverButton(); + button.setBorder(null); + button.setBackground(COLOR_PROMOTION, COLOR_PROMOTION_D); + button.setEnabled(false); + promotionPanel.add(promotionButtons[i] = button); + } + sidePanel.add(promotionPanel, BorderLayout.PAGE_END); + + // setup background + updateBackground(); + + // setup icons + updateIcons(); + + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + } + + @Override + void initListeners() { + for (int y = 0; y < 8; y++) { + for (int x = 0; x < 8; x++) { + final int fX = x, fY = y; + boardButtons[x][y].addActionListener(e -> buttonPressed(fX, fY)); + boardButtons[x][y].addMouseListener(new MouseListener() { + @Override + public void mouseClicked(MouseEvent e) { + + } + + @Override + public void mousePressed(MouseEvent e) { + + } + + @Override + public void mouseReleased(MouseEvent e) { + + } + + @Override + public void mouseEntered(MouseEvent e) { + setLabels(fX, fY); + } + + @Override + public void mouseExited(MouseEvent e) { + resetLabels(fX, fY); + } + }); + } + } + for (int i = 0; i < 4; i++) { + final int fI = i; + promotionButtons[i].addActionListener(e -> promotionButtonPressed(fI)); + } + } + + private void resetLabels(int x, int y) { + Font font = topLabels[x].getFont(); + topLabels[x].setFont(new Font(font.getName(), font.getStyle() & ~Font.BOLD, font.getSize())); + font = leftLabels[y].getFont(); + leftLabels[y].setFont(new Font(font.getName(), font.getStyle() & ~Font.BOLD, font.getSize())); + } + + private void setLabels(int x, int y) { + Font font = topLabels[x].getFont(); + topLabels[x].setFont(new Font(font.getName(), font.getStyle() | Font.BOLD, font.getSize())); + font = leftLabels[y].getFont(); + leftLabels[y].setFont(new Font(font.getName(), font.getStyle() | Font.BOLD, font.getSize())); + } + + private void buttonPressed(int x, int y) { + if (!chessboard.promotion) { + if (chessboard.getChessPiece(x, y) != null && chessboard.getChessPiece(x, y).playerColor == chessboard.playerColor && players[chessboard.playerColor.ordinal()] instanceof LocalPlayer && !chessboard.getPossibleMoves(x, y).isEmpty()) { + selected = new int[] { x, y }; + updateBackground(); + } else if (selected != null) { + Chessboard move = chessboard.checkAndMove(selected, new int[] { x, y }); + if (move != null) { + chessboard = move; + selected = null; + updateIcons(); + updateBackground(); + } + } + } + } + + private void promotionButtonPressed(int i) { + if (chessboard.promotion && players[chessboard.playerColor.ordinal()] instanceof LocalPlayer) { + chessboard = chessboard.promotion(ChessPieceType.POSITIONS_PROMOTION[i]); + updateIcons(); + } + } + + private void updateBackground() { + for (int y = 0; y < 8; y++) + for (int x = 0; x < 8; x++) + boardButtons[x][y].setBackground(((x ^ y) & 1) == 0 ? COLOR_1 : COLOR_2, ((x ^ y) & 1) == 0 ? COLOR_1_B : COLOR_2_B); + if (selected != null) { + boardButtons[selected[0]][selected[1]].setBackground(COLOR_SELECTED, COLOR_SELECTED_B); + for (int[] m : chessboard.getPossibleMoves(selected[0], selected[1])) + boardButtons[m[0]][m[1]].setBackground(COLOR_SHOW, COLOR_SHOW_B); + } + } + + private JLabel newLabel(String text) { + JLabel label = new JLabel(text); + label.setHorizontalAlignment(SwingConstants.CENTER); + label.setFont(FONT_LABEL); + label.addComponentListener(new ComponentAdapter() { + + @Override + public void componentResized(ComponentEvent e) { + Font font = label.getFont(); + label.setFont(new Font(font.getName(), font.getStyle(), (int) (label.getSize().height * 0.8))); + } + }); + return label; + } + + private void updateIcons() { + // update chessboard icons + for (int y = 0; y < 8; y++) { + for (int x = 0; x < 8; x++) { + ChessPiece chessPiece = chessboard.getChessPiece(x, y); + boardButtons[x][y].setIcon(chessPiece != null ? Icons.getIcon(chessPiece.playerColor, chessPiece.chessPieceType) : null); + } + } + + // update promotion icons + for (int i = 0; i < 4; i++) + promotionButtons[i].setIcon(Icons.getIcon(chessboard.playerColor, ChessPieceType.POSITIONS_PROMOTION[i])); + + // update promotion buttons + for (int i = 0; i < 4; i++) + promotionButtons[i].setEnabled(chessboard.promotion && players[chessboard.playerColor.ordinal()] instanceof LocalPlayer); + topLabel.setText(chessboard.playerColor.toString().toLowerCase()); + } +} diff --git a/src/main/java/de/tilosp/chess/gui/GUI.java b/src/main/java/de/tilosp/chess/gui/GUI.java new file mode 100644 index 0000000..f72b2b2 --- /dev/null +++ b/src/main/java/de/tilosp/chess/gui/GUI.java @@ -0,0 +1,27 @@ +package de.tilosp.chess.gui; + +import javax.swing.*; + +abstract class GUI extends JFrame { + + JPanel panel; + + GUI() { + // create panel + panel = new JPanel(); + + // initialise GUI + initGUI(); + + // add panel + setContentPane(panel); + pack(); + + // initialise Listeners + initListeners(); + } + + abstract void initGUI(); + + abstract void initListeners(); +} diff --git a/src/main/java/de/tilosp/chess/gui/HoverButton.java b/src/main/java/de/tilosp/chess/gui/HoverButton.java new file mode 100644 index 0000000..d308cfe --- /dev/null +++ b/src/main/java/de/tilosp/chess/gui/HoverButton.java @@ -0,0 +1,65 @@ +package de.tilosp.chess.gui; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; + +public class HoverButton extends JButton implements MouseListener { + + private Color color; + private Color colorHover; + private boolean hover; + + public HoverButton() { + super(); + addMouseListener(this); + } + + public void setBackground(Color color, Color colorHover) { + this.color = color; + this.colorHover = colorHover; + updateBackground(); + } + + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + updateBackground(); + } + + private void updateBackground() { + if (hover && isEnabled()) + setBackground(colorHover); + else + setBackground(color); + } + + @Override + public void mouseClicked(MouseEvent e) { + + } + + @Override + public void mousePressed(MouseEvent e) { + + } + + @Override + public void mouseReleased(MouseEvent e) { + + } + + @Override + public void mouseEntered(MouseEvent e) { + hover = true; + updateBackground(); + } + + @Override + public void mouseExited(MouseEvent e) { + hover = false; + updateBackground(); + } +} diff --git a/src/main/java/de/tilosp/chess/gui/NewGameGUI.java b/src/main/java/de/tilosp/chess/gui/NewGameGUI.java new file mode 100644 index 0000000..9e5713f --- /dev/null +++ b/src/main/java/de/tilosp/chess/gui/NewGameGUI.java @@ -0,0 +1,155 @@ +package de.tilosp.chess.gui; + +import de.tilosp.chess.player.LocalPlayer; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.awt.*; +import java.awt.event.ItemListener; + +public class NewGameGUI extends GUI { + + private JButton startButton; + private JRadioButton onePlayerRadioButton; + private JRadioButton twoPlayerRadioButton; + private JTextField hostTextField; + private JTextField portTextField; + private JComboBox twoPlayerModeComboBox; + private JComboBox colorComboBox; + private JLabel colorLabel; + private JLabel hostLabel; + private JLabel portLabel; + + @Override + void initGUI() { + setTitle("New Game"); + panel.setLayout(new GridBagLayout()); + panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); + GridBagConstraints c = new GridBagConstraints(); + + ButtonGroup buttonGroup = new ButtonGroup(); + + c.insets = new Insets(2, 2, 2, 2); + c.fill = GridBagConstraints.BOTH; + + c.gridy = 0; + c.gridx = 0; + c.gridwidth = 2; + panel.add(onePlayerRadioButton = new JRadioButton("1 Player"), c); + buttonGroup.add(onePlayerRadioButton); + onePlayerRadioButton.setSelected(true); + c.gridx = 2; + c.gridwidth = 1; + c.gridheight = 3; + panel.add(Box.createHorizontalStrut(10), c); + c.gridheight = 1; + c.gridx = 3; + panel.add(colorLabel = new JLabel("Color"), c); + c.gridx = 4; + panel.add(colorComboBox = new JComboBox<>(new String[] { "white", "black" }), c); + + c.gridy = 1; + c.gridx = 0; + c.gridwidth = 2; + panel.add(twoPlayerRadioButton = new JRadioButton("2 Player"), c); + buttonGroup.add(twoPlayerRadioButton); + c.gridx = 3; + c.gridwidth = 1; + panel.add(hostLabel = new JLabel("Host"), c); + c.gridx = 4; + panel.add(hostTextField = new JTextField("localhost"), c); + hostTextField.setPreferredSize(new Dimension(150, -1)); + + c.gridy = 2; + c.gridx = 0; + panel.add(Box.createHorizontalStrut(18), c); + c.gridx = 1; + panel.add(twoPlayerModeComboBox = new JComboBox<>(new String[] { "local", "server", "client" }), c); + c.gridx = 3; + panel.add(portLabel = new JLabel("Port"), c); + c.gridx = 4; + panel.add(portTextField = new JTextField("49152"), c); + + c.gridy = 3; + c.gridx = 0; + c.gridwidth = 5; + panel.add(startButton = new JButton("Start"), c); + getRootPane().setDefaultButton(startButton); + + updateEnabledStatus(); + + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + setResizable(false); + } + + @Override + void initListeners() { + // listener to update enabled status + ItemListener listener = e -> updateEnabledStatus(); + onePlayerRadioButton.addItemListener(listener); + twoPlayerRadioButton.addItemListener(listener); + twoPlayerModeComboBox.addItemListener(listener); + + // listener for start button + startButton.addActionListener(e -> onStartButtonPressed()); + + // listener to change the color of the port text field + portTextField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + update(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + update(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + update(); + } + + void update() { + try { + int port = Integer.parseInt(portTextField.getText()); + portTextField.setForeground(port < 49152 || port > 65535 ? Color.ORANGE : Color.BLACK); + } catch (NumberFormatException e) { + portTextField.setForeground(Color.RED); + } + } + }); + } + + private void onStartButtonPressed() { + if (onePlayerRadioButton.isSelected()) { + // TODO 1 Player + } else { + if (twoPlayerModeComboBox.getSelectedIndex() == 0) { + // 2 Players local + new ChessboardGUI(new LocalPlayer(), new LocalPlayer()).setVisible(true); + } else if(twoPlayerModeComboBox.getSelectedIndex() == 1) { + // TODO 2 Players host + } else { + // TODO 2 Players client + } + } + dispose(); + } + + private void updateEnabledStatus() { + twoPlayerModeComboBox.setEnabled(twoPlayerRadioButton.isSelected()); + + boolean enabled = onePlayerRadioButton.isSelected() || twoPlayerRadioButton.isSelected() && twoPlayerModeComboBox.getSelectedIndex() == 1; + colorLabel.setEnabled(enabled); + colorComboBox.setEnabled(enabled); + + enabled = twoPlayerRadioButton.isSelected() && twoPlayerModeComboBox.getSelectedIndex() == 2; + hostLabel.setEnabled(enabled); + hostTextField.setEnabled(enabled); + portLabel.setEnabled(enabled); + portTextField.setEnabled(enabled); + + } +} \ No newline at end of file diff --git a/src/main/java/de/tilosp/chess/icon/Icons.java b/src/main/java/de/tilosp/chess/icon/Icons.java new file mode 100644 index 0000000..e63f74c --- /dev/null +++ b/src/main/java/de/tilosp/chess/icon/Icons.java @@ -0,0 +1,22 @@ +package de.tilosp.chess.icon; + +import de.tilosp.chess.lib.ChessPieceType; +import de.tilosp.chess.lib.PlayerColor; + +import javax.swing.*; + +public final class Icons { + + public static final Icon[][] icons; + + static { + icons = new Icon[PlayerColor.values().length][ChessPieceType.values().length]; + for (PlayerColor playerColor : PlayerColor.values()) + for (ChessPieceType chessPieceType : ChessPieceType.values()) + icons[playerColor.ordinal()][chessPieceType.ordinal()] = new ImageIcon(Icons.class.getResource(playerColor.name().toLowerCase() + "_" + chessPieceType.name().toLowerCase() + ".png")); + } + + public static Icon getIcon(PlayerColor playerColor, ChessPieceType chessPieceType) { + return icons[playerColor.ordinal()][chessPieceType.ordinal()]; + } +} diff --git a/src/main/java/de/tilosp/chess/lib/ChessPiece.java b/src/main/java/de/tilosp/chess/lib/ChessPiece.java new file mode 100644 index 0000000..21e3bf4 --- /dev/null +++ b/src/main/java/de/tilosp/chess/lib/ChessPiece.java @@ -0,0 +1,55 @@ +package de.tilosp.chess.lib; + +public class ChessPiece { + + public final ChessPieceType chessPieceType; + public final PlayerColor playerColor; + private final int movements; + private final int movedInTurn; + private final boolean enPassant; + + public ChessPiece(ChessPieceType chessPieceType, PlayerColor playerColor) { + this(chessPieceType, playerColor, 0, 0, false); + } + + private ChessPiece(ChessPieceType chessPieceType, PlayerColor playerColor, int movements, int movedInTurn, boolean enPassant) { + this.chessPieceType = chessPieceType; + this.playerColor = playerColor; + this.movements = movements; + this.movedInTurn = movedInTurn; + this.enPassant = enPassant; + } + + public int[][] getMove() { + return chessPieceType.getMove(notMoved()); + } + + public int[][] getCapture() { + return chessPieceType.capture; + } + + public ChessPiece moved(int turn, boolean enPassant) { + return new ChessPiece(chessPieceType, playerColor, movements + 1, turn, enPassant); + } + + public ChessPiece promotion(ChessPieceType chessPieceType) { + return new ChessPiece(chessPieceType, playerColor, movements, movedInTurn, false); + } + + public boolean notMoved() { + return movements == 0; + } + + public boolean checkEnPassant(int turn) { + return turn == movedInTurn + 1 && enPassant; + } + + public boolean checkPromotion(int y) { + return chessPieceType == ChessPieceType.PAWN && (playerColor == PlayerColor.WHITE ? 0 : 7) == y; + } + + @Override + public String toString() { + return chessPieceType.toString() + playerColor.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/de/tilosp/chess/lib/ChessPieceType.java b/src/main/java/de/tilosp/chess/lib/ChessPieceType.java new file mode 100644 index 0000000..fec11d7 --- /dev/null +++ b/src/main/java/de/tilosp/chess/lib/ChessPieceType.java @@ -0,0 +1,73 @@ +package de.tilosp.chess.lib; + +public enum ChessPieceType { + + KING(new int[][] { + { 0, 1, }, + { 0, -1 }, + { 1, 0 }, + { -1, 0 }, + { 1, 1 }, + { 1, -1 }, + { -1, 1 }, + { -1, -1 } }), + QUEEN(new int[][] { + { 0, 1, 7 }, + { 0, -1, 7 }, + { 1, 0, 7 }, + { -1, 0, 7 }, + { 1, 1, 7 }, + { 1, -1, 7 }, + { -1, 1, 7 }, + { -1, -1, 7 } }), + ROOK(new int[][] { + { 0, 1, 7 }, + { 0, -1, 7 }, + { 1, 0, 7 }, + { -1, 0, 7 } }), + BISHOP(new int[][] { + { 1, 1, 7 }, + { 1, -1, 7 }, + { -1, 1, 7 }, + { -1, -1, 7 } }), + KNIGHT(new int[][] { + { 1, 2 }, + { -1, 2 }, + { 1, -2 }, + { -1, -2 }, + { 2, 1 }, + { 2, -1 }, + { -2, 1 }, + { -2, -1 } }), + PAWN(new int[][] { + { 0, 1 } + }, new int[][] { + { 0, 1, 2 } + }, new int[][] { + { 1, 1 }, + { -1, 1 }, + { 1, 1, 1, 0 }, + { -1, 1, -1, 0 } + }); + + public static final ChessPieceType[] POSITIONS_FIRST_ROW = { ROOK, KNIGHT, BISHOP, QUEEN, KING, BISHOP, KNIGHT, ROOK }; + public static final ChessPieceType[] POSITIONS_PROMOTION = { QUEEN, KNIGHT, ROOK, BISHOP }; + public final int[][] capture; + private final int[][] move; + private int[][] moveFirstTurn; + + ChessPieceType(int[][] move) { + this.move = move; + this.capture = move; + } + + ChessPieceType(int[][] move, int[][] moveFirstTurn, int[][] capture) { + this.move = move; + this.moveFirstTurn = moveFirstTurn; + this.capture = capture; + } + + public int[][] getMove(boolean notMoved) { + return notMoved && moveFirstTurn != null ? moveFirstTurn : move; + } +} diff --git a/src/main/java/de/tilosp/chess/lib/Chessboard.java b/src/main/java/de/tilosp/chess/lib/Chessboard.java new file mode 100644 index 0000000..516a9f9 --- /dev/null +++ b/src/main/java/de/tilosp/chess/lib/Chessboard.java @@ -0,0 +1,186 @@ +package de.tilosp.chess.lib; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.stream.Collectors; + +public class Chessboard { + + private static final ChessPiece[][] INITIAL_POSITIONS = new ChessPiece[8][8]; + + static { + for (int i = 0; i < 8; i++) { + INITIAL_POSITIONS[i][0] = new ChessPiece(ChessPieceType.POSITIONS_FIRST_ROW[i], PlayerColor.BLACK); + INITIAL_POSITIONS[i][1] = new ChessPiece(ChessPieceType.PAWN, PlayerColor.BLACK); + INITIAL_POSITIONS[i][6] = new ChessPiece(ChessPieceType.PAWN, PlayerColor.WHITE); + INITIAL_POSITIONS[i][7] = new ChessPiece(ChessPieceType.POSITIONS_FIRST_ROW[i], PlayerColor.WHITE); + } + } + + private final ChessPiece[][] chessPieces; + private final int turn; + public final PlayerColor playerColor; + public final boolean promotion; + private final int promotionX; + private final int promotionY; + + + public Chessboard() { + this(INITIAL_POSITIONS, 1, PlayerColor.WHITE, false, -1, -1); + } + + private Chessboard(ChessPiece[][] chessPieces, int turn, PlayerColor playerColor, boolean promotion, int promotionX, int promotionY) { + this.chessPieces = chessPieces; + this.turn = turn; + this.playerColor = playerColor; + this.promotion = promotion; + this.promotionX = promotionX; + this.promotionY = promotionY; + } + + public ArrayList getPossibleMoves(int x, int y) { + ArrayList possibilities = new ArrayList<>(); + ChessPiece chessPiece = chessPieces[x][y]; + if (chessPiece != null) { + for (int[] a : chessPiece.getCapture()) { + if (a.length == 4) { // en passant + int gX = x + a[0]; + int gY = y + a[1] * (chessPiece.playerColor.ordinal() * 2 - 1); + int bX = x + a[2]; + int bY = y + a[3] * (chessPiece.playerColor.ordinal() * 2 - 1); + if (gX < 0 || gX > 7 || gY < 0 || gY > 7) + continue; + if (chessPieces[gX][gY] == null && chessPieces[bX][bY] != null && chessPieces[bX][bY].playerColor != chessPiece.playerColor && chessPieces[bX][bY].chessPieceType == ChessPieceType.PAWN && chessPieces[bX][bY].checkEnPassant(turn)) + possibilities.add(new int[] { gX, gY, bX, bY }); + } else { + int extend = a.length == 3 ? a[2] : 1; + for (int i = 1; i <= extend; i++) { + int nX = x + i * a[0]; + int nY = y + i * a[1] * (chessPiece.playerColor.ordinal() * 2 - 1); + if (nX < 0 || nX > 7 || nY < 0 || nY > 7) + continue; + if (chessPieces[nX][nY] != null) { + if (chessPieces[nX][nY].playerColor != chessPiece.playerColor) + possibilities.add(new int[] { nX, nY }); + break; + } + } + } + } + for (int[] a : chessPiece.getMove()) { + int extend = a.length == 3 ? a[2] : 1; + for (int i = 1; i <= extend; i++) { + int nX = x + i * a[0]; + int nY = y + i * a[1] * (chessPiece.playerColor.ordinal() * 2 - 1); + if (nX < 0 || nX > 7 || nY < 0 || nY > 7) + continue; + if (chessPieces[nX][nY] == null) + possibilities.add(new int[] { nX, nY }); + else + break; + } + } + if (chessPiece.chessPieceType == ChessPieceType.KING) { + if (chessPiece.notMoved()) { + if (chessPieces[7][y] != null && chessPieces[7][y].notMoved() && chessPieces[6][y] == null && chessPieces[5][y] == null && !isThreatenedField(5, y, chessPiece.playerColor.otherColor())) + possibilities.add(new int[] { 6, y, 7, y, 5, y }); + if (chessPieces[0][y] != null && chessPieces[0][y].notMoved() && chessPieces[1][y] == null && chessPieces[2][y] == null && chessPieces[3][y] == null && !isThreatenedField(3, y, chessPiece.playerColor.otherColor())) + possibilities.add(new int[] { 2, y, 0, y, 3, y }); + } + } + possibilities.removeAll(possibilities.stream().filter(a -> move(new int[] { x, y }, a).inCheck(chessPiece.playerColor)).collect(Collectors.toCollection(ArrayList::new))); + } + return possibilities; + } + + private ArrayList getThreatenedFields(int x, int y) { + ArrayList possibilities = new ArrayList<>(); + ChessPiece chessPiece = chessPieces[x][y]; + if (chessPiece != null) { + for (int[] a : chessPiece.getCapture()) { + int extend = a.length == 3 ? a[2] : 1; + for (int i = 1; i <= extend; i++) { + int nX = x + i * a[0]; + int nY = y + i * a[1] * (chessPiece.playerColor.ordinal() * 2 - 1); + if (nX < 0 || nX > 7 || nY < 0 || nY > 7) + continue; + if (chessPieces[nX][nY] == null) { + possibilities.add(new int[] { nX, nY }); + } else { + possibilities.add(new int[] { nX, nY }); + break; + } + } + } + } + return possibilities; + } + + private boolean isThreatenedField(int x, int y, PlayerColor playerColor) { + for (int i1 = 0; i1 < 8; i1++) + for (int i2 = 0; i2 < 8; i2++) + if (chessPieces[i1][i2] != null && chessPieces[i1][i2].playerColor == playerColor) + for (int[] a : getThreatenedFields(i1, i2)) + if (a[0] == x && a[1] == y) + return true; + return false; + } + + private Chessboard move(int[] from, int[] to) { + ChessPiece[][] chessPieces = copy(this.chessPieces); + chessPieces[to[0]][to[1]] = this.chessPieces[from[0]][from[1]].moved(turn, Math.abs(from[1] - to[1]) > 1); + chessPieces[from[0]][from[1]] = null; + if (to.length == 4) { + chessPieces[to[2]][to[3]] = null; + } else if (to.length == 6) { + chessPieces[to[4]][to[5]] = this.chessPieces[to[2]][to[3]].moved(turn, false); + chessPieces[to[2]][to[3]] = null; + } + boolean promotion = chessPieces[to[0]][to[1]].checkPromotion(to[1]); + return new Chessboard(chessPieces, promotion ? turn : turn + 1, promotion ? playerColor : playerColor.otherColor(), promotion, promotion ? to[0] : -1, promotion ? to[1] : -1); + } + + + public Chessboard checkAndMove(int[] from, int[] to) { + ArrayList m = getPossibleMoves(from[0], from[1]); + for (int[] t : m) + if (t[0] == to[0] && t[1] == to[1]) + return move(from, t); + return null; + } + + private boolean inCheck(PlayerColor playerColor) { + int[] pos = findChessPiece(ChessPieceType.KING, playerColor); + return pos != null && isThreatenedField(pos[0], pos[1], playerColor.otherColor()); + } + + private int[] findChessPiece(ChessPieceType chessPieceType, PlayerColor playerColor) { + for (int y = 0; y < 8; y++) + for (int x = 0; x < 8; x++) + if (chessPieces[x][y] != null && chessPieces[x][y].chessPieceType == chessPieceType && chessPieces[x][y].playerColor == playerColor) + return new int[] { x, y }; + return null; + } + + public Chessboard promotion(ChessPieceType chessPieceType) { + ChessPiece[][] chessPieces = copy(this.chessPieces); + chessPieces[promotionX][promotionY] = chessPieces[promotionX][promotionY].promotion(chessPieceType); + return new Chessboard(chessPieces, turn + 1, playerColor.otherColor(), false, -1, -1); + } + + public ChessPiece getChessPiece(int x, int y) { + return chessPieces[x][y]; + } + + @Override + public String toString() { + return Arrays.deepToString(chessPieces); + } + + private ChessPiece[][] copy(ChessPiece[][] array) { + ChessPiece[][] a = new ChessPiece[array.length][]; + for (int i = 0; i < a.length; i++) + a[i] = Arrays.copyOf(array[i], array[i].length); + return a; + } +} diff --git a/src/main/java/de/tilosp/chess/lib/PlayerColor.java b/src/main/java/de/tilosp/chess/lib/PlayerColor.java new file mode 100644 index 0000000..37f3081 --- /dev/null +++ b/src/main/java/de/tilosp/chess/lib/PlayerColor.java @@ -0,0 +1,11 @@ +package de.tilosp.chess.lib; + +// the two player colors +public enum PlayerColor { + WHITE, + BLACK; + + public PlayerColor otherColor() { + return values()[ordinal() ^ 1]; + } +} diff --git a/src/main/java/de/tilosp/chess/player/LocalPlayer.java b/src/main/java/de/tilosp/chess/player/LocalPlayer.java new file mode 100644 index 0000000..84f2655 --- /dev/null +++ b/src/main/java/de/tilosp/chess/player/LocalPlayer.java @@ -0,0 +1,4 @@ +package de.tilosp.chess.player; + +public class LocalPlayer extends Player { +} diff --git a/src/main/java/de/tilosp/chess/player/Player.java b/src/main/java/de/tilosp/chess/player/Player.java new file mode 100644 index 0000000..4d319c0 --- /dev/null +++ b/src/main/java/de/tilosp/chess/player/Player.java @@ -0,0 +1,4 @@ +package de.tilosp.chess.player; + +public abstract class Player { +} diff --git a/src/main/resources/de/tilosp/chess/icon/black_bishop.png b/src/main/resources/de/tilosp/chess/icon/black_bishop.png new file mode 100644 index 0000000..f13ad08 Binary files /dev/null and b/src/main/resources/de/tilosp/chess/icon/black_bishop.png differ diff --git a/src/main/resources/de/tilosp/chess/icon/black_king.png b/src/main/resources/de/tilosp/chess/icon/black_king.png new file mode 100644 index 0000000..6a8440b Binary files /dev/null and b/src/main/resources/de/tilosp/chess/icon/black_king.png differ diff --git a/src/main/resources/de/tilosp/chess/icon/black_knight.png b/src/main/resources/de/tilosp/chess/icon/black_knight.png new file mode 100644 index 0000000..e57052e Binary files /dev/null and b/src/main/resources/de/tilosp/chess/icon/black_knight.png differ diff --git a/src/main/resources/de/tilosp/chess/icon/black_pawn.png b/src/main/resources/de/tilosp/chess/icon/black_pawn.png new file mode 100644 index 0000000..e39c5cb Binary files /dev/null and b/src/main/resources/de/tilosp/chess/icon/black_pawn.png differ diff --git a/src/main/resources/de/tilosp/chess/icon/black_queen.png b/src/main/resources/de/tilosp/chess/icon/black_queen.png new file mode 100644 index 0000000..2b49564 Binary files /dev/null and b/src/main/resources/de/tilosp/chess/icon/black_queen.png differ diff --git a/src/main/resources/de/tilosp/chess/icon/black_rook.png b/src/main/resources/de/tilosp/chess/icon/black_rook.png new file mode 100644 index 0000000..2b51a66 Binary files /dev/null and b/src/main/resources/de/tilosp/chess/icon/black_rook.png differ diff --git a/src/main/resources/de/tilosp/chess/icon/white_bishop.png b/src/main/resources/de/tilosp/chess/icon/white_bishop.png new file mode 100644 index 0000000..be23aaf Binary files /dev/null and b/src/main/resources/de/tilosp/chess/icon/white_bishop.png differ diff --git a/src/main/resources/de/tilosp/chess/icon/white_king.png b/src/main/resources/de/tilosp/chess/icon/white_king.png new file mode 100644 index 0000000..062b6eb Binary files /dev/null and b/src/main/resources/de/tilosp/chess/icon/white_king.png differ diff --git a/src/main/resources/de/tilosp/chess/icon/white_knight.png b/src/main/resources/de/tilosp/chess/icon/white_knight.png new file mode 100644 index 0000000..35e301c Binary files /dev/null and b/src/main/resources/de/tilosp/chess/icon/white_knight.png differ diff --git a/src/main/resources/de/tilosp/chess/icon/white_pawn.png b/src/main/resources/de/tilosp/chess/icon/white_pawn.png new file mode 100644 index 0000000..8c3aaa1 Binary files /dev/null and b/src/main/resources/de/tilosp/chess/icon/white_pawn.png differ diff --git a/src/main/resources/de/tilosp/chess/icon/white_queen.png b/src/main/resources/de/tilosp/chess/icon/white_queen.png new file mode 100644 index 0000000..3298ff5 Binary files /dev/null and b/src/main/resources/de/tilosp/chess/icon/white_queen.png differ diff --git a/src/main/resources/de/tilosp/chess/icon/white_rook.png b/src/main/resources/de/tilosp/chess/icon/white_rook.png new file mode 100644 index 0000000..52641b3 Binary files /dev/null and b/src/main/resources/de/tilosp/chess/icon/white_rook.png differ