diff --git a/src/main/java/com/bernard/greposimu/Utils.java b/src/main/java/com/bernard/greposimu/Utils.java index 83a8832..c6a91f4 100644 --- a/src/main/java/com/bernard/greposimu/Utils.java +++ b/src/main/java/com/bernard/greposimu/Utils.java @@ -2,13 +2,11 @@ package com.bernard.greposimu; import java.util.AbstractMap; import java.util.AbstractSet; -import java.util.Collection; import java.util.EnumSet; import java.util.Iterator; import java.util.Map; -import java.util.Map.Entry; +import java.util.Random; import java.util.Set; -import java.util.function.Function; import com.bernard.greposimu.model.game.Identified; @@ -82,4 +80,9 @@ public class Utils { }; } + + public static E randFromSet(Random r, Set set) { + E el = set.stream().sorted().skip(r.nextInt(set.size())).findFirst().get(); + return el; + } } diff --git a/src/main/java/com/bernard/greposimu/controller/JSONReader.java b/src/main/java/com/bernard/greposimu/controller/JSONReader.java index fd85e5a..004ce58 100644 --- a/src/main/java/com/bernard/greposimu/controller/JSONReader.java +++ b/src/main/java/com/bernard/greposimu/controller/JSONReader.java @@ -40,7 +40,6 @@ public class JSONReader { JSONObject powersJ = json.getJSONObject("powers"); Set powers = new HashSet<>(); for(String p : powersJ.keySet()) { - System.out.println("Power "+p); JSONObject power = powersJ.getJSONObject(p); JSONObject metadefaults = power.isNull("meta_defaults")?null:power.getJSONObject("meta_defaults"); if(power.isNull("god_id") || power.getString("god_id").isEmpty()) { @@ -61,7 +60,6 @@ public class JSONReader { .orElseGet(() -> power.optJSONObject("description")) ).getJSONObject(dependentKind).keySet(); for(String kind : kinds){ - System.out.println("kind "+kind); powers.add(new MultitypePower( power.getString("id")+":"+kind, Optional.ofNullable(power.optString("name")).orElseGet(() -> power.getJSONObject("name").getJSONObject(dependentKind).getString(kind)), diff --git a/src/main/java/com/bernard/greposimu/controller/Randomizer.java b/src/main/java/com/bernard/greposimu/controller/Randomizer.java new file mode 100644 index 0000000..500f26d --- /dev/null +++ b/src/main/java/com/bernard/greposimu/controller/Randomizer.java @@ -0,0 +1,7 @@ +package com.bernard.greposimu.controller; + +public class Randomizer { + + + +} diff --git a/src/main/java/com/bernard/greposimu/controller/SimulatorController.java b/src/main/java/com/bernard/greposimu/controller/SimulatorController.java index bbf22cb..8d57d9b 100644 --- a/src/main/java/com/bernard/greposimu/controller/SimulatorController.java +++ b/src/main/java/com/bernard/greposimu/controller/SimulatorController.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Random; import java.util.Set; import org.springframework.stereotype.Controller; @@ -11,13 +12,16 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; import com.bernard.greposimu.GrepoSimuApplication; import com.bernard.greposimu.Utils; import com.bernard.greposimu.engine.game.Fight; import com.bernard.greposimu.model.DefContext; import com.bernard.greposimu.model.FightStats; +import com.bernard.greposimu.model.OffContext; import com.bernard.greposimu.model.game.GameConfig; +import com.bernard.greposimu.model.game.God; import com.bernard.greposimu.model.game.units.Unit; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; @@ -26,91 +30,249 @@ import com.fasterxml.jackson.databind.SerializationFeature; public class SimulatorController { @GetMapping("/simulator") - public String simulator(Model model) throws IOException { + public String simulator(Model model, @RequestParam boolean random) throws IOException { GameConfig gc = GrepoSimuApplication.GREPOLIS_GC; model.addAttribute("heroes", gc.getHeroes()); model.addAttribute("defUnits", Fight.relevantDefUnits(gc)); - model.addAttribute("defCounsellors",Fight.relevantDefCounsellors(gc)); - model.addAttribute("defResearches",Fight.relevantDefResearches(gc)); - model.addAttribute("defPowers",Fight.relevantDefPowers(gc)); - model.addAttribute("ctx",new DefSimulatorParams()); + model.addAttribute("defCounsellors",DefContext.COUNSELLORS); + model.addAttribute("defResearches",DefContext.RESEARCHES); + model.addAttribute("defPowers",DefContext.POWERS); + model.addAttribute("offUnits", Fight.relevantOffUnits(gc)); + model.addAttribute("offCounsellors",OffContext.COUNSELLORS); + model.addAttribute("offResearches",OffContext.RESEARCHES); + model.addAttribute("offPowers",OffContext.POWERS); + SimulatorParams params = new SimulatorParams(); + if(random) + params.randomize(new Random(), gc); + model.addAttribute("ctx",params); return "simulator"; } @PostMapping("/simulate") @GetMapping("/simulate") - public String simulate(@ModelAttribute DefSimulatorParams defParams, Model model) throws IOException { - if(defParams == null) - defParams = new DefSimulatorParams(); + public String simulate(@ModelAttribute SimulatorParams params, Model model) throws IOException { + if(params == null) + params = new SimulatorParams(); GameConfig gc = GrepoSimuApplication.GREPOLIS_GC; - DefContext defCtx = defParams.asDefContext(gc); - - FightStats cityStats = Fight.computeDefStats(gc,defCtx); - ObjectMapper mapper = new ObjectMapper(); + DefContext defCtx = params.asDefContext(gc); + OffContext offCtx = params.asOffContext(gc); + FightStats defStats = Fight.computeDefStats(gc,defCtx); + FightStats offStats = Fight.computeOffStats(gc,offCtx); + ObjectMapper mapper = new ObjectMapper(); mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); - model.addAttribute("content",cityStats.toString()+"\n"+mapper.writerWithDefaultPrettyPrinter().writeValueAsString(defCtx)); + model.addAttribute("content", + defStats.toString()+"\n"+ + offStats.toString()+"\n"+ + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(defCtx)+"\n"+ + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(offCtx) + ); return "debug"; } -public static class DefSimulatorParams { +public static class SimulatorParams { // unitID -> number of units - public Map units = new HashMap<>(); - - public String hero = ""; - public int heroLevel = 0; + public Map defUnits = new HashMap<>(); + public Map offUnits = new HashMap<>(); + + public String defHero = ""; + public int defHeroLevel = 0; + public String offHero = ""; + public int offHeroLevel = 0; public int wallLevel = 0; public boolean hasTower = false; - public Set powers = new HashSet<>(); - public Set researches = new HashSet<>(); - - public Set counsellors = new HashSet<>(); + public Set defPowers = new HashSet<>(); + public Set offPowers = new HashSet<>(); + public Set defResearches = new HashSet<>(); + public Set offResearches = new HashSet<>(); + public Set defCounsellors = new HashSet<>(); + public Set offCounsellors = new HashSet<>(); public boolean nightBonus = false; + + int luck, moral; + + int olympicSwordGrepolympiaSummerLevel = 1; + int offOlympicSensesGrepolympiaSummerLevel = 1; + int aresRageLevel = 1; + int aresArmyFurySpent = 0; + int bloodlustFurySpent = 0; + + int defOlympicSensesGrepolympiaSummerLevel = 1; + int olympicTorchGrepolympiaSummerLevel = 1; + int soteriasShrineLevel = 1; + boolean strategyBreach = false; + boolean allianceModifier = false; + public DefContext asDefContext(GameConfig gc) { - Map unitsU = new HashMap<>(units.size()); - for(String u : units.keySet()) - unitsU.put(gc.getUnit(u), units.get(u)); + Map unitsU = new HashMap<>(defUnits.size()); + for(String u : defUnits.keySet()) + unitsU.put(gc.getUnit(u), defUnits.get(u)); return new DefContext( unitsU, - gc.getHero(hero), - heroLevel, + gc.getHero(defHero), + defHeroLevel, wallLevel, hasTower, - powers, - 1, 1, 1, - researches, - counsellors, + defPowers, + defOlympicSensesGrepolympiaSummerLevel, olympicTorchGrepolympiaSummerLevel, soteriasShrineLevel, + defResearches, + defCounsellors, nightBonus ); } - public Map getUnits() { - return units; + public static Object random() { + // TODO Auto-generated method stub + return null; } - public void setUnits(Map units) { - this.units = units; + public OffContext asOffContext(GameConfig gc) { + Map unitsU = new HashMap<>(offUnits.size()); + for(String u : offUnits.keySet()) + unitsU.put(gc.getUnit(u), offUnits.get(u)); + return new OffContext( + unitsU, + gc.getHero(defHero), + defHeroLevel, + luck, moral, defPowers, + olympicSwordGrepolympiaSummerLevel, offOlympicSensesGrepolympiaSummerLevel, aresRageLevel, aresArmyFurySpent, bloodlustFurySpent, defResearches, + defCounsellors, allianceModifier, allianceModifier + ); + } + + public void randomize(Random r,GameConfig gc) { + + offUnits = new HashMap<>(); + God g = null; + if(r.nextDouble()<0.8) + g = Utils.randFromSet(r, gc.getGods()); + for(Unit u : Fight.relevantOffUnits(gc)) + if(u.getGod() == null || u.getGod().equals(g)) + if(r.nextDouble() < 0.7) + offUnits.put(u.getId(), (int) ((r.nextExponential()*50+10)/Math.max(u.getPopulation(),1))); + defUnits = new HashMap<>(); + g = null; + if(r.nextDouble()<0.8) + g = Utils.randFromSet(r, gc.getGods()); + for(Unit u : Fight.relevantDefUnits(gc)) + if(u.getGod() == null || u.getGod().equals(g)) + if(r.nextDouble() < 0.7) + defUnits.put(u.getId(), (int) ((r.nextExponential()*50+10)/Math.max(u.getPopulation(),1))); + + offHero = null; + offHeroLevel = 0; + if(r.nextDouble()<.7) { + offHero = Utils.randFromSet(r, gc.getHeroes()).getId(); + offHeroLevel = r.nextInt(1, 21); + } + defHero = null; + defHeroLevel = 0; + if(r.nextDouble()<.7) { + defHero = Utils.randFromSet(r, gc.getHeroes()).getId(); + defHeroLevel = r.nextInt(1, 21); + } + + wallLevel = r.nextInt(0, 26); + hasTower = (wallLevel>20) && (r.nextDouble()<0.2); + + luck = (int) (Math.tanh(r.nextGaussian())*20); + moral = 100; + if(r.nextDouble()<.7) + moral = (int) Math.max(100-(r.nextExponential()*20),0); + + offPowers = new HashSet<>(); + defPowers = new HashSet<>(); + for(String p : OffContext.POWERS) { + if(r.nextDouble()<0.05) + offPowers.add(p); + if(r.nextDouble()<0.05) + defPowers.add(p); + } + + olympicSwordGrepolympiaSummerLevel = r.nextInt(1, 5); + offOlympicSensesGrepolympiaSummerLevel = r.nextInt(1, 5); + aresRageLevel = r.nextInt(1, 11); + aresArmyFurySpent = r.nextInt(1, 5000); + bloodlustFurySpent = r.nextInt(1, 5000); + defOlympicSensesGrepolympiaSummerLevel = r.nextInt(1, 5); + olympicTorchGrepolympiaSummerLevel = r.nextInt(1, 5); + soteriasShrineLevel = r.nextInt(1, 11); + + offResearches = new HashSet<>(); + defResearches = new HashSet<>(); + for(String p : OffContext.RESEARCHES) { + if(r.nextDouble()<0.20) + offResearches.add(p); + if(r.nextDouble()<0.20) + defResearches.add(p); + } + offCounsellors = new HashSet<>(); + defCounsellors = new HashSet<>(); + for(String p : OffContext.COUNSELLORS) { + if(r.nextDouble()<0.05) + offCounsellors.add(p); + if(r.nextDouble()<0.05) + defCounsellors.add(p); + } + + strategyBreach = (r.nextDouble()<0.02); + allianceModifier = (r.nextDouble()<0.001); + nightBonus = (r.nextDouble()<0.02); + } - public String getHero() { - return hero; + + public Map getDefUnits() { + return defUnits; } - public void setHero(String hero) { - this.hero = hero; + public void setDefUnits(Map defUnits) { + this.defUnits = defUnits; } - public int getHeroLevel() { - return heroLevel; + public Map getOffUnits() { + return offUnits; } - public void setHeroLevel(int heroLevel) { - this.heroLevel = heroLevel; + public void setOffUnits(Map offUnits) { + this.offUnits = offUnits; + } + + public String getDefHero() { + return defHero; + } + + public void setDefHero(String defHero) { + this.defHero = defHero; + } + + public int getDefHeroLevel() { + return defHeroLevel; + } + + public void setDefHeroLevel(int defHeroLevel) { + this.defHeroLevel = defHeroLevel; + } + + public String getOffHero() { + return offHero; + } + + public void setOffHero(String offHero) { + this.offHero = offHero; + } + + public int getOffHeroLevel() { + return offHeroLevel; + } + + public void setOffHeroLevel(int offHeroLevel) { + this.offHeroLevel = offHeroLevel; } public int getWallLevel() { @@ -129,22 +291,98 @@ public static class DefSimulatorParams { this.hasTower = hasTower; } - public Set getPowers() { - return powers; - } - - public Map getPowersAsMap() { - return Utils.setToMap(powers); + public Set getDefPowers() { + return defPowers; } - public Map getResearchesAsMap() { - return Utils.setToMap(researches); + public void setDefPowers(Set defPowers) { + this.defPowers = defPowers; } - - public Map getCounsellorsAsMap() { - return Utils.setToMap(counsellors); + + public Set getOffPowers() { + return offPowers; } - + + public void setOffPowers(Set offPowers) { + this.offPowers = offPowers; + } + + public Set getDefResearches() { + return defResearches; + } + + public void setDefResearches(Set defResearches) { + this.defResearches = defResearches; + } + + public Set getOffResearches() { + return offResearches; + } + + public void setOffResearches(Set offResearches) { + this.offResearches = offResearches; + } + + public Set getDefCounsellors() { + return defCounsellors; + } + + public void setDefCounsellors(Set defCounsellors) { + this.defCounsellors = defCounsellors; + } + + public Set getOffCounsellors() { + return offCounsellors; + } + + public void setOffCounsellors(Set offCounsellors) { + this.offCounsellors = offCounsellors; + } + + public int getLuck() { + return luck; + } + + public void setLuck(int luck) { + this.luck = luck; + } + + public int getMoral() { + return moral; + } + + public void setMoral(int moral) { + this.moral = moral; + } + + public boolean isStrategyBreach() { + return strategyBreach; + } + + public void setStrategyBreach(boolean strategyBreach) { + this.strategyBreach = strategyBreach; + } + + public boolean isAllianceModifier() { + return allianceModifier; + } + + public void setAllianceModifier(boolean allianceModifier) { + this.allianceModifier = allianceModifier; + } + + public Map getDefPowersAsMap() { + return Utils.setToMap(defPowers); + } + + public Map getDefResearchesAsMap() { + return Utils.setToMap(defResearches); + } + + public Map getDefCounsellorsAsMap() { + return Utils.setToMap(defCounsellors); + } + public boolean isNightBonus() { return nightBonus; } @@ -153,6 +391,5 @@ public static class DefSimulatorParams { this.nightBonus = nightBonus; } - } } diff --git a/src/main/java/com/bernard/greposimu/engine/game/Fight.java b/src/main/java/com/bernard/greposimu/engine/game/Fight.java index c1aa7ba..a3bb50b 100644 --- a/src/main/java/com/bernard/greposimu/engine/game/Fight.java +++ b/src/main/java/com/bernard/greposimu/engine/game/Fight.java @@ -8,6 +8,7 @@ import com.bernard.greposimu.model.FightStats; import com.bernard.greposimu.model.OffContext; import com.bernard.greposimu.model.game.GameConfig; import com.bernard.greposimu.model.game.researches.Research; +import com.bernard.greposimu.model.game.units.FightType; import com.bernard.greposimu.model.game.units.NavalUnit; import com.bernard.greposimu.model.game.units.TerrestrialUnit; import com.bernard.greposimu.model.game.units.Unit; @@ -41,18 +42,14 @@ public class Fight { if(u.isMythological()) unitsBonuses.put(u, FightStats.add(unitsBonuses.getOrDefault(u, FightStats.zero()), FightStats.cst(0.1))); if(def.hasPhalanx()) - for(Unit u : gc.getUnits()) - if(u.isGround()) - unitsBonuses.put(u, FightStats.add(unitsBonuses.getOrDefault(u, FightStats.zero()), FightStats.cst(0.1))); + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(0.1)); if(def.hasRam()) - for(Unit u : gc.getUnits()) - if(u.isNaval()) - unitsBonuses.put(u, FightStats.add(unitsBonuses.getOrDefault(u, FightStats.zero()), FightStats.cst(0.1))); + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.naval(0.1)); // Counsellors if(def.hasPriest()) for(Unit u : gc.getUnits()) - if(u.isGround()) + if(u.isMythological()) unitsBonuses.put(u, FightStats.add(unitsBonuses.getOrDefault(u, FightStats.zero()), FightStats.cst(0.2))); if(def.hasCommander()) everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(0.2)); @@ -75,7 +72,7 @@ public class Fight { if(def.getOlympicTorchGrepolympiaSummerLevel()!=0) everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.05*def.getOlympicTorchGrepolympiaSummerLevel())); if(def.getSoteriasShrineLevel()!=0) - everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.007*def.getOlympicTorchGrepolympiaSummerLevel())); + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.007*def.getSoteriasShrineLevel())); if(def.hasNarcissism()) everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(-0.1)); @@ -95,6 +92,16 @@ public class Fight { )); } + // HeroStat + if(def.getHero() != null) { + total = FightStats.add(total, new FightStats( + def.getHero().getHackDef() * (1.0+0.1*def.getHeroLevel()), + def.getHero().getPierceDef() * (1.0+0.1*def.getHeroLevel()), + def.getHero().getDistanceDef() * (1.0+0.1*def.getHeroLevel()), + 0.0 + )); + } + return total; } @@ -109,27 +116,127 @@ public class Fight { throw new UnsupportedOperationException("I don't know how to manage units of type "+u.getClass().getName()); } - public static List relevantDefPowers(GameConfig 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.grepolympia_summer", "olympic_senses.grepolympia_summer", "missions_power_4.missions_dionysia", - "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.not_cast","narcissism"); + public static FightStats computeOffStats(GameConfig gc, OffContext off) { + + FightStats everyoneStatsBonus = FightStats.zero(); + Map unitsBonuses; + + // Heroes + unitsBonuses = Heroes.heroFightBonuses(gc, off.getHero(), off.getHeroLevel(), false); + + // Researches + if(off.hasDivineSelection()) + for(Unit u : gc.getUnits()) + if(u.isMythological()) + unitsBonuses.put(u, FightStats.add(unitsBonuses.getOrDefault(u, FightStats.zero()), FightStats.cst(0.1))); + if(off.hasPhalanx()) + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(0.1)); + if(off.hasRam()) + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.naval(0.1)); + + + // Counsellors + if(off.hasPriest()) + for(Unit u : gc.getUnits()) + if(u.isMythological()) + unitsBonuses.put(u, FightStats.add(unitsBonuses.getOrDefault(u, FightStats.zero()), FightStats.cst(0.2))); + if(off.hasCommander()) + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(0.2)); + if(off.hasCaptain()) + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.naval(0.2)); + + // Powers + if(off.hasMyrmidionAttack()) + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.1)); + if(off.hasAttackBoost()) + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.1)); + if(off.hasAttackPenalty()) + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(-0.1)); + if(off.hasLongtermAttackBoost()) + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.1)); + if(off.hasLuxurious_residence()) + ;//XXX Unimplemented + if(off.hasAttack_ship_attack_boost_small()) + unitsBonuses.put(gc.getUnit("attack_ship"), FightStats.add(unitsBonuses.getOrDefault(gc.getUnit("attack_ship"), FightStats.zero()), FightStats.cst(0.1))); + if(off.hasAttack_ship_attack_boost_medium()) + unitsBonuses.put(gc.getUnit("attack_ship"), FightStats.add(unitsBonuses.getOrDefault(gc.getUnit("attack_ship"), FightStats.zero()), FightStats.cst(0.2))); + if(off.hasAttack_ship_attack_boost_large()) + unitsBonuses.put(gc.getUnit("attack_ship"), FightStats.add(unitsBonuses.getOrDefault(gc.getUnit("attack_ship"), FightStats.zero()), FightStats.cst(0.3))); + if(off.hasRareAttackBoost()) + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.1)); + if(off.hasEpicAttackBoost()) + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.2)); + if(off.getOlympicSwordGrepolympiaSummerLevel()!=0) + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.05*off.getOlympicSwordGrepolympiaSummerLevel())); + if(off.getAresRageLevel()!=0) + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.01*off.getAresRageLevel())); + if(off.getBloodlust()!=0) + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(0.05+0.01*Math.floorDiv(off.getBloodlust(),200))); + if(off.hasFairWind()) + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.naval(0.1)); + if(off.hasDesire()) + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(-0.1)); + if(off.hasStrengthOfHeroes()) + //XXX check if working + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(0.1)); + if(off.hasEffortOfTheHuntress()) + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.ofType(FightType.DISTANCE,0.15)); + + if(off.hasStrategyBreach()) + everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.naval(-0.5)); + + + // Units + FightStats total = FightStats.zero(); + for(Unit u : gc.getUnits()) { + // total = total + ucount * ((1+bonusA+bonusB) * ustats) + total = FightStats.add(total, + FightStats.prod(off.unitCount(u), + FightStats.mul( + FightStats.add(FightStats.one(),everyoneStatsBonus,unitsBonuses.getOrDefault(u, FightStats.zero())) + , makeOffStats(u)) + )); + } + if(off.getAresArmyFurySpent()!=0) + // Ading aresarmy/25 spartiates + total = FightStats.add(total, + FightStats.prod(Math.floorDiv(off.getAresArmyFurySpent(), 25), + FightStats.mul( + FightStats.add(FightStats.one(),everyoneStatsBonus,unitsBonuses.getOrDefault(gc.getUnit("spartoi"), FightStats.zero())) + , makeOffStats(gc.getUnit("spartoi"))) + )); + + + + // HeroStat + if(off.getHero() != null) { + total = FightStats.add(total, new FightStats( + off.getHero().getHackDef() * (1.0+0.1*off.getHeroLevel()), + off.getHero().getPierceDef() * (1.0+0.1*off.getHeroLevel()), + off.getHero().getDistanceDef() * (1.0+0.1*off.getHeroLevel()), + 0.0 + )); + } + + return total; } - public static List relevantDefResearches(GameConfig gd) { - return List.of("divine_selection","phalanx","ram") - .stream().map(gd::getResearch).toList(); + + public static FightStats makeOffStats(Unit u) { + if(u instanceof TerrestrialUnit) { + TerrestrialUnit tu = (TerrestrialUnit)u; + return FightStats.ofType(tu.getAttackType(), tu.getAttack()); + }else if(u instanceof NavalUnit) { + NavalUnit nu = (NavalUnit)u; + return new FightStats(0.0, 0.0, 0.0, nu.getAttack()); + } + throw new UnsupportedOperationException("I don't know how to manage units of type "+u.getClass().getName()); } + public static List relevantDefUnits(GameConfig gc) { return gc.getUnits().stream().toList(); } - public static List relevantDefCounsellors(GameConfig data) { - return List.of("priest","commander","captain"); - } - public FightStats computeOffStats(DefContext off) { - //TODO computeOffStats - throw new UnsupportedOperationException("Simulator not created"); + public static List relevantOffUnits(GameConfig gc) { + return gc.getUnits().stream().toList(); } public FightResult simulateFight(OffContext off, DefContext def) { diff --git a/src/main/java/com/bernard/greposimu/model/DefContext.java b/src/main/java/com/bernard/greposimu/model/DefContext.java index 26a4474..34059fc 100644 --- a/src/main/java/com/bernard/greposimu/model/DefContext.java +++ b/src/main/java/com/bernard/greposimu/model/DefContext.java @@ -1,21 +1,22 @@ package com.bernard.greposimu.model; -import java.util.AbstractMap; -import java.util.Collection; +import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; -import com.bernard.greposimu.model.game.GameConfig; import com.bernard.greposimu.model.game.units.Hero; import com.bernard.greposimu.model.game.units.Unit; public class DefContext { // UNITS - Map units; + // Units unrelated to the attacker + Map otherUnits; + // Units owned by allies of the attacker + Map alliedUnits; + // Units owned by the attacher + Map selfUnits; // HEROS Hero hero = null; @@ -81,8 +82,16 @@ public class DefContext { boolean nightBonus = false; public DefContext(Map units, Hero hero, int heroLevel, int wallLevel, boolean hasTower, + Set powers, int soteriasShrinePowerLevel, int olympicTorchGrepolympiaSummerLevel, int olympicSensesGrepolympiaSummerLevel, + Set researches, Set counsellors, boolean nightBonus) { + this(units,Map.of(),Map.of(),hero,heroLevel,wallLevel,hasTower,powers,soteriasShrinePowerLevel,olympicTorchGrepolympiaSummerLevel,olympicSensesGrepolympiaSummerLevel,researches,counsellors,nightBonus); + } + + public DefContext(Map otherUnits, Map alliedUnits, Map selfUnits, Hero hero, int heroLevel, int wallLevel, boolean hasTower, Set powers, int soteriasShrinePowerLevel, int olympicTorchGrepolympiaSummerLevel, int olympicSensesGrepolympiaSummerLevel, Set researches, Set counsellors, boolean nightBonus) { - this.units = units; + this.otherUnits = otherUnits; + this.alliedUnits = alliedUnits; + this.selfUnits = selfUnits; this.hero = hero; this.heroLevel = heroLevel; this.wallLevel = wallLevel; @@ -96,7 +105,7 @@ public class DefContext { if(powers.contains("longterm_defense_boost"))this.longtermDefenseBoost = true; if(powers.contains("assassins_acumen"))this.assassinsAcumen = true; if(powers.contains("rare_defense_boost"))this.rareDefenseBoost = true; - if(powers.contains("epic_defense_boost"))this.defenseBoost = true; + if(powers.contains("epic_defense_boost"))this.epicDefenseBoost = true; if(powers.contains("olympic_torch"))this.olympicTorchGrepolympiaSummerLevel = olympicTorchGrepolympiaSummerLevel; if(powers.contains("olympic_senses"))this.olympicSensesGrepolympiaSummerLevel = olympicSensesGrepolympiaSummerLevel; if(powers.contains("missions_power_4"))this.missionsPower4 = true; @@ -117,11 +126,11 @@ public class DefContext { this.nightBonus = nightBonus; } - public Map getUnits() { - return units; - } public int unitCount(Unit u) { - return Optional.ofNullable(units.getOrDefault(u,0)).orElse(0); + return + Optional.ofNullable(otherUnits.getOrDefault(u,0)).orElse(0) + + Optional.ofNullable(alliedUnits.getOrDefault(u,0)).orElse(0) + + Optional.ofNullable(selfUnits.getOrDefault(u,0)).orElse(0); } public Hero getHero() { return hero; @@ -247,4 +256,11 @@ public class DefContext { return narcissism; } + public static final List POWERS = 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", "narcissism"); + public static final List RESEARCHES = List.of("divine_selection","phalanx","ram"); + public static final List COUNSELLORS = List.of("priest","commander","captain"); } diff --git a/src/main/java/com/bernard/greposimu/model/FightStats.java b/src/main/java/com/bernard/greposimu/model/FightStats.java index 5cc0ea5..2b0eabc 100644 --- a/src/main/java/com/bernard/greposimu/model/FightStats.java +++ b/src/main/java/com/bernard/greposimu/model/FightStats.java @@ -2,6 +2,8 @@ package com.bernard.greposimu.model; import java.util.Arrays; +import com.bernard.greposimu.model.game.units.FightType; + public class FightStats implements Cloneable{ public double hack; public double pierce; @@ -15,6 +17,19 @@ public class FightStats implements Cloneable{ this.ship = ship; } + public static final FightStats ofType(FightType type, double value) { + switch(type) { + case HACK: + return new FightStats(value, 0.0, 0.0, 0.0); + case PIERCE: + return new FightStats(0.0, value, 0.0, 0.0); + case DISTANCE: + return new FightStats(0.0, 0.0, value, 0.0); + default: + return null; + } + } + public static final FightStats zero() { return new FightStats(0, 0, 0, 0); } diff --git a/src/main/java/com/bernard/greposimu/model/OffContext.java b/src/main/java/com/bernard/greposimu/model/OffContext.java index f374a5e..2e64252 100644 --- a/src/main/java/com/bernard/greposimu/model/OffContext.java +++ b/src/main/java/com/bernard/greposimu/model/OffContext.java @@ -1,23 +1,288 @@ package com.bernard.greposimu.model; +import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; +import com.bernard.greposimu.model.game.units.Hero; +import com.bernard.greposimu.model.game.units.Unit; + public class OffContext { - // unitID -> number of units - public Map units; - public String heros; - public int herosLevel; + Map units; - public int luck; - public int morale; + Hero hero; + int heroLevel; - public Set powers; - public Set researches; + int luck; + int moral; - public Set counsellors; + // RESEARCHES + boolean divineSelection= false, phalanx = false, ram=false, combatExperience=false; + + // COUNSELLORS + boolean commander= false; + boolean priest = false; + boolean captain = false; + + // EFFECTS + // PC x2 + boolean acumen = false; + // PC x4 + boolean divineSenses= false; + // Attq +10%, Def -10% + boolean myrmidionAttack= false; + // Attq +10% + boolean attackBoost= false; + // Attq -10% + boolean attackPenalty= false; + // Attq +10% + boolean longtermAttackBoost = false; + //XXX implement this :/ + boolean luxuriousResidence = false; + // BF attq +10% + boolean attack_ship_attack_boost_small = false; + // BF attq +20% + boolean attack_ship_attack_boost_medium = false; + // BF attq +30% + boolean attack_ship_attack_boost_large = false; + // PC +50% + boolean assassinsAcumen= false; + // Attq +10% + boolean rareAttackBoost= false; + // Attq +20% + boolean epicAttackBoost= false; + // Attq +5%*level + int olympicSwordGrepolympiaSummerLevel = 0; + // PC+10%*level + int olympicSensesGrepolympiaSummerLevel = 0; + // +50% PC + boolean missionsPower4= false; + // PC+50% (sauf BC, transport) + boolean divineBattleStrategyRare= false; + // PC+100% (sauf BC, transport) + boolean divineBattleStrategyEpic= false; + // PC+50% against naval (sauf BC,transports) + boolean navalBattleStrategyRare= false; + // PC+100% against naval (sauf BC, transport) + boolean navalBattleStrategyEpic= false; + // PC+50% against terrestres + boolean landBattleStrategyRare= false; + // PC+100% against terrestre + boolean landBattleStrategyEpic= false; + // Attq +1%*level + int aresRageLevel = 0; + + // SPELLS + // Add one sparte for every 25 fury used + int aresArmyFurySpent = 0; + // Attq +5% +(1% for each 200 fury spent) + // PC + 1% for each 100 fury spent + int bloodlustFurySpent = 0; + // Attq naval +10% + boolean fairWind = false; + // Attq -10% + boolean desire = false; + // Terr + aer +10% attq + boolean strengthOfHeroes = false; + // Attq distance +15% + boolean effortOfTheHuntress = false; + + boolean strategyBreach = false; + boolean allianceModifier = false; + + + public OffContext(Map units, Hero hero, int heroLevel, int luck, int moral, + Set powers, int olympicSwordGrepolympiaSummerLevel, int olympicSensesGrepolympiaSummerLevel, + int aresRageLevel, int aresArmyFurySpent, int bloodlustFurySpent, Set researches, Set counsellors, + boolean strategyBreach, boolean allianceModifier) { + this.units = units; + this.hero = hero; + this.heroLevel = heroLevel; + this.luck = luck; + this.moral = moral; + + if(powers.contains("acumen"))this.acumen = true; + if(powers.contains("divine_senses"))this.divineSenses = true; + if(powers.contains("myrmidion_attack"))this.myrmidionAttack = true; + if(powers.contains("attack_boost"))this.attackBoost = true; + if(powers.contains("attack_penalty"))this.attackPenalty = true; + if(powers.contains("longterm_attack_boost"))this.longtermAttackBoost = true; + if(powers.contains("attack_ship_attack_boost_small"))this.attack_ship_attack_boost_small = true; + if(powers.contains("attack_ship_attack_boost_medium"))this.attack_ship_attack_boost_medium = true; + if(powers.contains("attack_ship_attack_boost_large"))this.attack_ship_attack_boost_large = true; + if(powers.contains("assassins_acumen"))this.assassinsAcumen = true; + if(powers.contains("rare_attack_boost"))this.rareAttackBoost = true; + if(powers.contains("epic_attack_boost"))this.epicAttackBoost = true; + if(powers.contains("olympic_sword"))this.olympicSwordGrepolympiaSummerLevel = olympicSwordGrepolympiaSummerLevel; + if(powers.contains("olympic_senses"))this.olympicSensesGrepolympiaSummerLevel = olympicSensesGrepolympiaSummerLevel; + if(powers.contains("missions_power_4"))this.missionsPower4 = true; + if(powers.contains("divine_battle_strategy_rare"))this.divineBattleStrategyRare = true; + if(powers.contains("divine_battle_strategy_epic"))this.divineBattleStrategyEpic = true; + if(powers.contains("naval_battle_strategy_rare"))this.navalBattleStrategyRare = true; + if(powers.contains("naval_battle_strategy_epic"))this.navalBattleStrategyEpic = true; + if(powers.contains("land_battle_strategy_rare"))this.landBattleStrategyRare = true; + if(powers.contains("land_battle_strategy_epic"))this.landBattleStrategyEpic = true; + if(powers.contains("ares_rage"))this.aresRageLevel = aresRageLevel; + if(powers.contains("ares_army"))this.aresArmyFurySpent = aresArmyFurySpent; + if(powers.contains("bloodlust"))this.bloodlustFurySpent = bloodlustFurySpent; + if(powers.contains("fair_wind"))this.fairWind = true; + if(powers.contains("desire"))this.desire = true; + if(powers.contains("effort_of_the_huntress"))this.effortOfTheHuntress = true; + if(powers.contains("strength_of_heroes"))this.strengthOfHeroes = true; + if(researches.contains("divine_selection"))this.divineSelection = true; + if(researches.contains("phalanx"))this.phalanx = true; + if(researches.contains("ram"))this.ram = true; + if(researches.contains("combat_experience"))this.combatExperience = true; + if(counsellors.contains("commander"))this.commander = true; + if(counsellors.contains("priest"))this.priest = true; + if(counsellors.contains("captain"))this.captain = true; + this.strategyBreach = strategyBreach; + this.allianceModifier = allianceModifier; + } + + public int unitCount(Unit u) { + return Optional.ofNullable(units.getOrDefault(u,0)).orElse(0); + } + public Map getUnits() { + return units; + } + public Hero getHero() { + return hero; + } + public int getHeroLevel() { + return heroLevel; + } + public int getLuck() { + return luck; + } + public int getMorale() { + return moral; + } + public boolean hasDivineSelection() { + return divineSelection; + } + public boolean hasPhalanx() { + return phalanx; + } + public boolean hasRam() { + return ram; + } + public boolean hasCombatExperience() { + return combatExperience; + } + public boolean hasCommander() { + return commander; + } + public boolean hasPriest() { + return priest; + } + public boolean hasCaptain() { + return captain; + } + public boolean hasAcumen() { + return acumen; + } + public boolean hasDivineSenses() { + return divineSenses; + } + public boolean hasMyrmidionAttack() { + return myrmidionAttack; + } + public boolean hasAttackBoost() { + return attackBoost; + } + public boolean hasAttackPenalty() { + return attackPenalty; + } + public boolean hasLongtermAttackBoost() { + return longtermAttackBoost; + } + public boolean hasLuxurious_residence() { + return luxuriousResidence; + } + public boolean hasAttack_ship_attack_boost_small() { + return attack_ship_attack_boost_small; + } + public boolean hasAttack_ship_attack_boost_medium() { + return attack_ship_attack_boost_medium; + } + public boolean hasAttack_ship_attack_boost_large() { + return attack_ship_attack_boost_large; + } + public boolean hasAssassinsAcumen() { + return assassinsAcumen; + } + public boolean hasRareAttackBoost() { + return rareAttackBoost; + } + public boolean hasEpicAttackBoost() { + return epicAttackBoost; + } + public int getOlympicSwordGrepolympiaSummerLevel() { + return olympicSwordGrepolympiaSummerLevel; + } + public int getOlympicSensesGrepolympiaSummerLevel() { + return olympicSensesGrepolympiaSummerLevel; + } + public boolean hasMhassionsPower4() { + return missionsPower4; + } + public boolean hasDivineBattleStrategyRare() { + return divineBattleStrategyRare; + } + public boolean hasDivineBattleStrategyEpic() { + return divineBattleStrategyEpic; + } + public boolean hasNavalBattleStrategyRare() { + return navalBattleStrategyRare; + } + public boolean hasNavalBattleStrategyEpic() { + return navalBattleStrategyEpic; + } + public boolean hasLandBattleStrategyRare() { + return landBattleStrategyRare; + } + public boolean hasLandBattleStrategyEpic() { + return landBattleStrategyEpic; + } + public int getAresRageLevel() { + return aresRageLevel; + } + public int getAresArmyFurySpent() { + return aresArmyFurySpent; + } + public int getBloodlust() { + return bloodlustFurySpent; + } + public boolean hasFairWind() { + return fairWind; + } + public boolean hasDesire() { + return desire; + } + public boolean hasStrengthOfHeroes() { + return strengthOfHeroes; + } + public boolean hasEffortOfTheHuntress() { + return effortOfTheHuntress; + } + public boolean hasStrategyBreach() { + return strategyBreach; + } + public boolean hasAllianceModifier() { + return allianceModifier; + } + + public static final List POWERS = List.of("acumen", "divine_senses", "myrmidion_attack", "attack_boost", + "attack_penalty", "longterm_attack_boost", "attack_ship_attack_boost_small", + "attack_ship_attack_boost_medium", "attack_ship_attack_boost_large", "assassins_acumen", + "rare_attack_boost", "epic_attack_boost", "olympic_sword", "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", "ares_rage", + "ares_army", "bloodlust", "fair_wind", "desire", "effort_of_the_huntress", "strength_of_heroes"); + public static final List RESEARCHES = List.of("divine_selection","phalanx","ram","combat_experience"); + public static final List COUNSELLORS = List.of("priest","commander","captain"); - public boolean strategy_breach; - public boolean alliance_modifier; } diff --git a/src/main/java/com/bernard/greposimu/model/game/GameConfig.java b/src/main/java/com/bernard/greposimu/model/game/GameConfig.java index e77c2ed..ddab664 100644 --- a/src/main/java/com/bernard/greposimu/model/game/GameConfig.java +++ b/src/main/java/com/bernard/greposimu/model/game/GameConfig.java @@ -31,6 +31,10 @@ public class GameConfig { this.researches = researches; this.powers = powers; } + + public Set getGods() { + return gods; + } public Set getUnits() { return Collections.unmodifiableSet(this.units); diff --git a/src/main/java/com/bernard/greposimu/model/game/God.java b/src/main/java/com/bernard/greposimu/model/game/God.java index da82bfb..a3ee8e6 100644 --- a/src/main/java/com/bernard/greposimu/model/game/God.java +++ b/src/main/java/com/bernard/greposimu/model/game/God.java @@ -1,6 +1,8 @@ package com.bernard.greposimu.model.game; -public class God implements Identified{ +import java.util.Objects; + +public class God implements Identified,Comparable{ String id; String name; @@ -18,5 +20,33 @@ public class God implements Identified{ public String getName() { return name; } + + @Override + public int compareTo(God o) { + return this.getId().compareTo(o.getId()); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + God other = (God) obj; + return Objects.equals(id, other.id); + } + + @Override + public String toString() { + return "God [id=" + id + ", name=" + name + "]"; + } + } diff --git a/src/main/java/com/bernard/greposimu/model/game/units/Hero.java b/src/main/java/com/bernard/greposimu/model/game/units/Hero.java index ac6ca9d..a16510b 100644 --- a/src/main/java/com/bernard/greposimu/model/game/units/Hero.java +++ b/src/main/java/com/bernard/greposimu/model/game/units/Hero.java @@ -1,13 +1,12 @@ package com.bernard.greposimu.model.game.units; import java.util.Map; +import java.util.Objects; import java.util.Set; -import com.bernard.greposimu.model.game.God; import com.bernard.greposimu.model.game.Identified; -import com.bernard.greposimu.model.game.Resources; -public class Hero extends TerrestrialUnit{ +public class Hero extends TerrestrialUnit implements Comparable{ // Zero population // Non mythological @@ -76,6 +75,34 @@ public class Hero extends TerrestrialUnit{ return powerValuePerLevel; } + @Override + public int compareTo(Hero o) { + return this.getId().compareTo(o.getId()); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Hero other = (Hero) obj; + return Objects.equals(id, other.id); + } + + + @Override + public String toString() { + return "Hero [category=" + category + ", cost=" + cost + ", shortDescription=" + shortDescription + + ", powerBaseValue=" + powerBaseValue + ", powerValuePerLevel=" + powerValuePerLevel + "]"; + } - + } diff --git a/src/main/resources/static/images/powers/missions_power_4.disabled.png b/src/main/resources/static/images/powers/missions_power_4.disabled.png new file mode 120000 index 0000000..1adcccb --- /dev/null +++ b/src/main/resources/static/images/powers/missions_power_4.disabled.png @@ -0,0 +1 @@ +missions_power_4.missions_dionysia.disabled.png \ No newline at end of file diff --git a/src/main/resources/static/images/powers/missions_power_4.hover.png b/src/main/resources/static/images/powers/missions_power_4.hover.png new file mode 120000 index 0000000..2b16fa9 --- /dev/null +++ b/src/main/resources/static/images/powers/missions_power_4.hover.png @@ -0,0 +1 @@ +missions_power_4.missions_dionysia.hover.png \ No newline at end of file diff --git a/src/main/resources/static/images/powers/missions_power_4.png b/src/main/resources/static/images/powers/missions_power_4.png new file mode 120000 index 0000000..accfb0d --- /dev/null +++ b/src/main/resources/static/images/powers/missions_power_4.png @@ -0,0 +1 @@ +missions_power_4.missions_dionysia.png \ No newline at end of file diff --git a/src/main/resources/templates/simulator.html b/src/main/resources/templates/simulator.html index e385f0a..9e51081 100644 --- a/src/main/resources/templates/simulator.html +++ b/src/main/resources/templates/simulator.html @@ -85,6 +85,17 @@ span.fixed50px { border: 0px; margin: auto; } +.container { + display: inline-block; +} + +.columns { + display: flex; +} + +.formcol { + flex: 50%; +} @@ -92,95 +103,147 @@ span.fixed50px {
- -
- Héros - - - - - - -
-
- Unités - - - - -
+
+
+ Attaque +
+ Héros + + + + + + +
+
+ Unités +
- -
-
-
- Batiments - -
- - - - - -
-
- Pouvoirs - - - - -
+
+ + + +
+ Pouvoirs +
- -
-
-
- Recherches - - - - -
- - -
-
-
- Conseillers - - - - -
+ + + +
+ Recherches +
+ + +
+
+
+ Conseillers +
- -
-
-
- Bonus de jeu - -
-
- + + + +
+ Paramètres + + + + + + + + + +
+ +
+ Défense +
+ Héros + + + + + + +
+
+ Unités +
+ +
+ +
+
+
+ Batiments + +
+ + + + + +
+
+ Pouvoirs +
+ + +
+
+
+ Recherches +
+ + +
+
+
+ Conseillers +
+ + +
+
+
+ Bonus de jeu + +
+
+
+