6
\$\begingroup\$

I wrote this program as an assignment for an introductory programming course in Java, which I then decided to improve past the minimum assignment requirements. It allows two human players to play Connect Four. (If you don't know this game, its rules are explained in the code below.)

I would appreciate any feedback such as feature suggestions, bug fixes, optimizations, or other improvements!

/*ConnectFour
by Jughead
November 27th, 2016

This program lets two human players play Connect Four.
*/

import java.util.Scanner;

public class ConnectFour {
    public static void main(String[] args) {
        int turnAlternator, turnNumber, match, player, player1Wins, player2Wins;
        final int NUMBER_OF_ROWS, NUMBER_OF_COLUMNS, MINIMUM_CHAIN_TO_WIN;
        String player1GamePiece, player2GamePiece;
        boolean gameOver;
        String[][] gameBoard;
        Object [] gameOverAndPlayer1WinsAndPlayer2Wins; //Object array of following variables: gameOver, player1Wins, and player2Wins.

        System.out.println("  ____ ___  _   _ _   _ _____ ____ _____   _____ ___  _   _ ____  \n / ___/ _ \\| \\ | | \\ | | ____/ ___|_   _| |  ___/ _ \\| | | |  _ \\ \n" + 
            "| |  | | | |  \\| |  \\| |  _|| |     | |   | |_ | | | | | | | |_| |\n| |__| |_| | |\\  | |\\  | |__| |___  | |   |  _|| |_| | |_| |  _ | \n" + 
            " \\____\\___/|_| \\_|_| \\_|_____\\____| |_|   |_|   \\___/ \\___/|_| \\_\\\n\n" + 
            "Connect Four is a two-player connection game in which the players\nfirst choose a colour and then take turns dropping colored discs\nfrom the top into a seven-column, six-row grid. " + 
            "The pieces fall\nstraight down, occupying the next available space within the column.\nThe objective of the game is to connect four of one's own discs of\nthe same color next to each other vertically, horizontally, or\ndiagonally before your opponent.\n"); //Intro text.

        NUMBER_OF_ROWS = 6;//Number of game board rows.
        NUMBER_OF_COLUMNS = 7;//Number of game board columns.
        MINIMUM_CHAIN_TO_WIN = 4;//Minimum number of sequential game pieces needed to win.

        player1GamePiece = "○";//Sets Player 1 game piece.
        player2GamePiece = "●";//Sets Player 2 game piece.


        gameOverAndPlayer1WinsAndPlayer2Wins = new Object[] {false, 0, 0}; //Setting default values for gameOverAndPlayer1WinsAndPlayer2Wins.
        gameOver = (boolean)gameOverAndPlayer1WinsAndPlayer2Wins[0];
        player1Wins = (int)gameOverAndPlayer1WinsAndPlayer2Wins[1];
        player2Wins = (int)gameOverAndPlayer1WinsAndPlayer2Wins[2];

        outerLoop://Tracks number of matches played.
        for (match = 1; ; match++) {
            gameBoard = emptyBoard(NUMBER_OF_ROWS, NUMBER_OF_COLUMNS);//Resets game board.
            turnNumber = 1;
            System.out.println("_____________________________________________\nMatch: " + match + " | Turn: " + turnNumber + "\n");//Match and turn info.
            turnNumber++;
            printBoard(gameBoard);//Displays board.

            player = startingPlayerTurn(player1Wins, player2Wins);//Decides starting player turn.

            for (turnAlternator = player; ; turnAlternator++, turnNumber++) {//Tracks turn number and alternates between player turns.
                if (turnAlternator % 2 == 0) {
                    player = 2;
                }
                else {
                    player = 1;
                }

                dropPiece(gameBoard, getColumn(player, NUMBER_OF_COLUMNS, player1GamePiece, player2GamePiece) - 1, player, NUMBER_OF_COLUMNS, player1GamePiece, player2GamePiece);//Drops game piece into selected column.
                System.out.println();

                System.out.println("_____________________________________________\nMatch: " + match + " | Turn: " + turnNumber + "\n");//Match and turn info.
                printBoard(gameBoard);

                gameOverAndPlayer1WinsAndPlayer2Wins = checkForWin(gameBoard, gameOverAndPlayer1WinsAndPlayer2Wins, NUMBER_OF_COLUMNS, MINIMUM_CHAIN_TO_WIN, player1GamePiece, player2GamePiece);//Checks game board for winning conditions.

                gameOver = (boolean)gameOverAndPlayer1WinsAndPlayer2Wins[0];//Updates gameOverAndPlayer1WinsAndPlayer2Wins.
                player1Wins = (int)gameOverAndPlayer1WinsAndPlayer2Wins[1];
                player2Wins = (int)gameOverAndPlayer1WinsAndPlayer2Wins[2];

                if (gameOver == true) {//If game is over, restarts the match.
                    System.out.println("_____________________________________________\nPlayer 1 wins: " + player1Wins + " | Player 2 wins: " + player2Wins);//Number of times each player has won.
                    if (player1Wins >= 10 && player2Wins == 0) {//If Player 2 is brutally losing, ends the game.
                        System.out.println("_____________________________________________\nPlayer 2, you should just give up now...");
                        System.exit(0);
                    }
                    if (player2Wins >= 10 && player1Wins == 0) {//If Player 1 is brutally losing, ends the game.
                        System.out.println("_____________________________________________\nPlayer 1, you should just give up now...");
                        System.exit(0);
                    }
                    gameOver = false;
                    gameOverAndPlayer1WinsAndPlayer2Wins[0] = gameOver;
                    continue outerLoop;
                }
            }
        }
    }

    public static String[][] emptyBoard (int NUMBER_OF_ROWS, int NUMBER_OF_COLUMNS) {//Generates empty game board.
        int row, column;
        String[][] emptyBoard;

        for (row = 0, emptyBoard = new String [NUMBER_OF_ROWS][NUMBER_OF_COLUMNS]; row < emptyBoard.length; row++) {
            for (column = 0; column < emptyBoard[row].length; column++) {
                emptyBoard[row][column] = "[ ]";
            }
        }
        return emptyBoard;
    }

    public static void printBoard (String[][] gameBoard) {//Displays game board.
        int row, column, columnLabel;

        for (row = 0; row < gameBoard.length; row++) {//Prints each game board tile.
            for (column = 0; column < gameBoard[row].length; column++) {
                System.out.print(gameBoard[row][column]);
            }
            System.out.println();
        }
        System.out.print(" ");

        for (columnLabel = 1; columnLabel <= gameBoard[row - 1].length; columnLabel++) {//Adds column labels.
            System.out.print(columnLabel + "  ");
        }
        System.out.println();
    }

    public static String[][] dropPiece (String[][] gameBoard, int column, int player, int NUMBER_OF_COLUMNS, String player1GamePiece, String player2GamePiece) {//Drops game piece into selected column.
        int row;

        outerLoop:
        for (row = gameBoard.length - 1; row >= -1; row--) {
            if (row < 0) {//If chosen column is full, asks for a different column.
                System.out.println("Column " + (column + 1) + " is already full.");
                dropPiece(gameBoard, getColumn(player, NUMBER_OF_COLUMNS, player1GamePiece, player2GamePiece) - 1, player, NUMBER_OF_COLUMNS, player1GamePiece, player2GamePiece);
                break;
            }
            if (gameBoard[row][column].equals("[ ]")) {//Drops game piece into next available row of the selected column.
                if (player == 1) {
                    gameBoard[row][column] = "[" + player1GamePiece + "]";
                    break outerLoop;
                }
                if (player == 2) {
                    gameBoard[row][column] = "[" + player2GamePiece + "]";
                    break outerLoop;
                }
            }
        }
        return gameBoard;
    }

    public static Object[] checkForWin (String[][] gameBoard, Object [] gameOverAndPlayer1WinsAndPlayer2Wins, int NUMBER_OF_COLUMNS, int MINIMUM_CHAIN_TO_WIN, String player1GamePiece, String player2GamePiece) {//Checks game board for winning conditions.
        int row, column, player1MaximumChain, player2MaximumChain, diagonalStartPoint, player1Wins, player2Wins, columnNumber, fullColumns;
        boolean gameOver;

        gameOver = (boolean)gameOverAndPlayer1WinsAndPlayer2Wins[0];//Updates Object array gameOverAndPlayer1WinsAndPlayer2Wins. Utilizing gameOverAndPlayer1WinsAndPlayer2Wins allows method checkForWin to return multiple data types.
        player1Wins = (int)gameOverAndPlayer1WinsAndPlayer2Wins[1];
        player2Wins = (int)gameOverAndPlayer1WinsAndPlayer2Wins[2];

        horizontalOuterLoop://Scanning in lines from left to right, checks game board for horizontal chains. Starts checking at top-left and stops checking at bottom-left.
        for (row = 0, player1MaximumChain = 1, player2MaximumChain = 1; row < gameBoard.length; row++) {
            for (column = 0; column < gameBoard[row].length - 1; column++){
                if (gameBoard[row][column].equals("[" + player1GamePiece + "]") && gameBoard[row][column + 1].equals("[" + player1GamePiece + "]")) {
                    player1MaximumChain++;
                    if (player1MaximumChain >= MINIMUM_CHAIN_TO_WIN) {//Checks if Player 1 won horizontally.
                        player1Wins++;
                        System.out.println("\nPlayer 1 won horizontally!");
                        gameOver = true;
                        break horizontalOuterLoop;
                    }
                }
                else if (gameBoard[row][column].equals("[" + player2GamePiece + "]") && gameBoard[row][column + 1].equals("[" + player2GamePiece + "]")) {
                    player2MaximumChain++;
                    if (player2MaximumChain >= MINIMUM_CHAIN_TO_WIN) {//Checks if Player 2 won horizontally.
                        player2Wins++;
                        System.out.println("\nPlayer 2 won horizontally!");
                        gameOver = true;
                        break horizontalOuterLoop;
                    }
                }
                else {
                    player1MaximumChain = 1;
                    player2MaximumChain = 1;
                }
            }
            player1MaximumChain = 1;
            player2MaximumChain = 1;
        }
        verticalOuterLoop://Scanning in lines from top to bottom, checks game board for vertical chains. Starts checking at top-left and stops checking at top-right.
        for (row = 0, column = 0, player1MaximumChain = 1, player2MaximumChain = 1; column < gameBoard[row].length; column++) {
            for (row = 0; row < gameBoard.length - 1; row++){
                if (gameBoard[row][column].equals("[" + player1GamePiece + "]") && gameBoard[row + 1][column].equals("[" + player1GamePiece + "]")) {
                    player1MaximumChain++;
                    if (player1MaximumChain >= MINIMUM_CHAIN_TO_WIN) {//Checks if Player 1 won vertically.
                        player1Wins++;
                        System.out.println("\nPlayer 1 won vertically!");
                        gameOver = true;
                        break verticalOuterLoop;
                    }
                }
                else if (gameBoard[row][column].equals("[" + player2GamePiece + "]") && gameBoard[row + 1][column].equals("[" + player2GamePiece + "]")) {
                    player2MaximumChain++;
                    if (player2MaximumChain >= MINIMUM_CHAIN_TO_WIN) {//Checks if Player 2 won vertically.
                        player2Wins++;
                        System.out.println("\nPlayer 2 won vertically!");
                        gameOver = true;
                        break verticalOuterLoop;
                    }
                }
                else {
                    player1MaximumChain = 1;
                    player2MaximumChain = 1;
                }
            }
            player1MaximumChain = 1;
            player2MaximumChain = 1;
        }
        diagonalDownRightOuterLoop1://Scanning in lines from top-left to bottom-right, checks game board for diagonal chains. Starts checking at bottom-left and stops checking at top-left.
        for (diagonalStartPoint = gameBoard.length - 2, player1MaximumChain = 1, player2MaximumChain = 1; diagonalStartPoint >= 0; diagonalStartPoint--) {
            for (row = diagonalStartPoint, column = 0; row < gameBoard.length - 1 && column < gameBoard[row].length - 1; row++, column++) {
                if (gameBoard[row][column].equals("[" + player1GamePiece + "]") && gameBoard[row + 1][column + 1].equals("[" + player1GamePiece + "]")) {
                    player1MaximumChain++;
                    if (player1MaximumChain >= MINIMUM_CHAIN_TO_WIN) {//Checks if Player 1 won diagonally.
                        player1Wins++;
                        System.out.println("\nPlayer 1 won diagonally!");
                        gameOver = true;
                        break diagonalDownRightOuterLoop1;
                    }
                }
                else if (gameBoard[row][column].equals("[" + player2GamePiece + "]") && gameBoard[row + 1][column + 1].equals("[" + player2GamePiece + "]")) {
                    player2MaximumChain++;
                    if (player2MaximumChain >= MINIMUM_CHAIN_TO_WIN) {//Checks if Player 2 won diagonally.
                        player2Wins++;
                        System.out.println("\nPlayer 2 won diagonally!");
                        gameOver = true;
                        break diagonalDownRightOuterLoop1;
                    }
                }
                else {
                    player1MaximumChain = 1;
                    player2MaximumChain = 1;
                }
            }
            player1MaximumChain = 1;
            player2MaximumChain = 1;
        }
        diagonalDownRightOuterLoop2://Scanning in lines from top-left to bottom-right, checks game board for diagonal chains. Starts checking at top-left and stops checking at top-right.
        for (diagonalStartPoint = 1, player1MaximumChain = 1, player2MaximumChain = 1; diagonalStartPoint < gameBoard[0].length - 1; diagonalStartPoint++) {
            for (row = 0, column = diagonalStartPoint; row < gameBoard.length - 1 && column < gameBoard[row].length - 1; row++, column++) {
                if (gameBoard[row][column].equals("[" + player1GamePiece + "]") && gameBoard[row + 1][column + 1].equals("[" + player1GamePiece + "]")) {
                    player1MaximumChain++;
                    if (player1MaximumChain >= MINIMUM_CHAIN_TO_WIN) {//Checks if Player 1 won diagonally.
                        player1Wins++;
                        System.out.println("\nPlayer 1 won diagonally!");
                        gameOver = true;
                        break diagonalDownRightOuterLoop2;
                    }
                }
                else if (gameBoard[row][column].equals("[" + player2GamePiece + "]") && gameBoard[row + 1][column + 1].equals("[" + player2GamePiece + "]")) {
                    player2MaximumChain++;
                    if (player2MaximumChain >= MINIMUM_CHAIN_TO_WIN) {//Checks if Player 2 won diagonally.
                        player2Wins++;
                        System.out.println("\nPlayer 2 won diagonally!");
                        gameOver = true;
                        break diagonalDownRightOuterLoop2;
                    }
                }
                else {
                    player1MaximumChain = 1;
                    player2MaximumChain = 1;
                }
            }
            player1MaximumChain = 1;
            player2MaximumChain = 1;
        }
        diagonalDownLeftOuterLoop1://Scanning in lines from top-right to bottom-left, checks game board for diagonal chains. Starts checking at bottom-right and stops checking at top-right.
        for (diagonalStartPoint = gameBoard.length - 2, player1MaximumChain = 1, player2MaximumChain = 1; diagonalStartPoint >= 0; diagonalStartPoint--) {
            for (row = diagonalStartPoint, column = gameBoard[row].length - 1; row < gameBoard.length - 1 && column > 0; row++, column--) {
                if (gameBoard[row][column].equals("[" + player1GamePiece + "]") && gameBoard[row + 1][column - 1].equals("[" + player1GamePiece + "]")) {
                    player1MaximumChain++;
                    if (player1MaximumChain >= MINIMUM_CHAIN_TO_WIN) {//Checks if Player 1 won diagonally.
                        player1Wins++;
                        System.out.println("\nPlayer 1 won diagonally!");
                        gameOver = true;
                        break diagonalDownLeftOuterLoop1;
                    }
                }
                else if (gameBoard[row][column].equals("[" + player2GamePiece + "]") && gameBoard[row + 1][column - 1].equals("[" + player2GamePiece + "]")) {
                    player2MaximumChain++;
                    if (player2MaximumChain >= MINIMUM_CHAIN_TO_WIN) {//Checks if Player 2 won diagonally.
                        player2Wins++;
                        System.out.println("\nPlayer 2 won diagonally!");
                        gameOver = true;
                        break diagonalDownLeftOuterLoop1;
                    }
                }
                else {
                    player1MaximumChain = 1;
                    player2MaximumChain = 1;
                }
            }
            player1MaximumChain = 1;
            player2MaximumChain = 1;
        }
        diagonalDownLeftOuterLoop2://Scanning in lines from top-right to bottom-left, checks game board for diagonal chains. Starts checking at top-right and stops checking at top-left.
        for (diagonalStartPoint = gameBoard[0].length - 2, player1MaximumChain = 1, player2MaximumChain = 1; diagonalStartPoint > 0; diagonalStartPoint--) {
            for (row = 0, column = diagonalStartPoint; row < gameBoard.length - 1 && column > 0; row++, column--) {
                if (gameBoard[row][column].equals("[" + player1GamePiece + "]") && gameBoard[row + 1][column - 1].equals("[" + player1GamePiece + "]")) {
                    player1MaximumChain++;
                    if (player1MaximumChain >= MINIMUM_CHAIN_TO_WIN) {//Checks if Player 1 won diagonally.
                        player1Wins++;
                        System.out.println("\nPlayer 1 won diagonally!");
                        gameOver = true;
                        break diagonalDownLeftOuterLoop2;
                    }
                }
                else if (gameBoard[row][column].equals("[" + player2GamePiece + "]") && gameBoard[row + 1][column - 1].equals("[" + player2GamePiece + "]")) {
                    player2MaximumChain++;
                    if (player2MaximumChain >= MINIMUM_CHAIN_TO_WIN) {//Checks if Player 2 won diagonally.
                        player2Wins++;
                        System.out.println("\nPlayer 2 won diagonally!");
                        gameOver = true;
                        break diagonalDownLeftOuterLoop2;
                    }
                }
                else {
                    player1MaximumChain= 1;
                    player2MaximumChain = 1;
                }
            }
            player1MaximumChain = 1;
            player2MaximumChain = 1;
        }
        for (columnNumber = 0, fullColumns = 0; gameOver != true && columnNumber < NUMBER_OF_COLUMNS; columnNumber++) {//If the game board is full but neither player has won, the game is drawn.
            if (!gameBoard[0][columnNumber].equals("[ ]")) {
                fullColumns++;
                if (fullColumns >= NUMBER_OF_COLUMNS) {
                    System.out.println("\nPlayer 1 and Player 2 drew the game!");
                    gameOver = true;
                }
            }
        }
        gameOverAndPlayer1WinsAndPlayer2Wins[0] = gameOver;//Updates gameOverAndPlayer1WinsAndPlayer2Wins.
        gameOverAndPlayer1WinsAndPlayer2Wins[1] = player1Wins;
        gameOverAndPlayer1WinsAndPlayer2Wins[2] = player2Wins;

        return gameOverAndPlayer1WinsAndPlayer2Wins;
    }

    public static int getColumn (int player, int NUMBER_OF_COLUMNS, String player1GamePiece, String player2GamePiece) {//Gets column choice from user.
        int inputAsInt;
        String gamePiece;

        if (player == 1) {
            gamePiece = player1GamePiece;
        }
        else {
            gamePiece = player2GamePiece;
        }

        Scanner scanner = new Scanner(System.in);

        System.out.println("_____________________________________________\nPlayer " + player + ", choose the column for your " + gamePiece + " piece.");//Shows the current player and their game piece.

        outerLoop:
        while (true) {
            while(!scanner.hasNextInt()) {//If input is not integer, asks user again.
                System.out.println("Your choice must be an integer. Try again!");
                scanner.next();
            }
            inputAsInt = scanner.nextInt();

            if (inputAsInt >= 1 && inputAsInt <= NUMBER_OF_COLUMNS) {//Saves input (if valid).
                break outerLoop;
            }
            else {//If input is not valid column, asks user again.
                System.out.println("Your choice must be between 1 and " + NUMBER_OF_COLUMNS + ". Try again!");
                continue;
            }
        }
        return inputAsInt;
    }

    public static int startingPlayerTurn (int player1Wins, int player2Wins) {//Decides starting player turn.
        int player;

        if (player1Wins == player2Wins) {//If both players have won equally often, starting player turn is randomly chosen.
            player = (int)((Math.random() * 2) + 1);
        }
        else if (player1Wins > player2Wins) {//If Player 1 has won more often, starting player turn is given to Player 2.
            player = 2;
        }
        else {//If Player 2 has won more often, starting player turn is given to Player 1 instead.
            player = 1;
        }
        return player;
    }
}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

only static methods

Java is an object oriented programming language so you should get used to use objects. This begins by using an object of you class in the first place. Some may argue that this in not needed in you approach but I could think of some objects which could collaborate to make up the game.

naming

You correctly use camelCase names for your identifiers.

But the names of the identifiers should clearly express their purpose. Also method names should start with a verb while variable names should start with a noun or an adjective.

booleans are somewhat special and should start with "is" or "has" prefix.

Comments to structure the code

You use comments to guide the reader through your code.

You should better separate the code to which the comment belongs to a method of its own with a name derived from the comment.


[edit] @Jughead: "What do you mean by "You should better separate the code to which the comment belongs to a method of its own with a name derived from the comment."?"

example

old code
public static String[][] dropPiece (String[][] gameBoard, int column, int player, int NUMBER_OF_COLUMNS, String player1GamePiece, String player2GamePiece) {//Drops game piece into selected column.
    int row;

    outerLoop:
    for (row = gameBoard.length - 1; row >= -1; row--) {
 if (row < 0) {//If chosen column is full, asks for a different column.
            System.out.println("Column " + (column + 1) + " is already full.");
            dropPiece(gameBoard, getColumn(player, NUMBER_OF_COLUMNS, player1GamePiece, player2GamePiece) - 1, player, NUMBER_OF_COLUMNS, player1GamePiece, player2GamePiece);
            break;
        }
improvement
public static String[][] dropPieceIntoSelectedColumn (String[][] gameBoard, int column, int player, int NUMBER_OF_COLUMNS, String player1GamePiece, String player2GamePiece) {
    for (int row = gameBoard.length - 1; row >= -1; row--) {
        if(isChosenColumnFull(row)) {
           askForDifferentColumn();
        } else { // do not use 'breake' to leave a loop 
          // other 2 if here
        }
     }
  }

Of cause this does not compile because of missing parameters and return values, but I hope you get the idea...


Comments in general should tell why the code is like it is.

odd ball solutions

Your use of the for loop is quite unusual.

for (turnAlternator = player; ; turnAlternator++, turnNumber++) 

This would require a comment to explain it.

code duplication

large parts of your code are repeated. you should place that code in parameterized methods.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks for the suggestions! What do you mean by "You should better separate the code to which the comment belongs to a method of its own with a name derived from the comment."? \$\endgroup\$
    – HeadOfJarg
    Commented Nov 27, 2016 at 12:00
  • \$\begingroup\$ @Jughead I updated the answer. Does that make sense to you? \$\endgroup\$ Commented Nov 28, 2016 at 20:26

Not the answer you're looking for? Browse other questions tagged or ask your own question.