diff --git a/.gitignore b/.gitignore index 5fe5b57..e28257b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +.classpath +.project +.settings/** *.iml .idea **/target @@ -6,3 +9,5 @@ logs **/*.log output/move.txt +/target/ +**/*.class diff --git a/pom.xml b/pom.xml index a1b0466..7e72ac9 100644 --- a/pom.xml +++ b/pom.xml @@ -52,6 +52,12 @@ jackson-databind 2.2.3 + + + com.google.api-client + google-api-client-gson + 1.18.0-rc + @@ -93,4 +99,4 @@ - \ No newline at end of file + diff --git a/run.sh b/run.sh old mode 100644 new mode 100755 diff --git a/src/main/java/za/co/entelect/challenge/bot/BasicBot.java b/src/main/java/za/co/entelect/challenge/bot/BasicBot.java index a2e0151..3004356 100644 --- a/src/main/java/za/co/entelect/challenge/bot/BasicBot.java +++ b/src/main/java/za/co/entelect/challenge/bot/BasicBot.java @@ -1,23 +1,19 @@ package za.co.entelect.challenge.bot; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.jayway.jsonpath.JsonPath; -import com.jayway.jsonpath.PathNotFoundException; -import net.minidev.json.JSONArray; import za.co.entelect.challenge.dto.GameState; -import za.co.entelect.challenge.dto.Missile; import za.co.entelect.challenge.dto.Player; import za.co.entelect.challenge.dto.Settings; -import za.co.entelect.challenge.dto.enums.EntityType; import za.co.entelect.challenge.dto.enums.ShipCommand; +import za.co.entelect.challenge.dto.reader.BasicGameStateReader; +import za.co.entelect.challenge.dto.reader.GameStateReader; +import za.co.entelect.challenge.dto.reader.GsonGameStateReader; +import za.co.entelect.challenge.dto.reader.JacksonGameStateReader; import za.co.entelect.challenge.utils.BotHelper; import za.co.entelect.challenge.utils.FileHelper; import za.co.entelect.challenge.utils.LogHelper; import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; import java.util.Random; @@ -34,12 +30,13 @@ public BasicBot(Settings settings) { public void execute() { //Choose how you want to access the JSON - boolean basicAccess = false; - - gameState = basicAccess ? loadBasicState() : loadAdvancedState(); - - loadAdvancedState(); + GameStateReader reader = + new BasicGameStateReader(); + //new JacksonGameStateReader(); + //new GsonGameStateReader(); + gameState = loadGameState(reader); + logMatchState(); StringBuilder map = loadMap(); @@ -49,62 +46,15 @@ public void execute() { saveMove(move); } - /** - * This method accesses the json elements directly - * Advantage: Fast and no need initialise everything - only use what you need - * Disadvantage: Match/Game State model only partially initialised - * - * @return match - */ - private GameState loadBasicState() { - try { - GameState gameState = new GameState(); - - File jsonFile = FileHelper.getFile(settings.getDefaultOutputFolder(), settings.getStateFile()); - - loadRoundNumber(jsonFile, gameState); - - String player1Path = "$.Players[0]"; - String player2Path = "$.Players[1]"; - - Player player1 = loadPlayer(jsonFile, player1Path); - Player player2 = loadPlayer(jsonFile, player2Path); - - gameState.getPlayers().add(player1); - gameState.getPlayers().add(player2); - - return gameState; - } catch (IOException ioe) { - LogHelper.log("Unable to read state file: " + settings.getStateFile()); - ioe.printStackTrace(); - return null; - } catch (NumberFormatException nfe) { - LogHelper.log("Unable to convert Round Number to int: " + settings.getStateFile()); - nfe.printStackTrace(); - return null; - } - } - - /** - * This method initialises the entire Game State model using Jackson - * Advantage: All elements are accessible - * Disadvantage: slower than method loadBasicState() - * - * @return match - */ - private GameState loadAdvancedState() { + private GameState loadGameState(GameStateReader reader) { GameState gameState = null; File jsonFile = FileHelper.getFile(settings.getDefaultOutputFolder(), settings.getStateFile()); - // ObjectMapper provides functionality for data binding between - // Java Bean Objects/POJO and JSON constructs/string - ObjectMapper mapper = new ObjectMapper(); - try { - gameState = mapper.readValue(jsonFile, GameState.class); - } catch (IOException ioe) { - LogHelper.log("Unable to read state file: " + settings.getStateFile()); + gameState = reader.read(jsonFile); + } catch (Exception ioe) { + LogHelper.log("Error reading state file: " + settings.getStateFile()); ioe.printStackTrace(); return null; } @@ -112,65 +62,6 @@ private GameState loadAdvancedState() { return gameState; } - private void loadRoundNumber(File jsonFile, GameState gameState) throws IOException { - String roundNumber = "$.RoundNumber"; - gameState.setRoundNumber(Integer.valueOf(JsonPath.read(jsonFile, roundNumber).toString())); - } - - private Player loadPlayer(File jsonFile, String playerPath) throws IOException { - Player player = new Player(); - - try { - LinkedHashMap playerMap = JsonPath.read(jsonFile, playerPath); - - player.setPlayerName((String)playerMap.get("PlayerName")); - player.setPlayerNumber((Integer)playerMap.get("PlayerNumber")); - player.setPlayerNumberReal((Integer)playerMap.get("PlayerNumberReal")); - player.setKills((Integer)playerMap.get("Kills")); - player.setLives((Integer)playerMap.get("Lives")); - player.setMissileLimit((Integer)playerMap.get("MissileLimit")); - - JSONArray missiles = (JSONArray)playerMap.get("Missiles"); - - player.setMissiles(loadMissiles(missiles)); - - } catch (PathNotFoundException pnfe) { - LogHelper.log("Index out of bounds when evaluating path " + playerPath); - pnfe.printStackTrace(); - } - - return player; - } - - private List loadMissiles(JSONArray missiles) { - List playerMissiles = new ArrayList<>(); - Missile playerMissile; - - for (Object missile : missiles) { - playerMissile = new Missile(); - - LinkedHashMap playerMissilesMap = (LinkedHashMap) missile; - - playerMissile.setAlive((Boolean)playerMissilesMap.get("Alive")); - playerMissile.setX((Integer)playerMissilesMap.get("x")); - playerMissile.setY((Integer)playerMissilesMap.get("y")); - playerMissile.setWidth((Integer)playerMissilesMap.get("Width")); - playerMissile.setHeight((Integer)playerMissilesMap.get("Height")); - for (EntityType type : EntityType.values()) { - if (((String)playerMissilesMap.get("Type")).equalsIgnoreCase(type.toString())) { - playerMissile.setType(type); - break; - } - } - playerMissile.setPlayerNumber((Integer)playerMissilesMap.get("PlayerNumber")); - playerMissile.setActionRate((Integer)playerMissilesMap.get("ActionRate")); - - playerMissiles.add(playerMissile); - } - - return playerMissiles; - } - private void logMatchState() { LogHelper.log(LogHelper.PREFIX + "Game state:"); LogHelper.log("\tRound: " + gameState.getRoundNumber()); @@ -221,6 +112,4 @@ private void saveMove(String move) { ioe.printStackTrace(); } } - - } diff --git a/src/main/java/za/co/entelect/challenge/dto/AlienManager.java b/src/main/java/za/co/entelect/challenge/dto/AlienManager.java index 2332ae9..a65e685 100644 --- a/src/main/java/za/co/entelect/challenge/dto/AlienManager.java +++ b/src/main/java/za/co/entelect/challenge/dto/AlienManager.java @@ -1,7 +1,10 @@ package za.co.entelect.challenge.dto; import com.fasterxml.jackson.annotation.*; +import com.google.gson.annotations.SerializedName; + import javax.annotation.Generated; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -20,16 +23,22 @@ public class AlienManager { @JsonProperty("PlayerNumber") + @SerializedName("PlayerNumber") private Integer playerNumber; @JsonProperty("Disabled") + @SerializedName("Disabled") private Boolean disabled; @JsonProperty("Waves") + @SerializedName("Waves") private List> waves = new ArrayList>(); @JsonProperty("ShotEnergyCost") + @SerializedName("ShotEnergyCost") private Integer shotEnergyCost; @JsonProperty("ShotEnergy") + @SerializedName("ShotEnergy") private Integer shotEnergy; @JsonProperty("DeltaX") + @SerializedName("DeltaX") private Integer deltaX; @JsonIgnore private Map additionalProperties = new HashMap(); diff --git a/src/main/java/za/co/entelect/challenge/dto/BuildingsAvailable.java b/src/main/java/za/co/entelect/challenge/dto/BuildingsAvailable.java index 0a333b4..09e235d 100644 --- a/src/main/java/za/co/entelect/challenge/dto/BuildingsAvailable.java +++ b/src/main/java/za/co/entelect/challenge/dto/BuildingsAvailable.java @@ -1,8 +1,10 @@ package za.co.entelect.challenge.dto; import com.fasterxml.jackson.annotation.*; +import com.google.gson.annotations.SerializedName; import javax.annotation.Generated; + import java.util.HashMap; @JsonInclude(JsonInclude.Include.NON_NULL) @@ -15,10 +17,13 @@ public class BuildingsAvailable { @JsonProperty("Command") + @SerializedName("Command") private String command; @JsonProperty("Type") + @SerializedName("Type") private String type; @JsonProperty("Cost") + @SerializedName("Cost") private Integer cost; @JsonIgnore private java.util.Map additionalProperties = new HashMap(); diff --git a/src/main/java/za/co/entelect/challenge/dto/GameState.java b/src/main/java/za/co/entelect/challenge/dto/GameState.java index 4a26320..1aebb3a 100644 --- a/src/main/java/za/co/entelect/challenge/dto/GameState.java +++ b/src/main/java/za/co/entelect/challenge/dto/GameState.java @@ -1,7 +1,10 @@ package za.co.entelect.challenge.dto; import com.fasterxml.jackson.annotation.*; +import com.google.gson.annotations.SerializedName; + import javax.annotation.Generated; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -18,14 +21,19 @@ public class GameState { @JsonProperty("BuildingsAvailable") + @SerializedName("BuildingsAvailable") private List buildingsAvailable = new ArrayList(); @JsonProperty("Map") + @SerializedName("Map") private Map map; @JsonProperty("Players") + @SerializedName("Players") private List players = new ArrayList(); @JsonProperty("RoundNumber") + @SerializedName("RoundNumber") private Integer roundNumber; @JsonProperty("RoundLimit") + @SerializedName("RoundLimit") private Integer roundLimit; @JsonIgnore private java.util.Map additionalProperties = new HashMap(); diff --git a/src/main/java/za/co/entelect/challenge/dto/Map.java b/src/main/java/za/co/entelect/challenge/dto/Map.java index 9270efa..5dd9b8c 100644 --- a/src/main/java/za/co/entelect/challenge/dto/Map.java +++ b/src/main/java/za/co/entelect/challenge/dto/Map.java @@ -1,8 +1,10 @@ package za.co.entelect.challenge.dto; import com.fasterxml.jackson.annotation.*; +import com.google.gson.annotations.SerializedName; import javax.annotation.Generated; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -17,10 +19,13 @@ public class Map { @JsonProperty("Width") + @SerializedName("Width") private Integer width; @JsonProperty("Height") + @SerializedName("Height") private Integer height; @JsonProperty("Rows") + @SerializedName("Rows") private List> rows = new ArrayList>(); @JsonIgnore private java.util.Map additionalProperties = new HashMap(); diff --git a/src/main/java/za/co/entelect/challenge/dto/Player.java b/src/main/java/za/co/entelect/challenge/dto/Player.java index be0f383..c4e1b8d 100644 --- a/src/main/java/za/co/entelect/challenge/dto/Player.java +++ b/src/main/java/za/co/entelect/challenge/dto/Player.java @@ -1,8 +1,10 @@ package za.co.entelect.challenge.dto; import com.fasterxml.jackson.annotation.*; +import com.google.gson.annotations.SerializedName; import javax.annotation.Generated; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -27,30 +29,43 @@ public class Player { @JsonProperty("PlayerNumberReal") + @SerializedName("PlayerNumberReal") private Integer playerNumberReal; @JsonProperty("PlayerNumber") + @SerializedName("PlayerNumber") private Integer playerNumber; @JsonProperty("PlayerName") + @SerializedName("PlayerName") private String playerName; @JsonProperty("Ship") + @SerializedName("Ship") private Object ship; @JsonProperty("Kills") + @SerializedName("Kills") private Integer kills; @JsonProperty("Lives") + @SerializedName("Lives") private Integer lives; @JsonProperty("RespawnTimer") + @SerializedName("RespawnTimer") private Integer respawnTimer; @JsonProperty("Missiles") + @SerializedName("Missiles") private List missiles = new ArrayList(); @JsonProperty("MissileLimit") + @SerializedName("MissileLimit") private Integer missileLimit; @JsonProperty("AlienWaveSize") + @SerializedName("AlienWaveSize") private Integer alienWaveSize; @JsonProperty("AlienFactory") + @SerializedName("AlienFactory") private Object alienFactory; @JsonProperty("MissileController") + @SerializedName("MissileController") private Object missileController; @JsonProperty("AlienManager") + @SerializedName("AlienManager") private za.co.entelect.challenge.dto.AlienManager alienManager; @JsonIgnore private java.util.Map additionalProperties = new HashMap(); diff --git a/src/main/java/za/co/entelect/challenge/dto/Row.java b/src/main/java/za/co/entelect/challenge/dto/Row.java index 668fecd..e4fdb14 100644 --- a/src/main/java/za/co/entelect/challenge/dto/Row.java +++ b/src/main/java/za/co/entelect/challenge/dto/Row.java @@ -1,8 +1,10 @@ package za.co.entelect.challenge.dto; import com.fasterxml.jackson.annotation.*; +import com.google.gson.annotations.SerializedName; import javax.annotation.Generated; + import java.util.HashMap; @JsonInclude(JsonInclude.Include.NON_NULL) @@ -20,20 +22,28 @@ public class Row { @JsonProperty("Id") + @SerializedName("Id") private Integer id; @JsonProperty("Alive") + @SerializedName("Alive") private Boolean alive; - @JsonProperty("x") + @JsonProperty("X") + @SerializedName("X") private Integer x; - @JsonProperty("y") + @JsonProperty("Y") + @SerializedName("Y") private Integer y; @JsonProperty("Width") + @SerializedName("Width") private Integer width; @JsonProperty("Height") + @SerializedName("Height") private Integer height; @JsonProperty("Type") + @SerializedName("Type") private String type; @JsonProperty("PlayerNumber") + @SerializedName("PlayerNumber") private Integer playerNumber; @JsonIgnore private java.util.Map additionalProperties = new HashMap(); diff --git a/src/main/java/za/co/entelect/challenge/dto/Wave.java b/src/main/java/za/co/entelect/challenge/dto/Wave.java index f72e008..f39ec7c 100644 --- a/src/main/java/za/co/entelect/challenge/dto/Wave.java +++ b/src/main/java/za/co/entelect/challenge/dto/Wave.java @@ -1,8 +1,10 @@ package za.co.entelect.challenge.dto; import com.fasterxml.jackson.annotation.*; +import com.google.gson.annotations.SerializedName; import javax.annotation.Generated; + import java.util.HashMap; @JsonInclude(JsonInclude.Include.NON_NULL) @@ -23,26 +25,37 @@ public class Wave { @JsonProperty("DeltaY") + @SerializedName("DeltaY") private Integer deltaY; @JsonProperty("DeltaX") + @SerializedName("DeltaX") private Integer deltaX; @JsonProperty("Command") + @SerializedName("Command") private Integer command; @JsonProperty("Id") + @SerializedName("Id") private Integer id; @JsonProperty("Alive") + @SerializedName("Alive") private Boolean alive; - @JsonProperty("x") + @JsonProperty("X") + @SerializedName("X") private Integer x; - @JsonProperty("y") + @JsonProperty("Y") + @SerializedName("Y") private Integer y; @JsonProperty("Width") + @SerializedName("Width") private Integer width; @JsonProperty("Height") + @SerializedName("Height") private Integer height; @JsonProperty("Type") + @SerializedName("Type") private String type; @JsonProperty("PlayerNumber") + @SerializedName("PlayerNumber") private Integer playerNumber; @JsonIgnore private java.util.Map additionalProperties = new HashMap(); diff --git a/src/main/java/za/co/entelect/challenge/dto/reader/BasicGameStateReader.java b/src/main/java/za/co/entelect/challenge/dto/reader/BasicGameStateReader.java new file mode 100644 index 0000000..668d91b --- /dev/null +++ b/src/main/java/za/co/entelect/challenge/dto/reader/BasicGameStateReader.java @@ -0,0 +1,108 @@ +package za.co.entelect.challenge.dto.reader; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; + +import net.minidev.json.JSONArray; + +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.PathNotFoundException; + +import za.co.entelect.challenge.dto.GameState; +import za.co.entelect.challenge.dto.Missile; +import za.co.entelect.challenge.dto.Player; +import za.co.entelect.challenge.dto.enums.EntityType; +import za.co.entelect.challenge.utils.LogHelper; + +/** + * This class accesses the json elements directly + * Advantage: Fast and no need initialise everything - only use what you need + * Disadvantage: Match/Game State model only partially initialised + */ +public class BasicGameStateReader implements GameStateReader +{ + @Override + public GameState read(File jsonFile) throws IOException { + try { + GameState gameState = new GameState(); + + loadRoundNumber(jsonFile, gameState); + + String player1Path = "$.Players[0]"; + String player2Path = "$.Players[1]"; + + Player player1 = loadPlayer(jsonFile, player1Path); + Player player2 = loadPlayer(jsonFile, player2Path); + + gameState.getPlayers().add(player1); + gameState.getPlayers().add(player2); + + return gameState; + } catch (NumberFormatException nfe) { + throw new RuntimeException("Unable to convert Round Number to int", nfe); + } + } + + private void loadRoundNumber(File jsonFile, GameState gameState) throws IOException { + String roundNumber = "$.RoundNumber"; + gameState.setRoundNumber(Integer.valueOf(JsonPath.read(jsonFile, roundNumber).toString())); + } + + private Player loadPlayer(File jsonFile, String playerPath) throws IOException { + Player player = new Player(); + + try { + LinkedHashMap playerMap = JsonPath.read(jsonFile, playerPath); + + player.setPlayerName((String)playerMap.get("PlayerName")); + player.setPlayerNumber((Integer)playerMap.get("PlayerNumber")); + player.setPlayerNumberReal((Integer)playerMap.get("PlayerNumberReal")); + player.setKills((Integer)playerMap.get("Kills")); + player.setLives((Integer)playerMap.get("Lives")); + player.setMissileLimit((Integer)playerMap.get("MissileLimit")); + + JSONArray missiles = (JSONArray)playerMap.get("Missiles"); + + player.setMissiles(loadMissiles(missiles)); + + } catch (PathNotFoundException pnfe) { + LogHelper.log("Index out of bounds when evaluating path " + playerPath); + pnfe.printStackTrace(); + } + + return player; + } + + private List loadMissiles(JSONArray missiles) { + List playerMissiles = new ArrayList<>(); + Missile playerMissile; + + for (Object missile : missiles) { + playerMissile = new Missile(); + + @SuppressWarnings("unchecked") + LinkedHashMap playerMissilesMap = (LinkedHashMap) missile; + + playerMissile.setAlive((Boolean)playerMissilesMap.get("Alive")); + playerMissile.setX((Integer)playerMissilesMap.get("x")); + playerMissile.setY((Integer)playerMissilesMap.get("y")); + playerMissile.setWidth((Integer)playerMissilesMap.get("Width")); + playerMissile.setHeight((Integer)playerMissilesMap.get("Height")); + for (EntityType type : EntityType.values()) { + if (((String)playerMissilesMap.get("Type")).equalsIgnoreCase(type.toString())) { + playerMissile.setType(type); + break; + } + } + playerMissile.setPlayerNumber((Integer)playerMissilesMap.get("PlayerNumber")); + playerMissile.setActionRate((Integer)playerMissilesMap.get("ActionRate")); + + playerMissiles.add(playerMissile); + } + + return playerMissiles; + } +} diff --git a/src/main/java/za/co/entelect/challenge/dto/reader/GameStateReader.java b/src/main/java/za/co/entelect/challenge/dto/reader/GameStateReader.java new file mode 100644 index 0000000..fb3cc7c --- /dev/null +++ b/src/main/java/za/co/entelect/challenge/dto/reader/GameStateReader.java @@ -0,0 +1,11 @@ +package za.co.entelect.challenge.dto.reader; + +import java.io.File; +import java.io.IOException; + +import za.co.entelect.challenge.dto.GameState; + +public interface GameStateReader +{ + public GameState read(File jsonFile) throws IOException; +} diff --git a/src/main/java/za/co/entelect/challenge/dto/reader/GsonGameStateReader.java b/src/main/java/za/co/entelect/challenge/dto/reader/GsonGameStateReader.java new file mode 100644 index 0000000..32da0a7 --- /dev/null +++ b/src/main/java/za/co/entelect/challenge/dto/reader/GsonGameStateReader.java @@ -0,0 +1,20 @@ +package za.co.entelect.challenge.dto.reader; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; + +import com.google.gson.Gson; + +import za.co.entelect.challenge.dto.GameState; + +public class GsonGameStateReader implements GameStateReader +{ + @Override + public GameState read(File jsonFile) throws IOException { + + try (FileReader reader = new FileReader(jsonFile)) { + return (new Gson()).fromJson(reader, GameState.class); + } + } +} diff --git a/src/main/java/za/co/entelect/challenge/dto/reader/JacksonGameStateReader.java b/src/main/java/za/co/entelect/challenge/dto/reader/JacksonGameStateReader.java new file mode 100644 index 0000000..5ddce2f --- /dev/null +++ b/src/main/java/za/co/entelect/challenge/dto/reader/JacksonGameStateReader.java @@ -0,0 +1,20 @@ +package za.co.entelect.challenge.dto.reader; + +import java.io.File; +import java.io.IOException; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import za.co.entelect.challenge.dto.GameState; + +public class JacksonGameStateReader implements GameStateReader +{ + public GameState read(File jsonFile) throws IOException { + + // ObjectMapper provides functionality for data binding between + // Java Bean Objects/POJO and JSON constructs/string + ObjectMapper mapper = new ObjectMapper(); + + return mapper.readValue(jsonFile, GameState.class); + } +}