Exercice du jeu puissance 4 en c

Demande d'avis sur mon code

a marqué ce sujet comme résolu.

Salut à tous.

Tout d’abord, je souhaite dire un grand merci aux rédacteurs du cours de programmation en c, je l’ai trouvé très clair, du moins pour l’instant, je n’ai pas encore abordé la partie avancée.

Je me suis donc adonné au TP du puissance 4 (pas encore d’ia), bon j’avoue avoir un peu regardé la fonction pour vider le tampon et m’en servir dans mon code, mais autrement je suis parti de mon propre code, et j’aimerais avoir des avis sur ce qui n’est pas trop correct, incorrect, ce qui pourrait être amélioré, etc.

Mon code se situe ici (il y a 600 lignes, je ne sais pas si je peux le mettre sur le forum):

#include <stdlib.h>
#include <stdio.h>
#include <math.h>

enum {QUITTER, CONTINUER};

typedef struct
{
    char *grille;
    const unsigned largeur;
    const unsigned hauteur;
    const unsigned nbCases;
    const unsigned dimCase;
} Grille;


typedef struct
{
    char *numeros;
    char *ligne;
    char *ligneVide;
    char *casePion;
} ElementsGrille;


typedef struct
{
    char *nom;
    char pion;
} Joueur;


typedef struct
{
    unsigned id;
    unsigned lignes;
    unsigned colonnes;
    unsigned alignement;
    unsigned dimCase;
} Jeux;


const int DIRECTIONS[4][2][2] = {
    {
        {-1, 0}, {1, 0}, // o <- -> e
    },
    {
        {0, -1}, {0, 1}, // n <- -> s
    },
    {
        {-1, -1}, {1, 1}, // no <- -> se
    },
    {
        {1, -1},{-1, 1}, // ne <- -> so
    },
};

const Jeux JEUX[] = {
    {1, 6, 7, 4, 3},
    {2, 8, 12, 5, 1},
    {3, 3, 3, 3, 3},
};

const int NB_JEUX = 3;


int intlen(int n);
void convertirNombre(char *chaine, unsigned nombre, unsigned longueurNombre);
int intervalle(int v, int vmin, int vmax);
void viderTampon();

void recupererSaisieColonne(unsigned *colonne, const unsigned CMIN, const unsigned CMAX);
int insererPion(Grille *grille, unsigned colonne, const char pion);
unsigned compterPions(
        Grille *grille,
        char pion,
        unsigned ligne,
        unsigned colonne,
        int pasH,
        int pasV
);
unsigned alignement(Grille *grille, unsigned ligne, unsigned colonne, unsigned suite);

void initialiserGrille(Grille *grille);
unsigned initialiserElementsGrille(Grille *grille, ElementsGrille *elg);
void libererElementsGrille(ElementsGrille *elg);

void afficherGrille(Grille *grille, ElementsGrille *elg);
void genererNumerosColonnes(
        char *nums,
        unsigned nb_colonnes,
        unsigned largeurColonne
);
void genererLigneSeparation(char *ligne, unsigned nb_colonnes, unsigned nbsep);
void genererLigneVide(char *ligne, unsigned nb_colonnes, unsigned largeur_colonne);
void genererCasePion(char *casePion, unsigned largeurColonne);

void selectionIdJeu(unsigned *selection);
unsigned jouer(const Joueur *joueurs);
unsigned rejouer();


int main(int argc, char *argv[])
{
    printf(
        "\n*************************************"
        "\n*****                            ****"
        "\n***     JEU DES PIONS ALIGNES     ***"
        "\n*****                           *****"
        "\n*************************************\n\n"
    );
    printf(
        "INFORMATION:\nVous pouvez quitter le jeu en cours "
        "en saisissant 0 (zero) comme n° de colonne.\n\n"
    );

    unsigned statut;
    const char PIONS[] = {'X', 'O'};
    const Joueur joueurs[2] = {
        {"Joueur I", PIONS[0]},
        {"Joueur II", PIONS[1]}
    };

    while(1)
    {
        statut = jouer(joueurs);
        if(statut == QUITTER)
            break;
    }

    printf("\nAU REVOIR !\n\n");
    return 0;
}


int intlen(int n)
{
    int i = 1;
    while(n >= 10)
    {
        n /= 10;
        i++;
    }
    return i;
}


