#include "main.h"
// D'où s'exécute tout le jeu
// L'origine de tout
int main(void) {
afficher_logo(); // On affiche le (magnifique) logo
sleep(3); // On met en pose le programme 3 secondes
// Initialisation de l'interface graphique (mode texte avancé NCurses)
initialiser_ncurses();
int numero_niveau; // On déclare la variable ici au lieu de la placer dans un case du switch pour éviter les erreurs
int state = STATE_MAINMENU; // Contient l'état actuel de la navigation
while (state != STATE_QUIT) { // tant que l'état n'est pas "quitter"
switch(state) {
case STATE_MAINMENU:
switch (afficher_menu_principal()) {
case MAINMENU_PLAY:; // Si l'option choisie est "Jouer"
numero_niveau = choix_du_niveau(); // On demande le niveau que le joueur souhaite jouer
jouer_niveau(numero_niveau); // On met le niveau en route correspondant à "numero_niveau"
break;
case MAINMENU_RULES:; // Si l'option choisie est "Règles"
afficher_regles(); // On affiche les règles
break;
case MAINMENU_CONTROLS:; // Si l'option choisie est "Controles"
afficher_controles(); // On affche les controles
break;
case MAINMENU_CREDITS:; // Si l'option choisie est "Credits"
afficher_credits(); // On affiche les credits
break;
case MAINMENU_SCOREBOARD:; // Si l'option choisie est "Leaderboards"
numero_niveau = choix_du_niveau(); // On demande le niveau dont le joueur souhaite voir les scores
afficher_liste_niveau_scoreboard(numero_niveau); // On affiche un tableau de score du niveau correspondant à "numero_niveau"
break;
case MAINMENU_QUIT:; // Si l'option choisie est "Quitter"
if (afficher_menu_quitter() == 1) { // Si l'utilisateur sélectionne "Oui"
// On met l'état sur "quitter"
// Ce qui fait sortir de la boucle principale et quitte le programme
state = STATE_QUIT;
}
break;
}
break;
}
}
fermer_ncurses(); // Fermer toutes les fenêtres NCurses et revenir au mode texte simple
system("clear"); // Permet d'enlever le logo, sans ça, il reste et c'est moche ¯\_(ツ)_/¯.
return 0;
}
#include "main.h"
// Démarrer le mode graphique de Ncurses
void initialiser_ncurses(void) {
initscr(); // Initialisation de l'écran standard de Ncurses (stdscr)
noecho(); // Ne pas écrire ce que l'utilisateur tape dans la saisie
curs_set(false); // Cacher le curseur
// Si le terminal ne supporte pâs les couleurs, on termine l'initialisation
if (!has_colors()) {
return;
} else { // Sinon, on déclare les couleurs du mode graphique
// Activation du mode couleur de ncurses
start_color();
// Déclaration des palettes de couleur
initialiser_couleur(COLOR_RED); // Création de la couleur "rouge"
initialiser_couleur(COLOR_GREEN); // Création de la couleur "vert"
initialiser_couleur(COLOR_YELLOW); // Création de la couleur "jaune"
initialiser_couleur(COLOR_BLUE); // Création de la couleur "bleu"
initialiser_couleur(COLOR_MAGENTA); // Création de la couleur "magenta"
initialiser_couleur(COLOR_CYAN); // Création de la couleur "cyan"
initialiser_couleur(COLOR_WHITE); // Création de la couleur "blanc"
}
}
// Désinitialisation de Ncurses
void fermer_ncurses(void) {
endwin(); // Fermer toutes les fenêtres de ncurses actuellement ouvertes et quitter le mode "graphique"
}
// Initialisations des couleurs (pour Ncurses)
void initialiser_couleur(int couleur) {
// La librairie ncurses gère les couleurs d'une façon particulière:
// On doit déclarer une paire de couleurs de fond de texte avec un numéro de paire qui constituent une "palette"
// Exemple: paire 1 = rouge en fond et blanc en couleur de texte.
// Ici, on utilise un petit raccourci: Vu que les valeurs des couleurs de base du terminal vont de 0 à 7, on peut initialiser une paire avec ce même numéro
// Exemple:
// Paire #1:
// Couleur de texte: 1 (ROUGE)
// Couleur de fond: Noir
init_pair(couleur, couleur, COLOR_BLACK);
}
// Calcule le centre d'une zone rectangulaire
// Cette zone rectangulaire est définie comme suit :
/*
gauche (axe X)
v
haut > +-----------------+ ^
(axe Y) | | |
| | |
| X | | hauteur (axe Y)
| x;y (centre) | |
| | |
+-----------------+ V
<---------------->
largeur (axe X)
Les pointeurs x et y sont les valeurs de "retour" de la fonction
*/
void calculer_centre(int hauteur, int largeur, int haut, int gauche, int* y, int* x) {
*y = haut + (hauteur / 2); // Attribution de la hauteur dans y
*x = gauche + (largeur / 2); // Attribution de la largeur dans x
}
// Centre une fenêtre dans une zone rectangulaire définie par les valeurs haut, gauche, hauteur et largeur
// La fenêtre aura la taille spécifiée par hauteur_cible et largeur_cible
// (voir explications de calculer_centre pour plus d'informations)
void centrer_fenetre(WINDOW* fenetre, int hauteur_cible, int largeur_cible, int hauteur, int largeur, int haut, int gauche) {
//Initialisation des variabes "coordonnées"
int y, x;
calculer_centre(hauteur, largeur, haut, gauche, &y, &x); // calcule le centre de la fenêtre
wresize(fenetre, hauteur_cible, largeur_cible); //Change la taille de la fenêtre
mvwin(fenetre, y - (hauteur_cible / 2), x - (largeur_cible / 2)); //Change la position de la fenêtre
}
// Créer une nouvelle fenêtre de taille 0
WINDOW* nouvelle_fenetre(void) {
WINDOW* fenetre = newwin(0, 0, 0, 0); // Nouvelle fenêtre de taille et position 0
keypad(fenetre, true); // Ne pas attendre l'appui sur entrer pour recevoir la saisie
return fenetre; //renvoi fenetre
}
// Efface l'écran et l'actualise
void effacer_ecran(void) {
erase(); //Efface l'écran
refresh(); //Actualise l'écran
}
#include "main.h"
// Déplace le joueur (si possible) dans la direction indiquée
void deplacement (niveau_t* n, char direction){
// Pointeurs vers 1/2 case(s) en avant avant de se déplacer
point_t *un_en_avant, *deux_en_avant;
// On copie le niveau afin de ne pas modifier l'état précédent (par même référence mémoire)
niveau_t* niveau = copier_niveau(n);
// On calcule les coordonnées des cases un pas et deux pas en avant (en fonction de la direction)
switch (direction){
case DIR_UP: // Direction choisie : HAUT
un_en_avant = nouveau_point(niveau->perso->colonne + 0, niveau->perso->ligne - 1);
deux_en_avant = nouveau_point(niveau->perso->colonne + 0, niveau->perso->ligne - 2);
break;
case DIR_DOWN: // Direction choisie : BAS
un_en_avant = nouveau_point(niveau->perso->colonne + 0, niveau->perso->ligne + 1);
deux_en_avant = nouveau_point(niveau->perso->colonne + 0, niveau->perso->ligne + 2);
break;
case DIR_LEFT: // Direction choisie : GAUCHE
un_en_avant = nouveau_point(niveau->perso->colonne - 1, niveau->perso->ligne + 0);
deux_en_avant = nouveau_point(niveau->perso->colonne - 2, niveau->perso->ligne + 0);
break;
case DIR_RIGHT: // Direction choisie : DROITE
un_en_avant = nouveau_point(niveau->perso->colonne + 1, niveau->perso->ligne + 0);
deux_en_avant = nouveau_point(niveau->perso->colonne + 2, niveau->perso->ligne + 0);
break;
default: // Direction invalide
return;
}
// Si il y a une caisse devant nous, et que la case d'après est libre, on pousse la caisse
if (caisse_sur_terrain(niveau, un_en_avant->colonne, un_en_avant->ligne)
&& case_libre_sur_terrain(niveau, deux_en_avant->colonne, deux_en_avant->ligne)){
// On remplace la case en face par une case libre du bon type (cible/vide)
switch(lecture_du_terrain_par_coordonnees(niveau, un_en_avant)){
// Si la case en face est une caisse sur une cible
case TILE_CRATE_ON_TARGET:
//On remplace par une cible
place_sur_terrain_par_coordonnees(niveau, un_en_avant, TILE_TARGET);
break;
// Si la case en face est une caisse simple
case TILE_CRATE:
//On remplace par le néant le plus total
place_sur_terrain_par_coordonnees(niveau, un_en_avant, TILE_EMPTY);
break;
}
// On remplace la case un cran plus loin (que la case en face) par une case du bon type (caisse/caisse sur cible)
switch(lecture_du_terrain_par_coordonnees(niveau, deux_en_avant)){
// Si la case deux pas en avant est vide
case TILE_EMPTY:
//On place une caisse
place_sur_terrain_par_coordonnees(niveau, deux_en_avant, TILE_CRATE);
break;
// Si la case deux pas en avant est une cible
case TILE_TARGET:
//On remplace par une caisse sur une cible
place_sur_terrain_par_coordonnees(niveau, deux_en_avant, TILE_CRATE_ON_TARGET);
break;
}
}
// Si la case d'en face est libre
if (case_libre_sur_terrain(niveau, un_en_avant->colonne, un_en_avant->ligne)) {
// On "déplace" le personnage
niveau->perso->colonne = un_en_avant->colonne;
niveau->perso->ligne = un_en_avant->ligne;
//On ajoute un niveau dans "etats_niveaux"
ajouter_niveau(etats_niveaux, niveau);
} else {
//On libère la mémoire alouée par le niveau
liberation_du_niveau(niveau);
}
//On libère la mémoire alouée par "un_en_avant"
free(un_en_avant);
//On libère la mémoire alouée par "deux_en_avant"
free(deux_en_avant);
}
// Annule le dernier déplacement
void annuler_deplacement(void) {
//Si le joueur a effectuer au moins une action, reviens à la dernière position
if (etats_niveaux->taille > 1) enlever_dernier_niveau (etats_niveaux);
}
#include "main.h"
// Permet de lancer un niveau, grâce a un numéro.
// Gère la totalité du jeu, en passant par son lancemant, son déroulement, sa fin (déchargement des niveaux)
void jouer_niveau(int numero_niveau) {
int meilleur_score = lecture_du_score(numero_niveau);
DEBUT_JEU:;
// Attribution d'une nouvelle liste de niveaux (liste de taille 10)
etats_niveaux = nouvelle_liste_niveaux (10);
// Initialisation de niveau
// Attribution de la lecture de niveau sur le numéro de niveau choisi précdemment
niveau_t* niveau = lecture_du_niveau(numero_niveau);
if (!niveau) return; //Si le niveau n'existe pas, on quitte la fonction (pas de jeu)
// Initialisation de "scores"
// Attribution de la lecture de la liste des scores
liste_score_t* scores = lire_liste_scores(numero_niveau);
// Si il existe pas déjà un tableau de score
if (scores == NULL) {
//Création d'un tableau de score pour le niveau
scores = nouvelle_liste_scores();
}
// Ajout d'un niveau
ajouter_niveau (etats_niveaux, niveau);
int saisie = 0;
// On vérifie le niveua est terminé (si toutes les cases à remplir sont remplies)
while (nombre_de_caisse_restante_sur_terrain(haut_de_liste(etats_niveaux)) > 0) {
niveau = haut_de_liste(etats_niveaux);
saisie = affichage_niveau_ncurses(niveau, numero_niveau);
//On analyse la touche saisie par le joueur
switch (saisie) {
case LEAVE:
if (afficher_menu_quitter() == 1) { // Permet d'afficher un menu de confirmation avant de quitter
goto FIN_JEU; // Si on entre dans le if, on est redirigé à la fin de la boucle. et on apparait au menu principale
}
break;
case RESTART:
if (afficher_menu_recommencer() == 1){ // Permet d'afficher un menu de confirmation avant de recommencer
while (etats_niveaux->taille > 0) { // Permet libérer la totalité du tableau de niveau.
enlever_dernier_niveau(etats_niveaux);
}
goto DEBUT_JEU;
}
break;
case HELP:
afficher_controles();
break;
case CANCEL:
annuler_deplacement();
break;
default:
deplacement(niveau, saisie);
break;
}
}
int nb_pas = etats_niveaux->taille - 1;
//On vérifie si le score du joueur est
//meilleur que le meilleur score actuel
if (nb_pas < meilleur_score || meilleur_score < 0) {
ecriture_du_score(numero_niveau, nb_pas);
}
//On vérifie si le score du joueur possède sa place dans le tableau des meilleurs scores
if (inserable_dans_liste(scores, nb_pas)) { // Message
char message[100];
// Message de meilleur score, il n'est pas affiché si il n'y avait aucun meilleur score auparavant
// Le message s'affiche si 2 joueurs font un score égaux
if(meilleur_score >= nb_pas){
sprintf(message, "Vous avez terminé le niveau %i !!\nEt en seulement %d coups !!\nFélicitations !!", numero_niveau, nb_pas);
menu_message("NOUVEAU MEILLEUR SCORE !!", message, 40, 3, COLOR_RED, COLOR_YELLOW);
}else{ // Message de félicitation, quand un joueur a sa place dans le tableau des scores, mais pas à la première place.
sprintf(message, "Bravo, vous avez termine le niveau %i!\nEt en seulement %d coups !!", numero_niveau, nb_pas);
menu_message("Niveau fini", message, 40, 2, COLOR_GREEN, COLOR_YELLOW);
}
// On demande le nom du joueur, et on le place dans le tableau des scores
inserer_score_dans_liste(scores, nb_pas, nom_du_joueur());
enregistrer_liste_scores(scores, numero_niveau);
// Et on affiche le scoreboard après qu'il ai valider son nom
afficher_liste_niveau_scoreboard(numero_niveau);
} else { // Message s'affichant quand le joueur n'as pas sa place dans le tableau des scores
char message[100];
sprintf(message, "Bravo, vous avez termine le niveau %i!\nMais en trop de coups ...", numero_niveau);
menu_message("Niveau fini", message, 40, 2, COLOR_GREEN, COLOR_WHITE);
}
FIN_JEU:;
//On libère la mémoire que prend les scores
liberer_liste_scores(scores);
//Tant qu'on est pas de retour au premier état du terrain
//(où le joueur n'a pas encore effectuer de coup)
while (etats_niveaux->taille > 0) {
enlever_dernier_niveau(etats_niveaux); //Enlève le dernier niveau au tableau
}
}
#include "main.h"
// On utilise un tableau dynamique de niveau_t* pour stocker les
// etats du jeu au fur et a mesure que le joueur avance. Ainsi,
// etats_niveaux->memoire[0] est l'etat initial du jeu, et
// etats_niveaux->memoire[etats_niveaux->taille - 1] est l'etat courant.
// A chaque deplacement reussi, on ajoute une entree a la liste.
// A chaque annulation, on enleve le dernier element de la liste si il y en a plus qu'un.
liste_niveaux_t* etats_niveaux;
// Permet de créer une nouvelle liste de niveau, permettant par la suite d'utiliser le retour arrière
liste_niveaux_t* nouvelle_liste_niveaux(int taille) {
liste_niveaux_t *resultat = malloc(sizeof(liste_niveaux_t));
init_liste_niveaux (resultat, taille); //initialise la l
return resultat;
}
// Permet d'initialiser les instances d'une liste de niveau, en fonction de la taille donnée
void init_liste_niveaux(liste_niveaux_t* liste, int taille) {
liste->memoire = malloc (sizeof (niveau_t *) * taille);
liste->taille = 0;
liste->taille_memoire = taille;
}
// Permet de libérer la totalité des niveaux
void liberation_de_la_liste_niveaux(liste_niveaux_t* liste) {
free(liste->memoire);
liste->memoire = NULL;
liste->taille = 0;
liste->taille_memoire = 0;
}
// Permet d'agrandir la liste de niveau pour lui permettre d'accueillir un nouveau niveau (semblable à un tableau dynamique)
void agrandir_liste_de_niveaux(liste_niveaux_t *liste, int ajout) {
if (ajout > 0) {
niveau_t** nouveau = malloc(sizeof(niveau_t*) * (liste->taille_memoire + ajout));
memcpy(nouveau, liste->memoire, sizeof(niveau_t*) * liste->taille);
free(liste->memoire);
liste->memoire = nouveau;
liste->taille_memoire += ajout;
}
}
// Permet d'ajouter un niveau à une liste de niveau passer en paramètre
void ajouter_niveau(liste_niveaux_t *liste, niveau_t* niveau) {
if (liste->taille == liste->taille_memoire) {
agrandir_liste_de_niveaux (liste, 10);
}
liste->memoire[liste->taille] = niveau;
niveau->indice = liste->taille;
liste->taille += 1;
}
// Permet de retirer le dernier niveau de la liste passée en paramètre (après une annulation de déplacement)
void enlever_dernier_niveau(liste_niveaux_t *liste) {
if (liste->taille > 0) {
liberation_du_niveau (haut_de_liste(etats_niveaux));
liste->taille -= 1;
}
}
// Permet de renvoyer le dernier élément de la liste, si la liste ne contient rien, cela renvoit NULL
niveau_t* haut_de_liste(liste_niveaux_t* liste) {
if (liste->taille == 0) return NULL;
return liste->memoire[liste->taille - 1];
}
#include "main.h"
// Permet de créer un nouveau score
score_t* nouveau_score(void) {
return malloc(sizeof(score_t));
}
// Permet d'affecter les valeurs passées en paramètres à une instance de score_t déjà allouée
void initialiser_score(score_t* score, int points, char* nom) {
score->score = points;
memset(score->nom, '\0', 10);
strncpy(score->nom, nom, 9);
}
// Permet de libérer la mémoire d'une instance score_t
void liberer_score(score_t* score) {
free(score);
}
// Permet de créer une nouvelle liste de scores
liste_score_t* nouvelle_liste_scores(void) {
liste_score_t* scores = malloc(sizeof(liste_score_t));
scores->memoire = malloc(SCORE_BUFFER_SIZE * sizeof(score_t*));
for (int i = 0; i < SCORE_BUFFER_SIZE; ++i) {
scores->memoire[i] = nouveau_score();
initialiser_score(scores->memoire[i], 0, "");
}
scores->taille = 0;
return scores;
}
// Permet de libérer la mémoire de la liste de score
void liberer_liste_scores (liste_score_t* scores) {
for (int i = 0; i < SCORE_BUFFER_SIZE; ++i) {
liberer_score(scores->memoire[i]);
}
free(scores->memoire);
free(scores);
}
// Permet de lire la liste scores du niveau correspondant à "numero_niveau"
liste_score_t* lire_liste_scores (int numero_niveau) {
char chemin_du_fichier[100];
sprintf(chemin_du_fichier, "./niveau/score_multi_%d", numero_niveau);
FILE* fichier = fopen(chemin_du_fichier, "r");
if (!fichier) return NULL;
liste_score_t* liste_score = nouvelle_liste_scores();
int nombre_scores;
fscanf(fichier, "%d", &nombre_scores);
liste_score->taille = min(nombre_scores, SCORE_BUFFER_SIZE);
for (int numero_score = 0; numero_score < nombre_scores; numero_score++) {
fscanf(fichier, "%d %s", &(liste_score->memoire[numero_score]->score), liste_score->memoire[numero_score]->nom);
}
return liste_score;
}
// Permet d'enregistrer une liste de scores dans un fichier niveau/score_multi_<nb>
// Où nb est numero_niveau
void enregistrer_liste_scores (liste_score_t* scores, int numero_niveau) {
char nom_fichier[100];
sprintf(nom_fichier, "./niveau/score_multi_%d", numero_niveau);
FILE* fichier = fopen(nom_fichier, "w");
if (!fichier) return;
score_t* score;
fprintf(fichier, "%d\n", scores->taille);
for (int i = 0; i < scores->taille; ++i) {
score = scores->memoire[i];
fprintf(fichier, "%d %s\n", score->score, score->nom);
}
fclose(fichier);
}
// Retourne une vrai si
// le nombre de points passé en paramètre est un nouveau high score dans la liste
// ou si il reste au moins un emplacement libre dans la liste
bool inserable_dans_liste (liste_score_t* scores, int points) {
return scores->taille == 0 || points < scores->memoire[scores->taille - 1]->score || scores->taille < SCORE_BUFFER_SIZE;
}
// Permet d'insèrer le score défini par points/nom dans la liste scores
// L'insertion n'a lieu que si le score est meilleur que le dernier
// Si un score pour le joueur nom existe déjà, on le met à jour à la place
void inserer_score_dans_liste (liste_score_t* scores, int points, char* nom) {
if (!inserable_dans_liste(scores, points)) return;
score_t* score;
for (int numero_score = 0; numero_score < SCORE_BUFFER_SIZE; numero_score++) {
score = scores->memoire[numero_score];
if (numero_score < scores->taille) {
if (strncmp(nom, score->nom, 8) == 0) {
score->score = min(score->score, points);
trier_liste_score(scores);
return;
}
} else {
initialiser_score(score, points, nom);
scores->taille++;
trier_liste_score(scores);
return;
}
}
initialiser_score(scores->memoire[scores->taille - 1], points, nom);
trier_liste_score(scores);
}
// Permet de trie les scores de la liste par ordre croissant
void trier_liste_score (liste_score_t* scores) {
score_t** tab = scores->memoire;
score_t* tmp;
for (int fixe = 0; fixe < scores->taille; ++fixe) {
for (int curseur = fixe; curseur < scores->taille; ++curseur) {
if (tab[curseur]->score < tab[fixe]->score) {
tmp = tab[fixe];
tab[fixe] = tab[curseur];
tab[curseur] = tmp;
}
}
}
}
#include "main.h"
// Renvoie la valeur minimale entrée en paramètre
int min (int premier, int deuxieme){
//Compare les deux paramètres, et permet de renvoyer le plus petit
if(premier > deuxieme){
return deuxieme;
}else{
return premier;
}
}
// Renvoie la valeur maximale entrée en paramètre
int max (int premier, int deuxieme){
//Compare les deux paramètres, et permet de renvoyer le plus grand
if(premier < deuxieme){
return deuxieme;
}else{
return premier;
}
}
// Renvoie la lettre majuscule en minuscule.
int majuscule_en_minuscule(int lettre) {
// 65 = A ; Z = 90
if(lettre >= 'A' && lettre <= 'Z'){
return lettre + 32;
}else{
return lettre;
}
}
#include "main.h"
// Affiche le niveau de manière (très) brut.
void afficher_logo(void){
system("clear");
printf(
" ▄▄ \n"
" ▄██ ▄█▀▀▀█▄█ ▀███ \n"
" ██ ▄██ ▀█ ██ \n"
" ██▄████▄ ▄█▀██▄ ▀████████▄ ▀███▄ ▄██▀██▄ ██ ▄██▀ ▄██▀██▄ \n"
" ██ ▀████ ██ ██ ██ ▀█████▄██▀ ▀██ ██ ▄█ ██▀ ▀██\n"
" ██ ██ ▄█████ ██ ██ ▄ ▀████ ██ ██▄██ ██ ██\n"
" ██▄ ▄████ ██ ██ ██ ██ ████▄ ▄██ ██ ▀██▄ ██▄ ▄██\n"
" █▀█████▀ ▀████▀██▄████ ████▄ █▀█████▀ ▀█████▀▄████▄ ██▄▄ ▀█████▀ \n"); // 1017 chars > 552 symboles uniques
}
// Permet de demander à l'utilisateur un entier, pour ensuite choisir le niveau
int choix_du_niveau(void) {
return menu_saisie_nombre("Choisis un niveau");
}
// Permet d'afficher le menu des règles, que nous pouvons quitter avec la touche entrée
void afficher_regles(void) {
char regles_entiere[1000];
sprintf(regles_entiere, "Règles classiques du Bansoko :\nLe joueur doit placer toutes les caisses (%c)\nsur toutes les cibles (%c)\nMais attention !\nUn joueur est trop faible pour pousser 2 caisses a la fois ...\nOu même les tirer !\n\nPour quitter, appuyez sur ENTREE", DISPLAY_TILE_CRATE, DISPLAY_TILE_TARGET);
menu_message("Regles : ", regles_entiere, 50, 9, COLOR_GREEN, COLOR_WHITE);
}
// Permet d'afficher le menu des crédits, que nous pouvons quitter avec la touche entrée
void afficher_credits(void) {
char credits_entier[1000];
sprintf(credits_entier, "Un grand merci et bravo aux contributeurs\ndu projets\nJe cite :\n ALMEIDA Néo\n ROUX Hugo\n VANOORENBERGHE Amaury\nEt aux librairies natives\nEt à la superbe librairie Ncurses !\n\nPour quitter, appuyez sur ENTREE\n");
menu_message("Credits : ", credits_entier, 50, 10, COLOR_BLUE, COLOR_WHITE);
}
// Permet d'afficher le menu des controles, que nous pouvons quitter avec la touche entrée
void afficher_controles(void) {
char controles[1000];
sprintf(controles, "Se deplacer: fleches / ZQSD\nAnnuler deplacement: %c\nRecommencer: %c\nQuitter: %c\n\nPour quitter, appuyez sur ENTREE", CANCEL, RESTART, LEAVE);
menu_message("Controles : ", controles, 50, 6, COLOR_MAGENTA, COLOR_WHITE);
}
// Permet d'afficher une boite qui attend un chiffre, ce chiffre permet d'afficher les scores du niveau
// Le menu créer est quittable avec la touche entrée
void afficher_liste_niveau_scoreboard(int numero_niveau) {
FILE* fichier;
char nom_fichier[100];
sprintf(nom_fichier, "./niveau/niveau_%d", numero_niveau);
fichier = fopen(nom_fichier, "r");
if (!fichier) {
menu_message("Erreur", "Le niveau specifie n'existe pas", 32, 1, COLOR_RED, COLOR_WHITE);
return;
}
fclose(fichier);
int yMax, xMax; // Taille de la console en caractères
char titre[100];
sprintf(titre, "Niveau %d - Meilleurs scores", numero_niveau);
int largeur = max(40, strlen(titre));
const int marge = 8;
WINDOW* fenetre = nouvelle_fenetre();
WINDOW* zone_texte = derwin(fenetre, SCORE_BUFFER_SIZE + 2, largeur, 2, marge / 2);
liste_score_t* scores = lire_liste_scores(numero_niveau);
do {
getmaxyx(stdscr, yMax, xMax); // Lire la taille de la console
effacer_ecran();
centrer_fenetre(fenetre, 12, largeur + marge, yMax, xMax, 0, 0);
wrefresh(fenetre); // Raffraichissement de la fenêtre
wattron(fenetre, COLOR_PAIR(COLOR_CYAN)); // Texte en cyan activé
box(fenetre, 0, 0); // Dessine une bordure
wattron(fenetre, A_STANDOUT); // Texte en gras activé
mvwprintw(fenetre, 0, 2, titre);
wattroff(fenetre, A_STANDOUT); // Texte en gras désactivé
wattroff(fenetre, COLOR_PAIR(COLOR_CYAN)); // Texte en cyan désactivé
if (scores) {
wattron(zone_texte, A_BOLD);
mvwprintw(zone_texte, 0, 0, "Nom");
mvwprintw(zone_texte, 0, largeur - 5, "Score");
wattroff(zone_texte, A_BOLD);
char texte[20];
score_t* score;
for (int i = 0; i < scores->taille; ++i) {
score = scores->memoire[i];
if (i == 0) {
wattron(zone_texte, COLOR_PAIR(COLOR_YELLOW));
}
mvwprintw(zone_texte, 2 + i, 0, score->nom);
sprintf(texte, "%d coups", score->score);
mvwprintw(zone_texte, 2 + i, largeur - strlen(texte), texte);
wattroff(zone_texte, COLOR_PAIR(COLOR_YELLOW));
}
} else {
wattron(zone_texte, A_DIM); // Texte sombre activé
mvwprintw(zone_texte, 2, 0, "Aucun score disponible");
wattroff(zone_texte, A_DIM); // Texte sombre désactivé
}
wattron(fenetre, A_DIM); // Texte sombre activé
mvwprintw(fenetre, 10, 4, "Pour quitter, appuyer sur ENTREE");
wattroff(fenetre, A_DIM); // Texte sombre désactivé
} while (wgetch(fenetre) != KB_ENTER);
if (scores) {
liberer_liste_scores(scores);
}
}
// Permet d'afficher le menu principal, celui-ci peut être navigué grâce aux flêches directionnelles (haut et bas)
int afficher_menu_principal(void) {
char* entries[6];
entries[0] = "Jouer";
entries[1] = "Règles";
entries[2] = "Controles";
entries[3] = "Credits";
entries[4] = "Leaderboards";
entries[5] = "Quitter";
return menu_liste(17, 51, "Bansoko", entries, 6);
}
// Afficher un menu de confirmation (oui/non) navigable avec les flèches haut/bas
int afficher_menu_quitter(void) {
char* entries[2];
entries[0] = "Non";
entries[1] = "Oui";
return menu_liste(9, 20, "Quitter ?", entries, 2);
}
// Afficher un menu de confirmation (oui/non) navigable avec les flèches haut/bas, permettant de recommencer le niveau
int afficher_menu_recommencer(void) {
char* entries[2];
entries[0] = "Non";
entries[1] = "Oui";
return menu_liste(9, 20, "Recommencer ?", entries, 2);
}
// Affiche un menu composé d'une liste d'éléments
int menu_liste(int hauteur, int largeur, char* titre, char** elements, int nb_elements) {
int yMax, xMax; // Taille de la console en caractères
WINDOW* fenetre = nouvelle_fenetre(); // Nouvelle fenêtre
int selection = 0; // Element sélectionné
int input = '\0'; // Saisie
while (true)
{
getmaxyx(stdscr, yMax, xMax); // Lire la taille de la console
// Centrer la fenêtre
centrer_fenetre(fenetre, hauteur, largeur, yMax, xMax, 0, 0);
effacer_ecran(); // Efface l'ecran
wclear(fenetre); // Raffraichi la fenêtre
box(fenetre, 0, 0); // bordure de la fenêtre
mvwprintw(fenetre, 0, 2, titre); // Titre de la fenêtre
for (int num_element = 0; num_element < nb_elements; ++num_element) {
// Si l'élément actuel est sélectionné
if (selection == num_element) {
wattron(fenetre, A_STANDOUT); // Texte gras activé
wattron(fenetre, COLOR_PAIR(COLOR_RED)); // Texte rouge activé
mvwprintw(fenetre, 3 + (2 * num_element), 5, " "); // On affiche un espace (la barre rouge de l'élément sélectionné :D)
wattroff(fenetre, COLOR_PAIR(COLOR_RED)); // Texte rouge désactivé
}
//Affiche les multiples choix du menu
mvwprintw(fenetre, 3 + (2 * num_element), 7, elements[num_element]);
wattroff(fenetre, A_STANDOUT); // Texte gras désactivé
}
input = wgetch(fenetre); // Lecture de la saisie
switch (input) // Permet de naviguer dans le menu
{
case KB_UP:
selection--;
selection = selection < 0 ? 0 : selection;
break;
case KB_DOWN:
selection++;
selection = selection >= nb_elements ? nb_elements - 1 : selection;
break;
case KB_ENTER:
delwin(fenetre);
return selection;
break;
}
}
return -1;
}
// Affiche un menu permettant d'entrer (seulement) des nombres, que l'ont peut valider avec entrée, 9 chiffres sont acceptés. On peut les supprimer si besoin.
int menu_saisie_nombre(char* titre) {
int largeur_fenetre = max(strlen(titre), 9) + 10;
int yMax, xMax; // Console size in chars
WINDOW* fenetre = nouvelle_fenetre();
char texte_saisi[9] = { '\0' };
memset(texte_saisi, '\0', 9 * sizeof(char));
int longueur_texte = 0;
int saisie = '\0';
while (true)
{
longueur_texte = strlen(texte_saisi);
getmaxyx(stdscr, yMax, xMax); // Read the console size
centrer_fenetre(fenetre, 5, largeur_fenetre, yMax, xMax, 0, 0);
effacer_ecran();
wclear(fenetre); // Rafraîchi la fenêtre
box(fenetre, 0, 0); // Bordure de fenêtre
mvwprintw(fenetre, 0, 2, titre); // Titre de la fenêtre
wattron(fenetre, A_STANDOUT); // Surbrillance activée
mvwprintw(fenetre, 2, 5, texte_saisi);
if (longueur_texte < 9) {
wattron(fenetre, COLOR_PAIR(COLOR_YELLOW));
mvwprintw(fenetre, 2, 5 + longueur_texte, "_");
wattroff(fenetre, COLOR_PAIR(COLOR_YELLOW));
}
wattroff(fenetre, A_STANDOUT); // Surbrillance désactivée
saisie = wgetch(fenetre); // lecture saisie
switch (saisie)
{
case KB_BACKSPACE: // Effacer caractère
if (longueur_texte > 0) {
texte_saisi[longueur_texte - 1] = '\0';
}
break;
case KB_ENTER: // Valider saisie (seulement si saisie non vide)
if (longueur_texte == 0) break;
delwin(fenetre);
int numero = 10;
sscanf(texte_saisi, "%d", &numero);
return numero;
default:
// On accepte que les chiffres
if (longueur_texte < 9 && saisie >= '0' && saisie <= '9') {
texte_saisi[longueur_texte] = (char)saisie;
}
break;
}
}
return -1;
}
// Affiche un menu permettant d'entrer des chiffres, mais aussi des lettres, et des '_', que l'ont peut valider avec entrée, 8 caractère sont acceptés. On peut les supprimer si besoin.
char* menu_saisie_texte(char* titre, int longueur_chaine_max) {
int largeur_fenetre = max(strlen(titre), longueur_chaine_max) + 10;
int yMax, xMax; // Console size in chars
WINDOW* fenetre = nouvelle_fenetre();
char* texte_saisi = malloc(longueur_chaine_max * sizeof(char));
memset(texte_saisi, '\0', longueur_chaine_max * sizeof(char));
int longueur_saisie = 0;
int saisie = '\0';
while (true) {
longueur_saisie = strlen(texte_saisi);
getmaxyx(stdscr, yMax, xMax); // Lecture de la taille de la console
// Centrer fenetre
centrer_fenetre(fenetre, 5, largeur_fenetre, yMax, xMax, 0, 0);
effacer_ecran(); // Efface l'ecran
wclear(fenetre); // Raffraichi la fenêtre
box(fenetre, 0, 0); // Bordure de la fenêtre
mvwprintw(fenetre, 0, 2, titre); // Titre de la fenêtre
wattron(fenetre, A_STANDOUT); // Texte gras activé
mvwprintw(fenetre, 2, 5, texte_saisi);
if (longueur_saisie < longueur_chaine_max) {
mvwprintw(fenetre, 2, 5 + longueur_saisie, "_");
}
wattroff(fenetre, A_STANDOUT); // Texte gras désactivé
saisie = wgetch(fenetre); // Lire la saisie
switch (saisie) {
case KB_BACKSPACE:
if (longueur_saisie > 0) {
texte_saisi[longueur_saisie - 1] = '\0';
}
break;
case KB_ENTER:
delwin(fenetre);
return texte_saisi;
default:; // Saisie texte
// Ne pas autoriser la saisie si la longueur de chaine est déjà au maximum
if (longueur_saisie >= longueur_chaine_max) break;
// N'autoriser que les lettres (minuscules/majuscules), les chiffres, espaces et ttirets bas (underscores)
bool saisie_valide = false
|| (saisie == ' ' || saisie == '_')
|| (saisie >= '0' && saisie <= '9')
|| (saisie >= 'a' && saisie <= 'z')
|| (saisie >= 'A' && saisie <= 'Z');
if (saisie_valide) {
texte_saisi[longueur_saisie] = (char)saisie;
}
break;
}
}
return NULL;
}
// Affiche une fenêtre qui recouvre la fenêtre actuel, grâce à ses nombreux paramêtres, elle est customisable. On peut la quitter avec la touche entrée
void menu_message (char* titre, char* message, int largeur, int hauteur, int couleur, int couleur_texte) {
int yMax, xMax; // Taille de la console en caractères
WINDOW* fenetre = nouvelle_fenetre(); // Nouvelle fenêtre
WINDOW* zone_texte = derwin(fenetre, hauteur, largeur, 2, 2);
do {
getmaxyx(stdscr, yMax, xMax); // Lecture de la taille de la console
centrer_fenetre(fenetre, hauteur + 4, largeur + 4, yMax, xMax, 0, 0);
effacer_ecran();
wattron(fenetre, COLOR_PAIR(couleur)); // Sélection de la couleur de fond
wclear(fenetre);
box(fenetre, 0, 0);
wattron(fenetre, A_STANDOUT); // Texte gras activé
mvwprintw(fenetre, 0, 2, titre);
wattroff(fenetre, A_STANDOUT); // Texte gras désactivé
wattroff(fenetre, COLOR_PAIR(couleur)); // Désélection de la couleur de fond
wattron(zone_texte, COLOR_PAIR(couleur_texte)); // Sélection de la couleur de texte
mvwprintw(zone_texte, 0, 0, message);
wattroff(zone_texte, COLOR_PAIR(couleur_texte)); // Désélection de la couleur de texte
} while (wgetch(fenetre) != KB_ENTER);
delwin(zone_texte);
delwin(fenetre);
}
#include "main.h"
// Crée un nouveau niveau de taille nb_colonnes * nb_lignes et retourne un pointeur vers l'instance créée
niveau_t* nouveau_niveau (int nb_colonnes, int nb_lignes){
// Allocation de l'espace mémoire pour l'instance du niveau
niveau_t* niveau = malloc(sizeof(niveau_t));
// On assigne les valeurs des attributs de niveau_t
niveau->colonnes = nb_colonnes;
niveau->lignes = nb_lignes;
// On alloue un nouvel espace mémoire pour le terrain de nb_colonnes * nb_lignes cases
niveau->terrain = malloc(sizeof(char) * nb_colonnes * nb_lignes);
// Les coordonnées du joueur sont pour le moment inconnues...
niveau->perso = NULL;
// L'état précédent n'existe pas encore, on l'initialise
return niveau;
}
// Supprime une instance de type niveau_t passée en paramètre avec son pointeur
void liberation_du_niveau (niveau_t* niveau) {
// On libère la mémoire du terrain avant celle de l'instance
// Sinon on a une fuite de mémoire car on ne peut plus référencer niveau->terrain
free(niveau->perso);
free(niveau->terrain);
free(niveau);
}
// Affiche un niveau et renvoie la saisie de l'utilisateur
// Note : cette fonction récupère les inputs de l'utilisateur
// ATTENTION: Cette fonction se charge de l'affichage ET de la lecture de la saisie clavier
char affichage_niveau_ncurses (niveau_t* niveau, int numero_niveau) {
int yMax, xMax; // Taille de la console en caractères
int meilleur_score = lecture_du_score(numero_niveau);
char info[100];
int largeur = 0; // Largeur de la fenetre (création d'une variable car taille non constante)
WINDOW* fenetre = nouvelle_fenetre(); // Nouvelle fenêtre
int saisie = '\0';
while (true) {
getmaxyx(stdscr, yMax, xMax); // Lire la taille de la console
// Création du titre de la fenêtre:
// Si un high score existe déjà:
if (meilleur_score >= 0) {
sprintf(info, "Niveau %i | Record: %i coups | %i coups", numero_niveau, meilleur_score, etats_niveaux->taille - 1);
} else { // Sinon, si le niveau n'a pas de high score
sprintf(info, "Niveau %i | Record: aucun | %i coups", numero_niveau, etats_niveaux->taille - 1);
}
// Calcul de la largeur idéale de la fenêtre
largeur = 2 + max(niveau->colonnes, strlen(info));
// Centrer la fenêtre
centrer_fenetre(fenetre, niveau->lignes + 2, largeur + 2, yMax, xMax, 0, 0);
effacer_ecran(); // Efface l'ecran
box(fenetre, 0, 0); // Bordure de la fenêtre
// Permet de calculer le décalage horizontal du niveau pour le centrer dans la fenêtre
int decalage = (largeur - niveau->colonnes) / 2;
// Affichage du terrain
for (int ligne = 0; ligne < niveau->lignes; ++ligne) {
for (int colonne = 0; colonne < niveau->colonnes; ++colonne) {
//char case_terrain = lecture_du_terrain(niveau, colonne, ligne);
char case_affichee = '\0';
int attribut = modification_affichage_niveau(niveau, colonne, ligne, &case_affichee);
wattron(fenetre, attribut);
mvwaddch(fenetre, ligne + 1, colonne + 1 + decalage, case_affichee);
wattroff(fenetre, attribut);
wrefresh(fenetre);
}
}
//sprintf("Coups %c", niveau->nb_de_pas);
wattron(fenetre, A_STANDOUT);
mvwprintw(fenetre, 0, 2, info); // Titre de la fenetre (à changer pour afficher le niveau actuel)
//mvwprintw(fenetre, niveau->lignes+1, niveau->colonnes-5, nb_coups); // Nombre de pas (actuellement à 0 vu que c'est l'initialisation du niveau)
wattroff(fenetre, A_STANDOUT);
mvwprintw(fenetre, niveau->lignes + 1, 2, "[ F1 ou h : Afficher controles ]");
saisie = majuscule_en_minuscule(wgetch(fenetre));
//int saisie = wgetch(fenetre);
switch (saisie) {
case KB_UP:
case DIR_UP:
return DIR_UP;
case KB_DOWN:
case DIR_DOWN:
return DIR_DOWN;
case KB_LEFT:
case DIR_LEFT:
return DIR_LEFT;
case KB_RIGHT:
case DIR_RIGHT:
return DIR_RIGHT;
case LEAVE:
case RESTART:
case CANCEL:
case HELP:
return (char)saisie;
case KB_F1:
return HELP;
}
}
}
// Fonction en charge de lire une case de terrain et de retourner le bon symbole à afficher via le pointeur symbole
// Cette fonction renvoie un int correspondant à un code de mise en forme NCurses (couleur, gras, ...)
int modification_affichage_niveau (niveau_t* niveau, int x, int y, char* symbole) {
char case_terrain = lecture_du_terrain(niveau, x, y);
// COmme on n'enregistre pas le joueur comme position dans le terrain
// mais via le point_t niveau-> perso, on doit procéder à des vérifications avant l'affichage
// Si la case demandée est celle du joueur...
if (x == niveau->perso->colonne && y == niveau->perso->ligne) {
switch (case_terrain) {
case TILE_EMPTY: // Si la case est vide
case_terrain = TILE_PLAYER; // On dit que la case est un joueur
break;
case TILE_TARGET: // Si la case est une cible
case_terrain = TILE_PLAYER_ON_TARGET; // La case affichee sera un joueur sur une cible
break;
}
}
// Switch sur le type de case (lu depuis le terrain)
// pour remplacer par le caractèrre qui sera affiché
switch (case_terrain) {
case TILE_EMPTY:
*symbole = DISPLAY_TILE_EMPTY;
break;
case TILE_WALL:
*symbole = DISPLAY_TILE_WALL;
return A_DIM;
case TILE_CRATE:
*symbole = DISPLAY_TILE_CRATE;
return COLOR_PAIR(COLOR_YELLOW);
case TILE_TARGET:
*symbole = DISPLAY_TILE_TARGET;
break;
case TILE_CRATE_ON_TARGET:
*symbole = DISPLAY_TILE_CRATE_ON_TARGET;
return COLOR_PAIR(COLOR_YELLOW);
case TILE_PLAYER:
*symbole = DISPLAY_TILE_PLAYER;
return A_BOLD | COLOR_PAIR(COLOR_GREEN);
case TILE_PLAYER_ON_TARGET:
*symbole = DISPLAY_TILE_PLAYER_ON_TARGET;
return A_BOLD | COLOR_PAIR(COLOR_GREEN);
default:
*symbole = case_terrain;
break;
}
return 0;
}
// Afficher le contenu du niveau dans le terminal
void affichage_niveau(niveau_t* niveau){
// On "visite" le niveau par ligne puis par colonne
for (int ligne = 0; ligne < niveau->lignes; ligne++){
for (int colonne = 0; colonne < niveau->colonnes; colonne++){
// Lecture de la case du terrain
char car = lecture_du_terrain(niveau, colonne, ligne);
switch (car){
case TILE_EMPTY:
car = DISPLAY_TILE_EMPTY;
break;
case TILE_WALL:
car = DISPLAY_TILE_WALL;
break;
case TILE_CRATE:
car = DISPLAY_TILE_CRATE;
break;
case TILE_TARGET:
car = DISPLAY_TILE_TARGET;
break;
case TILE_CRATE_ON_TARGET:
car = DISPLAY_TILE_CRATE_ON_TARGET;
break;
case TILE_PLAYER:
car = DISPLAY_TILE_PLAYER;
break;
case TILE_PLAYER_ON_TARGET:
car = DISPLAY_TILE_PLAYER_ON_TARGET;
break;
}
// Si le joueur est sur la case
if (niveau->perso->colonne == colonne && niveau->perso->ligne == ligne){
switch(car){
case TILE_EMPTY: // Si la case est vide, afficher un joueur
car = DISPLAY_TILE_PLAYER;
break;
case TILE_TARGET: // Si la case est une cible, afficher un joueur sur une cible
car = DISPLAY_TILE_PLAYER_ON_TARGET;
break;
}
}
// On affiche le contenu de la case de coordonnées (colonne;ligne)
printf("%c", car);
}
printf("\n"); // Retour à la ligne
}
}
// Lire un niveau depuis un fichier (dans niveau/)
niveau_t* lecture_du_niveau (int numero_niveau){
FILE* fichier;
char chemin_du_niveau[100];
sprintf(chemin_du_niveau,"./niveau/niveau_%d", numero_niveau);
fichier = fopen(chemin_du_niveau, "r"); // On ouvre le fichier en lecture
// Gestion des erreurs...
if (!fichier) {
menu_message("Fichier introuvable", "Le niveau n'existe pas", 30, 1, COLOR_RED, COLOR_WHITE);
return NULL;
}
int colonne, ligne; // Variables pour stocker les coordonnées
fscanf(fichier, "%d %d", &colonne, &ligne); // On lit la taille du niveau dans le fichier
// On créée un niveau en mémoire avec les coordonnées lues
niveau_t* niveau = nouveau_niveau(colonne, ligne);
char car = fgetc(fichier); // On récupère le prochain caractère du fichier
int indice_terrain = 0;
int taille_tab_terrain = taille_tableau_terrain(niveau);
while(car != EOF && indice_terrain <= taille_tab_terrain)
{
indice_vers_coordonnees_niveau(niveau, indice_terrain, &colonne, &ligne);
// Si le caractère n'est pas un retour à la ligne
if (car != '\r' && car != '\n'){
// On place le caractère lu sur le terrain puis on incrémente
// l'adresse/l'indice d'écriture
place_sur_terrain(niveau, colonne, ligne, car);
analyser_case_niveau(niveau, indice_terrain);
++indice_terrain;
}
car = fgetc(fichier); // On récupère le prochain caractère du fichier
}
fclose(fichier); // On oublie pas de fermer le fichier :D
// Gestion des erreurs...
if (!niveau->perso) {
menu_message("Personnage introuvable", "Le niveau ne contient pas d'emplacement de depart ('@')", 30, 3, COLOR_YELLOW, COLOR_WHITE);
return NULL;
}
// Gestion des erreurs...
if (nombre_de_caisse_restante_sur_terrain(niveau) != nombre_cible_sur_terrain(niveau)) {
menu_message("Erreur de validation", "Le niveau ne contient pas assez de cibles (.)\nPour pouvoir placer toutes les caisses ($)", 48, 2, COLOR_YELLOW, COLOR_WHITE);
return NULL;
}
return niveau;
}
// Calcule le nombrede cibles sans caisses sur le terrain
int nombre_cible_sur_terrain(niveau_t* niveau){
int nombre_de_cibles = 0;
for(int indiceTerrain = 0; indiceTerrain < niveau->colonnes * niveau->lignes;indiceTerrain++){
if(niveau->terrain[indiceTerrain] == TILE_TARGET){
nombre_de_cibles++;
}
}
return nombre_de_cibles;
}
// Analyse la case à un indice donné et modifie les propriétés de niveau en conséquences
// Exemple: si la case lue est un @, on modifie la propriété niveau->perso puis on replace par une case sol
void analyser_case_niveau (niveau_t* niveau, int indice){
int colonne, ligne;
// On obtient les coordonnées de la case du terrain
indice_vers_coordonnees_niveau(niveau, indice, &colonne, &ligne);
// On lit la case du terrain
char car = lecture_du_terrain(niveau, colonne, ligne);
switch (car){
case TILE_PLAYER: // Si la case est un joueur, on modifie niveau->perso
niveau->perso = nouveau_point(colonne, ligne);
place_sur_terrain(niveau, colonne, ligne, TILE_EMPTY);
break;
}
}
// Effectue une copie d'un niveau (pour stocker les états précédents)
// Cette copie s'assure que l'on a bien une référence distincte sur le terrain
// Au lieu d'ue copie du pointeur vers ce dernier
niveau_t* copier_niveau (niveau_t *source) {
if (!source) return NULL;
niveau_t *copie = malloc (sizeof (niveau_t));
copie->lignes = source->lignes;
copie->colonnes = source->colonnes;
copie->terrain = malloc (sizeof (char) * taille_tableau_terrain(source));
memcpy (copie->terrain, source->terrain, sizeof(char) * taille_tableau_terrain(source));
copie->perso = malloc (sizeof (point_t));
memcpy (copie->perso, source->perso, sizeof (point_t));
return copie;
}
// La case est-elle navigable ? (le joueur/une caisse peut-il aller dessus ?)
bool case_libre_sur_terrain (niveau_t* niveau, int colonne, int ligne) {
char car = lecture_du_terrain(niveau, colonne, ligne);
// Si car = TILE_EMPTY ou TILE_TARGET
// Si la case en question est vide ou est une cible
switch (car){
case TILE_EMPTY:
case TILE_TARGET:
return true;
default:
return false;
}
}
// Vérifie si une caisse est présente aux coordonnées entrée en paramètres
bool caisse_sur_terrain (niveau_t* niveau, int colonne, int ligne){
char car = lecture_du_terrain(niveau, colonne, ligne);
// Si car = TILE_CRATE ou TILE_CRATE_ON_TARGET
// Si la case en question est une caisse ou une caisse sur une cible
switch (car){
case TILE_CRATE:
case TILE_CRATE_ON_TARGET:
return true;
default:
return false;
}
}
// Renvoie le nombre de caisses restantes (qui ne sont pas sur une cible)
int nombre_de_caisse_restante_sur_terrain(niveau_t* niveau){
int nombre_de_caisse = 0;
// On boucle sur tous le niveau pour chercher le nombre de caisses qui ne sont pas sur une cible
for(int indiceTerrain = 0; indiceTerrain < niveau->colonnes * niveau->lignes;indiceTerrain++){
//On vérifie si le le tableau à l'indice indiceTerrain est une caisse
//Si oui, on incrémente la valeur du nombre de caisse
if(niveau->terrain[indiceTerrain] == TILE_CRATE) {
nombre_de_caisse++;
}
}
return nombre_de_caisse;
}
#include "main.h"
// Détermine pour le niveau spéfcifié l'indice du tableau terrain correspondant aux coordonnées spécifiées
int coordonnees_vers_indice_terrain (niveau_t* niveau, int colonne, int ligne){
// On calcule la position dans le tableau terrain
// Lecture de gauche à droite puis de haut en bas
return ligne * niveau->colonnes + colonne;
}
// Fonction prenant un indice du tableau terrain et renvoyant la ligne/colonne correspondante
void indice_vers_coordonnees_niveau (niveau_t* niveau, int indice, int* colonne, int* ligne){
*ligne = (int)(indice / niveau->colonnes);
*colonne = (int)(indice % niveau->colonnes);
}
#include "main.h"
/* Fonction inutilisée mais demandée, voir fonction : "affichage_niveau_ncurses()"
char entree_du_joueur (void){
int var;
do
{
var = getchar();
var = majuscule_en_minuscule(var);
} while (var != DIR_UP && var != DIR_LEFT && var != DIR_DOWN && var != DIR_RIGHT && var != LEAVE);
printf("%c\n",var);
return var;
}*/
// Créer une instance d'un point avec les coordonnées spécifiées et renvoie un pointeur vers son adresse
point_t* nouveau_point (int colonne, int ligne){
// On alloue de la mémoire pour stocker un point
point_t* point = malloc(sizeof(point_t));
// Modification des attributs
point->colonne = colonne;
point->ligne = ligne;
return point;
}
// Parcours le terrain d'un niveau et renvoie un nouveau personnage
point_t* trouver_perso (niveau_t* niveau){
int index = 0; // Indice de parcours dans le tableau terrain du niveau
// Tant que la case n'est pas un joueur et que l'indice est dans le tableau
while (index < taille_tableau_terrain(niveau) && niveau->terrain[index] != TILE_PLAYER) {
++index; // Incrémenter l'indice de parcours
}
// Si l'indice est une adresse valide du tableau terrain
if (index < taille_tableau_terrain(niveau)){
int colonne, ligne;
// On récupère les coordonnées correspondantes à l'indice de parcours
indice_vers_coordonnees_niveau(niveau, index, &colonne, &ligne);
// On créée et initialise un nouveau personnage
point_t* personnage = nouveau_point(colonne, ligne);
return personnage;
}
// Si le niveau ne comporte pas de case personnage, on retourne un pointeur nul
return NULL;
}
#include "main.h"
// Lecture du meilleur score depuis le fichier score_nb
// Où nb est le numéro d'un niveau
int lecture_du_score(int quel_niveau) {
FILE* fichier;
char nom_fichier[100];
sprintf(nom_fichier,"./niveau/score_%d", quel_niveau);
if ((fichier = fopen(nom_fichier, "r"))) {
// Le fichier a bien été ouvert
int score;
fscanf(fichier, "%d", &score);
fclose(fichier);
return score;
} else {
ecriture_du_score(quel_niveau, -1);
// Erreur de lecture (le fichier n'existe pas ?)
return -1; // Retour d'une valeur d'erreur
}
}
// Insertion du meilleur score dans le fichier score_nb où nb est le numéro du niveau.
// (si le score obtenu en fin de partie est supérieur au meilleur score)
void ecriture_du_score(int quel_niveau, int score){
FILE* fichier;
char nom_fichier[100];
sprintf(nom_fichier, "./niveau/score_%d", quel_niveau);
fichier = fopen(nom_fichier, "w");
fprintf(fichier, "%d", score);
fclose(fichier);
}
// Demander le nom du joueur puis l'enregistre dans le tabeau des highscores
char* nom_du_joueur(void) {
return menu_saisie_texte("Entrez votre nom", 8);
}
#include "main.h"
/*
// Fonction qui permet de simuler une lecture de niveau, elle n'est pas utilisée
void initialise_terrain(niveau_t* niveau){
// On parcours les indices du terrain pour placer des murs sur chaque case
for (int indice = 0; indice < taille_tableau_terrain(niveau); ++indice) {
// On place un mur sur la case n°indice
niveau->terrain[indice] = TILE_WALL;
}
}
*/
// Modifie une case du terrain du niveau passé en paramètre et la remplace par car
void place_sur_terrain (niveau_t* niveau, int colonne, int ligne, char car) {
// On calcule l'indice de la case à modifier
int indice = coordonnees_vers_indice_terrain(niveau, colonne, ligne);
// On remplace la valeur par le char, passé en paramètre
niveau->terrain[indice] = car;
}
// Modifie une case du terrain du niveau passé en paramètre sous forme de point_t et la remplace par car
void place_sur_terrain_par_coordonnees(niveau_t* niveau, point_t* coord, char car) {
place_sur_terrain(niveau, coord->colonne, coord->ligne, car);
}
// Lis le contenu du terrain aux coordonées spécifiées en paramètre
char lecture_du_terrain(niveau_t* niveau, int colonne, int ligne) {
// On calcule l'indice de la case à lire
int indice = coordonnees_vers_indice_terrain(niveau, colonne, ligne);
return niveau->terrain[indice];
}
// Effectue une lecture du terrain aux coordonnées spécifiées sous forme de point_t
char lecture_du_terrain_par_coordonnees(niveau_t* niveau, point_t* coord) {
// On vérifie que les coordonnées sont valides
// Si elles ne sont pas dans le terrain, on retourne \0 (NUL)
if (coord->colonne < 0 || coord->colonne >= niveau->colonnes || coord->ligne < 0 || coord->ligne >= niveau->lignes){
return '\0';
}else{
return niveau->terrain[coordonnees_vers_indice_terrain(niveau, coord->colonne, coord->ligne)];
}
}
// Renvoie la longueur du tableau terrain du niveau spécifié
int taille_tableau_terrain(niveau_t* niveau) {
return niveau->lignes * niveau->colonnes;
}
#ifndef CONSTANTS_H_
#define CONSTANTS_H_
/*
— ’ ’ représente les cases vides
— ’#’ représente les murs (cases infranchissables)
— ’$’ représente une caisse que vous pouvez déplacer
— ’.’ représente une zone de rangement sur laquelle vous devez placer une caisse
— ’*’ représente une zone de rangement sur laquelle une caisse est actuellement placée
— ’@’ représente votre personnage
— ’+’ représente votre personnage lorsqu’il est sur une zone de rangement.
*/
#define TILE_EMPTY ' '
#define TILE_WALL '#'
#define TILE_CRATE '$'
#define TILE_TARGET '.'
#define TILE_CRATE_ON_TARGET '*'
#define TILE_PLAYER '@'
#define TILE_PLAYER_ON_TARGET '+'
#define DISPLAY_TILE_EMPTY ' '
#define DISPLAY_TILE_WALL '#'
#define DISPLAY_TILE_CRATE '$'
#define DISPLAY_TILE_TARGET '.'
#define DISPLAY_TILE_CRATE_ON_TARGET '*'
#define DISPLAY_TILE_PLAYER 'M'
#define DISPLAY_TILE_PLAYER_ON_TARGET '+'
#define DIR_UP 'z' // Touche permettant de se déplacer vers le haut
#define DIR_LEFT 'q' // Touche permettant de se déplacer vers la gauche
#define DIR_DOWN 's' // Touche permettant de se déplacer vers le bas
#define DIR_RIGHT 'd' // Touche permettant de se déplacer vers la droite
#define RESTART 'r' // Touche permettant de simuler la touche R, pendant un niveau, elle sert à recommencer le niveau
#define LEAVE 'l' // Touche permettant de simuler la touche L, pendant un niveau, elle sert à quitter le niveau
#define CANCEL 'a' // Touche permettant de simuler la touche A , pendant un niveau, elle sert à effectuer un retour arrière (annuler_deplacement)
#define HELP 'h' // Touche permettant de simuler la touche H, pendant un niveau, elle sert à afficher l'aide
#define KB_ENTER 10 // Touche permettant de simuler la touche entrée
#define KB_DOWN 258 // Touche permettant de se déplacer vers le bas
#define KB_UP 259 // Touche permettant de se déplacer vers le haut
#define KB_LEFT 260 // Touche permettant de se déplacer vers la gauche
#define KB_RIGHT 261 // Touche permettant de se déplacer vers la droite
#define KB_BACKSPACE 263 // Touche permettant de simuler la touche supprimer, pendant une demande de saisie, elle permet de supprimer du texte
#define KB_F1 265 // Touche permettant de simuler la touche fonction 1 (F1), pendant un niveau, elle sert à afficher l'aide
#define STATE_QUIT 0
#define STATE_MAINMENU 1
#define MAINMENU_PLAY 0
#define MAINMENU_RULES 1
#define MAINMENU_CONTROLS 2
#define MAINMENU_CREDITS 3
#define MAINMENU_SCOREBOARD 4
#define MAINMENU_QUIT 5
#define SCORE_BUFFER_SIZE 5
#endif // CONSTANTS_H_
#ifndef CURSES_UTIL_H
#define CURSES_UTIL_H
void initialiser_ncurses();
void fermer_ncurses();
void initialiser_couleur(int couleur);
void calculer_centre(int hauteur, int largeur, int haut, int gauche, int* y, int* x);
void centrer_fenetre(WINDOW* fenetre, int hauteur_cible, int largeur_cible, int hauteur, int largeur, int haut, int gauche);
WINDOW* nouvelle_fenetre();
void effacer_ecran();
#endif // CURSES_UTIL_H
#ifndef DEPLACEMENT_H_
#define DEPLACEMENT_H_
void deplacement(niveau_t* niveau, char direction);
void annuler_deplacement();
#endif // DEPLACEMENT_H_
#ifndef JOUER_H_
#define JOUER_H_
void jouer_niveau(int numero_niveau);
#endif // JOUER_H_
#ifndef LISTE_NIVEAUX_H_
#define LISTE_NIVEAUX_H_
extern liste_niveaux_t* etats_niveaux;
liste_niveaux_t *nouvelle_liste_niveaux(int taille);
void init_liste_niveaux(liste_niveaux_t* liste, int taille);
void liberation_de_la_liste_niveaux(liste_niveaux_t* liste);
void ajouter_niveau(liste_niveaux_t* liste, niveau_t* niveau);
void enlever_dernier_niveau(liste_niveaux_t* liste);
niveau_t* haut_de_liste(liste_niveaux_t* liste);
#endif // LISTE_NIVEAUX_H_
#ifndef LISTE_SCORE_H_
#define LISTE_SCORE_H_
score_t* nouveau_score(void);
void initialiser_score(score_t* score, int points, char* nom);
void liberer_score(score_t* score);
liste_score_t* nouvelle_liste_scores(void);
void liberer_liste_scores(liste_score_t* scores);
liste_score_t* lire_liste_scores(int numero_niveau);
void enregistrer_liste_scores(liste_score_t* scores, int numero_niveau);
bool inserable_dans_liste(liste_score_t* scores, int points);
void inserer_score_dans_liste(liste_score_t* scores, int points, char* nom);
void trier_liste_score(liste_score_t* scores);
#endif // LISTE_SCORE_H_
#ifndef MAIN_H_
#define MAIN_H_
#include <ncurses.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include "constants.h"
#include "math.h"
#include "structs.h"
#include "point.h"
#include "niveau.h"
#include "terrain.h"
#include "liste_niveaux.h"
#include "liste_score.h"
#include "curses_util.h"
#include "niveau_util.h"
#include "menu.h"
#include "score.h"
#include "deplacement.h"
#include "jouer.h"
#endif // MAIN_H_
#ifndef MATH_H_
#define MATH_H_
int min(int premier, int deuxieme);
int max(int premier, int deuxieme);
int majuscule_en_minuscule(int lettre);
#endif // MATH_H_
#ifndef MENU_H_
#define MENU_H_
void afficher_logo(void);
int choix_du_niveau(void);
void afficher_regles(void);
void afficher_credits(void);
void afficher_controles(void);
void instruction_menu(int instruction);
int afficher_menu_principal(void);
int afficher_menu_quitter(void);
int afficher_menu_recommencer(void);
void afficher_liste_niveau_scoreboard(int numero_niveau);
int menu_liste(int hauteur, int largeur, char* titre, char** elements, int nb_elements);
int menu_saisie_nombre(char* titre);
char* menu_saisie_texte(char* titre, int longueur_chaine_max);
void menu_message(char* titre, char* message, int largeur, int hauteur, int couleur, int couleur_texte);
#endif // MENU_H_
#ifndef NIVEAU_H_
#define NIVEAU_H_
niveau_t* nouveau_niveau (int nb_colonnes, int nb_lignes);
void liberation_du_niveau (niveau_t* niveau);
char affichage_niveau_ncurses (niveau_t* niveau, int numero_niveau);
int modification_affichage_niveau (niveau_t* niveau, int x, int y, char* symbole);
void affichage_niveau(niveau_t* niveau);
niveau_t* lecture_du_niveau(int quel_niveau);
int nombre_cible_sur_terrain(niveau_t* niveau);
void analyser_case_niveau(niveau_t* niveau, int indice);
bool case_libre_sur_terrain(niveau_t* niveau, int colonne, int ligne);
bool caisse_sur_terrain(niveau_t* niveau, int colonne, int ligne);
int nombre_de_caisse_restante_sur_terrain(niveau_t* niveau);
niveau_t* copier_niveau(niveau_t *source);
#endif // NIVEAU_H_
#ifndef NIVEAU_UTIL_H_
#define NIVEAU_UTIL_H_
int coordonnees_vers_indice_terrain(niveau_t* niveau, int colonne, int ligne);
void indice_vers_coordonnees_niveau(niveau_t* niveau, int indice, int* colonne, int* ligne);
#endif // NIVEAU_UTIL_H_
#ifndef POINT_H_
#define POINT_H_
//Fonction inutilisée mais demandée, voir fonction : "affichage_niveau_ncurses()"
//char entree_du_joueur(void);
point_t* nouveau_point(int colonne, int ligne);
point_t* trouver_perso(niveau_t* niveau);
#endif // POINT_H_
#ifndef SCORE_H_
#define SCORE_H_
int lecture_du_score(int quel_niveau);
void ecriture_du_score(int quel_niveau, int score);
char* nom_du_joueur(void);
#endif // SCORE_H_
#ifndef STRUCTS_H_
#define STRUCTS_H_
// Structure de donnée représentant la position du joueur
typedef struct point_t {
int ligne; // Coordonnée verticale (Y)
int colonne; // Coordonnée horizontale (X)
} point_t;
// Structure de donnée représentant un niveau
typedef struct niveau_t {
int lignes; // Nombre de lignes du niveau (hauteur en cases)
int colonnes; // Nombre de colonnes du niveau (largeur en cases)
char* terrain; // Contenu du niveau sous forme de caractères
point_t* perso; // Position du personnage
int indice; // Indice dans la liste de niveaux
} niveau_t;
// Représente un tableau* de niveaux (pour la fonction d'annulation de déplacements)
// *pile/stack
typedef struct liste_niveaux_t {
niveau_t** memoire; // Contient les pointeurs vers les instances de niveaux représentant des états précédents
int taille; // Nombre de cases utilisées
int taille_memoire; // Nombre de cases disponibles
} liste_niveaux_t;
// Représente une paire score/nom joueur
typedef struct score_t {
int score; //Score
char nom[9]; //Nom du joueur
} score_t;
// Représente une liste de scores de taille fixe (top 5)
typedef struct liste_score_t {
score_t** memoire; // Ensemble des scores
int taille; // Nombre de score stockés (MAX 5)
} liste_score_t;
#endif // STRUCTS_H_
#ifndef TERRAIN_H_
#define TERRAIN_H_
void initialise_terrain(niveau_t* niveau);
void place_sur_terrain(niveau_t* niveau, int colonne, int ligne, char car);
void place_sur_terrain_par_coordonnees(niveau_t* niveau, point_t* coord, char car);
char lecture_du_terrain(niveau_t* niveau, int colonne, int ligne);
char lecture_du_terrain_par_coordonnees(niveau_t* niveau, point_t* coord);
int taille_tableau_terrain(niveau_t* niveau);
#endif // TERRAIN_H_