diff --git a/.factorypath b/.factorypath new file mode 100644 index 0000000..41be359 --- /dev/null +++ b/.factorypath @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..e9441bb --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.apt.core.prefs b/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 0000000..dfa4f3a --- /dev/null +++ b/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=true +org.eclipse.jdt.apt.genSrcDir=target/generated-sources/annotations +org.eclipse.jdt.apt.genTestSrcDir=target/generated-test-sources/test-annotations diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..8f693c0 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,9 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=21 +org.eclipse.jdt.core.compiler.compliance=21 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.processAnnotations=enabled +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=21 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/src/main/java/com/epita/creeps/AsyncExec.java b/src/main/java/com/epita/creeps/AsyncExec.java new file mode 100644 index 0000000..46b04f3 --- /dev/null +++ b/src/main/java/com/epita/creeps/AsyncExec.java @@ -0,0 +1,44 @@ +package com.epita.creeps; + + +import kong.unirest.core.HttpResponse; +import kong.unirest.core.JsonNode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.function.Supplier; + +// Handles server operations to respect commands delays +@NoArgsConstructor +public class AsyncExec { + + + private static float ticksPerSecond = 1; +// private CompletableFuture task; + + + public static void setTicksPerSecond(float ticksPerSecond) { + AsyncExec.ticksPerSecond = ticksPerSecond; + } + + public static long ticksToTime(long ticks) { + return Math.ceilDiv(ticks, (long)Math.ceil(ticksPerSecond)); + } + + public static CompletableFuture asyncExec(Supplier supplier, long time) { + return CompletableFuture.supplyAsync(supplier).thenApplyAsync(x -> x, + CompletableFuture.delayedExecutor(ticksToTime(time), TimeUnit.SECONDS)); + } + public static CompletableFuture thenAsyncExec(CompletableFuture base, Function f, long time) { + return base.thenApplyAsync(f, CompletableFuture.delayedExecutor(time, TimeUnit.SECONDS)); + } + + public static void justWait(long time) { + CompletableFuture.supplyAsync(() -> 0, CompletableFuture.delayedExecutor(time, TimeUnit.SECONDS)).join(); + } + +} diff --git a/src/main/java/com/epita/creeps/Basics.java b/src/main/java/com/epita/creeps/Basics.java deleted file mode 100644 index bd6c91a..0000000 --- a/src/main/java/com/epita/creeps/Basics.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.epita.creeps; - -import kong.unirest.core.HttpResponse; -import kong.unirest.core.JsonNode; -import kong.unirest.core.Unirest; -import kong.unirest.core.UnirestException; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import org.slf4j.Logger; - -@AllArgsConstructor -@Getter -public class Basics { - - private Logger logger; - private String srvUrl; - - public void connectAccount(String login) { - HttpResponse response; - try { - response = Unirest.post(srvUrl + "/init/" + login).asJson(); - logger.debug(response.getBody().toPrettyString()); - } catch (UnirestException e) { - logger.error("Cannot create account."); - } - } - - public void getReport (String reportId) { - HttpResponse response; - try { - response = Unirest.get(srvUrl + "/report/" + reportId).asJson(); - logger.debug(response.getBody().toPrettyString()); - } catch (UnirestException e) { - logger.error("Could not retrieve report"); - } - } - -} diff --git a/src/main/java/com/epita/creeps/Program.java b/src/main/java/com/epita/creeps/Program.java index 5c55103..1a881ef 100644 --- a/src/main/java/com/epita/creeps/Program.java +++ b/src/main/java/com/epita/creeps/Program.java @@ -1,18 +1,29 @@ package com.epita.creeps; +import com.epita.creeps.commands.Basics; +import com.epita.creeps.given.extra.Cartographer; +import com.epita.creeps.given.vo.geometry.Direction; +import com.epita.creeps.given.vo.geometry.Point; +import com.epita.creeps.given.vo.response.CommandResponse; +import com.epita.creeps.given.vo.response.InitResponse; +import com.epita.creeps.given.vo.response.StatisticsResponse; +import com.epita.creeps.units.*; import kong.unirest.core.HttpResponse; import kong.unirest.core.JsonNode; import kong.unirest.core.Unirest; import kong.unirest.core.UnirestException; +import lombok.Getter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.net.ConnectException; +import java.util.ArrayList; +import java.util.List; public class Program { private static String srvUrl; private static Logger logger; + @Getter private static String login; public static void main(String[] args) { @@ -23,19 +34,25 @@ public class Program { srvUrl = "http://localhost:1664"; login = "marion.mavie"; - // Classes - logger = LoggerFactory.getLogger(Program.class); - Basics basics = new Basics(logger, srvUrl); - logger.info("Initializing"); - + // Arguments + boolean default_values = true; if (args.length == 3) { srvUrl = "http://" + args[0] + ":" + args[1]; login = args[2]; } else { - logger.warn("No given args: using the default values"); + default_values = false; } + + // Classes + logger = LoggerFactory.getLogger(Program.class); + logger.info("Initializing"); + Basics.setSrvUrl(srvUrl); + Citizen.srvUrl = srvUrl; logger.debug("Using server " + srvUrl + " with player " + login); + if (default_values) + logger.warn("No given args: using the default values"); + // Test HttpResponse response; try { @@ -44,39 +61,23 @@ public class Program { logger.error("Cannot connect to the server. Aborting..."); throw e; } -// JsonNode jsonNode = response.getBody(); -// System.out.println(jsonNode); + // Create account and get init infos logger.info("Creating account"); - basics.connectAccount(login); + InitResponse initResponse = Basics.connectAccount(login); + + login = initResponse.login; // Just in case + AsyncExec.setTicksPerSecond((float) initResponse.setup.ticksPerSeconds); + Citizen citizen1 = new Citizen(login, initResponse.citizen1Id, initResponse.householdCoordinates); + Citizen citizen2 = new Citizen(login, initResponse.citizen2Id, initResponse.householdCoordinates); + Unit.getUnits().add(citizen1); + Unit.getUnits().add(citizen2); + + // Et voilĂ , machtou pichtou + logger.info("Done"); } -// ### Get statistics -//GET http://localhost:1664/statistics -// -//### Get status -//GET http://localhost:1664/status -// -//### Get report -//GET http://localhost:1664/report/148997e9b -// -//### Login with user login_l -//POST http://localhost:1664/init/login_l -// -//> {% -//client.global.set("baseId", response.body.baseId); -//client.global.set("probeId", response.body.probeId); -//client.global.set("login", response.body.login); -//%} -// -//### Post noop commande -//POST http://localhost:1664/command/{{login}}/{{probeId}}/noop -// -//### -//POST http://localhost:1664/command/dumeig_a/8d87eea10/inspect -// -//### } diff --git a/src/main/java/com/epita/creeps/ServerReponseException.java b/src/main/java/com/epita/creeps/ServerReponseException.java new file mode 100644 index 0000000..5757612 --- /dev/null +++ b/src/main/java/com/epita/creeps/ServerReponseException.java @@ -0,0 +1,7 @@ +package com.epita.creeps; + +public class ServerReponseException extends RuntimeException { + public ServerReponseException(String message) { + super(message); + } +} diff --git a/src/main/java/com/epita/creeps/commands/Basics.java b/src/main/java/com/epita/creeps/commands/Basics.java new file mode 100644 index 0000000..070fab9 --- /dev/null +++ b/src/main/java/com/epita/creeps/commands/Basics.java @@ -0,0 +1,129 @@ +package com.epita.creeps.commands; + +import com.epita.creeps.AsyncExec; +import com.epita.creeps.Program; +import com.epita.creeps.given.exception.NoReportException; +import com.epita.creeps.given.extra.Cartographer; +import com.epita.creeps.given.json.Json; +import com.epita.creeps.given.vo.report.*; +import com.epita.creeps.given.vo.response.InitResponse; +import com.epita.creeps.given.vo.response.StatisticsResponse; +import com.epita.creeps.units.BomberBot; +import com.epita.creeps.units.Turret; +import com.epita.creeps.units.Unit; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.JsonNode; +import kong.unirest.core.Unirest; +import kong.unirest.core.UnirestException; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import lombok.Setter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.CompletableFuture; + +@AllArgsConstructor +@Getter +public class Basics { + + private static final Logger logger = LoggerFactory.getLogger(Basics.class); + private static String srvUrl; + + public static void setSrvUrl(String srvUrl) { + Basics.srvUrl = srvUrl; + } + + public static InitResponse connectAccount(String login) { + try { + CompletableFuture> resp = AsyncExec.asyncExec(() -> Unirest.post(srvUrl + "/init/" + login).asJson(), 0); + HttpResponse response = (HttpResponse) resp.join(); +// logger.debug(response.getBody().toPrettyString()); + InitResponse initResponse = Json.parse(response.getBody().toString(), InitResponse.class); + if (initResponse.error != null) { + logger.error("Error in server response"); + throw new RuntimeException("Cannot create account"); + } + return initResponse; + } catch (UnirestException e) { + logger.error("Cannot create account."); + throw e; + } + } + + + public static StatisticsResponse getStatistics() { + try { + CompletableFuture> resp = AsyncExec.asyncExec(() -> Unirest.get(srvUrl + "/statistics").asJson(), 0); + HttpResponse response = resp.join(); + return Json.parse(response.getBody().toString(), StatisticsResponse.class); + } catch (UnirestException e) { + logger.error("Cannot retrieve statistics."); + throw e; + } + } + + // Asks the server for a certain report + public static Report getReport (String reportId) { + HttpResponse response = null; + try { + response = Unirest.get(srvUrl + "/report/" + reportId).asJson(); +// logger.debug("Got report: " + response.getBody().toPrettyString()); + Report report = Json.parseReport(response.getBody().toString()); + handleReport(report); + return report; + } catch (UnirestException e) { + logger.error("Could not retrieve report"); + throw new RuntimeException(e); + } catch (NoReportException e) { + logger.error("Could not parse report: not a report"); + logger.debug(response.getBody().toPrettyString()); + throw new RuntimeException(e); + } + } + + public static void handleReport(Report report) { + Cartographer c = Cartographer.INSTANCE; + if (report.opcode.startsWith("farm")) { + c.register((FarmReport) report); + } + else if (report.opcode.startsWith("gather")) { + c.register((GatherReport) report); + } + else if (report.opcode.startsWith("move")) { + c.register((MoveReport) report); + } + else if (report.opcode.startsWith("build")) { + c.register((BuildReport) report); + } + else if (report.opcode.startsWith("observe")) { + c.register((ObserveReport) report); + } + else if (report.opcode.startsWith("spawn")) + { + SpawnReport spawnReport = (SpawnReport) report; + if (spawnReport.errorCode != null) { + logger.warn("Could not spawn unit: got report with an error: " + report.errorCode); + } + if (spawnReport.spawnedUnit.opcode.endsWith("turret")) { + Turret turret = new Turret(Program.getLogin(), spawnReport.spawnedUnitId, spawnReport.spawnedUnit.position); + Unit.getUnits().add(turret); + } + else if (spawnReport.spawnedUnit.opcode.endsWith("bomber-bot")) { + BomberBot bombhero = new BomberBot(Program.getLogin(), spawnReport.spawnedUnitId, spawnReport.spawnedUnit.position); + Unit.getUnits().add(bombhero); + } + else { + logger.warn("Spawned unit is an unknown type. Ignored..."); + } + } + else if (report.opcode.startsWith("fire")) { + logger.debug("FIRREEE!!"); + } + else { + logger.warn("Unknown opcode '" + report.opcode + "' in the server report. Ignored..."); + } + } + +} diff --git a/src/main/java/com/epita/creeps/commands/ObeserveReport.java b/src/main/java/com/epita/creeps/commands/ObeserveReport.java new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/com/epita/creeps/units/BomberBot.java b/src/main/java/com/epita/creeps/units/BomberBot.java new file mode 100644 index 0000000..c499dea --- /dev/null +++ b/src/main/java/com/epita/creeps/units/BomberBot.java @@ -0,0 +1,26 @@ +package com.epita.creeps.units; + +import com.epita.creeps.given.extra.Cartographer; +import com.epita.creeps.given.json.Json; +import com.epita.creeps.given.vo.geometry.Point; +import com.epita.creeps.given.vo.parameter.FireParameter; + +import java.util.List; + +public class BomberBot extends Unit { + + public BomberBot(String login, String id, Point position) { + super(login, id, position); + } + + // Retrieves all units that are turrets + public static List getBomberBotUnits() { + return getUnits().stream().filter(unit -> unit.getClass() == BomberBot.class).toList(); + } + + public BomberBot fire() { + FireParameter fp = new FireParameter(this.position); + sendActionWithBody("fire:bomber-bot", Json.serialize(fp), 2); + return this; + } +} diff --git a/src/main/java/com/epita/creeps/units/Building.java b/src/main/java/com/epita/creeps/units/Building.java new file mode 100644 index 0000000..2ceebd7 --- /dev/null +++ b/src/main/java/com/epita/creeps/units/Building.java @@ -0,0 +1,14 @@ +package com.epita.creeps.units; + +public enum Building { + TOWNHALL("town-hall"), + HOUSEHOLD("household"), + SAWMILL("sawmill"), + SMELTERY("smeltery"), + ROAD("road"); + + public final String name; + private Building(final String building) { + this.name = building; + } +} diff --git a/src/main/java/com/epita/creeps/units/Citizen.java b/src/main/java/com/epita/creeps/units/Citizen.java new file mode 100644 index 0000000..6d2b4a5 --- /dev/null +++ b/src/main/java/com/epita/creeps/units/Citizen.java @@ -0,0 +1,70 @@ +package com.epita.creeps.units; + +import com.epita.creeps.AsyncExec; +import com.epita.creeps.ServerReponseException; +import com.epita.creeps.commands.Basics; +import com.epita.creeps.given.exception.NoReportException; +import com.epita.creeps.given.json.Json; +import com.epita.creeps.given.vo.geometry.Direction; +import com.epita.creeps.given.vo.geometry.Point; +import com.epita.creeps.given.vo.parameter.MessageParameter; +import com.epita.creeps.given.vo.report.MoveReport; +import com.epita.creeps.given.vo.report.Report; +import com.epita.creeps.given.vo.response.CommandResponse; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.JsonNode; +import kong.unirest.core.Unirest; + +import java.util.concurrent.CompletableFuture; + + +public class Citizen extends Unit { + + public Citizen(String login, String citizen_id, Point position) { + super(login, citizen_id, position); + } + + public Citizen move(Direction direction) { + sendAction("move:"+direction.direction, 2); + return this; + } + public Citizen observe() { + sendAction("observe", 1); + return this; + } + public Citizen gather() { + sendAction("gather", 4); + return this; + } + public Citizen unload() { + sendAction("unload", 3); + return this; + } + public Citizen farm() { + sendAction("farm", 10); + return this; + } + public Citizen build(Building building) { + sendAction("build:" + building.name, 20); + return this; + } + public Citizen spawn(String unit) { + sendAction("spawn:" + unit, 6); + return this; + } + public Citizen refine(String resource) { + sendAction("refine:" + resource, 8); + return this; + } + public Citizen sendMessage(String receiver, String message) { + MessageParameter mp = new MessageParameter(receiver, message); + sendActionWithBody("message:send", Json.serialize(mp), 1); + return this; + } + public Citizen fetchMessages() { + sendAction("message:fetch", 1); + return this; + } + + +} diff --git a/src/main/java/com/epita/creeps/units/Turret.java b/src/main/java/com/epita/creeps/units/Turret.java new file mode 100644 index 0000000..f2ba638 --- /dev/null +++ b/src/main/java/com/epita/creeps/units/Turret.java @@ -0,0 +1,27 @@ +package com.epita.creeps.units; + +import com.epita.creeps.given.extra.Cartographer; +import com.epita.creeps.given.json.Json; +import com.epita.creeps.given.vo.geometry.Point; +import com.epita.creeps.given.vo.parameter.FireParameter; + +import java.util.List; + +public class Turret extends Unit { + + public Turret(String login, String id, Point position) { + super(login, id, position); + } + + // Retrieves all units that are turrets + public static List getTurretUnits() { + return getUnits().stream().filter(unit -> unit.getClass() == Turret.class).toList(); + } + + public Turret fire(Point target) { + FireParameter fp = new FireParameter(target); + Cartographer cartographer = Cartographer.INSTANCE; + sendActionWithBody("fire:turret", Json.serialize(fp), 2); + return this; + } +} diff --git a/src/main/java/com/epita/creeps/units/Unit.java b/src/main/java/com/epita/creeps/units/Unit.java new file mode 100644 index 0000000..d508fa4 --- /dev/null +++ b/src/main/java/com/epita/creeps/units/Unit.java @@ -0,0 +1,113 @@ +package com.epita.creeps.units; + +import com.epita.creeps.AsyncExec; +import com.epita.creeps.ServerReponseException; +import com.epita.creeps.commands.Basics; +import com.epita.creeps.given.json.Json; +import com.epita.creeps.given.vo.geometry.Point; +import com.epita.creeps.given.vo.report.Report; +import com.epita.creeps.given.vo.response.CommandResponse; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.JsonNode; +import kong.unirest.core.Unirest; +import lombok.Getter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public abstract class Unit { + + @Getter + private static final List units = new ArrayList<>(); + public static String srvUrl; + public static final Logger logger = LoggerFactory.getLogger(Unit.class); + + protected final String id; + @Getter + protected final Point position; + protected final String command_uri; + protected CompletableFuture> pendingAction; + protected CompletableFuture pendingReport; + protected Report lastReport; + protected boolean idle = true; + + public Unit(String login, String id, Point position) { + if (srvUrl == null) { + throw new RuntimeException("Tried to create a citizen without properly initializing static fields"); + } + this.id = id; + this.position = position; + command_uri = srvUrl + "/command/" + login + "/" + id + "/"; + } + + // Sends a specific action to the server using it's HTTPS string representation + // Sets pending action accordingly and asks for a report after `delay` ticks + public void sendActionWithBody(String actionCode, String body, long delay) { + if (!idle) { +// logger.warn("Unit is already busy, queuing action"); + this.waitFinished(); + } + // Do + idle = false; + pendingAction = AsyncExec.asyncExec(() -> Unirest.post(command_uri + actionCode).body(body).asJson(), delay) + .thenApplyAsync( x -> x ); + // Get report + pendingReport = getCommandReport(); + } + + // Just sends an action with an empty body + public void sendAction(String actionCode, long delay) { + sendActionWithBody(actionCode, "{}", delay); + } + + public void upgrade() { + sendAction("upgrade", 1); + } + public void noop() { + sendAction("noop", 1); + } + + /* + * @return true if action succeeded, false otherwise + */ + public boolean waitFinished() { + if (idle) { + logger.warn("Tried to wait for an idle citizen"); + return false; + } + if (pendingAction == null || pendingReport == null) { + logger.warn("Tried to wait a citizen with no pending action or report"); + return false; + } + lastReport = pendingReport.join(); + if (lastReport == null) { + logger.warn("Invalid report received: null value"); + return false; + } +// logger.debug("Got report: " + lastReport); + idle = true; + + return lastReport.errorCode != null; + } + + + // Retrieves the report of the current pending action from the server + private CompletableFuture getCommandReport() { + if (pendingAction == null) { + logger.warn("Tried to retrieve report but there's no pending action"); + return null; + } + return AsyncExec.thenAsyncExec(pendingAction, x -> { + CommandResponse cr = Json.parse(x.getBody().toString(), CommandResponse.class); + if (cr.error != null) { + logger.debug("Server reponse: " + cr.toString()); + throw new ServerReponseException("Error retrieving the report id"); + } + String reportId = cr.reportId; + return Basics.getReport(reportId); + }, 0); + } +}