Déplacement des joueurs sur un jeu tel Monopoly

Fermé
Anonyme12345 - Modifié le 3 janv. 2022 à 13:53
mamiemando Messages postés 33079 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 23 avril 2024 - 4 janv. 2022 à 12:16
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
A voir également:

1 réponse

mamiemando Messages postés 33079 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 23 avril 2024 7 749
3 janv. 2022 à 14:31
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
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
[Dal] Messages postés 6174 Date d'inscription mercredi 15 septembre 2004 Statut Contributeur Dernière intervention 2 février 2024 1 083 > Anonyme12345
Modifié le 3 janv. 2022 à 18:51
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
Anonyme12345 > [Dal] Messages postés 6174 Date d'inscription mercredi 15 septembre 2004 Statut Contributeur Dernière intervention 2 février 2024
Modifié le 4 janv. 2022 à 10:56
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
[Dal] Messages postés 6174 Date d'inscription mercredi 15 septembre 2004 Statut Contributeur Dernière intervention 2 février 2024 1 083 > Anonyme12345
Modifié le 4 janv. 2022 à 10:47
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
mamiemando Messages postés 33079 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 23 avril 2024 7 749 > [Dal] Messages postés 6174 Date d'inscription mercredi 15 septembre 2004 Statut Contributeur Dernière intervention 2 février 2024
Modifié le 4 janv. 2022 à 12:16
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