IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Voir le flux RSS

PhilippeGibault

Comment gérer les règles de Warhammer 40K avec les Design Pattern

Noter ce billet
par , 10/07/2022 à 14h11 (4941 Affichages)
C'est connu, il y a trop de règles à Warhammer 40K;

C'est d'ailleurs ce qui rebute.

Mais les règles deviennent importantes si on espère faire un calcul de probabilité.

Heureusement, il existe les Designs patterns qui vont permettre d'un peu simplifier le problème, et passer à un problème bourratif d'implémentation de règles.

Déjà, commençons par un point: Une figurine n'a pas de règles (excepté celles de base).

Si on prend un space marine, il a les règles de spaces-marines (Et il ne connaitrons pas la peur, les doctrines...).

Les règles du space marine s'ajoute aux règles de bases (aucune à la base, je précise, ça va avoir son importance).

Et puis il y a les chapitres. Comme par exemple les Dark Angels.

Les règles des Dark Angels (selon qu'il soit de la Deathwing, de la Ravenwing ou ni l'un, ni l'autre) qui s'ajoute aux règles de marines qui s'ajoute aux règles "de bases".

On remarque donc qu'on a une notion d'héritage

Ceci étant fait, il va falloir faire un point sur les Designs Patterns.

Les Designs Patterns
Il y a plusieurs approches.

La plus connue, dont on va parler est le GOF.

Effectivement, Erich Gamma, Richard Helm, Ralph Johnson et John Vlissides (Gang of Four, gang des 4) ont écrit un livre de référence sur les patrons architecturaux en langage objet. C'est de ça que l'on va parler.

A noter, que pour la théorie, il y a le GRAPS Et dans la vie du codeur de base, il y a SOLID.

SOLID est la philosophie qui doit guider le développement au quotidien. Mais on va voir que GOF et SOLID, si au premier abord n'ont pas la même approche, sont en fait complémentaires.

Partons d'un principe de base
On va faire plus simple. Je pense qu'il y a un principe de base pour faire une bonne architecture.

"Je sais ce que tu fais, mais je n'ai pas envie de savoir comment tu es codé".

Effectivement, le programme qui va calculer les proba a juste besoin des règles (et proflls). Il a besoin de ce que l'on appelle en UML l'interface, c'est à dire la signature des méthodes.
Il n'a pas besoin de savoir comment c'est implémenté.

C'est d'ailleurs pour ça que dans Effective Java Joshua Bloch explique qu'il faut préférer les classes abstraites aux interfaces.

Ce principe est identique au modèle Réseau OSI.

Ce qui tombe bien, l'Interface, notion en particulier UML a une réalité en Java. C'est un type.

Premier contrat, l'identifiable
On va donc définir nos interfaces, que je vais nommer contrat.
Et comme dans warhammer 40K tout le monde a sa petite règle (la figurine, le bâtiment,le pouvoir psychique et l'aura...), on va donc définir l'identifiable:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.calculateur.warhammer.data.identifiable;

/**
 * Cette interface permet de contractualiser un identifiable. Celui-ci est identifié par un id entier
 * @author phili
 *
 */
public interface IIdentifiable {

	/**
	 * 
	 * @return L'id de l'identifiable
	 */
	Integer getId();
}
L'identifiable est identifiable par son... identifiant. Ce qui pourra être utilse quand on le mettra en Base de données (où néanmoins, avec Hibernate, on préfère les Long).

Deux identifiables vont être important pour le calcul:

Le profil:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package com.calculateur.warhammer.data.identifiable;

import java.util.Set;

import com.calculateur.warhammer.data.enumeration.EProfil;

/**
 * Un profil à warhammer 40K
 * @author phili
 *
 */
public interface IProfil extends IIdentifiable{

	/**
	 * 
	 * @return Le mouvelment
	 */
	Integer getMouvement();
	
	/**
	 * 
	 * @return La CC
	 */
	Integer getCapaciteDeCombat();
	
	/**
	 * 
	 * @return La CT
	 */
	Integer getCapaciteTir();
	
	/**
	 * 
	 * @return La Force
	 */
	Integer getForce();
	
	/**
	 * 
	 * @return L'endurance
	 */
	Integer getEndurance();
	
	/**
	 * 
	 * @return Le nombre de point de vie
	 */
	Integer getNombrePointDeVie();
	
	/**
	 * 
	 * @return Le nombre d'attaque de base
	 */
	Integer getNombreAttaque();
	
	/**
	 * 
	 * @return Le nombre d'attaque par D3
	 */
	Integer getNombreAttaqueParD3();
	
	/**
	 * 
	 * @return Le nombre d'attaque par D6
	 */
	Integer getNombreAttaqueParD6();
	
	/**
	 * 
	 * @return Le commandement
	 */
	Integer getCommandement();
	
	/**
	 * 
	 * @return La sauvegarde
	 */
	Integer getSauvegarde();
	
	/**
	 * 
	 * @return La sauvegarde Invulnérable
	 */
	Integer getSauvegardeInvulnerable();
	
	/**
	 * 
	 * @return Les types du profil
	 */
	Set<EProfil> getTypesProfils();
	
	/**
	 * 
	 * @return Vrai si c'est un volant
	 */
	Boolean isVolant();
}
Le bâtiment:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.calculateur.warhammer.data.identifiable;

/**
 * Cette interface représente un batiment à 40K
 * @author phili
 *
 */
public interface IBatiment extends IIdentifiable{

	/**
	 * 
	 * @return Vrai si le batiment est identifiable
	 */
	boolean isDefendable();
	
	/**
	 * 
	 * @return Vrai si le batiment est un couvert dense
	 */
	boolean isCouvertDense();
	
	/**
	 * 
	 * @return Vrai si le batiment est un couvert léger
	 */
	boolean isCouvertLeger();
	
	/**
	 * 
	 * @return Vrai si le batiment est un couvert lourd
	 */
	boolean isCouvertLourd();
	
	/**
	 * 
	 * @return Vrai si bâtiment est exaltant
	 */
	boolean isExaltant();
}
Ainsi que les armes:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.calculateur.warhammer.data.identifiable;

/**
 * L'arme utilisé
 * @author phili
 *
 */
public interface IArme extends IIdentifiable{

	/**
	 * 
	 * @return La PA de l'arme
	 */
	Integer getPA();
	
	/**
	 * 
	 * @return Le nombre de D3 qu'il faut lancer pour avoir la PA
	 */
	Integer getPAParD3();
	
	/**
	 * 
	 * @return Le nomnbre de D6 qu'il faut lancer pour avoir la PA
	 */
	Integer getPAParD6();
	
	/**
	 * 
	 * @return Les dégats de base
	 */
	Integer getDegâts();
	
	/**
	 * 
	 * @return Le nombre de D3 qu'il faut avoir pour avoir les dégâts
	 */
	Integer getDegatsParD3();
	
	/**
	 * 
	 * @return Nombre d'attaque par D6
	 */
	Integer getDegatsParD6();
}
Notez qu'il existe des armes de corps à corps et de tir.
Dans le contexte des interfaces Java, on va composer.

On introduit donc les armes de tir:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.calculateur.warhammer.data.identifiable;

import com.calculateur.warhammer.data.enumeration.EArmeTir;

/**
 * Les spécifications pour l'arme de tir
 * @author phili
 *
 */
public interface IArmeDeTir extends IArme{

	/**
	 * 
	 * @return Portée minimale de l'arme
	 */
	Integer getPorteeMinimale();
	
	/**
	 * 
	 * @return Portée maximale de l'arme
	 */
	Integer getPorteeMaximale();
	
	/**
	 * 
	 * @return Portée maximale de l'arme
	 */
	EArmeTir getType();
	
	/**
	 * 
	 * @return Nombre d'attaque de base
	 */
	Integer getNombreAttaqueBase();
	
	/**
	 * 
	 * @return Le nombre de D3 qu'il faut lancer pour attaque
	 */
	Integer getNombreAttaqueD3();
	
	/**
	 * 
	 * @return Le nombre de D6 qu'il faut lancer pour une attaque
	 */
	Integer getNombreAttaqueD6();
	
	/**
	 * 
	 * @return le nombre minimum qu'il faut lancer pour un daka
	 */
	Integer getNombreDakaMin();
	
	/**
	 * 
	 * @return le nombre maximum qu'il faut lancer pour un Daka
	 */
	Integer getNombreDakaMax();
	
	/**
	 * 
	 * @return Vrai si il faut faire un jet de touche
	 */
	Boolean isJetTouche();
	
	/**
	 * 
	 * @return Vrai si l'arme est Blast
	 */
	Boolean isBlast();
	
	/**
	 * 
	 * @return Vrai si l'arme peut tirer sans ligne de vue
	 */
	boolean isTirIndirect();
}
Et l'arme de corps à corps:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.calculateur.warhammer.data.identifiable;

/**
 * Cette Interface représente les armes de corps à corps
 * @author phili
 *
 */
public interface IArmeDeCorpsACorps extends IArme{

	/**
	 * 
	 * @return Bonus d'attaques aditionnel
	 */
	Integer getBonusAttaqueFixe();
	
	/**
	 * 
	 * @return Bonus aditionnel donné par un nombre de D3
	 */
	Integer getBonusAttaqueD3();
	
	/**
	 * 
	 * @return Bonus aditionnel donné par un nombre de D6
	 */
	Integer getBonusAttaqueD6();
	
	/**
	 * 
	 * @return Bonus Additionnel en force donné par l'arme (F + bonus)
	 */
	Integer getBonusAditionForce();
	
	/**
	 * 
	 * @return Bonus multiplicatif donné par l'arme Bonus * (F + bonus aditionnel)
	 */
	Integer getBonusMultiplicationForce();
	
	/**
	 * 
	 * @return Bonus Additionnel en force donné par l'arme après avoir jeté un vombre de D3
	 */
	Integer getBonusAditionForceD3();
	
	/**
	 * 
	 * @return Bonus Additionnel en force donné par l'arme après avoir jeté un vombre de D6
	 */
	Integer getBonusAditionForceD6();
}
Contrat pour les règles
On va donc définir un contrat pour les règles.
Pour commencer, une règles est pour un identifiable. Notez que contrairement à ce que j'ai rendu pour le SMB116, on traite les bâtiments comme ayant des règles.
On a donc la règle, avec son identifiable (pour la retrouver, bien évidement).

package com.calculateur.warhammer.data.regles;

import com.calculateur.warhammer.data.identifiable.IIdentifiable;

/**
* Règle spéciale pour un identifiable
* @author phili
*
* @param <I> Identifiable auquel s'applique la règle
*/
public interface IRegle <I extends IIdentifiable>{

/**
*
* @return L'identifiable à laquelle la règle s'applique
*/
I getIdentifiable();
}
La pratique a montré qu'il fallait décomposer les règles de l'attaquant et du défenseur, au moins pour limiter la taille des classes, et pour avoir une meilleur lisibilité du code.

Donc on a:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package com.calculateur.warhammer.data.regles;

import java.util.Map;
import java.util.Set;

/**
 * Règle d'attaque
 * @author phili
 */
public interface IRegleAttaque{

	//Règle mouvement
	/**
	 * 
	 * @return Vrai si celui-ci ignore les malus du mouvement d'avance au tir
	 */
	boolean isIgnoreMouvementAvanceAuTir();
	
	/**
	 * 
	 * @return Vrai si celui-ci ignore les malus du mouvement normal au tir
	 */
	boolean isIgnoreMouvementNormalAuTir();
	
	/**
	 * 
	 * @return Vrai si pour une arme à tir rapide, la figurine tir toujours 2 fois
	 */
	boolean isMaitriseArmeTirRapide();
	
	//Jet de touche
	/**
	 * 
	 * @return Vrai si le jet de touche a un bonus.
	 */
	Integer getBonusJetTouche();
	
	/**
	 * 
	 * @return Les scores relançables
	 */
	Set<Integer> getScoreJetToucheRelancable();
	
	/**
	 * 
	 * @return Jet minimum pour toucher
	 */
	Integer getJetMinimumPourToucher();
	
	/**
	 * 
	 * @return La map qui donne un jet de blessures mortelles, et on s'arrête après le jet de touche. <br/> 
	 * Le second chiffre donne le nombre de blessures mortelles
	 */
	Map<Integer, Integer> getMapJetToucheBlessuresMortelleEtOnArrete();
	
	/**
	 * 
	 * @return La Map de blessures Mortelles et on continue la séquence. C'est donc des blessures mortelles en plus.<br/>
	 * Le second chiffre est le nombre de blessures mortelles
	 */
	Map<Integer,Integer> getMapJetToucheBlessuresMortellesEtContinue();
	
	/**
	 * 
	 * @return La map score qui donne des attaques en plus. Le second chiffre est le nombre d'attaqye
	 */
	Map<Integer,Integer> getMapJetToucheAttaqueSupplementaire();
	
	//Force
	/**
	 * 
	 * @return Le bonus de force de l'adversaire
	 */
	Integer getBonusForce();
	
	//Endurance
	/**
	 * 
	 * @return Malus appliqué à une cible sur l'endurance
	 */
	Integer getMalusEndurance();
	
	//Jet de blessures
	/**
	 * 
	 * @return le bonus au jet de touche
	 */
	Integer getBonusJetBlessures();
	
	/**
	 * 
	 * @return Jet de blessures relançable
	 */
	Set<Integer> getScoreJetBlessureRelancable();
	
	/**
	 * 
	 * @return Jet minimum pour blesser
	 */
	Integer getJetMinumumPourBlesser();
	
	/**
	 * 
	 * @return La map qui donne un jet de blessures mortelles, et on s'arrête après le jet de blessure. <br/> 
	 * Le second chiffre donne le nombre de blessures mortelles
	 */
	Map<Integer, Integer> getMapJetBlessuresBlessuresMortelleEtOnArrete();
	
	/**
	 * 
	 * @return La Map de blessures Mortelles et on continue la séquence. C'est donc des blessures mortelles en plus.<br/>
	 * Le second chiffre est le nombre de blessures mortelles
	 */
	Map<Integer,Integer> getMapJetBlessuresBlessuresMortellesEtContinue();
	
	/**
	 * 
	 * @return La map qui pour un jet de touche, donne un bonus à la PA.<br/>
	 * Le second chiffre est le bonus à la PA
	 */
	Map<Integer,Integer> getMapJetBlessureBonusPA();
	
	//Attaques
	Integer getBonusNombreAttaque();
	
	//Commandement
	/**
	 * 
	 * @return Le malus que la règle donne aux test d'attritions
	 */
	Integer getMalusSurAttrition();
	
	//Sauvegarde
	/**
	 * 
	 * @return Vrai si l'attaquant ignore la sauvegarde invulnérable
	 */
	boolean isIgnoreSauvegardeInvulnerable();
	
	/**
	 * 
	 * @return Vrai si on fait ignorer la sauvegarde de couvert
	 */
	boolean isIgnoreSauvegardeCouvert();
	
	//Se relève
	/**
	 * 
	 * @return Vrai si la cible ne peut pas ignorer les blessures
	 */
	boolean isIgnoreIgnoreBlessure();
}
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package com.calculateur.warhammer.data.regles;

import java.util.Set;

import com.calculateur.warhammer.data.enumeration.EBlessure;

/**
 * Règle pour se défendre
 * @author phili
 *
 */
public interface IRegleDefense {

	//Jet de touche
	/**
	 * 
	 * @return Malus que subit l'attaquant au jet de touche
	 */
	Integer getMalusJetTouche();
	
	/**
	 * 
	 * @return Le jet minimum pour être touché
	 */
	Integer getJetMinimumPourEtreTouche();
	
	//Force
	/**
	 * 
	 * @return Malus que l'attaquant subit à la sauvegarde
	 */
	Integer getMalusForce();
	
	//Endurance
	/**
	 * 
	 * @return Bonus en endurance dont bénéficie le défenseur
	 */
	Integer getBonusEndurance();
	
	//Jet blessure
	/**
	 * 
	 * @return Jet minimum pour être blessé
	 */
	Integer getJetMinimumPourEtreBlesse();
	
	/**
	 * 
	 * @return Malus au jet de blessure
	 */
	Integer getMalusJetBlessure();
	
	//PV
	/**
	 * 
	 * @return Le nombre maximum de PV que la figurine peut perdre par phase
	 */
	Integer getNombrePointsViesMaximumPerdable();
	
	//Attaques
	/**
	 * 
	 * @return Malus que l'attaquant subit au nombre d'attaque.
	 */
	Integer getMalusNombreAttaque();
	
	//Sauvegarde
	/**
	 * 
	 * @return Vrai si il y a une sauvegarde de couvert
	 */
	boolean isSauvegardeCouvert();
	
	//Commandement
	/**
	 * 
	 * @return Vrai si la défense ignore le test de commandement
	 */
	boolean isIgnoreTestCommandement();
	
	/**
	 * 
	 * @return Bonus aux tests de commandement
	 */
	Integer getBonusCommandement();
	
	/**
	 * 
	 * @return Vrai si le test d'attrition est ignoré
	 */
	boolean isIgnoreTestAttrition();
	
	/**
	 * 
	 * @return Vrai si les malus au tests d'attrition sont ignorés
	 */
	boolean isIgnoreMalusAttrition();
	
	//Se relève
	/**
	 * 
	 * @return Le score pour se relever après la mort
	 */
	Integer getJetSeReleve();
	
	/**
	 * 
	 * @return Les relance du jet pour se relever
	 */
	Set<Integer> getRelancesSeReleve();
	
	//Ignore blessures
	/**
	 * 
	 * @param typeBlessures
	 * @return Le jet pour ignorer une blessure
	 */
	Integer getJetSoreIgnoreBlessure(EBlessure typeBlessures);
}
A ce stade, on ne va pas implémenter pour chaque faction (il y en a trop) mais pour des cas généraux.

A la base, pas de règles: Design Pattern Template Method
Comme dit précédemment, à la base, on applique pas de règle spéciales, mais celle du livre de base.

Ce qui permet de faire une implémentation pour la base, soit:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143

package com.calculateur.warhammer.data.regles;

import java.util.Collections;
import java.util.Map;
import java.util.Set;

import com.calculateur.warhammer.data.identifiable.IIdentifiable;

/**
 * Règles d'attaque de base pour un identifiable (c'est à dire aucune)
 * 
 * @author phili
 *
 */
public class RegleAttaqueBase<I extends IIdentifiable> implements IRegle<I>, IRegleAttaque {

	private static final Integer NO_BONUS = 0;

	private static final Integer NO_JET_MINIMUM = 0;

	private final I identifiable;

	public RegleAttaqueBase(I identifiable) {
		this.identifiable = identifiable;
	}

	@Override
	public boolean isIgnoreMouvementAvanceAuTir() {
		return false;
	}

	@Override
	public boolean isIgnoreMouvementNormalAuTir() {
		return false;
	}

	@Override
	public boolean isMaitriseArmeTirRapide() {
		return false;
	}

	@Override
	public Integer getBonusJetTouche() {
		return NO_BONUS;
	}

	@Override
	public Set<Integer> getScoreJetToucheRelancable() {
		return Collections.emptySet();
	}

	@Override
	public Integer getJetMinimumPourToucher() {
		return NO_JET_MINIMUM;
	}

	@Override
	public Map<Integer, Integer> getMapJetToucheBlessuresMortelleEtOnArrete() {
		return Collections.emptyMap();
	}

	@Override
	public Map<Integer, Integer> getMapJetToucheBlessuresMortellesEtContinue() {
		return Collections.emptyMap();
	}

	@Override
	public Map<Integer, Integer> getMapJetToucheAttaqueSupplementaire() {
		return Collections.emptyMap();
	}

	@Override
	public Integer getBonusForce() {
		return NO_BONUS;
	}

	@Override
	public Integer getMalusEndurance() {
		return NO_BONUS;
	}

	@Override
	public Integer getBonusJetBlessures() {
		return NO_BONUS;
	}

	@Override
	public Set<Integer> getScoreJetBlessureRelancable() {
		return Collections.emptySet();
	}

	@Override
	public Integer getJetMinumumPourBlesser() {
		return NO_JET_MINIMUM;
	}

	@Override
	public Map<Integer, Integer> getMapJetBlessuresBlessuresMortelleEtOnArrete() {
		return Collections.emptyMap();
	}

	@Override
	public Map<Integer, Integer> getMapJetBlessuresBlessuresMortellesEtContinue() {
		return Collections.emptyMap();
	}

	@Override
	public Map<Integer, Integer> getMapJetBlessureBonusPA() {
		return Collections.emptyMap();
	}

	@Override
	public Integer getBonusNombreAttaque() {
		return NO_BONUS;
	}

	@Override
	public Integer getMalusSurAttrition() {
		return NO_BONUS;
	}

	@Override
	public I getIdentifiable() {
		return identifiable;
	}

	@Override
	public boolean isIgnoreSauvegardeInvulnerable() {
		return false;
	}

	@Override
	public boolean isIgnoreSauvegardeCouvert() {
		return false;
	}

	@Override
	public boolean isIgnoreIgnoreBlessure() {
		return false;
	}
}
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package com.calculateur.warhammer.data.regles;

import java.util.Collections;
import java.util.Set;

import com.calculateur.warhammer.data.enumeration.EBlessure;
import com.calculateur.warhammer.data.identifiable.IIdentifiable;

public class RegleDefenseBase<I extends IIdentifiable> implements IRegle<I>,IRegleDefense  {

	private static final Integer NO_BONUS = 0;

	private static final Integer NO_JET_MINIMUM = 0;

	private final I identifiable;
	
	public RegleDefenseBase(I identifiable) {
		this.identifiable = identifiable;
	}
	
	@Override
	public Integer getMalusJetTouche() {
		return NO_BONUS;
	}

	@Override
	public Integer getJetMinimumPourEtreTouche() {
		return NO_JET_MINIMUM;
	}

	@Override
	public Integer getMalusForce() {
		return NO_BONUS;
	}

	@Override
	public Integer getBonusEndurance() {
		return NO_BONUS;
	}

	@Override
	public Integer getJetMinimumPourEtreBlesse() {
		return NO_JET_MINIMUM;
	}

	@Override
	public Integer getMalusJetBlessure() {
		return NO_BONUS;
	}

	@Override
	public Integer getNombrePointsViesMaximumPerdable() {
		return NO_BONUS;
	}

	@Override
	public Integer getMalusNombreAttaque() {
		return NO_BONUS;
	}

	@Override
	public boolean isSauvegardeCouvert() {
		return false;
	}

	@Override
	public boolean isIgnoreTestCommandement() {
		return false;
	}

	@Override
	public Integer getBonusCommandement() {
		return NO_BONUS;
	}

	@Override
	public boolean isIgnoreTestAttrition() {
		return false;
	}

	@Override
	public boolean isIgnoreMalusAttrition() {
		return false;
	}

	@Override
	public Integer getJetSeReleve() {
		return NO_BONUS;
	}

	@Override
	public Set<Integer> getRelancesSeReleve() {
		return Collections.emptySet();
	}

	@Override
	public Integer getJetSoreIgnoreBlessure(EBlessure typeBlessures) {
		return NO_JET_MINIMUM;
	}

	@Override
	public I getIdentifiable() {
		return identifiable;
	}

}
On a donc de fait un Template Method, vu que les règles des Spaces Marines vont hériter, des règles de bases...

En général, ce qui est implémenté dans un Template Method dépends de spécifications qui dépendent du contexte.

C'est pour ça que ces "spécificatons" sont des méthodes abstraites. Le Template Méthode est donc une classe abstraite avec des méthodes abstraites pour pouvoir prendre en compte les spécifications qui seront dans les classes filles.

Ce n'est pas le cas ici.

Un autre problème, c'est qu'il risque dans certains cas, il risque d'y avoir beaucoup de classes filles.

Pour gérer l'ensemble des règles: Le Design Pattern Décorateur
Dans un cas réél, on est un space marine, avec ses règles, dans un bâtiment qui a ses règles, avec des chefs, qui eux aussi donnent leurs règles...

Pas de problème, il existe aussi un Design Pattern pour ça: le Design Pattern Décorateur.
On va passer la liste des règles, et on au final, l'utilisateur ne va voir qu'une seule règle, celles à appliquer.

Ici, la règle vue par l'utilisateur (donc celle à appliquer) est décorée par un ensemble de règle.

Ce qui donne:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
package com.calculateur.warhammer.data.regles;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * Un décorateur qui prend un ensemble de règles d'attaque et fait un traitement
 * pour sortir la bonne règle au bon moment.
 * 
 * @author phili
 *
 */
public class RegleAttaqueDecorateur implements IRegleAttaque {

	private final List<IRegleAttaque> regles;

	public RegleAttaqueDecorateur(List<IRegleAttaque> regles) {
		this.regles = regles;
	}

	@Override
	public boolean isIgnoreMouvementAvanceAuTir() {
		return regles.stream().anyMatch(r -> r.isIgnoreMouvementAvanceAuTir());
	}

	@Override
	public boolean isIgnoreMouvementNormalAuTir() {
		return regles.stream().anyMatch(r -> r.isIgnoreMouvementNormalAuTir());
	}

	@Override
	public boolean isMaitriseArmeTirRapide() {
		return regles.stream().anyMatch(r -> r.isMaitriseArmeTirRapide());
	}

	@Override
	public Integer getBonusJetTouche() {
		return regles.stream().mapToInt(r -> r.getBonusJetTouche()).sum();
	}

	@Override
	public Set<Integer> getScoreJetToucheRelancable() {
		Set<Integer> sReturn = new HashSet<>();
		regles.stream().forEach(r -> sReturn.addAll(r.getScoreJetToucheRelancable()));
		return sReturn;
	}

	@Override
	public Integer getJetMinimumPourToucher() {
		Integer result = 0;
		Integer aResult = regles.stream().filter(r -> r.getJetMinimumPourToucher() > 0)
				.mapToInt(r -> r.getJetMinimumPourToucher()).min().getAsInt();
		return (aResult != null) ? aResult : result;
	}

	@Override
	public Map<Integer, Integer> getMapJetToucheBlessuresMortelleEtOnArrete() {
		Map<Integer, Integer> mReturn = new HashMap<>();
		Integer value;
		for (IRegleAttaque regle : regles) {
			for (Entry<Integer, Integer> entry : regle.getMapJetToucheBlessuresMortelleEtOnArrete().entrySet()) {
				value = mReturn.computeIfAbsent(entry.getKey(), k -> 0);
				value += entry.getValue();
				mReturn.put(entry.getKey(), value);
			}
		}
		return mReturn;
	}

	@Override
	public Map<Integer, Integer> getMapJetToucheBlessuresMortellesEtContinue() {
		Map<Integer, Integer> mReturn = new HashMap<>();
		Integer value;
		for (IRegleAttaque regle : regles) {
			for (Entry<Integer, Integer> entry : regle.getMapJetToucheBlessuresMortellesEtContinue().entrySet()) {
				value = mReturn.computeIfAbsent(entry.getKey(), k -> 0);
				value += entry.getValue();
				mReturn.put(entry.getKey(), value);
			}
		}
		return mReturn;
	}

	@Override
	public Map<Integer, Integer> getMapJetToucheAttaqueSupplementaire() {
		Map<Integer, Integer> mReturn = new HashMap<>();
		Integer value;
		for (IRegleAttaque regle : regles) {
			for (Entry<Integer, Integer> entry : regle.getMapJetToucheAttaqueSupplementaire().entrySet()) {
				value = mReturn.computeIfAbsent(entry.getKey(), k -> 0);
				value += entry.getValue();
				mReturn.put(entry.getKey(), value);
			}
		}
		return mReturn;
	}

	@Override
	public Integer getBonusForce() {
		return regles.stream().mapToInt(r -> r.getBonusForce()).sum();
	}

	@Override
	public Integer getMalusEndurance() {
		return regles.stream().mapToInt(r -> r.getMalusEndurance()).sum();
	}

	@Override
	public Integer getBonusJetBlessures() {
		return regles.stream().mapToInt(r -> r.getBonusJetBlessures()).sum();
	}

	@Override
	public Set<Integer> getScoreJetBlessureRelancable() {
		Set<Integer> sReturn = new HashSet<>();
		regles.stream().forEach(r -> sReturn.addAll(r.getScoreJetBlessureRelancable()));
		return sReturn;
	}

	@Override
	public Integer getJetMinumumPourBlesser() {
		Integer result = 0;
		Integer aResult = regles.stream().filter(r -> r.getJetMinumumPourBlesser() > 0)
				.mapToInt(r -> r.getJetMinumumPourBlesser()).min().getAsInt();
		return (aResult != null) ? aResult : result;
	}

	@Override
	public Map<Integer, Integer> getMapJetBlessuresBlessuresMortelleEtOnArrete() {
		Map<Integer, Integer> mReturn = new HashMap<>();
		Integer value;
		for (IRegleAttaque regle : regles) {
			for (Entry<Integer, Integer> entry : regle.getMapJetBlessuresBlessuresMortelleEtOnArrete().entrySet()) {
				value = mReturn.computeIfAbsent(entry.getKey(), k -> 0);
				value += entry.getValue();
				mReturn.put(entry.getKey(), value);
			}
		}
		return mReturn;
	}

	@Override
	public Map<Integer, Integer> getMapJetBlessuresBlessuresMortellesEtContinue() {
		Map<Integer, Integer> mReturn = new HashMap<>();
		Integer value;
		for (IRegleAttaque regle : regles) {
			for (Entry<Integer, Integer> entry : regle.getMapJetBlessuresBlessuresMortellesEtContinue().entrySet()) {
				value = mReturn.computeIfAbsent(entry.getKey(), k -> 0);
				value += entry.getValue();
				mReturn.put(entry.getKey(), value);
			}
		}
		return mReturn;
	}

	@Override
	public Map<Integer, Integer> getMapJetBlessureBonusPA() {
		Map<Integer, Integer> mReturn = new HashMap<>();
		Integer value;
		for (IRegleAttaque regle : regles) {
			for (Entry<Integer, Integer> entry : regle.getMapJetBlessureBonusPA().entrySet()) {
				value = mReturn.computeIfAbsent(entry.getKey(), k -> 0);
				value += entry.getValue();
				mReturn.put(entry.getKey(), value);
			}
		}
		return mReturn;
	}

	@Override
	public Integer getBonusNombreAttaque() {
		return regles.stream().mapToInt(r -> r.getBonusNombreAttaque()).sum();
	}

	@Override
	public Integer getMalusSurAttrition() {
		return regles.stream().mapToInt(r -> r.getMalusSurAttrition()).sum();
	}

	@Override
	public boolean isIgnoreSauvegardeInvulnerable() {
		return regles.stream().anyMatch(r -> r.isIgnoreSauvegardeInvulnerable());
	}

	@Override
	public boolean isIgnoreSauvegardeCouvert() {
		return regles.stream().anyMatch(r -> r.isIgnoreSauvegardeInvulnerable());
	}

	@Override
	public boolean isIgnoreIgnoreBlessure() {
		return regles.stream().anyMatch(r -> r.isIgnoreIgnoreBlessure());
	}
}
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package com.calculateur.warhammer.data.regles;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.calculateur.warhammer.data.enumeration.EBlessure;

/**
 * Décorateur de règle pour la défense
 * @author phili
 *
 */
public class RegleDefenseDecorateur implements IRegleDefense{

	private final List<IRegleDefense> regles;
	
	public RegleDefenseDecorateur(List<IRegleDefense> regles) {
		this.regles = regles;
	}
	
	@Override
	public Integer getMalusJetTouche() {
		return regles.stream().mapToInt(r -> r.getMalusJetTouche()).sum();
	}

	@Override
	public Integer getJetMinimumPourEtreTouche() {
		Integer iReturn = 0;
		Integer result = regles.stream().filter(r -> r.getJetMinimumPourEtreTouche() > 0).mapToInt(r -> r.getJetMinimumPourEtreTouche()).max().getAsInt();
		return (result != null)?result:iReturn;
	}

	@Override
	public Integer getMalusForce() {
		return regles.stream().mapToInt(r -> r.getMalusForce()).sum();
	}

	@Override
	public Integer getBonusEndurance() {
		return regles.stream().mapToInt(r -> r.getBonusEndurance()).sum();
	}

	@Override
	public Integer getJetMinimumPourEtreBlesse() {
		Integer iReturn = 0;
		Integer result = regles.stream().filter(r -> r.getJetMinimumPourEtreBlesse() > 0).mapToInt(r -> r.getJetMinimumPourEtreBlesse()).max().getAsInt();
		return (result != null)?result:iReturn;
	}

	@Override
	public Integer getMalusJetBlessure() {
		return regles.stream().mapToInt(r -> r.getMalusJetBlessure()).sum();
	}

	@Override
	public Integer getNombrePointsViesMaximumPerdable() {
		Integer iReturn = 0;
		Integer result = regles.stream().filter(r -> r.getNombrePointsViesMaximumPerdable() > 0).mapToInt(r -> r.getNombrePointsViesMaximumPerdable()).min().getAsInt();
		return (result != null)?result:iReturn;
	}

	@Override
	public Integer getMalusNombreAttaque() {
		return regles.stream().mapToInt(r -> r.getMalusNombreAttaque()).sum();
	}

	@Override
	public boolean isSauvegardeCouvert() {
		return regles.stream().anyMatch(r -> r.isSauvegardeCouvert());
	}

	@Override
	public boolean isIgnoreTestCommandement() {
		return regles.stream().anyMatch(r -> r.isIgnoreTestCommandement());
	}

	@Override
	public Integer getBonusCommandement() {
		return regles.stream().mapToInt(r -> r.getBonusCommandement()).sum();
	}

	@Override
	public boolean isIgnoreTestAttrition() {
		return regles.stream().anyMatch(r -> r.isIgnoreTestAttrition());
	}

	@Override
	public boolean isIgnoreMalusAttrition() {
		return regles.stream().anyMatch(r -> r.isIgnoreMalusAttrition());
	}

	@Override
	public Integer getJetSeReleve() {
		Integer iReturn = 0;
		Integer result = regles.stream().filter(r -> r.getJetSeReleve() > 0).mapToInt(r -> r.getJetSeReleve()).min().getAsInt();
		return (result != null)?result:iReturn;
	}

	@Override
	public Set<Integer> getRelancesSeReleve() {
		Set<Integer> sReturn = new HashSet<>();
		regles.stream().forEach(r -> sReturn.addAll(r.getRelancesSeReleve()));
		return sReturn;
	}

	@Override
	public Integer getJetSoreIgnoreBlessure(EBlessure typeBlessures) {
		Integer iReturn = 0;
		Integer result = regles.stream().filter(r -> r.getJetSoreIgnoreBlessure(typeBlessures) > 0).mapToInt(r -> r.getJetSoreIgnoreBlessure(typeBlessures)).min().getAsInt();
		return (result != null)?result:iReturn;
	}

}
Et les bâtiment
Dans ce que j'ai rendu pour SMB116, je différentiais dans le calcul le bâtiment. Et c'était juste infernal.
Du coup, dans la reprise, un bâtiment, c'est juste un identifiable qui a ses propres règles.
Il suffit donc de faire une implémentation pour le bâtiment:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.calculateur.warhammer.data.regles;

import com.calculateur.warhammer.data.enumeration.EMouvement;
import com.calculateur.warhammer.data.enumeration.ESimule;
import com.calculateur.warhammer.data.identifiable.IBatiment;

/**
 * Règle pour l'attaquant étant dans un bâtiment
 * @author phili
 *
 */
public class RegleAttaqueBatiment extends RegleAttaqueBase<IBatiment> {

	private final IBatiment batiment;

	private final ESimule simule;

	private final EMouvement mouvementPrecedent;

	private final boolean isTirContreChargeAvant;

	private static final Integer BONUS_TOUCHE = -1;
	private static final Integer NO_BONUS = 1;

	public RegleAttaqueBatiment(IBatiment batiment, ESimule simule, EMouvement mouvementPrecedent,
			boolean isTirContreChargeAvant) {
		super(batiment);
		this.batiment = batiment;
		this.simule = simule;
		this.mouvementPrecedent = mouvementPrecedent;
		this.isTirContreChargeAvant = isTirContreChargeAvant;
	}

	@Override
	public Integer getBonusJetTouche() {
		Integer iReturn;
		if (mouvementPrecedent == EMouvement.IMMOBILE) {
			boolean isDefendable = batiment.isDefendable()
					&& (simule == ESimule.TIR_CONTRE_CHARGE || (!isTirContreChargeAvant && simule == ESimule.CORPS_A_CORPS_APRES_ACTIVATION));
			boolean isCouvertLourd = batiment.isCouvertLourd() && simule == ESimule.CORPS_A_CORPS_APRES_ACTIVATION && mouvementPrecedent == EMouvement.IMMOBILE;
			iReturn = (isDefendable || isCouvertLourd)?BONUS_TOUCHE:NO_BONUS;
		} else {
			iReturn = NO_BONUS;
		}
		return iReturn;
	}
}
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.calculateur.warhammer.data.regles;

import com.calculateur.warhammer.data.enumeration.ESimule;
import com.calculateur.warhammer.data.identifiable.IBatiment;

public class RegleDefenseBatiment extends RegleDefenseBase<IBatiment>{

	private final IBatiment batiment;

	private final ESimule simule;
	
	private static final Integer BONUS_TOUCHE = -1;
	private static final Integer BONUS_COMMANDEMENT = 1;
	private static final Integer NO_BONUS = 0;

	public RegleDefenseBatiment(IBatiment batiment, ESimule simule) {
		super(batiment);
		this.batiment = batiment;
		this.simule = simule;
	}

	@Override
	public Integer getMalusJetTouche() {
		return (simule == ESimule.TIR_PHASE_TIR && batiment.isCouvertDense()) ? BONUS_TOUCHE
				: NO_BONUS;
	}

	@Override
	public boolean isSauvegardeCouvert() {
		return simule == ESimule.TIR_PHASE_TIR ? batiment.isCouvertLeger() : false;
	}

	@Override
	public Integer getBonusCommandement() {
		return batiment.isExaltant() ? BONUS_COMMANDEMENT : NO_BONUS;
	}
}
Design Pattern Stratégie
Au final, on a aussi utilisé le Design Pattern Stratégie.

L'utilisateur ne voit que la règle, mais on l'obtient de différente façon, en changeant de stratégie en somme.

Et en cas de nouvelles règles (ou d'oubli...)
Effectivement, la dernière fois, j'avais oublié que si ils chargent ou si ils sont chargés, les marines on une attaque de plus.

Et pour les démons, les rumeurs parlent d'un nouveau type de sauvegarde.

Pas de problème, on l'ajoute dans l'interface et on l'implémente dans les deux classes (le décorateur et la base).

Bon, maintenant, il faut obtenir la règle: le Design Pattern Factory

Pour obtenir la règle, on va utiliser le Design Pattern Factory.

Ici, on a pas besoin, à ce stade, d'implémenter. On définit un contrat.

Définir ce contrat sera important pour pouvoir faire l'injection de dépendance quand on ajoutera Spring.

Soit:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.calculateur.warhammer.data.regles.factory;

import com.calculateur.warhammer.base.exception.FunctionnalExeption;
import com.calculateur.warhammer.data.regles.IRegleAttaque;
import com.calculateur.warhammer.data.regles.IRegleDefense;

/**
 * Factory qui va créer la règle
 * @author phili
 *
 */
public interface FactoryRegle {

	/**
	 * 
	 * @param parametres Les paramère nécessaires propre à la faction
	 * @return Les régles d'attaques de la faction
	 * @throws FactoryReglesException Si une exception s'est produite lors de la fabrication des rècles
	 * @throws FunctionnalExeption Si après vérification, il manque une règle
	 */
	IRegleAttaque getRegleAttaque(String parametres)throws FactoryReglesException,FunctionnalExeption;
	
	/**
	 * 
	 * @param parametres Les paramère nécessaires propre à la faction
	 * @return Les régles de défense la faction
	 * @throws FactoryReglesException Si une exception s'est produite lors de la fabrication des rècles
	 * @throws FunctionnalExeption Si après vérification, il manque une règle
	 */
	IRegleDefense getRegleDefense(String parametres)throws FactoryReglesException,FunctionnalExeption;
}
Même si je ne vais pas parler des exceptions Java (pour le moment), on note que la Factory jette une exception utilisateur (FunctionnalExeption) et une si elle n'arrive pas à fabriquer la règle (FactoryReglesException).

On passe sous forme de String (comme "isFigurinePerdu : true" par exemple) pour pouvoir paramètrer la règle.

On a aussi, comme c'est d'ailleurs l'usage en Java une Factoy pour la Factory.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.calculateur.warhammer.data.regles.factory;

/**
 * Factory pour fabriquer la Factory de règle.
 * @author phili
 *
 */
public interface FactoryFactoryRegle {

	/**
	 * 
	 * @param aClass Class de factory
	 * @return La factory de règles
	 * @throws FactoryReglesException
	 */
	FactoryRegle getFactory(String aClass)throws FactoryReglesException;
}
On utilisera sans doute l'introspection mais ça, c'est une autre histoire.

Envoyer le billet « Comment gérer les règles de Warhammer 40K avec les Design Pattern » dans le blog Viadeo Envoyer le billet « Comment gérer les règles de Warhammer 40K avec les Design Pattern » dans le blog Twitter Envoyer le billet « Comment gérer les règles de Warhammer 40K avec les Design Pattern » dans le blog Google Envoyer le billet « Comment gérer les règles de Warhammer 40K avec les Design Pattern » dans le blog Facebook Envoyer le billet « Comment gérer les règles de Warhammer 40K avec les Design Pattern » dans le blog Digg Envoyer le billet « Comment gérer les règles de Warhammer 40K avec les Design Pattern » dans le blog Delicious Envoyer le billet « Comment gérer les règles de Warhammer 40K avec les Design Pattern » dans le blog MySpace Envoyer le billet « Comment gérer les règles de Warhammer 40K avec les Design Pattern » dans le blog Yahoo

Catégories
Sans catégorie

Commentaires