diff --git a/pom.xml b/pom.xml
index fcc0726..7c59dcc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -79,7 +79,6 @@
dotenv-java
3.0.0
-
diff --git a/src/main/java/cz/trailsthroughshadows/algorithm/Dungeon.java b/src/main/java/cz/trailsthroughshadows/algorithm/Dungeon.java
index c542bb8..a96e0c0 100644
--- a/src/main/java/cz/trailsthroughshadows/algorithm/Dungeon.java
+++ b/src/main/java/cz/trailsthroughshadows/algorithm/Dungeon.java
@@ -1,232 +1,231 @@
-package cz.trailsthroughshadows.algorithm;
-
-import cz.trailsthroughshadows.algorithm.entity.Entity;
-import cz.trailsthroughshadows.algorithm.location.LocationImpl;
-import cz.trailsthroughshadows.algorithm.util.ListUtil;
-import cz.trailsthroughshadows.api.table.action.Action;
-import cz.trailsthroughshadows.api.table.action.attack.Attack;
-import cz.trailsthroughshadows.api.table.action.movement.Movement;
-import cz.trailsthroughshadows.api.table.action.restorecards.RestoreCards;
-import cz.trailsthroughshadows.api.table.action.skill.Skill;
-import cz.trailsthroughshadows.api.table.action.summon.Summon;
-import cz.trailsthroughshadows.api.table.action.summon.SummonAction;
-import cz.trailsthroughshadows.api.table.effect.Effect;
-import cz.trailsthroughshadows.api.table.enemy.Enemy;
-import cz.trailsthroughshadows.api.table.playerdata.character.Character;
-import cz.trailsthroughshadows.api.table.schematic.hex.Hex;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-import lombok.extern.slf4j.Slf4j;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-@Getter
-@AllArgsConstructor
-@Slf4j
-public class Dungeon {
-
- private final ArrayList enemies = new ArrayList<>();
- private final ArrayList characters = new ArrayList<>();
- private final ArrayList summons = new ArrayList<>();
- private final LocationImpl location;
-
- public String entityToString(Entity entity) {
- StringBuilder res = new StringBuilder();
- res.append("%s %s".formatted(entity.getClass().getSimpleName(), entity.getName()));
-
- if (entity instanceof Character character)
- res.append(" (%s)".formatted(character.getPlayerName()));
-
- Hex hex = entity.getHex();
- if (hex != null)
- res.append(" [%d]".formatted(entity.getHex().getKey().getId()));
- else res.append(" [no hex]");
-
- return res.toString();
- }
-
- public boolean isAlly(Entity e1, Entity e2) {
- if ((e1 instanceof Character || e1 instanceof Summon) && (e2 instanceof Character || e2 instanceof Summon)) {
- return true;
- }
- return e1 instanceof Enemy && e2 instanceof Enemy;
- }
-
- public void applyEffect(Entity entity, Effect effect) {
- log.info("\t\tApplying effect {} to {}", effect.toString(), entityToString(entity));
-
- entity.getActiveEffects().add(effect);
- // todo logic
- }
-
- public void damageEntity(Entity entity, int damage) {
- log.info("\t\tDamaging {} for {}", entityToString(entity), damage);
-
- switch (entity) {
- case Character character -> log.info("\t\t\tCharacter can't be damaged yet"); // TODO
- case Enemy enemy -> enemy.setBaseHealth(enemy.getBaseHealth() - damage);
- case Summon summon -> summon.setHealth(summon.getHealth() - damage);
- case null, default -> throw new IllegalStateException("Unexpected value: " + entity);
- }
-
- if (entity instanceof Enemy enemy) {
- if (enemy.getBaseHealth() <= 0) {
- log.info("\t\t{} died", entityToString(entity));
- enemies.remove(enemy);
- }
- }
- if (entity instanceof Summon summon) {
- if (summon.getHealth() <= 0) {
- log.info("\t\t{} died", entityToString(entity));
- summons.remove(summon);
- }
- }
- }
-
- public void moveCharacter(Character character, Hex hex) {
- character.setHex(hex);
- }
-
- public void moveEnemy(Enemy enemy, Hex hex) {
- enemy.setHex(hex);
- }
-
- public List calculateTarget(Entity entity, Effect.EffectTarget target) {
- return calculateTarget(entity, target, 0);
- }
-
- public List calculateTarget(Entity entity, Effect.EffectTarget target, int range) {
- List targets = new ArrayList<>();
- Hex hex = entity.getHex();
-
- log.info("Calculating target for " + entity + " at " + hex + " with range " + range + " and target " + target);
-
- switch (target) {
- case SELF:
- targets.add(entity);
- break;
- case ALL_ALLIES:
- targets.addAll(characters);
- targets.addAll(summons);
- break;
- case ALL_ENEMIES:
- targets.addAll(enemies);
- break;
- case ALL:
- targets.addAll(ListUtil.union(characters, enemies, summons).stream()
- .filter(ent -> location.getDistance(hex, ent.getHex()) <= range)
- .toList());
- break;
- case ONE:
- ListUtil.union(characters, enemies, summons).stream()
- .filter(ent -> location.getDistance(hex, ent.getHex()) <= range)
- .min((ent1, ent2) -> location.getDistance(hex, ent1.getHex()) - location.getDistance(hex, ent2.getHex()))
- .ifPresent(targets::add);
- break;
- default:
- throw new IllegalStateException("Unexpected value: " + target);
- }
-
- return targets.stream().sorted((ent1, ent2) -> location.getDistance(hex, ent1.getHex()) - location.getDistance(hex, ent2.getHex())).toList();
- }
-
- public void evaluateMovement(Entity entity, Movement movement) {
- log.info("Evaluating movement {} ({}) for {}", movement.getType(), movement.getRange(), entityToString(entity));
-
- // todo i dont want to do movement
- }
-
- public void evaluateSummon(Entity entity, Summon summon, int range) {
- log.info("\tEvaluating summon {} for {} with range {}", entityToString(summon), entityToString(entity), range);
-
- // todo
- }
-
- public void evaluateSummons(Entity entity, Collection summons) {
- log.info("Evaluating {} summons for {}", summons.size(), entityToString(entity));
-
- for (SummonAction summonAction : summons) {
- evaluateSummon(entity, summonAction.getSummon(), summonAction.getRange());
- }
- }
-
- public void evaluateSkill(Entity entity, Skill skill) {
- log.info("Evaluating skill {} for {}", skill.toString(), entityToString(entity));
-
- for (Effect effect : skill.getEffects()) {
- List targets = calculateTarget(entity, effect.getTarget(), skill.getRange());
- log.info("\tSkill: {} targets with range {}", targets.size(), skill.getRange());
- for (Entity target : targets) {
- applyEffect(target, effect);
- }
- }
- }
-
- public void evaluateAttack(Entity entity, Attack attack) {
- log.info("Evaluating attack {} for {}", attack.toString(), entityToString(entity));
-
- for (int i = 0; i < attack.getNumAttacks(); i++) {
- List targets = calculateTarget(entity, attack.getTarget(), attack.getRange());
- log.info("\tAttack {}: {} targets with range {}", i + 1, targets.size(), attack.getRange());
- for (Entity target : targets) {
- if (isAlly(entity, target)) {
- log.info("\t\t{} and {} are friends", entityToString(entity), entityToString(target));
- continue;
- }
-
- damageEntity(target, attack.getDamage());
- for (Effect effect : attack.getEffects()) {
- applyEffect(target, effect);
- }
- }
- }
- }
-
- public void evaluateRestoreCards(Entity entity, RestoreCards restoreCards) {
- log.info("Evaluating restoreCards {} ({}) for {}", restoreCards.getNumCards(), restoreCards.getTarget(), entityToString(entity));
- int remaining = restoreCards.getNumCards();
-
- for (Entity target : calculateTarget(entity, restoreCards.getTarget())) {
- if (!isAlly(entity, target)) continue;
-
- log.info("\tRestoring cards for {}", entityToString(target));
-// for (Action action : target.getActions()) {
-// if (remaining == 0) {
-// return;
+//package cz.trailsthroughshadows.algorithm;
+//
+//import cz.trailsthroughshadows.algorithm.entity.Entity;
+//import cz.trailsthroughshadows.algorithm.location.LocationImpl;
+//import cz.trailsthroughshadows.algorithm.util.List;
+//import cz.trailsthroughshadows.api.table.action.Action;
+//import cz.trailsthroughshadows.api.table.action.attack.Attack;
+//import cz.trailsthroughshadows.api.table.action.movement.Movement;
+//import cz.trailsthroughshadows.api.table.action.restorecards.RestoreCards;
+//import cz.trailsthroughshadows.api.table.action.skill.Skill;
+//import cz.trailsthroughshadows.api.table.action.summon.Summon;
+//import cz.trailsthroughshadows.api.table.action.summon.SummonAction;
+//import cz.trailsthroughshadows.api.table.effect.Effect;
+//import cz.trailsthroughshadows.api.table.enemy.Enemy;
+//import cz.trailsthroughshadows.api.table.playerdata.character.Character;
+//import cz.trailsthroughshadows.api.table.schematic.hex.Hex;
+//import lombok.AllArgsConstructor;
+//import lombok.Getter;
+//import lombok.extern.slf4j.Slf4j;
+//
+//import java.util.ArrayList;
+//import java.util.Collection;
+//
+//@Getter
+//@AllArgsConstructor
+//@Slf4j
+//public class Dungeon {
+//
+// private final ArrayList enemies = new ArrayList<>();
+// private final ArrayList characters = new ArrayList<>();
+// private final ArrayList summons = new ArrayList<>();
+// private final LocationImpl location;
+//
+// public String entityToString(Entity entity) {
+// StringBuilder res = new StringBuilder();
+// res.append("%s %s".formatted(entity.getClass().getSimpleName(), entity.getName()));
+//
+// if (entity instanceof Character character)
+// res.append(" (%s)".formatted(character.getPlayerName()));
+//
+// Hex hex = entity.getHex();
+// if (hex != null)
+// res.append(" [%d]".formatted(entity.getHex().getKey().getId()));
+// else res.append(" [no hex]");
+//
+// return res.toString();
+// }
+//
+// public boolean isAlly(Entity e1, Entity e2) {
+// if ((e1 instanceof Character || e1 instanceof Summon) && (e2 instanceof Character || e2 instanceof Summon)) {
+// return true;
+// }
+// return e1 instanceof Enemy && e2 instanceof Enemy;
+// }
+//
+// public void applyEffect(Entity entity, Effect effect) {
+// log.info("\t\tApplying effect {} to {}", effect.toString(), entityToString(entity));
+//
+// entity.getActiveEffects().add(effect);
+// // todo logic
+// }
+//
+// public void damageEntity(Entity entity, int damage) {
+// log.info("\t\tDamaging {} for {}", entityToString(entity), damage);
+//
+// switch (entity) {
+// case Character character -> log.info("\t\t\tCharacter can't be damaged yet"); // TODO
+// case Enemy enemy -> enemy.setBaseHealth(enemy.getBaseHealth() - damage);
+// case Summon summon -> summon.setHealth(summon.getHealth() - damage);
+// case null, default -> throw new IllegalStateException("Unexpected value: " + entity);
+// }
+//
+// if (entity instanceof Enemy enemy) {
+// if (enemy.getBaseHealth() <= 0) {
+// log.info("\t\t{} died", entityToString(entity));
+// enemies.remove(enemy);
+// }
+// }
+// if (entity instanceof Summon summon) {
+// if (summon.getHealth() <= 0) {
+// log.info("\t\t{} died", entityToString(entity));
+// summons.remove(summon);
+// }
+// }
+// }
+//
+// public void moveCharacter(Character character, Hex hex) {
+// character.setHex(hex);
+// }
+//
+// public void moveEnemy(Enemy enemy, Hex hex) {
+// enemy.setHex(hex);
+// }
+//
+// public java.util.List calculateTarget(Entity entity, Effect.EffectTarget target) {
+// return calculateTarget(entity, target, 0);
+// }
+//
+// public java.util.List calculateTarget(Entity entity, Effect.EffectTarget target, int range) {
+// java.util.List targets = new ArrayList<>();
+// Hex hex = entity.getHex();
+//
+// log.info("Calculating target for " + entity + " at " + hex + " with range " + range + " and target " + target);
+//
+// switch (target) {
+// case SELF:
+// targets.add(entity);
+// break;
+// case ALL_ALLIES:
+// targets.addAll(characters);
+// targets.addAll(summons);
+// break;
+// case ALL_ENEMIES:
+// targets.addAll(enemies);
+// break;
+// case ALL:
+// targets.addAll(List.union(characters, enemies, summons).stream()
+// .filter(ent -> location.getDistance(hex, ent.getHex()) <= range)
+// .toList());
+// break;
+// case ONE:
+// List.union(characters, enemies, summons).stream()
+// .filter(ent -> location.getDistance(hex, ent.getHex()) <= range)
+// .min((ent1, ent2) -> location.getDistance(hex, ent1.getHex()) - location.getDistance(hex, ent2.getHex()))
+// .ifPresent(targets::add);
+// break;
+// default:
+// throw new IllegalStateException("Unexpected value: " + target);
+// }
+//
+// return targets.stream().sorted((ent1, ent2) -> location.getDistance(hex, ent1.getHex()) - location.getDistance(hex, ent2.getHex())).toList();
+// }
+//
+// public void evaluateMovement(Entity entity, Movement movement) {
+// log.info("Evaluating movement {} ({}) for {}", movement.getType(), movement.getRange(), entityToString(entity));
+//
+// // todo i dont want to do movement
+// }
+//
+// public void evaluateSummon(Entity entity, Summon summon, int range) {
+// log.info("\tEvaluating summon {} for {} with range {}", entityToString(summon), entityToString(entity), range);
+//
+// // todo
+// }
+//
+// public void evaluateSummons(Entity entity, Collection summons) {
+// log.info("Evaluating {} summons for {}", summons.size(), entityToString(entity));
+//
+// for (SummonAction summonAction : summons) {
+// evaluateSummon(entity, summonAction.getSummon(), summonAction.getRange());
+// }
+// }
+//
+// public void evaluateSkill(Entity entity, Skill skill) {
+// log.info("Evaluating skill {} for {}", skill.toString(), entityToString(entity));
+//
+// for (Effect effect : skill.getEffects()) {
+// java.util.List targets = calculateTarget(entity, effect.getTarget(), skill.getRange());
+// log.info("\tSkill: {} targets with range {}", targets.size(), skill.getRange());
+// for (Entity target : targets) {
+// applyEffect(target, effect);
+// }
+// }
+// }
+//
+// public void evaluateAttack(Entity entity, Attack attack) {
+// log.info("Evaluating attack {} for {}", attack.toString(), entityToString(entity));
+//
+// for (int i = 0; i < attack.getNumAttacks(); i++) {
+// java.util.List targets = calculateTarget(entity, attack.getTarget(), attack.getRange());
+// log.info("\tAttack {}: {} targets with range {}", i + 1, targets.size(), attack.getRange());
+// for (Entity target : targets) {
+// if (isAlly(entity, target)) {
+// log.info("\t\t{} and {} are friends", entityToString(entity), entityToString(target));
+// continue;
// }
//
-// if (action.getDiscarded() && action.getDiscard() != Action.Discard.PERMANENT) {
-// action.setDiscarded(false);
-// log.info("\t\tRestored {} for {}", action.getTitle(), entityToString(target));
-// remaining -= 1;
+// damageEntity(target, attack.getDamage());
+// for (Effect effect : attack.getEffects()) {
+// applyEffect(target, effect);
// }
// }
- }
- }
-
- public void evaluateAction(Entity entity, Action action) {
- log.info("Evaluating action {} for {}", action.getTitle(), entityToString(entity));
- log.info("\tAction: {}", action);
- if (action.getMovement() != null) {
- evaluateMovement(entity, action.getMovement());
- }
- if (action.getSummonActions() != null) {
- evaluateSummons(entity, action.getSummonActions());
- }
- if (action.getSkill() != null) {
- evaluateSkill(entity, action.getSkill());
- }
- if (action.getAttack() != null) {
- evaluateAttack(entity, action.getAttack());
- }
- if (action.getRestoreCards() != null) {
- evaluateRestoreCards(entity, action.getRestoreCards());
- }
-
- if (action.getDiscard() != Action.Discard.NEVER) {
- action.setDiscarded(true);
- }
- }
-}
+// }
+// }
+//
+// public void evaluateRestoreCards(Entity entity, RestoreCards restoreCards) {
+// log.info("Evaluating restoreCards {} ({}) for {}", restoreCards.getNumCards(), restoreCards.getTarget(), entityToString(entity));
+// int remaining = restoreCards.getNumCards();
+//
+// for (Entity target : calculateTarget(entity, restoreCards.getTarget())) {
+// if (!isAlly(entity, target)) continue;
+//
+// log.info("\tRestoring cards for {}", entityToString(target));
+//// for (Action action : target.getActions()) {
+//// if (remaining == 0) {
+//// return;
+//// }
+////
+//// if (action.getDiscarded() && action.getDiscard() != Action.Discard.PERMANENT) {
+//// action.setDiscarded(false);
+//// log.info("\t\tRestored {} for {}", action.getTitle(), entityToString(target));
+//// remaining -= 1;
+//// }
+//// }
+// }
+// }
+//
+// public void evaluateAction(Entity entity, Action action) {
+// log.info("Evaluating action {} for {}", action.getTitle(), entityToString(entity));
+// log.info("\tAction: {}", action);
+// if (action.getMovement() != null) {
+// evaluateMovement(entity, action.getMovement());
+// }
+// if (action.getSummonActions() != null) {
+// evaluateSummons(entity, action.getSummonActions());
+// }
+// if (action.getSkill() != null) {
+// evaluateSkill(entity, action.getSkill());
+// }
+// if (action.getAttack() != null) {
+// evaluateAttack(entity, action.getAttack());
+// }
+// if (action.getRestoreCards() != null) {
+// evaluateRestoreCards(entity, action.getRestoreCards());
+// }
+//
+// if (action.getDiscard() != Action.Discard.NEVER) {
+// action.setDiscarded(true);
+// }
+// }
+//}
diff --git a/src/main/java/cz/trailsthroughshadows/algorithm/entity/Entity.java b/src/main/java/cz/trailsthroughshadows/algorithm/entity/Entity.java
index 35fb48b..76b8d1e 100644
--- a/src/main/java/cz/trailsthroughshadows/algorithm/entity/Entity.java
+++ b/src/main/java/cz/trailsthroughshadows/algorithm/entity/Entity.java
@@ -17,15 +17,8 @@ public abstract class Entity {
public Hex hex;
@Transient
public List activeEffects = new ArrayList<>();
- @Column(nullable = false)
- public CombatStyle combatStyle = CombatStyle.MELEE;
public abstract String getName();
// public abstract List getActions();
-
- enum CombatStyle {
- MELEE,
- RANGED,
- }
}
diff --git a/src/main/java/cz/trailsthroughshadows/algorithm/location/LocationImpl.java b/src/main/java/cz/trailsthroughshadows/algorithm/location/LocationImpl.java
index 2576ffb..a291902 100644
--- a/src/main/java/cz/trailsthroughshadows/algorithm/location/LocationImpl.java
+++ b/src/main/java/cz/trailsthroughshadows/algorithm/location/LocationImpl.java
@@ -1,12 +1,9 @@
package cz.trailsthroughshadows.algorithm.location;
-import cz.trailsthroughshadows.algorithm.utils.Vec3;
import cz.trailsthroughshadows.api.table.schematic.hex.Hex;
import cz.trailsthroughshadows.api.table.schematic.location.ILocation;
import cz.trailsthroughshadows.api.table.schematic.part.Part;
-import java.util.List;
-
public abstract class LocationImpl implements ILocation {
public Part getPart(Hex hex) {
@@ -15,21 +12,4 @@ public Part getPart(Hex hex) {
.findFirst()
.orElse(null);
}
-
- public int getDistance(Hex hex1, Hex hex2) {
- Vec3 vec = new Vec3<>(hex1.getQ() - hex2.getQ(), hex1.getR() - hex2.getR(), hex1.getS() - hex2.getS());
- return (Math.abs(vec.x()) + Math.abs(vec.y()) + Math.abs(vec.z())) / 2;
- }
-
- public List getNeighbors(Hex hex) {
- return getNeighbors(hex, 1);
- }
-
- public List getNeighbors(Hex hex, int range) {
- List neighbors = getPart(hex).getHexes().stream()
- .filter(neighbor -> hex != neighbor && getDistance(hex, neighbor) <= range)
- .toList();
-
- return neighbors;
- }
}
diff --git a/src/main/java/cz/trailsthroughshadows/algorithm/location/Navigation.java b/src/main/java/cz/trailsthroughshadows/algorithm/location/Navigation.java
new file mode 100644
index 0000000..1511eb9
--- /dev/null
+++ b/src/main/java/cz/trailsthroughshadows/algorithm/location/Navigation.java
@@ -0,0 +1,93 @@
+package cz.trailsthroughshadows.algorithm.location;
+
+import cz.trailsthroughshadows.algorithm.util.Vec3;
+import cz.trailsthroughshadows.api.table.schematic.hex.Hex;
+import cz.trailsthroughshadows.api.table.schematic.part.Part;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.*;
+
+@RequiredArgsConstructor
+@Slf4j
+public class Navigation {
+
+ private final List parts;
+
+ public Navigation(Part... parts) {
+ this.parts = Arrays.asList(parts);
+ }
+
+ public Part getPart(Hex hex) {
+ return parts.stream()
+ .filter(part -> part.getId() == hex.getKey().getIdPart())
+ .findFirst()
+ .orElse(null);
+ }
+
+ public int getDistance(Hex hex1, Hex hex2) {
+ Vec3 vec = new Vec3<>(hex1.getQ() - hex2.getQ(), hex1.getR() - hex2.getR(), hex1.getS() - hex2.getS());
+ return (Math.abs(vec.x()) + Math.abs(vec.y()) + Math.abs(vec.z())) / 2;
+ }
+
+ public List getNeighbors(Hex hex) {
+ return getNeighbors(hex, 1);
+ }
+
+ public List getNeighbors(Hex hex, int range) {
+ return getPart(hex).getHexes().stream()
+ .filter(neighbor -> hex != neighbor && getDistance(hex, neighbor) <= range)
+ .toList();
+ }
+
+ public List getPath(Hex hex1, Hex hex2) {
+ Map distances = new HashMap<>();
+ Queue queue = new LinkedList<>();
+ List path = new ArrayList<>();
+
+ distances.put(hex1, 0);
+ queue.add(hex1);
+
+ // calculate distance from hex1
+ while (!queue.isEmpty()) {
+ Hex current = queue.poll();
+ int distance = distances.get(current);
+
+ List neighbors = getNeighbors(current);
+ for (Hex neighbor : neighbors) {
+ if (distances.containsKey(neighbor))
+ continue;
+
+ distances.put(neighbor, distance + 1);
+ queue.add(neighbor);
+ }
+ }
+
+ // hex2 is not reachable from hex1
+ if (!distances.containsKey(hex2))
+ return null;
+
+ // create path to hex2
+ Hex currentHex = hex2;
+ int currentDistance = distances.get(hex2);
+
+ while (!currentHex.equals(hex1)) {
+ path.add(currentHex);
+
+ List neighbors = getNeighbors(currentHex);
+ for (Hex neighbor : neighbors) {
+ int neighborDistance = distances.get(neighbor);
+
+ if (neighborDistance < currentDistance) {
+ currentHex = neighbor;
+ currentDistance = neighborDistance;
+ break;
+ }
+ }
+ }
+ path.add(hex1);
+
+ Collections.reverse(path);
+ return path;
+ }
+}
diff --git a/src/main/java/cz/trailsthroughshadows/algorithm/util/ListUtil.java b/src/main/java/cz/trailsthroughshadows/algorithm/util/List.java
similarity index 66%
rename from src/main/java/cz/trailsthroughshadows/algorithm/util/ListUtil.java
rename to src/main/java/cz/trailsthroughshadows/algorithm/util/List.java
index 798a653..06829d1 100644
--- a/src/main/java/cz/trailsthroughshadows/algorithm/util/ListUtil.java
+++ b/src/main/java/cz/trailsthroughshadows/algorithm/util/List.java
@@ -1,17 +1,16 @@
package cz.trailsthroughshadows.algorithm.util;
import java.util.ArrayList;
-import java.util.List;
-public class ListUtil {
+public class List {
public static T getRandom(T[] array) {
return array[(int) (Math.random() * array.length)];
}
@SafeVarargs
- public static List union(List extends T>... lists) {
+ public static java.util.List union(java.util.List extends T>... lists) {
ArrayList union = new ArrayList<>();
- for (List extends T> list : lists) {
+ for (java.util.List extends T> list : lists) {
union.addAll(list);
}
return union;
diff --git a/src/main/java/cz/trailsthroughshadows/algorithm/util/Vec3.java b/src/main/java/cz/trailsthroughshadows/algorithm/util/Vec3.java
new file mode 100644
index 0000000..e483fa3
--- /dev/null
+++ b/src/main/java/cz/trailsthroughshadows/algorithm/util/Vec3.java
@@ -0,0 +1,4 @@
+package cz.trailsthroughshadows.algorithm.util;
+
+public record Vec3(T x, T y, T z) {
+}
diff --git a/src/main/java/cz/trailsthroughshadows/algorithm/utils/Vec3.java b/src/main/java/cz/trailsthroughshadows/algorithm/utils/Vec3.java
deleted file mode 100644
index 719ef3d..0000000
--- a/src/main/java/cz/trailsthroughshadows/algorithm/utils/Vec3.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package cz.trailsthroughshadows.algorithm.utils;
-
-public record Vec3(T x, T y, T z) {
-}
diff --git a/src/main/java/cz/trailsthroughshadows/api/rest/endpoints/ValidationController.java b/src/main/java/cz/trailsthroughshadows/api/rest/endpoints/ValidationController.java
index 6458b98..baf0cb4 100644
--- a/src/main/java/cz/trailsthroughshadows/api/rest/endpoints/ValidationController.java
+++ b/src/main/java/cz/trailsthroughshadows/api/rest/endpoints/ValidationController.java
@@ -5,7 +5,8 @@
import cz.trailsthroughshadows.api.rest.model.error.RestError;
import cz.trailsthroughshadows.api.rest.model.error.type.MessageError;
import cz.trailsthroughshadows.api.table.schematic.part.Part;
-import lombok.extern.slf4j.Slf4j;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
@@ -13,42 +14,37 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
-import java.util.ArrayList;
import java.util.List;
-@Slf4j
+@Log4j2
@Component
@RestController(value = "validation")
public class ValidationController {
+ private ValidationService validationService;
+
@PostMapping("/validate/part")
public ResponseEntity validatePart(@RequestBody Part part) {
- List errors = new ArrayList<>();
-
- // TODO: Implement Part validation @Bačkorče
- // Validations?:
- // 1. Part must have at least 5 hexes
- // 2. Part must have at most 50 hexes
- // 3. Part is maximum 8 hexes wide and 8 hexes tall
- // 4. All hexes must be connected
-
- if (part.getHexes().size() < 5) {
- errors.add("Part must have at least 5 hexes!");
- }
-
- if (part.getHexes().size() > 50) {
- errors.add("Part must have at most 50 hexes!");
- }
+ log.debug("Validating part " + part.getTag());
+ List errors = validationService.validatePart(part);
if (errors.isEmpty()) {
- return RestResponse.of(HttpStatus.OK,"Part is valid.");
+ log.debug("Part is valid!");
+ return new ResponseEntity<>(new RestResponse(HttpStatus.OK, "Part is valid!"), HttpStatus.OK);
}
+ log.debug("Part is not valid!");
RestError error = new RestError(HttpStatus.NOT_ACCEPTABLE, "Part is not valid!");
for (var e : errors) {
+ log.debug(" > " + e);
error.addSubError(new MessageError(e));
}
throw new RestException(error);
}
+
+ @Autowired
+ public void setValidationService(ValidationService validationService) {
+ this.validationService = validationService;
+ }
}
diff --git a/src/main/java/cz/trailsthroughshadows/api/rest/endpoints/ValidationService.java b/src/main/java/cz/trailsthroughshadows/api/rest/endpoints/ValidationService.java
new file mode 100644
index 0000000..7d234d7
--- /dev/null
+++ b/src/main/java/cz/trailsthroughshadows/api/rest/endpoints/ValidationService.java
@@ -0,0 +1,94 @@
+package cz.trailsthroughshadows.api.rest.endpoints;
+
+import cz.trailsthroughshadows.algorithm.location.Navigation;
+import cz.trailsthroughshadows.api.table.schematic.hex.Hex;
+import cz.trailsthroughshadows.api.table.schematic.part.Part;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+@Service
+@Slf4j
+public class ValidationService {
+
+ // TODO: Implement Part validation @Bačkorče
+ // Validations?:
+ // 1. Part must have at least 5 hexes
+ // 2. Part must have at most 50 hexes
+ // 3. Part is maximum 8 hexes wide and 8 hexes tall
+ // 4. All hexes must be connected
+ public List validatePart(Part part) {
+ List errors = new ArrayList<>();
+
+ int minHexes = 5;
+ int maxHexes = 50;
+ int maxHexesWide = 8;
+
+ // min 5 hexes
+ if (part.getHexes().size() < minHexes) {
+ errors.add("Part must have at least %d hexes!".formatted(minHexes));
+ }
+
+ // max 50 hexes
+ if (part.getHexes().size() > maxHexes) {
+ errors.add("Part must have at most 50 hexes!");
+ }
+
+ // max 8 hexes wide
+ int diffQ = part.getHexes().stream().mapToInt(Hex::getQ).max().getAsInt() - part.getHexes().stream().mapToInt(Hex::getQ).min().getAsInt();
+ int diffR = part.getHexes().stream().mapToInt(Hex::getR).max().getAsInt() - part.getHexes().stream().mapToInt(Hex::getR).min().getAsInt();
+ int diffS = part.getHexes().stream().mapToInt(Hex::getS).max().getAsInt() - part.getHexes().stream().mapToInt(Hex::getS).min().getAsInt();
+ if (diffQ > maxHexesWide || diffR > maxHexesWide || diffS > maxHexesWide) {
+ errors.add("Part must not be wider than %d hexes!".formatted(maxHexesWide));
+ }
+
+ // no hexes can be on the same position
+ int duplicates = 0;
+ for (Hex hex1 : part.getHexes()) {
+ for (Hex hex2 : part.getHexes()) {
+ if (hex1 == hex2)
+ continue;
+
+ if (hex1.getQ() == hex2.getQ() && hex1.getR() == hex2.getR() && hex1.getS() == hex2.getS()) {
+ duplicates++;
+ break;
+ }
+ }
+ }
+ if (duplicates > 0)
+ errors.add("Part must not have duplicate hexes!");
+
+ // every hex has to have correct coordinates
+ for (Hex hex : part.getHexes()) {
+ if (hex.getQ() + hex.getR() + hex.getS() != 0) {
+ errors.add("Every hex has to have correct coordinates!");
+ break;
+ }
+ }
+
+ // must include center hex
+ Optional centerHex = part.getHexes().stream().filter(hex -> hex.getQ() == 0 && hex.getR() == 0 && hex.getS() == 0).findFirst();
+ if (centerHex.isEmpty()) {
+ errors.add("Part must include a center hex!");
+ return errors;
+ }
+
+ // all hexes must be connected
+ Navigation navigation = new Navigation(part);
+
+ for (Hex hex : part.getHexes()) {
+ if (hex == centerHex.get())
+ continue;
+
+ if (navigation.getPath(centerHex.get(), hex) == null) {
+ errors.add("All hexes must be connected!");
+ break;
+ }
+ }
+
+ return errors;
+ }
+}
diff --git a/src/main/java/cz/trailsthroughshadows/api/table/schematic/hex/Hex.java b/src/main/java/cz/trailsthroughshadows/api/table/schematic/hex/Hex.java
index 1d1a48d..8a337e9 100644
--- a/src/main/java/cz/trailsthroughshadows/api/table/schematic/hex/Hex.java
+++ b/src/main/java/cz/trailsthroughshadows/api/table/schematic/hex/Hex.java
@@ -26,13 +26,6 @@ public class Hex {
@Column(name = "sCord", nullable = false)
private int s;
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof Hex hex)) return false;
- return q == hex.q && r == hex.r && s == hex.s && key.equals(hex.key);
- }
-
@Data
@Embeddable
public static class HexId implements Serializable {
diff --git a/src/main/java/cz/trailsthroughshadows/api/table/schematic/part/Part.java b/src/main/java/cz/trailsthroughshadows/api/table/schematic/part/Part.java
index ef1a023..ac47ea5 100644
--- a/src/main/java/cz/trailsthroughshadows/api/table/schematic/part/Part.java
+++ b/src/main/java/cz/trailsthroughshadows/api/table/schematic/part/Part.java
@@ -1,21 +1,18 @@
package cz.trailsthroughshadows.api.table.schematic.part;
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import cz.trailsthroughshadows.api.table.schematic.hex.Hex;
-import cz.trailsthroughshadows.api.table.schematic.hex.LocationDoor;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
-import java.util.Set;
+import java.util.List;
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "Part")
-@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Part {
@Id
@@ -25,11 +22,11 @@ public class Part {
@Column(name = "tag")
private String tag;
- @OneToMany(mappedBy = "key.idPart", cascade = CascadeType.ALL)
- private Set hexes;
+ @OneToMany(mappedBy = "key.idPart", cascade = CascadeType.ALL, orphanRemoval = true)
+ private List hexes;
- @OneToMany(mappedBy = "key.fromPart", cascade = CascadeType.ALL)
- private Set doors;
+// @OneToMany(mappedBy = "key.fromPart", cascade = CascadeType.ALL)
+// private Set doors;
@Column(name = "usages", columnDefinition = "INT default 0")
private int usages = 0;
diff --git a/src/main/java/cz/trailsthroughshadows/api/table/schematic/part/PartController.java b/src/main/java/cz/trailsthroughshadows/api/table/schematic/part/PartController.java
index cfa01b7..549f03c 100644
--- a/src/main/java/cz/trailsthroughshadows/api/table/schematic/part/PartController.java
+++ b/src/main/java/cz/trailsthroughshadows/api/table/schematic/part/PartController.java
@@ -78,12 +78,16 @@ public ResponseEntity updatePartById(@PathVariable int id, @Reques
.orElseThrow(() -> RestException.of(HttpStatus.NOT_FOUND, "Part with id '%d' not found!", id));
partToUpdate.setTag(part.getTag());
- partToUpdate.setHexes(part.getHexes());
+
+// partToUpdate.setHexes(part.getHexes());
+ partToUpdate.getHexes().retainAll(part.getHexes());
+ partToUpdate.getHexes().addAll(part.getHexes());
// TODO: It's not removing part hexes, but it's adding new ones or updating existing ones
// TODO: Validation for new or updates parts
// Issue: https://github.com/Trails-Through-Shadows/TTS-API/issues/28
partRepo.save(partToUpdate);
+
return RestResponse.of(HttpStatus.OK,"Part updated!");
}
diff --git a/src/main/java/cz/trailsthroughshadows/api/table/schematic/part/PartRepo.java b/src/main/java/cz/trailsthroughshadows/api/table/schematic/part/PartRepo.java
index 2415e39..9ffe9e5 100644
--- a/src/main/java/cz/trailsthroughshadows/api/table/schematic/part/PartRepo.java
+++ b/src/main/java/cz/trailsthroughshadows/api/table/schematic/part/PartRepo.java
@@ -11,11 +11,11 @@
public interface PartRepo extends JpaRepository {
@Override
- @EntityGraph(attributePaths = {"hexes", "doors"})
+ @EntityGraph(attributePaths = {"hexes"})
List findAll();
@Override
- @EntityGraph(attributePaths = {"hexes", "doors"})
+ @EntityGraph(attributePaths = {"hexes"})
Optional findById(Integer id);
diff --git a/src/main/java/cz/trailsthroughshadows/api/util/Pair.java b/src/main/java/cz/trailsthroughshadows/api/util/Pair.java
index 4f716a5..3ed2319 100644
--- a/src/main/java/cz/trailsthroughshadows/api/util/Pair.java
+++ b/src/main/java/cz/trailsthroughshadows/api/util/Pair.java
@@ -1,5 +1,5 @@
package cz.trailsthroughshadows.api.util;
-public record Pair() {
+public record Pair(A first, B second) {
}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index d94ac70..004bd72 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -19,7 +19,7 @@ server.forward-headers-strategy=framework
# Logging
logging.level.root=INFO
logging.level.cz.trailsthroughshadows.api=DEBUG
-logging.level.cz.trailsthroughshadows.algorithm=INFO
+logging.level.cz.trailsthroughshadows.algorithm=DEBUG
logging.config=classpath:log4j2.xml
#jackson configuration