diff --git a/src/Saboteur/SaboteurBoardState.java b/src/Saboteur/SaboteurBoardState.java index 3024e23..480fa6d 100644 --- a/src/Saboteur/SaboteurBoardState.java +++ b/src/Saboteur/SaboteurBoardState.java @@ -69,6 +69,8 @@ public class SaboteurBoardState extends BoardState { this.board[hiddenPos[i][0]][hiddenPos[i][1]] = new SaboteurTile(list.remove(idx)); this.hiddenCards[i] = this.board[hiddenPos[i][0]][hiddenPos[i][1]]; } + + //initialize the entrance this.board[originPos][originPos] = new SaboteurTile("entrance"); //initialize the deck. @@ -308,8 +310,8 @@ public int[][] getHiddenIntBoard() { } if(!isAnHiddenObjective) { int[][] path = this.board[i][j].getPath(); - for (int k = 0; i < 3; i++) { - for (int h = 0; i < 3; i++) { + for (int k = 0; k < 3; k++) { + for (int h = 0; h < 3; h++) { this.intBoard[i * 3 + k][j * 3 + h] = path[h][2-k]; } } @@ -825,7 +827,6 @@ private boolean pathToHidden(SaboteurTile[] objectives){ For each hidden objectives: We verify there is a path of cards between the start and the hidden objectives. If there is one, we do the same but with the 0-1s matrix! - To verify a path, we use a simple search algorithm where we propagate a front of visited neighbor. TODO To speed up: The neighbor are added ranked on their distance to the origin... (simply use a PriorityQueue with a Comparator) */ @@ -962,4 +963,4 @@ public static void main(String[] args) { } //pbs.printBoard(); } -} +} \ No newline at end of file diff --git a/src/Saboteur/cardClasses/SaboteurTile.java b/src/Saboteur/cardClasses/SaboteurTile.java index 704e087..68d8b0b 100644 --- a/src/Saboteur/cardClasses/SaboteurTile.java +++ b/src/Saboteur/cardClasses/SaboteurTile.java @@ -8,11 +8,12 @@ public class SaboteurTile extends SaboteurCard { private int[][] path; private String idx; + public SaboteurTile(String idx){ this.idx = idx; this.path = SaboteurTile.initializePath(this.idx); - } + public String getName(){ return "Tile:"+this.idx; } diff --git a/src/autoplay/Autoplay.java b/src/autoplay/Autoplay.java index 5ea61b9..8d81a51 100644 --- a/src/autoplay/Autoplay.java +++ b/src/autoplay/Autoplay.java @@ -24,7 +24,8 @@ public class Autoplay { public static void main(String args[]) { int n_games; try { - n_games = Integer.parseInt(args[0]); + // n_games = Integer.parseInt(args[0]); + n_games = 15; if (n_games < 1) { throw new Exception(); } @@ -46,7 +47,7 @@ public static void main(String args[]) { client1_pb.redirectOutput(ProcessBuilder.Redirect.INHERIT); ProcessBuilder client2_pb = new ProcessBuilder("java", "-cp", "bin", "-Xms520m", "-Xmx520m", - "boardgame.Client", "Saboteur.RandomSaboteurPlayer"); + "boardgame.Client", "student_player.StudentPlayer"); client2_pb.redirectOutput(ProcessBuilder.Redirect.INHERIT); for (int i = 0; i < n_games; i++) { diff --git a/src/boardgame/Server.java b/src/boardgame/Server.java index 8734c64..8f2c375 100644 --- a/src/boardgame/Server.java +++ b/src/boardgame/Server.java @@ -50,7 +50,7 @@ public class Server implements Runnable { protected static final String VERSION = "0.08"; protected static final int DEFAULT_PORT = 8123; - public static final int DEFAULT_TIMEOUT = 20000; + public static final int DEFAULT_TIMEOUT = 2000; private static final int DEFAULT_TIMEOUT_CUSHION = 1000; public static final int FIRST_MOVE_TIMEOUT = 30000; diff --git a/src/boardgame/ServerGUI.java b/src/boardgame/ServerGUI.java index 41a6d49..9955e2c 100644 --- a/src/boardgame/ServerGUI.java +++ b/src/boardgame/ServerGUI.java @@ -37,7 +37,7 @@ public class ServerGUI extends JFrame implements BoardPanel.BoardPanelListener { /** The list of games for which servers can be launched */ protected static final String[] BOARD_CLASSES = { "Saboteur.SaboteurBoard" }; /** The list of players that can be launched */ - protected static final String[] PLAYER_CLASSES = { "Saboteur.RandomSaboteurPlayer"}; // "alpha_beta.AlphaBetaPlayer" //"Saboteur.StudentPlayer" + protected static final String[] PLAYER_CLASSES = { "Saboteur.RandomSaboteurPlayer","student_player.StudentPlayer"}; // "alpha_beta.AlphaBetaPlayer" //"Saboteur.StudentPlayer" private static final int BOARD_SIZE = 800; private static final int LIST_WIDTH = 280; diff --git a/src/student_player/AndNode.java b/src/student_player/AndNode.java new file mode 100644 index 0000000..a2a7182 --- /dev/null +++ b/src/student_player/AndNode.java @@ -0,0 +1,66 @@ +package student_player; + +import java.util.ArrayList; + +import Saboteur.SaboteurMove; +import Saboteur.cardClasses.SaboteurCard; +import Saboteur.cardClasses.SaboteurTile; + +public class AndNode extends AndOrNode{ + public SaboteurCard dealedCard; + public double prob; + public ORNode parent; + public ArrayList succesors; + public int depth; + + public AndNode(String name, BoardState boardState,SaboteurCard dealedCard, double dealedCardProb) { + super(name,boardState); + this.dealedCard = dealedCard; + if(boardState.getTurnPlayer() == 1) { + boardState.player1Cards.add(dealedCard); + }else { + boardState.player2Cards.add(dealedCard); + } + this.prob = dealedCardProb; + ArrayList moves = boardState.getAllLegalMoves(); + int counter = 0; + this.succesors = new ArrayList<>(); + for(SaboteurMove move: moves) { + + String nodeName = "OR,"+(depth+1)+","+counter; + + BoardState nodeBoardState = new BoardState(boardState); + nodeBoardState.processMove(move, false); + ORNode node = new ORNode(nodeName,nodeBoardState,move); + node.parent = this; + succesors.add(node); + } + } + + public double getMinHeuristicVal(int[] goalPos,int depth, int maxDepth) { + double minHeuristicVal = Integer.MAX_VALUE; + for(ORNode node: succesors) { + if(node.move.getCardPlayed() instanceof SaboteurTile) { + if(!isGoodTile((SaboteurTile)node.move.getCardPlayed()))continue; + } + double curVal = node.getExpectedMinHeuistic(goalPos,depth+1, maxDepth); + if(curVal < minHeuristicVal) { + minHeuristicVal = curVal; + } + if(minHeuristicVal == Integer.MIN_VALUE) + break; + } + return minHeuristicVal; + } + + public boolean isGoodTile(SaboteurTile tile) { + if(tile.getIdx().equals("0")||tile.getIdx().equals("5_flip")||tile.getIdx().equals("5")||tile.getIdx().equals("6")|| + tile.getIdx().equals("6_flip")||tile.getIdx().equals("7_flip")||tile.getIdx().equals("7") + ||tile.getIdx().equals("8")||tile.getIdx().equals("9") + ||tile.getIdx().equals("9_flip")||tile.getIdx().equals("10")) { + return true; + } + return false; + } + +} diff --git a/src/student_player/AndOrNode.java b/src/student_player/AndOrNode.java new file mode 100644 index 0000000..9d5223d --- /dev/null +++ b/src/student_player/AndOrNode.java @@ -0,0 +1,46 @@ +package student_player; + +import java.util.ArrayList; +import Saboteur.cardClasses.SaboteurCard; +import Saboteur.cardClasses.SaboteurTile; + +public class AndOrNode { + public BoardState boardState; + public ArrayList player1Hands; + public ArrayList player2Hands; + public String name; + + public AndOrNode(String name, BoardState boardState) { + this.name = name; + this.boardState = boardState; + } + + //Dir: 0 Up; 1 Right; 2 Down; 3 Left + public boolean checkOpenEnd(int[][] intBoard,SaboteurTile[][] tileBoard,int i,int j,int dir) { + switch(dir){ + case 0: + if(intBoard[i][j]== 1&& i/3 == 0) return true; + if(intBoard[i][j]==1&&tileBoard[i/3-1][j/3]==null) + return true; + break; + case 1: + if(intBoard[i][j]== 1&& j/3 == 13) return true; + if(intBoard[i][j]==1&&tileBoard[i/3][j/3 + 1]==null) + return true; + break; + case 2: + if(intBoard[i][j]== 1&&i/3 == 13) return true; + if(intBoard[i][j]==1&&tileBoard[i/3+1][j/3]==null) + return true; + break; + case 3: + if(intBoard[i][j]== 1&&j/3 == 0) return true; + if(intBoard[i][j]==1&&tileBoard[i/3][j/3-1]==null) + return true; + break; + } + return false; + } + + +} diff --git a/src/student_player/BoardState.java b/src/student_player/BoardState.java new file mode 100644 index 0000000..ef1d8af --- /dev/null +++ b/src/student_player/BoardState.java @@ -0,0 +1,1080 @@ +package student_player; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import Saboteur.SaboteurBoardState; +import Saboteur.SaboteurMove; +import Saboteur.cardClasses.SaboteurBonus; +import Saboteur.cardClasses.SaboteurCard; +import Saboteur.cardClasses.SaboteurDestroy; +import Saboteur.cardClasses.SaboteurDrop; +import Saboteur.cardClasses.SaboteurMalus; +import Saboteur.cardClasses.SaboteurMap; +import Saboteur.cardClasses.SaboteurTile; +import boardgame.Board; + +public class BoardState { + public static final int BOARD_SIZE = 14; + public static final int originPos = 5; + + public static final int EMPTY = -1; + public static final int TUNNEL = 1; + public static final int WALL = 0; + + private static int FIRST_PLAYER = 1; + + public SaboteurTile[][] board; + public int[][] intBoard; + //player variables: + // Note: Player 1 is active when turnplayer is 1; + public ArrayList player1Cards; //hand of player 1 + public ArrayList player2Cards; //hand of player 2 + private int player1nbMalus; + private int player2nbMalus; + public boolean[] player1hiddenRevealed = {false,false,false}; + public boolean[] player2hiddenRevealed = {false,false,false}; + + private ArrayList Deck; //deck form which player pick + public int deckSize; + public static final int[][] hiddenPos = {{originPos+7,originPos-2},{originPos+7,originPos},{originPos+7,originPos+2}}; + protected SaboteurTile[] hiddenCards = new SaboteurTile[3]; + public boolean[] hiddenRevealed = {false,false,false}; //whether hidden at pos1 is revealed, hidden at pos2 is revealed, hidden at pos3 is revealed. + public Map possibleDeckCards; + private boolean existsAMapCard; + private int nuggetIndex; + + + private int turnPlayer; + private int turnNumber; + private int winner; + private Random rand; + + BoardState(int turnNumber, int turnPlayer) { + this.board = new SaboteurTile[BOARD_SIZE][BOARD_SIZE]; + for (int i = 0; i < BOARD_SIZE; i++) { + for (int j = 0; j < BOARD_SIZE; j++) { + this.board[i][j] = null; + } + } + this.intBoard = new int[BOARD_SIZE*3][BOARD_SIZE*3]; + for (int i = 0; i < BOARD_SIZE*3; i++) { + for (int j = 0; j < BOARD_SIZE*3; j++) { + this.intBoard[i][j] = EMPTY; + } + } + // initialize the hidden position: + + //initialize the entrance + this.board[originPos][originPos] = new SaboteurTile("entrance"); + //TODO:initialize the deck. + this.Deck = SaboteurCard.getDeck(); + //shuffle the deck. + //initialize the player effects: + player1nbMalus = 0; + player2nbMalus = 0; + //initialize the players hands: + this.player1Cards = new ArrayList(); + this.player2Cards = new ArrayList(); + rand = new Random(2019); + winner = Board.NOBODY; + this.turnPlayer = turnPlayer; + this.turnNumber = turnNumber; + + //Set current deck's size + this.deckSize = (55 - 14) - this.turnNumber; + this.possibleDeckCards = SaboteurCard.getDeckcomposition(); + } + + /** + * Clone a board + */ + public BoardState(BoardState boardState) { + this.board = new SaboteurTile[BOARD_SIZE][BOARD_SIZE]; + for (int i = 0; i < BOARD_SIZE; i++) { + for (int j = 0; j < BOARD_SIZE; j++) { + this.board[i][j] = null; + } + } + this.possibleDeckCards = SaboteurCard.getDeckcomposition(); + fillTileBoardFromOriginalBoard(boardState.getHiddenBoard()); + this.intBoard = boardState.getIntBoard(); + this.turnNumber = boardState.getTurnNumber(); + this.turnPlayer = boardState.getTurnPlayer(); + winner = Board.NOBODY; + //Set Player Maluses + this.player1nbMalus = boardState.getNbMalus(1); + this.player2nbMalus = boardState.getNbMalus(0); + //Initialize Player Cards + this.player1Cards = new ArrayList(); + this.player2Cards = new ArrayList(); + if(this.turnPlayer==1) { + for(SaboteurCard card: boardState.player1Cards) + this.player1Cards.add(card); + }else { + for(SaboteurCard card: boardState.player2Cards) + this.player2Cards.add(card); + } + //Clone Player Hidden Arrays + updatePlayerHiddenRevealedArray(); + //Set current deck's size + this.deckSize = (55 - 14) - this.turnNumber; + this.possibleDeckCards = new HashMap(); + for(String key: boardState.possibleDeckCards.keySet()) { + int num = boardState.possibleDeckCards.get(key); + this.possibleDeckCards.put(key, num); + } + + } + + /** + * Update this.board from original saboteur board + * @param board + */ + public void fillTileBoardFromOriginalBoard(SaboteurTile[][] board) { + for(int i = 0; i < BOARD_SIZE; i++) { + for(int j = 0; j < BOARD_SIZE; j++) { + this.board[i][j] = board[i][j]; + if(board[i][j]!=null&& !((i==originPos+7 && j == originPos+2)||(i==originPos+7 && j ==originPos)||(i==originPos+7 && j ==originPos-2))) { + String idx = board[i][j].getIdx(); + if(idx.equals("8")||idx.equals("0")||idx.equals("1")||idx.equals("2")||idx.equals("3")||idx.equals("4") + ||idx.equals("5")||idx.equals("6")||idx.equals("7")||idx.equals("9")||idx.equals("10")|| + idx.equals("11")||idx.equals("12")||idx.equals("13")||idx.equals("14")||idx.equals("15")) { + int curNum = this.possibleDeckCards.get(idx); + this.possibleDeckCards.put(idx, curNum-1); + } + + } + } + } + } + + + /** + * Remove last move + */ + public void removeLastMove(SaboteurMove move) { + int x = move.getPosPlayed()[0]; + int y = move.getPosPlayed()[1]; + SaboteurCard card = move.getCardPlayed(); + String idx = card.getName(); + if(idx.charAt(0) == 'T') { + idx = idx.substring(6,idx.length()); + this.board[x][y] = null; + }else if(idx.equals("Malus")) { + if(this.turnPlayer == 0) + this.player2nbMalus--; + else + this.player1nbMalus--; + } + int curNum = this.possibleDeckCards.get(idx); + this.possibleDeckCards.put(idx, curNum+1); + this.turnPlayer = 1 - this.turnPlayer; + this.turnNumber--; + this.deckSize +=1; + this.existsAMapCard = false; + } + + public void updateHiddenRevealed() { + int originPos =5; + ArrayList originTargets = new ArrayList<>(); + originTargets.add(new int[]{originPos*3+1, originPos*3+1}); + originTargets.add(new int[]{originPos*3+1, originPos*3+2}); + originTargets.add(new int[]{originPos*3+1, originPos*3}); + originTargets.add(new int[]{originPos*3, originPos*3+1}); + originTargets.add(new int[]{originPos*3+2, originPos*3+1}); + for(int h = 0;h<3;h++) { + int[] targetPos = new int[] {BoardState.hiddenPos[h][0],BoardState.hiddenPos[h][1]}; + targetPos[0] = targetPos[0]*3+1; + targetPos[1] = targetPos[1]*3+1; + if(cardPath(originTargets,targetPos,false)) { + + hiddenRevealed[h] = true; + } + } + + } + + + /** + * Show if nugget is revealed by map card, if nugget is found then set the nuggetIndex + * @return true if nugget found, false if not + */ + public boolean isNuggetFound() { + //Nugget Revealed if map + if(this.board[originPos+7][originPos-2].getName().contains("nugget")){ + nuggetIndex = 0; + return true; + } + if(this.board[originPos+7][originPos].getName().contains("nugget")) { + nuggetIndex = 1; + return true; + } + if(this.board[originPos+7][originPos+2].getName().contains("nugget")) { + nuggetIndex = 2; + return true; + } + //Nugget revealed by elminating options from playerRevealed + if(this.turnPlayer == 1) { + if(this.player1hiddenRevealed[0] && this.player1hiddenRevealed[1] && !this.player1hiddenRevealed[2]) { + nuggetIndex = 2; + return true; + }else if(this.player1hiddenRevealed[0] && !this.player1hiddenRevealed[1] && this.player1hiddenRevealed[2]) { + nuggetIndex = 1; + return true; + }else if(!this.player1hiddenRevealed[0] && this.player1hiddenRevealed[1] && this.player1hiddenRevealed[2]) { + nuggetIndex = 0; + return true; + }else if(this.player1hiddenRevealed[0] && this.hiddenRevealed[1] && !this.player1hiddenRevealed[2]) { + nuggetIndex = 2; + return true; + }else if(this.hiddenRevealed[0] && this.player1hiddenRevealed[1] && !this.player1hiddenRevealed[2]) { + nuggetIndex = 2; + return true; + }else if(this.hiddenRevealed[0] && this.player1hiddenRevealed[1] && !this.hiddenRevealed[2]) { + nuggetIndex = 2; + return true; + }else if(this.player1hiddenRevealed[0] && !this.player1hiddenRevealed[1] && this.hiddenRevealed[2]) { + nuggetIndex = 1; + return true; + }else if(this.hiddenRevealed[0] && !this.player1hiddenRevealed[1] && this.player1hiddenRevealed[2]) { + nuggetIndex = 1; + return true; + }else if(this.hiddenRevealed[0] && !this.player1hiddenRevealed[1] && this.hiddenRevealed[2]) { + nuggetIndex = 1; + return true; + }else if(!this.player1hiddenRevealed[0] && this.player1hiddenRevealed[1] && this.hiddenRevealed[2]) { + nuggetIndex = 0; + return true; + }else if(!this.player1hiddenRevealed[0] && this.hiddenRevealed[1] && this.player1hiddenRevealed[2]) { + nuggetIndex = 0; + return true; + }else if(!this.player1hiddenRevealed[0] && this.hiddenRevealed[1] && this.hiddenRevealed[2]) { + nuggetIndex = 0; + return true; + } + + }else{ + if(this.player2hiddenRevealed[0] && this.player2hiddenRevealed[1] && !this.player2hiddenRevealed[2]) { + nuggetIndex = 2; + return true; + }else if(this.player2hiddenRevealed[0] && !this.player2hiddenRevealed[1] && this.player2hiddenRevealed[2]) { + nuggetIndex = 1; + return true; + }else if(!this.player2hiddenRevealed[0] && this.player2hiddenRevealed[1] && this.player2hiddenRevealed[2]) { + nuggetIndex = 0; + return true; + }else if(this.player2hiddenRevealed[0] && this.hiddenRevealed[1] && !this.player2hiddenRevealed[2]) { + nuggetIndex = 2; + return true; + }else if(this.hiddenRevealed[0] && this.player2hiddenRevealed[1] && !this.player2hiddenRevealed[2]) { + nuggetIndex = 2; + return true; + }else if(this.hiddenRevealed[0] && this.player2hiddenRevealed[1] && !this.hiddenRevealed[2]) { + nuggetIndex = 2; + return true; + }else if(this.player2hiddenRevealed[0] && !this.player2hiddenRevealed[1] && this.hiddenRevealed[2]) { + nuggetIndex = 1; + return true; + }else if(this.hiddenRevealed[0] && !this.player2hiddenRevealed[1] && this.player2hiddenRevealed[2]) { + nuggetIndex = 1; + return true; + }else if(this.hiddenRevealed[0] && !this.player2hiddenRevealed[1] && this.hiddenRevealed[2]) { + nuggetIndex = 1; + return true; + }else if(!this.player2hiddenRevealed[0] && this.player2hiddenRevealed[1] && this.hiddenRevealed[2]) { + nuggetIndex = 0; + return true; + }else if(!this.player2hiddenRevealed[0] && this.hiddenRevealed[1] && this.player2hiddenRevealed[2]) { + nuggetIndex = 0; + return true; + }else if(!this.player2hiddenRevealed[0] && this.hiddenRevealed[1] && this.hiddenRevealed[2]) { + nuggetIndex = 0; + return true; + } + } + + return false; + } + + + /** + * Show if any player has placed a tile at row > [originPos+3] + * @return true if revealed, false if not + */ + public boolean isRowBelowOriginPosPlus5Revealed() { + for(int j = 0; j < BOARD_SIZE; j++) { + if(this.board[originPos+5][j]!=null) + return true; + } + return false; + } + + + /** + * Update Current Player Cards + * @param playerCards + */ + public void addCurrentPlayerHandCard(ArrayList playerCards) { + if(this.turnPlayer == 1) { + for(SaboteurCard card: playerCards) { + this.player1Cards.add(card); + String idx = card.getName(); + removeFromPossibleDeck(idx); + } + }else { + for(SaboteurCard card: playerCards) { + this.player2Cards.add(card); + String idx = card.getName(); + removeFromPossibleDeck(idx); + } + } + + } + + /** + * Utility function for editing possibleDeckCard + */ + public void removeFromPossibleDeck(String idx) { + if(idx.charAt(0) == 'T') + idx = idx.substring(5,idx.length()); + + if(idx.equals("8")||idx.equals("0")||idx.equals("1")||idx.equals("2")||idx.equals("3")||idx.equals("4") + ||idx.equals("5")||idx.equals("6")||idx.equals("7")||idx.equals("9")||idx.equals("10")|| + idx.equals("11")||idx.equals("12")||idx.equals("13")||idx.equals("14")||idx.equals("15") + ) { + int curNum = this.possibleDeckCards.get(idx); + this.possibleDeckCards.put(idx, curNum-1); + } + if(idx.equals("Destroy")) { + int curNum = this.possibleDeckCards.get("destroy"); + this.possibleDeckCards.put("destroy", curNum-1); + } + if(idx.equals("Malus")) { + int curNum = this.possibleDeckCards.get("malus"); + this.possibleDeckCards.put("malus", curNum-1); + } + if(idx.equals("Bonus")) { + int curNum = this.possibleDeckCards.get("bonus"); + this.possibleDeckCards.put("bonus", curNum-1); + } + if(idx.equals("Map")) { + int curNum = this.possibleDeckCards.get("map"); + this.possibleDeckCards.put("map", curNum-1); + } + } + + /** + * Update playerHiddenReaveled Array + */ + public void updatePlayerHiddenRevealedArray() { + for(int h=0;h<3;h++){ + if(!this.board[hiddenPos[h][0]][hiddenPos[h][1]].getName().contains("8")){ + if(this.turnPlayer == 1) { + this.player1hiddenRevealed[h] = true; + if(board[hiddenPos[h][0]][hiddenPos[h][1]].getName().contains("nugget")) { + this.hiddenCards[h] = new SaboteurTile("nugget"); + }else if(board[hiddenPos[h][0]][hiddenPos[h][1]].getName().contains("hidden1")){ + this.hiddenCards[h] = new SaboteurTile("hidden1"); + }else { + this.hiddenCards[h] = new SaboteurTile("hidden2"); + } + }else { + this.player2hiddenRevealed[h] = true; + if(board[hiddenPos[h][0]][hiddenPos[h][1]].getName().contains("nugget")) { + this.hiddenCards[h] = new SaboteurTile("nugget"); + }else if(board[hiddenPos[h][0]][hiddenPos[h][1]].getName().contains("hidden1")){ + this.hiddenCards[h] = new SaboteurTile("hidden1"); + }else { + this.hiddenCards[h] = new SaboteurTile("hidden2"); + } + } + }else { + this.hiddenCards[h] = new SaboteurTile("hidden"); //Notify pathToHidden this is not revealed yet + } + } + } + + /** + * Set Players' Num of Maluses + * @param numMalus1 + * @param numMalus2 + */ + public void setNbMalus(int numMalus1, int numMalus2) { + this.player1nbMalus = numMalus1; + this.player2nbMalus = numMalus2; + int curNum = this.possibleDeckCards.get("malus"); + this.possibleDeckCards.put("malus", curNum - numMalus1 - numMalus2); + } + + + //Fill the deck + public void randomizeDeck() { + //Infer Cards from board, numOfMalus and studentRecord + + //Naively fill deck with possible deck cards + + //If board tile not equal to empty, remove this from deck + + //infer opponents's hand + + } + + public SaboteurTile[][] getHiddenBoard(){ + // returns the board in SaboteurTile format, where the objectives become the 8 tiles. + // Note the inconsistency with the getHiddenIntBoard where the objectives become only -1 + // this is to stress that hidden cards are considered as empty cards which you can't either destroy or build on before they + // are revealed. + SaboteurTile[][] hiddenboard = new SaboteurTile[BOARD_SIZE][BOARD_SIZE]; + for (int i = 0; i < BOARD_SIZE; i++) { + System.arraycopy(this.board[i], 0, hiddenboard[i], 0, BOARD_SIZE); + } + for(int h=0;h<3;h++){ + if(turnPlayer==1 && !player1hiddenRevealed[h] || turnPlayer==0 && !player2hiddenRevealed[h]){ + hiddenboard[hiddenPos[h][0]][hiddenPos[h][1]] = new SaboteurTile("8"); + } + } + return hiddenboard; + } + + public int[][] getHiddenIntBoard() { + //update the int board, and provide it to the player with the hidden objectives set at EMPTY. + //Note that this function is available to the player. + boolean[] listHiddenRevealed; + if(turnPlayer==1) listHiddenRevealed= player1hiddenRevealed; + else listHiddenRevealed = player2hiddenRevealed; + int[][] intBoard = new int[BOARD_SIZE*3][BOARD_SIZE*3]; + for (int i = 0; i < BOARD_SIZE; i++) { + for (int j = 0; j < BOARD_SIZE; j++) { + if(this.board[i][j] == null){ + for (int k = 0; k < 3; k++) { + for (int h = 0; h < 3; h++) { + intBoard[i * 3 + k][j * 3 + h] = EMPTY; + } + } + } + else { + + int[][] path = this.board[i][j].getPath(); + for (int k = 0; k < 3; k++) { + for (int h = 0; h < 3; h++) { + intBoard[i * 3 + k][j * 3 + h] = path[h][2-k]; + } + } + + } + } + } + + return intBoard; + } + + public void processMove(SaboteurMove m, boolean switchPlayer) throws IllegalArgumentException { + + + // Verify that a move is legal (if not throw an IllegalArgumentException) + // And then execute the move. + // Concerning the map observation, the player then has to check by himself the result of its observation. + //Note: this method is ran in a BoardState ran by the server as well as in a BoardState ran by the player. + if (!isLegal(m)) { +// System.out.println("Found an invalid Move for player " + this.turnPlayer+" of board"+ this.hashCode()); +// ArrayList hand = this.turnPlayer==1? this.player1Cards : this.player2Cards; +// System.out.println("in hand:"); +// for(SaboteurCard card : hand) { +// if (card instanceof SaboteurTile){ +// System.out.println(card.getName()); +// } +// else{ +// System.out.println(card.getName()); +// } +// } +// throw new IllegalArgumentException("Invalid move. Move: " + m.toPrettyString()); + } + + SaboteurCard testCard = m.getCardPlayed(); + int[] pos = m.getPosPlayed(); + + if(testCard instanceof SaboteurTile){ + this.board[pos[0]][pos[1]] = new SaboteurTile(((SaboteurTile) testCard).getIdx()); + if(turnPlayer==1){ + //Remove from the player card the card that was used. + for(SaboteurCard card : this.player1Cards) { + if (card instanceof SaboteurTile) { + if (((SaboteurTile) card).getIdx().equals(((SaboteurTile) testCard).getIdx())) { + this.player1Cards.remove(card); + break; //leave the loop.... + } + else if(((SaboteurTile) card).getFlipped().getIdx().equals(((SaboteurTile) testCard).getIdx())) { + this.player1Cards.remove(card); + break; //leave the loop.... + } + } + } + } + else { + for (SaboteurCard card : this.player2Cards) { + if (card instanceof SaboteurTile) { + if (((SaboteurTile) card).getIdx().equals(((SaboteurTile) testCard).getIdx())) { + this.player2Cards.remove(card); + break; //leave the loop.... + } + else if(((SaboteurTile) card).getFlipped().getIdx().equals(((SaboteurTile) testCard).getIdx())) { + this.player2Cards.remove(card); + break; //leave the loop.... + } + } + } + } + } + else if(testCard instanceof SaboteurBonus){ + if(turnPlayer==1){ + player1nbMalus --; + for(SaboteurCard card : this.player1Cards) { + if (card instanceof SaboteurBonus) { + this.player1Cards.remove(card); + break; //leave the loop.... + } + } + } + else{ + player2nbMalus --; + for(SaboteurCard card : this.player2Cards) { + if (card instanceof SaboteurBonus) { + this.player2Cards.remove(card); + break; //leave the loop.... + } + } + } + } + else if(testCard instanceof SaboteurMalus){ + if(turnPlayer==1){ + player2nbMalus ++; + for(SaboteurCard card : this.player1Cards) { + if (card instanceof SaboteurMalus) { + this.player1Cards.remove(card); + break; //leave the loop.... + } + } + } + else{ + player1nbMalus ++; + for(SaboteurCard card : this.player2Cards) { + if (card instanceof SaboteurMalus) { + this.player2Cards.remove(card); + break; //leave the loop.... + } + } + } + } + else if(testCard instanceof SaboteurMap){ + if(turnPlayer==1){ + for(SaboteurCard card : this.player1Cards) { + if (card instanceof SaboteurMap) { + this.player1Cards.remove(card); + int ph = 0; + for(int j=0;j<3;j++) { + if (pos[0] == hiddenPos[j][0] && pos[1] == hiddenPos[j][1]) ph=j; + } + this.player1hiddenRevealed[ph] = true; + break; //leave the loop.... + } + } + } + else{ + for(SaboteurCard card : this.player2Cards) { + if (card instanceof SaboteurMap) { + this.player2Cards.remove(card); + int ph = 0; + for(int j=0;j<3;j++) { + if (pos[0] == hiddenPos[j][0] && pos[1] == hiddenPos[j][1]) ph=j; + } + this.player2hiddenRevealed[ph] = true; + break; //leave the loop.... + } + } + } + } + else if (testCard instanceof SaboteurDestroy) { + int i = pos[0]; + int j = pos[1]; + if(turnPlayer==1){ + for(SaboteurCard card : this.player1Cards) { + if (card instanceof SaboteurDestroy) { + this.player1Cards.remove(card); + this.board[i][j] = null; + break; //leave the loop.... + } + } + } + else{ + for(SaboteurCard card : this.player2Cards) { + if (card instanceof SaboteurDestroy) { + this.player2Cards.remove(card); + this.board[i][j] = null; + break; //leave the loop.... + } + } + } + } + else if(testCard instanceof SaboteurDrop){ + if(turnPlayer==1) this.player1Cards.remove(pos[0]); + else this.player2Cards.remove(pos[0]); + } + //TODO + this.updateWinner(); + if(switchPlayer) { + this.draw(); + turnPlayer = 1 - turnPlayer; // Swap player + } + turnNumber++; + } + + /** + * TODO:This part needs to be changed greatly to conform to uncertainty of Hidden Cards + */ + private void updateWinner() { + + pathToHidden(new SaboteurTile[]{new SaboteurTile("nugget"),new SaboteurTile("hidden1"),new SaboteurTile("hidden2")}); + int nuggetIdx = -1; + for(int i =0;i<3;i++){ + if(this.hiddenCards[i].getIdx().equals("nugget")){ + nuggetIdx = i; + break; + } + } + if(nuggetIdx != -1) { + boolean playerWin = this.hiddenRevealed[nuggetIdx]; + if (playerWin) { // Current player has won + winner = turnPlayer; + } else if (gameOver() && winner==Board.NOBODY) { + winner = Board.DRAW; + } + }else { + winner = Board.NOBODY; + } + + + } + + public int getTurnPlayer() { + return this.turnPlayer; + } + + public int getNbMalus(int playerNb){ + if(playerNb==1) return this.player1nbMalus; + return this.player2nbMalus; + } + + private void draw(){ + if(this.deckSize>0){ + if(turnPlayer==1){ + this.player1Cards.add(this.Deck.remove(0)); + } + else{ + this.player2Cards.add(this.Deck.remove(0)); + } + this.deckSize--; + } + } + + public int getWinner() { return winner; } + + private int[][] getIntBoard() { + //update the int board. + //Note that this tool is not available to the player. + for (int i = 0; i < BOARD_SIZE; i++) { + for (int j = 0; j < BOARD_SIZE; j++) { + if(this.board[i][j] == null){ + for (int k = 0; k < 3; k++) { + for (int h = 0; h < 3; h++) { + this.intBoard[i * 3 + k][j * 3 + h] = EMPTY; + } + } + } + else { + int[][] path = this.board[i][j].getPath(); + for (int k = 0; k < 3; k++) { + for (int h = 0; h < 3; h++) { + this.intBoard[i * 3 + k][j * 3 + h] = path[h][2-k]; + } + } + } + } + } + + return this.intBoard; } + + + public boolean pathToHidden(SaboteurTile[] objectives){ + /* This function look if a path is linking the starting point to the states among objectives. + :return: if there exists one: true + if not: false + In Addition it changes each reached states hidden variable to true: self.hidden[foundState] <- true + Implementation details: + For each hidden objectives: + We verify there is a path of cards between the start and the hidden objectives. + If there is one, we do the same but with the 0-1s matrix! + + To verify a path, we use a simple search algorithm where we propagate a front of visited neighbor. + TODO To speed up: The neighbor are added ranked on their distance to the origin... (simply use a PriorityQueue with a Comparator) + */ + this.getIntBoard(); //update the int board. + boolean atLeastOnefound = false; + int counter = 0; + for(SaboteurTile target : objectives){ + ArrayList originTargets = new ArrayList<>(); + originTargets.add(new int[]{originPos,originPos}); //the starting points + //get the target position + int[] targetPos = {0,0}; + int currentTargetIdx = -1; + for(int i =0;i<3;i++){ + if(this.hiddenCards[i].getIdx().equals(target.getIdx())){ + targetPos = SaboteurBoardState.hiddenPos[i]; + currentTargetIdx = i; + break; + } + } + + if(currentTargetIdx!=-1 && !this.hiddenRevealed[currentTargetIdx]) { //verify that the current target has not been already discovered. Even if there is a destruction event, the target keeps being revealed! + + if (cardPath(originTargets, targetPos, true)) { //checks that there is a cardPath + //next: checks that there is a path of ones. + ArrayList originTargets2 = new ArrayList<>(); + //the starting points + originTargets2.add(new int[]{originPos*3+1, originPos*3+1}); + originTargets2.add(new int[]{originPos*3+1, originPos*3+2}); + originTargets2.add(new int[]{originPos*3+1, originPos*3}); + originTargets2.add(new int[]{originPos*3, originPos*3+1}); + originTargets2.add(new int[]{originPos*3+2, originPos*3+1}); + //get the target position in 0-1 coordinate + int[] targetPos2 = {targetPos[0]*3+1, targetPos[1]*3+1}; + if (cardPath(originTargets2, targetPos2, false)) { + + this.hiddenRevealed[currentTargetIdx] = true; + this.player1hiddenRevealed[currentTargetIdx] = true; + this.player2hiddenRevealed[currentTargetIdx] = true; + + atLeastOnefound =true; + } + else{ + } + } + } + else{ + atLeastOnefound = true; + } + } + return atLeastOnefound; + } + + public Boolean cardPath(ArrayList originTargets,int[] targetPos,Boolean usingCard){ + // the search algorithm, usingCard indicate weither we search a path of cards (true) or a path of ones (aka tunnel)(false). + ArrayList queue = new ArrayList<>(); //will store the current neighboring tile. Composed of position (int[]). + ArrayList visited = new ArrayList(); //will store the visited tile with an Hash table where the key is the position the board. + visited.add(targetPos); + if(usingCard) addUnvisitedNeighborToQueue(targetPos,queue,visited,BOARD_SIZE,usingCard); + else addUnvisitedNeighborToQueue(targetPos,queue,visited,BOARD_SIZE*3,usingCard); + while(queue.size()>0){ + int[] visitingPos = queue.remove(0); + if(containsIntArray(originTargets,visitingPos)){ + return true; + } + visited.add(visitingPos); + if(usingCard) addUnvisitedNeighborToQueue(visitingPos,queue,visited,BOARD_SIZE,usingCard); + else addUnvisitedNeighborToQueue(visitingPos,queue,visited,BOARD_SIZE*3,usingCard); + } + return false; + } + + private void addUnvisitedNeighborToQueue(int[] pos,ArrayList queue, ArrayList visited,int maxSize,boolean usingCard){ + int[][] moves = {{0, -1},{0, 1},{1, 0},{-1, 0}}; + int i = pos[0]; + int j = pos[1]; + for (int m = 0; m < 4; m++) { + if (0 <= i+moves[m][0] && i+moves[m][0] < maxSize && 0 <= j+moves[m][1] && j+moves[m][1] < maxSize) { //if the hypothetical neighbor is still inside the board + int[] neighborPos = new int[]{i+moves[m][0],j+moves[m][1]}; + if(!containsIntArray(visited,neighborPos)){ + if(usingCard && this.board[neighborPos[0]][neighborPos[1]]!=null) queue.add(neighborPos); + else if(!usingCard && this.intBoard[neighborPos[0]][neighborPos[1]]==1) queue.add(neighborPos); + } + } + } + } + + private boolean containsIntArray(ArrayList a,int[] o){ //the .equals used in Arraylist.contains is not working between arrays.. + if (o == null) { + for (int i = 0; i < a.size(); i++) { + if (a.get(i) == null) + return true; + } + } else { + for (int i = 0; i < a.size(); i++) { + if (Arrays.equals(o, a.get(i))) + return true; + } + } + return false; + } + + public boolean gameOver() { + return this.deckSize ==0 && this.player1Cards.size()==0 && this.player2Cards.size()==0 || winner != Board.NOBODY; + } + + public ArrayList getAllLegalMoves() { + // Given the current player hand, gives back all legal moves he can play. + ArrayList hand; + boolean isBlocked; + if(turnPlayer == 1){ + hand = this.player1Cards; + isBlocked= player1nbMalus > 0; + } + else { + hand = this.player2Cards; + isBlocked= player2nbMalus > 0; + } + + ArrayList legalMoves = new ArrayList<>(); + + for(SaboteurCard card : hand){ + if( card instanceof SaboteurTile && !isBlocked) { + ArrayList allowedPositions = possiblePositions((SaboteurTile)card); + for(int[] pos:allowedPositions){ + legalMoves.add(new SaboteurMove(card,pos[0],pos[1],turnPlayer)); + } + //if the card can be flipped, we also had legal moves where the card is flipped; + if(SaboteurTile.canBeFlipped(((SaboteurTile)card).getIdx())){ + SaboteurTile flippedCard = ((SaboteurTile)card).getFlipped(); + ArrayList allowedPositionsflipped = possiblePositions(flippedCard); + for(int[] pos:allowedPositionsflipped){ + legalMoves.add(new SaboteurMove(flippedCard,pos[0],pos[1],turnPlayer)); + } + } + } + else if(card instanceof SaboteurBonus){ + if(turnPlayer ==1){ + if(player1nbMalus > 0) legalMoves.add(new SaboteurMove(card,0,0,turnPlayer)); + } + else if(player2nbMalus>0) legalMoves.add(new SaboteurMove(card,0,0,turnPlayer)); + } + else if(card instanceof SaboteurMalus){ + legalMoves.add(new SaboteurMove(card,0,0,turnPlayer)); + } + else if(card instanceof SaboteurMap){ + this.existsAMapCard = true; + for(int i =0;i<3;i++){ //for each hidden card that has not be revealed, we can still take a look at it. + if(! this.hiddenRevealed[i]) legalMoves.add(new SaboteurMove(card,hiddenPos[i][0],hiddenPos[i][1],turnPlayer)); + } + } + else if(card instanceof SaboteurDestroy){ + for (int i = 0; i < BOARD_SIZE; i++) { + for (int j = 0; j < BOARD_SIZE; j++) { //we can't destroy an empty tile, the starting, or final tiles. + if(this.board[i][j] != null && (i!=originPos || j!= originPos) && (i != hiddenPos[0][0] || j!=hiddenPos[0][1] ) + && (i != hiddenPos[1][0] || j!=hiddenPos[1][1] ) && (i != hiddenPos[2][0] || j!=hiddenPos[2][1] ) ){ + legalMoves.add(new SaboteurMove(card,i,j,turnPlayer)); + } + } + } + } + } + // we can also drop any of the card in our hand + for(int i=0;i possiblePositions(SaboteurTile card) { + // Given a card, returns all the possiblePositions at which the card could be positioned in an ArrayList of int[]; + // Note that the card will not be flipped in this test, a test for the flipped card should be made by giving to the function the flipped card. + ArrayList possiblePos = new ArrayList(); + int[][] moves = {{0, -1},{0, 1},{1, 0},{-1, 0}}; //to make the test faster, we simply verify around all already placed tiles. + for (int i = 0; i < BOARD_SIZE; i++) { + for (int j = 0; j < BOARD_SIZE; j++) { + if (this.board[i][j] != null) { + for (int m = 0; m < 4; m++) { + if (0 <= i+moves[m][0] && i+moves[m][0] < BOARD_SIZE && 0 <= j+moves[m][1] && j+moves[m][1] < BOARD_SIZE) { + if (this.verifyLegit(card.getPath(), new int[]{i + moves[m][0], j + moves[m][1]} )){ + possiblePos.add(new int[]{i + moves[m][0], j +moves[m][1]}); + } + } + } + } + } + } + return possiblePos; + } + + public boolean isLegal(SaboteurMove m) { + // For a move to be legal, the player must have the card in its hand + // and then the game rules apply. + // Note that we do not test the flipped version. To test it: use the flipped card in the SaboteurMove object. + + SaboteurCard testCard = m.getCardPlayed(); + int[] pos = m.getPosPlayed(); + int currentPlayer = m.getPlayerID(); + if (currentPlayer != turnPlayer) { + return false;} + + ArrayList hand; + boolean isBlocked; + if(turnPlayer == 1){ + hand = this.player1Cards; + isBlocked= player1nbMalus > 0; + } + else { + hand = this.player2Cards; + isBlocked= player2nbMalus > 0; + } + if(testCard instanceof SaboteurDrop){ + if(hand.size()>=pos[0]){ + return true; + } + } + boolean legal = false; + for(SaboteurCard card : hand){ + if (card instanceof SaboteurTile && testCard instanceof SaboteurTile && !isBlocked) { + if(((SaboteurTile) card).getIdx().equals(((SaboteurTile) testCard).getIdx())){ + return verifyLegit(((SaboteurTile) card).getPath(),pos); + } + else if(((SaboteurTile) card).getFlipped().getIdx().equals(((SaboteurTile) testCard).getIdx())){ + return verifyLegit(((SaboteurTile) card).getFlipped().getPath(),pos); + } + } + else if (card instanceof SaboteurBonus && testCard instanceof SaboteurBonus) { + if (turnPlayer == 1) { + if (player1nbMalus > 0) return true; + } else if (player2nbMalus > 0) return true; + return false; + } + else if (card instanceof SaboteurMalus && testCard instanceof SaboteurMalus ) { + return true; + } + else if (card instanceof SaboteurMap && testCard instanceof SaboteurMap) { + int ph = 0; + for(int j=0;j<3;j++) { + if (pos[0] == hiddenPos[j][0] && pos[1] == hiddenPos[j][1]) ph=j; + } + if (!this.hiddenRevealed[ph]) + return true; + } + else if (card instanceof SaboteurDestroy && testCard instanceof SaboteurDestroy) { + int i = pos[0]; + int j = pos[1]; + if (this.board[i][j] != null && (i != originPos || j != originPos) && (i != hiddenPos[0][0] || j != hiddenPos[0][1]) + && (i != hiddenPos[1][0] || j != hiddenPos[1][1]) && (i != hiddenPos[2][0] || j != hiddenPos[2][1])) { + return true; + } + } + } + return legal; + } + + public boolean verifyLegit(int[][] path,int[] pos){ + // Given a tile's path, and a position to put this path, verify that it respects the rule of positionning; + if (!(0 <= pos[0] && pos[0] < BOARD_SIZE && 0 <= pos[1] && pos[1] < BOARD_SIZE)) { + return false; + } + if(board[pos[0]][pos[1]] != null) return false; + + //the following integer are used to make sure that at least one path exists between the possible new tile to be added and existing tiles. + // There are 2 cases: a tile can't be placed near an hidden objective and a tile can't be connected only by a wall to another tile. + int requiredEmptyAround=4; + int numberOfEmptyAround=0; + + ArrayList objHiddenList=new ArrayList<>(); + for(int i=0;i<3;i++) { + if (!hiddenRevealed[i]){ + objHiddenList.add(this.board[hiddenPos[i][0]][hiddenPos[i][1]]); + } + } + //verify left side: + if(pos[1]>0) { + SaboteurTile neighborCard = this.board[pos[0]][pos[1] - 1]; + if (neighborCard == null) numberOfEmptyAround += 1; + else if(objHiddenList.contains(neighborCard)) requiredEmptyAround -= 1; + else { + int[][] neighborPath = neighborCard.getPath(); + if (path[0][0] != neighborPath[2][0] || path[0][1] != neighborPath[2][1] || path[0][2] != neighborPath[2][2] ) return false; + else if(path[0][0] == 0 && path[0][1]== 0 && path[0][2] ==0 ) numberOfEmptyAround +=1; + } + } + else numberOfEmptyAround+=1; + + //verify right side + if(pos[1]0) { + SaboteurTile neighborCard = this.board[pos[0]-1][pos[1]]; + if (neighborCard == null) numberOfEmptyAround += 1; + else if(objHiddenList.contains(neighborCard)) requiredEmptyAround -= 1; + else { + int[][] neighborPath = neighborCard.getPath(); + int[] p={path[0][2],path[1][2],path[2][2]}; + int[] np={neighborPath[0][0],neighborPath[1][0],neighborPath[2][0]}; + if (p[0] != np[0] || p[1] != np[1] || p[2] != np[2]) return false; + else if(p[0] == 0 && p[1]== 0 && p[2] ==0 ) numberOfEmptyAround +=1; + } + } + else numberOfEmptyAround+=1; + + //verify bottom side: + if(pos[0] getCurrentPlayerCards(){ + if(turnPlayer==1){ + ArrayList p1Cards = new ArrayList(); + for(int i=0;i p2Cards = new ArrayList(); + for(int i=0;i children; + public AndNode parent; + public ORNode(String name, BoardState boardState,SaboteurMove move) { + super(name,boardState); + this.move = move; + this.children = new ArrayList<>(); + } + + public double getExpectedMinHeuistic(int[] goalPos, int depth, int maxDepth) { + calculateHeuristic2(goalPos); + if(depth == maxDepth || heuristicVal == Integer.MIN_VALUE) { + return this.heuristicVal; + }else { + //Get all possible dealed cards + + int totalPossibleCardsSize = 0; + Set possibleCardNames = boardState.possibleDeckCards.keySet(); + for(String cardName:possibleCardNames) { + totalPossibleCardsSize += boardState.possibleDeckCards.get(cardName); + } + + //Create every possible AndNode and add it to list + int counter = 0; + for(String cardName:possibleCardNames) { + SaboteurCard card = null; + BoardState newBoard = new BoardState(boardState); + int size = newBoard.possibleDeckCards.get(cardName); + if(size>0) { + if(isATileCard(cardName)) { + card = new SaboteurTile(cardName); + }else if(cardName.equals("destroy")) { + card = new SaboteurDestroy(); + }else if(cardName.equals("malus")) { + card = new SaboteurMalus(); + }else if(cardName.equals("bonus")) { + card = new SaboteurBonus(); + }else if(cardName.equals("map")) { + card = new SaboteurMap(); + } + newBoard.possibleDeckCards.put(cardName, size-1); + //Calculate probabilities + double prob = ((double)size)/(double)totalPossibleCardsSize; + String name = "AND,"+depth+","+counter; + if(card != null) { + AndNode node = new AndNode(name,newBoard,card,prob); + node.parent = this; + node.depth = depth; + this.children.add(node); + } + counter++; + } + + } + + //Iterate through all children AndNodes, calculate p(AndNode) * getMinVal(AndNode) to get an expected value + double expectedValue = 0; + for(AndNode child: children) { + expectedValue += child.prob * child.getMinHeuristicVal(goalPos, depth, maxDepth); + } + return expectedValue; + } + + } + + public boolean isATileCard(String cardName) { + String[] tiles =new String[]{"0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15"}; + for(int i=0;i originTargets = new ArrayList<>(); + originTargets.add(new int[]{originPos*3+1, originPos*3+1}); + originTargets.add(new int[]{originPos*3+1, originPos*3+2}); + originTargets.add(new int[]{originPos*3+1, originPos*3}); + originTargets.add(new int[]{originPos*3, originPos*3+1}); + originTargets.add(new int[]{originPos*3+2, originPos*3+1}); + + int[] goalPosInInt = new int[2]; + goalPosInInt[0] = goalPosX*3+1; + goalPosInInt[1] = goalPosY*3+1; + + if(boardState.cardPath(originTargets,goalPosInInt,false)){ + + this.heuristicVal = Integer.MIN_VALUE; + return; + } + double minDist = Integer.MAX_VALUE; + ArrayList openEndPos = new ArrayList<>(); + double h1,h2,h3,h4; + int numOfGoodTilesAboveRow5=0; + for(int i = 0 ; i < BoardState.BOARD_SIZE;i++) + for(int j = 0; j < BoardState.BOARD_SIZE; j++) { + if(boardState.board[i][j]!=null) { + //Increment Good Tiles Num + if(i<5&&isGoodTile(boardState.board[i][j])) { + numOfGoodTilesAboveRow5++; + } + + int[] currentMiddlePoint1 = {3*i+2,3*j+1}; // row 3 block 2 + int[] currentMiddlePoint2 = {3*i+1,3*j}; //row 2 block 1 + int[] currentMiddlePoint3 = {3*i+1,3*j+2}; //row 2 block 3 + int[] currentMiddlePoint4 = {3*i+2,3*j+1}; + + //If there's CardPath from entrance to current position, update closest distance and open end list + if(boardState.cardPath(originTargets,currentMiddlePoint1,false) + &&(checkOpenEnd(boardState.intBoard,boardState.board,currentMiddlePoint1[0],currentMiddlePoint1[1],2) + ||checkOpenEnd(boardState.intBoard,boardState.board,currentMiddlePoint2[0],currentMiddlePoint2[1],1) + ||checkOpenEnd(boardState.intBoard,boardState.board,currentMiddlePoint3[0],currentMiddlePoint3[1],3))) { + + double curDist = W1*Math.abs(i-goalPosX)+W5_1*Math.abs(j-goalPosY); + if(boardState.intBoard[currentMiddlePoint1[0]][currentMiddlePoint1[1]]==1) { + curDist -= 300; + } + //Add Open Ends To Open End Lists + int[][] moves = {{0, -1},{0, 1},{1, 0},{-1, 0}}; + for(int m= 0;m<4;m++) { + int neighbourX = (3 * i + 1) + moves[m][0]; + int neighbourY = (3 * j + 1) + moves[m][1]; + if(0 <= neighbourX && neighbourX < BoardState.BOARD_SIZE*3 + && 0 <= neighbourY && neighbourY < BoardState.BOARD_SIZE*3 + && 0 <= i+moves[m][0] && i+moves[m][0] < BoardState.BOARD_SIZE + && 0 <= j+moves[m][1] && j+moves[m][1] < BoardState.BOARD_SIZE) { + if(boardState.intBoard[neighbourX][neighbourY]==1&&boardState.board[i+moves[m][0]][j+moves[m][1]]==null) { + openEndPos.add(new int[]{neighbourX,neighbourY}); + } + } + } + + if(minDist > curDist) { + minDist = curDist; + + } + } + } + + } + + h1 = minDist; + h2 = openEndPos.size(); + h3 = numOfGoodTilesAboveRow5; + this.heuristicVal = h1 + W2 * h2 + W3 * cursedMaluses + W4 * numOfGoodTilesAboveRow5 + W6 * selfMaluses ; + + } + + public void calculateHeuristic2(int[] goalPos) { + int goalPosX = goalPos[0]; + int goalPosY = goalPos[1]; + int originPos =5; + int turnPlayer = this.boardState.getTurnPlayer(); + int opponentMaluses; + int selfMaluses = 0; + int cursedMaluses = 0; + if(turnPlayer == 1) { + opponentMaluses = this.boardState.getNbMalus(0); + cursedMaluses = this.boardState.getNbMalus(1);; + }else { + opponentMaluses = this.boardState.getNbMalus(1); + cursedMaluses = this.boardState.getNbMalus(0);; + } + if(turnPlayer == 1) { + selfMaluses = this.boardState.getNbMalus(0); + }else { + selfMaluses = this.boardState.getNbMalus(1); + } + ArrayList originTargets = new ArrayList<>(); + originTargets.add(new int[]{originPos*3+1, originPos*3+1}); + originTargets.add(new int[]{originPos*3+1, originPos*3+2}); + originTargets.add(new int[]{originPos*3+1, originPos*3}); + originTargets.add(new int[]{originPos*3, originPos*3+1}); + originTargets.add(new int[]{originPos*3+2, originPos*3+1}); + + int[] goalPosInInt = new int[2]; + goalPosInInt[0] = goalPosX*3+1; + goalPosInInt[1] = goalPosY*3+1; + + if(boardState.cardPath(originTargets,goalPosInInt,false)){ + + this.heuristicVal = Integer.MIN_VALUE; + return; + } + double minDist = Integer.MAX_VALUE; + ArrayList openEndPos = new ArrayList<>(); + double h1,h2,h3,h4; + int numOfGoodTilesAboveRow5=0; + for(int i = 0 ; i < BoardState.BOARD_SIZE;i++) + for(int j = 0; j < BoardState.BOARD_SIZE; j++) { + if(boardState.board[i][j]!=null) { + //Increment Good Tiles Num + if(i<5&&isGoodTile(boardState.board[i][j])) { + numOfGoodTilesAboveRow5++; + } + + + int[] currentMiddlePoint1 = {3*i+2,3*j+1}; // row 3 block 2 + int[] currentMiddlePoint2 = {3*i+1,3*j}; //row 2 block 1 + int[] currentMiddlePoint3 = {3*i+1,3*j+2}; //row 2 block 3 + int[] currentMiddlePoint4 = {3*i+2,3*j+1}; + + + //If there's CardPath from entrance to current position, update closest distance and open end list + if(boardState.cardPath(originTargets,currentMiddlePoint1,false) + &&(checkOpenEnd(boardState.intBoard,boardState.board,currentMiddlePoint1[0],currentMiddlePoint1[1],2) + ||checkOpenEnd(boardState.intBoard,boardState.board,currentMiddlePoint2[0],currentMiddlePoint2[1],1) + ||checkOpenEnd(boardState.intBoard,boardState.board,currentMiddlePoint3[0],currentMiddlePoint3[1],3))) { + + double curDist = W1*Math.abs(i-goalPosX)+W5_2*Math.abs(j-goalPosY); + if(boardState.intBoard[currentMiddlePoint1[0]][currentMiddlePoint1[1]]==1) { + curDist -= 1800; + } + if(goalPosX < j && checkOpenEnd(boardState.intBoard,boardState.board,currentMiddlePoint2[0],currentMiddlePoint2[1],1)){ + curDist -= 500; + } + //Add Open Ends To Open End Lists + int[][] moves = {{0, -1},{0, 1},{1, 0},{-1, 0}}; + for(int m= 0;m<4;m++) { + int neighbourX = (3 * i + 1) + moves[m][0]; + int neighbourY = (3 * j + 1) + moves[m][1]; + if(0 <= neighbourX && neighbourX < BoardState.BOARD_SIZE*3 + && 0 <= neighbourY && neighbourY < BoardState.BOARD_SIZE*3 + && 0 <= i+moves[m][0] && i+moves[m][0] < BoardState.BOARD_SIZE + && 0 <= j+moves[m][1] && j+moves[m][1] < BoardState.BOARD_SIZE) { + if(boardState.intBoard[neighbourX][neighbourY]==1&&boardState.board[i+moves[m][0]][j+moves[m][1]]==null) { + openEndPos.add(new int[]{neighbourX,neighbourY}); + } + } + } + + if(minDist > curDist) { + minDist = curDist; + + } + } + } + + } + + h1 = minDist; + h2 = openEndPos.size(); + h3 = numOfGoodTilesAboveRow5; + this.heuristicVal = h1 + W2 * h2 + + W3 * cursedMaluses + W4 * numOfGoodTilesAboveRow5 + W6 * opponentMaluses ; + + } + + /* + * Return true if tile idx is one of the following tiles + */ + public boolean isGoodTile(SaboteurTile tile) { + if(tile.getIdx().equals("0")||tile.getIdx().equals("5_flip")||tile.getIdx().equals("5")||tile.getIdx().equals("6")|| + tile.getIdx().equals("6_flip")||tile.getIdx().equals("7_flip")||tile.getIdx().equals("7") + ||tile.getIdx().equals("8")||tile.getIdx().equals("9") + ||tile.getIdx().equals("9_flip")||tile.getIdx().equals("10")) { + return true; + } + return false; + } + +} diff --git a/src/student_player/StudentPlayer.java b/src/student_player/StudentPlayer.java index 8ab4a16..a37f8f0 100644 --- a/src/student_player/StudentPlayer.java +++ b/src/student_player/StudentPlayer.java @@ -3,7 +3,18 @@ import boardgame.Move; import Saboteur.SaboteurPlayer; +import Saboteur.cardClasses.SaboteurCard; +import Saboteur.cardClasses.SaboteurMap; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.lang.*; import Saboteur.SaboteurBoardState; +import Saboteur.SaboteurMove; + /** A player file submitted by a student. */ public class StudentPlayer extends SaboteurPlayer { @@ -14,9 +25,20 @@ public class StudentPlayer extends SaboteurPlayer { * associate you with your agent. The constructor should do nothing else. */ public StudentPlayer() { - super("xxxxxxxxx"); + super("260795889"); } - + + private BoardState currentBoardState; //Current Board State + private BoardState lastBoardState; //Record Last Board State so that we know what's opponent's move + private ArrayList playerCards; //hand of player this turn + private ArrayList possibleLastPlayedCardByOpponent; + private Map playedCardstillLastTurn; + private int playerNb; + enum GameState { + Opening, + End + } + private GameState gameState; /** * This is the primary method that you need to implement. The ``boardState`` * object contains the current state of the game, which your agent must use to @@ -26,12 +48,288 @@ public Move chooseMove(SaboteurBoardState boardState) { // You probably will make separate functions in MyTools. // For example, maybe you'll need to load some pre-processed best opening // strategies... - MyTools.getSomething(); + //MyTools.getSomething(); // Is random the best you can do? - Move myMove = boardState.getRandomMove(); - - // Return your move to be processed by the server. - return myMove; + //Move myMove = boardState.getRandomMove(); + double timeStart = System.currentTimeMillis(); + //Initialize In First Turn... + if(boardState.getTurnNumber() < 2) { + this.playerNb = boardState.getTurnPlayer(); + this.initializeInFirstTurn(this.playerNb == boardState.firstPlayer()); + this.gameState = GameState.Opening; + } + + initializeBoardState(boardState); + ArrayList list = boardState.getAllLegalMoves(); + if(gameState != GameState.End && currentBoardState.isRowBelowOriginPosPlus5Revealed()) { + gameState = GameState.End; + } + if(gameState == GameState.Opening) { + //If not nugget found and there's map card + ArrayList mapmoves = getAllMapMoves(list); + if(!currentBoardState.isNuggetFound() && mapmoves.size()>0) { + //If left goal tile not revealed, then Reveal Left GoalTile + //If left goal tile is already revealed, then reveal middle goalTile + return selectMapMove(mapmoves); + }else { + int[] goalTilePos = new int[2]; + if(currentBoardState.isNuggetFound()) { + int goalIndex = currentBoardState.getNuggetIndex(); + goalTilePos[0] = BoardState.hiddenPos[goalIndex][0]; + goalTilePos[1] = BoardState.hiddenPos[goalIndex][1]; + }else { + if(!currentBoardState.hiddenRevealed[1]) { + goalTilePos[0] = BoardState.hiddenPos[1][0]; + goalTilePos[1] = BoardState.hiddenPos[1][1]; + }else{ + //If middle goal tile is revealed and nuggetFound false, then both left or right goal tile + //are not revealed. In this case, randomly set goal as left tile. + goalTilePos[0] = BoardState.hiddenPos[0][0]; + goalTilePos[1] = BoardState.hiddenPos[0][1]; + } + + } + int level = 0; + int counter = 0; + double bestVal = Integer.MAX_VALUE; + int bestValIndex = -1; + + for(SaboteurMove move: list) { + + //Clone the resulting board + BoardState curBoard = new BoardState(currentBoardState); + + //Process Each Move + curBoard.processMove(move, false); + + curBoard.intBoard = curBoard.getHiddenIntBoard(); + //Create a OR node + String nodeName = "OR,"+level+","+counter; + ORNode node = new ORNode(nodeName,curBoard,move); + + //Calculate Heuristic + node.calculateHeuristic(goalTilePos); + if(node.heuristicVal mapmoves = getAllMapMoves(list); + if(!currentBoardState.isNuggetFound() && mapmoves.size()>0) { + //If left goal tile not revealed, then Reveal Left GoalTile + //If left goal tile is already revealed, then reveal middle goalTile + return selectMapMove(mapmoves); + }else { + int[] goalTilePos = new int[2]; + if(currentBoardState.isNuggetFound()) { + int goalIndex = currentBoardState.getNuggetIndex(); + goalTilePos[0] = BoardState.hiddenPos[goalIndex][0]; + goalTilePos[1] = BoardState.hiddenPos[goalIndex][1]; + }else { + if(!currentBoardState.hiddenRevealed[1]) { + goalTilePos[0] = BoardState.hiddenPos[1][0]; + goalTilePos[1] = BoardState.hiddenPos[1][1]; + }else{ + //If middle goal tile is revealed and nuggetFound false, then both left or right goal tile + //are not revealed. In this case, randomly set goal as left tile. + goalTilePos[0] = BoardState.hiddenPos[0][0]; + goalTilePos[1] = BoardState.hiddenPos[0][1]; + } + + } + int level = 0; + int counter = 0; + double bestVal = Integer.MAX_VALUE; + int bestValIndex = -1; + + ArrayList nodeList = new ArrayList<>(); + for(SaboteurMove move: list) { + + //Clone the resulting board + BoardState curBoard = new BoardState(currentBoardState); + + //Process Each Move + curBoard.processMove(move, false); + + curBoard.intBoard = curBoard.getHiddenIntBoard(); + //Create a OR node + + String nodeName = "OR,"+level+","+counter; + ORNode node = new ORNode(nodeName,curBoard,move); + nodeList.add(node); + //Calculate Heuristic + node.calculateHeuristic2(goalTilePos); + double curVal = node.heuristicVal; + if(curVal < bestVal) { + bestVal = curVal; + bestValIndex = counter; + } + counter++; + } + if(bestVal == Integer.MIN_VALUE) { + return list.get(bestValIndex); + } + Collections.sort(nodeList, new SortbyHeuristicVal()); + bestVal = Integer.MAX_VALUE; + bestValIndex = -1; + counter = 0; + for(ORNode node: nodeList) { + double timeCurrent = System.currentTimeMillis(); + if(timeCurrent - timeStart >1850) { + return nodeList.get(bestValIndex).move; + } + if(counter > 8) break; + + //Calculate Heuristic + double curVal = node.getExpectedMinHeuistic(goalTilePos, 0, 1); + if(curVal < bestVal) { + bestVal = curVal; + bestValIndex = counter; + } + counter++; + } + + + return nodeList.get(bestValIndex).move; + } + } + + +// int alpha = Integer.MIN_VALUE; +// int beta = Integer.MAX_VALUE; +// SaboteurMove finalmove = list.get(0); +// +// for(SaboteurMove move : list) { +// currentBoardState.processMove(move); +// +// int value = MyTools.alpha_beta_pruning(alpha,beta,1,currentBoardState); +// //TODO: Clear last move +// +// if(value > alpha) { +// alpha = value; +// finalmove = move; +// } +// +// if(beta <= alpha) { +// break; +// } +// } +// +// Move myMove = finalmove; +// //Store current board +// lastBoardState = currentBoardState; +// +// // Return your move to be processed by the server. +// return myMove; + } + + public ArrayList getAllMapMoves(ArrayList list) { + ArrayList mapList = new ArrayList<>(); + for(SaboteurMove move: list) { + SaboteurCard card = move.getCardPlayed(); + if(card instanceof SaboteurMap) { + mapList.add(move); + } + } + return mapList; + + } + + public SaboteurMove selectMapMove(ArrayList list) { + int minPosY = 10; + int minIndex = -1; + int counter= 0; + if(list.size() == 1) { + return list.get(0); + }else { + for(SaboteurMove move:list) { + int pos[] = move.getPosPlayed(); + int index = (pos[1]-3)/2; + boolean revealed = (this.playerNb == 1)?currentBoardState.player1hiddenRevealed[index] + :currentBoardState.player2hiddenRevealed[index]; + if(pos[1]< minPosY && !revealed) { + minPosY = pos[1]; + minIndex = counter; + } + counter++; + } + } + return list.get(minIndex); + } + + public void initializeInFirstTurn(boolean isFirstPlayer) { + this.playedCardstillLastTurn = SaboteurCard.getDeckcomposition(); + playedCardstillLastTurn.put("0",0); + playedCardstillLastTurn.put("1",0); + playedCardstillLastTurn.put("2",0); + playedCardstillLastTurn.put("3",0); + playedCardstillLastTurn.put("4",0); + playedCardstillLastTurn.put("5",0); + playedCardstillLastTurn.put("6",0); + playedCardstillLastTurn.put("7",0); + playedCardstillLastTurn.put("8",0); + playedCardstillLastTurn.put("9",0); + playedCardstillLastTurn.put("10",0); + playedCardstillLastTurn.put("11",0); + playedCardstillLastTurn.put("12",0); + playedCardstillLastTurn.put("13",0); + playedCardstillLastTurn.put("14",0); + playedCardstillLastTurn.put("15",0); + playedCardstillLastTurn.put("destroy",0); + playedCardstillLastTurn.put("malus",0); + playedCardstillLastTurn.put("bonus",0); + playedCardstillLastTurn.put("map",0); + if(!isFirstPlayer) { + String name = ""; + if(this.currentBoardState.board[5][6]!=null) { + name = this.currentBoardState.board[5][6].getName(); + playedCardstillLastTurn.put(name, 1); + }else if(this.currentBoardState.board[5][4]!=null) { + name = this.currentBoardState.board[5][4].getName(); + playedCardstillLastTurn.put(name, 1); + }else if(this.currentBoardState.board[4][5]!=null) { + name = this.currentBoardState.board[4][5].getName(); + playedCardstillLastTurn.put(name, 1); + }else if(this.currentBoardState.board[6][5]!=null) { + name = this.currentBoardState.board[6][5].getName(); + playedCardstillLastTurn.put(name, 1); + }else if(this.currentBoardState.getNbMalus(1-playerNb) == 1) { + playedCardstillLastTurn.put("malus", 1); + } + } + } + + //Not sure if this is necessary since we already added it to current board state... + public void initializePlayerCards(SaboteurBoardState boardState) { + playerCards = (ArrayList)boardState.getCurrentPlayerCards().clone(); + } + + public void initializeBoardState(SaboteurBoardState boardState) { + int turnPlayer = boardState.getTurnPlayer(); + int turnNumber = boardState.getTurnNumber(); + this.currentBoardState = new BoardState(turnNumber,turnPlayer); + this.currentBoardState.fillTileBoardFromOriginalBoard(boardState.getHiddenBoard()); + this.currentBoardState.intBoard = this.currentBoardState.getHiddenIntBoard(); + this.currentBoardState.setNbMalus(boardState.getNbMalus(1), boardState.getNbMalus(0)); + this.currentBoardState.addCurrentPlayerHandCard(boardState.getCurrentPlayerCards()); + this.currentBoardState.updatePlayerHiddenRevealedArray(); + this.currentBoardState.updateHiddenRevealed(); + } + class SortbyHeuristicVal implements Comparator + { + // Used for sorting in ascending order of + // heuristicval + public int compare(ORNode a, ORNode b) + { + return (int)(a.heuristicVal - b.heuristicVal); + } + } + } \ No newline at end of file