Empieza refactoring masivo

This commit is contained in:
Loic Prieto 2025-06-16 20:03:50 +02:00
parent feac5f79bd
commit e6ae93208d
12 changed files with 140 additions and 78 deletions

View file

@ -0,0 +1,15 @@
package ninja.thefirearchmage.games.fourtykcalculator;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
@AllArgsConstructor @Getter @Setter
public class AntiUnitEffect {
private UnitTypeKeyword unitType;
private int woundRollValue;
public boolean appliesTo(UnitTypeKeyword unitType) {
return this.unitType == unitType;
}
}

View file

@ -11,6 +11,7 @@ public class AttackScenario {
private static final int PROBABILITY_DENOMINATOR_D6 = 6; private static final int PROBABILITY_DENOMINATOR_D6 = 6;
// Attack attributes // Attack attributes
private UnitAttackModifiers unitAttackModifiers;
private List<Tuple2<Weapon, Integer>> weapons; private List<Tuple2<Weapon, Integer>> weapons;
private boolean unmodifiableHit; private boolean unmodifiableHit;
private boolean attackerWasStationary; private boolean attackerWasStationary;
@ -95,6 +96,9 @@ public class AttackScenario {
Weapon weapon = weaponData._1; Weapon weapon = weaponData._1;
int amountOfWeapons = weaponData._2; int amountOfWeapons = weaponData._2;
weapon.generateAttackResolution(defensiveProfile, )
var weaponAttackResolution = new WeaponAttackResolution();
// Attack funnel // Attack funnel
var passingHits = calculatePassingHits(weapon, amountOfWeapons); var passingHits = calculatePassingHits(weapon, amountOfWeapons);
var passingWounds = calculatePassingWounds(weapon, amountOfWeapons, passingHits); var passingWounds = calculatePassingWounds(weapon, amountOfWeapons, passingHits);

View file

@ -1,41 +1,25 @@
package ninja.thefirearchmage.games.fourtykcalculator; package ninja.thefirearchmage.games.fourtykcalculator;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import ninja.thefirearchmage.games.fourtykcalculator.utils.datastructures.Tuple;
import ninja.thefirearchmage.games.fourtykcalculator.utils.datastructures.Tuple2;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import static java.lang.String.format;
@Getter @Getter
public class AttackScenarioResolution { public class AttackScenarioResolution {
private Map<String, Map<WeaponStat, Object>> weaponStats; private List<WeaponAttackResolution> weaponAttacksStats;
@Setter
private double totalInflictedDamage; private double totalInflictedDamage;
@Setter
private double totalPreventedDamage; private double totalPreventedDamage;
public AttackScenarioResolution() { public AttackScenarioResolution() {
this.weaponStats = new HashMap<>(); this.weaponAttacksStats = new ArrayList<>();
totalInflictedDamage = 0; totalInflictedDamage = 0;
totalPreventedDamage = 0; totalPreventedDamage = 0;
} }
public void setWeaponStat(Weapon weapon, WeaponStat statName, Object statValue) { public void addWeaponAttackResolution(WeaponAttackResolution resolution) {
if(!statValue.getClass().isAssignableFrom(statName.getStatClass())) { this.weaponAttacksStats.add(resolution);
throw new IllegalArgumentException( totalInflictedDamage += resolution.getTotalDamage();
format("The stat %s's value does not have the expected class %s, but instead has %s class", totalPreventedDamage += resolution.getSaveResolution().getPreventedWounds();
statName.name(), statName.getStatClass().getName(), statValue.getClass().getName()));
}
var currentStats = weaponStats.putIfAbsent(weapon.getName(), Map.of(statName, statValue));
if(currentStats != null) {
currentStats.put(statName, statValue);
}
} }
} }

View file

@ -0,0 +1,6 @@
package ninja.thefirearchmage.games.fourtykcalculator;
public enum DamageReductionType {
REDUCE_BY_X,
REDUCE_TO_X;
}

View file

@ -1,8 +1,10 @@
package ninja.thefirearchmage.games.fourtykcalculator; package ninja.thefirearchmage.games.fourtykcalculator;
import lombok.Getter; import lombok.Getter;
import ninja.thefirearchmage.games.fourtykcalculator.utils.datastructures.Tuple3;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@ -13,7 +15,7 @@ public class DefensiveProfile {
private final int wounds; private final int wounds;
private final int bodies; private final int bodies;
private Optional<InvulnerableSave> invulnerableSave; private Optional<InvulnerableSave> invulnerableSave;
private Optional<FeelNoPainEffect> feelNoPain; private List<FeelNoPainEffect> feelNoPainEffects;
private Set<UnitTypeKeyword> unitTypeKeywords; private Set<UnitTypeKeyword> unitTypeKeywords;
private boolean isInCover; private boolean isInCover;
private boolean isVisible; private boolean isVisible;
@ -21,6 +23,9 @@ public class DefensiveProfile {
private boolean hasRerollFailedNormalSaves; private boolean hasRerollFailedNormalSaves;
private boolean hasRerollAnyNormalSaves; private boolean hasRerollAnyNormalSaves;
private boolean fishFor6NormalSaves; private boolean fishFor6NormalSaves;
private boolean hasStealth;
private Tuple3<Boolean, DamageReductionType, Integer> damageReductionModifier;
private Tuple3<Boolean, WeaponRangeType, Integer> apReduction;
public DefensiveProfile(int toughness, int wounds, int bodies, int normalSave) { public DefensiveProfile(int toughness, int wounds, int bodies, int normalSave) {
this(toughness, wounds, bodies, normalSave, Optional.empty(), Optional.empty(), this(toughness, wounds, bodies, normalSave, Optional.empty(), Optional.empty(),

View file

@ -1,6 +1,6 @@
package ninja.thefirearchmage.games.fourtykcalculator; package ninja.thefirearchmage.games.fourtykcalculator;
import lombok.Getter; import lombok.*;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -9,30 +9,28 @@ import java.util.stream.IntStream;
/** /**
* Assumes a D6 * Assumes a D6
*/ */
@Getter @Getter @AllArgsConstructor
public class Reroll { public class Reroll {
private RerollType type;
private final Set<Integer> rerollableValues; private final Set<Integer> rerollableValues;
private Set<UnitTypeKeyword> validTargetTypes;
private Reroll(Set<Integer> rerollableValues) { public static Reroll rerollOnOnes(Set<UnitTypeKeyword> validTargetTypes) {
this.rerollableValues = rerollableValues; return new Reroll(RerollType.ON_ONES, Set.of(1), validTargetTypes);
} }
public static Reroll rerollOnOnes() { public static Reroll rerollFailures(int targetHit, Set<UnitTypeKeyword> validTargetTypes) {
return new Reroll(Set.of(1));
}
public static Reroll rerollFailures(int targetHit) {
var rerollableValues = IntStream.range(1, targetHit).boxed() var rerollableValues = IntStream.range(1, targetHit).boxed()
.collect(Collectors.toSet()); .collect(Collectors.toSet());
return new Reroll(rerollableValues); return new Reroll(RerollType.ON_FAILURE, rerollableValues, validTargetTypes);
} }
public static Reroll rerollOnNot(Set<Integer> allowedValues) { public static Reroll rerollOnNot(Set<Integer> allowedValues, Set<UnitTypeKeyword> validTargetTypes) {
var rerollableValues = IntStream.range(1,7).filter(i-> !allowedValues.contains(i)) var rerollableValues = IntStream.range(1,7).filter(i-> !allowedValues.contains(i))
.boxed().collect(Collectors.toSet()); .boxed().collect(Collectors.toSet());
return new Reroll(rerollableValues); return new Reroll(RerollType.ANY, rerollableValues, validTargetTypes);
} }
/** /**
@ -41,4 +39,8 @@ public class Reroll {
public double getProbabilityNumerator() { public double getProbabilityNumerator() {
return (double) rerollableValues.size() / 6; return (double) rerollableValues.size() / 6;
} }
public boolean appliesTo(UnitTypeKeyword target) {
return this.validTargetTypes.contains(target);
}
} }

View file

@ -0,0 +1,7 @@
package ninja.thefirearchmage.games.fourtykcalculator;
public enum RerollType {
ON_ONES,
ON_FAILURE,
ANY;
}

View file

@ -0,0 +1,36 @@
package ninja.thefirearchmage.games.fourtykcalculator;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import ninja.thefirearchmage.games.fourtykcalculator.utils.datastructures.Tuple2;
import ninja.thefirearchmage.games.fourtykcalculator.utils.datastructures.Tuple3;
import java.util.List;
/**
* Models what modifiers a unit that is performing the attacking provides to its weapons.
* For example, a unit of eradicators has reroll on hits, wounds and damage against monsters and vehicles.
*/
@NoArgsConstructor @Getter @Setter
public class UnitAttackModifiers {
private Tuple3<Boolean, WeaponRangeType, Integer> sustainedHitsModifier;
private Tuple3<Boolean, WeaponRangeType, Integer> lethalHitsModifier;
private boolean lanceModifier;
private List<Reroll> rerollsModifier;
private Tuple3<Boolean, WeaponRangeType, Integer> apModifier;
private Tuple2<Boolean, WeaponRangeType> woundModifier;
private Tuple2<Boolean, WeaponRangeType> hitModifier;
private Tuple2<Boolean, WeaponRangeType> devastatingWoundsModifier;
private Tuple2<Boolean, WeaponRangeType> hazardousModifier;
private Tuple2<Boolean, WeaponRangeType> heavyModifier;
private Tuple3<Boolean, WeaponRangeType, Integer> strengthModifier;
private Tuple3<Boolean, WeaponRangeType, Integer> attacksModifier;
private boolean unmodifiableHitRoll;
private boolean unitHasRemainedStationaryModifier;
private boolean unitCharged;
private boolean targetInRapidFireRange;
private boolean isPsychic;
private boolean canRerollHazardous;
}

View file

@ -1,8 +1,11 @@
package ninja.thefirearchmage.games.fourtykcalculator; package ninja.thefirearchmage.games.fourtykcalculator;
public enum UnitTypeKeyword { public enum UnitTypeKeyword {
ALL,
INFANTRY, INFANTRY,
VEHICLE, VEHICLE,
PSYCHIC, PSYCHIC,
CHAOS,
CHARACTER,
MONSTER; MONSTER;
} }

View file

@ -2,10 +2,7 @@ package ninja.thefirearchmage.games.fourtykcalculator;
import lombok.Getter; import lombok.Getter;
import java.util.HashMap; import java.util.*;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
public class Weapon { public class Weapon {
@Getter @Getter
@ -27,7 +24,7 @@ public class Weapon {
private Optional<Integer> sustainedHits; private Optional<Integer> sustainedHits;
private boolean hasDevastatingWounds; private boolean hasDevastatingWounds;
private boolean hasHazardous; private boolean hasHazardous;
private Map<UnitTypeKeyword, Integer> antiStats; private List<AntiUnitEffect> antiUnitEffects;
private boolean hasHeavy; private boolean hasHeavy;
private boolean hasBlast; private boolean hasBlast;
private boolean hasLance; private boolean hasLance;
@ -37,50 +34,39 @@ public class Weapon {
private Optional<Integer> melta; private Optional<Integer> melta;
@Getter @Getter
private Optional<Integer> rapidFire; private Optional<Integer> rapidFire;
private boolean isPsychic;
public Weapon() { public Weapon(String name, int strength, int ap, int attacks, int hitValue, WeaponDamage damage,
this.antiStats = new HashMap<>(); WeaponRangeType weaponRangeType) {
this.name = name;
this.strength = strength;
this.ap = ap;
this.attacks = attacks;
this.hitValue = hitValue;
this.damage = damage;
this.rangeType = weaponRangeType;
this.antiUnitEffects = new ArrayList<>();
this.melta = Optional.empty(); this.melta = Optional.empty();
this.rapidFire = Optional.empty(); this.rapidFire = Optional.empty();
this.sustainedHits = Optional.empty(); this.sustainedHits = Optional.empty();
this.hasDevastatingWounds = false;
this.hasHazardous = false;
this.hasHeavy = false;
this.hasBlast = false;
this.hasLance = false;
this.ignoresCover = false;
this.hasIndirectFire = false;
this.hasLethalHits = false;
} }
public boolean hasLethalHits() { public WeaponAttackResolution generateAttackResolution(DefensiveProfile target, UnitAttackModifiers unitModifiers) {
return hasLethalHits; var weaponAttackResolution = new WeaponAttackResolution();
}
public boolean hasDevastatingWounds() {
return hasDevastatingWounds;
}
public boolean hasHazardous() {
return hasHazardous;
}
public boolean isHeavy() { return weaponAttackResolution;
return hasHeavy;
}
public boolean hasBlast() {
return hasBlast;
}
public boolean ignoresCover() {
return ignoresCover;
}
public boolean hasLance() {
return hasLance;
}
public boolean hasIndirectFire() {
return hasIndirectFire;
}
/**
* Returns whether this weapon has Anti-X where X is the given unit type.
*/
public Optional<Integer> getAnti(UnitTypeKeyword unitType) {
return Optional.ofNullable(antiStats.get(unitType));
} }
/** /**

View file

@ -1,11 +1,24 @@
package ninja.thefirearchmage.games.fourtykcalculator; package ninja.thefirearchmage.games.fourtykcalculator;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@AllArgsConstructor @NoArgsConstructor
@Getter @Setter
public class WeaponAttackResolution { public class WeaponAttackResolution {
private double normalWounds; private double normalWounds;
private double normalWoundsFromLethalHits;
private double normalWoundsFromRerolls;
private double normalWoundsWithoutRerolls;
private double mortalWounds; private double mortalWounds;
private double hits; private double hits;
private double woundsFromLethalHits; private double hitsFromSustainedHits;
private double woundsFromRerolls;
private double hitsFromRerolls; private double hitsFromRerolls;
private double hitsWithoutRerolls;
private double normalDamage;
private double mortalDamage;
private double totalDamage;
private SaveResolution saveResolution;
} }

View file

@ -1,6 +1,7 @@
package ninja.thefirearchmage.games.fourtykcalculator; package ninja.thefirearchmage.games.fourtykcalculator;
public enum WeaponRangeType { public enum WeaponRangeType {
ALL,
MELEE, MELEE,
RANGED; RANGED;
} }