Last active
August 29, 2015 14:17
-
-
Save Samuel-Retter/d71a3cd3c1887b034ed4 to your computer and use it in GitHub Desktop.
Plays the game Othello against the user (quite competitively). The game was made with three classes: Board, OthelloGame, and Player.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Created by Meir on 1/21/2015. | |
*/ | |
public class Board { | |
OthelloGame.CellState[][] data = new OthelloGame.CellState[SIZE][SIZE]; | |
public static final int SIZE = OthelloGame.BOARD_SIZE; | |
public Board() { | |
for (int x = 0; x < SIZE; x++) { | |
for (int y = 0; y < SIZE; y++) { | |
data[x][y] = OthelloGame.CellState.EMPTY; | |
} | |
} | |
data[SIZE/2][SIZE/2 - 1] = OthelloGame.CellState.BLACK; | |
data[SIZE/2 - 1][SIZE/2] = OthelloGame.CellState.BLACK; | |
data[SIZE/2 - 1][SIZE/2 - 1] = OthelloGame.CellState.WHITE; | |
data[SIZE/2][SIZE/2] = OthelloGame.CellState.WHITE; | |
} | |
public OthelloGame.CellState pieceAt(int[] loc) { | |
return data[loc[0]][loc[1]]; | |
} | |
public void updateCell(int[] loc, OthelloGame.CellState newValue) { | |
data[loc[0]][loc[1]] = newValue; | |
} | |
public boolean containsEmptyIn(int[] cell) { | |
return pieceAt(cell) == OthelloGame.CellState.EMPTY; | |
} | |
public boolean containsComputerIn(int[] cell, OthelloGame.CellState computer) { | |
return pieceAt(cell) == computer; | |
} | |
public boolean allContain(int[][] arr, OthelloGame.CellState piece) { | |
/** | |
* returns whether or not all locations on the board specified by *arr* contain *piece* | |
*/ | |
for (int i = 0; i < arr.length; i++) { | |
if (pieceAt(arr[i]) != piece) { | |
return false; | |
} | |
} | |
return true; | |
} | |
public boolean containsHumanIn(int[] cell, OthelloGame.CellState human) { | |
return pieceAt(cell) == human; | |
} | |
public int count(OthelloGame.CellState piece) { | |
int ret = 0; | |
for (int x = 0; x < SIZE; x++) { | |
for (int y = 0; y < SIZE; y++) { | |
int[] loc = {x,y}; | |
if (pieceAt(loc) == piece) { | |
ret++; | |
} | |
} | |
} | |
return ret; | |
} | |
public void adjust(int[] loc) { | |
/** | |
* adjusts the board by assuming that the piece at loc was just placed | |
*/ | |
for (int[] direction : OthelloGame.DIRECTIONS) { | |
int dx = direction[0]; | |
int dy = direction[1]; | |
int[][] toBeFlipped = new int[SIZE*SIZE][2]; | |
for (int i = 0; i < SIZE*SIZE; i++) { | |
toBeFlipped[i] = null; | |
} | |
int arrCounter = 0; // faster ArrayList | |
boolean keepGoing = true; | |
int[] focus = loc.clone(); | |
while (OthelloGame.isInBoard(focus) && keepGoing && OthelloGame.jumpIsSafe(focus, direction)) { | |
focus[0] += dx; | |
focus[1] += dy; | |
if (OthelloGame.isInBoard(focus)) { | |
if (this.pieceAt(focus) != OthelloGame.enemy(this.pieceAt(loc))) { | |
keepGoing = false; | |
} | |
} | |
if (keepGoing) { | |
toBeFlipped[arrCounter] = focus.clone(); | |
arrCounter ++; | |
} | |
} | |
toBeFlipped = OthelloGame.stripTrailingNulls(toBeFlipped); | |
if (OthelloGame.isInBoard(focus)) { | |
if (pieceAt(focus) == pieceAt(loc)) { | |
for (int[] cell : toBeFlipped) { | |
/*newBoard.*/updateCell(cell, OthelloGame.enemy(pieceAt(cell))); | |
} | |
} | |
} | |
} | |
//this.data = newBoard.data.clone(); | |
} | |
public boolean isLegal(int[] move, OthelloGame.CellState piece) { | |
/** | |
* returns whether is it legal for *piece* to be place in location *move* | |
*/ | |
if (pieceAt(move) != OthelloGame.CellState.EMPTY) { | |
return false; // cell is already occupied | |
} | |
for (int[] direction : OthelloGame.DIRECTIONS) { | |
int dx = direction[0]; | |
int dy = direction[1]; | |
int[] focus = move.clone(); | |
focus[0] += dx; | |
focus[1] += dy; | |
if (OthelloGame.isInBoard(focus)) { | |
if (this.data[focus[0]][focus[1]] == OthelloGame.enemy(piece)) { | |
while (OthelloGame.jumpIsSafe(focus, direction) && pieceAt(focus) == OthelloGame.enemy(piece)) { | |
focus[0] += dx; | |
focus[1] += dy; | |
} | |
if (pieceAt(focus) == piece) { | |
return true; | |
} | |
} | |
} | |
} | |
return false; | |
} | |
public boolean hasLegalMove(OthelloGame.CellState color) { | |
for (int x = 0; x < SIZE; x++) { | |
for (int y = 0; y < SIZE; y++) { | |
int[] loc = {x,y}; | |
if (isLegal(loc, color)) { | |
return true; | |
} | |
} | |
} | |
return false; | |
} | |
public int[][] legalMoves(OthelloGame.CellState piece) { | |
/** | |
* returns array of all legal moves for a piece | |
*/ | |
int[][] legalsArr = new int[SIZE*SIZE][2]; // there can never actually be 64 legal moves obviously | |
for (int i = 0; i < SIZE*SIZE; i++) { | |
legalsArr[i] = null; | |
} | |
int counter = 0; | |
for (int x = 0; x < SIZE; x++) { | |
for (int y = 0; y < SIZE; y++) { | |
int[] move = {x, y}; | |
if (isLegal(move, piece)) { | |
legalsArr[counter] = move; | |
counter += 1; | |
} | |
} | |
} | |
return OthelloGame.stripTrailingNulls(legalsArr); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Created by Meir on 1/13/2015. | |
*/ | |
import java.util.HashMap; | |
import java.util.Random; | |
import java.util.Scanner; | |
public class OthelloGame { | |
public static enum CellState { | |
EMPTY, | |
WHITE, | |
BLACK | |
} | |
public static final int BOARD_SIZE = 8; // should be even | |
public static final int[][] DIRECTIONS = {{1,0},{1,1},{0,1},{-1,1},{-1,0},{-1,-1},{0,-1},{1,-1}}; | |
public static final int[][] CORNERS = {{0,0},{0,BOARD_SIZE-1},{BOARD_SIZE-1,BOARD_SIZE-1},{BOARD_SIZE-1,0}}; | |
public static final String ALPH = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; | |
public static final boolean GLOBAL_HINTS = true; // allows for the possibility of hints being included in displayBoard | |
// for the hints to actually be given, the localHints parameter of displayBoard must be set to *true* | |
public static final int MAX_DEPTH = 4 ; | |
public static final int HIGH = 10000000; | |
public static final int LOW = -10000000; // better than Integer.MAX_VALUE etc. because of overflow issues | |
public static void displayInstruct() { | |
System.out.println(); | |
System.out.println("Welcome to Othello. You will make your move known by entering a letter, A-H, followed by a number."); | |
System.out.println("For example, d4."); | |
System.out.println("A dot means a move is legal for you."); | |
System.out.println(); | |
} | |
public static String stringMultiplied(String s, int times) { | |
String newString = ""; | |
for (int i = 0; i < times; i++) { | |
newString += s; | |
} | |
return newString; | |
} | |
public static boolean areOneDiagAway(int[] p1, int[] p2) { | |
/** | |
* returns whether p1 and p2 are diagonally adjacent (e.g. {2,3} and {1,4} | |
*/ | |
int dx = p2[0] - p1[0]; | |
int dy = p2[1] - p1[1]; | |
return (Math.abs(dx) == 1) && (Math.abs(dy) == 1); | |
} | |
public static void printIntArray(int[] arr) { | |
System.out.print("{"); | |
for (int i = 0; i < arr.length-1; i++) { | |
System.out.print(arr[0]+", "); | |
} | |
System.out.println(arr[arr.length-1]+"}"); | |
} | |
public static int randint(int min, int max) { | |
/** | |
* returns a random integer between min and max, inclusive like in python | |
*/ | |
Random random = new Random(); | |
return random.nextInt(max - min + 1) + min; | |
} | |
public static String input(String instructions) { | |
System.out.print(instructions); | |
Scanner scanner = new Scanner(System.in); | |
String ret = scanner.nextLine(); | |
return ret; | |
} | |
public static int intInput(String instructions) { | |
System.out.print(instructions); | |
Scanner scanner = new Scanner(System.in); | |
int ret = scanner.nextInt(); | |
return ret; | |
} | |
public static String letterAtIndex(String string, int index) { | |
return string.substring(index, index+1); | |
} | |
public static int[][] stripTrailingNulls(int[][] arr) { | |
/** | |
* for use to make faster implementation of ArrayList when you know a max size it will become | |
*/ | |
int trueLength = 0; // length without trailing nulls | |
while (arr[trueLength] != null) { | |
trueLength++; | |
} | |
int[][] newArr = new int[trueLength][2]; | |
for (int i = 0; i < trueLength; i++) { | |
newArr[i] = arr[i].clone(); | |
} | |
return newArr; | |
} | |
public static boolean isInBoard(int[] cell) { | |
int x = cell[0]; | |
int y = cell[1]; | |
return (x >= 0) && (x < BOARD_SIZE) && (y >= 0) && (y < BOARD_SIZE); | |
} | |
public static void displayBoard(Board board, CellState computer, CellState human, boolean localHints) { | |
HashMap<CellState, String> colorToSymbol = new HashMap<CellState, String>(); | |
colorToSymbol.put(CellState.EMPTY, " "); | |
colorToSymbol.put(CellState.BLACK, "#"); | |
colorToSymbol.put(CellState.WHITE, "O"); | |
String horizontalLine = " +" + stringMultiplied("-----+", BOARD_SIZE); | |
for (int i = 0; i < BOARD_SIZE; i++) { | |
String letter = letterAtIndex(ALPH, i); | |
System.out.print(" " + letter); | |
} | |
System.out.println(); | |
System.out.println(horizontalLine); | |
for (int y = 0; y < BOARD_SIZE; y++) { | |
String line = (y+1) + " | "; | |
for (int x = 0; x < BOARD_SIZE; x ++) { | |
int[] loc = {x,y}; | |
if (board.isLegal(loc, human) && GLOBAL_HINTS && localHints) { | |
line += '.' + " | "; | |
} else { | |
line += colorToSymbol.get(board.data[x][y]) + " | "; | |
} | |
} | |
System.out.println(line); | |
System.out.println(horizontalLine); | |
} | |
System.out.println(); | |
System.out.println("computer(" + colorToSymbol.get(computer) + "): " + board.count(computer)); | |
System.out.println("human (" + colorToSymbol.get(human) + "): " + board.count(human)); | |
System.out.println(); | |
} | |
public static Board copy(Board board) { | |
Board newBoard = new Board(); | |
CellState[][] newData = new CellState[BOARD_SIZE][BOARD_SIZE]; | |
for (int x = 0; x < BOARD_SIZE; x++) { | |
newData[x] = board.data[x].clone(); | |
} | |
newBoard.data = newData; | |
return newBoard; | |
} | |
public static CellState enemy(CellState piece) { | |
if (piece == CellState.BLACK) { | |
return CellState.WHITE; | |
} else if (piece == CellState.WHITE) { | |
return CellState.BLACK; | |
} | |
return null; | |
} | |
public static boolean jumpIsSafe(int[] startCell, int[] direction) { | |
int x = startCell[0]; | |
int y = startCell[1]; | |
int dx = direction[0]; | |
int dy = direction[1]; | |
int[] destination = {x + dx, y + dy}; | |
return isInBoard(destination); | |
} | |
public static int[] letterNumberToNumberNumber(String letterNumber) { | |
/** | |
* e.g. converts "d3" or "D3" to {3, 2} | |
*/ | |
int[] numberNumber = {ALPH.indexOf(letterNumber.substring(0,1).toUpperCase()), Integer.parseInt(letterNumber.substring(1,2))-1}; | |
return numberNumber; | |
} | |
public static String numberNumberToLetterNumber(int[] numberNumber) { | |
/** | |
* e.g. converts {3,2} to "D3" | |
*/ | |
int let = numberNumber[0]; // number that will become a letter | |
int num = numberNumber[1]; // number that will become a number | |
return ALPH.substring(let, let+1) + (num + 1); | |
} | |
public static int[][] cellsThrough(int[] startCell, int[] endCell) { | |
/** | |
* 1. assumes startCell and endCell are either in the same row or column | |
* 2. returns an int[][] of all cells between them, including sC and eC themselves | |
*/ | |
if (!(startCell[0] == endCell[0] || startCell[1] == endCell[1])) { | |
return null; | |
} | |
if (startCell == endCell) { | |
int[][] ret = {startCell.clone()}; | |
return ret; | |
} | |
int dx = endCell[0] - startCell[0]; | |
int dy = endCell[1] - startCell[1]; | |
int length = 0; // initialize length | |
int[] direction = {0,0}; // initialize direction | |
if (dx == 0) { | |
length = dy + 1; | |
direction[1] = dy/Math.abs(dy); | |
} else if (dy == 0) { | |
length = dx + 1; | |
direction[0] = dx/Math.abs(dx); | |
} | |
int[][] ret = new int[length][2]; | |
int counter = 0; | |
int[] focus = startCell.clone(); | |
for (int i = 0; i < length; i++) { | |
ret[counter] = focus.clone(); | |
counter++; | |
focus[0] += direction[0]; | |
focus[1] += direction[1]; | |
} | |
return ret; | |
} | |
public static Board boardAdjusted(Board board, int[] loc) { | |
/** | |
* like board.adjust(), but functional | |
*/ | |
Board newBoard = copy(board); | |
for (int[] direction : DIRECTIONS) { | |
int dx = direction[0]; | |
int dy = direction[1]; | |
int[][] toBeFlipped = new int[BOARD_SIZE*BOARD_SIZE][2]; | |
for (int i = 0; i < BOARD_SIZE*BOARD_SIZE; i++) { | |
toBeFlipped[i] = null; | |
} | |
int arrCounter = 0; // faster ArrayList | |
boolean keepGoing = true; | |
int[] focus = loc.clone(); | |
while (isInBoard(focus) && keepGoing && jumpIsSafe(focus, direction)) { | |
focus[0] += dx; | |
focus[1] += dy; | |
if (isInBoard(focus)) { | |
if (board.pieceAt(focus) != enemy(board.pieceAt(loc))) { | |
keepGoing = false; | |
} | |
} | |
if (keepGoing) { | |
toBeFlipped[arrCounter] = focus.clone(); | |
arrCounter++; | |
} | |
} | |
toBeFlipped = stripTrailingNulls(toBeFlipped); | |
if (isInBoard(focus)) { | |
if (board.pieceAt(focus) == board.pieceAt(loc)) { | |
for (int[] cell : toBeFlipped) { | |
newBoard.updateCell(cell, enemy(newBoard.pieceAt(cell))); | |
} | |
} | |
} | |
} | |
return copy(newBoard); | |
} | |
public static int evaluate(Board board, int pieceWeight, | |
int mobilityWeight, int cornerWeight, | |
int permWeight, int nearCornerWeight) { | |
/** | |
* positive favors black; negative favors white. | |
* as of now, permWeight is not used; if the implicit preference for stable discs | |
* created by mobility maximizing proves insufficient, I might include it. | |
*/ | |
CellState winner = winner(board); | |
if (winner == CellState.BLACK) { | |
return 1000000000; | |
} | |
if (winner == CellState.WHITE) { | |
return -1000000000; | |
} | |
int ret = 0; | |
// number of pieces for each side | |
ret += board.count(CellState.BLACK) * pieceWeight; | |
ret -= board.count(CellState.WHITE) * pieceWeight; | |
// each side's mobility | |
ret += board.legalMoves(CellState.BLACK).length * mobilityWeight; | |
ret -= board.legalMoves(CellState.WHITE).length * mobilityWeight; | |
// number of corners occupied by each side | |
int blacksInCorners = 0; | |
int whitesInCorners = 0; | |
for (int[] corner : CORNERS) { | |
CellState pieceAtCorner = board.pieceAt(corner); | |
if (pieceAtCorner == CellState.BLACK) { | |
blacksInCorners++; | |
} else if (pieceAtCorner == CellState.WHITE) { | |
whitesInCorners++; | |
} | |
} | |
ret += blacksInCorners * cornerWeight; | |
ret -= whitesInCorners * cornerWeight; | |
// cells next to corners occupied by each side (not desirable) | |
int blacksNearCorners = 0; | |
int whitesNearCorners = 0; | |
HashMap<int[], int[][]> cornerToNears = new HashMap<>(); // maps a corner to an int[][] of its three neighbors | |
int[][] neighbors0 = {{0, 1}, {1, 0}, {1, 1}}; | |
int[][] neighbors1 = {{0, BOARD_SIZE-2}, {1, BOARD_SIZE-2}, {1, BOARD_SIZE-1}}; | |
int[][] neighbors2 = {{BOARD_SIZE-1, BOARD_SIZE-2}, {BOARD_SIZE-2, BOARD_SIZE-2}, {BOARD_SIZE-2, BOARD_SIZE-1}}; | |
int[][] neighbors3 = {{BOARD_SIZE-1, 1}, {BOARD_SIZE-2, 0}, {BOARD_SIZE-2, 1}}; | |
cornerToNears.put(CORNERS[0], neighbors0); | |
cornerToNears.put(CORNERS[1], neighbors1); | |
cornerToNears.put(CORNERS[2], neighbors2); | |
cornerToNears.put(CORNERS[3], neighbors3); | |
for (int[] corner : CORNERS) { | |
if (board.pieceAt(corner) == CellState.EMPTY) { | |
int[][] nears = cornerToNears.get(corner); | |
for (int[] near : nears) { | |
// first make sure the piece at *near* isn't backed up all the way to the other corner (in which case it is a perm) | |
if (board.pieceAt(near) != CellState.EMPTY) { | |
int[][] backers = new int[BOARD_SIZE - 2][2]; | |
int[] directionFromCornerToNear = {near[0] - corner[0], near[1] - corner[1]}; | |
for (int i = 0; i < backers.length; i++) { | |
int x = near[0] + directionFromCornerToNear[0] * (i+1); | |
int y = near[1] + directionFromCornerToNear[1] * (i+1); | |
int[] backer = {x, y}; | |
backers[i] = backer; | |
} | |
if (board.pieceAt(near) == CellState.BLACK && (areOneDiagAway(corner, near) || !board.allContain(backers, CellState.BLACK))) { | |
blacksNearCorners++; | |
} else if (board.pieceAt(near) == CellState.WHITE && (areOneDiagAway(corner, near) || !board.allContain(backers, CellState.BLACK))) { | |
whitesNearCorners++; | |
} | |
} | |
} | |
} | |
} | |
ret -= blacksNearCorners * nearCornerWeight; // negative for black, positive for white | |
ret += whitesNearCorners * nearCornerWeight; // because occupying nears is not good | |
// now calculate the number of "perms" (permanents) for each color | |
// these are pieces that cannot be flipped for the rest of the game | |
return ret; | |
} | |
public static CellState winner(Board board) { | |
int blacksCount = board.count(CellState.BLACK); | |
int whitesCount = board.count(CellState.WHITE); | |
if ((board.count(CellState.EMPTY) == 0) || | |
(!board.hasLegalMove(CellState.BLACK) && | |
!board.hasLegalMove(CellState.WHITE))) { | |
if (blacksCount == whitesCount) { | |
return CellState.EMPTY; // returning EMPTY means the game is over and has resulted in a tie | |
} else if (blacksCount > whitesCount) { | |
return CellState.BLACK; | |
} else if (whitesCount > blacksCount) { | |
return CellState.WHITE; | |
} | |
} | |
return null; // returning null means the game is not over | |
} | |
public static int alphaBeta(Player player, CellState currentColor, Board board, int alpha, int beta, int depth) { | |
/** | |
* player is only to give the weights | |
* color is determined by currentColor | |
* this allows colors to be swapped without knowing information about the other player | |
*/ | |
if (winner(board) == CellState.BLACK) { | |
return HIGH; | |
} | |
if (winner(board) == CellState.WHITE) { | |
return LOW; | |
} | |
if (winner(board) == CellState.EMPTY) { | |
return 0; | |
} | |
if (!board.hasLegalMove(currentColor)) { // other player must have a move or the game would be over, so no infinite loops | |
return alphaBeta(player, enemy(currentColor), board, alpha, beta, depth); | |
} | |
int[][] legalMoves = board.legalMoves(currentColor); | |
if (depth == 0) { | |
return evaluate(board, player.pieceWeight, | |
player.mobilityWeight, player.cornerWeight, | |
player.permWeight, player.nearCornerWeight); | |
} | |
if (currentColor == CellState.BLACK) { | |
int ret = LOW; | |
for (int[] legalMove : legalMoves) { | |
Board tempBoard = copy(board); | |
tempBoard.updateCell(legalMove, currentColor); | |
ret = Integer.max(ret, alphaBeta(player, enemy(currentColor), boardAdjusted(tempBoard, legalMove), alpha, beta, depth - 1)); | |
alpha = Integer.max(alpha, ret); | |
if (beta <= alpha) { | |
break; | |
} | |
} | |
return ret; | |
} else { // if currentColor == CellState.White | |
int ret = HIGH; | |
for (int[] legalMove : legalMoves) { | |
Board tempBoard = copy(board); | |
tempBoard.updateCell(legalMove, currentColor); | |
ret = Integer.min(ret, alphaBeta(player, enemy(currentColor), boardAdjusted(tempBoard, legalMove), alpha, beta, depth - 1)); | |
beta = Integer.min(beta, ret); | |
if (beta <= alpha) { | |
break; | |
} | |
} | |
return ret; | |
} | |
} | |
public static int[] getMove(Player player, Board board) { | |
int[][] legalMoves = board.legalMoves(player.color); | |
int[] bestMove = null; | |
int bestValue = 0; // initialize | |
if (player.color == CellState.BLACK) { | |
bestValue = LOW; | |
for (int[] legalMove : legalMoves) { | |
Board tempBoard = copy(board); | |
tempBoard.updateCell(legalMove, player.color); | |
tempBoard = boardAdjusted(tempBoard, legalMove); | |
int value = alphaBeta(player, enemy(player.color), tempBoard, LOW, HIGH, MAX_DEPTH); | |
if (value >= bestValue) { | |
bestValue = value; | |
bestMove = legalMove; | |
} | |
} | |
} else if (player.color == CellState.WHITE) { | |
bestValue = HIGH; | |
for (int[] legalMove : legalMoves) { | |
Board tempBoard = copy(board); | |
tempBoard.updateCell(legalMove, player.color); | |
tempBoard = boardAdjusted(tempBoard, legalMove); | |
int value = alphaBeta(player, enemy(player.color), tempBoard, LOW, HIGH, MAX_DEPTH); | |
if (value <= bestValue) { | |
bestValue = value; | |
bestMove = legalMove; | |
} | |
} | |
} | |
System.out.println(bestValue); | |
return bestMove; | |
} | |
public static String getHumanMove(Board board, CellState humanColor) { | |
String ret = input("Enter a move: "); | |
if (ret.length() != 2) { | |
System.out.println("Move must be a letter followed by a number."); | |
return getHumanMove(board, humanColor); | |
} | |
if (!ALPH.toLowerCase().substring(0,BOARD_SIZE).contains(letterAtIndex(ret, 0).toLowerCase())) { | |
System.out.println("First character must be a letter (A - " + letterAtIndex(ALPH, BOARD_SIZE - 1) + ")."); | |
return getHumanMove(board, humanColor); | |
} | |
if (!"123456789".substring(0,BOARD_SIZE).contains(letterAtIndex(ret, 1))) { | |
System.out.println("Second character must be a number (1 - " + letterAtIndex("123456789", BOARD_SIZE - 1) + ")."); | |
return getHumanMove(board, humanColor); | |
} | |
if (!board.isLegal(letterNumberToNumberNumber(ret), humanColor)) { | |
System.out.println("That is not a legal move."); | |
return getHumanMove(board, humanColor); | |
} | |
return ret; | |
} | |
public static void congratWinner(CellState theWinner, CellState computer, CellState human) { | |
if (theWinner == human) { | |
System.out.println("You win!"); | |
} else if (theWinner == computer) { | |
System.out.println("You lose."); | |
} else { | |
System.out.println("Tie game."); | |
} | |
} | |
public static int[] getRandomMove(Board board, CellState color) { | |
int[][] legals = board.legalMoves(color); | |
Random ran = new Random(); | |
int r = ran.nextInt(legals.length); | |
return legals[r].clone(); | |
} | |
public static void startComputerVsHumanGame(Player computer) { | |
displayInstruct(); | |
Board board = new Board(); | |
CellState turn = CellState.BLACK; | |
int binaryHumanGoFirst = 0; | |
while (binaryHumanGoFirst != 1 && binaryHumanGoFirst != 2) { | |
binaryHumanGoFirst = intInput("Enter 1 to go first, 2 to go second. "); | |
} | |
CellState humanColor; | |
if (binaryHumanGoFirst == 1) { | |
computer.color = CellState.WHITE; | |
humanColor = CellState.BLACK; | |
} else { | |
computer.color = CellState.BLACK; | |
humanColor = CellState.WHITE; | |
} | |
displayBoard(board, computer.color, humanColor, humanColor == CellState.BLACK); | |
while (winner(board) == null) { | |
if (turn == humanColor) { | |
if (board.legalMoves(humanColor).length == 0) { | |
input("You have no moves. Press Enter."); | |
} else { | |
int[] humanMove = letterNumberToNumberNumber(getHumanMove(board, humanColor)); | |
board.updateCell(humanMove, humanColor); | |
displayBoard(board, computer.color, humanColor, false); | |
System.out.println(); | |
System.out.println("Your piece has been placed. Press Enter again to flip over pieces."); | |
input(""); | |
board.adjust(humanMove); | |
} | |
} else if (turn == computer.color) { | |
if (board.legalMoves(computer.color).length == 0) { | |
System.out.println("I have no moves. Press Enter."); | |
} else { | |
System.out.println("Computer is thinking..."); | |
int[] computerMove = getMove(computer, board); | |
System.out.println("I will place my piece in square " + numberNumberToLetterNumber(computerMove)); | |
board.updateCell(computerMove, computer.color); | |
displayBoard(board, computer.color, humanColor, false); | |
System.out.println(); | |
System.out.println("Computer's piece has been placed " + "in " + numberNumberToLetterNumber(computerMove)+". Press Enter to flip over pieces."); | |
input(""); | |
board.adjust(computerMove); | |
} | |
} | |
turn = enemy(turn); | |
displayBoard(board, computer.color, humanColor, turn == humanColor); | |
} | |
CellState theWinner = winner(board); | |
congratWinner(theWinner, computer.color, humanColor); | |
} | |
public static void startComputerVsComputerGame(Player computer1, Player computer2, int binaryComputer1GoFirst) { | |
/** | |
* computer2 is human for printing purposes | |
* binaryComputer1GoFirst should be 0 or 1 | |
*/ | |
Board board = new Board(); | |
CellState turn = CellState.BLACK; | |
if (binaryComputer1GoFirst != 0) { | |
computer1.color = CellState.BLACK; | |
computer2.color = CellState.WHITE; | |
} else { | |
computer2.color = CellState.BLACK; | |
computer1.color = CellState.WHITE; | |
} | |
displayBoard(board, computer1.color, computer2.color, false); | |
while (winner(board) == null) { | |
if (turn == computer2.color) { | |
if (board.legalMoves(computer2.color).length == 0) { | |
System.out.println("computer2 has no moves."); | |
} else { | |
int[] computer2Move = getMove(computer2, board);//getRandomMove(board, computer2.color); // to test a computer against a random mover | |
System.out.println("computer2 will place its piece in square " + numberNumberToLetterNumber(computer2Move)); | |
board.updateCell(computer2Move, computer2.color); | |
displayBoard(board, computer1.color, computer2.color, false); | |
//input(""); | |
//board.adjust(computer2Move); | |
board = boardAdjusted(board, computer2Move); | |
} | |
} else if (turn == computer1.color) { | |
if (board.legalMoves(computer1.color).length == 0) { | |
System.out.println("computer1 has no moves."); | |
} else { | |
int[] computer1Move = getMove(computer1, board); | |
System.out.println("computer1 will place its piece in square " + numberNumberToLetterNumber(computer1Move)); | |
board.updateCell(computer1Move, computer1.color); | |
displayBoard(board, computer1.color, computer2.color, false); | |
//input(""); | |
//board.adjust(computer1Move); | |
board = boardAdjusted(board, computer1Move); | |
} | |
} | |
displayBoard(board, computer1.color, computer2.color, false); | |
turn = enemy(turn); | |
} | |
CellState theWinner = winner(board); | |
congratWinner(theWinner, computer1.color, computer2.color); | |
} | |
public static void main(String[] args) { | |
// one can experiment with different stats, which offer different AI strategies | |
Player computer1 = new Player(CellState.EMPTY, 5,100,10000,0,30); // tested and tried | |
Player computer2 = new Player(CellState.EMPTY, 1,400,8000,0,700); | |
Player computer3 = new Player(CellState.EMPTY, 1,100,1000,0,10); | |
Player computer4 = new Player(CellState.EMPTY, -3,365,9463,0,700); | |
Player computer5 = new Player(CellState.EMPTY, 1,100,1000,0,1); | |
Player computer6 = new Player(CellState.EMPTY, 1,2,3,0,1); | |
Player computer7 = new Player(CellState.EMPTY, 5,100,10000,0,30); | |
Player computer8 = new Player(CellState.EMPTY, 1,1,1,0,1); | |
Player computer9 = new Player(CellState.EMPTY, -1,100,1000,0,0); | |
startComputerVsHumanGame(computer1); | |
//startComputerVsComputerGame(computer1, computer9, 0); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Created by Meir on 1/30/2015. | |
*/ | |
public class Player { | |
OthelloGame.CellState color; | |
int pieceWeight; | |
int mobilityWeight; | |
int cornerWeight; | |
int permWeight; | |
int nearCornerWeight; | |
public Player(OthelloGame.CellState myColor, int myPieceWeight, | |
int myMobilityWeight, int myCornerWeight, | |
int myPermWeight, int myNearCornerWeight) { | |
color = myColor; | |
pieceWeight = myPieceWeight; | |
mobilityWeight = myMobilityWeight; | |
cornerWeight = myCornerWeight; | |
permWeight = myPermWeight; | |
nearCornerWeight = myNearCornerWeight; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment