- cleaned up networking stuff

- added a state machine to the game
- added a status label
This commit is contained in:
Dorian Zedler 2020-02-25 11:17:17 +01:00
parent 8ae82e9f9a
commit 16a6d49c77
7 changed files with 420 additions and 248 deletions

View file

@ -19,7 +19,9 @@
package de.itsblue.ConnectFour;
import java.awt.*;
import java.io.Serializable;
import java.awt.event.*;
import java.io.IOException;
import java.io.StringReader;
import javax.swing.*;
@ -27,7 +29,7 @@ import de.itsblue.ConnectFour.Plate.PlateType;
import de.itsblue.ConnectFour.player.Player;
import de.itsblue.ConnectFour.player.*;
public class ConnectFour extends JFrame implements PlayerMoveListener {
public class ConnectFour extends JFrame implements ActionListener {
/**
*
*/
@ -40,6 +42,10 @@ public class ConnectFour extends JFrame implements PlayerMoveListener {
private ControllRow controllRow;
private int player = 0;
private JLabel statusLabel;
private int currentPlayer = -1;
private int winnerPlayer = -1;
private Player players[] = new Player[2];
@ -49,6 +55,12 @@ public class ConnectFour extends JFrame implements PlayerMoveListener {
Local, RemoteServer, RemoteClient
}
private GameState gameState = GameState.Idle;
enum GameState {
Idle, Waiting, Running, Over
}
/**
* Constructor
*/
@ -65,12 +77,14 @@ public class ConnectFour extends JFrame implements PlayerMoveListener {
// initialize GameBoard
this.gameBoard = new GameBoard();
this.gameBoard.addActionListener(this);
// initialize ButtonRow
this.buttonRow = new ButtonRow(this.gameBoard.getColumns());
// initialize ContollRow
this.controllRow = new ControllRow(this.gameBoard.getColumns());
this.statusLabel = new JLabel();
// add components to window
c.gridx = 0;
@ -85,64 +99,165 @@ public class ConnectFour extends JFrame implements PlayerMoveListener {
c.gridy = 1;
this.add(gameBoard, c);
c.gridy = 2;
this.add(this.statusLabel, c);
// finish up
this.pack();
this.setVisible(true);
}
public void startNewGame(GameType type) {
if (this.gameState != GameState.Idle)
return;
this.gameType = type;
this.setGameState(GameState.Waiting);
switch (type) {
case Local: {
this.players[0] = new LocalPlayer(this.buttonRow, PlateType.O);
this.players[0].addMoveListener(this);
this.players[1] = new LocalPlayer(this.buttonRow, PlateType.X);
this.players[1].addMoveListener(this);
this.player = 0;
this.players[player].setIsMyTurn(true);
this.players[0].addActionListener(this);
this.players[1].addActionListener(this);
this.currentPlayer = 0;
this.players[this.currentPlayer].setIsMyTurn(true);
break;
}
case RemoteClient: {
this.players[0] = new LocalPlayer(this.buttonRow, null);
this.players[1] = new RemotePlayerClient(this.buttonRow, "localhost", this.players[0]);
this.players[0].addMoveListener(this);
this.players[1].addMoveListener(this);
try {
this.players[1] = new RemotePlayerClient(this.buttonRow, "localhost", this.players[0]);
} catch (IOException e) {
e.printStackTrace();
}
this.players[0].addActionListener(this);
this.players[1].addActionListener(this);
break;
}
case RemoteServer: {
this.players[0] = new LocalPlayer(this.buttonRow, PlateType.O);
try {
this.players[1] = new RemotePlayerServer(this.buttonRow, PlateType.X, this.players[0]);
} catch (Exception e) {
e.printStackTrace();
}
this.players[0].addMoveListener(this);
this.players[1].addMoveListener(this);
this.players[0].addActionListener(this);
this.players[1].addActionListener(this);
this.player = 0;
this.players[player].setIsMyTurn(true);
this.currentPlayer = 0;
this.players[0].setIsMyTurn(true);
this.players[1].setIsMyTurn(false);
break;
}
}
this.setGameState(GameState.Running);
}
private void gameOver(String gameOverType) {
if (gameOverType.equals("draw")) {
this.winnerPlayer = -1;
this.statusLabel.setText("Game over. This was a draw!");
} else {
PlateType winnerPlateType = PlateType.valueOf(gameOverType);
this.winnerPlayer = this.players[0].getUsedPlateType().equals(winnerPlateType) ? 0 : 1;
if(!this.gameType.equals(GameType.Local) && this.players[this.winnerPlayer] instanceof LocalPlayer)
this.statusLabel.setText("Game over. You won the game!");
else
this.statusLabel.setText("Game over. " + this.players[this.winnerPlayer].getName() + " won the game!");
}
this.setGameState(GameState.Over);
}
/**
* Function to switch the player
* Function to switch the current player
*/
private void switchPlayer() {
this.players[player].setIsMyTurn(false);
this.players[this.currentPlayer].setIsMyTurn(false);
if (player == 0) {
player = 1;
if (this.currentPlayer == 0) {
this.currentPlayer = 1;
} else {
player = 0;
this.currentPlayer = 0;
}
this.players[player].setIsMyTurn(true);
this.players[this.currentPlayer].setIsMyTurn(true);
}
@Override
public void actionPerformed(ActionEvent e) {
// catch player moves
if (e.getSource() instanceof Player) {
Player src = (Player) e.getSource();
if (e.getActionCommand().startsWith("doMove")
&& (this.gameType == GameType.RemoteClient || this.players[this.currentPlayer].equals(src))) {
boolean res = this.gameBoard.insertPlate(new Plate(src.getUsedPlateType()),
Integer.parseInt(e.getActionCommand().split(" ")[1]));
if (!res) {
// beep in case of error
Toolkit.getDefaultToolkit().beep();
}
if (this.gameType != GameType.RemoteClient && this.gameState.equals(GameState.Running))
switchPlayer();
} else if (e.getActionCommand().startsWith("isMyTurnChanged")) {
if(e.getActionCommand().split(" ")[1].equals("true")) {
if(!this.gameType.equals(GameType.Local) && src instanceof LocalPlayer)
this.statusLabel.setText("Running, it's your turn!'");
else
this.statusLabel.setText("Running, it's " + src.getName() + "'s turn!'");
}
}
}
// catch board events
if (e.getSource() instanceof GameBoard) {
if (e.getActionCommand().startsWith("gameOver")) {
this.gameOver(e.getActionCommand().split(" ")[1]);
}
}
}
public void setGameState(GameState newState) {
this.gameState = newState;
switch (this.gameState) {
case Idle:
this.statusLabel.setText("IDLE");
this.buttonRow.setEnabled(false);
break;
case Waiting:
this.statusLabel.setText("Waiting for opponent to connect");
this.buttonRow.setEnabled(false);
break;
case Running:
this.buttonRow.setEnabled(true);
break;
case Over:
this.buttonRow.setEnabled(false);
break;
default:
break;
}
}
/**
@ -155,29 +270,6 @@ public class ConnectFour extends JFrame implements PlayerMoveListener {
return this.getSize().height < this.getSize().width;
}
/**
* Catch player moves
*/
@Override
public void movePerformed(int column, Player src) {
if (this.players[this.player].equals(src) || this.gameType == GameType.RemoteClient) {
String res;
res = this.gameBoard.insertPlate(new Plate(src.usingPlateType), column);
if (res == "err") {
// beep in case of error
Toolkit.getDefaultToolkit().beep();
} else if (res != "ok" && res != "err") {
PlateType winnerType = PlateType.valueOf(res);
System.out.println("A player won: " + winnerType);
}
if (this.gameType != GameType.RemoteClient)
switchPlayer();
}
}
/**
* Override validate in order to resacle the components when the window is
* rescaled
@ -206,9 +298,6 @@ public class ConnectFour extends JFrame implements PlayerMoveListener {
ConnectFour game;
System.out.println(game = new ConnectFour());
System.out.println("argc: " + args.length);
System.out.println("args[0]: " + args[0]);
if (args.length <= 0)
game.startNewGame(GameType.Local);
else if (args[0].equals("server"))

View file

@ -20,6 +20,8 @@ package de.itsblue.ConnectFour;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import de.itsblue.ConnectFour.Plate.PlateType;
@ -58,6 +60,16 @@ public class GameBoard extends JPanel {
*/
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
*/
@ -85,11 +97,11 @@ public class GameBoard extends JPanel {
* @return "ok" if the inserton was successfull, "err" if the column is full,
* PlateType as string if a plate type has won
*/
public String insertPlate(Plate plate, int column) {
public boolean insertPlate(Plate plate, int column) {
// check if the column is out of range
if (column > boardColumns - 1 || this.boardLocked)
return "err";
return false;
// search for an empty row
for (int i = boardRows - 1; i >= 0; i--) {
@ -97,16 +109,13 @@ public class GameBoard extends JPanel {
// if the container is empty -> add the plate
this.BoardContainers[column][i].insertPlate(plate);
PlateType winCheckResult = this.checkForWin();
this.checkForResult();
if (winCheckResult == null)
return "ok";
return winCheckResult.toString();
return true;
}
}
return "err";
return false;
}
/**
@ -169,18 +178,40 @@ public class GameBoard extends JPanel {
* @return <code>null</code> if there was no matching chain; otherwise the
* PlateType of the chain that was found
*/
public PlateType checkForWin() {
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)
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
@ -325,6 +356,38 @@ public class GameBoard extends JPanel {
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
*/

View file

@ -21,6 +21,8 @@ package de.itsblue.ConnectFour.player;
import de.itsblue.ConnectFour.Plate.*;
import java.util.ArrayList;
import java.awt.event.*;
import java.awt.*;
import de.itsblue.ConnectFour.*;
@ -35,22 +37,27 @@ public abstract class Player {
/**
* The button row used to control the game.
*/
public ButtonRow gameControllingButtonRow;
protected ButtonRow gameControllingButtonRow;
/**
* The type of plate the player is using.
*/
public PlateType usingPlateType;
protected PlateType usingPlateType;
/**
* An array containing all move listeners
* An array containing all action listeners
*/
ArrayList<PlayerMoveListener> playerMoveListeners = new ArrayList<PlayerMoveListener>();
private ArrayList<ActionListener> playerActionListeners = new ArrayList<ActionListener>();
/**
* The current action id
*/
private int currentActionId = 0;
/**
* Whether it is this player's turn
*/
public boolean isMyTurn = false;
protected boolean isMyTurn = false;
/**
* Constructor
@ -72,6 +79,7 @@ public abstract class Player {
if (isMyTurn)
this.gameControllingButtonRow.setColor(Plate.getColor(this.usingPlateType));
this.fireActionListeners("isMyTurnChanged " + (isMyTurn ? "true" : "false"));
}
/**
@ -79,31 +87,60 @@ public abstract class Player {
*
* @param column the column to insert the plate into
*/
public void doMove(int column) {
protected void doMove(int column) {
if (this.isMyTurn) {
System.out.println("[LOG] " + Plate.getColor(this.usingPlateType) + " is doing a move in col: " + column);
for (PlayerMoveListener playerMoveListener : playerMoveListeners) {
playerMoveListener.movePerformed(column, this);
}
this.fireActionListeners("doMove " + column);
}
}
/**
* Function to add a move listener
* Function to get the plate type used by the player
*
* @return used plate type
*/
public PlateType getUsedPlateType() {
return this.usingPlateType;
}
/**
* Function to get the name of the player
*
* @return player name
*/
public String getName() {
Color winnerColor = Plate.getColor(this.usingPlateType);
return winnerColor.equals(Color.BLACK) ? "Black":"Red";
}
/**
* 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 addMoveListener(PlayerMoveListener listener) {
this.playerMoveListeners.add(listener);
public void addActionListener(ActionListener listener) {
this.playerActionListeners.add(listener);
}
/**
* Function to remove a move listener
* Function to remove an ActionListener
*
* @param listener the listener to remove
*/
public void removeMoveListener(PlayerMoveListener listener) {
if(this.playerMoveListeners.contains(listener))
this.playerMoveListeners.remove(listener);
public void removeMoveListener(ActionListener listener) {
if (this.playerActionListeners.contains(listener))
this.playerActionListeners.remove(listener);
}
}

View file

@ -1,5 +0,0 @@
package de.itsblue.ConnectFour.player;
public abstract interface PlayerMoveListener {
public abstract void movePerformed(int column, Player src);
}

View file

@ -0,0 +1,116 @@
/*
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.player;
import de.itsblue.ConnectFour.Plate.*;
import java.awt.event.*;
import java.util.Scanner;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import de.itsblue.ConnectFour.*;
/**
* Player is an abstract class meant for usage with de.itsblue.ConnectFour. It
* is a template for a connect four player.
*
* @author Dorian Zedler
*/
public abstract class RemotePlayer extends Player implements ActionListener {
protected Socket socket;
protected Scanner in;
protected PrintWriter out;
protected Player opponent;
public RemotePlayer(ButtonRow gameControllingButtonRow, PlateType usingPlateType, Player opponent) {
super(gameControllingButtonRow, usingPlateType);
this.opponent = opponent;
opponent.addActionListener(this);
}
protected void startListening() {
// listen to the socket
ExecutorService pool = Executors.newFixedThreadPool(200);
pool.execute(this.new SocketListener(this.in, this));
}
protected abstract void handleResponse(String response);
/**
* Function to set wether it is this player's turn
*/
@Override
public void setIsMyTurn(boolean isMyTurn) {
super.setIsMyTurn(isMyTurn);
if (isMyTurn)
this.gameControllingButtonRow.setEnabled(false);
else
this.gameControllingButtonRow.setEnabled(true);
}
@Override
public void actionPerformed(ActionEvent e) {
if(!(e.getSource() instanceof Player))
return;
if (e.getActionCommand().startsWith("doMove")) {
int column = Integer.parseInt(e.getActionCommand().split(" ")[1]);
try {
this.out.println("movePerformed " + column);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
private class SocketListener implements Runnable {
RemotePlayer parent;
Scanner in;
public SocketListener(Scanner in, RemotePlayer parent) {
this.parent = parent;
this.in = in;
}
@Override
public void run() {
// listen to the socket
try {
while (in.hasNextLine()) {
String response = in.nextLine();
this.parent.handleResponse(response);
}
out.println("QUIT");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

View file

@ -18,7 +18,6 @@
package de.itsblue.ConnectFour.player;
import java.awt.event.*;
import java.util.Scanner;
import java.io.IOException;
import java.io.PrintWriter;
@ -35,114 +34,39 @@ import de.itsblue.ConnectFour.*;
*
* @author Dorian Zedler
*/
public class RemotePlayerClient extends Player implements PlayerMoveListener {
private Socket socket;
private Scanner in;
private PrintWriter out;
private Player opponent;
public class RemotePlayerClient extends RemotePlayer {
/**
* Constructor
*
* @param controlledByButtonRow The button row used to control the player.
*/
public RemotePlayerClient(ButtonRow gameControllingButtonRow, String serverAddress, Player opponent) {
super(gameControllingButtonRow, null);
public RemotePlayerClient(ButtonRow gameControllingButtonRow, String serverAddress, Player opponent)
throws IOException {
super(gameControllingButtonRow, null, opponent);
this.opponent = opponent;
opponent.addMoveListener(this);
try {
// initialize the socket
socket = new Socket(serverAddress, 4444);
in = new Scanner(socket.getInputStream());
out = new PrintWriter(socket.getOutputStream(), true);
System.out.println("Connected to Connect4 server");
// get our plate type
String response = in.nextLine();
if (response.split(" ")[0].equals("setUsingPlateType")) {
this.usingPlateType = PlateType.valueOf(response.split(" ")[1]);
this.opponent.usingPlateType = this.usingPlateType.equals(PlateType.O) ? PlateType.X : PlateType.O;
}
// check if it is out turn
response = in.nextLine();
if (response.split(" ")[0].equals("setIsMyTurn")) {
this.setIsMyTurn(response.split(" ")[1].equals("true"));
opponent.setIsMyTurn(response.split(" ")[1].equals("false"));
}
// listen to the socket
ExecutorService pool = Executors.newFixedThreadPool(200);
pool.execute(this.new ClientListener(in, this));
} catch (IOException e) {
e.printStackTrace();
}
this.startListening();
}
public void handleResponse(String response) {
System.out.println("[CLIENT]GOT: " + response);
if(response.startsWith("setIsMyTurn")) {
if (response.startsWith("setUsingPlateType")) {
// handle PlateType change
this.usingPlateType = PlateType.valueOf(response.split(" ")[1]);
this.opponent.usingPlateType = this.usingPlateType == PlateType.O ? PlateType.X : PlateType.O;
} else if (response.startsWith("setIsMyTurn")) {
// handle turn change
this.setIsMyTurn(response.split(" ")[1].equals("true"));
opponent.setIsMyTurn(response.split(" ")[1].equals("false"));
} else if (response.startsWith("movePerformed")) {
// handle move performed
this.doMove(Integer.parseInt(response.split(" ")[1]));
}
}
/**
* Function to set wether it is this player's turn
*/
@Override
public void setIsMyTurn(boolean isMyTurn) {
super.setIsMyTurn(isMyTurn);
if (isMyTurn)
this.gameControllingButtonRow.setEnabled(false);
else
this.gameControllingButtonRow.setEnabled(true);
}
@Override
public void movePerformed(int column, Player src) {
try {
this.out.println("movePerformed " + column);
} catch (Exception e) {
e.printStackTrace();
}
}
class ClientListener implements Runnable {
RemotePlayerClient parent;
Scanner in;
public ClientListener(Scanner in, RemotePlayerClient parent) {
this.parent = parent;
this.in = in;
}
@Override
public void run() {
// listen to the socket
try {
while (in.hasNextLine()) {
String response = in.nextLine();
this.parent.handleResponse(response);
}
out.println("QUIT");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

View file

@ -18,6 +18,8 @@
package de.itsblue.ConnectFour.player;
import java.awt.event.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
@ -36,14 +38,9 @@ import de.itsblue.ConnectFour.*;
*
* @author Dorian Zedler
*/
public class RemotePlayerServer extends Player implements PlayerMoveListener {
public class RemotePlayerServer extends RemotePlayer {
private ServerSocket listener;
private Socket clientSocket;
private Scanner in;
private PrintWriter out;
private Player opponent;
private ServerSocket serverSocket;
/**
* Constructor
@ -51,90 +48,41 @@ public class RemotePlayerServer extends Player implements PlayerMoveListener {
* @param controlledByButtonRow The button row used to control the player.
* @param usingPlateType The type of plate the player is using.
*/
public RemotePlayerServer(ButtonRow gameControllingButtonRow, PlateType usingPlateType, Player opponent) {
super(gameControllingButtonRow, usingPlateType);
public RemotePlayerServer(ButtonRow gameControllingButtonRow, PlateType usingPlateType, Player opponent)
throws Exception {
super(gameControllingButtonRow, usingPlateType, opponent);
this.opponent = opponent;
opponent.addMoveListener(this);
try {
this.listener = new ServerSocket(4444);
this.serverSocket = new ServerSocket(4444);
System.out.println("Connect4 Server is Running...");
this.clientSocket = listener.accept();
this.in = new Scanner(this.clientSocket.getInputStream());
this.out = new PrintWriter(this.clientSocket.getOutputStream(), true);
this.socket = serverSocket.accept(); // gets stuck here until someone connects
this.in = new Scanner(this.socket.getInputStream());
this.out = new PrintWriter(this.socket.getOutputStream(), true);
System.out.println("Client connected");
out.println("setUsingPlateType " + this.opponent.usingPlateType.name());
out.println("setIsMyTurn " + (this.opponent.isMyTurn ? "true" : "false"));
ExecutorService pool = Executors.newFixedThreadPool(200);
pool.execute(this.new ServerListener(in, this));
} catch (Exception e) {
e.printStackTrace();
}
this.startListening();
}
public void handleResponse(String response) {
System.out.println("GOT: " + response);
if (response.startsWith("movePerformed")) {
this.doMove(Integer.parseInt(response.split(" ")[1]));
}
}
/**
* Function to set wether it is this player's turn
* Catch actions of opponent and forward them to client
*/
@Override
public void setIsMyTurn(boolean isMyTurn) {
super.setIsMyTurn(isMyTurn);
public void actionPerformed(ActionEvent e) {
if(isMyTurn)
this.gameControllingButtonRow.setEnabled(false);
else
this.gameControllingButtonRow.setEnabled(true);
super.actionPerformed(e);
out.println("setIsMyTurn " + (this.isMyTurn ? "false":"true"));
}
@Override
public void movePerformed(int column, Player src) {
System.out.println("movePerformed");
try {
this.out.println("movePerformed " + column);
} catch (Exception e) {
e.printStackTrace();
if (e.getActionCommand().startsWith("isMyTurnChanged")) {
out.println("setIsMyTurn " + (e.getActionCommand().split(" ")[1]));
}
}
class ServerListener implements Runnable {
RemotePlayerServer parent;
Scanner in;
public ServerListener(Scanner in, RemotePlayerServer parent) {
this.parent = parent;
this.in = in;
}
@Override
public void run() {
// listen to the socket
try {
while (in.hasNextLine()) {
String response = in.nextLine();
this.parent.handleResponse(response);
}
out.println("QUIT");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}