Browse Source

Commentaires

master
Stéphane 8 years ago
parent
commit
5ce8c2764f
9 changed files with 140 additions and 88 deletions
  1. +1
    -1
      ai/Makefile
  2. +28
    -19
      ai/ai.c
  3. +4
    -9
      ai/gamestate.c
  4. +19
    -2
      ai/gamestate.h
  5. +6
    -0
      ai/list.c
  6. +3
    -0
      ai/queue.c
  7. +67
    -55
      ai/strategy.c
  8. +11
    -0
      ai/strategy.h
  9. +1
    -2
      ai/tree.h

+ 1
- 1
ai/Makefile View File

@ -4,7 +4,7 @@ SOURCES=$(wildcard *.c)
OBJECTS=$(SOURCES:.c=.o)
EXECUTABLE=ai.so
CFLAGS=-fPIC -g -std=gnu99 -Wall
CFLAGS=-fPIC -g -std=gnu99 -Wall -Wextra
LDFLAGS=-shared
test: $(OBJECTS) $(EXECUTABLE)


+ 28
- 19
ai/ai.c View File

@ -8,14 +8,13 @@
#include "limits.h"
char g_name[50];
EPlayer g_color;
int g_nbPlayers;
EPlayer g_color; // La couleur du joueur
int g_nbPlayers; // Le nombre de joueurs
Choice g_move;
int g_step;
int g_start;
int g_destination;
Choice g_move; // Le mouvement retenu par l'algo de choix du meilleur coup
int g_step; // La progression actuelle dans l'exécution du mouvement retenu
int g_start; // La pointe de la branche de départ
int g_destination; // La pointe de la branche d'arrivée
void InitLibrary(char name[50])
{
@ -30,6 +29,7 @@ void StartMatch(const unsigned int nbJoueur, const EPlayer couleur)
g_nbPlayers = nbJoueur;
g_color = couleur;
// Calcul des ID des trous à la pointe des branches de départ et d'arrivée
switch (g_color)
{
case color1:
@ -92,17 +92,21 @@ int NextMove(const SGameState* const gameState, int firstMove, SMove* move)
{
printf("(AI) Choosing next move...\n");
int pieces[10] = {};
findPlayerPieces(gameState, g_color, pieces);
{
// Infos utiles pour le débuggage
int pieces[10] = {};
findPlayerPieces(gameState, g_color, pieces);
printf("(AI) I'm using color %d. My pieces are in holes:", g_color);
for (int i=0; i<10; i++)
printf(" %d", pieces[i]);
printf("\n");
printf("(AI) I'm using color %d. My pieces are in holes:", g_color);
for (int i=0; i<10; i++)
printf(" %d", pieces[i]);
printf("\n");
}
if (firstMove)
{
// Affichage d'un warning si le mouvement demandé est le premier du tour, mais qu'on a pas fini d'exécuter tous les sauts du tableau
if (g_move.move != NULL)
{
fprintf(stderr, "(AI) Warning: Requested move have to be the first of this turn, but there are still moves available in the array. Flushing the array.\n");
@ -114,7 +118,7 @@ int NextMove(const SGameState* const gameState, int firstMove, SMove* move)
// Recherche des coups possibles
Tree* piecesMoves[10];
if (findAllPiecesValidMoves(gameState, g_color, piecesMoves) == 0)
if (findAllPiecesValidMoves(gameState, g_color, piecesMoves) == 0) // On récupère tous les mouvements possibles (si la fonction retourne 0, c'est qu'on est bloqué)
{
fprintf(stderr, "(AI) Warning: A move was requested, but pieces cannot move.\n");
@ -124,26 +128,31 @@ int NextMove(const SGameState* const gameState, int firstMove, SMove* move)
return 0;
}
g_move = getBestMove(piecesMoves, gameState);
g_move = getBestMove(piecesMoves, gameState); // On choisit le meilleur coup selon plusieurs critères
if (g_move.score == INT_MIN)
if (g_move.score == INT_MIN) // Si le coup retenu a un score de INT_MIN (valeur spéciale correspondant à un mouvement interdit [ex : se rendre dans une branche autre que celles de départ ou d'arrivée]), c'est que tous les coups possibles sont interdits
{
fprintf(stderr, "(AI) Warning: All moves are forbidden.\n");
for (int i=0; i<10; i++)
deleteTree(piecesMoves[i]);
return 0;
}
g_step = 0;
g_step = 0; // On commence au saut 0 du mouvement retenu
for (int i=0; i<10; i++)
deleteTree(piecesMoves[i]);
}
if (g_move.move == NULL)
if (g_move.move == NULL) // Si il n'y a plus de mouvement sélectionné, c'est qu'on a fini pour ce tour
return 0;
// On donne des valeurs à la variable move
move->startPos = g_move.move[g_step];
move->endPos = g_move.move[++g_step];
// Lorsqu'on arrive au dernier saut du tour, on libère la variable g_move.move
if (g_step == g_move.size - 1)
{
free(g_move.move);


+ 4
- 9
ai/gamestate.c View File

@ -406,7 +406,6 @@ void voisins(int idCase, int * voisinsCase)
}
}
int getNeighbour(const int hole, Direction dir)
{
int x,y;
@ -450,7 +449,7 @@ void getNeighbours(const int hole, int* neighbours)
neighbours[4] = getHoleIdFromCoordinates(x-1,y-1);
neighbours[5] = getHoleIdFromCoordinates(x,y-1);
}
void getNeighbours2(const int hole, int* neighbours)
{
int x,y;
@ -463,6 +462,7 @@ void getNeighbours2(const int hole, int* neighbours)
neighbours[5] = getHoleIdFromCoordinates(x,y-2);
}
void findReachableHoles(const SGameState* state, const int start, int* holes)
{
int n1[6], n2[6];
@ -495,10 +495,10 @@ int isStarBranch(const int hole)
int k=0;
if((hole <=9 && hole>=0) )
return 1; //OK
return 1;
if((hole <=120 && hole>=111))
return 4; //OK
return 4;
k=0;
for(i=0;i<4;i++)
@ -506,7 +506,6 @@ int isStarBranch(const int hole)
for(j=0;j<4-i;j++)
{
TDroiteHaut[k++]=lineMax[4+i]-j;
}
}
@ -520,7 +519,6 @@ int isStarBranch(const int hole)
for(j=0;j<4-i;j++)
{
TDroiteBas[k++]=lineMax[12-i]-j;
}
}
for(i=0;i<10;i++)
@ -533,7 +531,6 @@ int isStarBranch(const int hole)
for(j=0;j<4-i;j++)
{
TGaucheBas[k++]=lineMax[11-i]+1+j;
}
}
for(i=0;i<10;i++)
@ -546,7 +543,6 @@ int isStarBranch(const int hole)
for(j=0;j<4-i;j++)
{
TGaucheHaut[k++]=lineMax[3+i]+1+j;
}
}
for(i=0;i<10;i++)
@ -554,5 +550,4 @@ int isStarBranch(const int hole)
return 6;
return 0;
}