void convertirNombre(char *chaine, unsigned nombre, unsigned longueurNombre)
{
    unsigned i, p, n = 0;
    for(i=longueurNombre; i>0; i--)
    {
        p = pow(10, i-1);
        chaine[n++] = '0' + (nombre / p);
        nombre -= p * (nombre / p);
    }
    chaine[n] = '\0';
}


void viderTampon()
{
    char c = NULL;
    while(c != '\n')
    {
        scanf("%c", &c);
    }
}


unsigned compterPions(
        Grille *grille,
        char pion,
        unsigned ligne,
        unsigned colonne,
        int pasH,
        int pasV)
{
    unsigned i = 0;
    int l = ligne, c = colonne;
    int bh = pasH == 1 ? grille->largeur : -1;
    int bv = pasV == 1 ? grille->hauteur : -1;

    while(1)
    {
        l += pasV;
        c += pasH;
        if( l == bv ||
            c == bh ||
            *(grille->grille + (c + l * grille->largeur)) != pion
            )
            break;
        i++;

    }
    return i;
}


unsigned alignement(Grille *grille, unsigned ligne, unsigned colonne, unsigned suite)
{
    unsigned i, j, aligne;
    suite--; // - case de référence
    char pion = *(grille->grille + (colonne + ligne * grille->largeur));
    for(i=0; i<4; i++)
    {
        aligne = 0;
        for(j=0; j<2; j++)
        {
            aligne += compterPions(
                        grille,
                        pion,
                        ligne,
                        colonne,
                        DIRECTIONS[i][j][0],
                        DIRECTIONS[i][j][1]
                    );

            if(aligne >= suite)
                return 1;
        }
    }
    return 0;
}


unsigned jouer(const Joueur *joueurs)
{
    unsigned idJeu, indJeu, gagnant = 0, statut = CONTINUER;
    selectionIdJeu(&idJeu);
    indJeu = idJeu - 1;

    Grille grille = {
        NULL,
        JEUX[indJeu].colonnes,
        JEUX[indJeu].lignes,
        JEUX[indJeu].lignes * JEUX[indJeu].colonnes,
        JEUX[indJeu].dimCase
    };

    grille.grille = malloc(sizeof(char) * grille.nbCases);
    if(grille.grille == NULL)
        return EXIT_FAILURE;

    initialiserGrille(&grille);

    ElementsGrille elementsGrille = {NULL, NULL, NULL, NULL};
    if(!initialiserElementsGrille(&grille, &elementsGrille))
    {
        free(grille.grille);
        return EXIT_FAILURE;
    }

    const unsigned CMIN = 0, CMAX = grille.largeur;

    unsigned colonne, tour = 0;
    int ligne;

    while(tour < grille.nbCases)
    {
        afficherGrille(&grille, &elementsGrille);
        printf("\n%s : ", joueurs[tour % 2].nom);

        while(1)
        {
            recupererSaisieColonne(&colonne, CMIN, CMAX);
            if(!colonne)
            {
                statut = QUITTER;
                break;
            }

            ligne = insererPion(&grille, colonne-1, joueurs[tour % 2].pion);
            if(ligne != -1)
            {
                break;
            }
            else
            {
                printf("\n*** Cette colonne est pleine ! ***\n");
            }
        }

        if(statut == QUITTER)
            break;

        if(alignement(&grille, ligne, colonne-1, JEUX[indJeu].alignement))
        {
            gagnant = 1;
            break;
        }
        tour++;
    }

    if(statut != QUITTER)
    {
        afficherGrille(&grille, &elementsGrille);
        if(gagnant)
            printf("\n\n%s a gagné !\n\n", joueurs[tour % 2].nom);
        else
            printf("\n\nPartie nulle !\n\n");

        statut = rejouer();
    }

    free(grille.grille);
    libererElementsGrille(&elementsGrille);

    return statut;
}


unsigned rejouer()
{
    unsigned r, i;
    while(1)
    {
        printf(
            "Souhaitez-vous faire une autre partie ?\n"
            "1 - Oui\n"
            "0 - Non\n"
        );
        r = scanf("%d", &i);
        if(!r || (i != 0 && i != 1))
        {
            printf("\n*** Choix invalide ! ***\n\n");
        }
        else
        {
            break;
        }
        viderTampon();
    }
    return i == 0 ? QUITTER : CONTINUER;
}


