Shadows of the knight - WAR

Table of Contents

Ce document est disponible sur ma page personnelle

La page GitLab associé à ce document est consultable ici

joker.jpg

Figure 1: The Joker - The Dark Knight

1 Shadow of the knight

1.1 Problèmatique

Le but de cet exercice est de déterminer la position d'une bombe dans une grille. Pour ce faire, on dispose à chaque occurrence d'une boucle while, d'une entrée clavier nous renseignant sur la direction relative à des coordonnées que l'on affiche en fin de cette même boucle et représentant la position d'un personnage (Batman) sur cette grille.

Ainsi en fonction des directions que l'on reçoit sous forme d'une chaîne de caractères il est possible de connaitre la direction de la bombe par rapport aux dernières coordonnées affichées ou le cas échéant en fonction de la position de départ du personnage.

La chaîne de caractère donnée en entrée peut contenir:

  • 'U' ou 'D' pour indiquer que la bombe est situé plus haut ou plus bas que le personnage dans la grille.
  • 'L' ou 'R' pour indiquer que la bombe est situé plus à gauche ou plus à droite que le personnage dans la grille.

Enfin, la bombe doit être trouvée en moins de N déplacements sans quoi elle explose et le test est un échec.

1.2 Résolution

1.2.1 Principe de résolution

Pour résoudre de problème, il m'a tout de suite semblé évident qu'il fallait effectuer une sorte de recherche par dichotomie en 2 dimensions pour localiser le plus rapidement possible l'emplacement de la bombe.

En effet, puisque l'on travaille avec des coordonnées, on peut se représenter chaque dimension comme un tableau trié par ordre croissant, et la chaine de caractère donnée en entrée comme une information sur la position de l'index par rapport au personnage.

1.2.2 Réalisation

Pour réaliser cette solution j'ai d'abord créé des variables Xbot, Ybot, Xtop et Ytop correspondants aux bornes entre lesquelles se situe la bombe, et qui ont pour but de converger vers les coordonnées de cette bombe.

Puis pour resserrer les bornes sur la bombe, je détermine grâce à la fonction strchr() , s'il y a un caractère propre à l'une des quatre directions possibles dans la chaîne de caractères donnée en entrée.

Ainsi, si je détecte que la chaîne de caractères contient une certaine direction, je peux resserrer la borne opposée à cette direction sur la position actuelle du personnage avant de le replacer au centre des bornes et d'afficher sa position.

Par exemple si je détecte que la bombe est à droite, je place la borne de gauche à la position actuelle du personnage.

En, ce qui concerne, la variable N donnée en entrée, je ne l'utilise pas car le programme de test de CodinGame s'arrête automatiquement une fois que la position affichée est égale à la position de la bombe.

1.2.3 Code source

 1: #include <stdlib.h>
 2: #include <stdio.h>
 3: #include <string.h>
 4: 
 5: int main()
 6: {
 7:     int W, H, N, X0, Y0;
 8:     scanf("%d%d%d%d%d", &W, &H, &N, &X0, &Y0);
 9:     int Xbot, Ybot = 0;
10:     int Xtop = W-1;
11:     int Ytop = H-1;
12: 
13:     while (1) {
14:         char bomb_dir[4];
15:         scanf("%s", bomb_dir);
16: 
17:         if(strchr(bomb_dir,'U')!=NULL)  {Ytop = Y0-1;}
18:         if(strchr(bomb_dir,'D')!=NULL)  {Ybot = Y0+1;}
19:         if(strchr(bomb_dir,'L')!=NULL)  {Xtop = X0-1;}
20:         if(strchr(bomb_dir,'R')!=NULL)  {Xbot = X0+1;}
21: 
22:         X0=Xbot+((Xtop-Xbot)/2);
23:         Y0=Ybot+((Ytop-Ybot)/2);
24:         printf("%d %d\n",X0,Y0);
25:     }
26:     return 0;
27: }

1.3 Résultats de quelques tests

Puisque les entrées données à chaque occurrence de la boucle while sont donné par CodinGame en fonction de la position du personnage et que je ne dispose pas d'un tel programme, les solutions présentées correspondent à la dernière position de Batman sur la grille de jeu telle qu'obtenue sur CodinGame.