+ 19
- 2
ai/gamestate.h View File

@ -17,15 +17,32 @@ enum Direction
};
typedef enum Direction Direction;
// Permet de passer de la notation matrice à la notation tableau du plateau
int getHoleIdFromCoordinates(const int x, const int y);
// Permet de passer de la notation tableau à la notation matrice du plateau
void getHoleCoordinatesFromId(int id, int* x, int* y);
// Retourne le numéro de la case voisine d'une case dans la direction spécifiée
int getNeighbour(const int hole, Direction dir);
/* Replit neighbours avec les 6 cases voisines d'une case spécifiée.
-1 correspond à une case qui n'existe pas. */
void getNeighbours(const int hole, int* neighbours);
// Remplit neighbours avec les 6 cases situées derrière les voisins d'une case (c'est-à-dire les voisins de distance 2)
void getNeighbours2(const int hole, int* neighbours);
// Stocke l'emplacement de tous les pions d'un joueur dans pieces
void findPlayerPieces(const SGameState* state, const EPlayer color, int pieces[10]);
/* Liste les cases atteignables avec un mouvement simple ou un seul saut, et stocke le résultat dans holes.
-1 représente une case qui n'est pas atteignable. */
void findReachableHoles(const SGameState* state, const int start, int* holes);
void afficherGameState(SGameState gameState);
void voisins(int idCase, int * voisinsCase);
/* Vérifie si une case fait partie d'une branche de l'étoile.
Retourne 0 si la case ne fait pas partie d'une branche.
Retourne le numéro du joueur à qui appartient la branche dans les autres cas. */
int isStarBranch(const int hole);
#endif