void selectionIdJeu(unsigned *selection)
{
    unsigned i, r;
    while(1)
    {
        printf("\nChoisissez le type de jeu :\n");
        for(i=0; i<NB_JEUX; i++)
        {
            printf(
                "%d - %d lignes x %d colonnes, %d pions à aligner.\n",
                JEUX[i].id,
                JEUX[i].lignes,
                JEUX[i].colonnes,
                JEUX[i].alignement
            );
        }
        r = scanf("%d", selection);
        if(!r)
        {
            printf("\n*** Veuillez choisir un n° de jeu. ***\n\n");
        }
        else if(!intervalle(*selection, 1, NB_JEUX))
        {
            printf("\n*** Ce numéro de jeu est invalide. ***\n\n");
        }
        else
        {
            break;
        }
        viderTampon();
    }
}


void initialiserGrille(Grille *grille)
{
    unsigned i;
    for(i=0; i<grille->nbCases; i++)
    {
        *(grille->grille + i) = ' ';
    }
}


void genererNumerosColonnes(
        char *nums,
        unsigned nb_colonnes,
        unsigned largeurColonne)
{
    int i, j, k, ln, p, s, c = 0;
    char *pn = NULL;
    pn = malloc(sizeof(char) * ((intlen(nb_colonnes) + 1)));

    nums[c++] = ' ';
    for(i=1; i<=nb_colonnes; i++)
    {
        ln = intlen(i);
        convertirNombre(pn, i, ln);

        p = (largeurColonne - ln) / 2;
        s = largeurColonne - ln - p + 1;

        for(j=0; j<p; j++)
            nums[c++] = ' ';

        for(k=0; k<ln; k++)
            nums[c++] = pn[k];

        for(j=0; j<s; j++)
            nums[c++] = ' ';
    }
    nums[c] = '\0';
    free(pn);
}


void genererLigneSeparation(char *ligne, unsigned nb_colonnes, unsigned nbsep)
{
    unsigned i, j=0, s;
    for(i=0; i<nb_colonnes; i++)
    {
        ligne[j++] = '+';
        for(s=0; s<nbsep; s++)
            ligne[j++] = '-';
    }
    ligne[j++] = '+';
    ligne[j] = '\0';
}


void genererLigneVide(char *ligne, unsigned nb_colonnes, unsigned largeur_colonne)
{
    unsigned i, j, n = 0;
    for(i=0; i<nb_colonnes; i++)
    {
        ligne[n++] = '|';
        for(j=0; j<largeur_colonne; j++)
        {
            ligne[n++] = ' ';
        }
    }
    ligne[n++] = '|';
    ligne[n] = '\0';
}


void genererCasePion(char *casePion, unsigned largeurColonne)
{
    unsigned i, p, s, n = 0;
    largeurColonne--;
    p = largeurColonne / 2;
    s = largeurColonne - p;
    for(i=0; i<p; i++)
        casePion[n++] = ' ';

    casePion[n++] = '%';
    casePion[n++] = 'c';

    for(i=0; i<s; i++)
        casePion[n++] = ' ';

    casePion[n] = '\0';
}


void afficherGrille(Grille *grille, ElementsGrille *elg)
{
    unsigned i, j, t, tc;
    tc = grille->dimCase / 2;

    printf("\n%s\n", elg->numeros);
    for(i=0; i<grille->hauteur; i++)
    {
        printf("%s\n", elg->ligne);
        for(t=0; t<tc; t++)
            printf("%s\n", elg->ligneVide);

        for(j=0; j<grille->largeur; j++)
        {
            printf("|");
            printf(
                elg->casePion,
                *(grille->grille + (j + i * (grille->largeur)))
            );
        }
        printf("|\n");

        for(t=0; t<tc; t++)
            printf("%s\n", elg->ligneVide);
    }
    printf("%s\n", elg->ligne);
    printf("%s\n", elg->numeros);
}


int insererPion(Grille *grille, unsigned colonne, const char pion)
{
    int i = colonne + (grille->hauteur - 1) * grille->largeur;
    for(i=i; i>-1; i-=grille->largeur)
    {
        if(*(grille->grille + i) == ' ')
        {
            *(grille->grille + i) = pion;
            return (i - colonne) / grille->largeur;
        }
    }
    return -1;
}


