diff --git a/src/main/java/ubc/cosc322/algorithms/BFSAmazons.java b/src/main/java/ubc/cosc322/algorithms/BFSAmazons.java deleted file mode 100644 index e004c36..0000000 --- a/src/main/java/ubc/cosc322/algorithms/BFSAmazons.java +++ /dev/null @@ -1,252 +0,0 @@ -package ubc.cosc322.algorithms; - -import java.util.*; - -import ubc.cosc322.core.Board; - -public class BFSAmazons { - - // Used to speed counts up if finished. - public int totalBlackCount = 0; // Player 1 - public int totalWhiteCount = 0; // Player 2 - - Deque queue; // Queue of nodes to be searched. Cannot contain arrows. - HashSet visitedNodes; // Nodes already searched. - Deque whiteQueens; // White queens found in the same partition as another white queen. - Deque blackQueens; // Black queens found in the same partition as another black queen. - - public BFSAmazons() { - // Initialize array - queue = new ArrayDeque<>(); - visitedNodes = new HashSet<>(); - whiteQueens = new ArrayDeque<>(); - blackQueens = new ArrayDeque<>(); - } - - // Returns state of the board. - // 0 if partition in progress, 1 if finished. - public int searchBoardPartition(int[][] board, int x, int y, int color) { - // 1. Check that the initial queen is not completely blocked. If it is, the partition is complete. - // 2. Check if there is an adjacent queen of the same color. If it is, add to list and continue search. - // 3. Check if there is an adjacent queen of a different color. If it is, check if it is blocked. - // If it is blocked, continue search. Otherwise, return in progress. - - // System.out.println("NEW SEARCH"); - - // Variables - BFSNode currentNode; // - int tileValue, tileValue2; - int temporaryCount = 0; // initial queen position does not count. - - // If a queen of the same color was found in a previous partition, then the partition has already been searched. - if (color == 1) { - if (blackQueens.contains(new BFSNode(x, y, color))) - return 1; - } else { - if (whiteQueens.contains(new BFSNode(x, y, color))) - return 1; - } - - // Search begins here - queue.addLast(new BFSNode(x, y, color)); // Initial queen node. - - /* QUEEN ADJACENT SEARCH */ - // Do an initial check on the first node (queen), different rules than free space - currentNode = queue.removeFirst(); - - boolean isBlocked = true; - for (int i = -1; i < 2; i++) { // -1, 0, 1 - for (int j = -1; j < 2; j++) { // -1, 0, 1 - - if (i != 0 || j != 0) { // Skip the current node, where x=x and y=y - - // Only consider nodes on the board. - if((currentNode.x+i) > -1 && (currentNode.x+i) < Board.DEFAULT_BOARD_SIZE - && (currentNode.y+j) > -1 && (currentNode.y+j) < Board.DEFAULT_BOARD_SIZE) { - - // Check for complete blockage - if (board[currentNode.x+i][currentNode.y+j] == 0) { - isBlocked = false; - } - - } - } - } - } - - if (isBlocked) { - // System.out.println("IS BLOCKED"); - return 1; // Partition finished. - } - - /* ADJACENT SEARCH, QUEEN NOT BLOCKED */ - for (int i = -1; i < 2; i++) { // -1, 0, 1 - for (int j = -1; j < 2; j++) { // -1, 0, 1 - - if (i != 0 || j != 0) { // Skip the current node, where x=x and y=y - - // Only consider nodes on the board. - if((currentNode.x+i) > -1 && (currentNode.x+i) < Board.DEFAULT_BOARD_SIZE - && (currentNode.y+j) > -1 && (currentNode.y+j) < Board.DEFAULT_BOARD_SIZE) { // Skip nodes outside of board - - // Get the tile value of the adjacent node - tileValue = board[currentNode.x+i][currentNode.y+j]; - - // If the tile value is not a free space - if (tileValue != 0 && tileValue != 3) { - - // If there is a queen of the same color, add to list and continue search. - if (tileValue == color) { - - if (color == 1) { - blackQueens.add(currentNode); - } else { - whiteQueens.add(currentNode); - } - - // If there is a queen of a different color, if it is not blocked, return in progress. - } else { - - // Check for trapped opposing queen - for (int k = -1; k < 2; k++) { // -1, 0 , -1 - for (int l = -1; l < 2; l++) { // -1, 0 , -1 - if (k != 0 || l != 0) { - if( currentNode.x+i+k >= 0 && currentNode.x+i+k < Board.DEFAULT_BOARD_SIZE - && currentNode.y+j+l >= 0 && currentNode.y+j+l < Board.DEFAULT_BOARD_SIZE) { - tileValue2 = board[currentNode.x+i+k][currentNode.y+j+l]; - if (tileValue2 == 0) { - return 0; // other queen can move, so in progress. - } - } - } - } - } - - } - - // If the tile is a free space - } else if (tileValue == 0){ - // Check that we havent seen this node already - BFSNode checkNode = new BFSNode(currentNode.x+i, currentNode.y+j, color); - if (!visitedNodes.contains(checkNode)) { - queue.addLast(checkNode); - visitedNodes.add(checkNode); - temporaryCount++; - } - } - } - } - } - } - - /* REMAINING PARTITION SEARCH */ - int iter = 0; - while(!queue.isEmpty()) { // Continue as long as free spaces are found - currentNode = queue.removeFirst(); - visitedNodes.add(currentNode); - iter++; - if (iter > 500) - return -1; - - for (int i = -1; i < 2; i++) { // -1, 0, -1 - for (int j = -1; j < 2; j++) { // -1, 0, -1 - - if (i != 0 || j != 0) { // Skip the current node, x=x, y=y - - if(currentNode.x+i >= 0 && currentNode.x+i < Board.DEFAULT_BOARD_SIZE - && currentNode.y+j >= 0 && currentNode.y+j < Board.DEFAULT_BOARD_SIZE) { // Skip nodes outside of board - - // Get the tile value of the adjacent node - tileValue = board[currentNode.x+i][currentNode.y+j]; - // If the tile value is not a free space - if (tileValue != 0 && tileValue != 3) { - - // Return if we find a queen of a different color - if(tileValue != color) { - return 0; - - // Found another queen in same partition. Continue, add found queen to queens list. - } else if (tileValue == color) { - - // Don't return, but add to list of queens - if (color == 1) { - blackQueens.add(currentNode); - } else { - whiteQueens.add(currentNode); - } - } - - // If the tile is a free space - } else if (tileValue == 0) { - // Check that we havent seen this node already - BFSNode checkNode = new BFSNode((currentNode.x+i), (currentNode.y+j), color); - if (!visitedNodes.contains(checkNode)) { - queue.addLast(checkNode); - visitedNodes.add(checkNode); - temporaryCount++; - } - } - } - } - } - } - } - - if (color == 1) - totalBlackCount += temporaryCount; - else - totalWhiteCount += temporaryCount; - return 1; - } - - - public int evaluateBoardStatus() { - // Assuming you have a method to evaluate board status based on the current state of counts - // This method should return DRAW, P1, or P2 based on the territories controlled by each player - if (totalBlackCount != 0 && totalWhiteCount == 0) { - return Board.P1; - } else if (totalWhiteCount != 0 && totalBlackCount == 0) { - return Board.P2; - } else { - return Board.IN_PROGRESS; - } - } - -} - -// Helper class -class BFSNode { - public int x; - public int y; - public int color; - - BFSNode(int x, int y, int color) { - this.x = x; - this.y = y; - this.color = color; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - BFSNode node = (BFSNode) o; - - boolean result = (this.x == node.x && this.y == node.y); - - return result; - } - - @Override - public int hashCode() { - int result = 17; - result = 31 * result + x; - result = 31 * result + y; - return result; - } -} - diff --git a/src/main/java/ubc/cosc322/algorithms/MonteCarloTreeSearch.java b/src/main/java/ubc/cosc322/algorithms/MonteCarloTreeSearch.java index a7a67c3..3a7d93c 100644 --- a/src/main/java/ubc/cosc322/algorithms/MonteCarloTreeSearch.java +++ b/src/main/java/ubc/cosc322/algorithms/MonteCarloTreeSearch.java @@ -15,9 +15,7 @@ public class MonteCarloTreeSearch { // Lists to hold the positions of black and white queens on the board. List> blackPositions = new ArrayList<>(); List> whitePositions = new ArrayList<>(); - final String OPPONENT = "white"; // Assumed opponent color. static final int WIN_SCORE = 10; // Score indicating a win in simulations. - int level; // Represents the current level in the tree. final int UPPER_TIME_LIMIT = 25000; public static int numberOfNodes = 0; long end; @@ -46,18 +44,6 @@ public void initializePositions() { whitePositions.add(Arrays.asList(10, 4)); } - /** - * Sends a move message to the game server with the specified queen positions and the arrow position. - * - * @param queenPosCurrent The current position of the queen. - * @param queenPosNew The new position of the queen. - * @param arrowPos The position where the arrow is shot. - */ - public void sendMoveMessage(java.util.ArrayList queenPosCurrent, - java.util.ArrayList queenPosNew, - java.util.ArrayList arrowPos) { - } - /** * Finds the next best move using the MCTS algorithm. * @@ -70,10 +56,13 @@ public Board findNextMove(Board board, int playerNo) { Node rootNode = new Node(playerNo); rootNode.setState(board); expandNode(rootNode, rootNode.getPlayerNo()); - System.out.println("Size of child array from root node: "+rootNode.getChildArray().size()); + if(rootNode.getChildArray().isEmpty()){ + return board; + } // Use a single threaded context to manage the overall time-bound loop. ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + // Use parallel computing to increase efficiency of simulateRandomPlayout while (System.currentTimeMillis() < end) { List> tasks = new ArrayList<>(); for (Node childNode : rootNode.getChildren()) { @@ -91,31 +80,25 @@ public Board findNextMove(Board board, int playerNo) { } executor.shutdown(); System.out.println("Games played: "+Board.gamesPlayed); - //System.out.println("Score of root node " + rootNode.getScore()); Node winnerNode = selectPromisingNode(rootNode); - //Node winnerNode = rootNode.getChildWithMaxScore(); System.out.println("Winner node child with highest score: "+winnerNode.getScore()); - System.out.println("Number of children for node: " + rootNode.getChildren().size()); numberOfNodes = numberOfNodes + (rootNode.getChildren().size()); if (winnerNode == null) { System.out.println("winnerNode = null"); return board; } System.out.println("Winner node found."); - //Board.printBoard(winnerNode.getState().getBoard()); return winnerNode.getState(); } /** * Selects the most promising node to explore based on the UCT value. * - * @param node The node from which to select the promising node. + * @param rootNode The node from which to select the promising node. * @return The selected promising node. */ public Node selectPromisingNode(Node rootNode) { - //System.out.println("selectpromisingnode activated"); - //node with the highest amount of playouts is returned Node node; node = UCT.findBestNodeWithUCT(rootNode); return node; @@ -124,7 +107,6 @@ public Node selectPromisingNode(Node rootNode) { private void simulateRandomPlayout(Node currentNode, int playerNo) { int counter = 0; while (currentNode.getState().checkStatus() == Board.IN_PROGRESS && System.currentTimeMillis() < end) { - //System.out.println("Debug 1.2"); // Perform a random move and create a new state Board nextBoardState = currentNode.getState().clone(); nextBoardState.randomPlay(playerNo); // Assuming this method updates the board state @@ -132,42 +114,33 @@ private void simulateRandomPlayout(Node currentNode, int playerNo) { // Create a new node for this state and link it Node childNode = new Node(playerNo); childNode.setState(nextBoardState); + // Must synchronize adding child nodes synchronized (currentNode) { - //Following line prints out boards synchronized so we can see the states - //Board.printSynchronizedBoard(childNode.getState()); currentNode.addChild(childNode); } childNode.setNodeDepth(currentNode.getNodeDepth()+1); - // Prepare for the next iteration - currentNode = childNode; // Move the "focus" to the child node for the next iteration + currentNode = childNode; playerNo = 3 - playerNo; // Toggle Players counter++; - //System.out.println("Debug: status of current node state - " + childNode.getState().checkStatus()); } int status = currentNode.getState().checkStatus(); -// int result = evaluatePlayoutResult(status); backPropagation(currentNode, status); } /** - * Backpropagates the result of the simulation up the tree, updating the statistics of the nodes. + * Backpropagates the result of the simulation up the nodes, updating the statistics of the nodes. * * @param node The node from which to start backpropagation. * @param status The result of the playout to be backpropagated. */ public void backPropagation(Node node, int status) { - //System.out.println("activate back propagation"); final int finalDepth = 0; while (node != null && node.getNodeDepth() != finalDepth) { node.incrementVisit(); - // Only add score if the playout result corresponds to the node's player winning if (status == Board.getCurrentPlayer()) { - //System.out.println("node before add score " + node.getScore()); node.addScore(WIN_SCORE); - //System.out.println("Debug: Node WINSCORE"); } else if (status == (3 - (Board.getCurrentPlayer()))) { - //System.out.println("node before minus score " + node.getScore()); node.addScore(-WIN_SCORE); } else if (status == 0) { node.addScore(-WIN_SCORE); @@ -178,13 +151,7 @@ public void backPropagation(Node node, int status) { /** * Expands the given node by creating new child nodes that represent all possible future game states - * arising from the current state. This method leverages parallel processing to concurrently evaluate - * different potential game states and associated moves, enhancing the computational efficiency. - * The expansion considers all possible movements for each queen followed by all potential arrow shots - * for those moves, encapsulating the breadth of possible game progressions from the current state. - * - * Note: The method synchronizes access to the node's children to safely add new child nodes in a - * multithreaded environment, preventing concurrent modification issues. + * arising from the current state. * * @param node The node to be expanded, representing the current game state. * @param playerNo The player number (1 or 2) for whom the expansion is being done. diff --git a/src/main/java/ubc/cosc322/algorithms/Node.java b/src/main/java/ubc/cosc322/algorithms/Node.java index 2b2d4fc..041d544 100644 --- a/src/main/java/ubc/cosc322/algorithms/Node.java +++ b/src/main/java/ubc/cosc322/algorithms/Node.java @@ -12,17 +12,9 @@ public class Node { private Node parent; private List children; - //private double score; - //private int visitCount; private Board state; private int playerNo; private int nodeDepth; - private double winScore; // We are accounting for wins, losses and draws - private double drawScore; - private static final double WIN_SCORE = 1.0; // These are thresholds - private static final double DRAW_SCORE = 0.5; - public static final double WIN_SCORE_VALUE = 10.0; - public static final double DRAW_SCORE_VALUE = 5.0; private AtomicInteger visitCount = new AtomicInteger(0); private AtomicInteger score = new AtomicInteger(0); @@ -105,67 +97,11 @@ public Collection getChildArray() { return new ArrayList<>(children); } - /** - * Selects a random child node from this node's children. - * - * @return A randomly selected child node. - */ - public Node getRandomChildNode() { - int randomChild = new Random().nextInt(children.size()); - return children.get(randomChild); - } - /** - * Retrieves the child node with the maximum score. - * - * @return The child node with the highest score. - */ - public Node getChildWithMaxScore() { - return children.stream() - .max(Comparator.comparingDouble(Node::getScore)) - .orElse(null); - } public double getScore() { return score.get(); } - /** - * Updates the score based on the simulation result. - * - * @param result The result from the simulation to update the score accordingly. - */ -// public void updateScore(int result) { -// incrementVisit(); -// if (result == playerNo) { -// // The player associated with this node wins -// addScore(WIN_SCORE_VALUE); -// } else if (result == 3 - playerNo) { -// // The opponent wins -// addScore(-WIN_SCORE_VALUE); -// } else if (result == Board.DRAW) { -// // The game ends in a draw -// addScore(DRAW_SCORE_VALUE); -// } -// } - - /** - * Sets the parent node for this node. - * - * @param node The parent node to set. - */ - public void setParent(Node node) { - this.parent = node; - } - - /** - * Adds a specified score to this node's total score. - * - * @param score The score to add. - */ -// public void addScore(int score) { -// this.score += score; -// } - /** * Retrieves the player number associated with this node. * diff --git a/src/main/java/ubc/cosc322/algorithms/Tree.java b/src/main/java/ubc/cosc322/algorithms/Tree.java deleted file mode 100644 index 989c6ef..0000000 --- a/src/main/java/ubc/cosc322/algorithms/Tree.java +++ /dev/null @@ -1,29 +0,0 @@ -package ubc.cosc322.algorithms; - -/** - * Represents the search tree used in the Monte Carlo Tree Search (MCTS) algorithm. - * This tree structure is essential for navigating and expanding the game state possibilities during the search. - */ -public class Tree { - private Node root; - - /** - * Constructs a Tree instance with the specified root node. - * The root node typically represents the current game state from which the MCTS algorithm begins its exploration. - * - * @param root The root node of the tree, representing the initial state for MCTS exploration. - */ - public Tree(Node root) { - this.root = root; - } - - /** - * Retrieves the root node of the tree. - * The root node provides the starting point for the MCTS exploration and can be used to navigate through the tree. - * - * @return The root node of the tree. - */ - public Node getRoot() { - return root; - } -} diff --git a/src/main/java/ubc/cosc322/algorithms/UCT.java b/src/main/java/ubc/cosc322/algorithms/UCT.java index d8317f5..0f24f49 100644 --- a/src/main/java/ubc/cosc322/algorithms/UCT.java +++ b/src/main/java/ubc/cosc322/algorithms/UCT.java @@ -1,6 +1,5 @@ package ubc.cosc322.algorithms; -import ubc.cosc322.core.Board; import java.util.Collections; import java.util.Comparator; @@ -37,12 +36,6 @@ public static Node findBestNodeWithUCT(Node node) { //System.out.println("No children available for this node."); return null; // No children, so no move can be made. } - -// // Proceed as before if there are children. -// for (Node child : node.getChildren()) { -// //System.out.println("Child UCT Values: Score - " + child.getScore() + " Visit Count - "+ child.getVisitCount()); -// } - return Collections.max(node.getChildren(), Comparator.comparing(c -> uctValue(c.getScore(), c.getVisitCount()))); } diff --git a/src/main/java/ubc/cosc322/core/Board.java b/src/main/java/ubc/cosc322/core/Board.java index 119a0e9..a12878c 100644 --- a/src/main/java/ubc/cosc322/core/Board.java +++ b/src/main/java/ubc/cosc322/core/Board.java @@ -5,31 +5,22 @@ import java.util.List; import java.util.Random; -import ubc.cosc322.algorithms.Node; -import ygraph.ai.smartfox.games.BaseGameGUI; -import ygraph.ai.smartfox.games.GameClient; - - /** * Represents the game board for the Game of the Amazons. * Provides functionality to track and update the board state, * including calculating legal moves, performing moves, and checking the game status. */ public class Board { - public static int randomPlays = 0; public static int gamesPlayed = 0; // The 2D array representing the board state; 0 for empty, 1 for player 1, and 2 for player 2. private int[][] boardValues; - //public static int[][] mainBoardValues; + private static int[][] mainBoardValues = new int[10][10]; public static final int DEFAULT_BOARD_SIZE = 10; public static final int IN_PROGRESS = -1; public static final int DRAW = 0; public static final int P1 = 1; // this is subject to who joins first. 1 represents black public static final int P2 = 2; public static final int ARROW = 3; - public static final int RANDOM_ARROW = 4; - private GameClient gameClient; - private BaseGameGUI gameGui; // By introducing a currentPlayer variable at the board level, we can keep track of who is currently playing on the board. Must be updated throughout the game's progression. private static int currentPlayer = P1; // P1 always starts the game (black). We just need to know who is P1. @@ -66,7 +57,12 @@ private void initializePositions() { boardValues[pos.getX()][pos.getY()] = 2; } } - // Initialize the board from an ArrayList + + /** + * Gets all queen positions and returns them in a List. + * + * @return List of queen positions. + */ public List getQueenPositions(int playerNo) { List queenPositions = new ArrayList<>(); @@ -86,6 +82,7 @@ public List getQueenPositions(int playerNo) { * * @return A new Board instance with the same state as this board. */ + public Board clone() { Board newBoard = new Board(); for (int i = 0; i < DEFAULT_BOARD_SIZE; i++) { @@ -130,6 +127,7 @@ public List getLegalMoves(int x, int y) { * @param player The player number (1 or 2) making the move. * @param newPos The position to which the player is moving. */ + public void performMove(int player, Position currentPos, Position newPos) { // Remove the piece from its current position. this.boardValues[currentPos.getX()][currentPos.getY()] = 0; @@ -152,12 +150,11 @@ public void performMove(int player, Position currentPos, Position newPos) { * * @return An integer representing the game status (IN_PROGRESS, DRAW, P1 win, or P2 win). */ -// Make sure checkStatus is an instance method if it's going to call other instance methods like getLegalMoves + public int checkStatus() { boolean blackHasMoves = false; boolean whiteHasMoves = false; - // Use this to call instance methods for (int x = 0; x < DEFAULT_BOARD_SIZE; x++) { for (int y = 0; y < DEFAULT_BOARD_SIZE; y++) { int piece = this.boardValues[x][y]; @@ -191,16 +188,6 @@ else if (!blackHasMoves && whiteHasMoves) { else return IN_PROGRESS; // Game is still in progress or it's a draw } - - - /** - * Retrieves the current state of the board. - * - * @return The 2D array representing the board state. - */ - // Method to return the current board state - public int[][] getBoard() { return this.boardValues; } - /** * Sets the board state. Use with caution to avoid corrupting the game state. * @@ -223,15 +210,6 @@ public static int getBoardPlayerNo(boolean isPlayerWhite) { return currentPlayer; } - /** - * @param currentPlayer The player number of the current player. - * @return The opponent's player number. - */ - public int getOpponent(int currentPlayer) { - // Assuming only two players, this returns the opponent's number. - return (currentPlayer == P1) ? P2 : P1; // If we are player 1, then the opponent must be player 2 - } - /** * Generates all possible next states of the board from the current player's perspective. * @@ -257,95 +235,60 @@ public List getAllPossibleStates(int currentPlayer) { return possibleStates; } - /** - * Copies the board state from one 2D array to another. + * Randomly plays out a queen move and an arrow shot. * - * @param source The source 2D array. - * @param destination The destination 2D array. + * @param playerNo The player number (P1 or P2). */ - private void copyBoardState(int[][] source, int[][] destination) { - for (int i = 0; i < source.length; i++) { - System.arraycopy(source[i], 0, destination[i], 0, source[i].length); - } - } - - public static void printBoard(int [][] printBoard) { - System.out.println(); - for (int i = 9; i > -1; i--) { // Iterate through each row - for (int j = 0; j < 10; j++) { // Iterate through each column in the row - System.out.print(printBoard[i][j] + " "); // Print the value at the current position - } - System.out.println(); // Move to the next line after printing each row - } - System.out.println(); - } - public static synchronized void printSynchronizedBoard(Board board) { - System.out.println("Next Board State:"); - Board.printBoard(board.getBoard()); - } public void randomPlay(int playerNo) { - //randomPlays++; - //System.out.println("Debug: Activate randomPlay & Print Board Values"); - //System.out.println(Arrays.deepToString(boardValues)); Random random = new Random(); - // Determine the current player's positions List playerPositions = getQueenPositions(playerNo); if (!playerPositions.isEmpty()) { - // Choose a random queen from the current player's positions Position piecePosition = playerPositions.get(random.nextInt(playerPositions.size())); - // Find all legal moves for that queen List legalMoves = getLegalMoves(piecePosition.getX(), piecePosition.getY()); - if (!legalMoves.isEmpty()) { - // Select one of the legal moves at random Position selectedMove = legalMoves.get(random.nextInt(legalMoves.size())); - //System.out.println("Debug: Print Board Values before performMove"); - //System.out.println(Arrays.deepToString(boardValues)); performMove(playerNo, piecePosition, selectedMove); - //System.out.println("Debug: Print Board Values after performMove"); - //System.out.println(Arrays.deepToString(boardValues)); - - // After moving, find all possible positions to shoot the arrow List arrowShots = getLegalMoves(selectedMove.getX(), selectedMove.getY()); if (!arrowShots.isEmpty()) { - // Select a random position for the arrow Position arrowPosition = arrowShots.get(random.nextInt(arrowShots.size())); - //System.out.println("Debug: Print Board Values before shootArrow"); - //System.out.println(Arrays.deepToString(boardValues)); this.boardValues[arrowPosition.getX()][arrowPosition.getY()] = ARROW; - //System.out.println("Debug: Print Board Values after shootArrow"); - //System.out.println(Arrays.deepToString(boardValues)); } } } } - + /** + * Shoots an arrow from the queen position to desired placement on board. + * + * @param arrowPosition The position of the arrow being shot. + */ public void shootArrow(Position arrowPosition) { - //System.out.println("activate shoot arrow"); - // Check if the position is within the bounds of the board if(arrowPosition.getX() >= 0 && arrowPosition.getX() < DEFAULT_BOARD_SIZE && arrowPosition.getY() >= 0 && arrowPosition.getY() < DEFAULT_BOARD_SIZE) { - // Mark the position with a 3 to indicate an arrow boardValues[arrowPosition.getX()][arrowPosition.getY()] = ARROW; - //System.out.println("arrow shot at " + arrowPosition.getX() + " and " + arrowPosition.getY()); } else { System.out.println("Arrow position is out of bounds."); } } + /** + * Extract the move details from the currentBoard. We do this by taking in the currentBoard + * along with the bestMoveBoard that has our next move on it. We pull the move from that board + * and return those move details. + * + * @param currentBoard The current board in play. + * @param bestMoveBoard The board returned with our best move. + * + * @return Move details of what queen we are going to move and where we are + * going to shoot the arrow. + */ + public static ArrayList extractMoveDetails(Board currentBoard, Board bestMoveBoard) { - //System.out.println("current board" + Arrays.deepToString(currentBoard.getBoard())); - //System.out.println("best move board" + Arrays.deepToString(bestMoveBoard.getBoard())); ArrayList moveDetails = new ArrayList<>(); - - // Initialize variables to track the positions found. Integer oldQueenX = null, oldQueenY = null, newQueenX = null, newQueenY = null, arrowX = null, arrowY = null; - - // Loop over the board to identify the old queen position, new queen position, and arrow position. for (int x = 0; x < DEFAULT_BOARD_SIZE; x++) { for (int y = 0; y < DEFAULT_BOARD_SIZE; y++) { if (currentBoard.boardValues[x][y] != bestMoveBoard.boardValues[x][y]) { @@ -356,16 +299,13 @@ public static ArrayList extractMoveDetails(Board currentBoard, Board be arrowX = x + 1; arrowY = y + 1; } else{ - // The queen has moved from this position. oldQueenX = x + 1; oldQueenY = y + 1; } } else if (currentBoard.boardValues[x][y] == 0 && bestMoveBoard.boardValues[x][y] != 0 && bestMoveBoard.boardValues[x][y] != ARROW) { - // The queen has moved to this position. newQueenX = x + 1; newQueenY = y + 1; } else if (bestMoveBoard.boardValues[x][y] == ARROW) { - // The arrow has been shot to this position. arrowX = x + 1; arrowY = y + 1; } @@ -373,14 +313,12 @@ public static ArrayList extractMoveDetails(Board currentBoard, Board be } } - // Compile and return the move details if all components are identified. if (oldQueenX != null && oldQueenY != null && newQueenX != null && newQueenY != null && arrowX != null && arrowY != null) { moveDetails.addAll(Arrays.asList(oldQueenX, oldQueenY, newQueenX, newQueenY, arrowX, arrowY)); return moveDetails; } else if (oldQueenX == null && oldQueenY == null && newQueenX == null && newQueenY == null && arrowX == null && arrowY == null) { return moveDetails; } else { - // Log missing components for debugging purposes. System.err.println("Missing move components: oldQ=(" + oldQueenX + "," + oldQueenY + "), newQ=(" + newQueenX + "," + newQueenY + "), arrow=(" + arrowX + "," + arrowY + ")"); @@ -388,7 +326,78 @@ public static ArrayList extractMoveDetails(Board currentBoard, Board be } } + /** + * Gets current player. + * + * @return returns the currentPlayer + */ + public static int getCurrentPlayer() { return currentPlayer; } + + /** + * Prints out the main board using a loop and print line. + * + */ + + public void printMainBoard() { + for (int i = 9; i > -1; i--) { + for (int j = 0; j < 10; j++) { + System.out.print(mainBoardValues[i][j] + " "); + } + System.out.println(); + } + } + + /** + * Updates the main board values based on the positions taken from the move received. + * + * @param currentPosition The current position of the queen being moved. + * @param nextPosition The position where the queen is moving to. + * @param arrowPosition The position of the arrow being shot. + */ + + public static void updateMainBoard(ArrayList currentPosition, + ArrayList nextPosition, + ArrayList arrowPosition) { + int currentX = currentPosition.get(0) - 1; + int currentY = currentPosition.get(1) - 1; + int nextX = nextPosition.get(0) - 1; + int nextY = nextPosition.get(1) - 1; + int arrowX = arrowPosition.get(0) - 1; + int arrowY = arrowPosition.get(1) - 1; + int player = mainBoardValues[currentX][currentY]; + mainBoardValues[currentX][currentY] = 0; + mainBoardValues[nextX][nextY] = player; + mainBoardValues[arrowX][arrowY] = 3; + } + + /** + * Sets the values of the main board from the inputted gameBoardState. + * + * @param gameBoardState The state of the board that we want to set our mainBoardValues to. + */ + + public static void setMainBoard(ArrayList gameBoardState) { + int[][] array = new int[10][10]; + for(int i = 1; i < 11; i++){ + for(int j = 1; j < 11; j++){ + array[i-1][j-1] = gameBoardState.get(11*i + j); + } + } + mainBoardValues = array; + } + + /** + * Gets the mainBoard from the board values as a board object. + * + * @return The board with the main board values as a board object. + */ + + public static Board getMainBoard(){ + Board board = new Board(); + board.setBoard(mainBoardValues); + return board; + } } diff --git a/src/main/java/ubc/cosc322/core/actionFactory/Action.java b/src/main/java/ubc/cosc322/core/actionFactory/Action.java deleted file mode 100644 index 506ebf9..0000000 --- a/src/main/java/ubc/cosc322/core/actionFactory/Action.java +++ /dev/null @@ -1,20 +0,0 @@ -package ubc.cosc322.core.actionFactory; - -public class Action { - // TODO: Verify that this is the proper format as compared to server - int oldX, oldY, newX, newY; // Based on board, x: 0-9, y: 0-9, top-left to bottom-right - - Action(int oldX, int oldY, int newX, int newY) { - this.oldX = oldX; - this.oldY = oldY; - this.newX = newX; - this.newY = newY; - } - - public void displayMove(int i) { - System.out.println(); - System.out.println("Move #" + i); - System.out.println("Old Position row: " + oldX + " col: " + oldY); - System.out.println("New Position row: " + newX + " col: " + newY); - } -} diff --git a/src/main/java/ubc/cosc322/core/actionFactory/ActionFactory.java b/src/main/java/ubc/cosc322/core/actionFactory/ActionFactory.java deleted file mode 100644 index 4608b1b..0000000 --- a/src/main/java/ubc/cosc322/core/actionFactory/ActionFactory.java +++ /dev/null @@ -1,89 +0,0 @@ -package ubc.cosc322.core.actionFactory; - -import ubc.cosc322.core.actionFactory.GameState; - -import java.util.ArrayList; - -public class ActionFactory { - - private final int BOARD_SIDE_LENGTH = 10; - public ubc.cosc322.core.actionFactory.GameState state; - public ArrayList actions; // This is overwritten constantly, stored globally in class for ease of use - - public ArrayList getActions(ArrayList gameState, int type) { - - // Assuming that gameState is as provideed by server - actions = new ArrayList<>(); - - // Convert to an easier format to work with, if state is not yet stored - if (gameState != null) - state = new ubc.cosc322.core.actionFactory.GameState(gameState); - - // First, search for the applicable pieces based on type - for (int i = 0; i < BOARD_SIDE_LENGTH; i++) { - for (int j = 0; j < BOARD_SIDE_LENGTH; j++) { - - if (state.board[i][j] == type) { - // System.out.println(state.board[i][j]); - - // Branch with rules of a queen, clockwise - int directionFlags = 0; - int a = 1; // a is the number of squares in each direction - while (directionFlags != 255) { // 255 represents 8 bits flipped to 1. Each of said bits represent a direction, starting north and going CW. If a directionFlag bit is flipped high, then that direction can no longer be explored - - // N - if((directionFlags & 0b1) != 0b1) - directionFlags |= checkDirectionHelper(i, j, -1 * a, 0, 0b1); - // NE - if((directionFlags & 0b10) != 0b10) - directionFlags |= checkDirectionHelper(i, j, -1 * a, -1 * a, 0b10); - // E - if((directionFlags & 0b100) != 0b100) - directionFlags |= checkDirectionHelper(i, j, 0 * a, -1 * a, 0b100); - // SE - if((directionFlags & 0b1000) != 0b1000) - directionFlags |= checkDirectionHelper(i, j, 1 * a, 1 * a, 0b1000); - // S - if((directionFlags & 0b10000) != 0b10000) - directionFlags |= checkDirectionHelper(i, j, 1 * a, 0 * a, 0b10000); - // SW - if((directionFlags & 0b100000) != 0b100000) - directionFlags |= checkDirectionHelper(i, j, 1 * a, -1 * a, 0b100000); - // W - if((directionFlags & 0b1000000) != 0b1000000) - directionFlags |= checkDirectionHelper(i, j, 0 * a, -1 * a, 0b1000000); - // NW - if((directionFlags & 0b10000000) != 0b10000000) - directionFlags |= checkDirectionHelper(i, j, -1 * a, -1 * a, 0b10000000); - - a++; // Increment direction multiplier - } - } - } - } - - for(int i = 0; i < actions.size(); i++) { - actions.get(i).displayMove(i); - } - System.out.println("COUNT: " + actions.size()); - - return actions; - } - - public int checkDirectionHelper(int i, int j, int x, int y, int flag) { - - if (i+x >= 0 && i+x < BOARD_SIDE_LENGTH && j+y >= 0 && j+y < BOARD_SIDE_LENGTH && state.board[i+x][j+y] == 0) { - actions.add(new Action(i, j, i+x, j+y)); - return 0b0; - } else { - return flag; - } - - } - - // TODO: Fill this in when either we or the opponent makes a move. - public void updateGameState(Action move) { - // Do something - } - -} diff --git a/src/main/java/ubc/cosc322/core/actionFactory/GameState.java b/src/main/java/ubc/cosc322/core/actionFactory/GameState.java deleted file mode 100644 index 5a09a97..0000000 --- a/src/main/java/ubc/cosc322/core/actionFactory/GameState.java +++ /dev/null @@ -1,24 +0,0 @@ -package ubc.cosc322.core.actionFactory; - -import java.util.ArrayList; - -public class GameState { - - int[][] board = new int[10][10]; - - GameState(ArrayList gameState) { - - for (int i = 1; i < 11; i++) { - for (int j = 1; j < 11; j++) { - board[i-1][j-1] = gameState.get(11*i + j); // Pulled from setGameState(), 0 = nothing, 1 = black queen, 2 = white queen, 3 = arrow - } - } - - for (int i = 0; i < 10; i++) { - for (int j = 0; j < 10; j++) { - System.out.print(board[i][j] + " "); - } - System.out.println(); - } - } -} diff --git a/src/main/java/ubc/cosc322/driverCode/AIPlayerTest.java b/src/main/java/ubc/cosc322/driverCode/AIPlayerTest.java index af9697c..0964433 100644 --- a/src/main/java/ubc/cosc322/driverCode/AIPlayerTest.java +++ b/src/main/java/ubc/cosc322/driverCode/AIPlayerTest.java @@ -2,17 +2,13 @@ import java.util.*; -import com.beust.ah.A; import ubc.cosc322.algorithms.MonteCarloTreeSearch; -import ubc.cosc322.algorithms.Node; import ubc.cosc322.core.Board; -import ubc.cosc322.core.actionFactory.Action; import ygraph.ai.smartfox.games.GameMessage; import ygraph.ai.smartfox.games.BaseGameGUI; import ygraph.ai.smartfox.games.GameClient; import ygraph.ai.smartfox.games.GamePlayer; import ygraph.ai.smartfox.games.amazons.AmazonsGameMessage; -import ygraph.ai.smartfox.games.amazons.HumanPlayer; /** * A heavily documented and refactored version of AIPlayerTest which aims to @@ -26,7 +22,6 @@ public class AIPlayerTest extends GamePlayer { private GameClient gameClient = null; private BaseGameGUI gameGui = null; - public static int[][] mainBoardValues = new int[10][10]; // Assuming a 10x10 board private String userName; private String password; private String ourTeamColor = ""; @@ -38,6 +33,7 @@ public class AIPlayerTest extends GamePlayer { private ArrayList myNextPosition = new ArrayList<>(); private ArrayList myNextArrowPosition = new ArrayList<>(); MonteCarloTreeSearch mcts = new MonteCarloTreeSearch(); + private Board gameBoard = new Board(); /** * The entry point for the AI player. Initializes the player and sets up the GUI * if necessary. @@ -118,7 +114,7 @@ public boolean handleGameMessage(String messageType, Map msgDeta private void handleGameStateBoard(Map msgDetails) { ArrayList gameBoardState = (ArrayList) msgDetails.get(AmazonsGameMessage.GAME_STATE); gameGui.setGameState(gameBoardState); - setMainBoard(gameBoardState); + Board.setMainBoard(gameBoardState); } /** @@ -137,7 +133,6 @@ private void handleGameActionStart(Map msgDetails) { ourTeamColor = "Black"; opponentTeamColor = "White"; } - System.out.println(ourTeamColor + " == " + "Black"); System.out.println("Are we white? " + isAIPlayerWhite); if (ourTeamColor.equals("Black")) { generateAndSendMove(); // we make the first move @@ -145,10 +140,6 @@ private void handleGameActionStart(Map msgDetails) { System.out.println("Our team: " + ourTeamColor + " | Opponent team: " + opponentTeamColor); } - public static boolean getPlayerWhite(){ - return isAIPlayerWhite; - } - /** * Handles the game action move message. Updates the game state with the opponent's * last move and calculates the AI's next move. @@ -165,9 +156,9 @@ private void handleGameActionMove(Map msgDetails) { arrowPosition.get(0), arrowPosition.get(1))); gameGui.updateGameState(currentPosition, nextPosition, arrowPosition); - updateMainBoard(currentPosition, nextPosition, arrowPosition); + gameBoard.updateMainBoard(currentPosition, nextPosition, arrowPosition); System.out.println("Board After Opponent's Move"); - printMainBoard(); + gameBoard.printMainBoard(); generateAndSendMove(); } @@ -178,22 +169,14 @@ private void handleGameActionMove(Map msgDetails) { private void generateAndSendMove() { playerNo = Board.getBoardPlayerNo(isAIPlayerWhite); System.out.println("PlayerNo: " + playerNo); - Board bestMove = mcts.findNextMove(getMainBoard(), playerNo); - if (bestMove != null){ - System.out.println("success"); - } else { - System.out.println("fail"); - } - ArrayList moveDetails = Board.extractMoveDetails(getMainBoard(), bestMove); + Board bestMove = mcts.findNextMove(Board.getMainBoard(), playerNo); + ArrayList moveDetails = Board.extractMoveDetails(Board.getMainBoard(), bestMove); if (moveDetails.isEmpty()) { System.out.println("There are no moves for you to make. You lost."); - System.out.println("Number of times randomPlays was called: " + Board.randomPlays); - System.out.println("Number of nodes created: " + MonteCarloTreeSearch.numberOfNodes); } else { myCurrentPosition.clear(); myNextPosition.clear(); myNextArrowPosition.clear(); - System.out.println("Best Move Board moves successfully obtained"); System.out.printf("Our Move: Queen from [%d, %d] to [%d, %d], Arrow shot to [%d, %d]%n", moveDetails.get(0), moveDetails.get(1), moveDetails.get(2), moveDetails.get(3), moveDetails.get(4), moveDetails.get(5)); @@ -205,81 +188,14 @@ private void generateAndSendMove() { myNextArrowPosition.add(moveDetails.get(4)); // X coordinate myNextArrowPosition.add(moveDetails.get(5)); // Y coordinate - System.out.println("moves added to positions"); gameClient.sendMoveMessage(myCurrentPosition, myNextPosition, myNextArrowPosition); // we always need to update the game GUI and our internal board at the same time gameGui.updateGameState(myCurrentPosition, myNextPosition, myNextArrowPosition); - updateMainBoard(myCurrentPosition, myNextPosition, myNextArrowPosition); - System.out.println("moves sent to server"); + Board.updateMainBoard(myCurrentPosition, myNextPosition, myNextArrowPosition); System.out.println("Board After Our Move"); - printMainBoard(); - } - - } - - /** - * Generates a random position on the board. - * - * @param random Random generator to use for position generation. - * @return A random board position. - */ - private ArrayList generateRandomPosition(Random random) { - return new ArrayList<>(Arrays.asList(random.nextInt(10) + 1, random.nextInt(10) + 1)); - } - - public static void setMainBoard(ArrayList gameBoardState) { - // Initialize a new array to hold the updated board state - int[][] array = new int[10][10]; - int getVariable = 0; - // Iterate over the adjusted board state to populate the new 2D array - for(int i = 1; i < 11; i++){ - for(int j = 1; j < 11; j++){ - array[i-1][j-1] = gameBoardState.get(11*i + j); - } - } - System.out.println(Arrays.deepToString(mainBoardValues)); - // Update mainBoardValues with the new 2D array - mainBoardValues = array; - System.out.println(Arrays.deepToString(mainBoardValues)); - } - - public static void updateMainBoard(ArrayList currentPosition, - ArrayList nextPosition, - ArrayList arrowPosition) { - int currentX = currentPosition.get(0) - 1; - int currentY = currentPosition.get(1) - 1; - int nextX = nextPosition.get(0) - 1; - int nextY = nextPosition.get(1) - 1; - int arrowX = arrowPosition.get(0) - 1; - int arrowY = arrowPosition.get(1) - 1; - //System.out.println("Debug: printing mainBoardValues in updateMainBoard method:"); - //System.out.println(Arrays.deepToString(mainBoardValues)); - // Move piece to new position - //System.out.println("Debug: print mainBoardValues at currentX: "+(currentX+1)+" and currentY: "+(currentY+1)); - //System.out.println("Debug: mainBoardValues at currentX & currentY: "+mainBoardValues[currentX][currentY]); - int player = mainBoardValues[currentX][currentY]; // Get the player number from the current position - mainBoardValues[currentX][currentY] = 0; // Set current position to empty - //System.out.println("Debug: print mainBoardValues at nextX: "+(nextX+1)+" and nextY: "+(nextY+1)); - mainBoardValues[nextX][nextY] = player; // Move the player piece to the next position - //System.out.println("Debug: print mainBoardValues at arrowX: "+(arrowX+1)+" and currentY: "+(arrowY+1)); - // Place the arrow - mainBoardValues[arrowX][arrowY] = 3; - } - - public static Board getMainBoard(){ - Board board = new Board(); - board.setBoard(mainBoardValues); - return board; - } - - public void printMainBoard() { - for (int i = 9; i > -1; i--) { // Iterate through each row - for (int j = 0; j < 10; j++) { // Iterate through each column in the row - System.out.print(mainBoardValues[i][j] + " "); // Print the value at the current position - } - System.out.println(); // Move to the next line after printing each row + gameBoard.printMainBoard(); } } diff --git a/src/main/java/ubc/cosc322/driverCode/COSC322Test.java b/src/main/java/ubc/cosc322/driverCode/COSC322Test.java deleted file mode 100644 index 5f6e20f..0000000 --- a/src/main/java/ubc/cosc322/driverCode/COSC322Test.java +++ /dev/null @@ -1,198 +0,0 @@ - -package ubc.cosc322.driverCode; - -import java.util.*; - -import ubc.cosc322.core.actionFactory.Action; -import ubc.cosc322.core.actionFactory.ActionFactory; -import ygraph.ai.smartfox.games.GameMessage; -import ygraph.ai.smartfox.games.BaseGameGUI; -import ygraph.ai.smartfox.games.GameClient; -import ygraph.ai.smartfox.games.GamePlayer; -import ygraph.ai.smartfox.games.amazons.AmazonsGameMessage; - - -/** - * An example illustrating how to implement a GamePlayer - * @author Yong Gao (yong.gao@ubc.ca) - * Jan 5, 2021 - * - */ -public class COSC322Test extends GamePlayer{ - - private GameClient gameClient = null; - private BaseGameGUI gamegui = null; - - private String userName = "testRunG4"; - private String passwd = "testG4"; - private Boolean playerIsBlack = false; - - private ActionFactory a = new ActionFactory(); - - /** - * The main method - * @param args for name and passwd (current, any string would work) - */ - public static void main(String[] args) { - COSC322Test player = new COSC322Test(args[0], args[1]); - - if(player.getGameGUI() == null) { - player.Go(); - } - else { - BaseGameGUI.sys_setup(); - java.awt.EventQueue.invokeLater(new Runnable() { - public void run() { - player.Go(); - } - }); - } - } - - /** - * Any name and passwd - * @param userName - * @param passwd - */ - public COSC322Test(String userName, String passwd) { - this.userName = userName; - this.passwd = passwd; - - //To make a GUI-based player, create an instance of BaseGameGUI - //and implement the method getGameGUI() accordingly - this.gamegui = new BaseGameGUI(this); - } - - @Override - public void onLogin() { - userName = gameClient.getUserName(); - if (gamegui != null) { - gamegui.setRoomInformation(gameClient.getRoomList()); - } - } - - @Override - public boolean handleGameMessage(String messageType, Map msgDetails) { - System.out.println(messageType); - System.out.println(msgDetails); - - //This method will be called by the GameClient when it receives a game-related message - //from the server. - - //For a detailed description of the message types and format, - //see the method GamePlayer.handleGameMessage() in the game-client-api document. - - switch(messageType) { - case GameMessage.GAME_STATE_BOARD: - Object check = msgDetails.get(AmazonsGameMessage.GAME_STATE); - ArrayList gameBoardState = (ArrayList) check; - - //set game state - gamegui.setGameState(gameBoardState); - ArrayList moves = a.getActions(gameBoardState, 2); - break; - - case GameMessage.GAME_ACTION_START: - //handleGameMessage(GameMessage.GAME_STATE_BOARD, msgDetails); // Just use the last case - String playerNameBlack, playerNameWhite; - playerNameBlack = (String) msgDetails.get(AmazonsGameMessage.PLAYER_BLACK); - playerNameWhite = (String) msgDetails.get(AmazonsGameMessage.PLAYER_WHITE); - - //Change playerIsBlack variable here - - // TODO: Store player names as necessary - - break; - - case GameMessage.GAME_ACTION_MOVE: - // Update game state - - gamegui.updateGameState(msgDetails); - // Storing queen and arrow positions of previous move - ArrayList currentPositionResult = (ArrayList) msgDetails.get(AmazonsGameMessage.QUEEN_POS_CURR); - ArrayList nextPositionResult = (ArrayList) msgDetails.get(AmazonsGameMessage.QUEEN_POS_NEXT); - ArrayList arrowPositionResult = (ArrayList) msgDetails.get(AmazonsGameMessage.ARROW_POS); - - System.out.println(playerIsBlack); - - //Check if player is black, and only set currentPos to one of the queens that is ours - if(playerIsBlack){ - - }else{ - - } - - Random random = new Random(); - - ArrayList currentPos = new ArrayList<>(); - ArrayList nextPosition = new ArrayList<>(); - ArrayList arrowPosition = new ArrayList<>(); - System.out.println("BIG ERROR"); - currentPos.add(1); - currentPos.add(4); - - nextPosition.add(random.nextInt(10) + 1); - nextPosition.add(random.nextInt(10) + 1); - - arrowPosition.add(random.nextInt(10) + 1); - arrowPosition.add(random.nextInt(10) + 1); - - System.out.println(currentPos); - System.out.println(nextPosition); - System.out.println(arrowPosition); - - gameClient.sendMoveMessage(currentPos,nextPosition,arrowPosition); - // After updated game state calculate your move and send your move to the server using the method GameClient.sendMoveMessage(...) - break; - - default: - System.out.println("Unrecognized message received from server."); - return false; - } - - return true; - } - - public void sendMoveMessage() { -// // TODO Compute the move and send a message to the server -// int newYPos = (int) (Math.random() * 10); -// int newXPos = (int) (Math.random() * 10); -// -// int newXArrowPos = (int) (Math.random() * 10); -// int newYArrowPos = (int) (Math.random() * 10); -// -// queenPosNew.set(0, newXPos); -// queenPosNew.set(1, newYPos); -// -// arrowPos.set(0, newXArrowPos); -// arrowPos.set(1, newYArrowPos); -// // we have a 30s time limit, so we must send a move by then - - - } - - @Override - public String userName() { - return userName; - } - - @Override - public GameClient getGameClient() { - // TODO Auto-generated method stub - return this.gameClient; - } - - @Override - public BaseGameGUI getGameGUI() { - // TODO Auto-generated method stub - return gamegui; - } - - @Override - public void connect() { - // TODO Auto-generated method stub - this.gameClient = new GameClient(userName, passwd, this); - } - - -}//end of class