1.3.1 Beaucoup de sauts

Le premier test consiste en une grille de 4 \(\times\) 8 cases avec Batman initialement sur la case de coordonnées (2,3).

On finit par trouver que les coordonnées de la bombe sont les suivantes:

Abscisse Ordonnée
3 7

1.3.2 Encore moins de sauts

Le test Encore moins de sauts consiste en une grille de 40 \(\times\) 60 cases avec Batman initialement sur la case de coordonnées (6,6).

On finit par trouver que les coordonnées de la bombe sont les suivantes:

Abscisse Ordonnée
38 38

1.3.3 Pas là?

Le test Pas là? consiste en une grille de 9999 \(\times\) 9999 cases avec Batman initialement sur la case de coordonnées (54,77).

On finit par trouver que les coordonnées de la bombe sont les suivantes:

Abscisse Ordonnée
9753 2531

1.3.4 Réussite des tests

Les tests présentés ici ainsi que les autres tests exécutés par CodinGame, finissent tous par afficher le résultat attendu ce qui fait de ce puzzle une réussite.

1.4 Solution proposée par Alain-Delpuch

J'ai choisi d'étudier le code d'Alain-Delpuch, car la solution qu'il utilise pour étudier la direction relative de la bombe est une méthode que j'ai moi-même abordée avant de m'inspirer de la méthode de mzbear dans Detective Pikaptcha et que j'ai d'ailleurs déjà étudié.

1.4.1 Principe de résolution

Pour résoudre ce problème, Alain-Delpuch a dû avoir sensiblement le même raisonnement que j'ai pu avoir puisqu'il resserre l'intervalle où peut être la bombe avant de recentrer le personnage entre les nouvelles bornes.

1.4.2 Réalisation

Là où le code d'Alain-Delpuch diffère du mien, est la structure utilisée pour traiter les caractères de directions contenus dans la chaîne de caractères d'entrée. En effet, il utilise des switch là où j'utilisais des if évaluant le résultat de la commande strchr.

1.4.3 Code source

 1: #include <stdio.h>
 2: 
 3: int 
 4: main() {
 5:     int W; // width of the building.
 6:     int H; // height of the building.
 7:     scanf("%d%d", &W, &H);
 8: 
 9:     int N; // maximum number of turns before game over.
10:     scanf("%d", &N);
11: 
12:     int X0;
13:     int Y0;
14:     scanf("%d%d", &X0, &Y0);
15: 
16:     int xmin = 0 ; int xmax = W ;
17:     int ymin = 0 ; int ymax = H ;
18: 
19:     // game loop
20:     while (N--) {
21:         char bombDir[4]; // the direction of the bombs from batman's current location (U, UR, R, DR, D, DL, L or UL)
22:         scanf("%s", bombDir);
23:         switch (bombDir[0]) {
24:             case 'U' : ymax = Y0 ; break ;
25:             case 'D' : ymin = Y0 ; break ;
26:             case 'R' : xmin = X0 ; break ;
27:             case 'L' : xmax = X0 ; break ;
28:         }
29:         switch (bombDir[1]) {
30:             case 'R' : xmin = X0 ; break ;
31:             case 'L' : xmax = X0 ; break ;
32:         }
33: 
34:         Y0 = (ymax + ymin)/2 ; 
35:         X0 = (xmin + xmax)/2 ; 
36: 
37:         printf("%d %d\n", X0, Y0);
38:     }
39: }

2 WAR

2.1 Problèmatique

Le principe de ce puzzle est assez simple puisqu'il s'agit d'automatiser le jeu de cartes de la bataille et d'en déterminer un gagnant ainsi que le nombre de tours effectués pour gagner.

Pour ce faire, il faut respecter quelques règles:

  • Cas 1: Chaque joueur doit tirer une carte et le gagnant est celui qui a la carte avec la plus grande valeur parmi la liste {2,3,4,5,6,7,8,9,10,J,Q,K,A}.
  • Cas 2: Si les joueurs tirent une carte de la même valeur, chaque joueur pose 3 cartes sur la table puis retour au Cas 1.
  • Cas 3: Le joueur qui gagne le duel remporte toutes les cartes sur la table dans l'ordre et en empochant toujours les cartes du joueur 1 en premier.
  • Cas 4: Si un des joueurs n'a plus de cartes, alors l'autre a gagné.
  • Cas 5: Si un des joueurs n'a plus de cartes lors du Cas 2, alors il y a égalité.

