/* 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 java.awt.*; import java.awt.event.*; import java.io.IOException; import javax.swing.*; import de.itsblue.ConnectFour.Plate.PlateType; import de.itsblue.ConnectFour.player.Player; import de.itsblue.ConnectFour.player.*; public class ConnectFour extends JFrame implements ActionListener { /** * */ private static final long serialVersionUID = 1L; private GameBoard gameBoard; private ButtonRow buttonRow; private ControllRow controllRow; private JLabel statusLabel; private int currentPlayer = -1; private int winnerPlayer = -1; private Player players[] = new Player[2]; private GameType gameType; String serverIp; enum GameType { Local, RemoteServer, RemoteClient } private GameState gameState = GameState.Idle; enum GameState { Idle, Waiting, Running, Over } enum GameOverReason { Winner, Draw, OpponentDisconnected, Error } /** * Constructor */ ConnectFour() { // initialize window this.setDefaultCloseOperation(EXIT_ON_CLOSE); this.setTitle("Connect 4"); this.setPreferredSize(new Dimension(600, 600)); // initialize layout this.getContentPane().setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); // 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; c.gridy = 0; add(buttonRow, c); c.gridx = 13; c.gridy = 1; add(controllRow, c); c.gridx = 0; 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[1] = new LocalPlayer(this.buttonRow, PlateType.X); 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); try { this.players[1] = new RemotePlayerClient(this.buttonRow, this.serverIp, this.players[0]); } catch (IOException e) { e.printStackTrace(); } // do not add the listener of the local player here, as the client player will // handle its moves in order to make sure they are legal 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].addActionListener(this); this.players[1].addActionListener(this); this.currentPlayer = 0; this.players[0].setIsMyTurn(true); this.players[1].setIsMyTurn(false); break; } } this.setGameState(GameState.Running); } private void gameOver(GameOverReason reason, String attributes) { if (!this.gameState.equals(GameState.Running)) return; if (this.gameType.equals(GameType.RemoteServer)) ((RemotePlayerServer) this.players[1]).notifyGameOver(reason.toString() + " " + attributes); switch (reason) { case Draw: this.winnerPlayer = -1; this.statusLabel.setText("Game over. This was a draw!"); break; case Winner: PlateType winnerPlateType = PlateType.valueOf(attributes); 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!"); case OpponentDisconnected: break; case Error: break; } this.setGameState(GameState.Over); } public void resetGame() { if (!this.gameState.equals(GameState.Over)) return; if(this.gameType.equals(GameType.RemoteServer)) ((RemotePlayerServer) this.players[1]).notifyGameReset(); this.players[0] = null; this.players[1] = null; this.currentPlayer = -1; this.winnerPlayer = -1; this.gameBoard.clearBoard(); this.setGameState(GameState.Idle); } /** * Function to switch the current player */ private void switchPlayer() { this.players[this.currentPlayer].setIsMyTurn(false); if (this.currentPlayer == 0) { this.currentPlayer = 1; } else { this.currentPlayer = 0; } 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") // handle moves && (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(); return; } if (this.gameType != GameType.RemoteClient && this.gameState.equals(GameState.Running)) switchPlayer(); } else if (e.getActionCommand().startsWith("isMyTurnChanged")) { // handle a turn change 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!'"); } } else if (e.getActionCommand().startsWith("gameReset")) { if(!this.gameState.equals(GameState.Over)) this.gameOver(GameOverReason.OpponentDisconnected, ""); else this.resetGame(); } } // catch board events if (e.getSource() instanceof GameBoard) { if (e.getActionCommand().startsWith("gameOver")) { if(e.getActionCommand().split(" ")[1].equals("draw")) this.gameOver(GameOverReason.Draw, ""); else this.gameOver(GameOverReason.Winner, 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; } } /** * Function to check if the window is in landscape or portrait mode * * @return true if window is in landscape mode, false * if it is not */ public boolean landscape() { return this.getSize().height < this.getSize().width; } /** * Override validate in order to resacle the components when the window is * rescaled */ @Override public void validate() { // determine optimal size for board Dimension boardSize = new Dimension(); boardSize.width = (int)(this.landscape() ? this.getSize().height * 0.7 : this.getSize().width * 0.7); boardSize.height = this.landscape() ? this.getSize().width : this.getSize().height; // set board size this.gameBoard.setPreferredSize(boardSize); // set button row size this.buttonRow.setPreferredSize( new Dimension(this.gameBoard.getPreferredSize().width, this.gameBoard.getPreferredSize().height / 7)); // set button column size this.controllRow.setPreferredSize( new Dimension(this.gameBoard.getPreferredSize().width / 4, this.gameBoard.getPreferredSize().height)); // call super super.validate(); } public static void main(final String[] args) { ConnectFour game; System.out.println(game = new ConnectFour()); if (args.length <= 0) game.startNewGame(GameType.Local); else if (args[0].equals("server")) game.startNewGame(GameType.RemoteServer); else if (args[0].equals("client")) { game.serverIp = args[1]; game.startNewGame(GameType.RemoteClient); } else System.out.println("Usage: java ConnectFour.java [server|client]"); } }