package comp1110.ass2; import java.util.*; /** * Object to store the game state * This stores the state of the game in a way that is easy to access and modify * It stores the board height, number of players, current player, current phase, islands, stones, * unclaimed resources and players (with their data) * */ public class State { // region Constants // Constants for the game. Changing these could create errors final int maxPlayers = 4; // endregion // region Variables final int boardHeight; private int numPlayers; private int currentPlayer; private int currentPhase; // 0 for exploration, 1 for settlement private Island[] islands; final private Coord[] stonesCoords; private Resource[] resources; private Player[] players; // endregion // region Setup methods/constructors /** * Constructor for the state object * This takes a string containing the state of the game and initialises the object * @param stateString String containing the state of the game */ public State(String stateString) { // Split the state string into its components String[] components = stateString.split(";"); // For the game initialisation part String[] gameInitComponents = components[0].trim().split(" "); boardHeight = Integer.parseInt(gameInitComponents[1]); numPlayers = Integer.parseInt(gameInitComponents[2]); // Current state part String[] currentStateComponents = components[1].trim().split(" "); currentPlayer = Integer.parseInt(currentStateComponents[1]); if (currentStateComponents[2].equals("E")) currentPhase = 0; else currentPhase = 1; // Islands part int islandcount = 0; for (int i=2; i< components.length; i++) { // Split island part String[] islandComponents = components[i].trim().split(" "); // Check if the component is still an island if (!islandComponents[0].equals("i")) break; Island tmpIsland = new Island(Integer.parseInt(islandComponents[1])); // Add each coord for (int j=2; j= maxPlayers) return; // There are already the maximum number of players Player[] oldPlayers = players; players = new Player[numPlayers+1]; if (numPlayers >= 0) System.arraycopy(oldPlayers, 0, players, 0, numPlayers); players[numPlayers] = new Player(numPlayers); numPlayers++; } /** * Distribute the resources to the stone circles randomly */ public void distributeResources() { if (stonesCoords.length != 32) return; // There are not enough stones to distribute resources // Create a random object and an arrays and list to shuffle the stone circles Random rand = new Random(); // Number of times to shuffle the stone circles (can be changed) int shuffle_number = 3; // Create a copy of the stone circle array to shuffle Coord[] stoneCircleRandom = stonesCoords; // Shuffle the stone circles the specified number of times for (int i = 0; i < shuffle_number; i++) { // Create a temporary array to store the shuffled stone circles Coord[] tempStoneCircleRandom = new Coord[32]; // Create a list to store the used cords (to avoid duplicates) List usedCords = new ArrayList(); // Shuffle the array for (int j = 0; j < 32; j++) { // For 0-31 generate a random cord from the stone circle array and check if it has been used int randomIndex = rand.nextInt(31); while (usedCords.contains(stonesCoords[randomIndex])) { // If it has been used, try the next in line if (randomIndex == 31) { randomIndex = 0; } else randomIndex++; } // If it hasn't been used, add it to the new array tempStoneCircleRandom[j] = stoneCircleRandom[randomIndex]; usedCords.add(stonesCoords[randomIndex]); } // Replace the old array with the new one stoneCircleRandom = tempStoneCircleRandom; } // Initialise unclaimed resources resources = new Resource[32]; // Create an array for each resource type char[] toDistribute = {'C', 'B', 'W', 'P'}; // Create a variable to keep track of how many resources have been sorted int numSorted = 0; // For each resource type for (char r:toDistribute){ // Assign 6 to a stone circle for (int i = 0; i < 6; i++){ resources[numSorted] = new Resource(r, stoneCircleRandom[numSorted]); numSorted++; } } // Assign 8 statuettes to a stone circle for (int i = 0; i < 8; i++){ resources[numSorted] = new Resource('S', stoneCircleRandom[numSorted]); numSorted++; } } // endregion // region Getters/Setters /** * Get the board height * @return int board height */ public int getBoardHeight() { return boardHeight; } /** * Get the number of players * @return int number of players */ public int getNumPlayers() { return numPlayers; } /** * Get the current player ID * @return int current player */ public int getCurrentPlayerID() { return currentPlayer; } /** * Get the current player * @return Player current player */ public Player getCurrentPlayer() { return players[currentPlayer]; } /** * Get the current phase * Returns 'E' for exploration, 'S' for settlement, 'O' for game over * @return char current phase */ public char getCurrentPhase() { if (currentPhase == 0) return 'E'; else if (currentPhase == 1) return 'S'; else return 'O'; // Return 'O' for game over } /** * Get the islands * @return Island[] islands */ public Island[] getIslands() { return islands; } /** * Get one island (by 0 index) * @return Island island */ public Island getIsland(int i) { return islands[i]; } /** * Get the stones * @return Coord[] stones */ public Coord[] getStones() { return stonesCoords; } /** * Check if a stone is at a given coordinate * @param coord Coord coordinate to check * @return boolean true if stone is at coordinate */ public boolean isStone(Coord coord) { for (Coord stone : stonesCoords) { if (stone.equals(coord)) return true; } return false; } /** * Get the player with a given index * @param i int index of player * @return Player player */ public Player getPlayer(int i) { return players[i]; } /** * Get the unclaimed resources * @return Resource[] unclaimed resources */ public Resource[] getResources() { return resources; } /** * Get the unclaimed resources of a given type * @param type char type of resource * @return Resource[] unclaimed resources of type */ public Resource[] getResources(char type) { Resource[] tmpResources = new Resource[resources.length]; int i = 0; for (Resource resource : resources) { if (resource.getType() == type){ tmpResources[i] = resource; i++; } } Resource[] resources = new Resource[i]; System.arraycopy(tmpResources, 0, resources, 0, i); return resources; } /** * Get the unclaimed resource at a given coordinate * @param coord Coord coordinate to check * @return Resource unclaimed resource at coordinate */ public Resource getUnclaimedResource(Coord coord) { for (Resource resource : resources) { if (resource.getCoord().equals(coord)) return resource; } return null; } // endregion // region Game play functions /** * Start next player's turn */ public void nextPlayer() { currentPlayer++; if (currentPlayer >= numPlayers) currentPlayer = 0; } /** * Start next phase */ public void nextPhase() { currentPhase++; } /** * Place a piece on the board. Uses current turn's player * This does not check if the move is valid (Use isValidMove() first) * @param coord Coord coordinate to place piece * @param type char type of piece */ public void placePiece(Coord coord, char type) { if (type == 'S') { players[currentPlayer].addSettler(coord); } else if (type == 'V' || type == 'T') { players[currentPlayer].addVillage(coord); } // Claim resource if it is a stone circle if (isStone(coord)) { for (Resource resource : resources) { if (resource.getCoord().equals(coord) && resource.isAvailable()) { players[currentPlayer].addResource(1, resource.getType()); resource.setClaimed(); } } } } /** * Is move valid? * @param coord Coord coordinate to place piece * @param type char type of piece * @return boolean true if move is valid */ public boolean isMoveValid(Coord coord, char type){ // Create moveString String moveString = type + " " + coord.toString(); return BlueLagoon.isMoveValid(this.toString(), moveString); } /** * is the phase over? * Defaults to simple mode */ public boolean isPhaseOver() { return isPhaseOver(true); } /** * is the phase over? * @param simple boolean don't check all player moves */ public boolean isPhaseOver(boolean simple){ boolean resourcesLeft = false; for (Resource r : resources) { if (r.isAvailable() && r.getType() != 'S') resourcesLeft = true; } boolean moveLeft = false; if (simple) { if (getCurrentPlayer().canPlay(this)) moveLeft = true; } else { for (Player player : players) { if (player.canPlay(this)) moveLeft = true; } } return !resourcesLeft || !moveLeft; } /** * Clean board for next phase */ public void cleanBoard(){ for (Player player : players) { player.clearSettlers(); player.removeResources(); for (Coord coord : player.getVillages()) { if (isStone(coord)) { player.removeVillage(coord); } } } } // endregion // region Scoring /** * Score for that phase * This does not automatically move to the next phase */ public void scorePhase() { for (Player p: players) { p.addScore(createScore(p.getPlayerID())); } } /** * Get score of player ID based on current phase and game state * @param playerID int player ID base 0 * @return int score */ public int createScore(int playerID) { int score = 0; score += scoreTotalIslands(playerID); score += scoreLinks(playerID); score += scoreMajorities(playerID); score += scoreResources(playerID); score += scoreStatuettes(playerID); return score; } /** * Get score from total islands * @param playerID int player to score * @return int score */ public int scoreTotalIslands(int playerID) { int score = 0; int islandCount = 0; for (Island island : islands) { // Get island coords Coord[] islandCoords = island.getCoords(); // Get player's coords Coord[] playerCoords = players[playerID].getPieces(); // Check if player has a piece on the island boolean hasPiece = false; for (Coord playerCoord : playerCoords) { for (Coord islandCoord : islandCoords) { if (playerCoord.equals(islandCoord)) { hasPiece = true; break; } } if (hasPiece) break; } if (hasPiece) islandCount++; } if (islandCount >= 8) score = 20; else if (islandCount == 7) score = 10; return score; } /** * Score majorities * @param playerID int player to score * @return int score */ public int scoreMajorities(int playerID){ int score = 0; for (Island island:islands){ int[] playerPieces = new int[getNumPlayers()]; for (int i = 0; i < getNumPlayers(); i++){ playerPieces[i] = players[i].getNumPiecesOnIsland(island); } boolean ishighest = true; int ties = 0; for (int i = 0; i < getNumPlayers(); i++){ if (i == playerID) continue; if (playerPieces[i] > playerPieces[playerID]) { ishighest = false; break; } if (playerPieces[i] == playerPieces[playerID]) ties++; } if (ishighest && playerPieces[playerID] > 0) { if (ties > 0){ score += island.getBonus()/(ties + 1); } else { score += island.getBonus(); } } } return score; } /** * Score Links * A (potentially) branching path of neighbouring settlers and villages * belonging to a player forms a chain. Players earn points from the chain * of their pieces which links the most islands. Players earn 5 points * per linked island in this chain. * @param playerID int player to score * @return int score */ public int scoreLinks(int playerID) { Coord[] playerCoords = players[playerID].getPieces(); Set playerCoordsSet = new HashSet<>(Arrays.asList(playerCoords)); int maxScore = findLongestLinkScore(playerCoordsSet, islands); return maxScore; } public static int findLongestLinkScore ( Set allCoords, Island[] islands) { Set longest = new HashSet<>(); Set current = new HashSet<>(); Coord now; int max = scoreForLink(longest,islands); for(Island islandIter : islands) { for ( Coord coords : allCoords) { if(islandIter.containsCoord(coords)) { now = coords; current.add(coords); recursionLink(allCoords, current, longest, now); // if the score is bigger, update the Set if(scoreForLink(current, islands) > max) { longest.clear(); longest.addAll(current); max = scoreForLink(longest, islands); } current = new HashSet<>(); } } } return max; } public static void recursionLink(Set allCoords, Set current, Set longest, Coord now) { current.add(now); for(Coord c : allCoords) { if( now.isAdjacentDiagonal(c) && !current.contains(c) ) { recursionLink(allCoords, current, longest, c); } } } public static int scoreForLink(Set longestLink, Island[] islands) { Set connectedIslands = new HashSet<>(); for (Coord c : longestLink) { for (Island i : islands) { if (i.containsCoord(c)) { connectedIslands.add(i); break; } } } return connectedIslands.size() * 5; } /** * Score resources * @param playerID int player to score * @return int score */ public int scoreResources(int playerID) { int score = 0; char[] resourceTypes = {'C', 'B', 'W', 'P'}; for (char type : resourceTypes) { int numResources = players[playerID].getNumResource(type); if (numResources >= 4) { score += 20; } else if (numResources == 3) { score += 10; } else if (numResources == 2) { score += 5; } } int numCoconuts = players[playerID].getNumResource('C'); int numBananas = players[playerID].getNumResource('B'); int numWater = players[playerID].getNumResource('W'); int numPreciousStones = players[playerID].getNumResource('P'); if (numCoconuts >= 1 && numBananas >= 1 && numWater >= 1 && numPreciousStones >= 1) { score += 10; } return score; } /** * Score statuettes * @param playerID int player to score * @return int score */ public int scoreStatuettes(int playerID) { return players[playerID].getNumResource('S') * 4; } // endregion // region String conversion /** * Convert board to stateString format string * @return String stateString */ @Override public String toString() { StringBuilder str = new StringBuilder("a " + boardHeight + " " + getNumPlayers() + "; c " + getCurrentPlayerID() + " " + getCurrentPhase() + "; "); for (Island island : islands) { str.append(island.toString()).append(" "); } str.append("s"); for (Coord s: stonesCoords) { str.append(" ").append(s.toString()); } str.append("; r"); char[] types = {'C', 'B', 'W', 'P', 'S'}; for (char type : types) { str.append(" ").append(type); for (Resource resource : resources) { if (resource.getType() == type && resource.isAvailable()) str.append(" ").append(resource.getCoord().toString()); } } str.append(";"); for (Player player : players) { str.append(" ").append(player.toString()).append(";"); } return str.toString(); } // endregion }