int intervalle(int v, int vmin, int vmax)
{
    return v >= vmin && v <= vmax;
}


void recupererSaisieColonne(unsigned *colonne, const unsigned CMIN, const unsigned CMAX)
{
    unsigned r;

    while(1)
    {
        r = scanf("%d", colonne);
        if(!r)
        {
            printf("\n*** Veuillez entrer un nombre ! ***\n");
        }
        else if(!intervalle(*colonne, CMIN, CMAX))
        {
            printf("\n*** Veuillez entrer un n° de colonne valide ! ***\n");
        }
        else
        {
            break;
        }
        viderTampon();
    }
    viderTampon();
}


void libererElementsGrille(ElementsGrille *elg)
{
    free(elg->numeros);
    free(elg->ligne);
    free(elg->ligneVide);
    free(elg->casePion);
}


unsigned initialiserElementsGrille(Grille *grille, ElementsGrille *elg)
{
    unsigned tc = (grille->dimCase * 2 + 1) * grille->largeur + 2;

    elg->numeros = malloc(sizeof(char) * tc);
    elg->ligne = malloc(sizeof(char) * tc);
    elg->ligneVide = malloc(sizeof(char) * tc);
    elg->casePion = malloc(sizeof(char) * grille->dimCase * 2 + 1);

    if( elg->numeros == NULL ||
        elg->ligne == NULL ||
        elg->ligneVide == NULL ||
        elg->casePion == NULL )
    {
        libererElementsGrille(elg);
        return 0;
    }

    genererNumerosColonnes(elg->numeros, grille->largeur, grille->dimCase * 2);
    genererLigneSeparation(elg->ligne, grille->largeur, grille->dimCase * 2);
    genererLigneVide(elg->ligneVide, grille->largeur, grille->dimCase * 2);
    genererCasePion(elg->casePion, grille->dimCase * 2);

    return 1;
}

Merci d’avance à ceux qui essaieront d’analyser mon code.

Question subsidiaire : Si j’aperçois des fautes d’orthographe dans un cours, à qui dois-je le signaler ?

+0 -0

Coucou ^^

Ah ! Du C ! :)

Alors oui même 600 lignes, tu peux le poster ici. Il suffit simplement de le mettre entre des balises secret.

// Ton code ici

Tu copies/colle ton code, le sélectionne et clic sur le bouton ℹ puis celui du 🔒 et voila.

Du coup, c’est pas très pratique de lire ton code :( Je regarderais ça quand je pourais. Certainement que d’autres agrumes t’auront fait des remarques avant.

Pour ce qui est de la fonction qui vide le tampon. Elle n’a rien d’évident, surtout pour la faire correctement. À force, elle s’apprend par cœur …

Pour le signalement des fautes d’orthorgraphes dans un cours, tu peux utiliser le petit bouton "Signaler une faute dans ce chapitre" en fin de chapitre ^^

+0 -0

Bonjour.

Oui, elles sont en trop, pour la constante DIRECTION, elle est totalement inutile, pour la constante JEUX, c’est parce que j’avais testé sur d’autres dimensions de grilles et que j’ai supprimé ensuite ces autres grilles :p Mais ça ne pose pas de problème lors de la compilation.

Aussi, je trouve que c’est un code assez impressionnant, bonne continuation !

Ludwig

Merci :D

Salut, très bon exercice en effet :) le code a l’air correct, je n’ai vu qu’une seule chose qui me semble fausse, ligne 163 char c = NULL

NULL ne devrait etre utilisé que pour les pointeurs qui pointent sur rien, sa valeur exacte peut varier selon les implémentations donc c’est un ub ici je pense.

Et aussi tu verifies que le tampon contienne \n mais que se passe il si il contient pas \n, stdin vient de l’extérieur donc ca peut contenir n’importe quoi en théorie, généralement dans ce genre de boucle on verifie aussi pour EOF.

Et je vois que tu utilises malloc, l’exercice ne demande pas de pouvoir changer la taille de la grille en cours de jeu mais c’est bien d’avoir anticipé ce besoin.

