ajout des fichiers necessaires

This commit is contained in:
2024-08-03 18:35:22 +02:00
parent 820036c61c
commit 6c8482e4d2
25 changed files with 138 additions and 67 deletions

View File

@@ -2,17 +2,17 @@ package personnage;
import java.util.UUID;
import IA.*;
import connexion.Channel;
import environnement.Grid;
import environnement.Map;
import types.Mouvement;
import game.connexion.Channel;
import game.environnement.Grid;
import game.environnement.Map;
import personnage.IAQLearning.*;
import personnage.types.Mouvement;
/**
* La classe IAQLearning représente un joueur contrôlé par une Intelligence Artificielle
* utilisant l'algorithme de Q-learning pour prendre des décisions dans un jeu.
*/
public class IAQLearning extends Personnage {
public class IA extends Personnage {
private QLearning qLearning; // L'algorithme de Q-learning utilisé par l'IA.
/**
@@ -22,14 +22,14 @@ public class IAQLearning extends Personnage {
* @param gamma Le facteur de récompense future de l'algorithme de Q-learning.
* @param epsilon Le taux d'exploration de l'algorithme de Q-learning.
*/
public IAQLearning(int[] coordinate, QTable qTable, double alpha, double gamma, double epsilon) {
public IA(int[] coordinate, QTable qTable, double alpha, double gamma, double epsilon, String name) {
super(coordinate); // Appel au constructeur de la classe mère.
// Initialisation de l'algorithme de Q-learning avec les paramètres spécifiés.
this.qLearning = new QLearning(qTable, alpha, gamma, epsilon);
// Attribution d'un nom unique à l'IA.
this.name = "IA : " + UUID.randomUUID();
this.name = (name == null) ? "IA : " + UUID.randomUUID() : name;
}
/**
@@ -38,10 +38,10 @@ public class IAQLearning extends Personnage {
* @param coordinate
* @param qTable
*/
public IAQLearning(int[] coordinate, QTable qTable) {
public IA(int[] coordinate, QTable qTable, String name) {
super(coordinate); // Appel au constructeur de la classe mère.
this.qLearning = new QLearning(qTable, 0.0, 0.0, 0.0);
this.name = "IA : " + UUID.randomUUID();
this.name = (name == null) ? "IA : " + UUID.randomUUID() : name;
}
/**

View File

@@ -0,0 +1,74 @@
package personnage.IAQLearning;
import java.io.Serializable;
import java.util.Objects;
import personnage.types.Mouvement;
/**
* Cette classe permet de sauvegarder chaque action que l'IA
* fait. Il est essentiel pour le bon fonctionnement du programme
* car sans lui, aucun moyen que le programme puisse vérifier
* si dans son {@link QTable}, il contient les informations
* pour le bon fonctionnement. Elle peut être comparée à un
* <strong>tuple({@link State}, {@link Mouvement})</strong>.
*/
public class Actions implements Serializable {
private static final long serialVersionUID = 1L; // la version du serializable
/**
* Cette variable prend l'état du processus, c'est lui qui
* va sauvegarder <strong>la grille, la longueur du serpent
* et ses coordonnées</strong>.
*/
public State state;
/**
* Cette variable prend l'action que le serpent fait. Il
* peut prendre comme action : <strong>HAUT BAS GAUCHE
* DROITE</strong>.
*/
public Mouvement mouvement;
/**
* Le constructeur prend l'état et le mouvement de l'action
* que le serpent fait.
* @param state
* @param mouvement
*/
public Actions(State state, Mouvement mouvement) {
this.mouvement = mouvement;
this.state = state;
}
/**
* Cette fonction prend en paramètre un objet et vérifie si
* cette action est la même que celle en paramètre. Sinon, il retourne
* false.
* @param grid
* @return <pre><code>
* Si l'objet == la classe ou (objet.state == this.state et objet.mouvement == this.mouvement) ->
* retourner true
* Si l'objet == null ou la classe != la classe du paramètre ->
* retourner false
* </code></pre>
*/
@Override
public boolean equals(Object grid) {
if (this == grid) return true;
if (grid == null || getClass() != grid.getClass()) return false;
Actions action = (Actions) grid;
return action.state.equals(this.state) && action.mouvement.equals(this.mouvement);
}
@Override
public int hashCode() {
return Objects.hash(state, mouvement);
}
@Override
public String toString() {
return "Actions{state=" + state + ", mouvement=" + mouvement + '}';
}
}

View File

@@ -0,0 +1,131 @@
package personnage.IAQLearning;
import personnage.types.Mouvement;
/**
* Cette classe représente un algorithme d'apprentissage par renforcement
* utilisant la méthode de Q-learning. Elle permet d'entraîner un agent à prendre
* des décisions dans un environnement basé sur les récompenses reçues.
* <p>
* Elle stocke les informations nécessaires à l'apprentissage, notamment :
* <ol>
* <li>Un tableau de valeurs Q (QTable) qui associe des états et des actions à des valeurs de Q.</li>
* <li>Les paramètres d'apprentissage :
* <ul>
* <li>alpha : le taux d'apprentissage, déterminant dans quelle mesure les nouvelles informations
* modifient les anciennes valeurs de Q.</li>
* <li>gamma : le facteur de récompense future, influençant l'importance des récompenses futures
* par rapport aux récompenses immédiates.</li>
* <li>epsilon : le taux d'exploration, contrôlant dans quelle mesure l'agent explore de nouvelles
* actions par rapport à l'exploitation des actions déjà connues.</li>
* </ul>
* </li>
* </ol>
* L'algorithme de Q-learning fonctionne en ajustant itérativement les valeurs de Q en fonction des récompenses
* reçues par l'agent, dans le but de maximiser les récompenses cumulatives à long terme.
*/
public class QLearning {
/**
* cette variable stocke la liste d'action enregistrer pour ensuite la lire
* et voir toute les actions possibles ou crée de nouvelles actons.
*/
protected QTable qTable;
/**
* la variable alpha est la courbe d'apprentissage. Si il est à 1, il va
* ecraser à chaque fois toute les anciennes actions pour les remplacer
* avec les nouvelles, parcontre si il est à 0, aucune action va pouvoir
* être ecraser.
*/
private double alpha;
/**
* la variable gamma determine l'importance des rewards futur par rapport
* aux rewards actuels. Si il est egal à 1, il va donner beaucoup plus de
* rewards que si il est proche 0.
*/
private double gamma;
/**
* la variable epsilon determine l'importance de l'exploration, plus un
* epsilon est grand, plus il va prendre de choix aléatoire.
*/
private double epsilon;
/**
* ce constructor mets en place toute les informations sur comment va se derouler
* l'apprentissage par renforcement. il va definir un nouveau {@link QTable}, un
* {@link #alpha}, un {@link #gamma} et un {@link #epsilon}.
* @param QTable
* @param alpha
* @param gamma
* @param epsilon
*/
public QLearning(QTable qTable, double alpha, double gamma, double epsilon) {
this.qTable = qTable;
this.alpha = alpha;
this.gamma = gamma;
this.epsilon = epsilon;
}
/**
* cette fonction pourra choisir quelle position il va choisir en
* fonction de epsilon. Il prend un état en paramètre et soit choisi
* un mouvement aléatoire, soit il va chercher dans sa base de donnée
* @param state
* @return <pre><code>
* si epsilon > Math.random ->
* retourner un mouvement aléatoire
*sinon ->
* retourner meilleur mouvement dans la liste
* </code></pre>
*/
public Mouvement chooseMouvements(State state) {
if (Math.random() < epsilon) {
return Mouvement.values()[(int)(Math.random() * Mouvement.values().length)];
} else {
double maxQ = Double.NEGATIVE_INFINITY;
Mouvement bestMouvement = null;
for(Mouvement mouvement : Mouvement.values()) {
double q = qTable.getQValue(state, mouvement);
if (q > maxQ) {
maxQ = q;
bestMouvement = mouvement;
}
}
return bestMouvement;
}
}
/**
* Cette fonction met à jour la valeur Q dans la QTable pour une paire (état, action) donnée.
* Elle utilise l'algorithme de mise à jour Q-learning pour ajuster la valeur Q en fonction des récompenses
* reçues par l'agent.
*
* @param state L'état actuel dans lequel se trouve l'agent.
* @param mouvement L'action effectuée par l'agent dans l'état actuel.
* @param reward La récompense reçue par l'agent pour avoir effectué l'action dans l'état actuel.
* @param nextState L'état suivant vers lequel l'agent se déplace après avoir effectué l'action.
*
* @see QTable#setQValue(State, Mouvement, double) Méthode utilisée pour mettre à jour la valeur Q dans la QTable.
* @see QTable#getQValue(State, Mouvement) Méthode utilisée pour récupérer la valeur Q actuelle d'une paire (état, action) dans la QTable.
*/
public void updateQValue(State state, Mouvement mouvement, double reward, State nextState) {
double q = qTable.getQValue(state, mouvement);
double maxQNext = Double.NEGATIVE_INFINITY;
for(Mouvement nextMouvement : Mouvement.values()) {
double qNext = qTable.getQValue(nextState, nextMouvement);
if (qNext > maxQNext) {
maxQNext = qNext;
}
}
double newQ = q + alpha * (reward + gamma * maxQNext - q);
qTable.setQValue(state, mouvement, newQ);
}
}

View File

@@ -0,0 +1,91 @@
package personnage.IAQLearning;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import personnage.types.Mouvement;
/**
* cette classe permet de sauvegarder chaque action que l'IA
* fait, il est essentiel pour le bon fonctionnement du programme
* car sans lui, aucun moyen que le programme puisse verifier
* si dans son {@link QTable}, il contient les informations
* pour le bon fonctionnement. Elle peut etre comparé à un
* <strong>tuple({@link State}, {@link Mouvement})</strong>.
*/
public class QTable {
/**
* cette variable est utilisé pour stocker toute les informations
* necessaire pour que le bot puisse faire des actions.
*/
private HashMap<Actions, Double> qValues;
/**
* Constructeur de la classe QTabl cree le HashMap qValues.
*/
public QTable() {
qValues = new HashMap<>();
}
/**
* cette fonction renvoie soit la valeur associé à l'action de l'etat
* et du mouvement ou la crée dans le hashmap.
* @param state
* @param action
* @return
*/
public double getQValue(State state, Mouvement mouvement) {
return qValues.getOrDefault(new Actions(state, mouvement), 0.0);
}
/**
* Cette méthode ajoute une valeur Q associée à une paire état-action dans la QTable.
* @param state L'état pour lequel la valeur Q est associée.
* @param mouvement L'action pour laquelle la valeur Q est associée.
* @param value La valeur Q associée à la paire état-action.
*/
public void setQValue(State state, Mouvement mouvement, double value) {
qValues.put(new Actions(state, mouvement), value);
}
/**
* Cette méthode sauvegarde les valeurs Q dans un fichier spécifié.
* @param path le chemin du fichier où sauvegarder les données
*/
public void save(String path) {
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path))) {
oos.writeObject(qValues);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Cette méthode charge les valeurs Q depuis un fichier spécifié.
* @param path le chemin du fichier à partir duquel charger les données
*/
@SuppressWarnings("unchecked")
public void getValues(String path) {
try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path))) {
qValues = (HashMap<Actions, Double>) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
save(path);
}
}
/**
* cette méthode renvoie dans le terminal tout les elements du
* hashmap.
*/
public void printValues() {
for (Map.Entry<Actions, Double> value : qValues.entrySet()) {
System.out.println(value.getKey().toString() + " -> " + value.getValue());
}
}
public HashMap<Actions, Double> getqValues() {
return qValues;
}
}

