Basic Connect Four Game 2
$begingroup$
so here is an updated version of a basic connect four game I had posted previously. Changes include:
- an overhauled validator, simplifying the process by knowing where the
last move was made - Utilized an array with elements referncing how full each column is to simplify the logic of making a move and checking for a tie
- The game is now GUI based utilizing swing to build both the game and main menus, built on top of the previous text based logic and
functions - Added the ability to save, load, and start new games from the GUI menu
Constructive criticism/suggestions related to OOD/OOP as well as how to improve the game GUI are especially welcome, as that is where I think I need the most improvement currently. My next steps should involve adding the ability for players to play over a network, so any tips or advice to get there from where I'm at would be appreciated too.
ConnectFour.Java
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
public class ConnectFour implements Serializable {
private int gameBoard;
private int piecesInColumn; // Each index keeps track of how many game pieces are currently in the corresponding column
private static final int ROWS = 6;
private static final int COLUMNS = 7;
private static final int RED = 1;
private static final int YELLOW = 2;
private Player playerOne;
private Player playerTwo;
private int totalNumTurns;
public ConnectFour() {
this.gameBoard = new int[ROWS][COLUMNS];
this.piecesInColumn = new int[COLUMNS];
//Initialize each position in the game board to empty
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLUMNS; j++) {
gameBoard[i][j] = -1;
}
}
this.totalNumTurns = 0;
}
public static void main(String args) {
new ConnectFour().buildMainMenu();
}
public void buildMainMenu(){
// Construct a main menu that welcomes players and takes their names
JFrame mainFrame = new JFrame();
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel title = new JLabel("Welcome to Connect Four!");
Font titleFont = new Font(Font.SERIF, Font.BOLD, 20);
title.setFont(titleFont);
JPanel titlePanel = new JPanel();
titlePanel.add(title);
JTextField nameOne = new JTextField(10);
JLabel nameOneLabel = new JLabel("Player one's name:");
JTextField nameTwo = new JTextField(10);
JLabel nameTwoLabel = new JLabel("Player two's name");
JPanel namesPanel = new JPanel();
namesPanel.setLayout(new BoxLayout(namesPanel, BoxLayout.Y_AXIS));
namesPanel.add(nameOneLabel);
namesPanel.add(nameOne);
namesPanel.add(nameTwoLabel);
namesPanel.add(nameTwo);
JButton startGameButton = new JButton("Start Game!");
startGameButton.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
//Once we press start game, create player objects based on the names, close the window and open a new window with the game GUI
playerOne = new Player(nameOne.getText());
playerTwo = new Player(nameTwo.getText());
System.out.println("Welcome player one: " + playerOne.getName());
System.out.println("Welcome player two: " + playerTwo.getName());
mainFrame.dispose();
buildGameGUI();
}
});
JPanel startGamePanel = new JPanel();
startGamePanel.add(startGameButton);
JPanel centerPanel = new JPanel();
centerPanel.add(namesPanel);
mainFrame.getContentPane().add(BorderLayout.NORTH,titlePanel);
mainFrame.getContentPane().add(BorderLayout.SOUTH, startGamePanel);
mainFrame.getContentPane().add(BorderLayout.CENTER,centerPanel);
mainFrame.setSize(400,200);
mainFrame.setVisible(true);
}
public void buildGameGUI(){
JFrame gameFrame = new JFrame();
gameFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gameFrame.setSize(500,500);
gameFrame.setVisible(true);
ConnectFourVisualizer gameVisualizer = new ConnectFourVisualizer();
gameFrame.add(BorderLayout.CENTER, gameVisualizer);
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
JMenuItem exit = new JMenuItem("Exit");
JPanel chooseMovePanel = new JPanel();
JLabel chooseMoveLabel = new JLabel( playerOne.getName() + ", Choose a column from 1 to 7: ");
JTextField moveChoiceField = new JTextField(5);
JButton chooseMoveButton = new JButton("Choose");
exit.addActionListener(new ActionListener() {
//Close the window and stop the program if the user clicks exit
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
JMenuItem loadGame = new JMenuItem("Load Game");
//Add an action listener here for loading the game
//1.) Restore the state of connect four object from the connect_four.ser file
//2.) Repaint the game board gui to display the previous state's game board and allow the game to continue
JMenuItem saveGame = new JMenuItem("Save Game");
JMenuItem newGame = new JMenuItem("New Game");
newGame.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//Reset the board and redraw the visual representation based on the empty board
clearBoard();
gameVisualizer.repaint();
totalNumTurns = 0;
piecesInColumn = new int[COLUMNS];
chooseMoveLabel.setText(playerOne.getName() + ", Choose a column from 1 to 7: ");
//Re-enable the buttons and text field in case the previous game had a winner
moveChoiceField.setEnabled(true);
chooseMoveButton.setEnabled(true);
}
});
saveGame.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try{
FileOutputStream fileOutputStream = new FileOutputStream("connect_four.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(ConnectFour.this);
objectOutputStream.close();
fileOutputStream.close();
}catch (IOException ex){
ex.printStackTrace();
}
}
});
loadGame.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
//Set the current Connect four object references and values equal to the previously saved games values
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("connect_four.ser"));
ConnectFour previousGame = (ConnectFour) objectInputStream.readObject();
ConnectFour.this.gameBoard = previousGame.gameBoard;
ConnectFour.this.piecesInColumn = previousGame.piecesInColumn;
ConnectFour.this.playerOne = previousGame.playerOne;
ConnectFour.this.playerTwo = previousGame.playerTwo;
ConnectFour.this.totalNumTurns = previousGame.totalNumTurns;
System.out.println("The number of turns is: " + totalNumTurns);
//Determine whose turn it was when the game was saved, and display the appropriate label
if(totalNumTurns % 2 == 0){
chooseMoveLabel.setText(playerOne.getName() + ", choose a column from 1 to 7:");
}
else{
chooseMoveLabel.setText(playerTwo.getName() + ", choose a column from 1 to 7");
}
}catch(Exception ex){
ex.printStackTrace();
}
//Redraw the game gui no correctly show the loaded game state
gameVisualizer.repaint();
printGameBoard();
}
});
//Add the menu items to the menua bar
fileMenu.add(newGame);
fileMenu.add(saveGame);
fileMenu.add(loadGame);
fileMenu.add(exit);
menuBar.add(fileMenu);
gameFrame.getContentPane().add(BorderLayout.NORTH,menuBar);
chooseMovePanel.add(chooseMoveLabel);
chooseMovePanel.add(moveChoiceField);
chooseMovePanel.add(chooseMoveButton);
gameFrame.getContentPane().add(BorderLayout.SOUTH, chooseMovePanel);
chooseMoveButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//Notice here we're displaying whose turn in is after the current player makes their move
int move = Integer.parseInt(moveChoiceField.getText());
if(totalNumTurns % 2 == 0){
//It's player one's turn, so make their move and after change the text to ask for player two
boolean attempt = makeMove(playerOne, move);
if(attempt == false){
chooseMoveLabel.setText("That was an invalid move, please try again " + playerOne.getName()+ ":");
moveChoiceField.setText("");
return;
}
chooseMoveLabel.setText(playerTwo.getName() + ", choose a column from 1 to 7:");
moveChoiceField.setText("");
}
else{
//Otherwise it's player two's turn, so make their move and change the text to ask for player one
boolean attempt = makeMove(playerTwo, move);
if(attempt == false){
chooseMoveLabel.setText("That was an invalid move, please try again " + playerTwo.getName() + ":");
moveChoiceField.setText("");
return;
}
chooseMoveLabel.setText(playerOne.getName() + ", choose a column from 1 to 7:");
moveChoiceField.setText("");
}
printGameBoard();
totalNumTurns++;
gameVisualizer.repaint();
int hasWinner = checkForWinner(move);
if(hasWinner != -1){
moveChoiceField.setEnabled(false);
chooseMoveButton.setEnabled(false);
saveGame.setEnabled(false);
if(hasWinner == RED){
chooseMoveLabel.setText(playerOne.getName() + " wins!");
}
else if(hasWinner == YELLOW){
chooseMoveLabel.setText(playerTwo.getName() + " wins!");
}
}
}
});
}
public class ConnectFourVisualizer extends JPanel{
private int xBackground;
private int yBackground;
private int widthBackground;
private int heightBackGround;
private int xGameSlot;
private int yGameSlot;
private int widthGameSlot;
private int heightGameSlot;
public ConnectFourVisualizer(){
this.widthGameSlot = 20;
this.heightGameSlot = 20;
this.heightBackGround = 300;
this.widthBackground = 350;
this.xBackground = 75;
this.yBackground = 100;
}
public void paintComponent(Graphics g){
g.setColor(Color.YELLOW);
g.fillRect(xBackground, yBackground, widthBackground, heightBackGround);
int currentX = xBackground + 40;
int currentY = yBackground + 40;
for(int i = 0; i < 6; i++) {
for (int j = 0; j < 7; j++) {
if(gameBoard[i][j] == RED){
g.setColor(Color.RED);
}
else if(gameBoard[i][j] == YELLOW){
g.setColor(Color.BLACK);
}
else{
g.setColor(Color.WHITE);
}
g.fillOval(currentX, currentY, widthGameSlot, heightGameSlot);
currentX += 40;
}
currentX = xBackground + 40;
currentY += 40;
}
}
}
public boolean makeMove(Player player, int column) {
/* Since row position is determined by how many pieces are currently in a given column,
we only need to choose a column position and the row position will be determined as a result. */
//Decrement the column value by 1, as our array is zero indexed.
column--;
//Check if the column chosen is valid
if (column < 0 || column >= COLUMNS) {
System.out.println("Column choice must be between 1 and 7 !");
return false;
}
//Check if the column chosen is already full
if (isColumnFull(column)) {
System.out.println("That column is already full!");
return false;
}
/*Otherwise, start from the bottom of the column and change the value in
the first open row to the player's number*/
else {
// for (int i = ROWS - 1; i >= 0; i--) {
// if (gameBoard[i][column] == -1) {
// gameBoard[i][column] = player.getPlayerNumber();
// break;
// }
// }
// return true;
int nextOpenRow = ROWS - 1 - piecesInColumn[column];
gameBoard[nextOpenRow][column] = player.getPlayerNumber();
piecesInColumn[column]++;
return true;
}
}
public int checkForWinner(int latestMove){
//If the gameboard is full, the game is a TIE
if(isBoardFull()){
// 0 indicates a TIE
return 0;
}
int currentCol = latestMove - 1; //Remember, the user sees the columns numbered 1 -> 7 but our array is 0 indexed
int currentRow = ROWS - piecesInColumn[currentCol]; // Determine which row the last piece was placed in
int rows = checkRows(currentRow);
int columns = checkColumns(currentCol);
int diagonals = checkDiagonals(currentRow,currentCol);
if(rows == RED || columns == RED || diagonals == RED){
return RED;
}
if(rows == YELLOW || columns == YELLOW || diagonals == YELLOW){
return YELLOW;
}
//Otherwise we don't have a winner yer
return -1;
}
private int checkRows(int currentRow){
//Obtain the row and column of the latest move
for (int i = 0; i < COLUMNS - 3; i++) {
if (gameBoard[currentRow][i] == RED &&
gameBoard[currentRow][i + 1] == RED &&
gameBoard[currentRow][i + 2] == RED &&
gameBoard[currentRow][i + 3] == RED) {
return RED;
}
if (gameBoard[currentRow][i] == YELLOW &&
gameBoard[currentRow][i + 1] == YELLOW &&
gameBoard[currentRow][i + 2] == YELLOW &&
gameBoard[currentRow][i + 3] == YELLOW) {
return YELLOW;
}
}
//Otherwise, we have no winner yet
return -1;
}
private int checkColumns( int currentCol){
//Check the column
// if(piecesInColumn[currentCol] < 4){
// //If there are less than 4 game pieces in the column, there cannot be a winner here by column
// return -1;
// }
for (int i = ROWS - 1; i >= ROWS - 3; i--) {
if (gameBoard[i][currentCol] == RED &&
gameBoard[i - 1][currentCol] == RED &&
gameBoard[i - 2][currentCol] == RED &&
gameBoard[i - 3][currentCol] == RED) {
return RED;
}
if (gameBoard[i][currentCol] == YELLOW &&
gameBoard[i - 1][currentCol] == YELLOW &&
gameBoard[i - 2][currentCol] == YELLOW &&
gameBoard[i - 3][currentCol] == YELLOW) {
return YELLOW;
}
}
//Otherwise we have no winner
return -1;
}
private int checkDiagonals(int currentRow, int currentCol){
//Check the left-to-right upward diagonal containing the latest move
int startingRow = currentRow + 3;
int startingCol = currentCol - 3;
//get to a starting position that is valid on the board
while (startingRow > ROWS - 1 || startingCol < 0) {
startingRow--;
startingCol++;
}
while (startingRow - 3 >= 0 && startingCol + 3 < COLUMNS ) {
if (gameBoard[startingRow][startingCol] == RED &&
gameBoard[startingRow - 1][startingCol + 1] == RED &&
gameBoard[startingRow - 2][startingCol + 2] == RED &&
gameBoard[startingRow - 3][startingCol + 3] == RED) {
return RED;
}
if (gameBoard[startingRow][startingCol] == YELLOW &&
gameBoard[startingRow - 1][startingCol + 1] == YELLOW &&
gameBoard[startingRow - 2][startingCol + 2] == YELLOW &&
gameBoard[startingRow - 3][startingCol + 3] == YELLOW) {
return YELLOW;
}
startingRow--;
startingCol++;
}
//Check the left to right downward diagonal containing the latest placed game piece
//Reset the starting row and column variables
startingRow = currentRow - 3;
startingCol = currentCol - 3;
while(startingRow < 0 || startingCol < 0 ){
startingCol++;
startingRow++;
}
while(startingRow + 3 < ROWS && startingCol + 3 < COLUMNS ){
if(gameBoard[startingRow][startingCol] == RED &&
gameBoard[startingRow + 1][startingCol + 1] == RED &&
gameBoard[startingRow + 2][startingCol + 2] == RED &&
gameBoard[startingRow + 3][startingCol + 3] == RED){
return RED;
}
if(gameBoard[startingRow][startingCol] == YELLOW &&
gameBoard[startingRow + 1][startingCol + 1] == YELLOW &&
gameBoard[startingRow + 2][startingCol + 2] == YELLOW &&
gameBoard[startingRow + 3][startingCol + 3] == YELLOW){
return YELLOW;
}
startingRow++;
startingCol++;
}
//Otherwise, we have no winner yet
return -1;
}
private boolean isColumnFull(int columnNumber) {
/*Based on the way pieces are placed in a game of connect four, if the very first row of a column has
a piece in it, the column must be full.*/
if (gameBoard[0][columnNumber] == -1) {
return false;
} else {
return true;
}
}
private boolean isBoardFull() {
//If any value in our board is -1, the board is not full
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLUMNS; j++) {
if (gameBoard[i][j] == -1) {
return false;
}
}
}
//Otherwise the board is full
return true;
}
public void printGameBoard() {
System.out.println("==============================");
//Display the number for each column
System.out.println("1 2 3 4 5 6 7");
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLUMNS; j++) {
if (gameBoard[i][j] == RED) {
System.out.print("R ");
} else if (gameBoard[i][j] == YELLOW) {
System.out.print("Y ");
} else {
System.out.print("- ");
}
}
System.out.println();
}
System.out.println("==============================");
}
public void clearBoard() {
//Reset all board positions to -1
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLUMNS; j++) {
gameBoard[i][j] = -1;
}
}
}
}
Player.Java:
import java.io.Serializable;
public class Player implements Serializable {
private final String name;
private static int counter = 0;
private int playerNumber;
public Player(String name) {
this.name = name;
this.counter++;
this.playerNumber = counter;
}
public String getName() {
return name;
}
public int getPlayerNumber() {
return playerNumber;
}
}
java object-oriented swing serialization connect-four
$endgroup$
add a comment |
$begingroup$
so here is an updated version of a basic connect four game I had posted previously. Changes include:
- an overhauled validator, simplifying the process by knowing where the
last move was made - Utilized an array with elements referncing how full each column is to simplify the logic of making a move and checking for a tie
- The game is now GUI based utilizing swing to build both the game and main menus, built on top of the previous text based logic and
functions - Added the ability to save, load, and start new games from the GUI menu
Constructive criticism/suggestions related to OOD/OOP as well as how to improve the game GUI are especially welcome, as that is where I think I need the most improvement currently. My next steps should involve adding the ability for players to play over a network, so any tips or advice to get there from where I'm at would be appreciated too.
ConnectFour.Java
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
public class ConnectFour implements Serializable {
private int gameBoard;
private int piecesInColumn; // Each index keeps track of how many game pieces are currently in the corresponding column
private static final int ROWS = 6;
private static final int COLUMNS = 7;
private static final int RED = 1;
private static final int YELLOW = 2;
private Player playerOne;
private Player playerTwo;
private int totalNumTurns;
public ConnectFour() {
this.gameBoard = new int[ROWS][COLUMNS];
this.piecesInColumn = new int[COLUMNS];
//Initialize each position in the game board to empty
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLUMNS; j++) {
gameBoard[i][j] = -1;
}
}
this.totalNumTurns = 0;
}
public static void main(String args) {
new ConnectFour().buildMainMenu();
}
public void buildMainMenu(){
// Construct a main menu that welcomes players and takes their names
JFrame mainFrame = new JFrame();
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel title = new JLabel("Welcome to Connect Four!");
Font titleFont = new Font(Font.SERIF, Font.BOLD, 20);
title.setFont(titleFont);
JPanel titlePanel = new JPanel();
titlePanel.add(title);
JTextField nameOne = new JTextField(10);
JLabel nameOneLabel = new JLabel("Player one's name:");
JTextField nameTwo = new JTextField(10);
JLabel nameTwoLabel = new JLabel("Player two's name");
JPanel namesPanel = new JPanel();
namesPanel.setLayout(new BoxLayout(namesPanel, BoxLayout.Y_AXIS));
namesPanel.add(nameOneLabel);
namesPanel.add(nameOne);
namesPanel.add(nameTwoLabel);
namesPanel.add(nameTwo);
JButton startGameButton = new JButton("Start Game!");
startGameButton.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
//Once we press start game, create player objects based on the names, close the window and open a new window with the game GUI
playerOne = new Player(nameOne.getText());
playerTwo = new Player(nameTwo.getText());
System.out.println("Welcome player one: " + playerOne.getName());
System.out.println("Welcome player two: " + playerTwo.getName());
mainFrame.dispose();
buildGameGUI();
}
});
JPanel startGamePanel = new JPanel();
startGamePanel.add(startGameButton);
JPanel centerPanel = new JPanel();
centerPanel.add(namesPanel);
mainFrame.getContentPane().add(BorderLayout.NORTH,titlePanel);
mainFrame.getContentPane().add(BorderLayout.SOUTH, startGamePanel);
mainFrame.getContentPane().add(BorderLayout.CENTER,centerPanel);
mainFrame.setSize(400,200);
mainFrame.setVisible(true);
}
public void buildGameGUI(){
JFrame gameFrame = new JFrame();
gameFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gameFrame.setSize(500,500);
gameFrame.setVisible(true);
ConnectFourVisualizer gameVisualizer = new ConnectFourVisualizer();
gameFrame.add(BorderLayout.CENTER, gameVisualizer);
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
JMenuItem exit = new JMenuItem("Exit");
JPanel chooseMovePanel = new JPanel();
JLabel chooseMoveLabel = new JLabel( playerOne.getName() + ", Choose a column from 1 to 7: ");
JTextField moveChoiceField = new JTextField(5);
JButton chooseMoveButton = new JButton("Choose");
exit.addActionListener(new ActionListener() {
//Close the window and stop the program if the user clicks exit
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
JMenuItem loadGame = new JMenuItem("Load Game");
//Add an action listener here for loading the game
//1.) Restore the state of connect four object from the connect_four.ser file
//2.) Repaint the game board gui to display the previous state's game board and allow the game to continue
JMenuItem saveGame = new JMenuItem("Save Game");
JMenuItem newGame = new JMenuItem("New Game");
newGame.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//Reset the board and redraw the visual representation based on the empty board
clearBoard();
gameVisualizer.repaint();
totalNumTurns = 0;
piecesInColumn = new int[COLUMNS];
chooseMoveLabel.setText(playerOne.getName() + ", Choose a column from 1 to 7: ");
//Re-enable the buttons and text field in case the previous game had a winner
moveChoiceField.setEnabled(true);
chooseMoveButton.setEnabled(true);
}
});
saveGame.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try{
FileOutputStream fileOutputStream = new FileOutputStream("connect_four.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(ConnectFour.this);
objectOutputStream.close();
fileOutputStream.close();
}catch (IOException ex){
ex.printStackTrace();
}
}
});
loadGame.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
//Set the current Connect four object references and values equal to the previously saved games values
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("connect_four.ser"));
ConnectFour previousGame = (ConnectFour) objectInputStream.readObject();
ConnectFour.this.gameBoard = previousGame.gameBoard;
ConnectFour.this.piecesInColumn = previousGame.piecesInColumn;
ConnectFour.this.playerOne = previousGame.playerOne;
ConnectFour.this.playerTwo = previousGame.playerTwo;
ConnectFour.this.totalNumTurns = previousGame.totalNumTurns;
System.out.println("The number of turns is: " + totalNumTurns);
//Determine whose turn it was when the game was saved, and display the appropriate label
if(totalNumTurns % 2 == 0){
chooseMoveLabel.setText(playerOne.getName() + ", choose a column from 1 to 7:");
}
else{
chooseMoveLabel.setText(playerTwo.getName() + ", choose a column from 1 to 7");
}
}catch(Exception ex){
ex.printStackTrace();
}
//Redraw the game gui no correctly show the loaded game state
gameVisualizer.repaint();
printGameBoard();
}
});
//Add the menu items to the menua bar
fileMenu.add(newGame);
fileMenu.add(saveGame);
fileMenu.add(loadGame);
fileMenu.add(exit);
menuBar.add(fileMenu);
gameFrame.getContentPane().add(BorderLayout.NORTH,menuBar);
chooseMovePanel.add(chooseMoveLabel);
chooseMovePanel.add(moveChoiceField);
chooseMovePanel.add(chooseMoveButton);
gameFrame.getContentPane().add(BorderLayout.SOUTH, chooseMovePanel);
chooseMoveButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//Notice here we're displaying whose turn in is after the current player makes their move
int move = Integer.parseInt(moveChoiceField.getText());
if(totalNumTurns % 2 == 0){
//It's player one's turn, so make their move and after change the text to ask for player two
boolean attempt = makeMove(playerOne, move);
if(attempt == false){
chooseMoveLabel.setText("That was an invalid move, please try again " + playerOne.getName()+ ":");
moveChoiceField.setText("");
return;
}
chooseMoveLabel.setText(playerTwo.getName() + ", choose a column from 1 to 7:");
moveChoiceField.setText("");
}
else{
//Otherwise it's player two's turn, so make their move and change the text to ask for player one
boolean attempt = makeMove(playerTwo, move);
if(attempt == false){
chooseMoveLabel.setText("That was an invalid move, please try again " + playerTwo.getName() + ":");
moveChoiceField.setText("");
return;
}
chooseMoveLabel.setText(playerOne.getName() + ", choose a column from 1 to 7:");
moveChoiceField.setText("");
}
printGameBoard();
totalNumTurns++;
gameVisualizer.repaint();
int hasWinner = checkForWinner(move);
if(hasWinner != -1){
moveChoiceField.setEnabled(false);
chooseMoveButton.setEnabled(false);
saveGame.setEnabled(false);
if(hasWinner == RED){
chooseMoveLabel.setText(playerOne.getName() + " wins!");
}
else if(hasWinner == YELLOW){
chooseMoveLabel.setText(playerTwo.getName() + " wins!");
}
}
}
});
}
public class ConnectFourVisualizer extends JPanel{
private int xBackground;
private int yBackground;
private int widthBackground;
private int heightBackGround;
private int xGameSlot;
private int yGameSlot;
private int widthGameSlot;
private int heightGameSlot;
public ConnectFourVisualizer(){
this.widthGameSlot = 20;
this.heightGameSlot = 20;
this.heightBackGround = 300;
this.widthBackground = 350;
this.xBackground = 75;
this.yBackground = 100;
}
public void paintComponent(Graphics g){
g.setColor(Color.YELLOW);
g.fillRect(xBackground, yBackground, widthBackground, heightBackGround);
int currentX = xBackground + 40;
int currentY = yBackground + 40;
for(int i = 0; i < 6; i++) {
for (int j = 0; j < 7; j++) {
if(gameBoard[i][j] == RED){
g.setColor(Color.RED);
}
else if(gameBoard[i][j] == YELLOW){
g.setColor(Color.BLACK);
}
else{
g.setColor(Color.WHITE);
}
g.fillOval(currentX, currentY, widthGameSlot, heightGameSlot);
currentX += 40;
}
currentX = xBackground + 40;
currentY += 40;
}
}
}
public boolean makeMove(Player player, int column) {
/* Since row position is determined by how many pieces are currently in a given column,
we only need to choose a column position and the row position will be determined as a result. */
//Decrement the column value by 1, as our array is zero indexed.
column--;
//Check if the column chosen is valid
if (column < 0 || column >= COLUMNS) {
System.out.println("Column choice must be between 1 and 7 !");
return false;
}
//Check if the column chosen is already full
if (isColumnFull(column)) {
System.out.println("That column is already full!");
return false;
}
/*Otherwise, start from the bottom of the column and change the value in
the first open row to the player's number*/
else {
// for (int i = ROWS - 1; i >= 0; i--) {
// if (gameBoard[i][column] == -1) {
// gameBoard[i][column] = player.getPlayerNumber();
// break;
// }
// }
// return true;
int nextOpenRow = ROWS - 1 - piecesInColumn[column];
gameBoard[nextOpenRow][column] = player.getPlayerNumber();
piecesInColumn[column]++;
return true;
}
}
public int checkForWinner(int latestMove){
//If the gameboard is full, the game is a TIE
if(isBoardFull()){
// 0 indicates a TIE
return 0;
}
int currentCol = latestMove - 1; //Remember, the user sees the columns numbered 1 -> 7 but our array is 0 indexed
int currentRow = ROWS - piecesInColumn[currentCol]; // Determine which row the last piece was placed in
int rows = checkRows(currentRow);
int columns = checkColumns(currentCol);
int diagonals = checkDiagonals(currentRow,currentCol);
if(rows == RED || columns == RED || diagonals == RED){
return RED;
}
if(rows == YELLOW || columns == YELLOW || diagonals == YELLOW){
return YELLOW;
}
//Otherwise we don't have a winner yer
return -1;
}
private int checkRows(int currentRow){
//Obtain the row and column of the latest move
for (int i = 0; i < COLUMNS - 3; i++) {
if (gameBoard[currentRow][i] == RED &&
gameBoard[currentRow][i + 1] == RED &&
gameBoard[currentRow][i + 2] == RED &&
gameBoard[currentRow][i + 3] == RED) {
return RED;
}
if (gameBoard[currentRow][i] == YELLOW &&
gameBoard[currentRow][i + 1] == YELLOW &&
gameBoard[currentRow][i + 2] == YELLOW &&
gameBoard[currentRow][i + 3] == YELLOW) {
return YELLOW;
}
}
//Otherwise, we have no winner yet
return -1;
}
private int checkColumns( int currentCol){
//Check the column
// if(piecesInColumn[currentCol] < 4){
// //If there are less than 4 game pieces in the column, there cannot be a winner here by column
// return -1;
// }
for (int i = ROWS - 1; i >= ROWS - 3; i--) {
if (gameBoard[i][currentCol] == RED &&
gameBoard[i - 1][currentCol] == RED &&
gameBoard[i - 2][currentCol] == RED &&
gameBoard[i - 3][currentCol] == RED) {
return RED;
}
if (gameBoard[i][currentCol] == YELLOW &&
gameBoard[i - 1][currentCol] == YELLOW &&
gameBoard[i - 2][currentCol] == YELLOW &&
gameBoard[i - 3][currentCol] == YELLOW) {
return YELLOW;
}
}
//Otherwise we have no winner
return -1;
}
private int checkDiagonals(int currentRow, int currentCol){
//Check the left-to-right upward diagonal containing the latest move
int startingRow = currentRow + 3;
int startingCol = currentCol - 3;
//get to a starting position that is valid on the board
while (startingRow > ROWS - 1 || startingCol < 0) {
startingRow--;
startingCol++;
}
while (startingRow - 3 >= 0 && startingCol + 3 < COLUMNS ) {
if (gameBoard[startingRow][startingCol] == RED &&
gameBoard[startingRow - 1][startingCol + 1] == RED &&
gameBoard[startingRow - 2][startingCol + 2] == RED &&
gameBoard[startingRow - 3][startingCol + 3] == RED) {
return RED;
}
if (gameBoard[startingRow][startingCol] == YELLOW &&
gameBoard[startingRow - 1][startingCol + 1] == YELLOW &&
gameBoard[startingRow - 2][startingCol + 2] == YELLOW &&
gameBoard[startingRow - 3][startingCol + 3] == YELLOW) {
return YELLOW;
}
startingRow--;
startingCol++;
}
//Check the left to right downward diagonal containing the latest placed game piece
//Reset the starting row and column variables
startingRow = currentRow - 3;
startingCol = currentCol - 3;
while(startingRow < 0 || startingCol < 0 ){
startingCol++;
startingRow++;
}
while(startingRow + 3 < ROWS && startingCol + 3 < COLUMNS ){
if(gameBoard[startingRow][startingCol] == RED &&
gameBoard[startingRow + 1][startingCol + 1] == RED &&
gameBoard[startingRow + 2][startingCol + 2] == RED &&
gameBoard[startingRow + 3][startingCol + 3] == RED){
return RED;
}
if(gameBoard[startingRow][startingCol] == YELLOW &&
gameBoard[startingRow + 1][startingCol + 1] == YELLOW &&
gameBoard[startingRow + 2][startingCol + 2] == YELLOW &&
gameBoard[startingRow + 3][startingCol + 3] == YELLOW){
return YELLOW;
}
startingRow++;
startingCol++;
}
//Otherwise, we have no winner yet
return -1;
}
private boolean isColumnFull(int columnNumber) {
/*Based on the way pieces are placed in a game of connect four, if the very first row of a column has
a piece in it, the column must be full.*/
if (gameBoard[0][columnNumber] == -1) {
return false;
} else {
return true;
}
}
private boolean isBoardFull() {
//If any value in our board is -1, the board is not full
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLUMNS; j++) {
if (gameBoard[i][j] == -1) {
return false;
}
}
}
//Otherwise the board is full
return true;
}
public void printGameBoard() {
System.out.println("==============================");
//Display the number for each column
System.out.println("1 2 3 4 5 6 7");
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLUMNS; j++) {
if (gameBoard[i][j] == RED) {
System.out.print("R ");
} else if (gameBoard[i][j] == YELLOW) {
System.out.print("Y ");
} else {
System.out.print("- ");
}
}
System.out.println();
}
System.out.println("==============================");
}
public void clearBoard() {
//Reset all board positions to -1
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLUMNS; j++) {
gameBoard[i][j] = -1;
}
}
}
}
Player.Java:
import java.io.Serializable;
public class Player implements Serializable {
private final String name;
private static int counter = 0;
private int playerNumber;
public Player(String name) {
this.name = name;
this.counter++;
this.playerNumber = counter;
}
public String getName() {
return name;
}
public int getPlayerNumber() {
return playerNumber;
}
}
java object-oriented swing serialization connect-four
$endgroup$
add a comment |
$begingroup$
so here is an updated version of a basic connect four game I had posted previously. Changes include:
- an overhauled validator, simplifying the process by knowing where the
last move was made - Utilized an array with elements referncing how full each column is to simplify the logic of making a move and checking for a tie
- The game is now GUI based utilizing swing to build both the game and main menus, built on top of the previous text based logic and
functions - Added the ability to save, load, and start new games from the GUI menu
Constructive criticism/suggestions related to OOD/OOP as well as how to improve the game GUI are especially welcome, as that is where I think I need the most improvement currently. My next steps should involve adding the ability for players to play over a network, so any tips or advice to get there from where I'm at would be appreciated too.
ConnectFour.Java
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
public class ConnectFour implements Serializable {
private int gameBoard;
private int piecesInColumn; // Each index keeps track of how many game pieces are currently in the corresponding column
private static final int ROWS = 6;
private static final int COLUMNS = 7;
private static final int RED = 1;
private static final int YELLOW = 2;
private Player playerOne;
private Player playerTwo;
private int totalNumTurns;
public ConnectFour() {
this.gameBoard = new int[ROWS][COLUMNS];
this.piecesInColumn = new int[COLUMNS];
//Initialize each position in the game board to empty
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLUMNS; j++) {
gameBoard[i][j] = -1;
}
}
this.totalNumTurns = 0;
}
public static void main(String args) {
new ConnectFour().buildMainMenu();
}
public void buildMainMenu(){
// Construct a main menu that welcomes players and takes their names
JFrame mainFrame = new JFrame();
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel title = new JLabel("Welcome to Connect Four!");
Font titleFont = new Font(Font.SERIF, Font.BOLD, 20);
title.setFont(titleFont);
JPanel titlePanel = new JPanel();
titlePanel.add(title);
JTextField nameOne = new JTextField(10);
JLabel nameOneLabel = new JLabel("Player one's name:");
JTextField nameTwo = new JTextField(10);
JLabel nameTwoLabel = new JLabel("Player two's name");
JPanel namesPanel = new JPanel();
namesPanel.setLayout(new BoxLayout(namesPanel, BoxLayout.Y_AXIS));
namesPanel.add(nameOneLabel);
namesPanel.add(nameOne);
namesPanel.add(nameTwoLabel);
namesPanel.add(nameTwo);
JButton startGameButton = new JButton("Start Game!");
startGameButton.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
//Once we press start game, create player objects based on the names, close the window and open a new window with the game GUI
playerOne = new Player(nameOne.getText());
playerTwo = new Player(nameTwo.getText());
System.out.println("Welcome player one: " + playerOne.getName());
System.out.println("Welcome player two: " + playerTwo.getName());
mainFrame.dispose();
buildGameGUI();
}
});
JPanel startGamePanel = new JPanel();
startGamePanel.add(startGameButton);
JPanel centerPanel = new JPanel();
centerPanel.add(namesPanel);
mainFrame.getContentPane().add(BorderLayout.NORTH,titlePanel);
mainFrame.getContentPane().add(BorderLayout.SOUTH, startGamePanel);
mainFrame.getContentPane().add(BorderLayout.CENTER,centerPanel);
mainFrame.setSize(400,200);
mainFrame.setVisible(true);
}
public void buildGameGUI(){
JFrame gameFrame = new JFrame();
gameFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gameFrame.setSize(500,500);
gameFrame.setVisible(true);
ConnectFourVisualizer gameVisualizer = new ConnectFourVisualizer();
gameFrame.add(BorderLayout.CENTER, gameVisualizer);
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
JMenuItem exit = new JMenuItem("Exit");
JPanel chooseMovePanel = new JPanel();
JLabel chooseMoveLabel = new JLabel( playerOne.getName() + ", Choose a column from 1 to 7: ");
JTextField moveChoiceField = new JTextField(5);
JButton chooseMoveButton = new JButton("Choose");
exit.addActionListener(new ActionListener() {
//Close the window and stop the program if the user clicks exit
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
JMenuItem loadGame = new JMenuItem("Load Game");
//Add an action listener here for loading the game
//1.) Restore the state of connect four object from the connect_four.ser file
//2.) Repaint the game board gui to display the previous state's game board and allow the game to continue
JMenuItem saveGame = new JMenuItem("Save Game");
JMenuItem newGame = new JMenuItem("New Game");
newGame.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//Reset the board and redraw the visual representation based on the empty board
clearBoard();
gameVisualizer.repaint();
totalNumTurns = 0;
piecesInColumn = new int[COLUMNS];
chooseMoveLabel.setText(playerOne.getName() + ", Choose a column from 1 to 7: ");
//Re-enable the buttons and text field in case the previous game had a winner
moveChoiceField.setEnabled(true);
chooseMoveButton.setEnabled(true);
}
});
saveGame.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try{
FileOutputStream fileOutputStream = new FileOutputStream("connect_four.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(ConnectFour.this);
objectOutputStream.close();
fileOutputStream.close();
}catch (IOException ex){
ex.printStackTrace();
}
}
});
loadGame.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
//Set the current Connect four object references and values equal to the previously saved games values
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("connect_four.ser"));
ConnectFour previousGame = (ConnectFour) objectInputStream.readObject();
ConnectFour.this.gameBoard = previousGame.gameBoard;
ConnectFour.this.piecesInColumn = previousGame.piecesInColumn;
ConnectFour.this.playerOne = previousGame.playerOne;
ConnectFour.this.playerTwo = previousGame.playerTwo;
ConnectFour.this.totalNumTurns = previousGame.totalNumTurns;
System.out.println("The number of turns is: " + totalNumTurns);
//Determine whose turn it was when the game was saved, and display the appropriate label
if(totalNumTurns % 2 == 0){
chooseMoveLabel.setText(playerOne.getName() + ", choose a column from 1 to 7:");
}
else{
chooseMoveLabel.setText(playerTwo.getName() + ", choose a column from 1 to 7");
}
}catch(Exception ex){
ex.printStackTrace();
}
//Redraw the game gui no correctly show the loaded game state
gameVisualizer.repaint();
printGameBoard();
}
});
//Add the menu items to the menua bar
fileMenu.add(newGame);
fileMenu.add(saveGame);
fileMenu.add(loadGame);
fileMenu.add(exit);
menuBar.add(fileMenu);
gameFrame.getContentPane().add(BorderLayout.NORTH,menuBar);
chooseMovePanel.add(chooseMoveLabel);
chooseMovePanel.add(moveChoiceField);
chooseMovePanel.add(chooseMoveButton);
gameFrame.getContentPane().add(BorderLayout.SOUTH, chooseMovePanel);
chooseMoveButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//Notice here we're displaying whose turn in is after the current player makes their move
int move = Integer.parseInt(moveChoiceField.getText());
if(totalNumTurns % 2 == 0){
//It's player one's turn, so make their move and after change the text to ask for player two
boolean attempt = makeMove(playerOne, move);
if(attempt == false){
chooseMoveLabel.setText("That was an invalid move, please try again " + playerOne.getName()+ ":");
moveChoiceField.setText("");
return;
}
chooseMoveLabel.setText(playerTwo.getName() + ", choose a column from 1 to 7:");
moveChoiceField.setText("");
}
else{
//Otherwise it's player two's turn, so make their move and change the text to ask for player one
boolean attempt = makeMove(playerTwo, move);
if(attempt == false){
chooseMoveLabel.setText("That was an invalid move, please try again " + playerTwo.getName() + ":");
moveChoiceField.setText("");
return;
}
chooseMoveLabel.setText(playerOne.getName() + ", choose a column from 1 to 7:");
moveChoiceField.setText("");
}
printGameBoard();
totalNumTurns++;
gameVisualizer.repaint();
int hasWinner = checkForWinner(move);
if(hasWinner != -1){
moveChoiceField.setEnabled(false);
chooseMoveButton.setEnabled(false);
saveGame.setEnabled(false);
if(hasWinner == RED){
chooseMoveLabel.setText(playerOne.getName() + " wins!");
}
else if(hasWinner == YELLOW){
chooseMoveLabel.setText(playerTwo.getName() + " wins!");
}
}
}
});
}
public class ConnectFourVisualizer extends JPanel{
private int xBackground;
private int yBackground;
private int widthBackground;
private int heightBackGround;
private int xGameSlot;
private int yGameSlot;
private int widthGameSlot;
private int heightGameSlot;
public ConnectFourVisualizer(){
this.widthGameSlot = 20;
this.heightGameSlot = 20;
this.heightBackGround = 300;
this.widthBackground = 350;
this.xBackground = 75;
this.yBackground = 100;
}
public void paintComponent(Graphics g){
g.setColor(Color.YELLOW);
g.fillRect(xBackground, yBackground, widthBackground, heightBackGround);
int currentX = xBackground + 40;
int currentY = yBackground + 40;
for(int i = 0; i < 6; i++) {
for (int j = 0; j < 7; j++) {
if(gameBoard[i][j] == RED){
g.setColor(Color.RED);
}
else if(gameBoard[i][j] == YELLOW){
g.setColor(Color.BLACK);
}
else{
g.setColor(Color.WHITE);
}
g.fillOval(currentX, currentY, widthGameSlot, heightGameSlot);
currentX += 40;
}
currentX = xBackground + 40;
currentY += 40;
}
}
}
public boolean makeMove(Player player, int column) {
/* Since row position is determined by how many pieces are currently in a given column,
we only need to choose a column position and the row position will be determined as a result. */
//Decrement the column value by 1, as our array is zero indexed.
column--;
//Check if the column chosen is valid
if (column < 0 || column >= COLUMNS) {
System.out.println("Column choice must be between 1 and 7 !");
return false;
}
//Check if the column chosen is already full
if (isColumnFull(column)) {
System.out.println("That column is already full!");
return false;
}
/*Otherwise, start from the bottom of the column and change the value in
the first open row to the player's number*/
else {
// for (int i = ROWS - 1; i >= 0; i--) {
// if (gameBoard[i][column] == -1) {
// gameBoard[i][column] = player.getPlayerNumber();
// break;
// }
// }
// return true;
int nextOpenRow = ROWS - 1 - piecesInColumn[column];
gameBoard[nextOpenRow][column] = player.getPlayerNumber();
piecesInColumn[column]++;
return true;
}
}
public int checkForWinner(int latestMove){
//If the gameboard is full, the game is a TIE
if(isBoardFull()){
// 0 indicates a TIE
return 0;
}
int currentCol = latestMove - 1; //Remember, the user sees the columns numbered 1 -> 7 but our array is 0 indexed
int currentRow = ROWS - piecesInColumn[currentCol]; // Determine which row the last piece was placed in
int rows = checkRows(currentRow);
int columns = checkColumns(currentCol);
int diagonals = checkDiagonals(currentRow,currentCol);
if(rows == RED || columns == RED || diagonals == RED){
return RED;
}
if(rows == YELLOW || columns == YELLOW || diagonals == YELLOW){
return YELLOW;
}
//Otherwise we don't have a winner yer
return -1;
}
private int checkRows(int currentRow){
//Obtain the row and column of the latest move
for (int i = 0; i < COLUMNS - 3; i++) {
if (gameBoard[currentRow][i] == RED &&
gameBoard[currentRow][i + 1] == RED &&
gameBoard[currentRow][i + 2] == RED &&
gameBoard[currentRow][i + 3] == RED) {
return RED;
}
if (gameBoard[currentRow][i] == YELLOW &&
gameBoard[currentRow][i + 1] == YELLOW &&
gameBoard[currentRow][i + 2] == YELLOW &&
gameBoard[currentRow][i + 3] == YELLOW) {
return YELLOW;
}
}
//Otherwise, we have no winner yet
return -1;
}
private int checkColumns( int currentCol){
//Check the column
// if(piecesInColumn[currentCol] < 4){
// //If there are less than 4 game pieces in the column, there cannot be a winner here by column
// return -1;
// }
for (int i = ROWS - 1; i >= ROWS - 3; i--) {
if (gameBoard[i][currentCol] == RED &&
gameBoard[i - 1][currentCol] == RED &&
gameBoard[i - 2][currentCol] == RED &&
gameBoard[i - 3][currentCol] == RED) {
return RED;
}
if (gameBoard[i][currentCol] == YELLOW &&
gameBoard[i - 1][currentCol] == YELLOW &&
gameBoard[i - 2][currentCol] == YELLOW &&
gameBoard[i - 3][currentCol] == YELLOW) {
return YELLOW;
}
}
//Otherwise we have no winner
return -1;
}
private int checkDiagonals(int currentRow, int currentCol){
//Check the left-to-right upward diagonal containing the latest move
int startingRow = currentRow + 3;
int startingCol = currentCol - 3;
//get to a starting position that is valid on the board
while (startingRow > ROWS - 1 || startingCol < 0) {
startingRow--;
startingCol++;
}
while (startingRow - 3 >= 0 && startingCol + 3 < COLUMNS ) {
if (gameBoard[startingRow][startingCol] == RED &&
gameBoard[startingRow - 1][startingCol + 1] == RED &&
gameBoard[startingRow - 2][startingCol + 2] == RED &&
gameBoard[startingRow - 3][startingCol + 3] == RED) {
return RED;
}
if (gameBoard[startingRow][startingCol] == YELLOW &&
gameBoard[startingRow - 1][startingCol + 1] == YELLOW &&
gameBoard[startingRow - 2][startingCol + 2] == YELLOW &&
gameBoard[startingRow - 3][startingCol + 3] == YELLOW) {
return YELLOW;
}
startingRow--;
startingCol++;
}
//Check the left to right downward diagonal containing the latest placed game piece
//Reset the starting row and column variables
startingRow = currentRow - 3;
startingCol = currentCol - 3;
while(startingRow < 0 || startingCol < 0 ){
startingCol++;
startingRow++;
}
while(startingRow + 3 < ROWS && startingCol + 3 < COLUMNS ){
if(gameBoard[startingRow][startingCol] == RED &&
gameBoard[startingRow + 1][startingCol + 1] == RED &&
gameBoard[startingRow + 2][startingCol + 2] == RED &&
gameBoard[startingRow + 3][startingCol + 3] == RED){
return RED;
}
if(gameBoard[startingRow][startingCol] == YELLOW &&
gameBoard[startingRow + 1][startingCol + 1] == YELLOW &&
gameBoard[startingRow + 2][startingCol + 2] == YELLOW &&
gameBoard[startingRow + 3][startingCol + 3] == YELLOW){
return YELLOW;
}
startingRow++;
startingCol++;
}
//Otherwise, we have no winner yet
return -1;
}
private boolean isColumnFull(int columnNumber) {
/*Based on the way pieces are placed in a game of connect four, if the very first row of a column has
a piece in it, the column must be full.*/
if (gameBoard[0][columnNumber] == -1) {
return false;
} else {
return true;
}
}
private boolean isBoardFull() {
//If any value in our board is -1, the board is not full
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLUMNS; j++) {
if (gameBoard[i][j] == -1) {
return false;
}
}
}
//Otherwise the board is full
return true;
}
public void printGameBoard() {
System.out.println("==============================");
//Display the number for each column
System.out.println("1 2 3 4 5 6 7");
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLUMNS; j++) {
if (gameBoard[i][j] == RED) {
System.out.print("R ");
} else if (gameBoard[i][j] == YELLOW) {
System.out.print("Y ");
} else {
System.out.print("- ");
}
}
System.out.println();
}
System.out.println("==============================");
}
public void clearBoard() {
//Reset all board positions to -1
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLUMNS; j++) {
gameBoard[i][j] = -1;
}
}
}
}
Player.Java:
import java.io.Serializable;
public class Player implements Serializable {
private final String name;
private static int counter = 0;
private int playerNumber;
public Player(String name) {
this.name = name;
this.counter++;
this.playerNumber = counter;
}
public String getName() {
return name;
}
public int getPlayerNumber() {
return playerNumber;
}
}
java object-oriented swing serialization connect-four
$endgroup$
so here is an updated version of a basic connect four game I had posted previously. Changes include:
- an overhauled validator, simplifying the process by knowing where the
last move was made - Utilized an array with elements referncing how full each column is to simplify the logic of making a move and checking for a tie
- The game is now GUI based utilizing swing to build both the game and main menus, built on top of the previous text based logic and
functions - Added the ability to save, load, and start new games from the GUI menu
Constructive criticism/suggestions related to OOD/OOP as well as how to improve the game GUI are especially welcome, as that is where I think I need the most improvement currently. My next steps should involve adding the ability for players to play over a network, so any tips or advice to get there from where I'm at would be appreciated too.
ConnectFour.Java
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
public class ConnectFour implements Serializable {
private int gameBoard;
private int piecesInColumn; // Each index keeps track of how many game pieces are currently in the corresponding column
private static final int ROWS = 6;
private static final int COLUMNS = 7;
private static final int RED = 1;
private static final int YELLOW = 2;
private Player playerOne;
private Player playerTwo;
private int totalNumTurns;
public ConnectFour() {
this.gameBoard = new int[ROWS][COLUMNS];
this.piecesInColumn = new int[COLUMNS];
//Initialize each position in the game board to empty
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLUMNS; j++) {
gameBoard[i][j] = -1;
}
}
this.totalNumTurns = 0;
}
public static void main(String args) {
new ConnectFour().buildMainMenu();
}
public void buildMainMenu(){
// Construct a main menu that welcomes players and takes their names
JFrame mainFrame = new JFrame();
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel title = new JLabel("Welcome to Connect Four!");
Font titleFont = new Font(Font.SERIF, Font.BOLD, 20);
title.setFont(titleFont);
JPanel titlePanel = new JPanel();
titlePanel.add(title);
JTextField nameOne = new JTextField(10);
JLabel nameOneLabel = new JLabel("Player one's name:");
JTextField nameTwo = new JTextField(10);
JLabel nameTwoLabel = new JLabel("Player two's name");
JPanel namesPanel = new JPanel();
namesPanel.setLayout(new BoxLayout(namesPanel, BoxLayout.Y_AXIS));
namesPanel.add(nameOneLabel);
namesPanel.add(nameOne);
namesPanel.add(nameTwoLabel);
namesPanel.add(nameTwo);
JButton startGameButton = new JButton("Start Game!");
startGameButton.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
//Once we press start game, create player objects based on the names, close the window and open a new window with the game GUI
playerOne = new Player(nameOne.getText());
playerTwo = new Player(nameTwo.getText());
System.out.println("Welcome player one: " + playerOne.getName());
System.out.println("Welcome player two: " + playerTwo.getName());
mainFrame.dispose();
buildGameGUI();
}
});
JPanel startGamePanel = new JPanel();
startGamePanel.add(startGameButton);
JPanel centerPanel = new JPanel();
centerPanel.add(namesPanel);
mainFrame.getContentPane().add(BorderLayout.NORTH,titlePanel);
mainFrame.getContentPane().add(BorderLayout.SOUTH, startGamePanel);
mainFrame.getContentPane().add(BorderLayout.CENTER,centerPanel);
mainFrame.setSize(400,200);
mainFrame.setVisible(true);
}
public void buildGameGUI(){
JFrame gameFrame = new JFrame();
gameFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gameFrame.setSize(500,500);
gameFrame.setVisible(true);
ConnectFourVisualizer gameVisualizer = new ConnectFourVisualizer();
gameFrame.add(BorderLayout.CENTER, gameVisualizer);
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
JMenuItem exit = new JMenuItem("Exit");
JPanel chooseMovePanel = new JPanel();
JLabel chooseMoveLabel = new JLabel( playerOne.getName() + ", Choose a column from 1 to 7: ");
JTextField moveChoiceField = new JTextField(5);
JButton chooseMoveButton = new JButton("Choose");
exit.addActionListener(new ActionListener() {
//Close the window and stop the program if the user clicks exit
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
JMenuItem loadGame = new JMenuItem("Load Game");
//Add an action listener here for loading the game
//1.) Restore the state of connect four object from the connect_four.ser file
//2.) Repaint the game board gui to display the previous state's game board and allow the game to continue
JMenuItem saveGame = new JMenuItem("Save Game");
JMenuItem newGame = new JMenuItem("New Game");
newGame.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//Reset the board and redraw the visual representation based on the empty board
clearBoard();
gameVisualizer.repaint();
totalNumTurns = 0;
piecesInColumn = new int[COLUMNS];
chooseMoveLabel.setText(playerOne.getName() + ", Choose a column from 1 to 7: ");
//Re-enable the buttons and text field in case the previous game had a winner
moveChoiceField.setEnabled(true);
chooseMoveButton.setEnabled(true);
}
});
saveGame.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try{
FileOutputStream fileOutputStream = new FileOutputStream("connect_four.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(ConnectFour.this);
objectOutputStream.close();
fileOutputStream.close();
}catch (IOException ex){
ex.printStackTrace();
}
}
});
loadGame.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
//Set the current Connect four object references and values equal to the previously saved games values
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("connect_four.ser"));
ConnectFour previousGame = (ConnectFour) objectInputStream.readObject();
ConnectFour.this.gameBoard = previousGame.gameBoard;
ConnectFour.this.piecesInColumn = previousGame.piecesInColumn;
ConnectFour.this.playerOne = previousGame.playerOne;
ConnectFour.this.playerTwo = previousGame.playerTwo;
ConnectFour.this.totalNumTurns = previousGame.totalNumTurns;
System.out.println("The number of turns is: " + totalNumTurns);
//Determine whose turn it was when the game was saved, and display the appropriate label
if(totalNumTurns % 2 == 0){
chooseMoveLabel.setText(playerOne.getName() + ", choose a column from 1 to 7:");
}
else{
chooseMoveLabel.setText(playerTwo.getName() + ", choose a column from 1 to 7");
}
}catch(Exception ex){
ex.printStackTrace();
}
//Redraw the game gui no correctly show the loaded game state
gameVisualizer.repaint();
printGameBoard();
}
});
//Add the menu items to the menua bar
fileMenu.add(newGame);
fileMenu.add(saveGame);
fileMenu.add(loadGame);
fileMenu.add(exit);
menuBar.add(fileMenu);
gameFrame.getContentPane().add(BorderLayout.NORTH,menuBar);
chooseMovePanel.add(chooseMoveLabel);
chooseMovePanel.add(moveChoiceField);
chooseMovePanel.add(chooseMoveButton);
gameFrame.getContentPane().add(BorderLayout.SOUTH, chooseMovePanel);
chooseMoveButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//Notice here we're displaying whose turn in is after the current player makes their move
int move = Integer.parseInt(moveChoiceField.getText());
if(totalNumTurns % 2 == 0){
//It's player one's turn, so make their move and after change the text to ask for player two
boolean attempt = makeMove(playerOne, move);
if(attempt == false){
chooseMoveLabel.setText("That was an invalid move, please try again " + playerOne.getName()+ ":");
moveChoiceField.setText("");
return;
}
chooseMoveLabel.setText(playerTwo.getName() + ", choose a column from 1 to 7:");
moveChoiceField.setText("");
}
else{
//Otherwise it's player two's turn, so make their move and change the text to ask for player one
boolean attempt = makeMove(playerTwo, move);
if(attempt == false){
chooseMoveLabel.setText("That was an invalid move, please try again " + playerTwo.getName() + ":");
moveChoiceField.setText("");
return;
}
chooseMoveLabel.setText(playerOne.getName() + ", choose a column from 1 to 7:");
moveChoiceField.setText("");
}
printGameBoard();
totalNumTurns++;
gameVisualizer.repaint();
int hasWinner = checkForWinner(move);
if(hasWinner != -1){
moveChoiceField.setEnabled(false);
chooseMoveButton.setEnabled(false);
saveGame.setEnabled(false);
if(hasWinner == RED){
chooseMoveLabel.setText(playerOne.getName() + " wins!");
}
else if(hasWinner == YELLOW){
chooseMoveLabel.setText(playerTwo.getName() + " wins!");
}
}
}
});
}
public class ConnectFourVisualizer extends JPanel{
private int xBackground;
private int yBackground;
private int widthBackground;
private int heightBackGround;
private int xGameSlot;
private int yGameSlot;
private int widthGameSlot;
private int heightGameSlot;
public ConnectFourVisualizer(){
this.widthGameSlot = 20;
this.heightGameSlot = 20;
this.heightBackGround = 300;
this.widthBackground = 350;
this.xBackground = 75;
this.yBackground = 100;
}
public void paintComponent(Graphics g){
g.setColor(Color.YELLOW);
g.fillRect(xBackground, yBackground, widthBackground, heightBackGround);
int currentX = xBackground + 40;
int currentY = yBackground + 40;
for(int i = 0; i < 6; i++) {
for (int j = 0; j < 7; j++) {
if(gameBoard[i][j] == RED){
g.setColor(Color.RED);
}
else if(gameBoard[i][j] == YELLOW){
g.setColor(Color.BLACK);
}
else{
g.setColor(Color.WHITE);
}
g.fillOval(currentX, currentY, widthGameSlot, heightGameSlot);
currentX += 40;
}
currentX = xBackground + 40;
currentY += 40;
}
}
}
public boolean makeMove(Player player, int column) {
/* Since row position is determined by how many pieces are currently in a given column,
we only need to choose a column position and the row position will be determined as a result. */
//Decrement the column value by 1, as our array is zero indexed.
column--;
//Check if the column chosen is valid
if (column < 0 || column >= COLUMNS) {
System.out.println("Column choice must be between 1 and 7 !");
return false;
}
//Check if the column chosen is already full
if (isColumnFull(column)) {
System.out.println("That column is already full!");
return false;
}
/*Otherwise, start from the bottom of the column and change the value in
the first open row to the player's number*/
else {
// for (int i = ROWS - 1; i >= 0; i--) {
// if (gameBoard[i][column] == -1) {
// gameBoard[i][column] = player.getPlayerNumber();
// break;
// }
// }
// return true;
int nextOpenRow = ROWS - 1 - piecesInColumn[column];
gameBoard[nextOpenRow][column] = player.getPlayerNumber();
piecesInColumn[column]++;
return true;
}
}
public int checkForWinner(int latestMove){
//If the gameboard is full, the game is a TIE
if(isBoardFull()){
// 0 indicates a TIE
return 0;
}
int currentCol = latestMove - 1; //Remember, the user sees the columns numbered 1 -> 7 but our array is 0 indexed
int currentRow = ROWS - piecesInColumn[currentCol]; // Determine which row the last piece was placed in
int rows = checkRows(currentRow);
int columns = checkColumns(currentCol);
int diagonals = checkDiagonals(currentRow,currentCol);
if(rows == RED || columns == RED || diagonals == RED){
return RED;
}
if(rows == YELLOW || columns == YELLOW || diagonals == YELLOW){
return YELLOW;
}
//Otherwise we don't have a winner yer
return -1;
}
private int checkRows(int currentRow){
//Obtain the row and column of the latest move
for (int i = 0; i < COLUMNS - 3; i++) {
if (gameBoard[currentRow][i] == RED &&
gameBoard[currentRow][i + 1] == RED &&
gameBoard[currentRow][i + 2] == RED &&
gameBoard[currentRow][i + 3] == RED) {
return RED;
}
if (gameBoard[currentRow][i] == YELLOW &&
gameBoard[currentRow][i + 1] == YELLOW &&
gameBoard[currentRow][i + 2] == YELLOW &&
gameBoard[currentRow][i + 3] == YELLOW) {
return YELLOW;
}
}
//Otherwise, we have no winner yet
return -1;
}
private int checkColumns( int currentCol){
//Check the column
// if(piecesInColumn[currentCol] < 4){
// //If there are less than 4 game pieces in the column, there cannot be a winner here by column
// return -1;
// }
for (int i = ROWS - 1; i >= ROWS - 3; i--) {
if (gameBoard[i][currentCol] == RED &&
gameBoard[i - 1][currentCol] == RED &&
gameBoard[i - 2][currentCol] == RED &&
gameBoard[i - 3][currentCol] == RED) {
return RED;
}
if (gameBoard[i][currentCol] == YELLOW &&
gameBoard[i - 1][currentCol] == YELLOW &&
gameBoard[i - 2][currentCol] == YELLOW &&
gameBoard[i - 3][currentCol] == YELLOW) {
return YELLOW;
}
}
//Otherwise we have no winner
return -1;
}
private int checkDiagonals(int currentRow, int currentCol){
//Check the left-to-right upward diagonal containing the latest move
int startingRow = currentRow + 3;
int startingCol = currentCol - 3;
//get to a starting position that is valid on the board
while (startingRow > ROWS - 1 || startingCol < 0) {
startingRow--;
startingCol++;
}
while (startingRow - 3 >= 0 && startingCol + 3 < COLUMNS ) {
if (gameBoard[startingRow][startingCol] == RED &&
gameBoard[startingRow - 1][startingCol + 1] == RED &&
gameBoard[startingRow - 2][startingCol + 2] == RED &&
gameBoard[startingRow - 3][startingCol + 3] == RED) {
return RED;
}
if (gameBoard[startingRow][startingCol] == YELLOW &&
gameBoard[startingRow - 1][startingCol + 1] == YELLOW &&
gameBoard[startingRow - 2][startingCol + 2] == YELLOW &&
gameBoard[startingRow - 3][startingCol + 3] == YELLOW) {
return YELLOW;
}
startingRow--;
startingCol++;
}
//Check the left to right downward diagonal containing the latest placed game piece
//Reset the starting row and column variables
startingRow = currentRow - 3;
startingCol = currentCol - 3;
while(startingRow < 0 || startingCol < 0 ){
startingCol++;
startingRow++;
}
while(startingRow + 3 < ROWS && startingCol + 3 < COLUMNS ){
if(gameBoard[startingRow][startingCol] == RED &&
gameBoard[startingRow + 1][startingCol + 1] == RED &&
gameBoard[startingRow + 2][startingCol + 2] == RED &&
gameBoard[startingRow + 3][startingCol + 3] == RED){
return RED;
}
if(gameBoard[startingRow][startingCol] == YELLOW &&
gameBoard[startingRow + 1][startingCol + 1] == YELLOW &&
gameBoard[startingRow + 2][startingCol + 2] == YELLOW &&
gameBoard[startingRow + 3][startingCol + 3] == YELLOW){
return YELLOW;
}
startingRow++;
startingCol++;
}
//Otherwise, we have no winner yet
return -1;
}
private boolean isColumnFull(int columnNumber) {
/*Based on the way pieces are placed in a game of connect four, if the very first row of a column has
a piece in it, the column must be full.*/
if (gameBoard[0][columnNumber] == -1) {
return false;
} else {
return true;
}
}
private boolean isBoardFull() {
//If any value in our board is -1, the board is not full
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLUMNS; j++) {
if (gameBoard[i][j] == -1) {
return false;
}
}
}
//Otherwise the board is full
return true;
}
public void printGameBoard() {
System.out.println("==============================");
//Display the number for each column
System.out.println("1 2 3 4 5 6 7");
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLUMNS; j++) {
if (gameBoard[i][j] == RED) {
System.out.print("R ");
} else if (gameBoard[i][j] == YELLOW) {
System.out.print("Y ");
} else {
System.out.print("- ");
}
}
System.out.println();
}
System.out.println("==============================");
}
public void clearBoard() {
//Reset all board positions to -1
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLUMNS; j++) {
gameBoard[i][j] = -1;
}
}
}
}
Player.Java:
import java.io.Serializable;
public class Player implements Serializable {
private final String name;
private static int counter = 0;
private int playerNumber;
public Player(String name) {
this.name = name;
this.counter++;
this.playerNumber = counter;
}
public String getName() {
return name;
}
public int getPlayerNumber() {
return playerNumber;
}
}
java object-oriented swing serialization connect-four
java object-oriented swing serialization connect-four
asked 1 hour ago
dgr27dgr27
364
364
add a comment |
add a comment |
0
active
oldest
votes
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f215062%2fbasic-connect-four-game-2%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
0
active
oldest
votes
0
active
oldest
votes
active
oldest
votes
active
oldest
votes
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f215062%2fbasic-connect-four-game-2%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown