Développement d'un Jeu du Serpent en C pour la Console

Présentation du Projet

Ce document détaille l'implémentation d'une version simplifiée du jeu classique du Serpent (Snake) en langage C, conçue pour fonctionner dans un environnement de console Windows. Le projet illustre les concepts fondamentaux de la programmation de jeux, tels que la gestion de l'affichage textuel, le contrôle des entités, la détection des interactions et la boucle de jeu.

Organisation des Fichiers

Le code est structuré en deux parties pricnipales : un fichier d'en-tête pour les définitions globales et les prototypes de fonctions, et un fichier source pour l'implémentation détaillée de la logique du jeu.

Fichier d'En-tête (serpent.h)

Ce fichier définit les constantes du plateau de jeu, les structures de données nécessaires pour représenter le serpent et ses segments, ainsi que les variables globales pour la gestion des déplacements, de la nourriture et du score. Il déclare également les fonctions qui seront utilisées dans le fichier source principal.

#pragma once // Garantit que ce fichier n'est inclus qu'une seule fois

// Dimensions du plateau de jeu
#define LARGEUR_PLATEAU 60  // Largeur de la zone de jeu
#define HAUTEUR_PLATEAU 20  // Hauteur de la zone de jeu

// Structure pour représenter un point (coordonnées x, y)
typedef struct {
    int x; // Coordonnée horizontale
    int y; // Coordonnée verticale
} Point2D;

// Structure pour représenter le serpent
typedef struct {
    int tailleActuelle; // Nombre de segments formant le corps du serpent
    Point2D corps[LARGEUR_PLATEAU * HAUTEUR_PLATEAU]; // Tableau des segments du serpent
} Serpent;

// Variables globales pour le contrôle du jeu
int deplacementX; // Composante X du vecteur de déplacement de la tête
int deplacementY; // Composante Y du vecteur de déplacement de la tête

Point2D positionQueuePrecedente; // Position du dernier segment de la queue avant mise à jour
Point2D positionNourriture;      // Emplacement actuel de la nourriture

int scoreJeu; // Le score accumulé par le joueur

// Déclarations des fonctions principales du jeu
void InitialiserEnvironnementJeu(); // Configure la console et dessine les murs
void InitialiserSerpent();          // Positionne le serpent au début du jeu
void GenererNourriture();           // Crée une nouvelle nourriture à un endroit aléatoire
void AfficherEtatJeu();             // Met à jour l'affichage des éléments du jeu
void ExecuterBoucleJeu();           // Contient la logique principale de la partie

Fichier Source (main.c)

Ce fichier contient l'implémentation de toutes les fonctions déclarées, gérant l'initialisation, l'affichage via l'API Console de Windows, la logique de mouvement du serpent, la détection des collisions et la réponse aux entrées utilisateur.

#include <stdio.h>     // Pour printf
#include "serpent.h"   // Notre fichier d'en-tête personnalisé
#include <Windows.h>   // Pour l'API de la console (SetConsoleCursorPosition, etc.)
#include <conio.h>     // Pour _kbhit et _getch (gestion des entrées clavier)
#include <stdlib.h>    // Pour rand, srand (nombres aléatoires)
#include <time.h>      // Pour time (initialisation du générateur aléatoire)

// Instance globale du serpent utilisée dans le jeu
Serpent serpentPrincipal;

// Fonction utilitaire pour déplacer le curseur de la console à des coordonnées spécifiques
void PositionnerCurseur(int x, int y) {
    COORD coord = { (short)x, (short)y };
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}

// Initialise le cadre de jeu (murs) et configure la console
void InitialiserEnvironnementJeu() {
    // Masquer le curseur clignotant dans la console
    CONSOLE_CURSOR_INFO cci;
    cci.dwSize = sizeof(cci);
    cci.bVisible = FALSE; // Rendre le curseur invisible
    SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cci);

    // Dessiner les limites extérieures du plateau
    for (int ligne = 0; ligne <= HAUTEUR_PLATEAU; ligne++) {
        for (int colonne = 0; colonne <= LARGEUR_PLATEAU; colonne++) {
            if (ligne == HAUTEUR_PLATEAU) { // Bordure inférieure
                printf("=");
            } else if (colonne == LARGEUR_PLATEAU) { // Bordure droite
                printf("|");
            } else { // Espace vide à l'intérieur du plateau
                printf(" ");
            }
        }
        printf("\n"); // Nouvelle ligne après chaque rangée de caractères
    }
    // Afficher l'étiquette du score sous le plateau
    PositionnerCurseur(0, HAUTEUR_PLATEAU + 1);
    printf("Score: %d", scoreJeu);
}

// Initialise la position et la taille du serpent pour un nouveau jeu
void InitialiserSerpent() {
    serpentPrincipal.tailleActuelle = 2; // Le serpent commence avec deux segments
    // Position de la tête du serpent (au centre du plateau)
    serpentPrincipal.corps[0].x = LARGEUR_PLATEAU / 2;
    serpentPrincipal.corps[0].y = HAUTEUR_PLATEAU / 2;
    // Position du deuxième segment
    serpentPrincipal.corps[1].x = LARGEUR_PLATEAU / 2 - 1;
    serpentPrincipal.corps[1].y = HAUTEUR_PLATEAU / 2;

    // Le serpent commence par se déplacer vers la droite
    deplacementX = 1;
    deplacementY = 0;
}