View File

@@ -0,0 +1,90 @@
package personnage.IAQLearning;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
import game.environnement.Grid;
/**
* La classe Etats est cruciale pour le Q-Learning car c'est elle
* qui permet de stocker toutes les informations et de les comparer
* par la suite. Cette classe peut être comparée à un tuple avec
* toutes les informations nécessaires.
*/
public class State implements Serializable {
private static final long serialVersionUID = 1L; // la version du serializable
/**
* Cette variable stocke la grille du jeu de {@link Map},
* c'est une des choses les plus utiles pour le bon
* fonctionnement du Q-Learning car c'est elle qui sait dans
* quelles conditions il doit se comporter.
*/
public ArrayList<ArrayList<Grid>> grid;
/**
* Cette variable stocke toutes les coordonnées du serpent.
* La première coordonnée est la tête du serpent.
*/
public ArrayList<ArrayList<Integer>> coordinate;
/**
* Constructeur de la classe State.
* @param grid Un tableau 2D représentant la grille du jeu.
* @param coordinate Une liste de tableaux d'entiers représentant les coordonnées.
*/
public State(Grid[][] grid, ArrayList<int[]> coordinate) {
this.coordinate = convertCoordinatesToArrayList(coordinate);
this.grid = convertGridToArrayList(grid);
}
public ArrayList<ArrayList<Grid>> convertGridToArrayList(Grid[][] grid) {
ArrayList<ArrayList<Grid>> arrayList = new ArrayList<>();
for (Grid[] row : grid) {
arrayList.add(new ArrayList<>(Arrays.asList(row)));
}
return arrayList;
}
/**
* Convertit une ArrayList de tableaux d'entiers en ArrayList<ArrayList<Integer>>.
* @param coordinates La ArrayList de tableaux d'entiers à convertir.
* @return Une ArrayList<ArrayList<Integer>> représentant la ArrayList de tableaux d'entiers.
*/
public ArrayList<ArrayList<Integer>> convertCoordinatesToArrayList(ArrayList<int[]> coordinates) {
ArrayList<ArrayList<Integer>> arrayList = new ArrayList<>();
for (int[] row : coordinates) {
ArrayList<Integer> valueArrayList = new ArrayList<>();
for (int value : row) {
valueArrayList.add(value);
}
arrayList.add(valueArrayList);
}
return arrayList;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
State state = (State) o;
return grid.equals(state.grid) && coordinate.equals(state.coordinate);
}
@Override
public int hashCode() {
return Objects.hash(grid, coordinate);
}
@Override
public String toString() {
return "State{grid=" + grid.toString() + ", coordinate=" + coordinate + '}';
}
}

