/* Connect four - written in java Copyright (C) 2020 Oliver Schappacher and Dorian Zedler This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package de.itsblue.ConnectFour; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.ArrayList; import de.itsblue.ConnectFour.Plate.PlateType; /** * GameBoard is a fully usable connect4 game board. It can take plates and * insert them into a given column and check if somebody won the game. * * @author Oliver Schappacher * @author Dorian Zedler */ public class GameBoard extends JPanel { /** * */ private static final long serialVersionUID = 1L; /** * The rows of the board */ private int boardRows = 6; /** * The columns of the board */ private int boardColumns = 7; /** * Array containing all plate containers */ private PlateContainer[][] BoardContainers = new PlateContainer[boardColumns][boardRows]; /** * If set to true, the board will not accept any insertions. Will be set to * false by the clear() function. */ private boolean boardLocked = false; /** * An array containing all action listeners */ private ArrayList playerActionListeners = new ArrayList(); /** * The current action id */ private int currentActionId = 0; /** * Constructor */ GameBoard() { this.initBoard(); } /** * Constructor with custom rows and columns * * @param rows number of columns the board sould have * @param colums number of rows the board should have */ GameBoard(int rows, int colums) { this.boardRows = rows; this.boardColumns = colums; this.initBoard(); } /** * Function to insert a plate into a specific column * * @param plate Plate object to insert * @param column The column to insert the plate into * @return "ok" if the inserton was successfull, "err" if the column is full, * PlateType as string if a plate type has won */ public boolean insertPlate(Plate plate, int column) { // check if the column is out of range if (column > boardColumns - 1 || this.boardLocked) return false; // search for an empty row for (int i = boardRows - 1; i >= 0; i--) { if (!this.BoardContainers[column][i].containsPlate()) { // if the container is empty -> add the plate this.BoardContainers[column][i].insertPlate(plate); this.checkForResult(); return true; } } return false; } /** * Function to clear the board */ public void clearBoard() { // search for an empty row for (int c = 0; c < boardColumns; c++) { for (int r = 0; r < boardRows; r++) { // create the container this.BoardContainers[c][r].removePlate(); } } this.boardLocked = false; } /** * Function to fill the board with containers */ private void initBoard() { // configure the main layout this.setLayout(new GridLayout(this.boardRows, this.boardColumns)); // fill the grid with containers for (int i = 0; i < boardRows; i++) { for (int j = 0; j < boardColumns; j++) { // create the container this.BoardContainers[j][i] = new PlateContainer(); // add the container this.add(this.BoardContainers[j][i]); } } } /** * Function to calculate a size for the containers to fit a given dimension * * @param parentSize dimension for the containers to fit into * @return suggested container size */ private int getSuggestedContainerSize(Dimension parentSize) { int containerSize; if (parentSize.getWidth() / this.boardColumns > parentSize.getHeight() / this.boardRows) containerSize = (int) parentSize.getHeight() / this.boardRows; else containerSize = (int) parentSize.getWidth() / this.boardColumns; return containerSize; } /** * Function to check if there is a chain of at least four plates of the same * color anywhere on the board * * @return null if there was no matching chain; otherwise the * PlateType of the chain that was found */ public PlateType checkForResult() { for (int c = 0; c < this.boardColumns; c++) { for (int r = 0; r < this.boardRows; r++) { PlateType res = this.checkContainerForWin(c, r); if (res != null) { this.fireActionListeners("gameOver " + res.toString()); return res; } } } if(this.isFull()) { this.boardLocked = true; this.fireActionListeners("gameOver draw"); } return null; } public boolean isFull() { boolean allContainersOccupied = true; for (int c = 0; c < this.boardColumns; c++) { for (int r = 0; r < this.boardRows; r++) { if(!this.getPlateContainer(c, r).containsPlate()) { allContainersOccupied = false; break; } } } return allContainersOccupied; } /** * Function to check if a certain container is the beginning of a four plate log * chain of plates of the same type what would indicate the end of the game. If * a matching chain is found, the involved containers are highlited. * * @param c column of the container to check * @param r row of the container to check * @return null if there was no matching chain; otherwise the * PlateType of the chain that was found */ public PlateType checkContainerForWin(int c, int r) { // if there is no plate in the container to check // -> return false if (this.getPlateContainer(c, r) == null || !this.getPlateContainer(c, r).containsPlate()) return null; // check all possible winnings clockwise for (int pc = 0; pc < 8; pc++) { int sum = 0; PlateContainer[] currentContainers = this.getPlateContainerRow(c, r, pc); for (PlateContainer plateContainer : currentContainers) { if (plateContainer != null && plateContainer.containsPlate()) sum += plateContainer.getContainedPlate().getValue(); } if (Math.abs(sum) == 4) { this.boardLocked = true; for (PlateContainer plateContainer : currentContainers) { plateContainer.highlight(); } return this.getPlateContainer(c, r).getContainedPlate().getType(); } } return null; } /** * Function to get the container at a certain column and row. * * @param c column of the container * @param r row of the container * @return null if there is no container at the given posistion or * the container at the given position */ public PlateContainer getPlateContainer(int c, int r) { if (this.BoardContainers.length > c && c >= 0 && this.BoardContainers[c].length > r && r >= 0) return this.BoardContainers[c][r]; return null; } /** * Function to get a row of four containers starting at a given one * * @param c column of the container to start at * @param r row of the conatiner to start at * @param direction direction of the row (0-7) * @return list of containers starting with the given one */ private PlateContainer[] getPlateContainerRow(int c, int r, int direction) { PlateContainer[] containers = new PlateContainer[4]; if (direction < 0 || direction > 7) return containers; for (int i = 0; i < 4; i++) { int checkC = 0; int checkR = 0; switch (direction) { case 0: // check top checkC = c; checkR = r + i; break; case 1: // check top right vert checkC = c + i; checkR = r + i; break; case 2: // check right checkC = c + i; checkR = r; break; case 3: // check bottom right vert checkC = c + i; checkR = r - i; break; case 4: // check bottom checkC = c; checkR = r - i; break; case 5: // check bottom left vert checkC = c - i; checkR = r - i; break; case 6: // check left checkC = c - i; checkR = r; break; case 7: // check top left vert checkC = c - i; checkR = r + i; break; } containers[i] = this.getPlateContainer(checkC, checkR); } return containers; } /** * Function to get the column count * * @return the column count of the board */ public int getColumns() { return this.boardColumns; } /** * Function to get the row count * * @return the row count of the board */ public int getRows() { return this.boardRows; } /** * Function to fire all actionListeners with a given action command * * @param actionCommand The action command to fire the listeners with */ protected void fireActionListeners(String actionCommand) { for (ActionListener playerActionListener : playerActionListeners) { ActionEvent e = new ActionEvent(this, this.currentActionId, actionCommand); playerActionListener.actionPerformed(e); this.currentActionId++; } } /** * Function to add an ActionListener * * @param listener the listener to add */ public void addActionListener(ActionListener listener) { this.playerActionListeners.add(listener); } /** * Function to remove an ActionListener * * @param listener the listener to remove */ public void removeMoveListener(ActionListener listener) { if (this.playerActionListeners.contains(listener)) this.playerActionListeners.remove(listener); } /** * Override paint function to rescale all containers on every repaint */ @Override public void paint(Graphics g) { // update the size of all containers for (PlateContainer[] plateContainers : BoardContainers) { for (PlateContainer plateContainer : plateContainers) { plateContainer.setPreferredSize(new Dimension(this.getSuggestedContainerSize(this.getSize()), this.getSuggestedContainerSize(this.getSize()))); } } super.paint(g); } /** * Override setPrefferedSize function to force aspect ratio */ @Override public void setPreferredSize(Dimension preferredSize) { Dimension newSize = new Dimension(this.getSuggestedContainerSize(preferredSize) * this.boardColumns, this.getSuggestedContainerSize(preferredSize) * this.boardRows); super.setPreferredSize(newSize); } }