-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
First versio. Wrote it for fun so I didn't use version control
- Loading branch information
0 parents
commit ae6fdf6
Showing
10 changed files
with
770 additions
and
0 deletions.
There are no files selected for viewing
107 changes: 107 additions & 0 deletions
107
src/com/circuitsofimagination/tictactoe/TicTacToeAIPlayer.java
This file contains 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
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
182
src/com/circuitsofimagination/tictactoe/TicTacToeBoard.java
This file contains 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
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; | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
src/com/circuitsofimagination/tictactoe/TicTacToeException.java
This file contains 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
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
85
src/com/circuitsofimagination/tictactoe/TicTacToeGame.java
This file contains 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
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(); | ||
} | ||
|
||
} |
Oops, something went wrong.