View File

@@ -2,8 +2,8 @@ package personnage;
import java.util.ArrayList;
import environnement.*;
import types.*;
import game.environnement.*;
import personnage.types.*;
/**
* Cette classe est la primitive des classes

View File

@@ -2,9 +2,9 @@ package personnage;
import java.util.Scanner;
import connexion.Channel;
import environnement.Map;
import types.*;
import game.connexion.Channel;
import game.environnement.Map;
import personnage.types.*;
/**
* la classe Player a comme classe parent {@link Personnage}

View File

@@ -3,9 +3,9 @@ package personnage;
import java.util.ArrayList;
import java.util.Random;
import types.*;
import connexion.*;
import environnement.*;
import game.connexion.*;
import game.environnement.*;
import personnage.types.*;
public class Robot extends Personnage {

View File

@@ -0,0 +1,23 @@
package personnage.types;
/**
* cette enumérateur {@link Effects} contient tout les effets
* necessaire pour le bon déroulement du jeu et quelque effets
* amusant qui pousse un peu plus les mecaniques du jeu.
*/
public enum Effect {
/**
* Effet : Réduit la taille du serpent de 1 segment.
*/
DECREASESIZE,
/**
* Effet : Intransposable, entraînant la mort du joueur lors du contact.
*/
IMPASSABLE,
/**
* Effet : Vide, aucun effet.
*/
VOID;
}

