Commit 8881aff5 authored by DELBECQ Théo's avatar DELBECQ Théo

Merge branch 'Theo' into 'master'

Résolution de bug de compatibilité et ajout de commentaires et de documentation

See merge request !26
parents b7755baf b3b59407
#Mon, 04 May 2020 20:15:39 +0200
C\:\\Users\\timot\\OneDrive\\Desktop\\2019-2020\\Projet-Informatique\\tmp=
C\:\\Users\\theo\\Documents\\Ecole\\IMT\ Lille\ Douai\\L3\\Projet\ Info\\groupe3-tictactoe=
......@@ -39,8 +39,8 @@ public class AlgoRechercheMinMax extends AlgoRecherche{
/**
* <div>Le constructeur principal d'instance de l'algorithme</div>
* @param depth La profondeur d'étude déterminites des coups possibles
* @param d_gen Le nombre de tests al"atoires réalisés aux feuilles
* @param depth La profondeur d'étude déterminites des coups possibles. Entre 1 et 98.
* @param d_gen Le nombre de tests al"atoires réalisés aux feuilles. Supérieur à 1.
* @param joueur1 Le premier Joueur, il faut garder en mémoire qui est le joueur cible, celui dont on souhaite maximiser l'impact
* @param joueur2 Son opposant, les deux joueur doivent être connus pour dubpliquer leur jetons
* @param mem Paramètre indiquant si l'amélioration de mémoire est active.
......@@ -48,7 +48,7 @@ public class AlgoRechercheMinMax extends AlgoRecherche{
public AlgoRechercheMinMax(int depth, int d_gen, Joueur joueur1, Joueur joueur2, boolean mem){
this.depth = depth;
this.d_gen = d_gen;
//Cet algo ne marche qu'avec des jeux à deux joueurs
//Cet algo ne marche qu'avec des jeux à deux joueurs où alterne les tours car on cher à maximiser l'impact d'un joueur cible un tour sur deux en minimisant l'impact de son opposant le reste du temps
target = joueur1;
opponent = joueur2;
this.mem = mem;
......@@ -87,13 +87,18 @@ public class AlgoRechercheMinMax extends AlgoRecherche{
@Override
public Coup meilleurCoup( Plateau _plateau , Joueur _joueur , boolean _ponder ){
//On part du principe que la partie n'est pas terminée donc qu'il reste au moins un coup
//On part du principe que la partie n'est pas terminée donc qu'il reste au moins un coup sans quoi la méthode ne peux fonctionner car la racine n'a pas de fils
plateau = _plateau;
plateau.sauvegardePosition(0);
//On détermine du point de vue de quel joueur on évalue la valeur du coup
if(target != _joueur){
opponent = target;
target = _joueur;
}
/*
On construit l'arbre des coups récursivement avec une fonction auxiliaire. La même méthode évalue la valeur des feuilles
on les remonte avec l'algo du MinMax
*/
ArbreMinMax explore = new ArbreMinMax();
builder(explore, target, 0);
explore.MinMax(0);
......@@ -106,7 +111,8 @@ public class AlgoRechercheMinMax extends AlgoRecherche{
explore.getfils().get(i).setvalue(MemoireMinMax.eval(coup, ligne, colonne));
}
}
double m = Double.MIN_VALUE;
//On observe les coups de la racine et leur valeur pour choisir au final quel est le meilleur coup pour le joueur cible
double m = (double)Integer.MIN_VALUE;
Coup c = null;
for(int i = 0; i < explore.getfils().size() ; i++){
double n = explore.getfils().get(i).getvalue();
......@@ -115,18 +121,24 @@ public class AlgoRechercheMinMax extends AlgoRecherche{
c = explore.getfils().get(i).getcoup();
}
}
//On restaure le plateau qui a été modifiée pour évaluer la avleur des coups
plateau.restaurePosition(0);
return c;
}
/**
* <div>Fonction récursive de création et de parcours de l'abre des coups possibles</div>
* <div>Fonction récursive de création et de parcours de l'abre des coups possibles. Le parcours se fait en profondeur et on associe
* aux feuilles une valeur estimant la qualité du coup que l'on remontera avec l'algo MinMax</div>
* @param t L'arbre ou le sous-arbre parcouru
* @param currentJoueur Le joueur concerné à ce niveau de pronfondeur
* @param currentDepth Permet de savoir si on approche de la fin de partie et si on doit passer au remplissage des noeuds
*/
private void builder(ArbreMinMax t,Joueur currentJoueur, int currentDepth){
//On commence par mettre le plateau à jour en fonction du coup théorique joué
/*
A chaque appel récursif on arrive sur un nouveau noeud qui représente un coup. On ne conserve pas les parents donc on peut savoir les coups prcédent.
Cependant la méthode de parcours en profondeur préfixe implique que les parents on été visité avant et donc on a pu mémoriser les états de plateau correspondant.
On commence par mettre le plateau à jour en fonction du coup théorique joué
*/
if(currentDepth == 0){
plateau.restaurePosition(0);
}
......@@ -136,8 +148,9 @@ public class AlgoRechercheMinMax extends AlgoRecherche{
plateau.joueCoup(t.getcoup());
plateau.sauvegardePosition(currentDepth);
}
//On crée les nouveau noeuds à partir des coups disponible du point de vue du joueur à ce niveau de l'arbre
//On crée les nouveau noeuds à partir des coups disponible du point de vue du joueur à ce niveau de l'arbre. Il y a 3 cas.
if(plateau.partieTerminee()){
//Si la partie est terminée on est sur une feuille et on attribue directement une valeur au coup en fonction du vainqueur de la partie par rapport au joueur cible de l'algo
Joueur winner = plateau.vainqueur();
if(winner == target){
t.setvalue(1.0);
......@@ -150,10 +163,17 @@ public class AlgoRechercheMinMax extends AlgoRecherche{
}
}
else if (currentDepth == depth){
//Si on atteinte la profondeur d'évaluation demandée on arrete l'eapansion en profondeur et on crée une feuille auquel on associe une valeur estimée en testants des fin de partie aléatoires à partir de ce point
double c = (double)Generator.random_tests(plateau, d_gen, target);
t.setvalue(c);
}
else{
/*
Dans le dernier cas on se situe sur un noeud interne et on crée les nouveau fils à partir des coups possibles.
On recrée la liste des coups possibles à chaque fois pour que l'algo soit adaptable à d'autres jeu. En effet, les coups possibles
peuvent être différents pour les deux joueurs à chaque tour en fonction de comment l'adversaire à joué au tour précédent.
Cela permet notamment un algo unique pour le tictactoe classique et l'ultimate tictactoe.
*/
ArrayList<Coup> coups = plateau.getListeCoups(currentJoueur);
ArrayList<ArbreMinMax> fils = new ArrayList<>();
for(int i=0; i<coups.size();i++){
......
......@@ -71,6 +71,7 @@ public class Arbitre {
}
public void startTournament( int _nbPartie , boolean _trace) {
//La mémorisation ne se fait que si l'un des deux joueurs est une IA utilisant l'algo MinMax et que dans les tournois car il faut jouer plusieurs parties pour qu'elle soit utile
boolean mem = false;
if(joueur1 instanceof JoueurOrdi){
AlgoRecherche algo = ((JoueurOrdi)joueur1).getAlgoRecherche();
......@@ -88,6 +89,8 @@ public class Arbitre {
}
}
}
//On adapte la mémorisation à la taille du plateau
MemoireMinMax.setup(plateau);
double[] nbVictoire = new double[2];
Joueur vainqueur;
......@@ -98,6 +101,7 @@ public class Arbitre {
for (int i = 0 ; i < _nbPartie ; i++ ) {
vainqueur = startNewGame(_trace);
//Si la mémorisation est activée on l'enrichit avec la partie qui vient d'être jouée
if(mem){MemoireMinMax.learn(plateau);}
if ( vainqueur == joueur1 ) nbVictoire[0]++;
......
......@@ -9,18 +9,23 @@ import java.util.ArrayList;
/**
*
* @author senda
* <p>Structure d'arbre pour l'algoritme MinMax.</p><p>Cette structure est récursive et peut être un noeud ou un élément vide.
* Les noeuds référencent un ou plusieurs coups, une valeur évaluant la qualité dues coups et une liste pointant vers d'autrs noeuds qui sont
* ses fils.</p>
*/
public class ArbreMinMax {
//La valeur est stockée dans un double pour povoir est modulée facilement par l'amélioration de mémorisation
protected double value;
//Coup théorique joué à ce noeud
protected Coup coup;
//Si on fait plusieurs coups
protected ArrayList<Coup> coups;
protected ArrayList<ArbreMinMax> fils;
// Les constructeurs :
//L'abre vide est représenté par des attributs vides
public ArbreMinMax(){
}
......@@ -75,7 +80,7 @@ public class ArbreMinMax {
}
//Des choses sans nom :
//Les méthodes de réglage des attributs :
public void setvalue(double value){
this.value = value;
}
......@@ -113,8 +118,14 @@ public class ArbreMinMax {
}
}
//fonctions auxiliaires pour l'algo MinMax
/**
* <div>Fonction déterminant la valeur minimum parmi les noeuds fils</div>
* @return
*/
public double Min(){
double m = Double.MAX_VALUE;
double m = (double)Integer.MAX_VALUE;
int a = this.getfils().size();
for(int i = 0; i < a ; i++){
double n = this.getfils().get(i).getvalue();
......@@ -125,8 +136,12 @@ public class ArbreMinMax {
return m;
}
/**
* <div>Fonction déterminant la valeur maximum parmi les noeuds fils</div>
* @return
*/
public double Max(){
double m = Double.MIN_VALUE;
double m = (double)Integer.MIN_VALUE;
int a = this.getfils().size();
for(int i = 0; i < a ; i++){
double n = this.getfils().get(i).getvalue();
......@@ -137,6 +152,12 @@ public class ArbreMinMax {
return m;
}
/**
* <div>Algorithme du Minmax. L'algo fonctione récursivement, on mémorise la profondeur à laquelle on se situe
* pour savoir si on remonte la valeur maximale où minimale. A la racine on veut toukours le maximum puisqu'on évalue du point
* de vue du joeuru cible. Ensuite la parité de la profondeur permet déterminer le joueur qui s'apprêtre à jouer</div>
* @param c Porofndeur en cours dans le parcours de l'arbre
*/
public void MinMax(int c){
// c = compteur
// Le compteur doit être initialisé à 0 donc pair -> Max, impair -> Min
......
......@@ -42,9 +42,11 @@ public class Generator {
opponent.forceId(i);
Coup coup;
//On mémorise la position du plateau à partir du point d'étude pour le réitialiser à chaque nouevelle simultaion
plateau.sauvegardePosition(99);
for(i=0; i<nb_tests;i++){
while (!plateau.partieTerminee()) {
//Tant que la partie n'est pas terminée on joue un coup aléatoirement
coup = currentPlayer.joue(plateau);
plateau.joueCoup(coup);
......@@ -58,6 +60,7 @@ public class Generator {
}
Joueur vainqueur = plateau.vainqueur();
//Si le joueur cible est vainqueur on incrémente l'évaluation, s'il perd on al décrémente, si le match est nul on ne fait rien
if(vainqueur != null){
if ( vainqueur.getIdJoueur() == target.getIdJoueur() )
c++;
......
......@@ -14,6 +14,9 @@ package tictactoecodingame;
* mais on ne peut pas l'utiliser dès le début, il faut beaucoup de données sinon la précision de la recherche risque dêtre
* dégradée par l'imprécision des coefficients.</p><p>Les coeffiencients sont multipliés à la valeur des coups tentés sur une
* case selon des reglès spécifique pour conserver l'ordre malgré les différences de signes.</p>
* <p>Cette amélioration est incomptablie avec CodinGame dans la fafonc dont elle est implémentée dans la mesure
* où elle utilise plusieurs parties et apporte une modification à la classe arbitre pour pouvoir faire un apprentissage
* progressif dans les parties d'un tournoi.</p>
*/
public class MemoireMinMax {
private static int mode;
......@@ -39,6 +42,12 @@ public class MemoireMinMax {
return seuil;
}
/**
* <div>Fonction d'apprentissage de la mémorisation. On sauvegarde les positions prises par le gagnant et le perdant pour alimenter les coefficients.
* Si la partie est nulle, elle n'apporte pas d'informations pour la mémorisation. Pour chaque case, on ajoute 1 pour chaque partie gagnée ou ce coup est joué
* par le gagnant et on retire 1 si ce coup est joué par le perdant. On divise par le total de parties mémorisées.</div>
* @param grille
*/
public static void learn(Plateau grille){
if(grille.partieNulle()){
return;
......@@ -47,7 +56,7 @@ public class MemoireMinMax {
//Le nombre de partie analysées est conservé pour garder des coefficients entre -1 et 1
state++;
for(int i=0; i<mode; i++){
for(int j=0; j<mode; i++){
for(int j=0; j<mode; j++){
Piece piece = grille.getPiece(new Case(j,i));
if(piece == null){
continue;
......@@ -63,7 +72,15 @@ public class MemoireMinMax {
}
}
/**
* <div>Fonction d'application du coefficient. Cette fonction utilise la note mémorisée pour une case pour moduler la valeur d'un coup.</div>
* @param coup
* @param ligne
* @param colonne
* @return
*/
public static double eval(double coup, int ligne, int colonne){
//On n'utilise la mémoire des parties précédente qu'apèrs un certain nombre de partie jouées sans quoi les données qu'elle fournit ne sont pas significatives
if(state < seuil){
return coup;
}
......
......@@ -12,15 +12,22 @@ public class Player {
public static void main(String args[]) {
//JoueurHumain humain = new JoueurHumain("Humain");
/*
Il faut choisir deux joueurs parmi les possibilités ci-dessous car l'id est différent pour chaque joueur et il
fixe le jeton d'où la nécéssité d'en avoir deux distincts.
*/
// JoueurHumain humain1 = new JoueurHumain("Humain1");
// JoueurHumain humain2 = new JoueurHumain("Humain2");
JoueurOrdi joueurOrdi1 = new JoueurOrdi("Ordi1");
JoueurOrdi joueurOrdi2 = new JoueurOrdi("Ordi2");
//Il faut deux joueurs car l'id fixe le jeton
GrilleTicTacToe9x9 grille = new GrilleTicTacToe9x9();
//Grille de tictactoe classique
GrilleTicTacToe3x3 grille = new GrilleTicTacToe3x3();
//Grille d'ultimate tictactoe
//GrilleTicTacToe9x9 grille = new GrilleTicTacToe9x9();
MemoireMinMax.setup(grille);
AlgoRechercheMinMax minmax = new AlgoRechercheMinMax(3, 10, joueurOrdi1, joueurOrdi2, true);
AlgoRechercheMCTS mcts1000 = new AlgoRechercheMCTS(joueurOrdi2, joueurOrdi1, 1000, Math.sqrt(2), grille.getNbLignes(), grille.getNbColonnes(), false);
......@@ -33,10 +40,10 @@ public class Player {
//Arbitre a = new Arbitre(grille, joueurOrdi , humain );
a.startNewGame(true); // Demarre une partie en affichant la grille du jeu
//a.startNewGame(true); // Demarre une partie en affichant la grille du jeu
// Pour lancer un tournooi de 100 parties en affichant la grille du jeu
//a.startTournament(1000 , false);
// Pour lancer un tournooi de 1000 parties sans afficher la grille du jeu
a.startTournament(1000 , false);
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment