package edu.colorado.games; import java.util.Vector; public class Connect4 extends AbstractGame implements Cloneable { public static final int ROWS = 6; public static final int COLUMNS = 7; public static final int DEPTH = 4; // Private static member constants, defined here. // The FOUR_VALUE is the value returned by // the value function when it finds four-in-a-row. For the current // implementation of evaluate to work, the FOUR_VALUE should be at least // 24 times as large as the total number of spots on the board. // MOVE_STRINGS is an array of all possible moves, which must be strings // corresponding to the integers 0 through COLUMNS-1. private static final int FOUR_VALUE = 24*ROWS*COLUMNS; private static final String[ ] MOVE_STRINGS = {"0", "1", "2", "3", "4", "5", "6"}; private Player[ ][ ] data = new Player[ROWS][COLUMNS]; private int[ ] manyUsed = new int[COLUMNS]; private int mostRecentColumn; public static void main(String[ ] args) { repeatPlay("edu.colorado.games.Connect4", DEPTH); } protected Connect4 clone( ) { Connect4 answer; int i; try { answer = (Connect4) super.clone( ); } catch (Exception e) { throw new RuntimeException ("This class does not implement Cloneable."); } // Make new copies of the data and manyUsed arrays: answer.manyUsed = manyUsed.clone( ); answer.data = data.clone( ); for (i = 0; i < ROWS; i++) answer.data[i] = data[i].clone( ); return answer; } protected Vector computeMoves( ) { Vector moves = new Vector( ); int i; for (i = 0; i < COLUMNS; i++) { if (manyUsed[i] < ROWS) moves.addElement(MOVE_STRINGS[i]); } return moves; } protected void displayStatus( ) { int row, column; System.out.println("\nCurrent game status"); System.out.println("(HUMAN = # and COMPUTER = @):"); if (movesCompleted( ) != 0) System.out.println("Most recent move in column " + mostRecentColumn); for (row = ROWS-1; row >= 0; --row) { for (column = 0; column < COLUMNS; ++column) { switch (data[row][column]) { case human: System.out.print(" #"); break; case computer: System.out.print(" @"); break; case nobody: System.out.print(" ."); break; } } System.out.println( ); } for (column = 0; column < COLUMNS; ++column) System.out.printf("%3d", column); if (isGameOver( )) System.out.println("\nGame over."); else if (nextMover( ) == Player.human) System.out.println("\nHuman's turn (please type a column number)"); else System.out.println("\nComputer's turn to move (please wait)..."); } protected double evaluate( ) { // NOTE: Positive answer is good for the computer. int row, column; int answer = 0; // For each possible starting spot, calculate the value of the spot for // a potential four-in-a-row, heading down, left, and to the lower-left. // Normally, this value is in the range [-3...+3], but if a // four-in-a-row is found for the player, the result is FOUR_VALUE which // is large enough to make the total answer larger than any evaluation // that occurs with no four-in-a-row. // Value moving down from each spot: for (row = 3; row < ROWS; ++row) for (column = 0; column < COLUMNS; ++column) answer += value(row, column, -1, 0); // Value moving left from each spot: for (row = 0; row < ROWS; ++row) for (column = 3; column < COLUMNS; ++column) answer += value(row, column, 0, -1); // Value heading diagonal (lower-left) from each spot: for (row = 3; row < ROWS; ++row) for (column = 3; column < COLUMNS; ++column) answer += value(row, column, -1, -1); // Value heading diagonal (lower-right) from each spot: for (row = 3; row < ROWS; ++row) for (column = 0; column <= COLUMNS-4; ++column) answer += value(row, column, -1, +1); return answer; } protected boolean isGameOver( ) { int row, column; int i; // Two simple cases: if (movesCompleted( ) == 0) return false; if (movesCompleted( ) == ROWS*COLUMNS) return true; // Check whether most recent move is part of a four-in-a-row // for the player who just moved. column = mostRecentColumn; row = manyUsed[column] - 1; // Vertical: if (Math.abs(value(row, column, -1, 0)) == FOUR_VALUE) return true; for (i = 0; i <= 3; i++) { // Diagonal to the upper-right: if (Math.abs(value(row-i, column-i, 1, 1)) == FOUR_VALUE) return true; // Diagonal to the lower-right: if (Math.abs(value(row-i, column+i, 1, -1)) == FOUR_VALUE) return true; // Horizontal: if (Math.abs(value(row, column-i, 0, 1)) == FOUR_VALUE) return true; } return false; } protected boolean isLegal(String move) { int column; try { column = Integer.valueOf(move).intValue( ); } catch (NumberFormatException e) { return false; } return (!isGameOver( )) && (column < COLUMNS) && (manyUsed[column] < ROWS); } protected void makeMove(String move) { int row, column; super.makeMove(move); column = Integer.valueOf(move).intValue( ); row = manyUsed[column]++; data[row][column] = nextMover( ); mostRecentColumn = column; } private int value(int row, int column, int deltar, int deltac) { // NOTE: Positive return value is good for the computer. int i; int endRow = row + 3*deltar; int endColumn = column + 3*deltac; int playerCount= 0; int opponentCount = 0; if ( (row < 0) || (column < 0) || (endRow < 0) || (endColumn < 0) || (row >= ROWS) || (endRow >= ROWS) || (column >= COLUMNS) || (endColumn >= COLUMNS) ) return 0; for (i = 1; i <= 4; ++i) { if (data[row][column] == Player.computer) ++playerCount; else if (data[row][column] != Player.nobody) ++opponentCount; row += deltar; column += deltac; } if ((playerCount > 0) && (opponentCount > 0)) return 0; // Neither player can get four-in-a-row here. else if (playerCount == 4) return FOUR_VALUE; else if (opponentCount == 4) return -FOUR_VALUE; else return playerCount - opponentCount; } }