CSC 4103 – Programmation système

Portail informatique

Contrôle Final 1 – Année 2021/2022

Le barème est donné à titre indicatif

Echauffement (3 points)

On dispose de la backtrace suivante:
(gdb) bt #0 maj_to_min (maj=69 'E') at server_code_etudiant.c:251 #1 0x00005555555560f9 in process_cmd_unmot (client=0x55555555a6d0, param=0x7fffffffc3a7 "ETOUFFE") at server_code_etudiant.c:275 #2 0x00005555555568a7 in handle_incoming_cmd (cli=0x55555555a6d0) at server_code_etudiant.c:393 #3 0x000055555555598c in main (argc=1, argv=0x7fffffffcd28) at server.c:160 (gdb)

1 point

Dans quelle fonction le programme s'est-il arrêté ?
Barème : 1 point
maj_to_min

2 points

On souhaite examiner la variable locale buff_in de la fonction handle_incoming_cmd. Quelle suite de commandes gdb peut-on utiliser pour afficher la valeur de buff_in ?
Barème : 2 points
frame 2 puis p buff_in (ou print buff_in)

Motus (12 points)

Le but de cet exercice est d'ajouter une dimension ludique à la chatroom étudiée lors du cours intégré 10 en y intégrant le jeu Motus (ou Wordle). Les règles du jeu sont simples:

  • Au démarrage de la partie (lorsque la commande /motus est saisie par un joueur), un mot de 7 lettres est choisi au hasard dans un dictionnaire, par exemple INITIEE. Il s'agit du mot_a_trouver.
  • Les joueurs peuvent proposer avec la commande /unmot des mots de 7 lettres appelés mot_propose (par exemple: INTERNE) pour deviner le mot_a_trouver;
  • Chacune des 7 lettres du mot_propose est comparée à la lettre placée à la même position dans mot_a_trouver:
    • Si la ième lettre du mot_propose est la même que la ième lettre de mot_a_trouver, le serveur affiche la lettre en majuscule;
    • Si la ième lettre du mot_propose apparaît à une autre position dans mot_a_trouver, le serveur affiche la lettre en minuscule;
    • Si la ième lettre du mot_propose n'apparaît pas dans mot_a_trouver, le serveur affiche un tiret (-) à la place.

Par exemple, si le mot_a_trouver est INITIEE et qu'un joueur propose INTERNE, le serveur affiche INte-nE:

  1. I est correctement positionné;
  2. N est correctement positionné;
  3. t n'est pas en 3ème position du mot_a_trouver, mais apparaît ailleurs (en 4ème position);
  4. e n'est pas en 4ème position du mot_a_trouver, mais apparaît ailleurs (en 6ème position);
  5. r n'apparaît pas dans le mot_a_trouver et est donc remplacé par -;
  6. n n'est pas en 6ème position du mot_a_trouver, mais apparaît ailleurs (en 2ème position);
  7. E est correctement positionné;

La partie s'achève lorsqu'un joueur propose le mot_a_trouver. Voici un exemple de déroulement d'une partie:

(chatroom) $ /motus * Motus: new word: I------ (chatroom) $ /unmot INTERNE * Motus: Didier_le_bourdon suggests INTERNE * Motus: -> INte-nE (chatroom) $ /unmot INEPSIE * Motus: Didier_le_bourdon suggests INEPSIE * Motus: -> INe--iE (chatroom) $ /unmot INITIAL * Motus: Didier_le_bourdon suggests INITIAL * Motus: -> INITI-- (chatroom) $ /unmot INITIEE * Motus: Didier_le_bourdon suggests INITIEE * Motus: congratulations to Didier_le_bourdon who found 'INITIEE'
Pour simplifier le développement, on considère dans la suite de l'exercice que les utilisateurs ne proposent que des mots de 7 lettres écrits en majuscule. Il n'est donc pas demandé de gérer les cas particuliers de caractères accentués ou des erreurs de saisie.

2 points

Dans un premier temps, il est nécessaire de générer un fichier contenant tous les mots valides. On suppose qu'on dispose d'un script generate.sh nom_fichier qui crée le fichier nom_fichier.

Quelles modifications faut-il apporter au fichier Makefile pour que la commande make gènère le fichier word_list.dat s'il n'existe pas ?

barème: 2 points
BIN=server client wordlist.dat [...] wordlist.dat: generate.sh wordlist.dat

5 points

Implémentez la fonction void get_random_word(char word[WORD_LEN+1]). Cette fonction ouvre le fichier word_list.dat et lit un mot au hasard. Ce mot est stocké dans le paramètre word de la fonction.
Pour obtenir un nombre aléatoire, utilisez la fonction int rand() qui retourne un entier aléatoire compris entre 0 et RAND_MAX.
Le fichier word_list.dat est potentiellement très grand. Heureusement, il ne contient que des mots de 7 lettres séparés par le caractère '\n'. Il est donc possible d'accéder rapidement au ième mot du fichier.
Barème:
  • Calcul de la taille du fichier: 2 points
  • Lecture du mot: 2 points
  • Ouverture/fermeture du fichier: 1 point
static void get_random_word(char word[WORD_LEN+1]) { char* filename="wordlist.dat"; FILE*f = fopen(filename, "r"); if(! f) { fprintf(stderr, "Cannot open %s\n", filename); abort(); } fseek(f, 0, SEEK_END); int pos = ftell(f); int nbwords= pos/(WORD_LEN+1); int index = rand() % nbwords; fseek(f, index * (WORD_LEN+1), SEEK_SET); fread(word, sizeof(char), WORD_LEN, f); fclose(f); }

2 points

Implémentez la fonction void process_cmd_motus(client_t* client, char *param);. Cette fonction, qui est appelée lorsqu'un client lance la commande /motus, doit appeler la fonction get_random_word et stocker le résultat dans la variable globale mot_a_trouver. La fonction crée ensuite la chaîne de caractères mot_masque qui contient WORD_LEN caractères: la première lettre du mot_a_trouver suivie de tirets ('-'). Enfin, la fonction envoie un message à tous, par exemple:

* [Motus] New word: P------

On considère que la variable globale mot_a_trouver a été déclarée de cette façon:

static char mot_a_trouver[WORD_LEN+1];

