package comp1110.ass2; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Set; public class BlueLagoon { // The Game Strings for five maps have been created for you. // They have only been encoded for two players. However, they are // easily extendable to more by adding additional player statements. // region Checks on strings /** * Check if the string encoding of the game state is well-formed. * Note that this does not mean checking that the state is valid * (represents a state that players could reach in game play), * only that the string representation is syntactically well-formed. *
* A description of the state string will be included in README.md * in an update of the project after D2B is complete. * * @param stateString a string representing a game state * @return true if stateString is well-formed and false otherwise */ public static boolean isStateStringWellFormed(String stateString){ // Create an array of regex strings to match the state string // The state string contains 5 parts, each of which is matched by a regex string String[] matchArray = new String[6]; // For the gameArrangementStatement use the following regex string matchArray[0] = "a \\d{1,2} \\d{1,2}; "; // For the currentStateStatement use the following regex string matchArray[1] = "c \\d{1,2} [E|S]; "; // For the islandStatement use the following regex string matchArray[2] = "(i \\d{1,2} (\\d{1,2},\\d{1,2} )*\\d{1,2},\\d{1,2}; )*"; // For the stonesStatement use the following regex string matchArray[3] = "(s (\\d{1,2},\\d{1,2} )+\\d{1,2},\\d{1,2}; )"; // For the resources and statuettes use the following regex string matchArray[4] = "r C (\\d{1,2},\\d{1,2} )*B (\\d{1,2},\\d{1,2} )*W (\\d{1,2},\\d{1,2} )*P (\\d{1,2},\\d{1,2} " + ")*S( \\d{1,2},\\d{1,2})*;"; // For the playersStatement use the following regex string matchArray[5] = "( p \\d \\d{1,3} \\d{1,2} \\d{1,2} \\d{1,2} \\d{1,2} \\d{1,2} S (\\d{1,2},\\d{1,2} )*T( " + "(\\d{1,2},\\d{1,2} ?)*)?;)*"; // Combine the regex strings into one string to match the state string StringBuilder matchString = new StringBuilder(); for (String match:matchArray) { matchString.append(match); } // Check if the state string matches the regex string if (!stateString.matches(matchString.toString())) return false; // Check that there is one and only one of each player id // This fixed test 2-3 of D2DTests.testIsStateStringWellFormed int numPlayers = Character.getNumericValue(stateString.charAt(stateString.indexOf(";")-1)); for (int i = 0; i < numPlayers; i++) { if (stateString.length() - stateString.replaceAll("p "+i,"").length() != 3) return false; } return true; } /** * Check if the string encoding of the move is syntactically well-formed. *
* A description of the move string will be included in README.md * in an update of the project after D2B is complete. * * @param moveString a string representing a player's move * @return true if moveString is well-formed and false otherwise * * coordinate = row , col (i.e. "0,1" means row 0 col 1) */ public static boolean isMoveStringWellFormed(String moveString){ return moveString.matches("[ST] \\d{1,2},\\d{1,2}"); // If the 1st element of moveString is neither a "S" nor a "T" return false // if the 2nd element is not a whitespace return false // if the 3rd and/or 4th element (as long as it is before ",") are not // digits, return false // if the 6th and/or 7th element (as long as it is after ",") are not digits, // return false } // endregion // region Distribute resources /** * Given a state string which is yet to have resources distributed amongst the stone circles, * randomly distribute the resources and statuettes between all the stone circles. *
* There will always be exactly 32 stone circles. *
* The resources and statuettes to be distributed are: * - 6 coconuts * - 6 bamboo * - 6 water * - 6 precious stones * - 8 statuettes *
* The distribution must be random. * * @param stateString a string representing a game state without resources distributed * @return a string of the game state with resources randomly distributed */ public static String distributeResources(String stateString) { State state = new State(stateString); state.distributeResources(); return state.toString(); } // endregion // region Check and generate moves /** * Given a state string and a move string, determine if the move is * valid for the current player. *
* For a move to be valid, the player must have enough pieces left to * play the move. The following conditions for each phase must also * be held. *
* In the Exploration Phase, the move must either be: * - A settler placed on any unoccupied sea space * - A settler or a village placed on any unoccupied land space * adjacent to one of the player's pieces. *
* In the Settlement Phase, the move must be:
* - Only a settler placed on an unoccupied space adjacent to
* one of the player's pieces.
* Importantly, players can now only play on the sea if it is
* adjacent to a piece they already own.
*
* @param stateString a string representing a game state
* @param moveString a string representing the current player's move
* @return true if the current player can make the move and false otherwise
*/
public static boolean isMoveValid(String stateString, String moveString) {
// Check if the inputs are wellFormed or not
if (!isStateStringWellFormed(stateString)) return false;
if (!isMoveStringWellFormed(moveString)) return false;
String[] parts = stateString.split("; ?");
// List of initializations used
String currentPhase = "";
// Coords of the island tiles
ArrayList
* A move is playable if it is valid.
*
* @param stateString a string representing a game state
* @return a set of strings representing all moves the current player can play
*/
public static Set
* A phase is over when either of the following conditions hold:
* - All resources (not including statuettes) have been collected.
* - No player has any remaining valid moves.
*
* @param stateString a string representing a game state
* @return true if the state is at the end of either phase and false otherwise
*/
public static boolean isPhaseOver(String stateString){
State state = new State(stateString);
return state.isPhaseOver();
}
/**
* Given a state string and a move string, place the piece associated with the
* move on the board. Ensure the player collects any corresponding resource or
* statuettes.
*
* Do not handle switching to the next player here.
*
* @param stateString a string representing a game state
* @param moveString a string representing the current player's move
* @return a new state string achieved by placing the move on the board
*/
public static String placePiece(String stateString, String moveString){
State state = new State(stateString);
char pieceType = moveString.charAt(0);
String coordStr = moveString.substring(2);
int y = Integer.parseInt(coordStr.split(",")[0]);
int x = Integer.parseInt(coordStr.split(",")[1]);
Coord coord = new Coord(y, x);
state.placePiece(coord, pieceType);
return state.toString();
}
/**
* Given a state string, calculate the "Islands" portion of the score for
* each player as if it were the end of a phase. The return value is an
* integer array sorted by player number containing the calculated score
* for the respective player.
*
* The "Islands" portion is calculated for each player as follows:
* - If the player has pieces on 8 or more islands, they score 20 points.
* - If the player has pieces on 7 islands, they score 10 points.
* - No points are scored otherwise.
*
* @param stateString a string representing a game state
* @return an integer array containing the calculated "Islands" portion of
* the score for each player
*/
public static int[] calculateTotalIslandsScore(String stateString) {
State state = new State(stateString);
int[] scores = new int[state.getNumPlayers()];
for (int i = 0; i < state.getNumPlayers(); i++) {
scores[i] = state.scoreTotalIslands(i);
}
return scores;
}
/**
* Given a state string, calculate the "Links" portion of the score for
* each player as if it were the end of a phase. The return value is an
* integer array sorted by player number containing the calculated score
* for the respective player.
*
* Players earn points for their chain of pieces that links the most
* islands. For each island linked by this chain, they score 5 points.
*
* Note the chain needn't be a single path. For instance, if the chain
* splits into three or more sections, all of those sections are counted
* towards the total.
*
* @param stateString a string representing a game state
* @return an integer array containing the calculated "Links" portion of
* the score for each player
*/
public static int[] calculateIslandLinksScore(String stateString){
State state = new State(stateString);
int[] scores = new int[state.getNumPlayers()];
for (int i = 0; i < state.getNumPlayers(); i++) {
scores[i] = state.scoreLinks(i);
}
return scores;
}
/**
* Given a state string, calculate the "Majorities" portion of the score for
* each player as if it were the end of a phase. The return value is an
* integer array sorted by player number containing the calculated score
* for the respective player.
*
* The "Majorities" portion is calculated for each island as follows:
* - The player with the most pieces on the island scores the number
* of points that island is worth.
* - In the event of a tie for pieces on an island, those points are
* divided evenly between those players rounding down. For example,
* if two players tied for an island worth 7 points, they would
* receive 3 points each.
* - No points are awarded for islands without any pieces.
*
* @param stateString a string representing a game state
* @return an integer array containing the calculated "Majorities" portion
* of the score for each player
*/
public static int[] calculateIslandMajoritiesScore(String stateString){
State state = new State(stateString);
int[] scores = new int[state.getNumPlayers()];
for (int i = 0; i < state.getNumPlayers(); i++) {
scores[i] = state.scoreMajorities(i);
}
return scores;
}
/**
* Given a state string, calculate the "Resources" and "Statuettes" portions
* of the score for each player as if it were the end of a phase. The return
* value is an integer array sorted by player number containing the calculated
* score for the respective player.
*
* Note that statuettes are not resources.
*
* In the below "matching" means a set of the same resources.
*
* The "Resources" portion is calculated for each player as follows:
* - For each set of 4+ matching resources, 20 points are scored.
* - For each set of exactly 3 matching resources, 10 points are scored.
* - For each set of exactly 2 matching resources, 5 points are scored.
* - If they have all four resource types, 10 points are scored.
*
* The "Statuettes" portion is calculated for each player as follows:
* - A player is awarded 4 points per statuette in their possession.
*
* @param stateString a string representing a game state
* @return an integer array containing the calculated "Resources" and "Statuettes"
* portions of the score for each player
*/
public static int[] calculateResourcesAndStatuettesScore(String stateString){
State state = new State(stateString);
int[] scores = new int[state.getNumPlayers()];
for (int i = 0; i < state.getNumPlayers(); i++) {
scores[i] = state.scoreResources(i)+state.scoreStatuettes(i);
}
return scores;
}
/**
* Given a state string, calculate the scores for each player as if it were
* the end of a phase. The return value is an integer array sorted by player
* number containing the calculated score for the respective player.
*
* It is recommended to use the other scoring functions to assist with this
* task.
*
* @param stateString a string representing a game state
* @return an integer array containing the calculated scores for each player
*/
public static int[] calculateScores(String stateString){
State state = new State(stateString);
int[] scores = new int[state.getNumPlayers()];
for (int i = 0; i < state.getNumPlayers(); i++) {
scores[i] = state.createScore(i);
}
return scores;
}
// endregion
/**
* Given a state string representing an end of phase state, return a new state
* achieved by following the end of phase rules. Do not move to the next player
* here.
*
* In the Exploration Phase, this means:
* - The score is tallied for each player.
* - All pieces are removed from the board excluding villages not on stone circles.
* - All resources and statuettes remaining on the board are removed. All resources are then
* randomly redistributed between the stone circles.
*
* In the Settlement Phase, this means:
* - Only the score is tallied and added on for each player.
*
* @param stateString a string representing a game state at the end of a phase
* @return a string representing the new state achieved by following the end of phase rules
*/
public static String endPhase(String stateString){
State state = new State(stateString);
state.scorePhase();
if (state.getCurrentPhase() == 'E') {
state.cleanBoard();
state.distributeResources();
state.nextPhase();
}
return state.toString();
}
// 2 phases, exploration and settlement
/**
* Given a state string and a move string, apply the move to the board.
*
* If the move ends the phase, apply the end of phase rules.
*
* Advance current player to the next player in turn order that has a valid
* move they can make.
*
* @param stateString a string representing a game state
* @param moveString a string representing the current player's move
* @return a string representing the new state after the move is applied to the board
*/
public static String applyMove(String stateString, String moveString){
State state = new State(stateString);
char pieceType = moveString.charAt(0);
String coordStr = moveString.substring(2);
int y = Integer.parseInt(coordStr.split(",")[0]);
int x = Integer.parseInt(coordStr.split(",")[1]);
Coord coord = new Coord(y, x);
// if the move is valid, place it
if ( isMoveValid(stateString, moveString)) state.placePiece(coord, pieceType);
// if the move ends the phase
if (state.isPhaseOver()){
// Applying end of Phase rules
// For Exploration Phase
// Tally up the score, clean the board, distribute resources, change to next Phase
if (state.getCurrentPhase() == 'E') {
state.scorePhase();
state.cleanBoard();
state.distributeResources();
state.nextPhase();
}
// For Settlement Phase
// Tally up the score
else if (state.getCurrentPhase() == 'S') {
state.scorePhase();
}
}
// After the endPhase is over, move to the next player
state.nextPlayer();
// if the current player cannot play the move, move to the next player
if (!state.getCurrentPlayer().canPlay(state)) state.nextPlayer();
return state.toString();
}
//upStream pull lol
/**
* Given a state string, returns a valid move generated by your AI.
*
* As a hint, generateAllValidMoves() may prove a useful starting point,
* maybe if you could use some form of heuristic to see which of these
* moves is best?
*
* Your AI should perform better than randomly generating moves,
* see how good you can make it!
*
* @param stateString a string representing a game state
* @return a move string generated by an AI
*/
public static String generateAIMove(String stateString){
State state = new State(stateString);
return state.getCurrentPlayer().createAIMove(state);
}
}