Added beginning of simulator

This commit is contained in:
Samy Avrillon 2024-06-25 20:02:54 +02:00
parent 26d8058cf4
commit 6122aa1670
Signed by: Mysaa
GPG Key ID: 0220AC4A3D6A328B
26 changed files with 34454 additions and 2 deletions

View File

@ -27,6 +27,7 @@ dependencies {
implementation 'org.yaml:snakeyaml:2.2' implementation 'org.yaml:snakeyaml:2.2'
implementation 'org.ojalgo:ojalgo:54.0.0' implementation 'org.ojalgo:ojalgo:54.0.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1'
} }
tasks.named('test') { tasks.named('test') {

View File

@ -1,18 +1,58 @@
package com.bernard.greposimu; package com.bernard.greposimu;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map;
import java.util.stream.Collectors;
import com.bernard.greposimu.engine.Registerar; import com.bernard.greposimu.engine.Registerar;
import com.bernard.greposimu.engine.game.Buildings;
import com.bernard.greposimu.engine.json.MapJsonDeserializer;
import com.bernard.greposimu.model.Dieu; import com.bernard.greposimu.model.Dieu;
import com.bernard.greposimu.model.Heros; import com.bernard.greposimu.model.Heros;
import com.bernard.greposimu.model.OffDefStats; import com.bernard.greposimu.model.OffDefStats;
import com.bernard.greposimu.model.game.GameData;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.StreamReadFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
public class GrepoSimu { public class GrepoSimu {
public static GameData readGameData() throws IOException {
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
try (InputStream is = classLoader.getResourceAsStream("gamedata.json")) {
if (is == null) return null;
try (InputStreamReader isr = new InputStreamReader(is);
BufferedReader reader = new BufferedReader(isr)) {
// Reading the file
String json = reader.lines().collect(Collectors.joining(System.lineSeparator()));
// De-serialize to an object
JsonFactory jsonFactory = JsonFactory.builder()
.enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION)
.build();
SimpleModule module = new SimpleModule();
module.addDeserializer(Map.class, new MapJsonDeserializer());
ObjectMapper mapper = new ObjectMapper(jsonFactory);
mapper.registerModule(module);
GameData data = mapper.readValue(json, GameData.class);
return data;
}
}
}
public static void main(String[] args) { public static void main(String[] args) {
for(int i=0;i<26;i++)
System.out.println(i+"->"+Buildings.wallBonus(i));
}
public static void mainZ(String[] args) {
Registerar.regiter(); Registerar.regiter();
System.out.println(Registerar.unites.size()); System.out.println(Registerar.unites.size());

View File

@ -0,0 +1,14 @@
/*package com.bernard.greposimu;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(GrepoSimuApplication.class);
}
}
*/

View File

@ -0,0 +1,69 @@
package com.bernard.greposimu;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Utils {
public static final double poly5(double a0, double a1, double a2, double a3, double a4, double a5, double x) {
return
a0 +
a1 * x +
a2 * x * x +
a3 * x * x * x +
a4 * x * x * x * x +
a5 * x * x * x * x * x;
}
public static final <T> Map<T,Boolean> setToMap(Set<T> set){
return new AbstractMap<T,Boolean>() {
@Override
public Set<Entry<T, Boolean>> entrySet() {
return new AbstractSet<Map.Entry<T,Boolean>>() {
@Override
public Iterator<Entry<T, Boolean>> iterator() {
Iterator<T> it = set.iterator();
return new Iterator<Map.Entry<T,Boolean>>() {
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public Entry<T, Boolean> next() {
// TODO Auto-generated method stub
return new AbstractMap.SimpleEntry<>(it.next(), true);
}
@Override
public void remove() {
it.remove();
}
};
}
@Override
public int size() {
return set.size();
}
};
}
@Override
public Boolean put(T k, Boolean v) {
Boolean old = set.contains(k);
if(v != null && v)
set.add(k);
return old;
}
};
}
}

View File

@ -1,4 +1,7 @@
package com.bernard.greposimu.controller; package com.bernard.greposimu.controller;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
@ -14,17 +17,68 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import com.bernard.greposimu.GrepoSimu;
import com.bernard.greposimu.engine.Optimizer; import com.bernard.greposimu.engine.Optimizer;
import com.bernard.greposimu.engine.Optimizer.UnitesProportions; import com.bernard.greposimu.engine.Optimizer.UnitesProportions;
import com.bernard.greposimu.engine.Registerar; import com.bernard.greposimu.engine.Registerar;
import com.bernard.greposimu.engine.game.Fight;
import com.bernard.greposimu.engine.game.Game;
import com.bernard.greposimu.model.DefContext;
import com.bernard.greposimu.model.Dieu; import com.bernard.greposimu.model.Dieu;
import com.bernard.greposimu.model.FightStats;
import com.bernard.greposimu.model.Heros; import com.bernard.greposimu.model.Heros;
import com.bernard.greposimu.model.OffDefStats; import com.bernard.greposimu.model.OffDefStats;
import com.bernard.greposimu.model.Unite; import com.bernard.greposimu.model.Unite;
import com.bernard.greposimu.model.game.GameData;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
@Controller @Controller
public class GrepoSimuController { public class GrepoSimuController {
@GetMapping("/gamedata")
public String gamedata(Model model) {
System.out.println("Reading game data");
GameData data;
try {
data = GrepoSimu.readGameData();
model.addAttribute("content",data.toString());
} catch (IOException e) {
StringWriter writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
model.addAttribute("content",writer.toString());
}
return "debug";
}
@GetMapping("/simulator")
public String simulator(Model model) throws IOException {
GameData data = GrepoSimu.readGameData();
model.addAttribute("heroes", data.heroes.values());
model.addAttribute("defUnits", Fight.relevantDefUnits(data));
model.addAttribute("defCounsellors",Fight.relevantDefCounsellors(data));
model.addAttribute("defResearches",Fight.relevantDefResearch(data));
model.addAttribute("defPowers",Fight.relevantDefPowers(data));
model.addAttribute("defCtx",new DefContext());
return "simulator";
}
@PostMapping("/simulate")
@GetMapping("/simulate")
public String simulate(@ModelAttribute DefContext defCtx, Model model) throws IOException {
if(defCtx == null)
defCtx = new DefContext();
GameData data = GrepoSimu.readGameData();
Game g = new Game(data);
FightStats cityStats = g.fight.computeDefStats(defCtx);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
model.addAttribute("content",cityStats.toString()+"\n"+mapper.writerWithDefaultPrettyPrinter().writeValueAsString(defCtx));
return "debug";
}
@GetMapping("/optimizer") @GetMapping("/optimizer")
public String greeting(Model model) { public String greeting(Model model) {
model.addAttribute("heros", Registerar.heros); model.addAttribute("heros", Registerar.heros);

View File

@ -0,0 +1,26 @@
package com.bernard.greposimu.engine.game;
import com.bernard.greposimu.Utils;
import com.bernard.greposimu.model.FightStats;
public class Buildings {
Game g;
public Buildings(Game g) {
this.g = g;
}
public static FightStats cityBaseStats(int wallLevel) {
double value = 10.0+10.0*wallLevel;
return new FightStats(value, value, value, 0.0);
}
public static FightStats wallBonus(int wallLevel) {
double value = (Math.pow(1.035, wallLevel)-1);
//TODO find exact value
value = Utils.poly5(0.0, 0.036679, 0.000528, 0.000011, 0.0, 0.0, wallLevel);
return new FightStats(value, value, value, 0.0);
}
}

View File

@ -0,0 +1,124 @@
package com.bernard.greposimu.engine.game;
import java.util.List;
import java.util.Map;
import com.bernard.greposimu.model.DefContext;
import com.bernard.greposimu.model.FightStats;
import com.bernard.greposimu.model.OffContext;
import com.bernard.greposimu.model.game.GameData;
import com.bernard.greposimu.model.game.Power;
import com.bernard.greposimu.model.game.Research;
import com.bernard.greposimu.model.game.Unit;
public class Fight {
Game g;
public Fight(Game g) {
this.g = g;
}
public FightStats computeDefStats(DefContext def) {
FightStats everyoneStatsBonus = FightStats.zero();
Map<String,FightStats> unitsBonuses;
FightStats cityBaseStats;
// Heroes
unitsBonuses = g.heroes.heroFightBonuses(def.hero, def.heroLevel, false);
// Tower & wall
cityBaseStats = Buildings.cityBaseStats(def.wallLevel);
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, Buildings.wallBonus(def.wallLevel));
if(def.hasTower)
// Add 10% to all units
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.cst(0.1));
// Powers
//TODO powers
// Researches
if(def.counsellors.contains("divine_selection"))
for(String uid : g.data.units.keySet())
if(g.data.units.get(uid).isMythological())
unitsBonuses.put(uid, FightStats.add(unitsBonuses.getOrDefault(uid, FightStats.zero()), FightStats.cst(0.1)));
if(def.counsellors.contains("phalanx"))
for(String uid : g.data.units.keySet())
if(g.data.units.get(uid).isGround())
unitsBonuses.put(uid, FightStats.add(unitsBonuses.getOrDefault(uid, FightStats.zero()), FightStats.cst(0.1)));
if(def.counsellors.contains("ram"))
for(String uid : g.data.units.keySet())
if(g.data.units.get(uid).isNaval())
unitsBonuses.put(uid, FightStats.add(unitsBonuses.getOrDefault(uid, FightStats.zero()), FightStats.cst(0.1)));
// Counsellors
if(def.counsellors.contains("priest"))
for(String uid : g.data.units.keySet())
if(g.data.units.get(uid).isMythological())
unitsBonuses.put(uid, FightStats.add(unitsBonuses.getOrDefault(uid, FightStats.zero()), FightStats.cst(0.2)));
if(def.counsellors.contains("commander"))
for(String uid : g.data.units.keySet())
if(g.data.units.get(uid).isGround())
unitsBonuses.put(uid, FightStats.add(unitsBonuses.getOrDefault(uid, FightStats.zero()), FightStats.cst(0.2)));
if(def.counsellors.contains("captain"))
for(String uid : g.data.units.keySet())
if(g.data.units.get(uid).isNaval())
unitsBonuses.put(uid, FightStats.add(unitsBonuses.getOrDefault(uid, FightStats.zero()), FightStats.cst(0.2)));
// Night Bonus
if(def.nightBonus)
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.cst(1.0));
// Units
FightStats total = cityBaseStats.clone();
for(Unit u : g.data.units.values()) {
// total = total + ucount * ((1+bonus+bonus) * ustats)
if(def.units.containsKey(u.id) && def.units.get(u.id) != null)
total = FightStats.add(total,
FightStats.prod(def.units.get(u.id),
FightStats.mul(
FightStats.add(FightStats.one(),everyoneStatsBonus,unitsBonuses.getOrDefault(u.id, FightStats.zero()))
, u.getDefStats())
));
}
return total;
}
public static List<Power> relevantDefPowers(GameData gd) {
return List.of("acumen","divine_senses","myrmidion_attack","trojan_defense","defense_boost","defense_penalty","longterm_defense_boost","assassins_acumen","rare_defense_boost","epic_defense_boost","olympic_torch","olympic_senses","missions_power_4","divine_battle_strategy_rare","divine_battle_strategy_epic","naval_battle_strategy_rare","naval_battle_strategy_epic","land_battle_strategy_rare","land_battle_strategy_epic","soterias_shrine")
.stream().map(gd.powers::get).toList();
}
public static List<Research> relevantDefResearch(GameData gd) {
return List.of("divine_selection","phalanx","ram")
.stream().map(gd.researches::get).toList();
}
public static List<Unit> relevantDefUnits(GameData data) {
return data.units.values().stream().toList();
}
public static List<String> relevantDefCounsellors(GameData data) {
return List.of("priest","commander","captain");
}
public FightStats computeOffStats(DefContext off) {
//TODO computeOffStats
throw new UnsupportedOperationException("Simulator not created");
}
public FightResult simulateFight(OffContext off, DefContext def) {
//TODO simulateFight
throw new UnsupportedOperationException("Simulator not created");
}
public static class FightResult {
Map<String, Integer> def_losses;
Map<String, Integer> att_losses;
int wall_loss;
int att_battle_points;
int def_battle_points;
}
}

View File

@ -0,0 +1,20 @@
package com.bernard.greposimu.engine.game;
import com.bernard.greposimu.model.game.GameData;
public class Game {
GameData data;
public Heroes heroes;
public Buildings buildings;
public Fight fight;
public Game(GameData data) {
this.data = data;
this.heroes = new Heroes(this);
this.buildings = new Buildings(this);
this.fight = new Fight(this);
}
}

View File

@ -0,0 +1,119 @@
package com.bernard.greposimu.engine.game;
import java.util.HashMap;
import java.util.Map;
import com.bernard.greposimu.model.FightStats;
import com.bernard.greposimu.model.game.Hero;
public class Heroes {
Game g;
public Heroes(Game g) {
this.g = g;
}
/**
* Compute the bonus applied to each unit
* @param hero The uid of the hero
* @param level The level of the hero
* @param off true for the off stat, false for the def stat
* @return For each unit, the bonus that should be applied to it
*/
public Map<String,FightStats> heroFightBonuses(String hero, int level, boolean off){
Map<String,FightStats> bonuses = new HashMap<>();
if(hero == null)
return new HashMap<>();
double bonus = 0.0;
Hero heroD = g.data.heroes.get(hero);
if(heroD != null && heroD.description_args != null && heroD.description_args.containsKey("1")) {
Hero.DescriptionArgs args = heroD.description_args.get("1");
if(args.unit.equals("%"))
bonus = args.value + level * args.level_mod;
else
throw new UnsupportedOperationException("I don't know about unit "+args.unit);
}
FightStats appliedBonus = null;
switch(hero) {
case "agamemnon":
appliedBonus = FightStats.terrestre(bonus);
bonuses.put("hoplite", appliedBonus);
bonuses.put("archer", appliedBonus);
break;
case "ajax":
appliedBonus = FightStats.terrestre(bonus);
bonuses.put("hoplites", appliedBonus);
break;
case "alexandrios":
appliedBonus = FightStats.terrestre(bonus);
bonuses.put("archers", appliedBonus);
break;
case "deimos":
appliedBonus = FightStats.cst(bonus);
if(off)
for(String uid : g.data.units.keySet())bonuses.put(uid,appliedBonus);
break;
case "hector":
appliedBonus = FightStats.terrestre(bonus);
bonuses.put("sword", appliedBonus);
bonuses.put("slinger", appliedBonus);
break;
case "lysippe":
appliedBonus = FightStats.terrestre(bonus);
bonuses.put("rider", appliedBonus);
break;
case "leonidas":
appliedBonus = FightStats.terrestre(bonus);
if(!off)
for(String uid : g.data.units.keySet())bonuses.put(uid,appliedBonus);
break;
case "mihalis":
appliedBonus = new FightStats(bonus, bonus, 0.0, 0.0);
for(String uid : g.data.units.keySet())bonuses.put(uid,appliedBonus);
break;
case "medea":
appliedBonus = FightStats.terrestre(bonus);
bonuses.put("slinger", appliedBonus);
break;
case "melousa":
appliedBonus = FightStats.terrestre(bonus);
bonuses.put("chariot", appliedBonus);
break;
case "pelops":
appliedBonus = FightStats.terrestre(bonus);
bonuses.put("hoplite", appliedBonus);
bonuses.put("chariot", appliedBonus);
break;
case "themistokles":
appliedBonus = FightStats.terrestre(bonus);
bonuses.put("godsent", appliedBonus);
bonuses.put("rider", appliedBonus);
break;
case "telemachos":
appliedBonus = FightStats.terrestre(bonus);
bonuses.put("sword", appliedBonus);
break;
case "urephon":
appliedBonus = FightStats.terrestre(bonus);
for(String uid : g.data.units.keySet())if(g.data.units.get(uid).isMythological())bonuses.put(uid,appliedBonus);
break;
case "zuretha":
appliedBonus = new FightStats(0.0, 0.0, 0.0, bonus);
for(String uid : g.data.units.keySet())bonuses.put(uid,appliedBonus);
break;
default:
// No buff
break;
}
return bonuses;
}
}

View File

@ -0,0 +1,57 @@
package com.bernard.greposimu.engine.json;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
public class MapJsonDeserializer extends StdDeserializer<Map<String,?>> implements ContextualDeserializer{
private static final long serialVersionUID = -888029778299077908L;
private JavaType type;
public MapJsonDeserializer() {
super(Object.class);
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException{
MapJsonDeserializer deserializer = new MapJsonDeserializer();
if(property != null)
deserializer.type = property.getType().containedType(1);
else
deserializer.type = ctxt.getContextualType();
return deserializer;
}
@Override
public Map<String,?> deserialize(JsonParser parser, DeserializationContext deserializationContext) throws IOException {
JsonNode node = parser.getCodec().readTree(parser);
if(node.isNull() || (node.isArray() && node.isEmpty()))
return new HashMap<>();
else if(node.isObject()) {
ObjectCodec codec = parser.getCodec();
Map<String,Object> output = new HashMap<>();
java.util.Iterator<Entry<String, JsonNode>> it = node.fields();
while(it.hasNext()) {
Entry<String, JsonNode> entry = it.next();
output.put(entry.getKey(), entry.getValue().traverse(codec).readValueAs(this.type.getRawClass()));
}
return output;
} else
throw MismatchedInputException.from(parser, Map.class, "JSON is neither an empty array nor a map");
}
}

View File

@ -0,0 +1,118 @@
package com.bernard.greposimu.model;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.bernard.greposimu.Utils;
public class DefContext {
// unitID -> number of units
public Map<String, Integer> units;
public String hero;
public int heroLevel;
public int wallLevel;
public boolean hasTower;
public Set<String> powers;
public Set<String> researches;
public Set<String> counsellors;
public boolean nightBonus;
public DefContext() {
this.units = new HashMap<>();
this.hero = null;
this.heroLevel = 0;
this.wallLevel = 0;
this.hasTower = false;
this.powers = new HashSet<>();
this.researches = new HashSet<>();
this.counsellors = new HashSet<>();
this.nightBonus = false;
}
public Map<String, Integer> getUnits() {
return units;
}
public String getHero() {
return hero;
}
public int getHeroLevel() {
return heroLevel;
}
public int getWallLevel() {
return wallLevel;
}
public boolean isHasTower() {
return hasTower;
}
public Set<String> getPowers() {
return powers;
}
public Set<String> getResearches() {
return researches;
}
public Set<String> getCounsellors() {
return counsellors;
}
public Map<String, Boolean> getPowersAsMap() {
return Utils.setToMap(powers);
}
public Map<String, Boolean> getResearchesAsMap() {
return Utils.setToMap(researches);
}
public Map<String, Boolean> getCounsellorsAsMap() {
return Utils.setToMap(counsellors);
}
public boolean isNightBonus() {
return nightBonus;
}
public void setHero(String hero) {
this.hero = hero;
}
public void setHeroLevel(int heroLevel) {
this.heroLevel = heroLevel;
}
public void setWallLevel(int wallLevel) {
this.wallLevel = wallLevel;
}
public void setHasTower(boolean hasTower) {
this.hasTower = hasTower;
}
public void setNightBonus(boolean nightBonus) {
this.nightBonus = nightBonus;
}
@Override
public String toString() {
return "DefContext [units=" + units + ", heros=" + hero + ", herosLevel=" + heroLevel + ", wallLevel="
+ wallLevel + ", hasTower=" + hasTower + ", powers=" + powers + ", researches=" + researches
+ ", counsellors=" + counsellors + ", nightBonus=" + nightBonus + "]";
}
}

View File

@ -0,0 +1,63 @@
package com.bernard.greposimu.model;
import java.util.Arrays;
public class FightStats implements Cloneable{
public double hack;
public double pierce;
public double distance;
public double ship;
public FightStats(double hack, double pierce, double distance, double ship) {
this.hack = hack;
this.pierce = pierce;
this.distance = distance;
this.ship = ship;
}
public static final FightStats zero() {
return new FightStats(0, 0, 0, 0);
}
public static final FightStats one() {
return new FightStats(1.0, 1.0, 1.0, 1.0);
}
public static final FightStats terrestre(double value) {
return new FightStats(value, value, value, 0.0);
}
public static final FightStats cst(double value) {
return new FightStats(value, value, value, value);
}
public static final FightStats add(FightStats a,FightStats b) {
return new FightStats(a.hack + b.hack, a.pierce + b.pierce, a.distance + b.distance, a.ship + b.ship);
}
public static final FightStats add(FightStats... arr) {
return Arrays.stream(arr).reduce(FightStats.zero(), FightStats::add);
}
public static final FightStats sub(FightStats a,FightStats b) {
return new FightStats(a.hack - b.hack, a.pierce - b.pierce, a.distance - b.distance, a.ship - b.ship);
}
public static final FightStats mul(FightStats a,FightStats b) {
return new FightStats(a.hack * b.hack, a.pierce * b.pierce, a.distance * b.distance, a.ship * b.ship);
}
public static final FightStats prod(int k,FightStats b) {
return new FightStats(k * b.hack, k * b.pierce, k * b.distance, k * b.ship);
}
@Override
public FightStats clone() {
return new FightStats(hack, pierce, distance, ship);
}
@Override
public String toString() {
return "(%.4f,%.4f,%.4f,%.4f)".formatted(hack,pierce,distance,ship);
}
}

View File

@ -0,0 +1,23 @@
package com.bernard.greposimu.model;
import java.util.Map;
import java.util.Set;
public class OffContext {
// unitID -> number of units
public Map<String, Integer> units;
public String heros;
public int herosLevel;
public int luck;
public int morale;
public Set<String> powers;
public Set<String> researches;
public Set<String> counsellors;
public boolean strategy_breach;
public boolean alliance_modifier;
}

View File

@ -0,0 +1,53 @@
package com.bernard.greposimu.model.game;
import java.util.List;
import java.util.Map;
public class Building {
public String id;
public String name;
public String controller;
public Object image;
public String description;
public Object level;
public int max_level;
public int min_level;
public Object requiredBuildings;
public String coordinates;
public Resources resources;
public int pop;
public double wood_factor;
public double stone_factor;
public double iron_factor;
public double pop_factor;
public Object hide_factor;
public int points;
public double points_factor;
public int build_time;
public double build_time_factor;
public double build_time_reduction;
public Object bolt_protected;
public List<Integer> image_levels;
public Map<String,Integer> dependencies;
public Map<Integer,Integer> fixed_building_times;
public Map<Integer, LeveledFactor> level_build_time_factors;
public boolean special;
public Object resourcesFor;
public List<Object> resourcesForLevelFixed;
public Map<Integer, Double> resourcesForLevelFactor;
public List<Object> resourcesForLevelReduceFactor;
public List<Object> offset_value_map;
public double catapult_factor;
public double catapult_power;
public double def_factor_per_level;
public double storage_factor;
public double storage_pow;
public double farm_pow;
public double farm_factor;
public double thermal_pow;
public static class LeveledFactor {
public int level;
public double factor;
}
}

View File

@ -0,0 +1,37 @@
package com.bernard.greposimu.model.game;
import java.io.StringWriter;
import java.util.Map;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.Yaml;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class GameData {
public Map<String,Unit> units;
public Map<String, Power> powers;
public Map<String, God> gods;
public Map<String, Hero> heroes;
public Map<String, Research> researches;
public Map<String, Building> buildings;
@Override
public String toString() {
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(FlowStyle.BLOCK);
options.setPrettyFlow(true);
Yaml yaml = new Yaml(options);
StringWriter writer = new StringWriter();
yaml.dump(this, writer);
return writer.toString();
}
}

View File

@ -0,0 +1,12 @@
package com.bernard.greposimu.model.game;
import java.util.List;
public class God {
public String name;
public String id;
public List<Unit> units;
public List<String> powers;
public String topic;
public String description;
}

View File

@ -0,0 +1,36 @@
package com.bernard.greposimu.model.game;
import java.util.List;
import java.util.Map;
public class Hero {
public String id;
public String category;
public String name;
public String description;
public Map<String,DescriptionArgs> description_args;
public String short_description;
public int default_level;
public int cost;
public List<Object> award_requirements;
public boolean is_naval;
public boolean exclusive;
public boolean hidden;
public String attack_type;
public int attack;
public int def_hack;
public int def_pierce;
public int def_distance;
public int speed;
public int booty;
public List<Object> preconditions;
public int max_per_attack;
public int max_per_support;
public static class DescriptionArgs {
public double value;
public double level_mod;
public String unit;
}
}

View File

@ -0,0 +1,8 @@
package com.bernard.greposimu.model.game;
public class Image {
public String mini;
public String small;
public String medium;
public String large;
}

View File

@ -0,0 +1,48 @@
package com.bernard.greposimu.model.game;
import java.math.BigInteger;
import java.util.List;
public class Power {
public Object effect;
public int lifetime;
public String id;
public Object name;
public Object description;
public String short_effect;
public int favor;
public int fury_percentage_cost;
public String god_id;
public BigInteger temple_level_sum_dependency;
public List<String> targets;
public boolean only_own_towns;
public boolean boost;
public boolean is_fake_power;
public List<Object> area_of_effect;
public boolean destructive;
public boolean negative;
public boolean extendible;
public String power_group;
public int power_group_level;
public List<String> seeds_to;
public Image images;
public List<String> effects;
public boolean is_valid_for_happenings;
public List<String> meta_fields;
public Object meta_defaults;
public boolean removed_on_target_loss;
public boolean needs_level;
public boolean requires_god;
public boolean ignores_democritus;
public boolean display_amount;
public boolean wasteable;
public boolean is_ritual;
public boolean recreate_on_restart;
public boolean transfer_to_casual_world;
public boolean is_onetime_power;
public boolean is_upgradable;
public boolean is_capped;
public List<String> compatible_powers;
public boolean no_lifetime;
public boolean passive;
}

View File

@ -0,0 +1,15 @@
package com.bernard.greposimu.model.game;
import java.util.List;
import java.util.Map;
public class Research {
public String id;
public String name;
public String description;
public List<Object> research_dependencies;
public Map<String,Integer> building_dependencies;
public Resources resources;
public int required_time;
public int research_points;
}

View File

@ -0,0 +1,7 @@
package com.bernard.greposimu.model.game;
public class Resources {
public int wood;
public int stone;
public int iron;
}

View File

@ -0,0 +1,70 @@
package com.bernard.greposimu.model.game;
import java.util.List;
import java.util.Map;
import com.bernard.greposimu.model.FightStats;
public class Unit {
public String id;
public String name;
public String name_plural;
public int speed;
public int attack;
public String description;
public Resources resources;
public int favor;
public int population;
public int build_time;
public String god_id;
public List<String> research_dependencies;
public Map<String, Integer> building_dependencies;
public boolean is_naval;
public int max_per_attack;
public int max_per_support;
public String unit_function;
public String category;
public List<Object> special_abilities;
public String passive;
public boolean is_npc_unit_only;
public int def_hack;
public int def_pierce;
public int def_distance;
public int booty;
public Object infantry;
public boolean flying;
public String attack_type;
// Naval
public int defense;
public boolean transport;
public int capacity;
public FightStats getDefStats() {
return new FightStats(def_hack, def_pierce, def_distance, defense);
}
public FightStats getAttStats() {
switch(attack_type) {
case "hack":
return new FightStats(attack, 0.0, 0.0, 0.0);
case "pierce":
return new FightStats(0.0, attack, 0.0, 0.0);
case "distance":
return new FightStats(0.0, 0.0, attack, 0.0);
}
if(is_naval)
return new FightStats(0.0, 0.0, 0.0, attack);
throw new IllegalStateException("This unit has no known attack type, and is not a ship");
}
public boolean isMythological() {
return category.equals("mythological_ground") || category.equals("mythological_naval");
}
public boolean isGround() {
return category.equals("regular_ground") || category.equals("mythological_ground");
}
public boolean isNaval() {
return category.equals("regular_naval") || category.equals("mythological_naval");
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta name="description" content="Simulation du MMO Grepolis">
<title>GrepoSimu - Debug</title>
<link rel="shortcut icon" th:href="@{/favicon.ico}" type="image/x-icon">
<style>
body {
background-color: #ffecc3;
}
</style>
</head>
<body>
<pre th:text="${content}"></pre>
</body>
</html>

View File

@ -0,0 +1,233 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta name="description" content="Simulation du MMO Grepolis">
<title>GrepoSimu</title>
<link rel="shortcut icon" th:href="@{/favicon.ico}" type="image/x-icon">
<style>
body {
background-color: #ffecc3;
}
.squareImage {
width: 50px;
height: 50px;
}
.squareNumber {
width: 50px;
}
.slider {
-webkit-appearance: none; /* Override default CSS styles */
appearance: none;
width: 276px; /* Full-width */
height: 8px; /* Specified height */
background-image: url("[[@{/images/slider3.png}]]"); /* Grey background */
outline: none; /* Remove outline */
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none; /* Override default look */
appearance: none;
width: 18px; /* Set a specific slider handle width */
height: 18px; /* Slider handle height */
background-image: url("[[@{/images/sliderButton.png}]]"); /* Grey background */
}
.slider::-moz-range-thumb {
width: 18px; /* Set a specific slider handle width */
height: 18px; /* Slider handle height */
background-image: url("[[@{/images/sliderButton.png}]]"); /* Grey background */
}
span.fixed50px {
width: 50px;
display: inline-block;
}
.parameters-selection {
display: flex;
column-gap: 0px
}
.small-number-selection {
border: 2px solid #e1af55;
background: #ffe2a1;
display: inline-flex;
padding: 4px;
}
.numbered-unit {
width: 50px;
height: 50px;
text-align: center;
background-color: #CCCCCC;
position: relative;
display: inline-block;
}
.numbered-unit span {
width: 50px;
color: #000000;
float: left;
position: absolute;
left: 0px;
padding-top: 26px;
text-align: right;
color: #fff;
text-shadow: 1px 1px 0 #000;
font-family: Verdana;
font-weight: 700;
}
.numbered-unit img {
width: 50px;
height: 50px;
border: 0px;
margin: auto;
}
</style>
</head>
<body>
<form action="#" th:object="${defCtx}" method="post" id="simuform" >
<fieldset id="herosFields">
<legend>Héros</legend>
<select name="heros" id="heros" th:field="*{hero}">
<option value="none">Aucun</option>
<option th:each="hero : ${heroes}" th:value="${hero.id}"><span th:text="${hero.name}"/></option>
</select>
<label for="heroLevelSlider">Niveau du héros: (<span id="heroLevelSliderInfo" class="fixed50px"></span>)</label>
<input type="range" min="1" max="20" value="1" class="slider" id="heroLevelSlider" th:field="*{heroLevel}">
<script>
var heroSlider = document.getElementById("heroLevelSlider");
var heroOutput = document.getElementById("heroLevelSliderInfo");
heroOutput.innerHTML = "lvl. "+heroSlider.value;
// Update the current slider value (each time you drag the slider handle)
heroSlider.oninput = function() {
heroOutput.innerHTML = "lvl. "+this.value;
}
</script>
</fieldset>
<fieldset>
<legend>Unités</legend>
<table cellspacing="0" cellpadding="0">
<tr>
<td th:each="unite : ${defUnits}" class="squareContainer">
<img class="squareImage" th:for="'unite-'+${unite.id}" th:src="@{/images/units/{uname}.png(uname=${unite.id})}"/>
<input type="number" class="squareNumber" th:id="'unite-'+${unite.id}" th:field="*{units[__${unite.id}__]}"/>
</td>
</tr>
</table>
</fieldset>
<fieldset>
<legend>Batiments</legend>
<label for="hasTower">Tour :</label>
<input type="checkbox" id="hasTower" th:field="*{hasTower}"/><br/>
<label for="wallLevelSlider">Niveau des remparts: (<span id="wallLevelSliderInfo" class="fixed50px"></span>)</label>
<input type="range" min="0" max="25" value="1" class="slider" id="wallLevelSlider" th:field="*{wallLevel}">
<script>
var wallSlider = document.getElementById("wallLevelSlider");
var wallOutput = document.getElementById("wallLevelSliderInfo");
wallOutput.innerHTML = "lvl. "+wallSlider.value;
// Update the current slider value (each time you drag the slider handle)
wallSlider.oninput = function() {
wallOutput.innerHTML = "lvl. "+this.value;
}
</script>
</fieldset>
<fieldset>
<legend>Pouvoirs</legend>
<table cellspacing="0" cellpadding="0">
<tr>
<td th:each="power : ${defPowers}" class="squareContainer">
<img class="squareImage" th:for="'power-'+${power.id}" th:src="@{/images/powers/{pname}.png(pname=${power.id})}"/>
<input type="checkbox" th:id="'power-'+${power.id}" th:field="*{powersAsMap[__${power.id}__]}" th:value="true"/>
</td>
</tr>
</table>
</fieldset>
<fieldset>
<legend>Recherches</legend>
<table cellspacing="0" cellpadding="0">
<tr>
<td th:each="research : ${defResearches}" class="squareContainer">
<img class="squareImage" th:for="'research-'+${research.id}" th:src="@{/images/researches/{rname}.png(rname=${research.id})}"/>
<input type="checkbox" th:id="'research-'+${research.id}" th:field="*{researchesAsMap[__${research.id}__]}" th:value="true"/>
</td>
</tr>
</table>
</fieldset>
<fieldset>
<legend>Conseillers</legend>
<table cellspacing="0" cellpadding="0">
<tr>
<td th:each="counsellor : ${defCounsellors}" class="squareContainer">
<img class="squareImage" th:for="'counsellor-'+${counsellor}" th:src="@{/images/counsellor/{cname}.png(cname=${counsellor})}"/>
<input type="checkbox" th:id="'counsellor-'+${counsellor}" th:field="*{counsellorsAsMap[__${counsellor}__]}" th:value="true"/>
</td>
</tr>
</table>
</fieldset>
<fieldset>
<legend>Bonus de jeu</legend>
<label for="nightBonus">Bonus de nuit :</label>
<input type="checkbox" id="nightBonus" th:field="*{nightBonus}" th:value="true"/><br/>
</fieldset>
<button type="button" id="compute">Calculer</button>
</form>
<section id="result">
</section>
<script>
function computeResults() {
var xhr = new XMLHttpRequest();
xhr.open("POST", "[[@{/simulate}]]");
xhr.onload = function(event){
document.getElementById("result").innerHTML = event.target.response
};
// or onerror, onabort
var formData = new FormData(document.getElementById("simuform"));
xhr.send(formData);
}
document.getElementById("compute").addEventListener('click',computeResults)
</script>
</body>

View File

@ -1,5 +1,7 @@
package com.bernard.greposimu; package com.bernard.greposimu;
import java.io.IOException;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
@ -7,7 +9,9 @@ import org.springframework.boot.test.context.SpringBootTest;
class GrepoSimuApplicationTests { class GrepoSimuApplicationTests {
@Test @Test
void contextLoads() { void loadGameData() throws IOException {
// System.out.println("Reading game data");
// System.out.println(GrepoSimu.readGameData());
} }
} }