Globalement j’ai l’impression qu’il y a beaucoup de fonctions, peut etre pourrais tu simplifier ta structure de données pour pouvoir travailler dessus directement plutot que d’avoir besoin de toutes ces fonctions ad hoc, enfin c’est mon avis,

+0 -0

Salut, très bon exercice en effet :) le code a l’air correct, je n’ai vu qu’une seule chose qui me semble fausse, ligne 163 char c = NULL

NULL ne devrait etre utilisé que pour les pointeurs qui pointent sur rien, sa valeur exacte peut varier selon les implémentations donc c’est un ub ici je pense.

Meithal

Oui, en effet lorsque je l’ai déclaré, je ne savais pas avec quelle valeur je pouvais l’initialiser, du coup j’ai utilisé NULL. Je n’ai pas compris ce que veut dire ub.

Et aussi tu verifies que le tampon contienne \n mais que se passe il si il contient pas \n, stdin vient de l’extérieur donc ca peut contenir n’importe quoi en théorie, généralement dans ce genre de boucle on verifie aussi pour EOF.

Meithal

Je crois que je n’ai pas encore bien compris cette histoire de EOF, si le joueur valide sa saisie en appuyant sur la touche entrée, c’est bien le \n qui se retrouvera en dernière position non ? Et si j’affiche ce que vaut EOF, c’est -1, ce qui fait que je suis encore plus dans le flou… Je vais faire une recherche plus approfondie pour comprendre le truc.

Et je vois que tu utilises malloc, l’exercice ne demande pas de pouvoir changer la taille de la grille en cours de jeu mais c’est bien d’avoir anticipé ce besoin.

Meithal

Merci, l’idée m’est venue que lorsque j’ai commencé à utiliser des structures dans mon code, ce qui n’était pas le cas lors de mes premiers jets, j’ai quand même bien galéré au début avec les pointeurs de mon tableau pour y arriver, mais cela m’a été bénéfique.

Globalement j’ai l’impression qu’il y a beaucoup de fonctions, peut etre pourrais tu simplifier ta structure de données pour pouvoir travailler dessus directement plutot que d’avoir besoin de toutes ces fonctions ad hoc, enfin c’est mon avis,

Meithal

J’admets qu’il y a vraiment beaucoup de fonctions pour faire un jeu aussi simple, mais je ne vois pas trop comment je pourrais réduire cela, je ne suis sans doute pas encore assez calé pour le faire.

Merci pour tes commentaires sur mon code de débutant :)

Je n’ai pas compris ce que veut dire ub

UB veut dire "undefined behavior", c’est à dire un comportement risqué qui peut planter à tout moment, quelque chose qu’il faut à tout pris pas avoir dans son code

Je crois que je n’ai pas encore bien compris cette histoire de EOF

EOF c’est quand tu arrives à la fin du buffer, c’est une manière un peu plus sécurisée de le faire, une autre idée de sécurisation serait de mettre une limite haute arbitraire, c’est à dire "ne pas itérer plus de 500 fois", mais alors tu risque de te retrouver avec un buffer non vide dans ton programme, et là encore un ub.. Le plus sûr serait d’arrêter le programme si tu n’arrives pas à vider ton buffer après 500 itérations.. Car cela provient certainement d’un comportement malicieux. Une manière qui résoudrait tous les problèmes mais non standart de le faire est d’utiliser fflush sur stdin, mais c’est pas standart…

J’admets qu’il y a vraiment beaucoup de fonctions pour faire un jeu aussi simple, mais je ne vois pas trop comment je pourrais réduire cela

Tu peux t’inspirer de ça alors, en moins de 300 lignes de code https://zestedesavoir.com/forums/sujet/10815/correction-de-tp/?page=1#p181431,

avec une IA on a un peu plus de 300 lignes de code https://gist.github.com/Meithal/8ce036caf56c72291084c43fee774ae3

Après chacun sa manière de faire, on n’est pas obligé d’aller au plus court :)

L’idée globale reste comme je l’ai dis d’avoir une structure de données suffisamment simple pour pouvoir y travailler directement sans devoir multiplier les fonctions pour le faire à notre place

+0 -0

Salut,

EOF c’est quand tu arrives à la fin du buffer

