This repository has been archived on 2022-08-16. You can view files and clone it, but cannot push or open issues or pull requests.
connect-four/src/de/itsblue/ConnectFour/GameBoard.java
Dorian Zedler 16a6d49c77 - cleaned up networking stuff
- added a state machine to the game
- added a status label
2020-02-25 11:24:29 +01:00

417 lines
12 KiB
Java

/*
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 <http://www.gnu.org/licenses/>.
*/
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<ActionListener> playerActionListeners = new ArrayList<ActionListener>();
/**
* 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 <code>null</code> 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 <code>null</code> 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 <code>null</code> 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);
}
}