Here is a simple text-based version of connect four I made. I have been building this in an attempt to improve my Java skills (and possibly mention on my resume).
My goals with this project are as follows:
Clean, optimize, and restructure: code, classes, and game logic based on feedback.
Transform this program from text-based to graphics based using JavaFX
Add computer AI logic to face a challenging opponent (assuming this is feasible to implement at my current skill level)
ConnectFour.java:
import java.util.HashSet;
import java.util.Set;
public class ConnectFour {
private final int[][] gameBoard;
private static final int ROWS = 6;
private static final int COLUMNS = 7;
private static final int RED = 1;
private static final int YELLOW = 2;
public ConnectFour(Player playerOne, Player playerTwo) {
this.gameBoard = new int[ROWS][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;
}
}
}
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 positive and be no greater than 6!");
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;
}
}
public int validateGameBoard() {
//1.) Check each row for four sequential pieces of the same color
//2.) Check each column for four sequential pieces of the same color
//3.) check each diagonal(with more than four spaces along it) for four sequential pieces of the same color
//Return -1 if no current winner
//Return 0 if the board is full, indicating a tie
//Return 1 if player one wins
//Return 2 if player 2 wins
if (isBoardFull()) {
System.out.println("The board is full!");
return 0;
}
int checkRows = validateRows();
int checkColumns = validateColumns();
int checkDiagonals = validateDiagonals();
if (checkRows == 1 || checkColumns == 1 || checkDiagonals == 1) {
return 1;
} else if (checkRows == 2 || checkColumns == 2 || checkDiagonals == 2) {
return 2;
} else {
return -1;
}
}
private int validateRows() {
//System.out.println("Now validating rows");
//To validate the rows we do the following:
//1.) For each row, we select a slice of 4 columns.
//2.) We place each of these column values in a hash set.
//3.) Since hash sets do not allow duplicates, we will easily know if our group of 4 were the same number(color)
//4.) We repeat this process for each group of four columns in the row, for every row of the board.
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLUMNS - 3; j++) {
Set<Integer> pieceSet = new HashSet<Integer>();
pieceSet.add(gameBoard[i][j]);
pieceSet.add(gameBoard[i][j + 1]);
pieceSet.add(gameBoard[i][j + 2]);
pieceSet.add(gameBoard[i][j + 3]);
if (pieceSet.size() == 1) {
if (pieceSet.contains(RED)) {
//Player One Wins
return RED;
} else if (pieceSet.contains(YELLOW)) {
//Player Two Wins
return YELLOW;
}
}
}
}
return -1;
}
private int validateColumns() {
//System.out.println("Now validating columns");
//To validate the columns, we use a similar hash set validation process to the row validation.
// The key difference is, for every column, we select a slice of 4 rows.
// each time we grab one of these slices, we check the hash set exactly the way we did the the row validator
for (int j = 0; j < COLUMNS; j++) {
for (int i = ROWS - 1; i >= 3; i--) {
Set<Integer> pieceSet = new HashSet<Integer>();
pieceSet.add(gameBoard[i][j]);
pieceSet.add(gameBoard[i - 1][j]);
pieceSet.add(gameBoard[i - 2][j]);
pieceSet.add(gameBoard[i - 3][j]);
if (pieceSet.size() == 1) {
//We have a winner
if (pieceSet.contains(RED)) {
//Player 1 Wins
return RED;
} else if (pieceSet.contains(YELLOW)) {
//Player 2 Wins
return YELLOW;
}
}
}
}
return -1;
}
private int validateDiagonals() {
//Start by moving across the first row(left to right), and check all diagonals that can fit more than 4 pieces.
//System.out.println("Now validating diagonals left to right");
//Validating the diagonals is more involved than the last two validations:
/*First, move across the first row, validating all left diagonals (diagonals which connect the top row to the
left most column)*/
//Note that not every diagonal will contain 4 positions, so we can skip such diagonals
for (int i = 3; i < COLUMNS; i++) {
int j = 0; // Check each left diagonal in the first row
int k = i;
while (k - 3 >= 0 && j + 3 < ROWS) {
Set<Integer> pieces = new HashSet<>();
pieces.add(gameBoard[j][k]);
pieces.add(gameBoard[j + 1][k - 1]);
pieces.add(gameBoard[j + 2][k - 2]);
pieces.add(gameBoard[j + 3][k - 3]);
if (pieces.size() == 1) {
if (pieces.contains(RED)) {
return RED;
} else if (pieces.contains(YELLOW)) {
return YELLOW;
}
}
j++;
k--;
}
}
/*Then we move down the right most column and validate each diagonal
which connects this column to the bottom row*/
//Note that our previous top row diagonal validator will have checked the fist column's diagonal already
for (int i = 1; i < 3;i++) {
int j = i; // set the row number to change with i
int k = COLUMNS - 1;// only traverse the last column
while (j + 3 < ROWS && k - 3 >= 0) {
Set<Integer> pieces = new HashSet<>();
pieces.add(gameBoard[j][k]);
pieces.add(gameBoard[j + 1][k - 1]);
pieces.add(gameBoard[j + 2][k - 2]);
pieces.add(gameBoard[j + 3][k - 3]);
if (pieces.size() == 1) {
if (pieces.contains(RED)) {
return RED;
} else if (pieces.contains(YELLOW)) {
return YELLOW;
}
}
j++;
k--;
}
}
//System.out.println("Now validating diagonals right to left");
/*Now we repeat the above process, but begin by validating each right diagonal(diagonals which connect
the top row to the rightmost column*/
//Note we can again ignore diagonals that are shorter than 4 board positions
for (int i = COLUMNS - 4; i >= 0; i--) {
//Moving across the top row from right to left, validate each diagonal
int j = 0; //Move across the first row
int k = i;// set the column number to change with i
while (j + 3 < ROWS && k + 3 < COLUMNS) {
Set<Integer> pieces = new HashSet<>();
pieces.add(gameBoard[j][k]);
pieces.add(gameBoard[j + 1][k + 1]);
pieces.add(gameBoard[j + 2][k + 2]);
pieces.add(gameBoard[j + 3][k + 3]);
if (pieces.size() == 1) {
if (pieces.contains(RED)) {
return RED;
} else if (pieces.contains(YELLOW)) {
return YELLOW;
}
}
j++;
k++;
}
}
/* Lastly, move down the leftmost column and check each diagonal which connects the left most column
to the bottom row*/
for (int i = 1; i < 3; i++) {
//validate each diagonal here
int j = i;// set the row number to change with i;
int k = 0;// before entering the while loop, begin at the first column(column 0);
while (j + 3 < ROWS && k + 3 < COLUMNS) {
Set<Integer> pieces = new HashSet<>();
pieces.add(gameBoard[j][k]);
pieces.add(gameBoard[j + 1][k + 1]);
pieces.add(gameBoard[j + 2][k + 2]);
pieces.add(gameBoard[j + 3][k + 3]);
if (pieces.size() == 1) {
if (pieces.contains(RED)) {
return RED;
} else if (pieces.contains(YELLOW)) {
return YELLOW;
}
}
j++;
k++;
}
}
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:
public class Player {
private final String name;
private static int counter = 0;
private int playerNumber;
//private Scanner scanner = new Scanner(System.in);
public Player(String name) {
//Initialize player number to increment based on how many instances there have been of the class
this.name = name;
this.counter++;
this.playerNumber = counter;
}
public String getName() {
return name;
}
public int getPlayerNumber() {
return playerNumber;
}
}
Main.Java:
import java.util.Scanner;
public class Main {
private static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) {
//Create two players and get their names from user input
System.out.println("Welcome to Connect Four!");
System.out.println("Player 1 please enter your name: ");
String player1_name = scanner.nextLine();
System.out.println("Player 2 Enter your name: ");
String player2_name = scanner.nextLine();
Player playerOne = new Player(player1_name);
Player playerTwo = new Player(player2_name);
System.out.println(playerOne.getName() + " will be red (R on the board)");
System.out.println(playerTwo.getName() + " will be yellow (Y on the board)\n");
ConnectFour connectFour = new ConnectFour(playerOne,playerTwo);
connectFour.printGameBoard();
System.out.println("\n");
boolean hasWon = false;
while(hasWon == false){
System.out.println(playerOne.getName() + ", Please enter a column to make your move");
int move = scanner.nextInt();
while(connectFour.makeMove(playerOne, move) == false){
System.out.println("Please try again: ");
move =scanner.nextInt();
}
connectFour.printGameBoard();
int winner = connectFour.validateGameBoard();
whoWon(winner);
if(winner != -1){
hasWon = false;
break;
}
System.out.println(playerTwo.getName() + ", Please enter a column to make your move");
move = scanner.nextInt();
while(connectFour.makeMove(playerTwo, move) == false){
System.out.println("Please try again: ");
move =scanner.nextInt();
}
connectFour.printGameBoard();
winner = connectFour.validateGameBoard();
whoWon(winner);
if(winner != -1){
hasWon = false;
break;
}
}
}
private static void whoWon(int winner){
if(winner == 0){
System.out.println("It's a tie!");
}
else if(winner == 1){
System.out.println("Player One wins!");
}
else if(winner == 2){
System.out.println("Player Two wins!");
}
else{
System.out.println("No winner yet!\n");
}
}
}