View File

@@ -0,0 +1,67 @@
package personnage.types;
import game.environnement.Grid;
/**
* cette enum représente différents types d'objets dans le jeu.
*/
public enum Item implements Grid {
/**
* Mur impassable.
* Effet associé : <pre>{@code types.Effect.IMPASSABLE}</pre>
* utf : null car c'est une autre fonction qui gère le mur.
*/
WALL(Effect.IMPASSABLE, null),
/**
* Zone vide sans effet.
* Effet associé : <pre>{@code types.Effect.VOID}</pre>
* utf : vide
*/
VOID(Effect.VOID, " "),
/**
* Fraise.
* Effet associé : <pre>{@code types.Effect.DECREASESIZE}</pre>
* utf : un drapeau (0x26FF)
*/
FRAISE(Effect.DECREASESIZE, " \u26FF ");
private final Effect EFFECT;
private String utfCode;
/**
* @param effect L'effet associé à l'objet.
*/
Item(Effect effect, String utf) {
this.EFFECT = effect;
this.utfCode = utf;
}
/**
* cette methode donne l'effet associé à l'objet.
*
* @return L'effet associé à l'objet.
*/
@Override
public Effect get() {
return this.EFFECT;
}
@Override
public Item[] getValues() {
return values();
}
@Override
public String getStringCode() {
return this.utfCode;
}
@Override
public void updateStringCode(String textCode) {
this.utfCode = textCode;
}
}

