package com.captstudios.games.tafl.core.es.model.rules; import java.util.Random; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.IntArray; import com.badlogic.gdx.utils.IntIntMap; import com.badlogic.gdx.utils.IntIntMap.Values; import com.captstudios.games.tafl.core.consts.Constants; import com.captstudios.games.tafl.core.enums.DrawReasonEnum; import com.captstudios.games.tafl.core.enums.LifeCycle; import com.captstudios.games.tafl.core.es.model.TaflBoard; import com.captstudios.games.tafl.core.es.model.TaflMatch; import com.captstudios.games.tafl.core.es.model.ai.optimization.BitBoard; import com.captstudios.games.tafl.core.es.model.ai.optimization.moves.Move; public class FetlarRulesEngine implements RulesEngine { protected static final Array<Move> NO_MOVES = new Array<Move>(); protected Array<Move> blackLegalMoves; protected Array<Move> whiteLegalMoves; protected Array<Move> kingLegalMoves; protected BitBoard legalMoves; protected BitBoard allPieces; protected BitBoard tempBitBoard; protected BitBoard allWhiteBitBoard; protected IntArray boardConfigHistory; protected IntIntMap configurationCounter; protected TaflBoard board; protected Random random; protected Move[] shuffleArray; public FetlarRulesEngine() { blackLegalMoves = new Array<Move>(); whiteLegalMoves = new Array<Move>(); kingLegalMoves = new Array<Move>(); boardConfigHistory = new IntArray(); configurationCounter = new IntIntMap(); random = new Random(); shuffleArray = new Move[Constants.GameConstants.MAX_NUMBER_OF_MOVES]; } @Override public boolean isGameOver(int team) { return checkWinner() != Constants.BoardConstants.NO_TEAM || checkDraw(team) != null; } @Override public void initializeMatch(TaflMatch match) { board = match.board; legalMoves = new BitBoard(match.board.boardSize); allPieces = new BitBoard(match.board.boardSize); tempBitBoard = new BitBoard(match.board.boardSize); allWhiteBitBoard = new BitBoard(match.board.boardSize); calculateLegalMoves(match.turn); boardConfigHistory.add(match.board.hashCode()); } @Override public void removePieces(TaflMatch match, int team, BitBoard capturedPieces) { boardConfigHistory.items[boardConfigHistory.size - 1] = match.board.hashCode(); } @Override public void applyMove(TaflMatch match, Move move) { boardConfigHistory.add(match.board.hashCode()); } @Override public void undoMove(TaflMatch match, Move move) { if (boardConfigHistory.size > 0) { boardConfigHistory.pop(); } } @Override public void changeTurn(TaflMatch match) { calculateLegalMoves(match.turn); } @Override public void gameOver(TaflMatch match, LifeCycle status) { } public void calculateLegalMoves(int team) { Array<Move> allLegalMoves = (team == Constants.BoardConstants.WHITE_TEAM) ? whiteLegalMoves : blackLegalMoves; board.movePool.freeAll(allLegalMoves); allLegalMoves.clear(); allPieces.set(board.whiteBitBoard()).or(board.blackBitBoard()).or(board.kingBitBoard()); calculateMoves(team, allLegalMoves); shuffle(allLegalMoves); if (team == Constants.BoardConstants.WHITE_TEAM) { kingLegalMoves.clear(); calculateMoves(Constants.BoardConstants.KING, kingLegalMoves); // we want king moves in the beginning kingLegalMoves.addAll(allLegalMoves); whiteLegalMoves.clear(); whiteLegalMoves.addAll(kingLegalMoves); } } protected void shuffle(Array<Move> moves) { int size = moves.size; for (int i = 0; i < size; i++) { shuffleArray[i] = moves.get(i); } for (int i = 0; i < size; i++) { int randomIndex = random.nextInt(size); Move current = shuffleArray[i]; shuffleArray[i] = shuffleArray[randomIndex]; shuffleArray[randomIndex] = current; } moves.clear(); moves.addAll(shuffleArray, 0, size); } protected void calculateMoves(int pieceType, Array<Move> allLegalMoves) { BitBoard bitBoard = board.bitBoards[pieceType]; for (int source = bitBoard.nextSetBit(0); source >= 0; source = bitBoard.nextSetBit(source+1)) { BitBoard moves = calculateMoves(source); for (int dest = moves.nextSetBit(0); dest >= 0; dest = moves.nextSetBit(dest+1)) { Move move = board.movePool.obtain(); move.pieceType = pieceType; move.source = source; move.destination = dest; allLegalMoves.add(move); } } } @Override public DrawReasonEnum checkDraw(int team) { DrawReasonEnum reason = checkDrawMoves(team); if (reason == null) { reason = checkDrawThreePeat(); } return reason; } @Override public int checkWinner() { int winner = Constants.BoardConstants.NO_TEAM; if (board.getKingCaptured()) { winner = Constants.BoardConstants.BLACK_TEAM; } else if (board.corners.intersects(board.kingBitBoard())) { winner = Constants.BoardConstants.WHITE_TEAM; } return winner; } @Override public BitBoard getCapturedPieces(Move move) { tempBitBoard.clear(); int capturer = move.destination; int capturingTeam = Constants.BoardConstants.WHITE_TEAM; if (move.pieceType == Constants.BoardConstants.BLACK_TEAM) { capturingTeam = Constants.BoardConstants.BLACK_TEAM; } allWhiteBitBoard.clear(); allWhiteBitBoard.set(board.whiteBitBoard()).or(board.kingBitBoard()); BitBoard oppositeBoard; BitBoard capturingBoard; if (capturingTeam == Constants.BoardConstants.WHITE_TEAM) { capturingBoard = allWhiteBitBoard; oppositeBoard = board.blackBitBoard(); } else { capturingBoard = board.blackBitBoard(); oppositeBoard = allWhiteBitBoard; } int king = board.getKing(); // CAPTURE ABOVE int beingCaptured = capturer + board.dimensions; int teammate = capturer + 2 * board.dimensions; if (board.isValid(beingCaptured) && oppositeBoard.get(beingCaptured)) { if (board.kingBitBoard().get(beingCaptured)) { int teammate2 = beingCaptured - 1; int teammate3 = beingCaptured + 1; if (isKingHostileVertical(capturingBoard, teammate) && isKingHostileHorizontal(capturingBoard, king, teammate2) && isKingHostileHorizontal(capturingBoard, king, teammate3)) { tempBitBoard.set(king); } } else { if (isHostile(beingCaptured, capturingTeam, capturingBoard, teammate)) { tempBitBoard.set(beingCaptured); } } } // CAPTURE BELOW beingCaptured = capturer - board.dimensions; teammate = capturer - 2 * board.dimensions; if (board.isValid(beingCaptured) && oppositeBoard.get(beingCaptured)) { if (board.kingBitBoard().get(beingCaptured)) { int teammate2 = beingCaptured - 1; int teammate3 = beingCaptured + 1; if (isKingHostileVertical(capturingBoard, teammate) && isKingHostileHorizontal(capturingBoard, king, teammate2) && isKingHostileHorizontal(capturingBoard, king, teammate3)) { tempBitBoard.set(king); } } else { if (isHostile(beingCaptured, capturingTeam, capturingBoard, teammate)) { tempBitBoard.set(beingCaptured); } } } // CAPTURE LEFT beingCaptured = capturer - 1; teammate = capturer - 2; if (board.isValid(beingCaptured) && board.inRow(capturer, beingCaptured) && oppositeBoard.get(beingCaptured)) { if (board.kingBitBoard().get(beingCaptured)) { int teammate2 = beingCaptured + board.dimensions; int teammate3 = beingCaptured - board.dimensions; if (isKingHostileHorizontal(capturingBoard, king, teammate) && isKingHostileVertical(capturingBoard, teammate2) && isKingHostileVertical(capturingBoard, teammate3)) { tempBitBoard.set(king); } } else { if (board.isValid(teammate) && board.inRow(capturer, teammate) && isHostile(beingCaptured, capturingTeam, capturingBoard, teammate)) { tempBitBoard.set(beingCaptured); } } } // CAPTURE RIGHT beingCaptured = move.destination + 1; teammate = move.destination + 2; if (board.isValid(beingCaptured) && board.inRow(move.destination, beingCaptured) && oppositeBoard.get(beingCaptured)) { if (board.kingBitBoard().get(beingCaptured)) { int teammate2 = beingCaptured + board.dimensions; int teammate3 = beingCaptured - board.dimensions; if (isKingHostileHorizontal(capturingBoard, king, teammate) && isKingHostileVertical(capturingBoard, teammate2) && isKingHostileVertical(capturingBoard, teammate3)) { tempBitBoard.set(king); } } else { if (board.isValid(teammate) && board.inRow(capturer, teammate) && isHostile(beingCaptured, capturingTeam, capturingBoard, teammate)) { tempBitBoard.set(beingCaptured); } } } return tempBitBoard; } protected boolean isKingHostileVertical(BitBoard capturingBoard, int oppositeCell) { return board.isValid(oppositeCell) && capturingBoard.get(oppositeCell); } protected boolean isKingHostileHorizontal(BitBoard capturingBoard, int cell, int oppositeCell) { return board.inRow(cell, oppositeCell) && capturingBoard.get(oppositeCell); } @Override public int getFirstTurn() { return Constants.BoardConstants.BLACK_TEAM; } protected boolean isHostile(int beingCaptured, int capturingTeam, BitBoard capturingBoard, int oppositeCell) { return board.isValid(oppositeCell) && (capturingBoard.get(oppositeCell) || (!board.canWalk(capturingTeam, oppositeCell) && board.getKing() != oppositeCell)); } @Override public boolean isMoveLegal(int team, int source, int destination) { return getLegalMoves(team, source).get(destination); } @Override public Array<Move> allLegalMoves(int team) { if (isGameOver(team)) { return NO_MOVES; } return retrieveLegalMoves(team); } protected Array<Move> retrieveLegalMoves(int team) { Array<Move> allLegalMoves = (team == Constants.BoardConstants.WHITE_TEAM) ? whiteLegalMoves : blackLegalMoves; if (allLegalMoves.size == 0) { calculateLegalMoves(team); } return allLegalMoves; } @Override public BitBoard getLegalMoves(int team, int source) { Array<Move> allLegalMoves = (team == Constants.BoardConstants.WHITE_TEAM) ? whiteLegalMoves : blackLegalMoves; legalMoves.clear(); for (Move move : allLegalMoves) { if (move.source == source) { legalMoves.set(move.destination); } } return legalMoves; } @Override public boolean isVulnerable(int team, int cellId) { int oppositeTeam = (team + 1) % 2; BitBoard oppositeBoard; if (team == Constants.BoardConstants.WHITE_TEAM) { oppositeBoard = board.blackBitBoard(); } else { allWhiteBitBoard.clear(); allWhiteBitBoard.set(board.whiteBitBoard()).or(board.kingBitBoard()); oppositeBoard = allWhiteBitBoard; } int cellAbove = cellId + board.dimensions; int cellBelow = cellId - board.dimensions; int cellLeft = cellId - 1; int cellRight = cellId + 1; // ABOVE if (board.isValid(cellBelow) && isHostile(cellId, oppositeTeam, oppositeBoard, cellAbove)) { tempBitBoard.set(board.getRow(cellBelow)).and(oppositeBoard); for (int i = tempBitBoard.nextSetBit(0); i >= 0; i = tempBitBoard.nextSetBit(i+1)) { if (board.rules.isMoveLegal(oppositeTeam, i, cellId)) { return true; } } } // BELOW if (board.isValid(cellAbove) && isHostile(cellId, oppositeTeam, oppositeBoard, cellBelow)) { tempBitBoard.set(board.getRow(cellAbove)).and(oppositeBoard); for (int i = tempBitBoard.nextSetBit(0); i >= 0; i = tempBitBoard.nextSetBit(i+1)) { if (board.rules.isMoveLegal(oppositeTeam, i, cellId)) { return true; } } } // LEFT if (board.isValid(cellLeft) && board.isValid(cellRight) && board.inRow(cellId, cellLeft) && board.inRow(cellId, cellRight) && isHostile(cellId, oppositeTeam, oppositeBoard, cellLeft)) { tempBitBoard.set(board.getColumn(cellRight)).and(oppositeBoard); for (int i = tempBitBoard.nextSetBit(0); i >= 0; i = tempBitBoard.nextSetBit(i+1)) { if (board.rules.isMoveLegal(oppositeTeam, i, cellId)) { return true; } } } // RIGHT if (board.isValid(cellLeft) && board.isValid(cellRight) && board.inRow(cellId, cellLeft) && board.inRow(cellId, cellRight) && isHostile(cellId, oppositeTeam, oppositeBoard, cellRight)) { tempBitBoard.set(board.getColumn(cellLeft)).and(oppositeBoard); for (int i = tempBitBoard.nextSetBit(0); i >= 0; i = tempBitBoard.nextSetBit(i+1)) { if (board.rules.isMoveLegal(oppositeTeam, i, cellId)) { return true; } } } return false; } @Override public boolean teamCanMoveToLocation(int team, int cellId) { BitBoard teamBoard; if (team == Constants.BoardConstants.WHITE_TEAM) { allWhiteBitBoard.clear(); allWhiteBitBoard.set(board.whiteBitBoard()).or(board.kingBitBoard()); teamBoard = allWhiteBitBoard; } else { teamBoard = board.blackBitBoard(); } tempBitBoard.set(board.getRow(cellId)).or(board.getColumn(cellId)).and(teamBoard); for (int i = tempBitBoard.nextSetBit(0); i >= 0; i = tempBitBoard.nextSetBit(i+1)) { if (board.rules.isMoveLegal(team, i, cellId)) { return true; } } return false; } protected BitBoard calculateMoves(int source) { legalMoves.clear(); // LEGAL UP for (int i = source + board.dimensions; i < board.boardSize; i += board.dimensions) { if (!allPieces.get(i)) { if (board.canWalk(source, i)) { legalMoves.set(i); } } else { break; } } // LEGAL DOWN for (int i = source - board.dimensions; i >= 0; i -= board.dimensions) { if (!allPieces.get(i)) { if (board.canWalk(source, i)) { legalMoves.set(i); } } else { break; } } // LEGAL RIGHT int nextRow = ((source + board.dimensions) / board.dimensions) * board.dimensions; for (int i = source + 1; i < nextRow; i++) { if (!allPieces.get(i)) { if (board.canWalk(source, i)) { legalMoves.set(i); } } else { break; } } // LEGAL LEFT int previousRow = (source / board.dimensions) * board.dimensions - 1; for (int i = source - 1; i > previousRow; i--) { if (!allPieces.get(i)) { if (board.canWalk(source, i)) { legalMoves.set(i); } } else { break; } } return legalMoves; } protected DrawReasonEnum checkDrawMoves(int team) { if (board.undoStack.size >= Constants.GameConstants.DRAW_MOVE_THRESHHOLD) { return DrawReasonEnum.DRAW_TOO_MANY_TURNS; } else if (retrieveLegalMoves(team).size == 0){ if (team == Constants.BoardConstants.WHITE_TEAM) { return DrawReasonEnum.DRAW_NO_MOVES_WHITE; } else { return DrawReasonEnum.DRAW_NO_MOVES_BLACK; } } return null; } protected DrawReasonEnum checkDrawThreePeat() { int boardsToExamine = Constants.GameConstants.DRAW_BOARD_REPETITION_THRESHHOLD * Constants.GameConstants.DRAW_MOVES_TO_CHECK; if (boardConfigHistory.size >= boardsToExamine) { configurationCounter.clear(); for (int i = boardConfigHistory.size - 1; i >= boardConfigHistory.size - boardsToExamine; i--) { configurationCounter.getAndIncrement(boardConfigHistory.items[i], 0, 1); } Values values = configurationCounter.values(); while (values.hasNext) { if (values.next() >= Constants.GameConstants.DRAW_BOARD_REPETITION_THRESHHOLD) { return DrawReasonEnum.DRAW_THREE_PEAT; } } } return null; } @Override public void freeMoves(Array<Move> moves) { board.movePool.freeAll(moves); } @Override public Array<Move> generateLegalMoves(int team) { calculateLegalMoves(team); return allLegalMoves(team); } }