package aima.core.environment.tictactoe;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import aima.core.util.datastructure.XYLocation;
/**
* A state of the Tic-tac-toe game is characterized by a board containing
* symbols X and O, the next player to move, and an utility information.
*
* @author Ruediger Lunde
*
*/
public class TicTacToeState implements Cloneable {
public static final String O = "O";
public static final String X = "X";
public static final String EMPTY = "-";
//
private String[] board = new String[] { EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EMPTY, EMPTY, EMPTY };
private String playerToMove = X;
private double utility = -1; // 1: win for X, 0: win for O, 0.5: draw
public String getPlayerToMove() {
return playerToMove;
}
public boolean isEmpty(int col, int row) {
return Objects.equals(board[getAbsPosition(col, row)], EMPTY);
}
public String getValue(int col, int row) {
return board[getAbsPosition(col, row)];
}
public double getUtility() {
return utility;
}
public void mark(XYLocation action) {
mark(action.getXCoOrdinate(), action.getYCoOrdinate());
}
public void mark(int col, int row) {
if (utility == -1 && Objects.equals(getValue(col, row), EMPTY)) {
board[getAbsPosition(col, row)] = playerToMove;
analyzeUtility();
playerToMove = (Objects.equals(playerToMove, X) ? O : X);
}
}
private void analyzeUtility() {
if (lineThroughBoard()) {
utility = (Objects.equals(playerToMove, X) ? 1 : 0);
} else if (getNumberOfMarkedPositions() == 9) {
utility = 0.5;
}
}
public boolean lineThroughBoard() {
return (isAnyRowComplete() || isAnyColumnComplete() || isAnyDiagonalComplete());
}
private boolean isAnyRowComplete() {
for (int row = 0; row < 3; row++) {
String val = getValue(0, row);
if (!Objects.equals(val, EMPTY) && Objects.equals(val, getValue(1, row)) && Objects.equals(val, getValue(2, row))) {
return true;
}
}
return false;
}
private boolean isAnyColumnComplete() {
for (int col = 0; col < 3; col++) {
String val = getValue(col, 0);
if (!Objects.equals(val, EMPTY) && Objects.equals(val, getValue(col, 1)) && Objects.equals(val, getValue(col, 2))) {
return true;
}
}
return false;
}
private boolean isAnyDiagonalComplete() {
String val = getValue(0, 0);
if (!Objects.equals(val, EMPTY) && Objects.equals(val, getValue(1, 1)) && Objects.equals(val, getValue(2, 2))) {
return true;
}
val = getValue(0, 2);
if (!Objects.equals(val, EMPTY) && Objects.equals(val, getValue(1, 1)) && Objects.equals(val, getValue(2, 0))) {
return true;
}
return false;
}
public int getNumberOfMarkedPositions() {
int retVal = 0;
for (int col = 0; col < 3; col++) {
for (int row = 0; row < 3; row++) {
if (!(isEmpty(col, row))) {
retVal++;
}
}
}
return retVal;
}
public List<XYLocation> getUnMarkedPositions() {
List<XYLocation> result = new ArrayList<>();
for (int col = 0; col < 3; col++) {
for (int row = 0; row < 3; row++) {
if (isEmpty(col, row)) {
result.add(new XYLocation(col, row));
}
}
}
return result;
}
@Override
public TicTacToeState clone() {
TicTacToeState copy = null;
try {
copy = (TicTacToeState) super.clone();
copy.board = Arrays.copyOf(board, board.length);
} catch (CloneNotSupportedException e) {
e.printStackTrace(); // should never happen...
}
return copy;
}
@Override
public boolean equals(Object anObj) {
if (anObj != null && anObj.getClass() == getClass()) {
TicTacToeState anotherState = (TicTacToeState) anObj;
for (int i = 0; i < 9; i++) {
if (!Objects.equals(board[i], anotherState.board[i]))
return false;
}
return true;
}
return false;
}
@Override
public int hashCode() {
// Need to ensure equal objects have equivalent hashcodes (Issue 77).
return toString().hashCode();
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
buffer.append(getValue(col, row)).append(" ");
}
buffer.append("\n");
}
return buffer.toString();
}
//
// PRIVATE METHODS
//
private int getAbsPosition(int col, int row) {
return row * 3 + col;
}
}