View File

@@ -0,0 +1,63 @@
package personnage.types;
/**
* Cet enumerateur prend en charge tout les mouvements possible
* pour le serpent, il a uniquement la possibilité de se déplacer
* grâce a {@link Mouvement} pour la classe Player et Robot.
*/
public enum Mouvement {
/**
* HAUT prend comme coordonnée (0, -1) pour se déplacer.
* @param x = 0
* @param y = -1
*/
HAUT(0, -1),
/**
* BAS prend comme coordonnée (0, 1) pour se déplacer.
* @param x = 0
* @param y = 1
*/
BAS(0, 1),
/**
* GAUCHE prend comme coordonnée (1, 0) pour se déplacer.
* @param x = -1
* @param y = 0
*/
GAUCHE(-1, 0),
/**
* @DROITE prend comme coordonnée (-1, 0) pour se déplacer.
* @param x = 1
* @param y = 0
*/
DROITE(1, 0);
private final int deltaX;
private final int deltaY;
Mouvement(int deltaX, int deltaY) {
this.deltaX = deltaX;
this.deltaY = deltaY;
}
/**
* Cette fonction prend les coordonnées mis en paramètre et
* modifie avec les coordonnées de l'enum.
* @param coordinate prend principalement les coordonnées du
* personnage
*/
public void updateCoordinate(int[] coordinate) {
coordinate[0] += this.deltaX;
coordinate[1] += this.deltaY;
}
/**
* Cette fonction retourne les coordonnées des valeurs du mouvement.
* @return la liste qui contient [0] = x et [1] = y
*/
public int[] getCoordinate() {
return new int[] {this.deltaX, this.deltaY};
}
}

View File

@@ -0,0 +1,36 @@
package personnage.types;
import game.environnement.Grid;
public enum SnakePart implements Grid {
HEAD(Effect.IMPASSABLE, " \u25CF "),
BODY(Effect.IMPASSABLE, " \u25A1 ");
private final Effect EFFECT;
private String utfCode;
SnakePart(Effect effect, String utf) {
this.EFFECT = effect;
this.utfCode = utf;
}
@Override
public Effect get() {
return this.EFFECT;
}
@Override
public SnakePart[] getValues() {
return values();
}
@Override
public String getStringCode() {
return this.utfCode;
}
@Override
public void updateStringCode(String textCode) {
this.utfCode = textCode;
}
}