Skip to content

Commit

Permalink
First versio. Wrote it for fun so I didn't use version control
Browse files Browse the repository at this point in the history
  • Loading branch information
tantalum committed Feb 25, 2013
0 parents commit ae6fdf6
Show file tree
Hide file tree
Showing 10 changed files with 770 additions and 0 deletions.
107 changes: 107 additions & 0 deletions src/com/circuitsofimagination/tictactoe/TicTacToeAIPlayer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.circuitsofimagination.tictactoe;

import com.circuitsofimagination.tictactoe.TicTacToeBoard.*;

import java.util.*;

public class TicTacToeAIPlayer implements TicTacToePlayer {

private TicTacToeGame game;
private Cell team;

@Override
public void initialize(Cell team, TicTacToeGame game) {
this.game = game;
this.team = team;
}

@Override
public Location makeMove() throws TicTacToeException {
List<Location> possibleLocations = game.board.emptySlots();

float thisScore;
float bestScore = -1;
Location bestMove = null;
TicTacToeBoard currentBoard;
/*
* Find the best move based on the minimax algorithm
* http://en.wikipedia.org/wiki/Minimax
* This is the first iteration, where on Max
* We implement the first iteration here
* so we can find the best move
* instead of having min and max
* return both a score and location
*/
for(Location l : possibleLocations) {
currentBoard = game.board.copy();
currentBoard.makeMove(team, l);
thisScore = min(currentBoard);
if(thisScore >= bestScore) {
bestScore = thisScore;
bestMove = l;
}
}

return bestMove;
}



private float min(TicTacToeBoard board)throws TicTacToeException {
float minScore = 1;

Cell winner = board.winner();
if(winner != null) {
return evaluateWinner(winner);
}

List<Location> possibleMoves = board.emptySlots();
float thisScore;
TicTacToeBoard currentBoard;
//TODO: We have three copies of almost the same loop
// Find a way to combine them
// aka: DRY
for(Location l : possibleMoves) {
currentBoard = board.copy();
currentBoard.makeMove(TicTacToeBoard.oppositePlayer(team), l);
thisScore = max(currentBoard);
if(thisScore <= minScore) {
minScore = thisScore;
}
}
return minScore;
}

private float max(TicTacToeBoard board) throws TicTacToeException {
float maxScore = -1;

Cell winner = board.winner();
if(winner != null) {
return evaluateWinner(winner);
}

List<Location> possibleMoves = board.emptySlots();
float thisScore;
TicTacToeBoard currentBoard;
for(Location l : possibleMoves) {
currentBoard = board.copy();
currentBoard.makeMove(team, l);
thisScore = min(currentBoard);
if(thisScore >= maxScore) {
maxScore = thisScore;
}
}
return maxScore;
}

private float evaluateWinner(Cell winner) {
if((winner == null) || (winner == Cell.EMPTY)) {
return 0;
} else if(winner == team) {
return 1;
} else {
return -1;
}
}

}
182 changes: 182 additions & 0 deletions src/com/circuitsofimagination/tictactoe/TicTacToeBoard.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package com.circuitsofimagination.tictactoe;

import java.util.*;