Coté programme:

  • Cas 1: Si un des joueurs a gagné, il faut afficher son numéro puis le nombre de tours pour gagner, séparés par un espace.
  • Cas 2: S'il y a égalité, il faut afficher "PAT".

Enfin, en ce qui concerne les entrées, elles sont les suivantes:

  • Le nombre de cartes n du joueur 1 puis les n cartes données une à une.
  • Le nombre de cartes n du joueur 2 puis les n cartes données une à une.

2.2 Résolution

2.2.1 Structure de stockage utilisé

Dans le cadre du jeu de la bataille, il faut toujours jouer la carte la plus ancienne du paquet et ajouter les cartes gagnées à la fin du paquet ce qui correspond justement à la définition d'une file. C'est pourquoi j'ai utilisé cette structure de données pour représenter la main des joueurs ainsi que leur tas de cartes en jeu.

Ainsi, j'ai créé une structure élément contenant une chaine de caractère ainsi qu'un pointeur sur l'élément suivant et un sur l'élément précédent. Puis j'ai créé une structure file contenant deux pointeurs sur un élément et permettant de rentrer dans la file soit par le premier élément soit par le dernier. De cette façon, il me suffit de créer une fonction pour ajouter un élément à la file et une fonction pour en enlever afin d'avoir une structure de stockage adapté rendant le reste du programme assez simple à gérer.

2.2.2 Déterminer le gagnant d'un duel

Le problème avec le format d'entrée des cartes est que les caractères représentant la valeur des cartes sont parmi la liste {2,3,4,5,6,7,8,9,10,J,Q,K,A}, ils ne sont pas facilement comparables comme pourraient l'être des entiers ou des caractères qui se suivent dans la table ASCII.

Il m'a donc fallu créé une fonction winner qui étant donné deux pointeurs sur des cartes, renvoie le numéro de l'argument ayant la plus grande valeur ou 0 s'ils ont la même grâce à la fonction strchr().

2.2.3 Déroulement du jeu

Pour ce puzzle, j'ai trouvé l'utilisation d'une fonction récursive assez justifiée et assez simple à structurer.

J'ai donc créé la fonction récursive play() que l'on peut décomposer en trois parties:

  • Première partie: Si la file du joueur 1 est vide, alors le joueur 2 a gagné et la fonction retourne 2.
  • Deuxième partie: Si la file du joueur 2 est vide, alors le joueur 1 a gagné et la fonction retourne 1.
  • Troisième partie: Si la file du joueur 1 et du joueur 2 ne sont pas vide, alors on dépile leur file et on étudie le résultat de winner() avec ces deux arguments.
    • Un des joueurs gagne le duel: On empile toutes les cartes des tas dans la file de ce joueur.
    • Les joueurs font égalité: Si les piles des deux joueurs contiennent au moins 4 éléments, on dépile trois cartes de chaque joueur vers leur tas avant de rappeler la récursive, sinon il y a match nul et la fonction retourne 0

graph.png