+ 6
- 0
ai/list.c View File

@ -2,6 +2,7 @@
#include <stdlib.h>
// Ajoute une case à la fin d'une liste
LCell* append(List* list, LData data)
{
LCell* cell = malloc(sizeof(LCell));
@ -16,6 +17,7 @@ LCell* append(List* list, LData data)
return cell;
}
// Ajoute une liste à la fin d'une liste
void appendList(List* a, List b)
{
if (*a == NULL)
@ -24,6 +26,7 @@ void appendList(List* a, List b)
last(*a)->next = b;
}
// Retourne un pointeur vers la dernière case d'une liste
LCell* last(List list)
{
if (list == NULL)
@ -36,6 +39,7 @@ LCell* last(List list)
return current;
}
// Sélectionne une valeur aléatoire de la liste
LData pickRandom(List list)
{
if (list == NULL)
@ -53,6 +57,7 @@ LData pickRandom(List list)
return current->data;
}
// Compte les éléments d'une liste
int listSize(List list)
{
if (list == NULL)
@ -69,6 +74,7 @@ int listSize(List list)
return size;
}
// Supprime une liste
void deleteList(List* list)
{
if ((*list) == NULL)


+ 3
- 0
ai/queue.c View File

@ -3,6 +3,7 @@
#include <stdlib.h>
#include <stdio.h>
// Ajoute un élément à la fin d'une file
void push(Queue** queue, QData data)
{
Queue* cell = malloc(sizeof(Queue));
@ -21,6 +22,7 @@ void push(Queue** queue, QData data)
}
}
// Supprime un élément d'une file et retourne sa valeur
QData pop(Queue** queue)
{
if (*queue == NULL)
@ -48,6 +50,7 @@ QData pop(Queue** queue)
return data;
}
// Supprime une file
void deleteQueue(Queue** queue)
{
if (*queue == NULL)


+ 67
- 55
ai/strategy.c View File

@ -9,52 +9,59 @@
#include "queue.h"
#include "ai.h"
Tree* findPieceValidMoves(const SGameState* state, const int hole)
{
// Un tableau qui liste les cases déjà atteintes grâce à un saut (de 2 cases)
int reached[121];
bzero(reached, 121*sizeof(int));
// Création d'un faux état du jeu le pion que l'on souhaite déplacer n'est plus présent (pour éviter qu'il ne saute par-dessus lui-même)
SGameState* tmpState = malloc(sizeof(SGameState));
memcpy(tmpState, state, sizeof(SGameState));
tmpState->board[hole] = none;
Tree* root = newRoot(hole);
Queue* queue = NULL;
Tree* root = newRoot(hole); // L'arbre des mouvements possibles
Queue* queue = NULL; // La file contenant les mouvements pouvant être continués
{
// On liste toutes les cases atteignables en un seul mouvement (simple ou saut)
int reachable[6];
findReachableHoles(tmpState, hole, reachable);
for (int i=0; i<6; i++)
if (reachable[i] != -1)
for (int i=0; i<6; i++) // Pour chacune de ces cases
if (reachable[i] != -1) // Si elle existe
{
Tree* node = newNode(root, i, reachable[i]);
if (reachable[i] != getNeighbour(hole, (Direction) i))
Tree* node = newNode(root, i, reachable[i]); // On rajoute un nœud à l'arbre des mouvements
if (reachable[i] != getNeighbour(hole, (Direction) i)) // Si la case atteignable n'est pas le voisin de la case de départ, c'est qu'on effectue un saut
{
// Dans ce cas, on note que la case a é atteinte grâce à un saut, et on note qu'il faut chercher à continuer ce saut
reached[reachable[i]] = 1;
push(&queue, node);
}
}
}
while (queue != NULL)
while (queue != NULL) // Tant qu'il reste des mouvements que l'on peut continuer
{
Tree* current = pop(&queue);
Tree* current = pop(&queue); // On récupère le nœud du mouvement à continuer
for (int i=0; i<6; i++)
for (int i=0; i<6; i++) // Pour chacune des 6 directions possibles
{
int neighbour = getNeighbour(current->data, (Direction) i);
int neighbour = getNeighbour(current->data, (Direction) i); // On récupère le voisin de la case actuelle
if (neighbour == -1)
if (neighbour == -1) // Si ce voisin n'existe pas, on passe à la case suivante
continue;
// Si il existe et qu'il y a un pion dedans
if (tmpState->board[neighbour] != none)
{
neighbour = getNeighbour(neighbour, (Direction) i);
if (neighbour != -1 && tmpState->board[neighbour] == none && !reached[neighbour])
if (neighbour != -1 && tmpState->board[neighbour] == none && !reached[neighbour]) // On vérifie que la case après ce voisin est libre, et qu'on ne l'a pas encore atteinte grâce à un saut
{
reached[neighbour] = 1;
Tree* tmp = newNode(current, i, neighbour);
push(&queue, tmp);
reached[neighbour] = 1; // On note qu'on l'atteint grâce à un saut
Tree* tmp = newNode(current, i, neighbour); // On rajoute un nœud à l'arbre des mouvements
push(&queue, tmp); // On note qu'il faut continuer ce mouvement
}
}
}
@ -68,24 +75,24 @@ Tree* findPieceValidMoves(const SGameState* state, const int hole)
int findAllPiecesValidMoves(const SGameState* state, const EPlayer color, Tree** moves)
{
// On liste toutes les pièces du joueur
int pieces[10];
findPlayerPieces(state, color, pieces);
int movables = 0;
int movables = 0; // Compte les pièces pouvant bouger
for (int i=0; i<10; i++)
for (int i=0; i<10; i++) // Pour chacune de ces pièces
{
Tree* pieceMoves = findPieceValidMoves(state, pieces[i]);
if (countChilds(pieceMoves) > 0)
Tree* pieceMoves = findPieceValidMoves(state, pieces[i]); // On récupère l'arbre des mouvements possibles pour cette pièce
if (countChilds(pieceMoves) > 0) // Si l'arbre n'est pas un nœud simple, c'est que la pièce peut bouger
{
moves[i] = pieceMoves;
movables++;
moves[i] = pieceMoves; // On stocke l'arbre des mouvements
movables++; // Et on incrémente le compteur de pièces pouvant bouger
}
else
else // Si la pièce ne peut pas bouger
{
deleteTree(pieceMoves);
moves[i] = NULL;
deleteTree(pieceMoves); // On efface l'arbre des mouvements de cette pièce
moves[i] = NULL; // Et on ne stocke que NULL
}
}
@ -98,13 +105,13 @@ Choice treeToMoveChoice(Tree* root, const SGameState* const state)
int size = getParents(root, &array);
if (size < 2) // Ce n'est pas un movement correct : un mouvement doit au moins comporter 2 cases, une de départ et une d'arrivée
{
free(array);
Choice c = {NULL, 0, 0};
return c;
}
// On inverse le contenu du tableau (pour que la case de départ soit au début)
for (int i=0; i<size/2; i++)
{
int tmp = array[i];
@ -112,23 +119,25 @@ Choice treeToMoveChoice(Tree* root, const SGameState* const state)
array[size-1-i] = tmp;
}
// On crée un choix possible de mouvemnt
Choice c;
c.move = array;
c.size = size;
c.score = moveScore(c, state);
c.score = moveScore(c, state); // Le "score" de ce mouvement est calculé
return c;
}
int distance(int a, int b)
{
// On récupère les coordonnées des cases a et b
int xa, ya, xb, yb;
getHoleCoordinatesFromId(a, &xa, &ya);
getHoleCoordinatesFromId(b, &xb, &yb);
int distance = 0;
// On fait varier les coordonnées xa et ya d'une case à chaque fois pour atteindre xb et yb
while (xa != xb || ya != yb)
{
if (ya == yb)
@ -159,13 +168,15 @@ int distance(int a, int b)
int nearestEmptyDestinationHole(const int hole, const SGameState* const state)
{
// On liste les 10 cases de la branche de destination
int destination[10];
for (int i=0, j=0; i<121; i++)
{
if (isStarBranch(i) == ((g_color+2) % 6) + 1)
if (isStarBranch(i) == (int) ((g_color+2) % 6) + 1)
destination[j++] = i;
}
// Parmi ces 10 cases, on cherche celle qui minimise la distance avec la case indiquée
int min = -1;
int minDist = INT_MAX;
for (int i=0; i<10; i++)
@ -186,17 +197,18 @@ int nearestEmptyDestinationHole(const int hole, const SGameState* const state)
int moveScore(Choice move, const SGameState* const state)
{
if (move.move == NULL)
if (move.move == NULL) // Si le coup indiqué n'existe pas, on renvoie INT_MIN
return INT_MIN;
// Si on doit s'arrêter dans une branche interdite, on renvoie INT_MIN
if (isStarBranch(move.move[move.size-1]) != 0 &&
isStarBranch(move.move[move.size-1]) != g_color &&
isStarBranch(move.move[move.size-1]) != ((g_color+2) % 6) + 1)
isStarBranch(move.move[move.size-1]) != (int) g_color &&
isStarBranch(move.move[move.size-1]) != (int) ((g_color+2) % 6) + 1)
{
return INT_MIN;
}
// Sinon, on renvoie un score calculé en fonction de différents critères
return (distance(move.move[0], g_destination) - distance(move.move[move.size - 1], g_destination)) * 8 // +8 pour chaque case parcourue vers l'avant, -8 pour celles parcourues vers l'arrière
+ (distance(move.move[0], g_destination)) * 2 // +2 points pour chaque case séparant le pion de l'arrivée (pour ne pas trop laisser les pions derrière)
+ (distance(move.move[0], nearestEmptyDestinationHole(move.move[0], state)) - (distance(move.move[move.size - 1], nearestEmptyDestinationHole(move.move[0], state)))) // On tente de se rapprocher de la case d'arrivée vide la plus proche
@ -207,45 +219,42 @@ List getPieceBestMove(Tree* root, List bestMoves, const SGameState* const state)
{
if (root == NULL)
return bestMoves;
// On transforme le nœud passé en paramètre en choix de mouvement
Choice tmp = treeToMoveChoice(root, state);
if (tmp.move != NULL)
if (tmp.move != NULL) // Si le mouvement existe
{
if (bestMoves == NULL)
{
append(&bestMoves, tmp);
}
else if (tmp.score >= bestMoves->data.score)
if (bestMoves == NULL) // Si on n'a actuellement aucun mouvement dans la liste
append(&bestMoves, tmp); // On rajoute le mouvement à la liste des choix possibles
else if (tmp.score >= bestMoves->data.score) // Si on a des mouvements dans la liste et que le score du mouvement à examiner est au moins égal à ceux des mouvements dans la liste, on va le rajouter
{
if (tmp.score > bestMoves->data.score)
{
deleteList(&bestMoves);
}
if (tmp.score > bestMoves->data.score) // Si le mouvement à rajouter est meilleur que ceux de la liste
deleteList(&bestMoves); // On vide d'abord la liste
append(&bestMoves, tmp);
}
else
{
else // Si le mouvement à examiner est moins bon que ceux de la liste, on le supprime
free(tmp.move);
}
}
for (int i=0; i<6; i++)
for (int i=0; i<6; i++) // Ensuite, on continue à examiner les mouvements pour tous les sauts qui continuent le mouvement que l'on vient d'examiner
bestMoves = getPieceBestMove(root->child[i], bestMoves, state);
return bestMoves;
return bestMoves; // On retourne la liste des meilleurs mouvements trouvés
}
Choice getBestMove(Tree* moves[], const SGameState* const state)
{
List bestMoves = NULL;
for (int i=0; i<10; i++)
for (int i=0; i<10; i++) // Pour chaque pion
{
List tmp = getPieceBestMove(moves[i], NULL, state);
List tmp = getPieceBestMove(moves[i], NULL, state); // On récupère la liste des meilleurs mouvements de ce pion
// On ne garde dans la liste finale que les mouvements ayant le plus haut score
if (tmp == NULL)
continue;
@ -258,8 +267,10 @@ Choice getBestMove(Tree* moves[], const SGameState* const state)
deleteList(&tmp);
}
// Ensuite, on choisit un coup au hasard dans cette liste
Choice c = pickRandom(bestMoves);
// Si le score est égal à INT_MIN (valeur indiquant un coup interdit), on libère la mémoire et on renvoie un mouvement vide
if (c.score == INT_MIN)
{
deleteList(&bestMoves);
@ -268,6 +279,7 @@ Choice getBestMove(Tree* moves[], const SGameState* const state)
return c;
}
// Sinon, on recopie le mouvement choisi et on le renvoie
int* moveCopy = malloc(sizeof(int) * c.size);
memcpy(moveCopy, c.move, sizeof(int) * c.size);
deleteList(&bestMoves);


+ 11
- 0
ai/strategy.h View File

@ -4,6 +4,7 @@
#include "gamestate.h"
#include "tree.h"
// Structure stockant un choix possible de mouvement
struct Choice
{
int* move;
@ -14,9 +15,19 @@ typedef struct Choice Choice;
#include "list.h"
// Liste tous les déplacements possibles pour une pièce, et les retourne sous forme d'arbre
Tree* findPieceValidMoves(const SGameState* state, const int hole);
/* Liste tous les déplacements possibles pour un joueur, et place ces mouvements dans moves.
Retourne le nombre de pièces qui peuvent bouger. */
int findAllPiecesValidMoves(const SGameState* state, const EPlayer color, Tree** moves);
/* Calcule le score d'un mouvement.
Le score est un entier calculé en fonction de plusieurs critères.
Le mouvement retenu par la stratégie est celui dont le score est le plus important. */
int moveScore(Choice move, const SGameState* const state);
/* Retourne le meilleur mouvement possible dans la situation actuelle, d'après les critères de choix de la stratégie. */
Choice getBestMove(Tree* moves[], const SGameState* const state);
#endif

+ 1
- 2
ai/tree.h View File

@ -20,7 +20,6 @@ Tree* newRoot(Data data);
Tree* newNode(Tree* parent, int pos, Data data);
void deleteTree(Tree* root);
int countChilds(Tree* node);
void printTree(Tree* root, char* printfOption, int offset);
int getParents(Tree* node, int** parents); // Don't forget free(*parents)
int getParents(Tree* node, int** parents); // Ne pas oublier free(*parents)
#endif

Loading…
Cancel
Save