public class TicTacToeBoard {

public enum Cell {
EMPTY, X, O
}

public static class Location {
public int x;
public int y;

public Location(int x, int y) {
this.x = x;
this.y = y;
}

@Override
public String toString() {
return "("+x+", "+y+")";
}
}

public static final int WIDTH = 3;
public static final int HEIGHT=3;
private static final int NUM_CELLS=WIDTH*HEIGHT;

private Cell[] board;

public TicTacToeBoard() {
board = new Cell[NUM_CELLS];
for(int i=0; i<NUM_CELLS; i++) {
board[i] = Cell.EMPTY;
}
}

/*
* Private constructor used for copying (aka. cloning) boards
*/
private TicTacToeBoard(Cell[] cells){
board = new Cell[NUM_CELLS];
for(int i=0; i<NUM_CELLS; i++) {
board[i] = cells[i];
}
}

/*
* Helper function to flip the players
*/
public static Cell oppositePlayer(Cell team) {
if(team == Cell.EMPTY){
return Cell.EMPTY;
} else if(team == Cell.X){
return Cell.O;
} else {
return Cell.X;
}
}

/*
* Helper function for creating location instances
*/
public static Location newLocation(int x, int y) {
return new Location(x, y);
}

/*
* Find the correct index in the board array for a given
* x and y
*/
private int indexFor(Location l) {
return l.x+(l.y*WIDTH);
}

/*
* Make a clone of the board. Used by AI
*/
public TicTacToeBoard copy() {
return new TicTacToeBoard(board);
}

/*
* Apply a move by a team to the board
*/
public void makeMove(Cell c, Location l) throws TicTacToeException {
try {
if(cellAt(l) != Cell.EMPTY) {
throw new TicTacToeException("Cell is already take");
} else {
board[indexFor(l)] = c;
}
}catch(IndexOutOfBoundsException exc){
throw new TicTacToeException("Invalid cell");
}
}

/*
* Returns the value of a given cell
*/
public Cell cellAt(Location l) {
return board[indexFor(l)];
}

/*
* Check if there are any winners
* return values:
* X = Player X has won
* O = Player O has won
* EMPTY = Game is a Draw
* null = Neither player has won yet
*
*/
public Cell winner() {
//TODO: Get rid of magic numbers
int[][] winning_indexes = new int[][]{
// Horizontal lines
{0, 1, 2},
{3, 4, 5},
{6, 7, 8},

// Vertical lines
{0, 3, 6},
{1, 4, 7},
{2, 5, 8},

// Diagonals
{0, 4, 8},
{2, 4, 6}
};

// Return the winner if there is one
for(int i=0; i<winning_indexes.length; i++){
if(board[winning_indexes[i][0]] != Cell.EMPTY
&& (board[winning_indexes[i][0]] == board[winning_indexes[i][1]])
&& (board[winning_indexes[i][1]] == board[winning_indexes[i][2]])){
return board[winning_indexes[i][0]];
}
}

// If there is no winner and the board in full than the game is a draw
// TODO: We can check if it is impossible for any one to win
// But that would be too much work for now
if(boardFull()) {
return Cell.EMPTY;
}

// Otherwise there is no winner and the game can go on
return null;
}

/*
* Check to see if the board has been filled
*/

public boolean boardFull() {
for(int i=0; i<NUM_CELLS; i++) {
if(board[i] == Cell.EMPTY) {
return false;
}
}
return true;
}

/*
* Return the coordinates for empty slots
*/
public List<Location> emptySlots() {
ArrayList<Location> slots = new ArrayList<Location>();
for(int y=0; y<HEIGHT; y++) {
for(int x=0; x<WIDTH; x++) {
Location l = new Location(x, y);
Cell c = cellAt(l);
if(c == Cell.EMPTY) {
slots.add(l);
}
}
}
return slots;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.circuitsofimagination.tictactoe;

public class TicTacToeException extends Exception {
public TicTacToeException(String message) {
super(message);
}

}
85 changes: 85 additions & 0 deletions src/com/circuitsofimagination/tictactoe/TicTacToeGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.circuitsofimagination.tictactoe;

import javax.swing.JOptionPane;

import com.circuitsofimagination.tictactoe.ui.*;
import com.circuitsofimagination.tictactoe.TicTacToeBoard.*;

public class TicTacToeGame implements Runnable {
public TicTacToeGameView gameView;
public TicTacToeBoardView boardView;

public TicTacToeBoard board;

public TicTacToePlayer xPlayer;
public TicTacToePlayer oPlayer;

public TicTacToeGame(String xPlayerName, String oPlayerName) {
try {
this.xPlayer = TicTacToePlayerFactory.createPlayer(xPlayerName);
xPlayer.initialize(Cell.X, this);
this.oPlayer = TicTacToePlayerFactory.createPlayer(oPlayerName);
oPlayer.initialize(Cell.O, this);
} catch (TicTacToeException exc) {
//This shouldn't happen since were using a combo box
}
board = new TicTacToeBoard();
boardView = new TicTacToeBoardView(board);
gameView = new TicTacToeGameView(boardView);
}

@Override
public void run() {
boolean playing = true;
Location move;

while(playing) {
try {
move = xPlayer.makeMove();
board.makeMove(Cell.X, move);
boardView.repaint();
if(board.winner() != null) {
playing = false;
break;
}


move = oPlayer.makeMove();
board.makeMove(Cell.O, move);
boardView.repaint();
if(board.winner() != null) {
playing = false;
}
} catch(TicTacToeException exc) {
JOptionPane.showMessageDialog(gameView, exc.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
}

String message = null;
Cell winner = board.winner();
if (winner == Cell.X) {
message = "X wins!!";
} else if(winner == Cell.O) {
message = "O wins!!";
}else {
message = "Game is a draw";
}

JOptionPane.showMessageDialog(gameView, message, "Game Over", JOptionPane.INFORMATION_MESSAGE);
}

public static void startGame(String xPlayerName, String oPlayerName) {
TicTacToeGame theGame = new TicTacToeGame(xPlayerName, oPlayerName);
// Run the game in a seperate thread so that blocking calls
// by the game (e.g. makeMove) won't block the GUI events
Thread gameThread = new Thread(theGame);
gameThread.setDaemon(true);
gameThread.start();
}

public static void main(String [] args) {
TicTacToePlayerSelectionView selector = new TicTacToePlayerSelectionView(null, "Select Players", true);
selector.execute();
}

}
Loading

0 comments on commit ae6fdf6

Please sign in to comment.