07-01-2023, 06:36 PM
Et maintenant, la sauvegarde qui s'est révélé relativement facile à traité (TU compris).
Pour commencé, j'ai défini l'interface:
Notez que l'on a un optional, étant donné qu'il se peut qu'il n'y ait pas de sauvegarde. On ajoute aussi le bonus de PA (d'armure) vu que certaines armées, comme les SDB augmentent la PA de 1 sur 6+ au tir. Le bonus dépend donc du jet d'avant.
Comme une sauvegarde est comprise entre 2 et 6, on a donc un traitement pour ramener dans la bonne fourchette:
Reste à implémenter pour les cas connus (l'invulnérable a 2 cas suivant que ce soit sur le profil ou les règles comme c'est le cas des eldars noirs).
Evidement, hors de question de boucler sur toutes les sauvegardes qui existent.
On va donc utiliser un Design Pattern du GOFF pour ne voir qu'un seul calcul, le Design Pattern Décorateur:
Reste à fabriquer tout ça avec une factory (autre Design pattern du GOFF):
Et l'implémentation:
On n'a plus qu'à modifier le paramétrage pour Spring.
Pour commencé, j'ai défini l'interface:
Code:
package com.calculateur.warhammer.calcul.mort.sauvegarde;
import java.util.Optional;
import com.calculateur.warhammer.data.action.IContexteAction;
import com.calculateur.warhammer.data.identifiable.IArme;
import com.calculateur.warhammer.data.unite.IConstituantAttaquant;
import com.calculateur.warhammer.data.unite.IConstituantDefenseur;
/**
* Calcule la sauvegarde d'une figururine.
* @author phili
*
*/
public interface ICalculSauvegarde<A extends IArme> {
/**
*
* @param attaquant Attaquant effectuant l'action
* @param defenseur Défenseur effectuant l'action
* @param contexteAction Contexte de l'action
* @param bonusPA Bonus supplémentaire à la PA d'armure (peut-être null).
* @return La sauvegarde d'armure (vide si pas de sauvegarde).
*/
Optional<Integer> getSauvegarde(IConstituantAttaquant<A> attaquant,IConstituantDefenseur defenseur,IContexteAction<A> contexteAction,Integer bonusPA);
}
Comme une sauvegarde est comprise entre 2 et 6, on a donc un traitement pour ramener dans la bonne fourchette:
Code:
package com.calculateur.warhammer.calcul.mort.sauvegarde;
import java.util.Optional;
import com.calculateur.warhammer.data.action.IContexteAction;
import com.calculateur.warhammer.data.identifiable.IArme;
import com.calculateur.warhammer.data.unite.IConstituantAttaquant;
import com.calculateur.warhammer.data.unite.IConstituantDefenseur;
/**
* Template Methode permettant d'ajuster la sauvegarde entre 2+ et 6+.
* @author phili
*
* @param <A>
*/
public abstract class AbstractCalculSauvegarde<A extends IArme> implements ICalculSauvegarde<A>{
private static final Integer SAUVEGARDE_ARMURE_MAXIMUM = 6;
private static final Integer SAUVEGARDE_ARMURE_MINIMUM = 2;
protected AbstractCalculSauvegarde() {
}
@Override
public Optional<Integer> getSauvegarde(IConstituantAttaquant<A> attaquant, IConstituantDefenseur defenseur,
IContexteAction<A> contexteAction, Integer bonusPA) {
Optional<Integer> sauvegardeBase = getSauvegardeBase(attaquant, defenseur, contexteAction, bonusPA);
return (sauvegardeBase.isPresent())?ajusteSauvegarde(sauvegardeBase.get()):sauvegardeBase;
}
protected abstract Optional<Integer> getSauvegardeBase(IConstituantAttaquant<A> attaquant,IConstituantDefenseur defenseur,IContexteAction<A> contexteAction,Integer bonusPA);
private Optional<Integer> ajusteSauvegarde(Integer sauvegardeBase){
Integer sauvegarde;
if(sauvegardeBase < SAUVEGARDE_ARMURE_MINIMUM) {
sauvegarde = SAUVEGARDE_ARMURE_MINIMUM;
}else if(sauvegardeBase > SAUVEGARDE_ARMURE_MAXIMUM) {
sauvegarde = null;
}else {
sauvegarde = sauvegardeBase;
}
return Optional.ofNullable(sauvegarde);
}
}
Reste à implémenter pour les cas connus (l'invulnérable a 2 cas suivant que ce soit sur le profil ou les règles comme c'est le cas des eldars noirs).
Code:
package com.calculateur.warhammer.calcul.mort.sauvegarde;
import java.util.Optional;
import com.calculateur.warhammer.data.action.IContexteAction;
import com.calculateur.warhammer.data.identifiable.IArme;
import com.calculateur.warhammer.data.unite.IConstituantAttaquant;
import com.calculateur.warhammer.data.unite.IConstituantDefenseur;
/**
* Calcul de la sauvegarde d'armure
* @author phili
*
* @param <A>
*/
public class CalculSauvegardeArmure<A extends IArme> extends AbstractCalculSauvegarde<A>{
private static final Integer MAXIMUM_PA = 0;
private static final Integer BONUS_BASE_PA = 0;
@Override
protected Optional<Integer> getSauvegardeBase(IConstituantAttaquant<A> attaquant, IConstituantDefenseur defenseur,
IContexteAction<A> contexteAction, Integer bonusPA) {
Integer sauvegardeBase = defenseur.getProfil().getSauvegarde();
return (sauvegardeBase != null)?Optional.of(getSauvegarde(sauvegardeBase, attaquant, defenseur, bonusPA)) :Optional.empty();
}
private Integer getSauvegarde(Integer sauvegardeBase,IConstituantAttaquant<A> attaquant,IConstituantDefenseur defenseur,Integer bonusPA) {
Integer pa = getPA(attaquant, defenseur, bonusPA);
boolean isCouvert = defenseur.getRegles().isSauvegardeCouvert() && !attaquant.getRegles().isIgnoreSauvegardeCouvert();
Integer bonusCouvert = isCouvert?-1:0;
Integer bonusDefenseurSauvegarde = defenseur.getRegles().getBonusSauvegardeArmure();
Integer bonusDefenseurSauvegardeCalcul = (bonusDefenseurSauvegarde != null)?bonusDefenseurSauvegarde:0;
return (sauvegardeBase + bonusCouvert + bonusDefenseurSauvegardeCalcul) - pa;
}
private Integer getPA(IConstituantAttaquant<A> attaquant,IConstituantDefenseur defenseur,Integer bonusPA) {
Integer paArme = attaquant.getArme().getPA();
Integer paArmeCalcul = (paArme != null)?paArme:MAXIMUM_PA;
Integer bonusPARegle = attaquant.getRegles().getBonusPA();
Integer bonusPaRegleCalcul = (bonusPARegle != null)?bonusPARegle:BONUS_BASE_PA;
Integer reductionPA = defenseur.getRegles().getReductionPA();
Integer reductionPACalcul = (reductionPA != null)?reductionPA:BONUS_BASE_PA;
Integer bonusPACalcul = (bonusPA != null)?bonusPA:BONUS_BASE_PA;
Integer pa = paArmeCalcul + bonusPaRegleCalcul + reductionPACalcul + bonusPACalcul;
return (pa < MAXIMUM_PA)?pa:MAXIMUM_PA;
}
}
Code:
package com.calculateur.warhammer.calcul.mort.sauvegarde;
import java.util.Optional;
import com.calculateur.warhammer.data.action.IContexteAction;
import com.calculateur.warhammer.data.identifiable.IArme;
import com.calculateur.warhammer.data.unite.IConstituantAttaquant;
import com.calculateur.warhammer.data.unite.IConstituantDefenseur;
/**
* Calcul de la sauvegarde démoniaque
* @author phili
*
*/
public class CalculSauvegardeDemoniaque<A extends IArme> extends AbstractCalculSauvegarde<A>{
@Override
protected Optional<Integer> getSauvegardeBase(IConstituantAttaquant<A> attaquant, IConstituantDefenseur defenseur,
IContexteAction<A> contexteAction, Integer bonusPA) {
Optional<Integer> oSauvegarde;
if(contexteAction.getSimule().isActionTir()) {
oSauvegarde = Optional.ofNullable(defenseur.getProfil().getSauvegardeDemoniaqueTir());
}else {
oSauvegarde = Optional.ofNullable(defenseur.getProfil().getSauvegardeDemoniaqueCorpsACorps());
}
return oSauvegarde;
}
}
Code:
package com.calculateur.warhammer.calcul.mort.sauvegarde;
import java.util.Optional;
import com.calculateur.warhammer.data.action.IContexteAction;
import com.calculateur.warhammer.data.identifiable.IArme;
import com.calculateur.warhammer.data.unite.IConstituantAttaquant;
import com.calculateur.warhammer.data.unite.IConstituantDefenseur;
/**
* Calcul de la sauvegarde invulnérable par rapport au profil.
* @author phili
*
*/
public class CalculSauvegardeInvulnerableProfil<A extends IArme> extends AbstractCalculSauvegarde<A>{
@Override
protected Optional<Integer> getSauvegardeBase(IConstituantAttaquant<A> attaquant, IConstituantDefenseur defenseur,
IContexteAction<A> contexteAction, Integer bonusPA) {
return attaquant.getRegles().isIgnoreSauvegardeInvulnerable()?Optional.empty():Optional.ofNullable(defenseur.getProfil().getSauvegardeInvulnerable());
}
}
Code:
package com.calculateur.warhammer.calcul.mort.sauvegarde;
import java.util.Optional;
import com.calculateur.warhammer.data.action.IContexteAction;
import com.calculateur.warhammer.data.identifiable.IArme;
import com.calculateur.warhammer.data.regles.IRegleDefense;
import com.calculateur.warhammer.data.unite.IConstituantAttaquant;
import com.calculateur.warhammer.data.unite.IConstituantDefenseur;
/**
* Calcul de la sauvegarde invulnérable due aux règles
* @author phili
*
* @param <A>
*/
public class CalculSauvegardeInvulnerableRegle<A extends IArme> extends AbstractCalculSauvegarde<A>{
@Override
protected Optional<Integer> getSauvegardeBase(IConstituantAttaquant<A> attaquant, IConstituantDefenseur defenseur,
IContexteAction<A> contexteAction, Integer bonusPA) {
return attaquant.getRegles().isIgnoreSauvegardeInvulnerable()?Optional.empty():getSauvegardeFromRegles(defenseur.getRegles());
}
private Optional<Integer> getSauvegardeFromRegles(IRegleDefense regle){
return Optional.ofNullable(regle.getSauvegardeInvulnerable()).filter(s -> s > 0);
}
}
Evidement, hors de question de boucler sur toutes les sauvegardes qui existent.
On va donc utiliser un Design Pattern du GOFF pour ne voir qu'un seul calcul, le Design Pattern Décorateur:
Code:
package com.calculateur.warhammer.calcul.mort.sauvegarde;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import com.calculateur.warhammer.data.action.IContexteAction;
import com.calculateur.warhammer.data.identifiable.IArme;
import com.calculateur.warhammer.data.unite.IConstituantAttaquant;
import com.calculateur.warhammer.data.unite.IConstituantDefenseur;
/**
* Décorateur pour calculer toutes les sauvegardes et prendre la meilleure.
* @author phili
*
* @param <A>
*/
public class CalculSauvegardeDecorateur<A extends IArme> implements ICalculSauvegarde<A>{
private final List<ICalculSauvegarde<A>> sauvegardes;
public CalculSauvegardeDecorateur(List<ICalculSauvegarde<A>> sauvegardes) {
this.sauvegardes = sauvegardes;
}
@Override
public Optional<Integer> getSauvegarde(IConstituantAttaquant<A> attaquant, IConstituantDefenseur defenseur,
IContexteAction<A> contexteAction, Integer bonusPA) {
OptionalInt sRes = sauvegardes.stream().map(cs -> cs.getSauvegarde(attaquant, defenseur, contexteAction, bonusPA))
.filter(Optional::isPresent).filter(os -> os.get() > 0).mapToInt(Optional::get).min();
return sRes.isPresent()?Optional.of(sRes.getAsInt()):Optional.empty();
}
}
Reste à fabriquer tout ça avec une factory (autre Design pattern du GOFF):
Code:
package com.calculateur.warhammer.calcul.mort.factory.sauvegarde;
import com.calculateur.warhammer.calcul.mort.factory.exception.SimulationException;
import com.calculateur.warhammer.calcul.mort.sauvegarde.ICalculSauvegarde;
import com.calculateur.warhammer.data.enumeration.ESimule;
import com.calculateur.warhammer.data.identifiable.IArmeDeCorpsACorps;
import com.calculateur.warhammer.data.identifiable.IArmeDeTir;
/**
* Factory pour instancier les calcul de sauvegarde
* @author phili
*
*/
public interface IFactorySauvegarde {
/**
*
* @param simule Ce qui est simulé
* @return Le calcul de la sauvegarde
* @throws SimulationException Si ce qui est simulé est une action de mêlée
*/
ICalculSauvegarde<IArmeDeTir> calculSauvegardeTir(ESimule simule)throws SimulationException;
/**
*
* @param simule Ce qui est simulé
* @return Le calcul de la sauvegarde
* @throws SimulationException Si ce qui est simulé est une action de tir
*/
ICalculSauvegarde<IArmeDeCorpsACorps> calculSauvegardeCorpsACorp(ESimule simule)throws SimulationException;
}
Et l'implémentation:
Code:
package com.calculateur.warhammer.calcul.mort.factory.sauvegarde;
import java.util.Arrays;
import com.calculateur.warhammer.calcul.mort.factory.exception.SimulationException;
import com.calculateur.warhammer.calcul.mort.factory.utils.FactoryUtils;
import com.calculateur.warhammer.calcul.mort.sauvegarde.CalculSauvegardeDemoniaque;
import com.calculateur.warhammer.calcul.mort.sauvegarde.CalculSauvegardeArmure;
import com.calculateur.warhammer.calcul.mort.sauvegarde.CalculSauvegardeDecorateur;
import com.calculateur.warhammer.calcul.mort.sauvegarde.CalculSauvegardeInvulnerableProfil;
import com.calculateur.warhammer.calcul.mort.sauvegarde.CalculSauvegardeInvulnerableRegle;
import com.calculateur.warhammer.calcul.mort.sauvegarde.ICalculSauvegarde;
import com.calculateur.warhammer.data.enumeration.ESimule;
import com.calculateur.warhammer.data.identifiable.IArmeDeCorpsACorps;
import com.calculateur.warhammer.data.identifiable.IArmeDeTir;
public class FactorySauvegarde implements IFactorySauvegarde{
private final ICalculSauvegarde<IArmeDeTir> calculTir;
private final ICalculSauvegarde<IArmeDeCorpsACorps> calculCorpsACorps;
public FactorySauvegarde() {
calculTir = new CalculSauvegardeDecorateur<>(Arrays.asList(
new CalculSauvegardeArmure<>(),
new CalculSauvegardeDemoniaque<>(),
new CalculSauvegardeInvulnerableProfil<>(),
new CalculSauvegardeInvulnerableRegle<>()));
calculCorpsACorps = new CalculSauvegardeDecorateur<>(Arrays.asList(
new CalculSauvegardeArmure<>(),
new CalculSauvegardeDemoniaque<>(),
new CalculSauvegardeInvulnerableProfil<>(),
new CalculSauvegardeInvulnerableRegle<>()));
}
@Override
public ICalculSauvegarde<IArmeDeTir> calculSauvegardeTir(ESimule simule) throws SimulationException {
FactoryUtils.valideActionTir(simule);
return calculTir;
}
@Override
public ICalculSauvegarde<IArmeDeCorpsACorps> calculSauvegardeCorpsACorp(ESimule simule) throws SimulationException {
FactoryUtils.valideActionCorpsACorps(simule);
return calculCorpsACorps;
}
}
On n'a plus qu'à modifier le paramétrage pour Spring.
Code:
package com.calculateur.warhammer.calcul.mort.parametres;
import com.calculateur.warhammer.calcul.mort.factory.sauvegarde.IFactorySauvegarde;
import com.calculateur.warhammer.calcul.mort.factory.validation.IFactoryValidation;
/**
* Paramètres pour fabriquer les factory nécessaires au calcul.
* @author phili
*
*/
public interface IParametres {
IFactoryValidation getFactoryValidation();
IFactorySauvegarde getFactorySauvegarde();
}