On considère que dans handle_incoming_cmd(), le code suivant a été ajouté:

} else if(!strcmp(command, "/motus")) { process_cmd_motus(cli, cmd_line);
Barème:
  • Appel à get_random_word: 0.5 pt
  • Création du mot_masque: 1 pt
  • Envoi du message: 0.5 pt
void process_cmd_motus(client_t* client, char*param) { get_random_word(mot_a_trouver); char mot_masque[WORD_LEN+1]; mot_masque[0] = mot_a_trouver[0]; for(int i = 1; i < WORD_LEN; i++) mot_masque[i] = '-'; mot_masque[WORD_LEN] = '\0'; char buff_out[1024]; sprintf(buff_out, "* Motus: new word: %s\n", mot_masque); send_message_all(buff_out); }

3 points

Implémentez la fonction static int comparer_mots(char mot_propose[WORD_LEN+1], char mot_masque[WORD_LEN+1]).

Cette fonction, qui est appelée lorsqu'un client lance la commande /unmot motproposé, doit comparer les lettres du mot_propose avec les lettres du mot_a_trouver. La fonction rempli mot_masque, et indique, pour chaque lettre du mot_propose:

  • si elle est correctement placée. Dans ce cas, la lettre est écrite en majuscule dans mot_masque;
  • si elle incorrectement placée (c'est à dire que la lettre apparait dans mot_a_trouver, mais à une autre position), la lettre est écrite en minuscule dans mot_masque;
  • si elle n'est pas présente dans mot_a_trouver, la lettre est remplacée par le caractère '-' dans mot_masque.
La fonction retourne le nombre de lettres correctement placées.
Barème:
  • 2.5 points pour l'algo général
  • 0.5 point pour le '\0' final
static int comparer_mots(char mot_propose[WORD_LEN+1], char mot_masque[WORD_LEN+1]) { int ncorrect = 0; for(int i=0; i<WORD_LEN; i++) { /* make sure the proposed word is uppercase */ mot_propose[i] = upper(mot_propose[i]); if(mot_propose[i] == mot_a_trouver[i]) { mot_masque[i] = upper(mot_propose[i]); ncorrect++; } else { mot_masque[i] = '-'; for(int j=0; j<WORD_LEN; j++) if(mot_a_trouver[j] == mot_propose[i]) mot_masque[i] = lower(mot_propose[i]); } } mot_masque[WORD_LEN] = '\0'; return ncorrect; }

Leaderboard (8 points)

On souhaite maintenant pouvoir afficher un classement des meilleurs joueurs de MOTUS. Lorsqu'un joueur devine le mot_a_trouver, il gagne 100 points.

Pour gérer ce classement, il vous est demandé d'implémenter une liste chaînée des joueurs triée par score. Ainsi le joueur ayant le score le plus élevé est stocké au début de la liste, le second du classement est en deuxième position, etc.

La liste leaderboard est définie comme suit:

struct joueur { client_t *client; int score; struct joueur *next; }; struct joueur* leaderboard = NULL;

3 points

Implémentez la fonction void process_cmd_classement(client_t* client, char*param);. Cette fonction, qui est appelée lorsqu'un client lance la commande /classement, doit parcourir la liste chaînée leaderboard et envoyer à tous les clients le classement. Par exemple:

(chatroom) $ /classement * Classement: * ------------ * [1] Colin_le_colin (200 points) * [2] Gérard_le_canard (100 points)
Bareme:
  • Parcours de la liste: 2 points
  • Affichage correct: 1 point
void process_cmd_classement(client_t* client, char*param) { char buff_out[1024]; struct joueur* j = leaderboard; sprintf(buff_out, "* Classement:\n"); strcat(buff_out, "* ------------\n"); int rank = 1; while(j) { char tmp[1024]; sprintf(tmp, "* [%d] %s (%d points)\n", rank++, j->client->name, j->score); strcat(buff_out, tmp); j = j->next; } send_message_all(buff_out); }

On considère que dans handle_incoming_cmd(), le code suivant a été ajouté:

} else if(!strcmp(command, "/classement")) { process_cmd_classement(cli, cmd_line);

5 points

Lorsqu'un joueur trouve le mot_a_trouver, il marque 100 points et le classement doit être mis à jour. Pour cela, implémentez la fonction void add_points(client_t* client, int nb_points). Cette fonction est appelée par process_cmd_unmot. Le paramètre client est le client auquel il fait ajouter nb_points points.

Implémentez la fonction add_points en suivant l'algorithme suivant:

  • Rechercher le struct joueur correspondant à client dans la liste leaderboard, et l'enlever de la liste chaînée.
    • Si le joueur était présent dans la liste, ajouter nb_points à son score;
    • Sinon, allouer un struct joueur et l'initialiser.
  • Insérer le struct joueur dans la liste chaînée leaderboard. La liste doit rester triée par score descendant (le score le plus élévé en début de liste)
barème:
  • recherche/suppression du joueur: 2 points
  • allocation/modification du score: 1 point
  • Insertion dans la liste: 2 points
void add_points(client_t* client, int nb_points) { struct joueur* j = leaderboard; struct joueur* prev = NULL; /* remove the client from the list */ while(j) { if(j->client == client) { if(prev) prev->next = j->next; else leaderboard = j->next; break; } prev = j; j = j->next; } if(j == NULL) { j = malloc(sizeof(struct joueur)); j->client = client; j->score = nb_points; j->next = NULL; } else { j->score += nb_points; } /* insert in first position if needed */ if(! leaderboard || leaderboard->score < j->score) { j->next = leaderboard; leaderboard = j; return; } struct joueur* cur_joueur = leaderboard; while(cur_joueur->next) { if(cur_joueur->next->score < j->score) { /* insert after cur_joueur */ break; } cur_joueur = cur_joueur->next; } j->next = cur_joueur->next; cur_joueur->next = j; }