MainStats
29
Rapport/AlphaBetaPlayer.java
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
public Pair<Point, Point> play(State game) {
|
||||||
|
int bestValue = Integer.MIN_VALUE;
|
||||||
|
Pair<Point, Point> bestMove = null;
|
||||||
|
for(Pair<Point, Point> move : game.getMove(game.getCurrentPlayer())) {
|
||||||
|
State nextState = game.play(move);
|
||||||
|
complexity++;
|
||||||
|
int value = -alphabeta(nextState, this.depth,Integer.MIN_VALUE,Integer.MAX_VALUE);
|
||||||
|
if (value > bestValue) {
|
||||||
|
bestValue = value;
|
||||||
|
bestMove = move;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int alphabeta(State state, int depth,int alpha,int beta) {
|
||||||
|
if(depth == 0 || state.isOver()) {
|
||||||
|
return evaluate(state);
|
||||||
|
} else {
|
||||||
|
for (Pair<Point, Point> move : state.getMove(state.getCurrentPlayer())) {
|
||||||
|
State nextState = state.play(move);
|
||||||
|
alpha = Math.max(alpha,-alphabeta(nextState,depth-1,-beta,-alpha));
|
||||||
|
if(alpha >= beta)
|
||||||
|
return alpha;
|
||||||
|
}
|
||||||
|
return alpha;
|
||||||
|
}
|
||||||
|
}
|
27
Rapport/NegamaxPlayer.java
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
public Pair<Point, Point> play(State game) {
|
||||||
|
int bestValue = Integer.MIN_VALUE;
|
||||||
|
Pair<Point, Point> bestMove = null;
|
||||||
|
for(Pair<Point, Point> move : game.getMove(game.getCurrentPlayer())) {
|
||||||
|
State nextState = game.play(move);
|
||||||
|
int value = -negamax(nextState, this.depth);
|
||||||
|
if (value > bestValue) {
|
||||||
|
bestValue = value;
|
||||||
|
bestMove = move;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int negamax(State state, int depth) {
|
||||||
|
if(depth == 0 || state.isOver()) {
|
||||||
|
return evaluate(state);
|
||||||
|
} else{
|
||||||
|
int m = Integer.MIN_VALUE;
|
||||||
|
for (Pair<Point, Point> move : state.getMove(state.getCurrentPlayer())) {
|
||||||
|
State nextState = state.play(move);
|
||||||
|
complexity++;
|
||||||
|
m= Math.max(m,-negamax(nextState,depth-1));
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,12 @@
|
|||||||
\usepackage[french]{babel}
|
\usepackage[french]{babel}
|
||||||
\usepackage{hyperref}
|
\usepackage{hyperref}
|
||||||
\usepackage{graphicx}
|
\usepackage{graphicx}
|
||||||
|
\usepackage{listings}
|
||||||
|
\usepackage[linesnumbered,ruled,french,onelanguage]{algorithm2e}
|
||||||
|
\lstset{numbers=left, numberstyle=\tiny, stepnumber=1, numbersep=6pt}
|
||||||
|
\makeatletter
|
||||||
|
\g@addto@macro{\@algocf@init}{\SetKwInput{KwOut}{Sortie}}
|
||||||
|
\makeatother
|
||||||
\title{Aide à la Décision - Othello}
|
\title{Aide à la Décision - Othello}
|
||||||
\author{Antonin Boyon \and Quentin Legot \and Arthur Page}
|
\author{Antonin Boyon \and Quentin Legot \and Arthur Page}
|
||||||
\date{\today}
|
\date{\today}
|
||||||
@ -20,19 +26,182 @@
|
|||||||
\newpage
|
\newpage
|
||||||
|
|
||||||
\section{Introduction}
|
\section{Introduction}
|
||||||
Le but de notre projet était de concevoir un algorithme de recherche performant sur un jeu d' \textit{Othello}. Le jeu est le plus abstrait possible, la partie nous intéressant étant la réalisation d'un algorithme de recherche efficace. Il est ainsi impossible de jouer au jeu, on ne peut que regarder le résultat d'une partie entre deux joueurs artificiels.
|
|
||||||
|
Le but de notre projet était de concevoir un algorithme de recherche performant sur un jeu d' \textit{Othello}. Le jeu est le plus abstrait possible, la partie nous intéressant étant la réalisation d'un algorithme de recherche efficace. Il est ainsi impossible de jouer au jeu, on ne peut que regarder le résultat d'une partie entre deux joueurs artificiels.\\
|
||||||
Une fois le jeu et l'algorithme de recherche implémentés, nous serons en mesure d'analyser ce dernier pour définir ses paramètres de fonctionnement optimaux. Nous aborderons dans un premier temps l'implémentation du jeu, puis celle de l'algorithme et enfin la présentation et l'analyse des mesures observées.
|
Une fois le jeu et l'algorithme de recherche implémentés, nous serons en mesure d'analyser ce dernier pour définir ses paramètres de fonctionnement optimaux. Nous aborderons dans un premier temps l'implémentation du jeu, puis celle de l'algorithme et enfin la présentation et l'analyse des mesures observées.
|
||||||
|
|
||||||
\section{Le jeu}
|
|
||||||
|
|
||||||
|
|
||||||
\section{L'algorithme de recherche}
|
\section{L'algorithme de recherche}
|
||||||
\subsection{Algorithme de base}
|
\subsection{Algorithme de base}
|
||||||
|
|
||||||
|
Nous avons utilisé un algorithme Negamax pour résoudre le problème.
|
||||||
|
|
||||||
|
\lstinputlisting[language=Java]{NegamaxPlayer.java}
|
||||||
|
|
||||||
\subsection{Algorithme d'élagage}
|
\subsection{Algorithme d'élagage}
|
||||||
|
|
||||||
|
\lstinputlisting[language=Java]{AlphaBetaPlayer.java}
|
||||||
|
|
||||||
\section{Mesures}
|
\section{Mesures}
|
||||||
\subsection{Présentation des mesures}
|
|
||||||
\subsection{Analyse des mesures}
|
\subsection{Présentation}
|
||||||
|
|
||||||
|
Les graphiques qui vont suivre ont été conçus à l’aide des algorithmes AlphaBeta et Negamax.\\
|
||||||
|
Ils sont l’objet de comparaisons entre les algorithmes, en fonction de leur type ou du joueur concerné (premier ou second joueur).\\
|
||||||
|
Ils traduisent la complexité de l’algorithmes (le nombre de nœuds traversés) au fur et à mesure des tours de la partie.\\
|
||||||
|
Le premier joueur est associé à la courbe rouge et le deuxième à la bleue.\\
|
||||||
|
La profondeur de recherche des deux joueurs sera toujours la même.\\
|
||||||
|
|
||||||
|
Tout les tests incluant un temps ont été fait sur la même machine et en même temps: Raspberry pi 3 avec un processeur Quad Core 1.2GHz 64bit sous Raspbian OS 32 bits sans Bureau.
|
||||||
|
\newpage
|
||||||
|
\subsection{AlphaBeta}
|
||||||
|
|
||||||
|
\subsubsection{Profondeur 1}
|
||||||
|
|
||||||
|
\begin{figure}[!h]
|
||||||
|
\includegraphics[width=\textwidth]{prof1alphabeta.png}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
Le joueur 1 obtient assez vite (tour 5) un avantage (il possède plus de possibilités) qui augmente au fur et à mesure des tours. A son maximum (fin de la partie) cet avantage est 69\% plus important par rapport au second joueur.\\
|
||||||
|
L’augmentation de la complexité est plutôt linéaire.\\
|
||||||
|
Il semblerait que jouer en premier est un avantage.
|
||||||
|
\newpage
|
||||||
|
\subsubsection{Profondeur 2}
|
||||||
|
|
||||||
|
\begin{figure}[!h]
|
||||||
|
\includegraphics[width=\textwidth]{prof2alphabeta.png}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
Malgré qu’il soit second à jouer, joueur 2 obtient un avantage au niveau du tour 10 environ. Cet avantage augmente jusqu’au tour 30, avec un pic à 30\% par rapport au joueur 1, mais reste marginal. Il se réduit ensuite jusqu’à la fin de la partie.\\
|
||||||
|
Le nombre de tour est largement inférieur par rapport au précédent graphique. La complexité du joueur 1 est deux fois moins importante que sur le graphique précédent, malgré la profondeur plus importante.\\
|
||||||
|
Mais malgré cet avantage, la victoire est pour le joueur 1.\\
|
||||||
|
La courbe est linéaire, comme sur la graphique précédent.\\
|
||||||
|
Être le premier à jouer semble donner un avantage, et le nombre de possibilités du joueur 2 plus important n’était pas suffisant pour le résorber. La profondeur ne semble pas forcément augmenter le nombre de possibilités.
|
||||||
|
\newpage
|
||||||
|
\subsubsection{Profondeur 3}
|
||||||
|
|
||||||
|
\begin{figure}[!h]
|
||||||
|
\includegraphics[width=\textwidth]{prof3alphabeta.png}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
Comme pour la profondeur 1, le joueur 1 a tôt, au tour 5 environ, un avantage qui augmente également au fur et à mesure de la partie et gagne cette dernière.\\
|
||||||
|
La situation est similaire à AlphaBeta de profondeur 1.
|
||||||
|
\newpage
|
||||||
|
\subsubsection{Profondeur 4}
|
||||||
|
|
||||||
|
\begin{figure}[!h]
|
||||||
|
\includegraphics[width=\textwidth]{prof4alphabeta.png}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
Similaire au graphique de profondeur 2, le second joueur possède un avantage à partir du tour 5 environ. Mais au tour 20 cet avantage augmente drastiquement pour atteindre 100\% de plus, en fin de partie, par rapport au joueur 1. La complexité du premier joueur est particulièrement basse, presque 70\% moins importante qu’avec la profondeur de 1.\\
|
||||||
|
Pour la première fois, c’est le second joueur qui gagne la partie.\\
|
||||||
|
L’avantage très important du joueur 2 lui a permis de l’emporter, malgré son désavantage de joueur en second.\\
|
||||||
|
|
||||||
|
\begin{figure}[!h]
|
||||||
|
\includegraphics[width=\textwidth]{prof4alphabeta-console.png}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
Le temps de résolution de cet algorithme est de 1 minute et 12 secondes pour un total de 2 226 nœuds visités (pour les deux joueurs).
|
||||||
|
|
||||||
|
\subsubsection{Profondeur 5}
|
||||||
|
|
||||||
|
\begin{figure}[!h]
|
||||||
|
\includegraphics[width=\textwidth]{prof5alphabeta.png}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
Au tour 5 environ, le premier joueur obtient un léger avantage qui se résorbent au tour 30.\\
|
||||||
|
Pour le reste de la partie, il n’y a pas de réelle avantage d’un joueur.\\
|
||||||
|
Conformément aux observations précédentes, sans avantage du joueur 2, c’est le premier joueur qui l’emporte.
|
||||||
|
|
||||||
|
\begin{figure}[!h]
|
||||||
|
\includegraphics[width=\textwidth]{prof5alphabeta-console.png}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
Pour 3 094 nœuds visités, l’algorithme dure 6 minutes et 54 secondes.
|
||||||
|
|
||||||
|
\subsubsection{Conclusion d’Alphabeta}
|
||||||
|
|
||||||
|
Jouer en premier donne un avantage. Il faut au second joueur un avantage conséquent (situé entre 30\% et 100\% par rapport au premier) pour lui permettre de l’emporter.\\
|
||||||
|
De plus, c’est sur les profondeur pairs que le second joueur semble posséder un avantage.\\
|
||||||
|
L’augmentation de la profondeur de l’algorithme AlphaBeta n’augmente pas forcément la complexité de ce dernier. Cependant l’augmentation de la complexité en fonction du nombre de tour est relativement linéaire.\\
|
||||||
|
Le temps de résolution des algorithme pour des petites profondeurs (1, 2, 3) est de quelques secondes mais augmente drastiquement avec la profondeur, AlphaBeta(5) s’exécute pendant plusieurs minutes.
|
||||||
|
|
||||||
|
\subsection{Negamax}
|
||||||
|
|
||||||
|
\subsubsection{Profondeur 1}
|
||||||
|
|
||||||
|
\begin{figure}[!h]
|
||||||
|
\includegraphics[width=\textwidth]{prof1negamax.png}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
Aucun des joueurs n’a d’avantage particulier.\\
|
||||||
|
La complexité est environ 20 fois plus importante qu’AlphaBeta.\\
|
||||||
|
Le joueur 1 est le gagnant. Avec cet algorithme aussi il semblerait que le premier joueur possède un avantage.\\
|
||||||
|
L’augmentation de la complexité est moins importante au début et à la fin de partie mais est assez linéaire.
|
||||||
|
|
||||||
|
\subsubsection{Profondeur 2}
|
||||||
|
|
||||||
|
\begin{figure}[!h]
|
||||||
|
\includegraphics[width=\textwidth]{prof2negamax.png}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
La complexité est 40 fois plus importante qu’avec la profondeur précédente.\\
|
||||||
|
La forme de la courbe est similaire au graphique précédent.\\
|
||||||
|
Aucun joueur n’a d’avantage majeur au cours de la partie. Le second joueur a un petit avantage qui commence au tour 15 et qui finit au tour 33 où le premier prend l’avantage, qui reste faible, jusqu’à la fin de la partie. \\
|
||||||
|
Cependant c’est le second joueur qui l’emporte, alors qu’il n’avait pas un grand avantage. Cela différencie cet algorithme de AlphaBeta.
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\subsubsection{Profondeur 3}
|
||||||
|
|
||||||
|
\begin{figure}[!h]
|
||||||
|
\includegraphics[width=\textwidth]{prof3negamax.png}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
La complexité de cet algorithme est 15 fois supérieur au précédent.\\
|
||||||
|
Le joueur 2 commence à avoir un avantage au tour 23, avantage qui augmente un peu jusqu’à la fin de la partie. Cependant cet avantage n’est pas suffisant et c’est le premier joueur qui gagne.\\
|
||||||
|
La courbe est similaire à celles des autres profondeurs.
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\subsubsection{Profondeur 4}
|
||||||
|
|
||||||
|
\begin{figure}[!h]
|
||||||
|
\includegraphics[width=\textwidth]{prof4negamax.png}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
La complexité est environ 11 fois supérieur à Negamax de profondeur 3. Le premier joueur possède un avantage au tour 10 qui augmente jusqu’au tour 22 et se réduit ensuite jusqu’à la fin de la partie.\\
|
||||||
|
Malgré cet avantage c’est le second joueur qui remporte la partie.
|
||||||
|
|
||||||
|
\begin{figure}[!h]
|
||||||
|
\includegraphics[width=\textwidth]{prof4negamax-console.png}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
Pour le parcours des 536 329 836 nœuds, l’algorithme met 69 minutes et 43 secondes.
|
||||||
|
|
||||||
|
\subsubsection{Profondeur 5}
|
||||||
|
|
||||||
|
Nous avons estimé la complexité de Negamax de profondeur 5 à au moins 8.2 milliards de nœuds.\\
|
||||||
|
Nous avons essayer de le lancer mais après 28 heures d’exécution sur le Raspberry Pi celui-ci n’étais pas terminé, de plus la complexité stocké comme attribut de la classe Player est stocké sur un entier signé 32 bits dont la limite positive est $2^{31} - 1$ soit inférieur à 8 milliards.
|
||||||
|
|
||||||
|
\subsubsection{Conclusion de Negamax}
|
||||||
|
|
||||||
|
L’augmentation de la complexité en fonction de la profondeur de l’algorithme est exponentielle.\\
|
||||||
|
La victoire d’un joueur n’a pas l’air influé par son avantage ou par son ordre de jeu.\\
|
||||||
|
Cet algorithme est très long et du à sa complexité exponentielle, son temps d’exécution l’est également.
|
||||||
|
|
||||||
|
\section{Difficultés rencontrés}
|
||||||
|
|
||||||
|
Nous avons rencontrés quelques difficultés durant la réalisation du jeu, notamment du à des incompréhension des règles du jeu:\\
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item Lors du clonage toutes les cases même inoccupées étaient modifiées.
|
||||||
|
\item Le saut était possible même quand il n'y avait pas de pion en dessous et ne transformait pas les pions adverses.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\section{Expérimentations}
|
||||||
|
|
||||||
|
Nous avons mis en place un logger afin de pouvoir exporter plus facilement les données notamment pour faire les graphiques.\\
|
||||||
|
|
||||||
|
Le flux s'ouvre au lancement du programme et se ferme à sa fermeture ce qui fait que les fichiers de log resteront vides pendant le fonctionnement du programme, ce qui rend impossible la lecture de la complexité durant le fonctionnement du programme notamment pour le negamax de profondeur 5 ou plus qui demande beaucoup de performances et de temps de calcul.
|
||||||
|
|
||||||
|
|
||||||
\section{Conclusion}
|
\section{Conclusion}
|
||||||
\end{document}
|
\end{document}
|
BIN
Rapport/prof1alphabeta.png
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
Rapport/prof1negamax.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
Rapport/prof2alphabeta.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
Rapport/prof2negamax.png
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
Rapport/prof3alphabeta.png
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
Rapport/prof3negamax.png
Normal file
After Width: | Height: | Size: 83 KiB |
BIN
Rapport/prof4alphabeta-console.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
Rapport/prof4alphabeta.png
Normal file
After Width: | Height: | Size: 79 KiB |
BIN
Rapport/prof4negamax-console.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Rapport/prof4negamax.png
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
Rapport/prof5alphabeta-console.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
Rapport/prof5alphabeta.png
Normal file
After Width: | Height: | Size: 84 KiB |
14
readme.txt
@ -0,0 +1,14 @@
|
|||||||
|
# Jeu d’infection - Othello
|
||||||
|
|
||||||
|
## Comment lancer le jeu
|
||||||
|
|
||||||
|
Après avoir compilés et placer les fichiers dans une archive .jar:
|
||||||
|
|
||||||
|
`java -jar othello.jar profondeurJ1 profondeurJ2 alphabeta`
|
||||||
|
|
||||||
|
`profondeurJ1` et `profondeurJ2` correspondent à un entier indiquant à l'algorithme de décision de combien de profondeur il doit calculer et
|
||||||
|
`alphabeta` correspond à un booléen indiquant au programme si vous souhaitez utiliser l'algorithme alphabeta (True ou true) ou l'algorithme Negamax (toutes autres valeurs que true)
|
||||||
|
|
||||||
|
Si profondeurJ1 ou profondeurJ2 ne sont pas corrects, l'algorithme utilisera des paramètres par défaut
|
||||||
|
|
||||||
|
Si vous ne souhaitez pas utiliser le logger, indiquer dans le fichier META_INF que vous souhaitez qu'il se lance sur le fichier Main et non sur MainStats
|
3
src/META-INF/MANIFEST.MF
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Manifest-Version: 1.0
|
||||||
|
Main-Class: othello.MainStats
|
||||||
|
|
@ -1,19 +1,20 @@
|
|||||||
package othello;
|
package othello;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
|
||||||
import othello.players.AlphaBetaPlayer;
|
import othello.players.AlphaBetaPlayer;
|
||||||
|
import othello.players.NegamaxPlayer;
|
||||||
import othello.players.Player;
|
import othello.players.Player;
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
|
|
||||||
|
|
||||||
public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException {
|
public static void main(String[] args) {
|
||||||
Player p1 = new AlphaBetaPlayer(4);
|
Player[] players = extractArgs(args);
|
||||||
Player p2 = new AlphaBetaPlayer(4);
|
Player p1 = players[0];
|
||||||
Player[][] board = initialize(p1, p2);
|
Player p2 = players[1];
|
||||||
|
Player[][] board = initializeBoard(p1, p2);
|
||||||
State game = new State(board, p1, p2);
|
State game = new State(board, p1, p2);
|
||||||
System.out.println("joueur 1: " + p1);
|
System.out.println("joueur 1: " + p1);
|
||||||
System.out.println("joueur 2: " + p2);
|
System.out.println("joueur 2: " + p2);
|
||||||
@ -22,15 +23,48 @@ public class Main {
|
|||||||
System.out.println(game.toString());
|
System.out.println(game.toString());
|
||||||
game = game.play(player.play(game));
|
game = game.play(player.play(game));
|
||||||
}
|
}
|
||||||
System.out.println(game.toString());
|
endGame(game, p1, p2);
|
||||||
System.out.println(game.getN1()+" "+ game.getN2());
|
|
||||||
System.out.println(game.getWinner() + " a gagné la partie");
|
|
||||||
System.out.println("Score joueur 1 -> " + game.getN1());
|
|
||||||
System.out.println("Score joueur 2 -> "+ game.getN2());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method will interpret the arguments and return a {@link Player} instance depending on the arguments given
|
||||||
|
* <ul><li>argument 0 {@code int}: player 1 depth search</li>
|
||||||
|
* <li>argument 1 {@code int}: player 2 depth search</li>
|
||||||
|
* <li>argument 2 {@code boolean}: true if player should be {@link AlphaBetaPlayer} instance,
|
||||||
|
* {@link NegamaxPlayer} otherwise</li></ul>
|
||||||
|
* If arguments are missing or invalid, default value will be use {@code (4, 4, true)}
|
||||||
|
* @param args program arguments, given when user write the command to execute the program
|
||||||
|
* @return Player 1 and 2 instance
|
||||||
|
*/
|
||||||
|
public static Player[] extractArgs(String[] args) {
|
||||||
|
Player p1;
|
||||||
|
Player p2;
|
||||||
|
int depthP1 = 4;
|
||||||
|
int depthP2 = 4;
|
||||||
|
boolean useAlphaBeta = true;
|
||||||
|
try {
|
||||||
|
if(args.length >= 3) { // les paramètres > 3 sont ignorés
|
||||||
|
depthP1 = Integer.parseInt(args[0]);
|
||||||
|
depthP2 = Integer.parseInt(args[1]);
|
||||||
|
useAlphaBeta = Boolean.parseBoolean(args[2]);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
System.out.println("Les arguments de lancement ne pas corrects (pas des nombres entiers)");
|
||||||
|
System.out.println("Utilisation des paramètres par défaut ( 4 4 true )");
|
||||||
|
} finally {
|
||||||
|
if(useAlphaBeta) {
|
||||||
|
p1 = new AlphaBetaPlayer(depthP1);
|
||||||
|
p2 = new AlphaBetaPlayer(depthP2);
|
||||||
|
} else {
|
||||||
|
p1 = new NegamaxPlayer(depthP1);
|
||||||
|
p2 = new NegamaxPlayer(depthP2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Player[]{p1, p2};
|
||||||
|
}
|
||||||
|
|
||||||
public static Player[][] initialize(Player p1, Player p2){
|
public static Player[][] initializeBoard(Player p1, Player p2){
|
||||||
Player[][] board = new Player[7][7];
|
Player[][] board = new Player[7][7];
|
||||||
board[0][0] = p2;
|
board[0][0] = p2;
|
||||||
board[0][6] = p1;
|
board[0][6] = p1;
|
||||||
@ -38,5 +72,13 @@ public class Main {
|
|||||||
board[6][6] = p2;
|
board[6][6] = p2;
|
||||||
return board;
|
return board;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void endGame(State game, Player p1, Player p2) {
|
||||||
|
System.out.println(game.toString());
|
||||||
|
System.out.println(game.getN1()+" "+ game.getN2());
|
||||||
|
System.out.println(game.getWinner() + " a gagné la partie");
|
||||||
|
System.out.println("Score joueur 1 -> " + game.getScore(p1));
|
||||||
|
System.out.println("Score joueur 2 -> "+ game.getScore(p2));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package othello;
|
package othello;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import othello.players.AlphaBetaPlayer;
|
import othello.players.AlphaBetaPlayer;
|
||||||
import othello.players.NegamaxPlayer;
|
import othello.players.NegamaxPlayer;
|
||||||
@ -12,39 +14,61 @@ public class MainStats {
|
|||||||
|
|
||||||
|
|
||||||
public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException {
|
public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException {
|
||||||
Player p1 = new NegamaxPlayer(2);
|
Player p1 = new NegamaxPlayer(1);
|
||||||
Player p2 = new NegamaxPlayer(2);
|
Player p2 = new NegamaxPlayer(1);
|
||||||
Player[][] board = initialize(p1, p2);
|
Player[][] board = initializeBoard(p1, p2);
|
||||||
|
|
||||||
State game = new State(board, p1, p2);
|
State game = new State(board, p1, p2);
|
||||||
System.out.println("joueur 1: " + p1);
|
System.out.println("joueur 1: " + p1);
|
||||||
System.out.println("joueur 2: " + p2);
|
System.out.println("joueur 2: " + p2);
|
||||||
int tour = 1; // Pour le rapport
|
int tour = 1; // Pour le rapport
|
||||||
PrintWriter writer = new PrintWriter("statsj1.txt", "UTF-8");
|
PrintWriter writer = new PrintWriter("statsj1.txt");
|
||||||
PrintWriter writer2 = new PrintWriter("statsj2.txt", "UTF-8");
|
PrintWriter writer2 = new PrintWriter("statsj2.txt");
|
||||||
|
|
||||||
while(!game.isOver()) {
|
while(!game.isOver()) {
|
||||||
Player player = game.getCurrentPlayer();
|
Player player = game.getCurrentPlayer();
|
||||||
System.out.println(game.toString());
|
System.out.println(game.toString());
|
||||||
game = game.play(player.play(game));
|
game = game.play(player.play(game));
|
||||||
if(tour%2 == 0) {
|
|
||||||
|
if(tour % 2 == 0)
|
||||||
writer2.println(player.getComplexity());
|
writer2.println(player.getComplexity());
|
||||||
}
|
else
|
||||||
else {
|
|
||||||
writer.println(player.getComplexity());
|
writer.println(player.getComplexity());
|
||||||
}
|
|
||||||
tour++;
|
tour++;
|
||||||
}
|
}
|
||||||
writer.close();
|
writer.close();
|
||||||
writer2.close();
|
writer2.close();
|
||||||
System.out.println(game.toString());
|
Main.endGame(game, p1, p2);
|
||||||
System.out.println(game.getN1()+" "+ game.getN2());
|
|
||||||
System.out.println(game.getWinner() + " a gagné la partie");
|
|
||||||
System.out.println("Score joueur 1 -> " + game.getN1());
|
|
||||||
System.out.println("Score joueur 2 -> "+ game.getN2());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Player[][] initialize(Player p1, Player p2){
|
public static Player[] extractArgs(String[] args) {
|
||||||
|
Player p1;
|
||||||
|
Player p2;
|
||||||
|
int depthP1 = 4;
|
||||||
|
int depthP2 = 4;
|
||||||
|
boolean useAlphaBeta = true;
|
||||||
|
try {
|
||||||
|
if(args.length >= 3) { // les paramètres > 3 sont ignorés
|
||||||
|
depthP1 = Integer.parseInt(args[0]);
|
||||||
|
depthP2 = Integer.parseInt(args[1]);
|
||||||
|
useAlphaBeta = Boolean.parseBoolean(args[2]);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
System.out.println("Les arguments de lancement ne pas corrects (pas des nombres entiers)");
|
||||||
|
System.out.println("Utilisation des paramètres par défaut ( 4 4 true )");
|
||||||
|
} finally {
|
||||||
|
if(useAlphaBeta) {
|
||||||
|
p1 = new AlphaBetaPlayer(depthP1);
|
||||||
|
p2 = new AlphaBetaPlayer(depthP2);
|
||||||
|
} else {
|
||||||
|
p1 = new NegamaxPlayer(depthP1);
|
||||||
|
p2 = new NegamaxPlayer(depthP2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Player[]{p1, p2};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Player[][] initializeBoard(Player p1, Player p2){
|
||||||
Player[][] board = new Player[7][7];
|
Player[][] board = new Player[7][7];
|
||||||
board[0][0] = p2;
|
board[0][0] = p2;
|
||||||
board[0][6] = p1;
|
board[0][6] = p1;
|
||||||
@ -52,5 +76,13 @@ public class MainStats {
|
|||||||
board[6][6] = p2;
|
board[6][6] = p2;
|
||||||
return board;
|
return board;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void endGame(State game, Player p1, Player p2) {
|
||||||
|
System.out.println(game.toString());
|
||||||
|
System.out.println(game.getN1()+" "+ game.getN2());
|
||||||
|
System.out.println(game.getWinner() + " a gagné la partie");
|
||||||
|
System.out.println("Score joueur 1 -> " + game.getScore(p1));
|
||||||
|
System.out.println("Score joueur 2 -> "+ game.getScore(p2));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,16 +4,23 @@ import othello.players.Player;
|
|||||||
|
|
||||||
public class Point {
|
public class Point {
|
||||||
|
|
||||||
private int x;
|
private final int x;
|
||||||
private int y;
|
private final int y;
|
||||||
|
|
||||||
public Point(int y, int x) {
|
public Point(int y, int x) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check if {@code other} point is considered as jump
|
||||||
|
* @param other arrival point
|
||||||
|
* @param board current {@link State} situation
|
||||||
|
* @return {@code true} if other is considered as a jump depending of {@code this}, {@code false} otherwise
|
||||||
|
*/
|
||||||
public boolean isJump(Point other, Player[][] board) {
|
public boolean isJump(Point other, Player[][] board) {
|
||||||
return (board[(x+other.getX())/2][(y+other.getY())/2] != null);
|
double value = Math.pow(other.x - this.x, 2) + Math.pow(other.y - this.y, 2);
|
||||||
|
return (value == 4 || value == 8) && board[(x+other.getX())/2][(y+other.getY())/2] != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getX(){
|
public int getX(){
|
||||||
|
@ -7,7 +7,12 @@ import java.util.List;
|
|||||||
|
|
||||||
public class State {
|
public class State {
|
||||||
|
|
||||||
public static List<State> previousSituations = new LinkedList<>();
|
/**
|
||||||
|
* Contains previous situations of the {@link State#board}, if the game return in a situation which have already
|
||||||
|
* been played, the game ends.
|
||||||
|
* We only keep the 10 previous situations due to performances issues
|
||||||
|
*/
|
||||||
|
public static List<Player[][]> previousSituations = new LinkedList<>();
|
||||||
|
|
||||||
private final Player[][] board;
|
private final Player[][] board;
|
||||||
private final Player player1;
|
private final Player player1;
|
||||||
@ -26,9 +31,18 @@ public class State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOver() {
|
public boolean isOver() {
|
||||||
return n1 == 0 || n2 == 0 || (getMove(player1).isEmpty() && getMove(player2).isEmpty());
|
return n1 == 0 || n2 == 0 || (getMove(player1).isEmpty() && getMove(player2).isEmpty())
|
||||||
|
|| previousSituations.contains(this.board);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method check every possible movement which can do {@code player}'s pawns and add the movement in a list when
|
||||||
|
* there is no-one on the {@link State#board board}. the left side of the {@link Pair tuple} contains the position
|
||||||
|
* where the pawn currently is and the right side the position where it can go by cloning itself or jumping over an
|
||||||
|
* other pawn
|
||||||
|
* @param player the player whose possible movements will be checked
|
||||||
|
* @return a {@link LinkedList list} containing every movement which can do {@code player} in this current situation
|
||||||
|
*/
|
||||||
public LinkedList<Pair<Point, Point>> getMove(Player player) {
|
public LinkedList<Pair<Point, Point>> getMove(Player player) {
|
||||||
// Pair<Depart, Arrivee>
|
// Pair<Depart, Arrivee>
|
||||||
LinkedList<Pair<Point, Point>> moves = new LinkedList<>();
|
LinkedList<Pair<Point, Point>> moves = new LinkedList<>();
|
||||||
@ -61,10 +75,12 @@ public class State {
|
|||||||
public int getScore(Player player) {
|
public int getScore(Player player) {
|
||||||
return player == player1 ? (n1/(n1+n2)) : (n2/(n1+n2));
|
return player == player1 ? (n1/(n1+n2)) : (n2/(n1+n2));
|
||||||
}
|
}
|
||||||
public int getN1(){
|
|
||||||
|
public int getN1() {
|
||||||
return this.n1;
|
return this.n1;
|
||||||
}
|
}
|
||||||
public int getN2(){
|
|
||||||
|
public int getN2() {
|
||||||
return this.n2;
|
return this.n2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,24 +92,34 @@ public class State {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method create a copy of itself and modify this copy depending on the {@code move} parameter, it'll clone or jump
|
||||||
|
* a pawn from the left side of {@code move} to the right side, switch current player and recalculate players' score
|
||||||
|
* @param move a {@link Pair tuple} containing 2 elements,
|
||||||
|
* the left side contains the starting point (where is the point)
|
||||||
|
* and the right side contains the point where it'll clone or jump
|
||||||
|
* @return a modified copy of the current situation
|
||||||
|
*/
|
||||||
public State play(Pair<Point,Point> move) {
|
public State play(Pair<Point,Point> move) {
|
||||||
|
if(previousSituations.size() == 10) // on ne garde que les 10 dernieres situations par soucis de perfs
|
||||||
|
previousSituations.remove(0);
|
||||||
|
previousSituations.add(board);
|
||||||
State copy = this.copy();
|
State copy = this.copy();
|
||||||
boolean isJump = move.getLeft().isJump(move.getRight(), copy.board);
|
boolean isJump = move.getLeft().isJump(move.getRight(), copy.board);
|
||||||
copy.board[move.getRight().getY()][move.getRight().getX()] = copy.currentPlayer;
|
copy.board[move.getRight().getY()][move.getRight().getX()] = copy.currentPlayer;
|
||||||
if (isJump) {
|
if (isJump) {
|
||||||
copy.board[move.getLeft().getY()][move.getLeft().getX()] = null;
|
copy.board[move.getLeft().getY()][move.getLeft().getX()] = null;
|
||||||
copy.board[(move.getLeft().getY() + move.getRight().getY()) / 2][(move.getLeft().getX() + move.getRight().getX()) / 2] = copy.currentPlayer;
|
}
|
||||||
} else {
|
for (int i = -1; i < 2; i++) {
|
||||||
for (int i = -1; i < 2; i++) {
|
for (int z = -1; z < 2; z++) {
|
||||||
for (int z = -1; z < 2; z++) {
|
try {
|
||||||
try {
|
if(copy.board[move.getRight().getY() + i][move.getRight().getX() + z] != null)
|
||||||
if(copy.board[move.getRight().getY() + i][move.getRight().getX() + z] != null)
|
copy.board[move.getRight().getY() + i][move.getRight().getX() + z] = copy.currentPlayer;
|
||||||
copy.board[move.getRight().getY() + i][move.getRight().getX() + z] = copy.currentPlayer;
|
} catch (IndexOutOfBoundsException ignored) {}
|
||||||
} catch (IndexOutOfBoundsException ignored) {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int ni = 0, nj = 0;
|
int ni = 0, nj = 0;
|
||||||
for (Player[] players : copy.board) {
|
for (Player[] players : copy.board) {
|
||||||
for (Player player : players) {
|
for (Player player : players) {
|
||||||
@ -133,10 +159,7 @@ public class State {
|
|||||||
public void switchPlayer() {
|
public void switchPlayer() {
|
||||||
setCurrentPlayer(getCurrentPlayer() == this.player1 ? player2 : player1);
|
setCurrentPlayer(getCurrentPlayer() == this.player1 ? player2 : player1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: display the current state of the board
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder str = new StringBuilder();
|
StringBuilder str = new StringBuilder();
|
||||||
@ -154,6 +177,7 @@ public class State {
|
|||||||
}
|
}
|
||||||
return str.toString();
|
return str.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object state) {
|
public boolean equals(Object state) {
|
||||||
boolean bool;
|
boolean bool;
|
||||||
|
@ -10,6 +10,13 @@ public class AlphaBetaPlayer extends Player{
|
|||||||
super(depth);
|
super(depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will find the best move to try to win the game by searching all possible movement given by
|
||||||
|
* {@link State#getMove(Player)} in the limit of {@link Player#depth}
|
||||||
|
* @see AlphaBetaPlayer#alphabeta(State, int, int, int)
|
||||||
|
* @see Player#play(State)
|
||||||
|
* @see NegamaxPlayer#play(State)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Pair<Point, Point> play(State game) {
|
public Pair<Point, Point> play(State game) {
|
||||||
int bestValue = Integer.MIN_VALUE;
|
int bestValue = Integer.MIN_VALUE;
|
||||||
|
@ -10,6 +10,13 @@ public class NegamaxPlayer extends Player {
|
|||||||
super(depth);
|
super(depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will find the best move to try to win the game by searching all possible movement given by
|
||||||
|
* {@link State#getMove(Player)} in the limit of {@link Player#depth}
|
||||||
|
* @see NegamaxPlayer#negamax(State, int)
|
||||||
|
* @see AlphaBetaPlayer#play(State)
|
||||||
|
* @see Player#play(State)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Pair<Point, Point> play(State game) {
|
public Pair<Point, Point> play(State game) {
|
||||||
int bestValue = Integer.MIN_VALUE;
|
int bestValue = Integer.MIN_VALUE;
|
||||||
|
@ -18,6 +18,11 @@ public abstract class Player {
|
|||||||
return this.complexity;
|
return this.complexity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param board current {@link State} situation
|
||||||
|
* @return a {@link Pair tuple} with on the left side the starting point of a pawn and on the right side the arrival
|
||||||
|
* point
|
||||||
|
*/
|
||||||
public abstract Pair<Point, Point> play(State board);
|
public abstract Pair<Point, Point> play(State board);
|
||||||
|
|
||||||
protected int evaluate(State game){
|
protected int evaluate(State game){
|
||||||
|
52
statsj1.txt
@ -1,52 +0,0 @@
|
|||||||
432
|
|
||||||
1992
|
|
||||||
4828
|
|
||||||
8150
|
|
||||||
14407
|
|
||||||
23013
|
|
||||||
30491
|
|
||||||
42053
|
|
||||||
51206
|
|
||||||
69655
|
|
||||||
86676
|
|
||||||
104309
|
|
||||||
115501
|
|
||||||
127748
|
|
||||||
139306
|
|
||||||
158959
|
|
||||||
181347
|
|
||||||
195343
|
|
||||||
234427
|
|
||||||
298978
|
|
||||||
384890
|
|
||||||
454787
|
|
||||||
503066
|
|
||||||
554466
|
|
||||||
603597
|
|
||||||
682788
|
|
||||||
747703
|
|
||||||
858815
|
|
||||||
961301
|
|
||||||
1018546
|
|
||||||
1086933
|
|
||||||
1165047
|
|
||||||
1235929
|
|
||||||
1286641
|
|
||||||
1310141
|
|
||||||
1346611
|
|
||||||
1375881
|
|
||||||
1403297
|
|
||||||
1430326
|
|
||||||
1469132
|
|
||||||
1498301
|
|
||||||
1534663
|
|
||||||
1553484
|
|
||||||
1572641
|
|
||||||
1589467
|
|
||||||
1603894
|
|
||||||
1615109
|
|
||||||
1619279
|
|
||||||
1621225
|
|
||||||
1623460
|
|
||||||
1624735
|
|
||||||
1624811
|
|
52
statsj2.txt
@ -1,52 +0,0 @@
|
|||||||
720
|
|
||||||
2712
|
|
||||||
4940
|
|
||||||
7337
|
|
||||||
11453
|
|
||||||
16716
|
|
||||||
23764
|
|
||||||
35025
|
|
||||||
42907
|
|
||||||
60299
|
|
||||||
70521
|
|
||||||
89718
|
|
||||||
109605
|
|
||||||
126838
|
|
||||||
156244
|
|
||||||
205938
|
|
||||||
237343
|
|
||||||
281749
|
|
||||||
344586
|
|
||||||
392942
|
|
||||||
456525
|
|
||||||
513322
|
|
||||||
591740
|
|
||||||
680035
|
|
||||||
734402
|
|
||||||
789176
|
|
||||||
888420
|
|
||||||
962506
|
|
||||||
1032354
|
|
||||||
1101620
|
|
||||||
1172407
|
|
||||||
1217925
|
|
||||||
1260536
|
|
||||||
1283650
|
|
||||||
1301117
|
|
||||||
1333526
|
|
||||||
1343617
|
|
||||||
1359554
|
|
||||||
1392955
|
|
||||||
1420325
|
|
||||||
1444803
|
|
||||||
1469183
|
|
||||||
1491000
|
|
||||||
1506375
|
|
||||||
1519135
|
|
||||||
1539940
|
|
||||||
1550685
|
|
||||||
1554788
|
|
||||||
1556482
|
|
||||||
1557548
|
|
||||||
1559067
|
|
||||||
1559202
|
|