2.2.4 Code source

  1: #include <stdlib.h>
  2: #include <stdio.h>
  3: #include <string.h>
  4: 
  5: typedef struct element element;
  6: struct element{element * prec; char data[4]; element * suiv;};
  7: 
  8: typedef struct File file;
  9: struct File{element * first; element * last;};
 10: 
 11: 
 12: void file_file(file * F, element * add){
 13:     add->prec = NULL; add->suiv = NULL;
 14:     if(F->first==NULL){
 15:         F->first = add; F->last = add;
 16:     }
 17:     else {
 18:         F->last->suiv = add;
 19:         add->prec = F->last;
 20:         F->last = add;
 21:     }
 22: }
 23: 
 24: void file_append(file * F, char str[4]){
 25:     element * append = malloc(sizeof(element *));
 26:     strcpy(append->data, str);
 27:     file_file(F, append);
 28: }
 29: 
 30: element * file_unfile(file * F){
 31:     element * res = F->first;
 32:     F->first = res->suiv;
 33:     return res;
 34: }
 35: 
 36: int winner(element * play1, element * play2){
 37:     const char * table = "234567891JQKA";
 38:     int p1 = strchr(table,play1->data[0])-table;
 39:     int p2 = strchr(table,play2->data[0])-table;
 40:     return (p1==p2)?0:((p1>p2)?1:2);
 41: }
 42: 
 43: int play(file * handP1, file * handP2, file * pool1, file * pool2, int * round){
 44:     if(handP1->first==NULL){
 45:         return 2;
 46:     }
 47:     else if(handP2->first==NULL){
 48:         return 1;
 49:     }    
 50:     else {
 51:         file_file(pool1, file_unfile(handP1));
 52:         file_file(pool2, file_unfile(handP2));
 53:         int result = winner(pool1->last, pool2->last);
 54:         if(result == 0){
 55:             for(int i = 0; i < 3; i++){
 56:                 if(handP1->first == NULL || handP2->first == NULL){
 57:                     return 0;
 58:                 }
 59:                 file_file(pool1, file_unfile(handP1));
 60:                 file_file(pool2, file_unfile(handP2));
 61:             }
 62:         }
 63:         else{
 64:             file * dest = (result==1)?handP1:handP2;
 65:             while(pool1->first != NULL){
 66:                 file_file(dest, file_unfile(pool1));
 67:             }
 68:             while(pool2->first != NULL){
 69:                 file_file(dest, file_unfile(pool2));
 70:             }
 71:             *round += 1;
 72:         }
 73:         return play(handP1, handP2, pool1, pool2, round);
 74:     }
 75: }
 76: 
 77: int main(){
 78:     file handP1 = {NULL, NULL}; file handP2 = {NULL, NULL};
 79:     file pool1 = {NULL, NULL}; file pool2 = {NULL, NULL};
 80:     int n, round, winner;
 81:     char  read[4];
 82:     scanf("%d", &n);
 83:     for (int i = 0; i < n; i++) {
 84:         scanf("%s", read);
 85:         file_append(&handP1, read);
 86:     }
 87:     scanf("%d", &n);
 88:     for (int i = 0; i < n; i++) {
 89:         scanf("%s", read);
 90:         file_append(&handP2, read);
 91:     }
 92:     round = 0;
 93:     winner = play(&handP1, &handP2, &pool1, &pool2, &round);
 94:     if (winner == 0){
 95:         printf("|GAGNANT |MANCHES |\n|--------+--------|\n| PAT | %d |\n", round);
 96:     }
 97:     else{
 98:         printf("|GAGNANT |MANCHES |\n|--------+--------|\n| %d | %d |\n", winner, round);
 99:     }
100: 
101:     return 0;
102: }

2.3 Résultats de quelques tests

2.3.1 Trois cartes

Entrées

Cartes de départ du joueur 1 AD KC QC
Cartes de départ du joueur 2 KH QS JC

Sorties

GAGNANT MANCHES
1 3

2.3.2 Parties longues avec soumissions successives

Entrées

Cartes de départ du joueur 1 AH 4H 5D 6D QC +21
Cartes de départ du joueur 2 10H 4C 6H 3C KC +21

Sorties

GAGNANT MANCHES
2 1262

2.3.3 Un autre PAT

Entrées

Cartes de départ du joueur 1 4C 4S QS 7D QD +21
Cartes de départ du joueur 2 3H 7C KS 9D 4D +21

Sorties

GAGNANT MANCHES
PAT 56

2.3.4 Réussite des tests

Les tests présentés ici ainsi que les autres tests exécutés par CodinGame, finissent tous par afficher le résultat attendu ce qui fait de ce puzzle une réussite.

2.4 Solution proposée par Spasen

Pour ce puzzle, j'ai choisi de comparer mon code à celui de Spasen puisqu'elle utilise une structure de données semblable à la mienne mais sans récursivité.

2.4.1 Structure de stockage utilisé

Pour stocker ses données, Spasen créé une structure de données contenant un entier ainsi qu'un pointeur sur l'élément suivant.

