From 3f84efcafbfd74f9d6c7e8b4a8a124340bec2ce4 Mon Sep 17 00:00:00 2001 From: ZeroLifeCat Date: Thu, 9 Apr 2020 03:02:04 +0800 Subject: [PATCH 01/18] Created Board State Class --- src/Saboteur/SaboteurBoardState.java | 1 + src/Saboteur/cardClasses/SaboteurTile.java | 3 +- src/student_player/BoardState.java | 707 +++++++++++++++++++++ src/student_player/MyTools.java | 270 ++++++++ src/student_player/PathTree.java | 91 +++ src/student_player/StudentPlayer.java | 49 +- src/student_player/TileNode.java | 33 + 7 files changed, 1148 insertions(+), 6 deletions(-) create mode 100644 src/student_player/BoardState.java create mode 100644 src/student_player/PathTree.java create mode 100644 src/student_player/TileNode.java diff --git a/src/Saboteur/SaboteurBoardState.java b/src/Saboteur/SaboteurBoardState.java index 3024e23..921b8fd 100644 --- a/src/Saboteur/SaboteurBoardState.java +++ b/src/Saboteur/SaboteurBoardState.java @@ -319,6 +319,7 @@ public int[][] getHiddenIntBoard() { } return this.intBoard; } + 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 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/student_player/BoardState.java b/src/student_player/BoardState.java new file mode 100644 index 0000000..1f20197 --- /dev/null +++ b/src/student_player/BoardState.java @@ -0,0 +1,707 @@ +package student_player; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +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; + + private SaboteurTile[][] board; + private int[][] intBoard; + //player variables: + // Note: Player 1 is active when turnplayer is 1; + private ArrayList player1Cards; //hand of player 1 + private ArrayList player2Cards; //hand of player 2 + private int player1nbMalus; + private int player2nbMalus; + private boolean[] player1hiddenRevealed = {false,false,false}; + private 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. + + + 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"); + //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 = 52 - this.turnNumber; + } + + /** + * 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]; + } + } + + /** + * Update Current Player Cards + * @param playerCards + */ + public void addCurrentPlayerHandCard(ArrayList playerCards) { + if(this.turnPlayer == 1) { + for(SaboteurCard card: playerCards) { + this.player1Cards.add(card); + } + }else { + for(SaboteurCard card: playerCards) { + this.player2Cards.add(card); + } + } + + } + + /** + * Update hiddenReaveled Array + */ + public void updateHiddenRevealedArray() { + for(int h=0;h<3;h++){ + if( this.board[hiddenPos[h][0]][hiddenPos[h][1]] != new SaboteurTile("8")){ + this.hiddenRevealed[h] = true; + } + } + } + + /** + * Set Players' Num of Maluses + * @param numMalus1 + * @param numMalus2 + */ + public void setNbMalus(int numMalus1, int numMalus2) { + this.player1nbMalus = numMalus1; + this.player2nbMalus = numMalus2; + } + + public void randomizeDeck() { + //Infer played Cards from board, numOfMalus and studentPlayer's compare method + + //Naively fill deck + + //infer opponents's hand + + } + + public void processMove(SaboteurMove m) throws IllegalArgumentException { +// +// if(m.getFromBoard()){ +// this.initializeFromStringForInitialCopy(m.getBoardInit()); +// System.out.println("inititalized"+this.hashCode()); +// turnNumber++; +// return; +// } + + // 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.draw(); + this.updateWinner(); + 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; + } + } + boolean playerWin = this.hiddenRevealed[nuggetIdx]; + if (playerWin) { // Current player has won + winner = turnPlayer; + } else if (gameOver() && winner==Board.NOBODY) { + winner = Board.DRAW; + } + + } + + 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; } + private 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; + 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(!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; + } + + private 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){ + 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] list = state.getAllLegalMoves(); + for(SaboteurMove move : list) { + SaboteurBoardState clone = (SaboteurBoardState) state.clone(); + clone.processMove(move); + + int value = alpha_beta_pruning(alpha, beta, depth+1, clone); + + if(alpha>= beta) { + return depth%2 ==0? alpha: beta; + } + + if(depth%2 ==0) { + if(value < val) { + val = value; + } + if(beta > val) { + beta = val; + } + } + else { + if(value > val) { + val = value; + } + if(alpha < val) { + alpha = val; + } + } + + if(alpha >= beta) { + return depth%2 ==0? alpha:beta; + } + } + + return val; + } + + + //TODO: Observe the board state and calculate the heuristic value. + public static int GetHeuristic(SaboteurBoardState state) { + + int score = 0; + + int origin = 5; + int goal = 12; + int opponent = state.getTurnPlayer(); + int self = 1-opponent; + SaboteurTile[][] board = state.getBoardForDisplay(); + + //check winner state + if(state.getWinner() == opponent) { + return -100000; + } + if(state.getWinner() == self) { + return 100000; + } + + //Malus state + int nbmalusself = state.getNbMalus(self); + int nbmalusoppo = state.getNbMalus(opponent); + if(nbmalusself > 0) { + score -= 50; + } + if(nbmalusoppo > 0) { + score += 50; + } + + //Hidden Tile + boolean GoalHidden = true; + int[] nugget = new int[2]; + if(board[goal][origin].getIdx().equals("nugget")) { + nugget[0] = goal; + nugget[1] = origin; + GoalHidden = false; + } + else if(board[goal][origin-2].getIdx().equals("nugget")) { + nugget[0] = goal; + nugget[1] = origin-2; + GoalHidden = false; + } + else if(board[goal][origin+2].getIdx().equals("nugget")) { + nugget[0] = goal; + nugget[1] = origin+2; + GoalHidden = false; + } + + int[][] tilemap = new int[board.length][board[0].length]; + for(int i = 0;i leaves = pathTree.leaves; + //two mode + if(GoalHidden) { + int mindist = 100; + int mindistleft = 100; + int mindistright = 100; + for(TileNode leaf : leaves) { + int dist = checkDist(leaf.x(),leaf.y(),5, 12); + int dist2 = checkDist(leaf.x(),leaf.y(),3,12); + int dist3 = checkDist(leaf.x(),leaf.y(),7, 12); + boolean isDeadEnd = checkDeadEnd(leaf.tile); + if(dist < mindist && !isDeadEnd) { + mindist = dist; + } + if(dist2 < mindistleft && !isDeadEnd) { + mindistleft = dist2; + } + if(dist3 < mindistright && !isDeadEnd) { + mindistright = dist3; + } + } + score += 3/(mindist+mindistleft+mindistright); + } + else { + int mindist = 100; + for(TileNode leaf : leaves) { + int dist = checkDist(leaf.x(),leaf.y(),nugget[0], nugget[1]); + if(dist < mindist && !checkDeadEnd(leaf.tile)) { + mindist = dist; + } + } + score += 1/mindist; + } + + return score; + } + + public static int checkDist(int i, int j, int i2, int j2) { + return Math.abs(i-i2) + Math.abs(j-j2); + } + + public static boolean checkDeadEnd(SaboteurTile tile) { + String idx = tile.getIdx(); + if(idx.equals("1")||idx.equals("2")||idx.equals("2_flip")||idx.equals("3")||idx.equals("3_flip")|| + idx.equals("4")||idx.equals("4_flip")||idx.equals("11")||idx.equals("11_flip")|| + idx.equals("12")||idx.equals("12_flip")||idx.equals("13")||idx.equals("14")||idx.equals("14_flip")) { + return true; + } + return false; + } + + /*public class TileNode{ + public SaboteurTile tile; + public int[] position = new int[2]; + public int depth; + public ArrayList children = new ArrayList(); + + public TileNode(SaboteurTile t, int i, int j, int d){ + tile = t; + position[0] = i; + position[1] = j; + depth = d; + } + + public void Addchild(TileNode t) { + children.add(t); + } + + public int x() { + return position[0]; + } + + public int y() { + return position[1]; + } + + } + + public class PathTree{ + public TileNode root; + public ArrayList leaves = new ArrayList(); + public int length; + + //Init + public PathTree(SaboteurTile[][] board, int[][] map) { + int depth = 1; + //root = origin + root = new TileNode(board[5][5],5,5,0); + //visited map + boolean[][] visited = new boolean[board.length][board[0].length]; + visited[5][5] = true; + + //parents list + ArrayList parents = new ArrayList(); + parents.add(root); + + while(parents.size() != 0) { + //children list + ArrayList children = new ArrayList(); + + for(TileNode p : parents) { + //if p is a dead end, then there is no child + if(checkDeadEnd(p.tile)) { + leaves.add(p); + } + + else { + int[][] neighbors = {{p.x()+1,p.y()},{p.x()-1,p.y()},{p.x(),p.y()+1},{p.x(),p.y()-1}}; + int nbChild = 0; + //check children + for(int[] nei : neighbors) { + //check if the position exist on the board and either there is a tile(not a null object) + if(nei[0] < 14 && nei[0] > 0 && nei[1] < 14 && nei[1] > 0) { + if(map[nei[0]][nei[1]] == 1) { + SaboteurTile neighbor = board[nei[0]][nei[1]]; + + //check if connected + boolean connected = false; + if(nei[0]-p.x() == p.tile.getPath()[1][2]) connected = true; + else if(nei[0]-p.x() == -p.tile.getPath()[1][0]) connected = true; + else if(nei[1]-p.y() == -p.tile.getPath()[0][1]) connected = true; + else if(nei[1]-p.y() == p.tile.getPath()[2][1]) connected = true; + + //if the node is connected and not visited + if(connected && !visited[nei[0]][nei[1]]) { + TileNode child = new TileNode(neighbor, nei[0], nei[1], depth); + p.Addchild(child); + children.add(child); + visited[nei[0]][nei[1]] = true; + nbChild++; + } + } + } + } + + //no child , becomes a leaf + if(nbChild == 0) { + leaves.add(p); + } + } + } + + //children become next parents + parents = children; + depth++; + } + + length = depth; + } + + }*/ } \ No newline at end of file diff --git a/src/student_player/PathTree.java b/src/student_player/PathTree.java new file mode 100644 index 0000000..6cf581b --- /dev/null +++ b/src/student_player/PathTree.java @@ -0,0 +1,91 @@ +package student_player; + +import java.util.ArrayList; + +import Saboteur.cardClasses.SaboteurTile; + +public class PathTree{ + public TileNode root; + public ArrayList leaves = new ArrayList(); + public int length; + + //Init + public PathTree(SaboteurTile[][] board, int[][] map) { + int depth = 1; + //root = origin + root = new TileNode(board[5][5],5,5,0); + //visited map + boolean[][] visited = new boolean[board.length][board[0].length]; + visited[5][5] = true; + + //parents list + ArrayList parents = new ArrayList(); + parents.add(root); + + while(parents.size() != 0) { + //children list + ArrayList children = new ArrayList(); + + for(TileNode p : parents) { + //if p is a dead end, then there is no child + if(checkDeadEnd(p.tile)) { + leaves.add(p); + } + + else { + int[][] neighbors = {{p.x()+1,p.y()},{p.x()-1,p.y()},{p.x(),p.y()+1},{p.x(),p.y()-1}}; + int nbChild = 0; + //check children + for(int[] nei : neighbors) { + //check if the position exist on the board and either there is a tile(not a null object) + if(nei[0] < 14 && nei[0] > 0 && nei[1] < 14 && nei[1] > 0) { + if(map[nei[0]][nei[1]] == 1) { + SaboteurTile neighbor = board[nei[0]][nei[1]]; + + //check if connected + boolean connected = false; + if(nei[0]-p.x() == p.tile.getPath()[1][2]) connected = true; + else if(nei[0]-p.x() == -p.tile.getPath()[1][0]) connected = true; + else if(nei[1]-p.y() == -p.tile.getPath()[0][1]) connected = true; + else if(nei[1]-p.y() == p.tile.getPath()[2][1]) connected = true; + + //if the node is connected and not visited + if(connected && !visited[nei[0]][nei[1]]) { + TileNode child = new TileNode(neighbor, nei[0], nei[1], depth); + p.Addchild(child); + children.add(child); + visited[nei[0]][nei[1]] = true; + nbChild++; + } + } + } + } + + //no child , becomes a leaf + if(nbChild == 0) { + leaves.add(p); + } + } + } + + //children become next parents + parents = children; + depth++; + } + + length = depth; + } + + public boolean checkDeadEnd(SaboteurTile tile) { + String idx = tile.getIdx(); + if(idx.equals("1")||idx.equals("2")||idx.equals("2_flip")||idx.equals("3")||idx.equals("3_flip")|| + idx.equals("4")||idx.equals("4_flip")||idx.equals("11")||idx.equals("11_flip")|| + idx.equals("12")||idx.equals("12_flip")||idx.equals("13")||idx.equals("14")||idx.equals("14_flip")) { + return true; + } + return false; + } + +} + + diff --git a/src/student_player/StudentPlayer.java b/src/student_player/StudentPlayer.java index 8ab4a16..0e3aa26 100644 --- a/src/student_player/StudentPlayer.java +++ b/src/student_player/StudentPlayer.java @@ -3,7 +3,13 @@ import boardgame.Move; import Saboteur.SaboteurPlayer; +import Saboteur.cardClasses.SaboteurCard; + +import java.util.ArrayList; + import Saboteur.SaboteurBoardState; +import Saboteur.SaboteurMove; + /** A player file submitted by a student. */ public class StudentPlayer extends SaboteurPlayer { @@ -14,9 +20,11 @@ public class StudentPlayer extends SaboteurPlayer { * associate you with your agent. The constructor should do nothing else. */ public StudentPlayer() { - super("xxxxxxxxx"); + super("Linda"); } - + private ArrayList playerCards; //hand of player + private BoardState myBoardState; + private ArrayList possibleLatPlayedCardByOpponent; /** * 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 +34,43 @@ 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(); - + //Move myMove = boardState.getRandomMove(); + + int val = -1000000; + ArrayList list = boardState.getAllLegalMoves(); + SaboteurMove finalmove = list.get(0); + + for(SaboteurMove move : list) { + SaboteurBoardState clone = (SaboteurBoardState) boardState.clone(); + clone.processMove(move); + + int value = MyTools.alpha_beta_pruning(1000000,val,0,boardState); + + if(value > val) { + finalmove = move; + val = value; + } + } + + Move myMove = finalmove; // Return your move to be processed by the server. return myMove; } + + public void initializePlayerCards(SaboteurBoardState boardState) { + playerCards = (ArrayList)boardState.getCurrentPlayerCards().clone(); + } + + public void initializeBoardState(SaboteurBoardState boardState) { + int turnPlayer = boardState.getTurnPlayer(); + int turnNumber = boardState.getTurnNumber(); + this.myBoardState = new BoardState(turnPlayer,turnNumber); + this.myBoardState.setNbMalus(boardState.getNbMalus(1), boardState.getNbMalus(0)); + this.myBoardState.fillTileBoardFromOriginalBoard(boardState.getHiddenBoard()); + this.myBoardState.addCurrentPlayerHandCard(boardState.getCurrentPlayerCards()); + } + } \ No newline at end of file diff --git a/src/student_player/TileNode.java b/src/student_player/TileNode.java new file mode 100644 index 0000000..e257392 --- /dev/null +++ b/src/student_player/TileNode.java @@ -0,0 +1,33 @@ +package student_player; + +import java.util.ArrayList; + +import Saboteur.cardClasses.SaboteurTile; + + +public class TileNode{ + public SaboteurTile tile; + public int[] position = new int[2]; + public int depth; + public ArrayList children = new ArrayList(); + + public TileNode(SaboteurTile t, int i, int j, int d){ + tile = t; + position[0] = i; + position[1] = j; + depth = d; + } + + public void Addchild(TileNode t) { + children.add(t); + } + + public int x() { + return position[0]; + } + + public int y() { + return position[1]; + } + +} \ No newline at end of file From b1525c1bdd277e197d400d9ef4a687f0d2ef10b7 Mon Sep 17 00:00:00 2001 From: ZeroLifeCat Date: Thu, 9 Apr 2020 05:39:19 +0800 Subject: [PATCH 02/18] Started working on deck and opponent hand inference --- src/student_player/BoardState.java | 6 +-- src/student_player/StudentPlayer.java | 56 ++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/student_player/BoardState.java b/src/student_player/BoardState.java index 1f20197..d35c922 100644 --- a/src/student_player/BoardState.java +++ b/src/student_player/BoardState.java @@ -26,7 +26,7 @@ public class BoardState { private static int FIRST_PLAYER = 1; - private SaboteurTile[][] board; + public SaboteurTile[][] board; private int[][] intBoard; //player variables: // Note: Player 1 is active when turnplayer is 1; @@ -81,7 +81,7 @@ public class BoardState { this.turnNumber = turnNumber; //Set current deck's size - this.deckSize = 52 - this.turnNumber; + this.deckSize = (55 - 14) - this.turnNumber; } /** @@ -134,7 +134,7 @@ public void setNbMalus(int numMalus1, int numMalus2) { } public void randomizeDeck() { - //Infer played Cards from board, numOfMalus and studentPlayer's compare method + //Infer played Cards from board, numOfMalus and stude //Naively fill deck diff --git a/src/student_player/StudentPlayer.java b/src/student_player/StudentPlayer.java index 0e3aa26..253d848 100644 --- a/src/student_player/StudentPlayer.java +++ b/src/student_player/StudentPlayer.java @@ -6,6 +6,8 @@ import Saboteur.cardClasses.SaboteurCard; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import Saboteur.SaboteurBoardState; import Saboteur.SaboteurMove; @@ -23,8 +25,10 @@ public StudentPlayer() { super("Linda"); } private ArrayList playerCards; //hand of player - private BoardState myBoardState; - private ArrayList possibleLatPlayedCardByOpponent; + private BoardState currentBoardState; + private ArrayList possibleLastPlayedCardByOpponent; + private Map playedCardstillLastTurn; + private BoardState lastBoardState; /** * 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 @@ -60,6 +64,46 @@ public Move chooseMove(SaboteurBoardState boardState) { return myMove; } + public void initializePlayedCardsNumInFirstTurn(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); + } + } + } + public void initializePlayerCards(SaboteurBoardState boardState) { playerCards = (ArrayList)boardState.getCurrentPlayerCards().clone(); } @@ -67,10 +111,10 @@ public void initializePlayerCards(SaboteurBoardState boardState) { public void initializeBoardState(SaboteurBoardState boardState) { int turnPlayer = boardState.getTurnPlayer(); int turnNumber = boardState.getTurnNumber(); - this.myBoardState = new BoardState(turnPlayer,turnNumber); - this.myBoardState.setNbMalus(boardState.getNbMalus(1), boardState.getNbMalus(0)); - this.myBoardState.fillTileBoardFromOriginalBoard(boardState.getHiddenBoard()); - this.myBoardState.addCurrentPlayerHandCard(boardState.getCurrentPlayerCards()); + this.currentBoardState = new BoardState(turnPlayer,turnNumber); + this.currentBoardState.setNbMalus(boardState.getNbMalus(1), boardState.getNbMalus(0)); + this.currentBoardState.fillTileBoardFromOriginalBoard(boardState.getHiddenBoard()); + this.currentBoardState.addCurrentPlayerHandCard(boardState.getCurrentPlayerCards()); } } \ No newline at end of file From 862c57012ae0d61c7fff1c3710a902c4e5be252f Mon Sep 17 00:00:00 2001 From: ZeroLifeCat Date: Thu, 9 Apr 2020 06:32:16 +0800 Subject: [PATCH 03/18] Update --- src/student_player/BoardState.java | 9 ++++++++- src/student_player/StudentPlayer.java | 18 +++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/student_player/BoardState.java b/src/student_player/BoardState.java index d35c922..97fe612 100644 --- a/src/student_player/BoardState.java +++ b/src/student_player/BoardState.java @@ -328,6 +328,11 @@ private void updateWinner() { } + public int getNbMalus(int playerNb){ + if(playerNb==1) return this.player1nbMalus; + return this.player2nbMalus; + } + private void draw(){ if(this.deckSize>0){ if(turnPlayer==1){ @@ -366,7 +371,9 @@ private int[][] getIntBoard() { } return this.intBoard; } - private boolean pathToHidden(SaboteurTile[] objectives){ + + + 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 diff --git a/src/student_player/StudentPlayer.java b/src/student_player/StudentPlayer.java index 253d848..c461657 100644 --- a/src/student_player/StudentPlayer.java +++ b/src/student_player/StudentPlayer.java @@ -25,10 +25,12 @@ public StudentPlayer() { super("Linda"); } private ArrayList playerCards; //hand of player - private BoardState currentBoardState; + private BoardState currentBoardState; //Current Board State + private BoardState lastBoardState; //Record Last Board State so that we know what's opponent's move private ArrayList possibleLastPlayedCardByOpponent; private Map playedCardstillLastTurn; - private BoardState lastBoardState; + private int playerNb; + /** * 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 @@ -43,6 +45,10 @@ public Move chooseMove(SaboteurBoardState boardState) { // Is random the best you can do? //Move myMove = boardState.getRandomMove(); + //Initialize In First Turn... + this.playerNb = boardState.getTurnPlayer(); + initializeBoardState(boardState); + int val = -1000000; ArrayList list = boardState.getAllLegalMoves(); SaboteurMove finalmove = list.get(0); @@ -64,7 +70,7 @@ public Move chooseMove(SaboteurBoardState boardState) { return myMove; } - public void initializePlayedCardsNumInFirstTurn(boolean isFirstPlayer) { + public void initializeInFirstTurn(boolean isFirstPlayer) { this.playedCardstillLastTurn = SaboteurCard.getDeckcomposition(); playedCardstillLastTurn.put("0",0); playedCardstillLastTurn.put("1",0); @@ -100,10 +106,16 @@ public void initializePlayedCardsNumInFirstTurn(boolean isFirstPlayer) { }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); + }else if(this.currentBoardState.hiddenRevealed[0] + ||this.currentBoardState.hiddenRevealed[1]||this.currentBoardState.hiddenRevealed[2]) { + playedCardstillLastTurn.put("map", 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(); } From 3f4c8a0e1c314bcce99978ab073bca98cf9852ae Mon Sep 17 00:00:00 2001 From: ZeroLifeCat Date: Sun, 12 Apr 2020 04:24:45 +0800 Subject: [PATCH 04/18] Working on best-first search in start of game --- src/Saboteur/SaboteurBoardState.java | 4 ++ src/student_player/BoardState.java | 98 ++++++++++++++++++++++++++- src/student_player/MyTools.java | 57 ++++++++-------- src/student_player/StudentPlayer.java | 58 ++++++++++++---- 4 files changed, 171 insertions(+), 46 deletions(-) diff --git a/src/Saboteur/SaboteurBoardState.java b/src/Saboteur/SaboteurBoardState.java index 921b8fd..f982d83 100644 --- a/src/Saboteur/SaboteurBoardState.java +++ b/src/Saboteur/SaboteurBoardState.java @@ -528,6 +528,10 @@ else if(card instanceof SaboteurDestroy){ for(int i=0;i