mirror of
https://github.com/Cpt-Adok/SNAKE.git
synced 2026-01-25 03:34:05 +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:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,4 +4,6 @@
|
||||
#le dossier binary pour séparer et pas importer les .class qui sont inutile a Github.
|
||||
bin/
|
||||
|
||||
# res/video/
|
||||
|
||||
*.class
|
||||
6
Makefile
6
Makefile
@@ -13,6 +13,10 @@ LIB_DIR = lib
|
||||
|
||||
JAR = $(LIB_DIR)/*
|
||||
|
||||
#Arguments pour java
|
||||
channel = $(arg1)
|
||||
adversaire = $(arg2)
|
||||
|
||||
# main
|
||||
all: clean $(MAIN_FILE) run
|
||||
|
||||
@@ -23,7 +27,7 @@ $(BIN_DIR)/$(MAIN_FILE).class : $(SRC_DIR)/$(MAIN_FILE).java
|
||||
$(JAVAC) -d $(BIN_DIR) -sourcepath $(SRC_DIR) -classpath $(JAR) $<
|
||||
|
||||
run:
|
||||
java -cp $(BIN_DIR) $(MAIN_FILE)
|
||||
java -cp $(BIN_DIR) $(MAIN_FILE) $(channel) $(adversaire)
|
||||
|
||||
clean:
|
||||
@rm -rf $(BIN_DIR)
|
||||
115
README.md
115
README.md
@@ -20,6 +20,119 @@ C'est un projet de fin de Licence L1 en Informatique à UPEC, la création d'un
|
||||
|
||||
- La partie **PROBLÈME ET SOLUTION RENCONTRÉES**
|
||||
|
||||
## AVANT TOUT : Comment lancer le jeu.
|
||||
|
||||
Pour lancer le jeu, nous pouvons utiliser les addons de vscode en appuyant sur ***run*** sur la methode **main**.
|
||||
|
||||
Sinon vous pouvez utiliser le Makefile, il va stocker tout les fichiers `.class` dans un fichier **bin**. Nous pouvons le re-utiliser plus tard grâce à une autre commande sur le Makefile.
|
||||
|
||||
Les commandes pour le makefile :
|
||||
|
||||
- `Make` : il va lancer la compilation et va lancer l'interpréteur java (il va aussi nettoyer le fichier bin avant).
|
||||
|
||||
- `Make clean` : il va supprimer le fichier bin.
|
||||
|
||||
- `Make run` : il va uniquement lancer l'interpréteur java (Attention à ne pas supprimer le bin avant)
|
||||
|
||||
Pour lancer le jeu en multijoueur avec Makefile (mot1 et mot2 sont des mots à changer en fonction du canal):
|
||||
- `Make channel=mot1 adversaire=mot2` : compile tout le programme et le lance en multijoueur.
|
||||
- `Make run channel=mot1 adversaire=mot2` : lance uniquement le programme en multijoueur.
|
||||
|
||||
Sur windows, vous avez une autre option que personnellement je vous encourage, c'est l'utilisation du `run.bat`. Ce programme va lancer le Makefile mais aussi le faite de mettre l'utf-8 sur la session. Si vous utilisez pas le `run.bat`, vous aurez possiblement certains problèmes avec certains caractères qui seront remplacés par des "?", pour lancer le programme run.bat (mot1 et mot2 sont des mots à changer en fonction du canal):
|
||||
|
||||
- `run.bat mot1 mot2` : il va uniquement lancer `Make channel=mot1 adversaire=mot2` et en même temps lancer `chcp 65001` qui va mettre l'utf-8.
|
||||
|
||||
|
||||
# JEU et GAMEPLAY
|
||||
|
||||
Ce jeu est un 1vs1 snake tactique tour par tour avec une gestion de mur et de fruits (que l'on peut ajouter aléatoirement ou en le directement en le placant par x et y), nous pouvons se déplacer avec les touches **z q s d ou/et w a s d**, le jeu se termine quand l'un des 2 snake meurt soit en foncant dans un corps, soit par un mur.
|
||||
|
||||
le programme pour le joueur recupère la touche envoyé et le transforme en ascii et une autre fonction le prend et le change en mouvement.
|
||||
|
||||

|
||||
|
||||
Nous pouvons changer quelque settings comme le nombre de tour avant que la taille du snake s'accumule en faisant `Personnage.n = n`.
|
||||
|
||||
# RÉSEAU ET COMMUNICATION
|
||||
|
||||
## - Channel
|
||||
|
||||
Channel est le module d'échange entre la machine de l'utilisateur et son adversaire via le site Padiflac.
|
||||
Nous l'avons définie comme une sous-classe de Personnage.
|
||||
|
||||
Pour communiquer, elle reprend essentiellement des méthodes de Réseau, comme sendContent ou getLastedContent. Son travail est donc en grande partie consacré à la conversion de ce qui est reçu et de ce qui va être envoyé.
|
||||
|
||||
Le channel récupère les données contenues sur le channel qui y est dédié (le sien) et les représente dans le jeu du joueur local après conversion. Quand c'est au tour de celui-ci de jouer, il envoie le coup sur le channel adversaire et le tranforme en direction lisible, pour que celui-ci puisse l'utiliser.
|
||||
|
||||
Nous avons testé Channel à distance sur 2 machines et la partie s'est déroulée correctement.
|
||||
|
||||
|
||||
# IA (Q-Learning)
|
||||
|
||||
## - Explications
|
||||
|
||||
Le Q-Learning ou l'apprentissage par renforcement est un type d'apprentissage où le personnage ou autre apprend par une base de donnée de tout ce qu'il a réussi dans le passé. Il apprend en sauvegardant dans sa base de donnée toute les actions qu'il a pu faire au long de son apprentissage.
|
||||
|
||||
Il y a 2 types de temps dans son apprentissage :
|
||||
|
||||
- La phase d'exploration
|
||||
- La phase d'apprentissage
|
||||
|
||||
### La phase d'exploration
|
||||
|
||||
La phase d'exploration se passe le plus possible au début de son apprentissage, il va tester tout les actions aléatoire qu'il a en dispositions. Il va souvent se planter et il va sauvegarder toute sa progression.
|
||||
|
||||
### La phase d'apprentissage
|
||||
|
||||
La phase d'apprentissage est souvent beaucoup plus long dans son apprentissage, il va tester des mécaniques qu'il a apprises et essayer un max possible de faire des actions qui sont dans sa base de donnée.
|
||||
|
||||
## - Calcul et Compréhension
|
||||
|
||||
$$Q(s_t, a_t) = Q(s_t, a_t) + \alpha * (R_t + \gamma * \max(Q(s_{t+1}, a)) - Q(s_t, a_t))$$
|
||||
|
||||
Ce calcul sera la valeur de toutes les actions que l'IA va enregistrer dans sa base de donnée, il y aura toutes les informations comme la position du snake ou la grille du jeu puis ce calcul pour définir la "fiabilité" du coup.
|
||||
|
||||
- $Q(s_t, a_t)$ : est la valeur de Q actuelle, il contient $s_t$ qui l'état et $a_t$ qui est l'action de Q.
|
||||
|
||||
- $\alpha$ : est le taux d'apprentissage, c'est lui qui détermine si on doit écraser les valeurs ou non.
|
||||
- $R_t$ : est la récompense de l'action, si c'est une bonne action ou non.
|
||||
- $\gamma$ : est l'importance des futures récompenses.
|
||||
- $\max(Q(s_{t+1}, a))$ : est la valeur maximale de Q du prochain tour parmi toute sa base de donnée.
|
||||
|
||||
## Resultat :
|
||||
|
||||

|
||||
|
||||
Dans cette vidéo, l'ia s'est entrainé pendant 15min tout seul et il a trouvé le meilleur chemin selon lui jusqu'à sa derniere erreur sur son apprentissage.
|
||||
si je l'apprennais encore un peu plus, il pourra rester le plus longtemps possible.
|
||||
|
||||
# IA (sans Q-Learning)
|
||||
|
||||
Nous avons rédigé une deuxième IA, beaucoup plus simple, pour prévenir des problèmes rencontrés avec la première.
|
||||
|
||||
Celle-ci repose sur des mécaniques très simples.
|
||||
Dans un premier temps, elle analyse les cases situées autour de sa tête à une distance de une seule case. Elle détermine ensuite lesquelles sont des coups possibles et n'entrainent pas une mort directe. Pour faire le choix final, elle utilise la méthode nextInt(index) de Random.
|
||||
Quand elle est coincée et qu'aucune des cases l'entourant n'est un choix possible, elle gènère à nouveau un choix aléatoire parmi les 4 cases l'entourant.
|
||||
|
||||
Les choix qu'elle fait sont donc partiellement aléatoires et évitent essentiellement une mort au coup suivant.
|
||||
|
||||
# GRAPHIQUE
|
||||
|
||||
Nous avons utiliser le terminal pour faire notre parti graphique avec de l'utf-8, il est très important d'activer l'utf-8 sur les terminaux avant de lancer le jeu car vous pourrez avoir des problèmes comme des "?".
|
||||
|
||||
# PROBLÈME ET SOLUTION RENCONTRÉES
|
||||
|
||||
## - Impressions FARIA Théo:
|
||||
|
||||
Durant ce projet j'ai parfois été complètement perdu par certaines méthodes très avancées utilisées par mon camarade.
|
||||
Cependant, grâce à lui j'ai énormément appris et son aide m'a souvent été précieuse. Je lui suis très reconnaissant de sa patience et de sa volonté de m'inclure malgré tout. Il a réussi à me déléguer des taches plus simples et à m'expliquer ce que lui faisait.
|
||||
|
||||
Je trouve notre projet bien construit, utilisant des moyens adaptés à chacuns des points du jeu. La division des classes est pertinente et les commentaires apportent une grande plus-value à ceux qui voudrait l'utiliser.
|
||||
|
||||
## - Impressions GUEZO Loïc
|
||||
|
||||
Durant ce projet, j'ai pu tester et apprendre de nouvelles choses. J'ai pu aussi "tester" à gérer une "mini équipe" (on etait que 2). Mon camarade s'est quand même bien débrouillé comparé à notre différence de penser et comment gérer les problèmes.
|
||||
|
||||
# CRÉDITS
|
||||
|
||||
Ce projet à été réalisé par FARIA Théo et GUEZO Loïc.
|
||||
Ce projet à été réalisé par GUEZO Loïc et FARIA Théo.
|
||||
|
||||
BIN
res/save/learn.ser
Normal file
BIN
res/save/learn.ser
Normal file
Binary file not shown.
BIN
res/video/ia_solo_15min_apprentissage.gif
Normal file
BIN
res/video/ia_solo_15min_apprentissage.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 816 KiB |
4
run.bat
4
run.bat
@@ -5,7 +5,7 @@ set "error_file=error.txt"
|
||||
REM ceci est pour mettre un batch en utf-8 car sinon, nous aurons des problèmes avec les caractères.
|
||||
chcp 65001
|
||||
|
||||
make 2> %error_file%
|
||||
make channel=%1 adversaire=%2 2> %error_file%
|
||||
|
||||
for %%A in ("%error_file%") do set "errror_size=%%~zA"
|
||||
|
||||
@@ -15,5 +15,5 @@ if %errror_size% gtr 0 (
|
||||
|
||||
del /Q %error_file%
|
||||
|
||||
pause > null
|
||||
pause
|
||||
exit
|
||||
@@ -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