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/Program.java b/src/main/java/com/epita/creeps/Program.java
index 1d34f99..1a881ef 100644
--- a/src/main/java/com/epita/creeps/Program.java
+++ b/src/main/java/com/epita/creeps/Program.java
@@ -1,8 +1,83 @@
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.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) {
- System.out.println("Hello World!");
+
+ //Initialization
+
+ // Default Vars
+ srvUrl = "http://localhost:1664";
+ login = "marion.mavie";
+
+ // Arguments
+ boolean default_values = true;
+ if (args.length == 3) {
+ srvUrl = "http://" + args[0] + ":" + args[1];
+ login = args[2];
+ } else {
+ 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 {
+ response = Unirest.get(srvUrl + "/status").asJson();
+ } catch (UnirestException e) {
+ logger.error("Cannot connect to the server. Aborting...");
+ throw e;
+ }
+
+ // Create account and get init infos
+ logger.info("Creating account");
+ 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");
}
+
+
}
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);
+ }
+}