Aussi, pour pouvoir facilement lire les cartes d'une file ou en rajouter, elle définie une fonction enfiler ainsi qu'une fonction defiler , qui contrairement à la fonction équivalente que j'ai créée, ne renvoie pas le pointeur d'un élément mais la copie de sa valeur. Ainsi, si l'on souhaite transférer un élément d'une pile à l'autre, en réalité on le supprime puis on le recréer ailleurs avec la même valeur. C'est d'ailleurs précisément ce que fait sa fonction renfiler .

2.4.2 Stockage des données

Pour comparer le rang des cartes, Spasen décide de stocker les cartes sous forme d'entiers de 2 à 14 grâce à une fonction cartevalue et l'utilisation d'un switch. L'utilisation d'un switch est intéressante puisque ce dernier permet de mettre en évidence à la lecture du code que la valeur renvoyée par la fonction ne dépend que de la valeur de son argument.

Cependant être simplifiée comme suit:

 1: int cartevalue(char c){
 2:     switch(c){
 3:         case'1':return 10;
 4:         case'J':return 11;
 5:         case'Q':return 12;
 6:         case'K':return 13;
 7:         case'A':return 14;
 8:         default: return c - '0';
 9:     }
10: }

2.4.3 Déroulement du jeu

Globalement, bien que Spasen utilise une boucle while() à la place d'une fonction récursive, le fonctionnement de cette dernière est assez semblable à la mienne.

Cependant, puisque Spasen stock l'entier correspondant à la valeur de chaque carte et non la chaîne de caractère correspondant comme je le fait, elle n'a pas besoin de passer par une fonction pour déterminer le gagnant d'un duel comme à la ligne 148.

