* 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:
Loïc GUEZO
2024-05-26 23:33:12 +02:00
committed by GitHub
parent 79178886b6
commit 6cced603df
20 changed files with 1093 additions and 43 deletions

View File

@@ -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();
}

View File

@@ -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
View 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
View 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
View 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
View 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 + '}';
}
}

View File

@@ -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]);
}
}
}

View 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
View 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;
}
}

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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
View 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
View 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));
}
}