mirror of
https://github.com/Cpt-Adok/SNAKE.git
synced 2026-01-25 13:34:07 +00:00
Ia (#4)
* correction de probleme et ajout d'un quick multi * correction de probleme * changer le main * programme fini~ pour l'ia * correction de probleme * Robot (#3) * Robot qui marche pas encore * Robot teubé mais marche bien, marche très bien en local mais problème en réseau * Robot fini mais problème en réseau * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md --------- Co-authored-by: Cpt-Adok <theo.faria@laposte.net> Co-authored-by: Cpt-Adok <126670243+Cpt-Adok@users.noreply.github.com> * correction de probleme et ajout de learn.ser --------- Co-authored-by: Cpt-Adok <theo.faria@laposte.net> Co-authored-by: Cpt-Adok <126670243+Cpt-Adok@users.noreply.github.com>
This commit is contained in:
@@ -1,13 +1,48 @@
|
||||
package environnements;
|
||||
|
||||
import java.io.Serializable;
|
||||
import types.Effect;
|
||||
|
||||
public interface Grid {
|
||||
public default String getName() { return this.toString().toLowerCase(); }
|
||||
public default Effect get() { return null; }
|
||||
public interface Grid extends Serializable {
|
||||
/**
|
||||
* Retourne le nom de la grille.
|
||||
* Par défaut, il convertit la représentation toString de l'objet en minuscules.
|
||||
*
|
||||
* @return le nom de la grille.
|
||||
*/
|
||||
public default String getName() {
|
||||
return this.toString().toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'effet par défaut associé à la grille.
|
||||
* Par défaut, il retourne null.
|
||||
*
|
||||
* @return l'effet par défaut ou null.
|
||||
*/
|
||||
public default Effect get() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour la représentation du code de la grille sous forme de chaîne.
|
||||
*
|
||||
* @param textCode le nouveau code de chaîne à définir.
|
||||
*/
|
||||
public void updateStringCode(String textCode);
|
||||
|
||||
/**
|
||||
* Retourne la représentation du code de la grille sous forme de chaîne.
|
||||
*
|
||||
* @return le code de chaîne.
|
||||
*/
|
||||
public String getStringCode();
|
||||
|
||||
/**
|
||||
* Retourne un tableau de toutes les valeurs possibles de la grille.
|
||||
* Cette méthode peut être utilisée de manière similaire à la méthode values d'une énumération.
|
||||
*
|
||||
* @return un tableau de toutes les valeurs de la grille.
|
||||
*/
|
||||
public Grid[] getValues();
|
||||
}
|
||||
@@ -37,6 +37,7 @@ public class Map {
|
||||
* le constructeur crée une grille "vide" qui
|
||||
* contient uniquement l'enumerateur Items.VOID
|
||||
* avec la longueur et la largeur en paramètre.
|
||||
* <pre><code>grid[largueur][longueur]</code></pre>
|
||||
*
|
||||
* @param longueur pour la grille du jeu
|
||||
* @param largeur pour la grille du jeu
|
||||
@@ -53,6 +54,14 @@ public class Map {
|
||||
this.fillGrid();
|
||||
}
|
||||
|
||||
public void replaceGrid(Grid[][] grid) {
|
||||
if (this.grid.length == grid.length && this.grid[0].length == grid[0].length) {
|
||||
for (int i = 0; i<this.grid.length; i++) {
|
||||
this.grid[i] = grid[i].clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* rempli toute la grille de Items.VOID pour eviter
|
||||
* des problemes de null ou des problemes de
|
||||
|
||||
74
src/IA/Actions.java
Normal file
74
src/IA/Actions.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package IA;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
import 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 + '}';
|
||||
}
|
||||
}
|
||||
130
src/IA/QLearning.java
Normal file
130
src/IA/QLearning.java
Normal file
@@ -0,0 +1,130 @@
|
||||
package IA;
|
||||
|
||||
import 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);
|
||||
}
|
||||
}
|
||||
91
src/IA/QTable.java
Normal file
91
src/IA/QTable.java
Normal file
@@ -0,0 +1,91 @@
|
||||
package IA;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import 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;
|
||||
}
|
||||
}
|
||||
90
src/IA/State.java
Normal file
90
src/IA/State.java
Normal file
@@ -0,0 +1,90 @@
|
||||
package IA;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import environnements.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 + '}';
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,46 @@
|
||||
import connexion.Channel;
|
||||
import connexion.Reseau;
|
||||
import java.io.File;
|
||||
|
||||
import IA.QTable;
|
||||
import environnements.*;
|
||||
import game.Terminal;
|
||||
import personnages.*;
|
||||
import types.Item;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
Personnage.n = 2;
|
||||
Personnage.n = 4;
|
||||
|
||||
Map map = new Map(20, 20);
|
||||
Map map = new Map(12, 22);
|
||||
|
||||
Personnage[] personnages = new Personnage[] {
|
||||
new Player(new int[] {0, 0}, "Philippe Etchebest"),
|
||||
};
|
||||
// lancer en local
|
||||
if (args.length < 2) {
|
||||
Grid[][] grid = map.getGrid();
|
||||
|
||||
|
||||
// map.addObjects(Item.FRAISE, 0, 0);
|
||||
// map.addObjectsRandomize(new Item[] {Item.FRAISE}, 1);
|
||||
QTable qTable = new QTable();
|
||||
qTable.getValues("res" + File.separator + "save" + File.separator + "learn.ser");
|
||||
|
||||
new Terminal(map, personnages).run("channel129", "channel128");
|
||||
// Avant de jouer contre l'ia, vous pouvez essayer de l'entrainer avec la fonction tests.IATest.learnIAvsIA()
|
||||
// il jouera avec lui meme et mettra les sauvegardes dans le dossier learn.ser,
|
||||
|
||||
// Attention lors de l'apprentissage, ne pas couper le processus sinon vous allez perdre toute vos donnees
|
||||
Personnage[] personnages = new Personnage[] {
|
||||
new Player(new int[] {2, 2}, "Philippe Etchebest"),
|
||||
new Player(new int[] {grid[0].length - 3, grid.length - 3}, "Luke Skywalker"),
|
||||
// new Robot("Robot", new int[] {grid[0].length - 3, grid.length - 3}),
|
||||
// new IAQLearning(new int[] {grid[0].length - 3, grid.length - 3),
|
||||
};
|
||||
|
||||
// map.addObjectsRandomize(new Item[] {Item.FRAISE, Item.WALL}, 2);
|
||||
// map.addObjects(Item.FRAISE, 2, 2);
|
||||
|
||||
new Terminal(map, personnages).run();
|
||||
}
|
||||
// lancer en ligne
|
||||
else {
|
||||
Personnage[] personnages = new Personnage[] {
|
||||
new Player(new int[] {0, 0}, "Philippe Etchebest"),
|
||||
};
|
||||
|
||||
new Terminal(map, personnages).run(args[0], args[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
99
src/Personnages/IAQLearning.java
Normal file
99
src/Personnages/IAQLearning.java
Normal file
@@ -0,0 +1,99 @@
|
||||
package personnages;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import IA.*;
|
||||
import connexion.Channel;
|
||||
import environnements.Grid;
|
||||
import environnements.Map;
|
||||
import 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 {
|
||||
private QLearning qLearning; // L'algorithme de Q-learning utilisé par l'IA.
|
||||
|
||||
/**
|
||||
* @param coordinate Les coordonnées initiales de l'IA sur la grille de jeu.
|
||||
* @param qTable La table Q utilisée par l'IA pour stocker les valeurs Q.
|
||||
* @param alpha Le taux d'apprentissage de l'algorithme de Q-learning.
|
||||
* @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) {
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* ce constructeur est uniquement pour lancer le programme en tant que joueur,
|
||||
* il n'apprendra rien du tout dans cette configuration.
|
||||
* @param coordinate
|
||||
* @param qTable
|
||||
*/
|
||||
public IAQLearning(int[] coordinate, QTable qTable) {
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* cette classe obtient l'état actuel de l'IA à partir de la grille de jeu.
|
||||
*
|
||||
* @param grid La grille de jeu.
|
||||
* @return L'état actuel de l'IA.
|
||||
*/
|
||||
public State getCurrentState(Grid[][] grid) {
|
||||
return new State(grid, this.getCoordinate());
|
||||
}
|
||||
|
||||
/**
|
||||
* cette classe sélectionne le meilleur mouvement à effectuer dans l'état actuel selon l'algorithme de Q-learning.
|
||||
*
|
||||
* @param state L'état actuel de l'IA.
|
||||
* @return Le meilleur mouvement à effectuer.
|
||||
*/
|
||||
public Mouvement bestMouvement(State state) {
|
||||
return qLearning.chooseMouvements(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* cette classe reçoit une récompense pour l'action effectuée dans l'état actuel et met à jour la valeur Q correspondante.
|
||||
*
|
||||
* @param state L'état actuel de l'IA.
|
||||
* @param mouvement Le mouvement effectué dans l'état actuel.
|
||||
* @param reward La récompense reçue pour l'action effectuée.
|
||||
* @param nextState L'état suivant vers lequel l'IA se déplace après avoir effectué l'action.
|
||||
*/
|
||||
public void receiveReward(State state, Mouvement mouvement, double reward, State nextState) {
|
||||
qLearning.updateQValue(state, mouvement, reward, nextState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean round(Map map, String channel) {
|
||||
Mouvement mouvement = this.bestMouvement(this.getCurrentState(map.getGrid()));
|
||||
this.moveSnake(mouvement);
|
||||
|
||||
int[] coordinate = this.getHeadCoordinate();
|
||||
|
||||
if (channel != null) Channel.envoyerMessage(mouvement);
|
||||
if(map.isGameOver(coordinate) || this.applyEffects(map.getEffect(coordinate))) return true;
|
||||
map.deleteItems(coordinate);
|
||||
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
this.increaseRound();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
160
src/Personnages/Robot.java
Normal file
160
src/Personnages/Robot.java
Normal file
@@ -0,0 +1,160 @@
|
||||
package personnages;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
|
||||
import types.*;
|
||||
import environnements.*;
|
||||
import connexion.*;
|
||||
|
||||
public class Robot extends Personnage {
|
||||
|
||||
Map m;
|
||||
Mouvement move;
|
||||
String name;
|
||||
|
||||
public Robot(String name, int[] coordinate) {
|
||||
super(coordinate);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**Fonction commune aux sous-classes de Personnage
|
||||
* permettant le renvoi d'un mouvement pour chacun.
|
||||
*/
|
||||
|
||||
@Override
|
||||
public boolean round(Map map, String channel){
|
||||
this.m = map;
|
||||
|
||||
this.move=this.compare(this.choix(),this.getHeadCoordinate());
|
||||
// System.out.println("Mouvement choisi : "+this.move);
|
||||
this.moveSnake(move);
|
||||
|
||||
int[] coordinate = this.getHeadCoordinate();
|
||||
if (channel != null) Channel.envoyerMessage(this.move);
|
||||
|
||||
if(map.isGameOver(coordinate) || this.applyEffects(map.getEffect(coordinate))) return true;
|
||||
|
||||
map.deleteItems(coordinate);
|
||||
this.increaseRound();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accès à la variable move
|
||||
* @return Mouvement
|
||||
*/
|
||||
public Mouvement getMove(){
|
||||
if (this.move!=null){
|
||||
return move;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permet de savoir si une case de la map est vide ou si elle est impassable
|
||||
* @param x coordonnée longueur
|
||||
* @param y coordonnée largeur
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean estPossible(int x,int y){
|
||||
Grid [][] grille=this.m.getGrid();
|
||||
if (x>=0 && x<grille.length && y>=0 && y<grille[0].length){
|
||||
if (m.getEffect(this.creerTab(x, y))!=Effect.IMPASSABLE){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fonction pour éviter les répétitions
|
||||
* Permet de créer un tableau à partir de
|
||||
* @param x et
|
||||
* @param y
|
||||
* @return int []
|
||||
*/
|
||||
public int [] creerTab(int x,int y){
|
||||
int [] t=new int [] {x,y};
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifie les cases présentes autour de la tête du serpent
|
||||
* @param co coordonnées de la tête
|
||||
* @return ArrayList<int []>, ArrayList des positions autour
|
||||
*/
|
||||
public ArrayList<int []> casesAutour(int [] co){
|
||||
ArrayList<int []> autour=new ArrayList<>();
|
||||
int x=co[0]; // x= ligne
|
||||
int y=co[1]; // y= colonne
|
||||
autour.add(creerTab(x+1, y));
|
||||
autour.add(creerTab(x-1, y));
|
||||
autour.add(creerTab(x, y+1));
|
||||
autour.add(creerTab(x, y-1));
|
||||
return autour;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permet d'identifier les cases valables parmi les voisines de la tête du serpent
|
||||
* @param co
|
||||
* @return ArrayList<int[]> regroupant les cases voisines qui sont jouables sans mourir
|
||||
*/
|
||||
|
||||
public ArrayList<int []> coupsPossibles(int [] co) {
|
||||
ArrayList<int []> coupsValables=new ArrayList<int []> ();
|
||||
ArrayList<int []> autour=casesAutour(co);
|
||||
for (int [] e:autour){
|
||||
if (this.estPossible(e[0], e[1])){
|
||||
coupsValables.add(e);
|
||||
}
|
||||
}
|
||||
return coupsValables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Décision finale aléatoire du coup parmi ceux possibles
|
||||
* @return int [] des coordonnées du coup
|
||||
*/
|
||||
|
||||
public int [] choix(){
|
||||
Random r=new Random();
|
||||
ArrayList <int[]> cases=coupsPossibles(this.getHeadCoordinate());
|
||||
if (cases.size()==0){
|
||||
return this.suicide();
|
||||
}
|
||||
int [] choix=cases.get(r.nextInt(cases.size()));
|
||||
return choix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permet de mettre fin à la partie quand le serpent est coincé, et que aucun n'est valable
|
||||
* @return int [] des coordonnées du coup
|
||||
*/
|
||||
|
||||
public int [] suicide(){
|
||||
ArrayList <int []> murs=this.casesAutour(getHeadCoordinate());
|
||||
Random r=new Random();
|
||||
return murs.get(r.nextInt(4));
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparaison des coordonnées de la tête par rapport à celle du coup
|
||||
* @param t coup
|
||||
* @param t2 tête
|
||||
* @return Mouvement pour jouer le coup
|
||||
*/
|
||||
|
||||
public Mouvement compare(int[] t,int[] t2){
|
||||
if (t[0]>t2[0]){
|
||||
return Mouvement.DROITE;
|
||||
}else if (t[0]<t2[0]){
|
||||
return Mouvement.GAUCHE;
|
||||
}else if (t[1]<t2[1]){
|
||||
return Mouvement.HAUT;
|
||||
}else if (t[1]>t2[1]){
|
||||
return Mouvement.BAS;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
package connexion;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import environnements.*;
|
||||
import types.Item;
|
||||
import types.Mouvement;
|
||||
import personnages.Personnage;
|
||||
|
||||
@@ -12,12 +9,9 @@ public class Channel extends Personnage {
|
||||
private static Reseau adversaire;
|
||||
private String channel;
|
||||
|
||||
private Map map;
|
||||
|
||||
public Channel(Map map, String channel, String autreChannel) {
|
||||
super(new int [] {map.getGrid()[0].length - 1, map.getGrid().length - 1});
|
||||
|
||||
this.map = map;
|
||||
this.name = autreChannel;
|
||||
|
||||
this.channel = channel;
|
||||
|
||||
@@ -28,9 +28,10 @@ public class Display {
|
||||
|
||||
public static void printMap(Grid[][] map) {
|
||||
for (int i = 0; i<map.length; i++) {
|
||||
if (i > 0 && map.length > i+1) System.out.print(" ");
|
||||
for(int k = 0; k<map[0].length; k++) {
|
||||
if (map[i][k] == Item.WALL) printWall(map, k, i);
|
||||
print(map[i][k]);
|
||||
print(map[i][k]);
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
@@ -52,7 +53,10 @@ public class Display {
|
||||
};
|
||||
|
||||
for(Wall value : Wall.values()) {
|
||||
if(value.isEqual(position)) map[y][x].updateStringCode(value.getAscii());
|
||||
if(value.isEqual(position)) {
|
||||
map[y][x].updateStringCode(value.getAscii());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
package display;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public enum Wall {
|
||||
ALL("\u2550\u256C\u2550", new boolean[] {true, true, true, true}),
|
||||
AUCUN("\u2550\u256C\u2550", new boolean[] {false, false, false, false}),
|
||||
|
||||
HAUT("\u2551 ", new boolean[] {true, false, false, false}),
|
||||
BAS("\u2551 ", new boolean[] {false, true, false, false}),
|
||||
|
||||
GAUCHE("\u2550\u2550\u2550", new boolean[] {false, false, true, false}),
|
||||
DROITE("\u2550\u2550\u2550", new boolean[] {false, false, false, true}),
|
||||
|
||||
HAUT_BAS("\u2551", new boolean[] {true, true, false, false}),
|
||||
GAUCHE_DROITE("\u2550\u2550\u2550", new boolean[] {false, false, true, true}),
|
||||
|
||||
HAUT_GAUCHE("\u255D ", new boolean[]{true, false, true, false}),
|
||||
HAUT_DROITE("\u255A", new boolean[]{true, false, false, true}),
|
||||
|
||||
BAS_GAUCHE("\u2557 ", new boolean[]{false, true, true, false}),
|
||||
BAS_DROITE("\u2554", new boolean[] {false, true, false, true}),
|
||||
|
||||
HAUT_BAS_DROITE("\u2560", new boolean[] {true, true, false, true}),
|
||||
HAUT_BAS_GAUCHE("\u2563", new boolean[] {true, true, true, false}),
|
||||
|
||||
HAUT_GAUCHE_DROITE("\u2550\u2569\u2550", new boolean[] {true, false, true, true}),
|
||||
BAS_GAUCHE_DROITE("\u2550\u2566\u2550", new boolean[] {false, true, true, true});
|
||||
GAUCHE_DROITE("\u2550\u2550\u2550", new boolean[] {false, false, true, true}),
|
||||
|
||||
HAUT_GAUCHE("\u255D ", new boolean[]{true, false, true, false}),
|
||||
HAUT_DROITE(" \u255A", new boolean[]{true, false, false, true}),
|
||||
|
||||
BAS_GAUCHE("\u2557 ", new boolean[]{false, true, true, false}),
|
||||
BAS_DROITE(" \u2554", new boolean[] {false, true, false, true}),
|
||||
|
||||
HAUT_BAS_DROITE("\u2560", new boolean[] {true, true, false, true}),
|
||||
HAUT_BAS_GAUCHE("\u2563", new boolean[] {true, true, true, false}),
|
||||
|
||||
HAUT_GAUCHE_DROITE("\u2569\u2550\u2550", new boolean[] {true, false, true, true}),
|
||||
BAS_GAUCHE_DROITE("\u2566\u2550\u2550", new boolean[] {false, true, true, true});
|
||||
|
||||
private final boolean[] POSITION;
|
||||
private final String ASCII;
|
||||
@@ -34,7 +38,14 @@ public enum Wall {
|
||||
}
|
||||
|
||||
public boolean isEqual(boolean[] position) {
|
||||
return Arrays.equals(this.POSITION, position);
|
||||
boolean[] positionAscii = this.getPosition();
|
||||
|
||||
for (int i = 0; i < position.length; i++) {
|
||||
if (positionAscii[i] != position[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean[] getPosition() {
|
||||
|
||||
149
src/tests/IATest.java
Normal file
149
src/tests/IATest.java
Normal file
@@ -0,0 +1,149 @@
|
||||
package tests;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import IA.QTable;
|
||||
import IA.State;
|
||||
import environnements.Grid;
|
||||
import environnements.Map;
|
||||
import personnages.IAQLearning;
|
||||
import personnages.Personnage;
|
||||
import types.Mouvement;
|
||||
|
||||
public class IATest {
|
||||
private final static String path = "res" + File.separator +
|
||||
"save" + File.separator +
|
||||
"learn.ser";
|
||||
|
||||
public static void learnIA() {
|
||||
double alpha = 0.1;
|
||||
double gamma = 0.9;
|
||||
double epsilon = 1.0;
|
||||
double decay_rate = 0.995;
|
||||
double minEpsilon = 0.01;
|
||||
|
||||
int totalEpisodes = 1000;
|
||||
|
||||
Personnage.n = 2;
|
||||
|
||||
for(int episode = 0; episode < totalEpisodes; episode++) {
|
||||
QTable qTable = new QTable();
|
||||
IAQLearning iaqLearning = new IAQLearning(new int[] {0, 0}, qTable, alpha, gamma, epsilon);
|
||||
Map map = new Map(20, 20);
|
||||
|
||||
qTable.getValues(path);
|
||||
|
||||
while (true) {
|
||||
Map mapIA = new Map(map.getGrid()[0].length, map.getGrid().length);
|
||||
mapIA.replaceGrid(map.getGrid());
|
||||
|
||||
map.placePersonnages(iaqLearning);
|
||||
|
||||
State currentState = iaqLearning.getCurrentState(map.getGrid());
|
||||
Mouvement mouvement = iaqLearning.bestMouvement(currentState);
|
||||
|
||||
iaqLearning.moveSnake(mouvement);
|
||||
|
||||
int[] coordinate = iaqLearning.getHeadCoordinate();
|
||||
|
||||
if(map.isGameOver(coordinate) || iaqLearning.applyEffects(map.getEffect(coordinate))) {
|
||||
iaqLearning.receiveReward(currentState, mouvement, -1.0, currentState);
|
||||
break;
|
||||
}
|
||||
|
||||
mapIA.placePersonnages(iaqLearning);
|
||||
|
||||
State nextState = iaqLearning.getCurrentState(mapIA.getGrid());
|
||||
|
||||
iaqLearning.receiveReward(currentState, mouvement, 0.1, nextState);
|
||||
iaqLearning.increaseRound();
|
||||
|
||||
mapIA.clearMap();
|
||||
map.clearMap();
|
||||
}
|
||||
|
||||
qTable.save(path);
|
||||
|
||||
epsilon = Math.max(minEpsilon, epsilon * decay_rate);
|
||||
System.out.println("Episode : " + episode + " | Robot 1 States : " + qTable.getqValues().size());
|
||||
}
|
||||
}
|
||||
|
||||
public static void learnIAvsIA() {
|
||||
double alpha = 0.1;
|
||||
double gamma = 0.9;
|
||||
|
||||
double[] epsilon = new double[] {1.0,};
|
||||
|
||||
double decay_rate = 0.995;
|
||||
double minEpsilon = 0.01;
|
||||
|
||||
int totalEpisodes = 1000;
|
||||
|
||||
Personnage.n = 4;
|
||||
|
||||
for (int episode = 0; episode < totalEpisodes; episode++) {
|
||||
QTable qTable = new QTable();
|
||||
|
||||
IAQLearning[] iaqLearnings = new IAQLearning[] {
|
||||
new IAQLearning(new int[] {2, 2}, qTable, alpha, gamma, epsilon[0]),
|
||||
new IAQLearning(new int[] {9, 19}, qTable, alpha, gamma, epsilon[1])
|
||||
};
|
||||
|
||||
Map map = new Map(12, 22);
|
||||
|
||||
boolean isGameOver = false;
|
||||
|
||||
qTable.getValues(path);
|
||||
|
||||
while(true) {
|
||||
for (int i = 0; i < iaqLearnings.length; i++) {
|
||||
IAQLearning iaqLearning = iaqLearnings[i];
|
||||
|
||||
Grid[][] gridMap = map.getGrid();
|
||||
Map mapIA = new Map(gridMap[0].length, gridMap.length);
|
||||
mapIA.replaceGrid(gridMap);
|
||||
|
||||
for (IAQLearning value : iaqLearnings) {
|
||||
map.placePersonnages(value);
|
||||
}
|
||||
|
||||
State currentState = iaqLearning.getCurrentState(map.getGrid());
|
||||
Mouvement mouvement = iaqLearning.bestMouvement(currentState);
|
||||
|
||||
iaqLearning.moveSnake(mouvement);
|
||||
|
||||
int[] coordinate = iaqLearning.getHeadCoordinate();
|
||||
|
||||
for (int[] snakeCoordinate : iaqLearnings[(i + 1) % 2].getCoordinate()) {
|
||||
if (coordinate[0] == snakeCoordinate[0] && coordinate[1] == snakeCoordinate[1]) {
|
||||
iaqLearning.receiveReward(currentState, mouvement, -10.0, currentState);
|
||||
iaqLearnings[(i + 1) % 2].receiveReward(currentState, mouvement, 10.0, currentState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mapIA.placePersonnages(iaqLearning);
|
||||
|
||||
State nextState = iaqLearning.getCurrentState(mapIA.getGrid());
|
||||
iaqLearning.receiveReward(currentState, mouvement, -0.1, nextState);
|
||||
|
||||
iaqLearning.increaseRound();
|
||||
|
||||
mapIA.clearMap();
|
||||
map.clearMap();
|
||||
}
|
||||
|
||||
if(isGameOver) break;
|
||||
}
|
||||
|
||||
qTable.save(path);
|
||||
|
||||
for (int i = 0; i < epsilon.length; i++) {
|
||||
epsilon[i] = Math.max(minEpsilon, epsilon[i] * decay_rate);
|
||||
}
|
||||
|
||||
System.out.println("Episode: " + episode + " | Robot 1 States: " + qTable.getqValues().size());
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/tests/QTableTest.java
Normal file
63
src/tests/QTableTest.java
Normal file
@@ -0,0 +1,63 @@
|
||||
package tests;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import IA.QTable;
|
||||
import IA.State;
|
||||
import environnements.Grid;
|
||||
import types.Mouvement;
|
||||
|
||||
public class QTableTest {
|
||||
private final static String path = "res" + File.separator +
|
||||
"save" + File.separator +
|
||||
"test.ser";
|
||||
|
||||
public static void searchValue() {
|
||||
QTable qTable = new QTable();
|
||||
State state = new State(new Grid[3][3], new ArrayList<>());
|
||||
Mouvement mouvement = Mouvement.BAS;
|
||||
|
||||
qTable.setQValue(state, mouvement, 10.2);
|
||||
|
||||
qTable.printValues();
|
||||
|
||||
System.out.println(qTable.getQValue(state, mouvement)); // Devrait retourner 10.2
|
||||
}
|
||||
|
||||
public static void writeValueFile() {
|
||||
QTable qTable = new QTable();
|
||||
State state = new State(new Grid[3][3], new ArrayList<>());
|
||||
qTable.setQValue(state, Mouvement.BAS, 10.3);
|
||||
qTable.save(path); // Devrait sauvegarder dans test.ser le state avec la valeur 10.3
|
||||
}
|
||||
|
||||
public static void searchValueFile() {
|
||||
QTable qTable = new QTable();
|
||||
qTable.getValues(path);
|
||||
|
||||
State state = new State(new Grid[3][3], new ArrayList<>());
|
||||
qTable.printValues();
|
||||
System.out.println(qTable.getQValue(state, Mouvement.BAS)); // Devrait retourner 10.3
|
||||
}
|
||||
|
||||
public static void getSaveValue() {
|
||||
writeValueFile();
|
||||
searchValueFile();
|
||||
}
|
||||
|
||||
public static void getRealInformation() {
|
||||
State state = new State(new Grid[3][3], new ArrayList<>(Arrays.asList(new int[] {1, 1})));
|
||||
Mouvement mouvement = Mouvement.GAUCHE;
|
||||
|
||||
QTable qTableSend = new QTable();
|
||||
QTable qTableReceived = new QTable();
|
||||
|
||||
qTableSend.setQValue(state, mouvement, 102.0);
|
||||
qTableSend.save(path);
|
||||
|
||||
qTableReceived.getValues(path);
|
||||
System.out.println(qTableReceived.getQValue(state, mouvement));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user