// Génère une nouvelle position aléatoire pour la nourriture sur le plateau
void GenererNourriture() {
    // Initialiser le générateur de nombres aléatoires une seule fois
    // Note: time(NULL) est passé à (unsigned int) pour la conformité du type
    srand((unsigned int)time(NULL));
    positionNourriture.x = rand() % LARGEUR_PLATEAU;
    positionNourriture.y = rand() % HAUTEUR_PLATEAU;
}

// Affiche les éléments dynamiques du jeu (serpent, nourriture, score)
void AfficherEtatJeu() {
    // Effacer l'ancien segment de la queue en le remplaçant par un espace
    PositionnerCurseur(positionQueuePrecedente.x, positionQueuePrecedente.y);
    printf(" ");

    // Dessiner le serpent, 'O' pour la tête et '*' pour le corps
    for (int i = 0; i < serpentPrincipal.tailleActuelle; i++) {
        PositionnerCurseur(serpentPrincipal.corps[i].x, serpentPrincipal.corps[i].y);
        if (i == 0) {
            printf("O"); // Tête du serpent
        } else {
            printf("*"); // Corps du serpent
        }
    }

    // Dessiner la nourriture ('@')
    PositionnerCurseur(positionNourriture.x, positionNourriture.y);
    printf("@");

    // Mettre à jour et afficher le score
    PositionnerCurseur(0, HAUTEUR_PLATEAU + 1);
    printf("Score: %d", scoreJeu);
}

// Boucle principale du jeu, gérant les mises à jour et les événements
void ExecuterBoucleJeu() {
    char toucheDirection = 'D'; // Direction par défaut au démarrage (droite)

    // La boucle continue tant que la tête du serpent est dans les limites du plateau
    while (serpentPrincipal.corps[0].x >= 0 && serpentPrincipal.corps[0].x < LARGEUR_PLATEAU &&
           serpentPrincipal.corps[0].y >= 0 && serpentPrincipal.corps[0].y < HAUTEUR_PLATEAU) {

        AfficherEtatJeu(); // Rafraîchir l'affichage du jeu

        // Vérifier si une touche a été pressée
        if (_kbhit()) {
            toucheDirection = (char)_getch(); // Lire la touche sans attendre Enter
        }

        // Mettre à jour la direction du déplacement en fonction de la touche pressée
        switch (toucheDirection) {
            case 'D': case 'd': deplacementX = 1; deplacementY = 0; break; // Droite
            case 'A': case 'a': deplacementX = -1; deplacementY = 0; break; // Gauche
            case 'W': case 'w': deplacementY = -1; deplacementX = 0; break; // Haut
            case 'S': case 's': deplacementY = 1;  deplacementX = 0; break; // Bas
        }

        // Détection de collision avec la nourriture
        if (serpentPrincipal.corps[0].x == positionNourriture.x && serpentPrincipal.corps[0].y == positionNourriture.y) {
            GenererNourriture();           // Générer une nouvelle nourriture
            serpentPrincipal.tailleActuelle++; // Augmenter la taille du serpent
            scoreJeu++;                      // Augmenter le score
        }

        // Détection d'auto-collision (tête du serpent avec son corps)
        for (int i = 1; i < serpentPrincipal.tailleActuelle; i++) {
            if (serpentPrincipal.corps[0].x == serpentPrincipal.corps[i].x && serpentPrincipal.corps[0].y == serpentPrincipal.corps[i].y) {
                return; // Le jeu se termine si le serpent se mord
            }
        }

        // Enregistrer la position du dernier segment avant le mouvement
        positionQueuePrecedente.x = serpentPrincipal.corps[serpentPrincipal.tailleActuelle - 1].x;
        positionQueuePrecedente.y = serpentPrincipal.corps[serpentPrincipal.tailleActuelle - 1].y;

        // Déplacer les segments du corps du serpent : chaque segment prend la place du précédent
        for (int i = serpentPrincipal.tailleActuelle - 1; i > 0; i--) {
            serpentPrincipal.corps[i] = serpentPrincipal.corps[i - 1];
        }

        // Mettre à jour la position de la tête du serpent selon la direction actuelle
        serpentPrincipal.corps[0].x += deplacementX;
        serpentPrincipal.corps[0].y += deplacementY;

        Sleep(200); // Pause de 200 millisecondes pour contrôler la vitesse du jeu
    }
}

// Fonction principale du programme
int main() {
    InitialiserEnvironnementJeu(); // Préparer la console et le plateau
    InitialiserSerpent();          // Mettre en place le serpent
    GenererNourriture();           // Placer la première nourriture
    ExecuterBoucleJeu();           // Lancer la boucle principale du jeu

    // Afficher un message de fin de partie
    PositionnerCurseur(0, HAUTEUR_PLATEAU + 3);
    printf("Game Over! Score final: %d\n", scoreJeu);
    return 0;
}

Étiquettes: C Game Development Console Application Windows API Data Structures

Publié le 20 juillet à 16h37