package com.captstudios.games.tafl.core.es.model.ai.optimization;
import com.badlogic.gdx.utils.Array;
import com.captstudios.games.tafl.core.consts.Constants;
import com.captstudios.games.tafl.core.es.model.ai.optimization.moves.Move;
import com.captstudios.games.tafl.core.es.model.ai.optimization.transposition.ZorbistHash;
public abstract class GameBoard {
public int dimensions;
public int pieceTypes;
public int boardSize;
public BitBoard[] bitBoards;
public BitBoard[] rows;
public BitBoard[] columns;
public ZorbistHash zorbistHash;
public Array<Move> undoStack;
public Array<Move> simulatedStack;
public int hashCode;
int applyCount = 0;
int undoCount = 0;
public GameBoard(int dimensions, int pieceTypes, ZorbistHash zorbistHash) {
this.dimensions = dimensions;
this.pieceTypes = pieceTypes;
this.boardSize = dimensions * dimensions;
this.zorbistHash = zorbistHash;
this.undoStack = new Array<Move>();
this.simulatedStack = new Array<Move>();
bitBoards = new BitBoard[pieceTypes];
for (int i = 0; i < pieceTypes; i++) {
bitBoards[i] = new BitBoard(boardSize);
}
this.hashCode = calculateHashCode();
rows = new BitBoard[dimensions];
for (int i = 0; i < dimensions; i++) {
rows[i] = new BitBoard(boardSize);
rows[i].set(i * dimensions, i * dimensions + dimensions);
}
columns = new BitBoard[dimensions];
for (int i = 0; i < dimensions; i++) {
columns[i] = new BitBoard(boardSize);
for (int j = 0; j < dimensions; j++) {
columns[i].set(j * dimensions + i);
}
}
}
public void applyMove(Move move, boolean simulate) {
BitBoard bitBoard = bitBoards[move.pieceType];
bitBoard.clear(move.source);
bitBoard.set(move.destination);
hashCode ^= zorbistHash.hash[move.pieceType][move.source];
hashCode ^= zorbistHash.hash[move.pieceType][move.destination];
if (!simulate) {
Move clone = move.clone();
undoStack.add(clone);
} else {
simulatedStack.add(move);
}
}
public Move undoMove() {
if (undoStack.size > 0) {
Move move = undoStack.pop();
undoMove(move);
return move;
}
return null;
}
protected void undoMove(Move move) {
BitBoard bitBoard = bitBoards[move.pieceType];
bitBoard.clear(move.destination);
bitBoard.set(move.source);
hashCode ^= zorbistHash.hash[move.pieceType][move.source];
hashCode ^= zorbistHash.hash[move.pieceType][move.destination];
addPieces((move.pieceType + 1) % 2, move.capturedPieces);
}
public void addPiece(int pieceType, int piece) {
bitBoards[pieceType].set(piece);
hashCode ^= zorbistHash.hash[pieceType][piece];
}
public void addPieces(int pieceType, BitBoard pieces) {
for (int i = pieces.nextSetBit(0); i >= 0; i = pieces.nextSetBit(i+1)) {
addPiece(pieceType, i);
}
}
public void removePieces(int pieceType, BitBoard pieces) {
for (int i = pieces.nextSetBit(0); i >= 0; i = pieces.nextSetBit(i+1)) {
bitBoards[pieceType].clear(i);
hashCode ^= zorbistHash.hash[pieceType][i];
}
}
public void simulateMove(Move move) {
applyMove(move, true);
move.capturedPieces.set(getCapturedPieces(move));
if (move.pieceType == Constants.BoardConstants.BLACK_TEAM) {
removePieces(Constants.BoardConstants.WHITE_TEAM, move.capturedPieces);
removePieces(Constants.BoardConstants.KING, move.capturedPieces);
} else {
removePieces(Constants.BoardConstants.BLACK_TEAM, move.capturedPieces);
}
}
protected abstract BitBoard getCapturedPieces(Move move);
public void undoSimulatedMove() {
undoMove(simulatedStack.pop());
}
public void reset() {
for (BitBoard bitBoard : bitBoards) {
bitBoard.clear();
}
}
/**
* Compute a 32-bit integer to represent the board, according to Zobrist[70]
*/
@Override
public int hashCode() {
return hashCode;
}
private int calculateHashCode() {
int hash = 0;
// Look at all pieces, one at a time
for (int currPiece = 0; currPiece < pieceTypes; currPiece++) {
BitBoard board = bitBoards[currPiece];
// Search for all pieces on all cells. We could optimize here: not
// looking for pawns on the back row (or the eight row), getting out
// of the "currSqaure" loop once we found one king of one color,
// etc.
// But for simplicity's sake, we'll keep things generic.
for (int currCell = 0; currCell < boardSize; currCell++) {
// Zobrist's method: generate a bunch of random bitfields, each
// representing a certain "piece X is on cell Y" predicate;
// XOR
// the bitfields associated with predicates which are true.
// Therefore, if we find a piece (in tmp) in a certain cell,
// we accumulate the related HashKeyComponent.
if (board.get(currCell)) {
hash ^= zorbistHash.hash[currPiece][currCell];
}
}
}
return hash;
}
@Override
public boolean equals(Object other) {
if (other != null && other instanceof GameBoard) {
return hashCode() == other.hashCode();
}
return false;
}
public boolean isValid(int cellId) {
return cellId >= 0 && cellId < boardSize;
}
public boolean inRow(int cellId, int other) {
return rows[cellId / dimensions].get(other);
}
public boolean inColumn(int cellId, int other) {
return columns[cellId % dimensions].get(other);
}
public BitBoard getRow(int cellId) {
return rows[cellId / dimensions];
}
public BitBoard getColumn(int cellId) {
return columns[cellId % dimensions];
}
}