Déplacement des joueurs sur un jeu tel Monopoly

Anonyme12345 -  
mamiemando Messages postés 33228 Date d'inscription   Statut Modérateur Dernière intervention   -
Bonjour,

Je me suis lancé un objectif (assez long) avec des amis : faire un Monopoly.
Cependant nous sommes bloqués au moment de déplacer les joueurs sur le plateau du jeu…
Quelqu'un peut-il nous éclairer sur comment deplacer des pions (joueurs) sur le plateau ? (devons-nous utiliser des tableaux ? ou des pointeurs ? ou des structures ?)

Merci par avance de votre aide

Configuration: Windows / Chrome 96.0.4664.110

1 réponse

  1. mamiemando Messages postés 33228 Date d'inscription   Statut Modérateur Dernière intervention   7 943
     
    Bonjour,

    Étant données le fait que les pions n'interagissent pas entre eux au Monopoly, il suffit de construire pour chaque joueur une structure qui maintient sa case courante et la liste de ses possessions.

    Pour gérer l'ensemble des joueurs et la notion de tour, il peut être intéressant de stocker les joueurs (donc les structures qui leur sont associées) dans une liste sur laquelle tu itéreras à chaque tour (pense à revenir en début de liste quand tu as fini de faire jouer le dernier joueur). Un pointeur sur la structure du joueur courant sera bien pratique.

    Concernant le plateau, tu peux le modéliser par une liste de case, où chaque case stocke les informations qui la concerne (le nom, la couleur s'il y en a une, un booléen qui indique si elle est "achetable", un pointeur vers son éventuel possesseur).

    Ensuite, il te faudra une fonction pour faire le rendu (qu'il soit en mode texte ou en mode graphique) et qui sera paramétré par ta liste de joueur et ton plateau.

    Il faudra enfin prévoir quelques informations additionnelles pour gérer les autres éléments du jeu (parc public, pot d'argent collecté par les impôts et les taxes de luxe, gestion de la prison etc...)

    Bonne chance
    0
    1. Anonyme12345
       
      Merci de ta réponse,

      Effectivement je viens de commencer à réaliser les structures pour chaque joueur c’est une très bonne idée.
      Cependant tu m’as conseillé de stocker les structures de chaque joueur dans une liste cependant je n’ai pas encore vu les listes chaînées. Y aurait-il par hasard une autre manière de faire ?

      Merci
      0
      1. [Dal] Messages postés 6122 Date d'inscription   Statut Contributeur Dernière intervention   1 108 > Anonyme12345
         
        Tu peux faire une liste de joueurs avec un simple tableau de type
        struct joueur
        d'une taille déterminée, par exemple :
        struct joueur joueurs[3];
        .

        Tu peux aussi allouer dynamiquement la mémoire nécessaire au stockage de nb_joueurs, en utilisant malloc sur un pointeur de type
        struct joueur
        pour un nombre de bytes égal à
        sizeof(struct joueur) * nb_joueurs
        . Tu pourras itérer sur les
        struct joueur
        avec l'opérateur
        []
        comme s'il s'agissait d'un tableau.

        Cela donne ceci :

        #include <stdlib.h>
        #include <string.h>
        
        #define MAX_ST 255
        
        struct joueur {
                char nom[MAX_ST];
                char prenom[MAX_ST];
                int solde_liquidites;
        };
        
        int main(void)
        {
                struct joueur * joueurs;
                int nb_joueurs = 3;
        
                joueurs = malloc(sizeof(struct joueur) * nb_joueurs);
        
                strcpy(joueurs[0].nom, "CHAPLIN");
                strcpy(joueurs[0].prenom, "Charlie");
                joueurs[0].solde_liquidites = 1500;
                strcpy(joueurs[1].nom, "LLOYD");
                strcpy(joueurs[1].prenom, "Harold");
                joueurs[1].solde_liquidites = 1500;
                strcpy(joueurs[2].nom, "KEATON");
                strcpy(joueurs[2].prenom, "Buster");
                joueurs[2].solde_liquidites = 1500;
        
                free(joueurs);
        
                return 0;
        }
        0
      2. Anonyme12345 > [Dal] Messages postés 6122 Date d'inscription   Statut Contributeur Dernière intervention  
         
        le code suivant :
          struct joueur 
        {
                char nom[MAX_ST];
                char prenom[MAX_ST];
                int solde_liquidites;
        }; 

        nous l'avons déjà utilisé dans le menu car dès le debut de partie nous entrons toutes informations necessaires (nom, prenom...) en lançant une partie.

        En revanche, merci pour la suite, j'ai encore du mal avec la fonction strcpy mais je vais essayer de comprendre et de m'inspirer de ce que tu as fait.
        Dernière petite question, n'ayant pas vu les listes chainées, je comptais m'occuper du déplacement des joueurs d'une manière simple en utilisant les coordonnées (un int position)
        Y aurait-il plus simple ? Car cette méthode demande une longue repetition pour chaque case du plateau.
        0
      3. [Dal] Messages postés 6122 Date d'inscription   Statut Contributeur Dernière intervention   1 108 > Anonyme12345
         
        Oublie les listes chaînées, elles ne seraient concrètement sans doutes pas nécessaires dans ce jeu et ajouteraient une complexité inutile.

        Mon code était un code d'exemple visant à montrer comment, à partir d'un type struct, tu obtiens un tableau d'une taille fixée par ton programme, ou d'une taille dynamique, dont tu peux parcourir les éléments avec un indice.

        strcpy() n'a rien de compliqué :

        https://www.cplusplus.com/reference/cstring/strcpy/

        La seule difficulté est de s'assurer que la chaîne copiée ne dépasse pas l'espace mémoire disponible à la chaîne de destination, ce dont tu devrais t'assurer en amont dans ta fonction gérant la saisie d'une ligne de texte que tu dis avoir déjà implémentée.

        Je comptais m'occuper du déplacement des joueurs d'une manière simple en utilisant les coordonnées (un int position)
        Y aurait-il plus simple ? Car cette méthode demande une longue repetition pour chaque case du plateau.


        Oui, tu peux rajouter un champ "int position" dans la
        struct joueur
        .

        Ce int peut représenter l'indice d'un élément d'un tableau de
        struct case
        qui va contenir le nom de la case, son type, et les autres informations utiles au jeu afférentes à la case.

        Pourquoi dis tu que "cette méthode demande une longue repetition pour chaque case du plateau" ?
        0
      4. mamiemando Messages postés 33228 Date d'inscription   Statut Modérateur Dernière intervention   7 943 > [Dal] Messages postés 6122 Date d'inscription   Statut Contributeur Dernière intervention  
         
        Bonjour

        Pour préciser ce qui a été dit plus haut : j'ai dit liste mais effectivement un tableau est plus adapté.

        Code

        #include <stdio.h>
        
        #define MAX_NUM_PAYERS 10
        #define STR_MAX_LEN 255
        
        typedef struct player_t {
            char name[STR_MAX_LEN];
            unsigned money;
            //...
        } player_t;
        
        int main(){
            player_t players[MAX_NUM_PAYERS];
            player_t * player;
            unsigned action, player_id, num_players;
            printf("Nombre de joueurs ? ");
            scanf("%d", &num_players);
                      
            // Initialiser les joueurs
            for (
                player_id = 0, player = &players[0];
                player_id < num_players;
                player_id++, player++
            ) { 
                printf("Nom du joueur %d ? ", player_id);
                scanf("%s", &(player->name[0]));
                player->money = 150000;
            }  
               
            player_id = 0;
            while(1) {
                player_t * player = players + player_id; // ou &(players[player_id])
                printf("C'est au tour de %s (joueur %d) (il a %d euros)\n", player->name, player_id, player->money);
                printf("Choisir une action\n");                                          
                // Afficher le menu des actions possibles                                
                scanf("%d", &action);                                                    
                // Appliquer l'action
                player_id = (player_id + 1) % num_players;
            }
        
            return 0;
        } 


        Résultat

        (mando@silk) (~) $ gcc -W -Wall toto.c && ./a.out 
        Nombre de joueurs ? 2
        Nom du joueur 0 ? toto
        Nom du joueur 1 ? tata
        C'est au tour de toto (joueur 0) (il a 150000 euros)
        Choisir une action
        1
        C'est au tour de tata (joueur 1) (il a 150000 euros)
        Choisir une action
        2
        C'est au tour de toto (joueur 0) (il a 150000 euros)
        Choisir une action
        1
        C'est au tour de tata (joueur 1) (il a 150000 euros)
        Choisir une action
        1
        C'est au tour de toto (joueur 0) (il a 150000 euros)
        Choisir une action


        Ici j'impose que le nombre de joueurs est au plus 10 car je pars du principe que tu n'as pas peut-être pas encore vu les allocations dynamiques. Si tu les as vu il vaut mieux allouer players à l'aide de
        malloc
        ou
        calloc
        .

        Tu noteras que tu n'as pas besoin de
        strcpy
        à ce stade. On peut directement écrire dans la structure comme le buffer qui stocke le nom est pré-alloué.

        Comment utiliser strcpy

        Commençons sans pointeurs.

        En toute rigueur il vaudrait mieux passer par un buffer intermédiaire plus grand qui contrôle que le nom ne dépasse pas
        STR_MAX_LEN
        , et si c'est le cas, copier ce buffer avec
        strcpy
        vers
        player->name
        avec une fonction de ce genre :

        #include <string.h>
        
        void saisie(const char * label, char * dst, unsigned dst_size) {
            char buffer[1000];
            while(true) {
                printf(label);
                scanf("%s", &(buffer[0]));
                if (strlen(buffer) + 1 < dst_size) break;
                fprintf(stderr, "Texte trop long (maximum %d caractères)\n", dst_size);
            }
            strcpy(dst, &(buffer[0]));
        }


        Les pointeurs

        Cette notion est en général très mal expliquée et très mal comprises par les étudiants. Je t'invite donc à prendre le temps de lire ce qui suit et à demander des précisions si ça n'est pas clair.

        Ce qui est fondamental, c'est comprendre qu'un pointeur est une adresse mémoire. Une adresse est donc une valeur numérique. Quelle que soit la nature de la donnée pointée, une adresse fait toujours une taille bien précise (64 bits sur OS 64 bits, 32 bits sur un OS 32 bits, etc...).

        Une adresse générique est de type
        void *
        . Pour une adresse générique, les opérateurs sur les pointeurs ne sont pas définis, car la taille du bloc mémoire pointé n'est pas spécifié. Cela signifie que les opérateurs des pointeurs (comme l'opérateur unaire
        *
        pour accéder à la donnée pointée, l'opérateur
        +
        et l'opérateur
        ++
        ) ne sont pas définis. Ce type de pointeur est donc peu pratique, il n'est d'ailleurs utilisé que pour des cas bien précis (tout ce qui travaille à l'échelle de la mémoire).

        C'est pour ça que la plupart du temps on veut typer un pointeur. Ça sera toujours une adresse mémoire, mais pour le compilateur, le fait de la typer va lui donner un sens qui va débloquer les opérateurs dont je parlais et contrôler qu'on écrit pas n'importe quoi.

        Prenons un exemple simple :

        int x = 7;
        int * px = &x;
        printf("%d\n", x);
        printf("%d\n", *px); // *px équivaut à x


        L'opérateur
        &
        retourne l'adresse d'un objet (ici un
        int
        ). Il est donc légitime de typer ce pointeur en
        int *
        . Si
        x
        avait été un
        const int
        , il aurait fallu que
        px
        soit de type
        const int *
        .

        Bien entendu rien n'oblige de faire des pointeurs de pointeurs, voire imaginer de multiples redirections. C'est cette capacité à naviguer d'adresse en adresse qui leur vaut le nom de pointeur. L'exemple ci dessous est un peu scolaire mais permet de voir le principe :

        int x = 7;
        int * px = &x;
        int ** ppx = &px;
        printf("%d\n", x);
        printf("%d\n", *px);
        printf("%d\n", **ppx); // **ppx équivaut à *px qui équivaut à px


        Quand on manipule un tableau d'objets homogènes (disons des
        player_t
        ), par exemple, on sait que chaque case est de type
        player_t
        et est de taille
        sizeof(player_t)
        . Cela signifie que passer à la case suivant revient à avancer de
        sizeof(player_t)
        octets. Un tel pointeur se note
        player_t *
        ou
        const player_t *
        selon qu'il donne un accès en lecture/écriture ou juste un accès en lecture.

        Concernant le typage des pointeurs, qui peut le plus peut le moins (mais pas le contraire) :
        • Cela signifie que si une fonction ne nécessite qu'un accès en lecture, elle devrait stipuler que le pointeur attendu est
          const
          (sans quoi les gens qui ont un pointeur
          const
          ne peuvent pas utiliser ta fonction). Si on prend l'exemple de
          strcpy
          , on a besoin d'un accès en lecture (mais pas en écriture) à la chaîne source, et d'un accès en lecture/écriture à la chaîne destination donc son prototype est du genre
          strcpy(char * dst, const char * src)
          .
        • Cela signifie aussi que si une fonction marche avec un
          void *
          , elle marche a fortiori avec un
          player_t *
          (c'est le cas de
          memcpy
          ).
        • Par contre, les réciproques sont généralement fausses, et le compilateur partira du principe qu'elles le sont en refusant de compiler. Si en tant que programmeur, on est sûr que c'est légitime, alors on peut faire un cast (c'est-à-dire, affirmer que le pointeur est d'un autre type). C'est typiquement ce qui se passe quand on fait une allocation dynamique avec
          malloc
          . Et ce sont a priori les seuls cas où un cast est légitime.


        Si on revient à notre exemple de
        strcpy
        , il faut bien comprendre que ça n'est pas parce qu'on fournit une adresse que cet adresse est valide (c'est-à-dire correspond au début d'un bloc suffisamment large pour accueillir la donnée saisie). Il faut donc soit :
        • le préallouer (comme dans le premier programme de ce message) avec une taille suffisamment grande et connue à la compilation (c'est le cas dans ce programme grâce à
          STR_MAX_LEN
          ) ;
        • l'allouer dynamiquement (on y est contraint quand la taille n'est pas connue à la compilation, mais à l'exécution). Dans ce cas on doit passer par une fonction comme
          malloc
          ou
          calloc
          .


        Une "bonne" allocation mémoire doit :
        • être contrôlée (car elle peut échouer si ton système n'a plus de mémoire) : si tu ne utilises un pointeur obtenu par une allocation qui a échoué, ton programme plantera (erreur de segmentation) ;
        • être désalloué quand on n'en a plus besoin : si tu ne le fais pas la mémoire reste réservée et ton programme gonflera en mémoire (on parle de fuite mémoire - leak).
        • la désallocation doit être fait une seule fois : si tu le désalloues deux fois le même pointeur (alors qu'il n'a pas été réalloué entretemps) tu vas corrompre la mémoire de ton programme et il plantera.


        Une "bonne" utilisation d'un pointeur impose :
        • que la zone pointée soit bien définie (soit une variable locale et donc allouée dans la pile, soit parce que le pointeur a été alloué dynamiquement) ;
        • que la zone pointée soit de taille suffisante (selon le théorème du chausse pied, qui peu le plus peu le moins, mais dans le cas contraire, tu t'exposes à une erreur de segmentation) ;
        • en particulier, si tu manipules des tableaux, attention à ne pas sortir de leurs limites (sinon le programme plantera avec une erreur de segmentation !).


        Voici à quoi pourrait ressembler alors une ébauche de ton programme :

        #include <stdio.h>   // printf, scanf
        #include <stdlib.h>  // malloc, free
        #include <stdbool.h> // bool
        #include <string.h>  // strcpy, strlen
        
        #define MAX_NUM_PAYERS 10
        #define STR_MAX_LEN 255
        
        typedef struct player_t {
            char name[STR_MAX_LEN];
            unsigned money;
            //...
        } player_t;
        
        void saisie(const char * label, char * dst, unsigned dst_size) {
            char buffer[1000];
            while(true) {
                printf(label);
                scanf("%s", &(buffer[0]));
                if (strlen(buffer) + 1 < dst_size) break;
                fprintf(stderr, "Texte trop long (maximum %d caractères)\n", dst_size);
            }
            strcpy(dst, &(buffer[0]));
        }
        
        int main(){
            player_t * players;
            player_t * player;
            unsigned action, player_id, num_players;
            printf("Nombre de joueurs ? ");
            scanf("%d", &num_players);
            players = (player_t *) malloc(sizeof(player_t) * num_players);
            if (!players) {
                fprintf(stderr, "Erreur mémoire");
                return 1;
            }
        
            // Initialiser les joueurs
            for (
                player_id = 0, player = &players[0];
                player_id < num_players;
                player_id++, player++
            ) {
                saisie("Nom du joueur ? ", player->name, STR_MAX_LEN);
                player->money = 150000;
            }
        
            player_id = 0;
            while(1) {
                player_t * player = players + player_id; // ou &(players[player_id])
                printf("C'est au tour de %s (joueur %d) (il a %d euros)\n", player->name, player_id, player->money);
                printf("Choisir une action\n");
                // Afficher le menu des actions possibles
                scanf("%d", &action);
                // Appliquer l'action
                player_id = (player_id + 1) % num_players;
            }
        
            free(players);
            return 0;
        }
        


        Dernier point important. En C, ça n'est pas parce qu'un programme semble marcher une fois qu'il est juste. Parfois la mémoire "se met bien" et le programme ne plante pas "par chance" alors que le code est conceptuellement faux et devrait provoquer une erreur de segmentation. Il est recommandé de contrôler que la mémoire est bien utilisée avec des outils comme
        valgrind
        .

        Bonne chance
        0