La constante EOF est retournée lorsque la fin du fichier est rencontrée ou dans le cas de la survenance d’une erreur (suivant la fonction employée), pas lorsque la fin d’un tampon est rencontrée. Dans un tel cas, soit de nouvelles données sont lues pour le remplir, soit le tampon est vidé pour écrire les données.

Pour la fonction vider_tampon(), un exemple correct est donné dans les corrections du TP. Si tu souhaites utiliser scanf(), la bonne méthode serait celle-ci.

int
vider_tampon(FILE *fp)
{
    int c;
    int n;

    while ((n = fscanf(fp, "%c", &c)) == 1 && c != '\n')
        ;

    return ferror(fp) ? 0 : 1;
}

Ainsi la fonction retourne 1 si tout s’est bien passé, c’est-à-dire si la sortie de boucle a eu lieu soit parce qu’un \n a été rencontré soit si la fin du fichier a été rencontrée (ce qui n’est pas une erreur, c’est juste la fin du fichier). Si en revanche une erreur a été rencontrée, la fonction retourne 0.

+0 -0

Merci pour toutes ces explications :)

Tu peux t’inspirer de ça alors, en moins de 300 lignes de code https://zestedesavoir.com/forums/sujet/10815/correction-de-tp/?page=1#p181431,

avec une IA on a un peu plus de 300 lignes de code https://gist.github.com/Meithal/8ce036caf56c72291084c43fee774ae3

Après chacun sa manière de faire, on n’est pas obligé d’aller au plus court :)

Meithal

Merci pour cet exemple, je vais voir de quoi je peux m’inspirer pour raccourcir mon code, le tien est pour un jeu de puissance 4 classique, c’est-à-dire fixe, alors que celui sur lequel j’ai commencé mon code est pour une dimension variable que l’on peut modifier à loisir, du coup il va falloir que je fasse la part des choses dans ce que je peux améliorer dans mon code sans perdre de vue de garder ce paramètre en compte.

Après je sais pas si ça raccourci ou pas, mais ligne 118 à 122 on pourrait rendre meilleur en lisibilité avec une initialisation directe peut-être.

typedef struct{

    const char *nom;
    const char pion;
} Joueur;
Joueur joueur_1 = {"Joueur I", 'X'};
Joueur joueur_2 = {"Joueur II", 'O'};

C’est un petit détail !

Ça fait longtemps que j’ai pas fais du C… :lol:

Découper plus tes fonctions, une fonction longue est le signe qu’elle fait trop de choses. Si tu as plus de 2 boucles/test imbriqués dans une fonction, c’est souvent le signe qu’il est probablement possible de faire une nouvelle fonction qui sera peut être réutilisable et qui simplifiera la lecture de la fonction originale (n’oublie jamais qu’on passe plus de temps à relire notre code qu’à l’écrire ;) )

Pour les globales, non, non et non, les globales c’est le mal absolu. Avec les globales, tu affaiblis ton programme. La globale, c’est open bar, n’importe qui peut venir y mettre n’importe quoi, et c’est le problème, avec une globale, tu n’as aucune garantie que son contenu sera valide, c’est à éviter comme la peste si en plus tu as des pointeurs qui traînent, ça devient le salaire de la peur…

+0 -0

Pour les globales, non, non et non, les globales c’est le mal absolu. Avec les globales, tu affaiblis ton programme. La globale, c’est open bar, n’importe qui peut venir y mettre n’importe quoi, et c’est le problème, avec une globale, tu n’as aucune garantie que son contenu sera valide, c’est à éviter comme la peste si en plus tu as des pointeurs qui traînent, ça devient le salaire de la peur…

int21h

Déjà, il faut distinguer, parce que « globales » en C cela peut renvoyer à deux choses : des variables propres à une unité de compilation (définie avec le mot-clé static) et/ou des variables utilisables dans n’importe quelle unité de compilation (définie sans le mot-clé static).

Dans le premier cas, c’est une pratique fort utile et d’ailleurs idiomatique. S’en priver reviendrait à complexifier inutilement son code. Dans le second cas, en revanche, une utilisation plus parcimonieuse s’impose effectivement, à l’image de l’instruction goto. Mais non, ce n’est pas « le mal absolu » (ça c’est le C++ :-° ), c’est simplement une pratique qui doit être réfléchie et justifiée.

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte