Finished attack funnel. Now implementing defence funnel
This commit is contained in:
parent
434b534012
commit
440c9dd9d5
2 changed files with 63 additions and 6 deletions
|
@ -6,8 +6,6 @@ import ninja.thefirearchmage.games.fourtykcalculator.utils.datastructures.Tuple2
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.IntStream;
|
|
||||||
|
|
||||||
public class AttackScenario {
|
public class AttackScenario {
|
||||||
private static final int PROBABILITY_DENOMINATOR_D6 = 6;
|
private static final int PROBABILITY_DENOMINATOR_D6 = 6;
|
||||||
|
@ -96,13 +94,26 @@ public class AttackScenario {
|
||||||
Weapon weapon = weaponData._1;
|
Weapon weapon = weaponData._1;
|
||||||
int amountOfWeapons = weaponData._2;
|
int amountOfWeapons = weaponData._2;
|
||||||
|
|
||||||
// Attack probabilities
|
// Attack funnel
|
||||||
var passingHits = calculatePassingHits(weapon, amountOfWeapons);
|
var passingHits = calculatePassingHits(weapon, amountOfWeapons);
|
||||||
var passingWounds = calculatePassingWounds(weapon, amountOfWeapons, passingHits);
|
var passingWounds = calculatePassingWounds(weapon, amountOfWeapons, passingHits);
|
||||||
|
var normalWounds = passingWounds._1;
|
||||||
|
var mortalWounds = passingWounds._2;
|
||||||
|
|
||||||
|
// Defence funnel
|
||||||
|
executeDefenceFunnel(weapon, normalWounds, mortalWounds);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void executeDefenceFunnel(Weapon weapon, double normalWounds, double mortalWounds) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a tuple of normal passing wounds and mortal wounds so that the caller can handle them differently.
|
||||||
|
*/
|
||||||
private Tuple2<Double, Double> calculatePassingWounds(Weapon weapon, int amountOfWeapons, double passingHits) {
|
private Tuple2<Double, Double> calculatePassingWounds(Weapon weapon, int amountOfWeapons, double passingHits) {
|
||||||
double lethalHitsCount = calculateLethalHits(weapon, amountOfWeapons);
|
double lethalHitsCount = calculateLethalHits(weapon, amountOfWeapons);
|
||||||
var normalHits = passingHits - lethalHitsCount;
|
var normalHits = passingHits - lethalHitsCount;
|
||||||
|
@ -111,8 +122,38 @@ public class AttackScenario {
|
||||||
int woundRollGoal = calculateWoundRollGoal(weapon);
|
int woundRollGoal = calculateWoundRollGoal(weapon);
|
||||||
int woundSuccessProbabilityNumerator = calculateProbabilityNumeratorFrom(woundRollGoal);
|
int woundSuccessProbabilityNumerator = calculateProbabilityNumeratorFrom(woundRollGoal);
|
||||||
|
|
||||||
var passingWounds = normalHits * ( (double) woundSuccessProbabilityNumerator / PROBABILITY_DENOMINATOR_D6)
|
var passingNormalWounds = normalHits * ( (double) woundSuccessProbabilityNumerator / PROBABILITY_DENOMINATOR_D6);
|
||||||
|
results.setWeaponStat(weapon, WeaponStat.WOUNDS_COUNT_WITHOUT_REROLL, passingNormalWounds+lethalHitsCount);
|
||||||
|
var woundsRerolls = prepareWoundRerolls(woundRollGoal);
|
||||||
|
if(woundsRerolls.isPresent()) {
|
||||||
|
var rerolls = woundsRerolls.get();
|
||||||
|
var failedWounds = normalHits * ((double) (1 - woundSuccessProbabilityNumerator) / PROBABILITY_DENOMINATOR_D6);
|
||||||
|
var extraWoundsFromRerolls = failedWounds * rerolls.getProbabilityNumerator() / PROBABILITY_DENOMINATOR_D6;
|
||||||
|
results.setWeaponStat(weapon, WeaponStat.WOUNDS_COUNT_FROM_REROLL, extraWoundsFromRerolls);
|
||||||
|
passingNormalWounds += extraWoundsFromRerolls;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mortal wounds
|
||||||
|
var passingMortalWounds = 0d;
|
||||||
|
if(weapon.hasDevastatingWounds()) {
|
||||||
|
var criticalWoundRollGoal = weapon.getBestAntiFor(defensiveProfile)
|
||||||
|
.orElse(criticalWoundValue);
|
||||||
|
var criticalProbability = calculateProbabilityNumeratorFrom(criticalWoundRollGoal);
|
||||||
|
passingMortalWounds += (normalHits*criticalProbability/PROBABILITY_DENOMINATOR_D6);
|
||||||
|
if(fishCriticalWounds && hasRerollWoundsAny) {
|
||||||
|
passingMortalWounds += (normalHits * (1-criticalProbability)/PROBABILITY_DENOMINATOR_D6)*criticalProbability/PROBABILITY_DENOMINATOR_D6;
|
||||||
|
}
|
||||||
|
|
||||||
|
results.setWeaponStat(weapon, WeaponStat.MORTAL_WOUNDS_COUNT, passingMortalWounds);
|
||||||
|
// passing wounds was calculated from the whole, but we must substract the critical wounds from the whole
|
||||||
|
// to be able to handle them separately, as mortal wounds replace normal wounds.
|
||||||
|
passingNormalWounds -= passingMortalWounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
passingNormalWounds += lethalHitsCount;
|
||||||
|
results.setWeaponStat(weapon, WeaponStat.WOUNDS_TOTAL_COUNT, passingNormalWounds+passingMortalWounds);
|
||||||
|
|
||||||
|
return Tuple.of(passingNormalWounds, passingMortalWounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double calculateLethalHits(Weapon weapon, int amountOfWeapons) {
|
private double calculateLethalHits(Weapon weapon, int amountOfWeapons) {
|
||||||
|
@ -143,7 +184,9 @@ public class AttackScenario {
|
||||||
var hitRerolls = prepareHitRerolls(hitRollGoal);
|
var hitRerolls = prepareHitRerolls(hitRollGoal);
|
||||||
if(hitRerolls.isPresent()) {
|
if(hitRerolls.isPresent()) {
|
||||||
var rerolls = hitRerolls.get();
|
var rerolls = hitRerolls.get();
|
||||||
passingHits += rerolls.getProbabilityNumerator() * ((double) hitSuccessProbabilityNumerator / PROBABILITY_DENOMINATOR_D6);
|
var successfulHitFromRerolls = rerolls.getProbabilityNumerator() * ((double) hitSuccessProbabilityNumerator / PROBABILITY_DENOMINATOR_D6);
|
||||||
|
results.setWeaponStat(weapon, WeaponStat.HITS_COUNT_FROM_REROLL, successfulHitFromRerolls);
|
||||||
|
passingHits += successfulHitFromRerolls;
|
||||||
}
|
}
|
||||||
results.setWeaponStat(weapon, WeaponStat.HITS_TOTAL_COUNT, passingHits);
|
results.setWeaponStat(weapon, WeaponStat.HITS_TOTAL_COUNT, passingHits);
|
||||||
|
|
||||||
|
@ -161,6 +204,17 @@ public class AttackScenario {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Optional<Reroll> prepareWoundRerolls(int woundRollGoal) {
|
||||||
|
if(hasRerollWoundsOnes) {
|
||||||
|
return Optional.of(Reroll.rerollOnOnes());
|
||||||
|
}
|
||||||
|
if(hasRerollWoundsFailures || hasRerollWoundsAny) {
|
||||||
|
return Optional.of(Reroll.rerollFailures(woundRollGoal));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
private double calculateWeaponAttacks(Weapon weapon, int amountOfWeapons) {
|
private double calculateWeaponAttacks(Weapon weapon, int amountOfWeapons) {
|
||||||
double weaponAttacks = calculateBasicWeaponAttacks(weapon, amountOfWeapons);
|
double weaponAttacks = calculateBasicWeaponAttacks(weapon, amountOfWeapons);
|
||||||
results.setWeaponStat(weapon, WeaponStat.POTENTIAL_HITS_COUNT, weaponAttacks);
|
results.setWeaponStat(weapon, WeaponStat.POTENTIAL_HITS_COUNT, weaponAttacks);
|
||||||
|
|
|
@ -5,9 +5,12 @@ import lombok.Getter;
|
||||||
@Getter
|
@Getter
|
||||||
public enum WeaponStat {
|
public enum WeaponStat {
|
||||||
HITS_COUNT_WITHOUT_REROLL(Double.class),
|
HITS_COUNT_WITHOUT_REROLL(Double.class),
|
||||||
|
HITS_COUNT_FROM_REROLL(Double.class),
|
||||||
HITS_TOTAL_COUNT(Double.class),
|
HITS_TOTAL_COUNT(Double.class),
|
||||||
POTENTIAL_HITS_COUNT(Double.class),
|
POTENTIAL_HITS_COUNT(Double.class),
|
||||||
WOUNDS_COUNT(Double.class),
|
WOUNDS_TOTAL_COUNT(Double.class),
|
||||||
|
WOUNDS_COUNT_WITHOUT_REROLL(Double.class),
|
||||||
|
WOUNDS_COUNT_FROM_REROLL(Double.class),
|
||||||
MORTAL_WOUNDS_COUNT(Double.class),
|
MORTAL_WOUNDS_COUNT(Double.class),
|
||||||
TOTAL_DAMAGE(Double.class),
|
TOTAL_DAMAGE(Double.class),
|
||||||
LETHAL_HITS_COUNT(Double.class),
|
LETHAL_HITS_COUNT(Double.class),
|
||||||
|
|
Loading…
Add table
Reference in a new issue