166 lines
4.6 KiB
C
166 lines
4.6 KiB
C
|
|
/* src/game_logic.c */
|
||
|
|
|
||
|
|
#include "game_logic.h"
|
||
|
|
#include "utils.h"
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
|
||
|
|
/* --- Utilitaires de positionnement --- */
|
||
|
|
|
||
|
|
/* Convertit un index 1D en coordonnées 2D */
|
||
|
|
static void index_to_coords(int index, int cols, int *r, int *c)
|
||
|
|
{
|
||
|
|
*r = index / cols;
|
||
|
|
*c = index % cols;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Trouve les indices des tuiles adjacentes à la case vide */
|
||
|
|
static int find_adjacent_indices(int idx, int rows, int cols, int *out_indices)
|
||
|
|
{
|
||
|
|
int r = idx / cols;
|
||
|
|
int c = idx % cols;
|
||
|
|
int n = 0;
|
||
|
|
|
||
|
|
if (r > 0) out_indices[n++] = (r - 1) * cols + c; /* Haut */
|
||
|
|
if (r < rows - 1) out_indices[n++] = (r + 1) * cols + c; /* Bas */
|
||
|
|
if (c > 0) out_indices[n++] = r * cols + (c - 1); /* Gauche */
|
||
|
|
if (c < cols - 1) out_indices[n++] = r * cols + (c + 1); /* Droite */
|
||
|
|
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/* --- Fonctions publiques --- */
|
||
|
|
|
||
|
|
/* Initialise le jeu à l'état résolu et alloue la mémoire */
|
||
|
|
void game_init(game_state_t *state, int rows, int cols, tile_t **tiles)
|
||
|
|
{
|
||
|
|
int num_tiles = rows * cols;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
state->rows = rows;
|
||
|
|
state->cols = cols;
|
||
|
|
state->moves = 0UL;
|
||
|
|
state->is_solved = 1;
|
||
|
|
state->all_tiles = tiles;
|
||
|
|
|
||
|
|
/* Allocation dynamique de la permutation (malloc) */
|
||
|
|
state->permutation = (int *)malloc(num_tiles * sizeof(int));
|
||
|
|
|
||
|
|
if (state->permutation == NULL) {
|
||
|
|
fprintf(stderr, "Erreur: Allocation permutation echouee.\n");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Remplissage à l'état résolu : IDs 0, 1, 2, ... N-2 */
|
||
|
|
for (i = 0; i < num_tiles - 1; i++) {
|
||
|
|
state->permutation[i] = i;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* La dernière case est la case vide (-1) */
|
||
|
|
state->permutation[num_tiles - 1] = -1;
|
||
|
|
state->empty_index = num_tiles - 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Libère la mémoire allouée par game_init */
|
||
|
|
void game_free(game_state_t *state)
|
||
|
|
{
|
||
|
|
if (state->permutation != NULL) {
|
||
|
|
free(state->permutation);
|
||
|
|
state->permutation = NULL;
|
||
|
|
}
|
||
|
|
/* Note: tiles sont libérés par images_free_tiles dans main.c */
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Vérifie si la tuile peut bouger (adjacence) */
|
||
|
|
int game_can_move(const game_state_t *state, int index_to_move)
|
||
|
|
{
|
||
|
|
int r_tile, c_tile;
|
||
|
|
int r_empty, c_empty;
|
||
|
|
|
||
|
|
if (index_to_move == state->empty_index) return 0;
|
||
|
|
if (index_to_move < 0 || index_to_move >= state->rows * state->cols) return 0;
|
||
|
|
|
||
|
|
index_to_coords(index_to_move, state->cols, &r_tile, &c_tile);
|
||
|
|
index_to_coords(state->empty_index, state->cols, &r_empty, &c_empty);
|
||
|
|
|
||
|
|
/* Test d'adjacence géographique (manhattan distance = 1) */
|
||
|
|
if (abs_val(r_tile - r_empty) + abs_val(c_tile - c_empty) == 1) {
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Déplace la tuile vers la case vide si possible */
|
||
|
|
int game_move_tile(game_state_t *state, int index_to_move)
|
||
|
|
{
|
||
|
|
int temp_tile_id;
|
||
|
|
|
||
|
|
if (!game_can_move(state, index_to_move)) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Échange des valeurs (swap) */
|
||
|
|
temp_tile_id = state->permutation[index_to_move];
|
||
|
|
state->permutation[index_to_move] = state->permutation[state->empty_index];
|
||
|
|
state->permutation[state->empty_index] = temp_tile_id;
|
||
|
|
|
||
|
|
/* Mise à jour de la nouvelle position de la case vide */
|
||
|
|
state->empty_index = index_to_move;
|
||
|
|
|
||
|
|
/* Mise à jour des compteurs */
|
||
|
|
state->moves++;
|
||
|
|
state->is_solved = game_is_solved(state);
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Vérifie si le plateau est dans l'état résolu */
|
||
|
|
int game_is_solved(const game_state_t *state)
|
||
|
|
{
|
||
|
|
int num_tiles = state->rows * state->cols;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
/* La case vide doit être en dernière position (-1) */
|
||
|
|
if (state->permutation[num_tiles - 1] != -1) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Toutes les autres tuiles doivent être dans l'ordre (ID = Position) */
|
||
|
|
for (i = 0; i < num_tiles - 1; i++) {
|
||
|
|
if (state->permutation[i] != i) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Mélange garanti solvable par N mouvements aléatoires */
|
||
|
|
void game_shuffle_safe(game_state_t *state, int steps)
|
||
|
|
{
|
||
|
|
int prev_move = -1;
|
||
|
|
int choices[4];
|
||
|
|
int n_choices;
|
||
|
|
int pick_index;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
for (i = 0; i < steps; i++) {
|
||
|
|
n_choices = find_adjacent_indices(state->empty_index, state->rows, state->cols, choices);
|
||
|
|
|
||
|
|
/* Choisir une tuile adjacente au hasard, sans revenir en arrière immédiatement */
|
||
|
|
do {
|
||
|
|
pick_index = choices[random_int(n_choices)];
|
||
|
|
} while (n_choices > 1 && pick_index == prev_move);
|
||
|
|
|
||
|
|
/* Déplacer la tuile (utilise la logique de déplacement) */
|
||
|
|
game_move_tile(state, pick_index);
|
||
|
|
|
||
|
|
/* L'ancienne position vide est la tuile qui vient d'être déplacée (pour la prochaine itération) */
|
||
|
|
prev_move = pick_index;
|
||
|
|
}
|
||
|
|
|
||
|
|
state->moves = 0UL;
|
||
|
|
state->is_solved = 0;
|
||
|
|
}
|