You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

289 lines
9.9 KiB

#include "strategy.h"
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <stdio.h>
#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 où 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); // 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++) // Pour chacune de ces cases
if (reachable[i] != -1) // Si elle existe
{
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 été 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) // Tant qu'il reste des mouvements que l'on peut continuer
{
Tree* current = pop(&queue); // On récupère le nœud du mouvement à continuer
for (int i=0; i<6; i++) // Pour chacune des 6 directions possibles
{
int neighbour = getNeighbour(current->data, (Direction) i); // On récupère le voisin de la case actuelle
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]) // 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; // 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
}
}
}
}
deleteQueue(&queue);
free(tmpState);
return root;
}
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; // Compte les pièces pouvant bouger
for (int i=0; i<10; i++) // Pour chacune de ces pièces
{
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; // On stocke l'arbre des mouvements
movables++; // Et on incrémente le compteur de pièces pouvant bouger
}
else // Si la pièce ne peut pas bouger
{
deleteTree(pieceMoves); // On efface l'arbre des mouvements de cette pièce
moves[i] = NULL; // Et on ne stocke que NULL
}
}
return movables;
}
Choice treeToMoveChoice(Tree* root, const SGameState* const state)
{
int* array;
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];
array[i] = array[size-i-1];
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); // 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)
{
if (xa < xb)
xa ++;
else
xa --;
}
else if (ya < yb)
{
ya ++;
if (xa < xb)
xa ++;
}
else
{
ya --;
if (xa > xb)
xa --;
}
distance ++;
}
return distance;
}
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) == (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++)
{
if (state->board[destination[i]] != none)
continue;
int dist = distance(hole, destination[i]);
if (dist < minDist)
{
min = i;
minDist = dist;
}
}
return destination[min];
}
int moveScore(Choice move, const SGameState* const state)
{
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]) != (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
;
}
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) // Si le mouvement existe
{
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) // Si le mouvement à rajouter est meilleur que ceux de la liste
deleteList(&bestMoves); // On vide d'abord la liste
append(&bestMoves, tmp);
}
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++) // 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; // 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++) // Pour chaque pion
{
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;
if (bestMoves != NULL && bestMoves->data.score < tmp->data.score)
deleteList(&bestMoves);
if (bestMoves == NULL || bestMoves->data.score <= tmp->data.score)
appendList(&bestMoves, tmp);
else
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);
c.move = NULL;
c.size = 0;
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);
c.move = moveCopy;
return c;
}