2.4.4 Code Source

  1: #include <stdlib.h>
  2: #include <stdio.h>
  3: 
  4: //structure de la file
  5: typedef struct ElementF{
  6:     int val;
  7:     struct ElementF* suivant;
  8: }ElementF;
  9: 
 10: typedef ElementF* file;
 11: 
 12:  //======================================================//
 13: 
 14: //fonction qui ajoute un élement en fin de file
 15: void enfiler(file* f, int val){
 16:     file new = malloc(sizeof(file));
 17:     file tmp = *f;
 18:     new->val = val;
 19:     if(*f == NULL){
 20:         (*f) = new;
 21:     }else{
 22:         while(tmp->suivant != NULL) tmp = tmp->suivant;
 23: 
 24:         tmp->suivant=new;
 25:     }
 26: }
 27: 
 28: //fonction pour enliver le 1er élément de la file
 29: int defiler(file* f){
 30:     int elt;
 31:     if(*f != NULL){
 32:         elt = (*f)->val;
 33:         file tmp = (*f)->suivant;
 34:         free(*f);
 35:         *f = tmp;
 36:     }else{
 37:         elt = 0;
 38:     }
 39:     return elt;
 40: }
 41: 
 42: 
 43: //mets 3 carte dans le packet de jeu et renvoie la 4ème
 44: int defausse(file* f, file* p){
 45:     for(int i=0; i<3; i++){
 46: 
 47:         //on retire une carte
 48:         int elt=defiler(f);
 49: 
 50:         //si le packet est non vide (ie elt != 0)
 51:         if(elt>0){
 52:             //on mets la carte dans le packet en jeu
 53:             enfiler(p, elt);}
 54: 
 55:         else{
 56:             //sinon on retourn elt = 0 (il y aura PAT)
 57:             return elt;}
 58: 
 59:     }
 60:     //on retire la 4ème carte qu'on mets dans la file en jeu et on la retourne
 61:     int elt = defiler(f);
 62:     enfiler(p,elt);
 63:     return elt;
 64: }
 65: 
 66: //transfert une file dans une autre
 67: void renfiler(file* f, file* p){
 68:     while(*p != NULL){
 69:         enfiler(f, defiler(p));
 70:     }
 71: }
 72: 
 73: //compte le nombre d'éléments de la file
 74: int count(file f){
 75:     file tmp = f;
 76:     int count=0;
 77:     while(tmp != NULL) count++, tmp=tmp->suivant;
 78: 
 79:     return count;
 80: }
 81: 
 82: //donne la valeur de la carte en jeu (entre 2 et 14(pour l'as))
 83: int cartevalue(char c){
 84:     switch(c){
 85:         case'2':return 2;
 86:         case'3':return 3;
 87:         case'4':return 4;
 88:         case'5':return 5;
 89:         case'6':return 6;
 90:         case'7':return 7;
 91:         case'8':return 8;
 92:         case'9':return 9;
 93:         case'1':return 10;
 94:         case'J':return 11;
 95:         case'Q':return 12;
 96:         case'K':return 13;
 97:         case'A':return 14;
 98:         default: printf(" ERROR\n"); return 0;
 99:     }
100: }
101: 
102: //=======================================================================//
103: 
104: 
105: int main()
106: {
107:     //packet de carte joueur 1, 2 packet de carte en jeu du joueur 1,2
108:     file J1=NULL, J2=NULL, P1=NULL, P2=NULL;
109: 
110:     // the number of cards for player 1
111:     int n;
112:     scanf("%d", &n);
113: 
114:     // the n cards of player 1
115:     for (int i = 0; i < n; i++) {
116:         char cardp_1[4];
117:         scanf("%s", cardp_1);
118:         //on enfile la carte dans le packet du J1
119:         enfiler(&J1, cartevalue(cardp_1[0]));
120:     }
121: 
122:     // the number of cards for player 2
123:     int m;
124:     scanf("%d", &m);
125: 
126:     // the m cards of player 2
127:     for (int i = 0; i < m; i++) {
128:         char cardp_2[4];
129:         scanf("%s", cardp_2);
130:         //on enfile la carte dans le packet du J2
131:         enfiler(&J2, cartevalue(cardp_2[0]));
132:     }
133: 
134: 
135:     //check if 1 des 2 jeu est vides
136:     if(!m || !n){
137:         (m)?printf("1 0\n"):printf("2 0\n");
138:     }else{
139: 
140:         //variable qui boucle le jeu
141:         int jeu=0;
142:         //variable qui compte le nombre de tour
143:         int tour=0;
144:         //variable pour vérifié si il y a PAT
145:         int pat =0;
146: 
147:         //boucle de jeu:
148:         while(!jeu){
149:             //on pioche 2 cartes et on les mets dans la pile de jeu
150:             int carte1 = defiler(&J1), carte2 = defiler(&J2);
151:             enfiler(&P1, carte1); enfiler(&P2, carte2);
152: 
153:             //cas de bataille
154:             if(carte1 == carte2){
155: 
156:                 //tant qu'il y a bataille
157:                 while(carte1 == carte2){
158: 
159:                         carte1 = defausse(&J1, &P1);
160:                         carte2 = defausse(&J2, &P2);
161: 
162:                         //si 1 des 2 joueurs n'a plus de carte : PAT
163:                         if(!carte1 || !carte2){pat=1; break;}
164: 
165:                         //si cartes!= ie il n'y a plus bataille
166:                         else if(carte1 != carte2){
167:                             //on ajoute les cartes en jeu a la fin du packet du gagnant (carte du joueur1 en premier)
168:                             if(carte1<carte2){renfiler(&J2, &P1); renfiler(&J2, &P2);}
169:                             else{renfiler(&J1, &P1); renfiler(&J1, &P2);}
170:                         }
171:                 }
172: 
173:             //cas manche normal
174:             }else{
175:                 //on ajoute les cartes en jeu a la fin du packet du gagnant (carte du joueur1 en premier)
176:                 if(carte1<carte2){renfiler(&J2, &P1); renfiler(&J2, &P2);}
177:                 else{renfiler(&J1, &P1); renfiler(&J1, &P2);}
178:             }
179: 
180:             //si une bataille ne peux pas se finir (manque de carte) : pat=1
181:             if(pat) break;
182: 
183:             //si un des packets n'a plus de carte
184:             (!count(J1))?jeu=2:0;
185:             (!count(J2))?jeu=1:0;
186: 
187:             //compte le nombre de tour
188:             tour++;
189:         }
190: 
191:         if(pat) printf("PAT\n");
192:         else    printf("%d %d\n", jeu, tour);
193: 
194:     }
195: 
196:     return 0;
197: }

Author: Yannis ROCHE

Created: 2021-05-08 sam. 22:24

Validate