Pas de dépendances
#ifdef WIN32 #include <windows.h> #include <conio.h> #endif #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #define WIDTH 4 void LocateColor(int x,int y,int color) { #ifdef WIN32 COORD C; HANDLE H=GetStdHandle(STD_OUTPUT_HANDLE); C.X=(SHORT)x; C.Y=(SHORT)y; SetConsoleTextAttribute(H,(WORD)color); SetConsoleCursorPosition(H,C); #endif } typedef struct Jeu { int Tab[WIDTH*WIDTH]; } Jeu; int Index(int d,int x,int y) { switch (d) { case 0: // droite return y*WIDTH+x; case 1: // gauche return y*WIDTH+(WIDTH-x-1); case 2: // bas return x*WIDTH+y; case 3: // haut return (WIDTH-x-1)*WIDTH+y; default: break; } return 0; } int Log2(int i) { int res = 0; while(!((i>>res)&1)) res++; return res; } void Show(Jeu* J) { int i,j; for(j=0;j<WIDTH;j++) { for(i=0;i<WIDTH;i++) { int val = J->Tab[Index(0,i,j)]; LocateColor(6*i,j,15); // blanc pour les | if (val==0) printf(" |"); else { LocateColor(6*i,j,(Log2(val)+1)|8); // FOREGROUNDINTENSITY printf("%4d |",val); } } printf("\n"); } printf("\n"); } void NewCase(Jeu* J) { int choix = 0; do { choix = rand()%(WIDTH*WIDTH); } while (J->Tab[choix]!=0); J->Tab[choix] = 1; } int Pousse_Fusion(Jeu* J,int dir,int y,int fusion,int verif) { int i; for(i=1;i<WIDTH;i++) { int crt = J->Tab[Index(dir,i,y)]; int pred = J->Tab[Index(dir,i-1,y)]; if ((fusion==0 && pred==0 && crt>0) || (fusion==1 && pred==crt && crt!=0)) { if (verif) return 1; J->Tab[Index(dir,i-1,y)] = crt+pred; J->Tab[Index(dir,i,y)] = 0; if (fusion==0) i=0; // recommence. } } return 0; } int Move(Jeu* J,int dir) { int i; for(i=0;i<WIDTH;i++) { Pousse_Fusion(J,dir,i,0,0); Pousse_Fusion(J,dir,i,1,0); Pousse_Fusion(J,dir,i,0,0); } return 0; } int Possible(Jeu* J,int dir) { int i; for(i=0;i<WIDTH;i++) if (Pousse_Fusion(J,dir,i,0,1) || Pousse_Fusion(J,dir,i,1,1)) return 1; return 0; } int GetDir() { char* dir = "fhtg"; // keyboard int key,res,i; do { #ifdef WIN32 key = getch(); #else key = getchar(); #endif res = -1; for(i=0;i<4;i++) if (key == dir[i]) res = i; } while(res==-1); return res; } int main() { Jeu J; srand((unsigned int)time(NULL)); memset(&J,0,sizeof(Jeu)); NewCase(&J); NewCase(&J); while(Possible(&J,0) || Possible(&J,1) || Possible(&J,2) || Possible(&J,3)) { int dir; Show(&J); do { dir = GetDir(); } while (!Possible(&J,dir)); Move(&J,dir); NewCase(&J); } return 0; }
Voici un petit 2048 en console. Utilisez les lettres FGHT pour jouer. Quelques explications : Regardez le main : je crée une structure jeu (qui est un tableau de 16 cases), que je mets à 0 avec le memset. Je crée 2 valeurs aléatoires avec la fonction NewCase qui trouve une case libre pour y mettre un '1'. Le while du main continue tant qu'il est possible de faire un mouvement dans une des 4 directions. Dans le while, j'affiche, je récupère une direction (si il est possible qu'elle fasse bouger le jeu), je bouge le jeu selon la direction, puis je crée une nouvelle case (un nouveau 1). Bon, ça, c'est le main. Maintenant, voyons tout de suite la fonction Move. La fonction Move prend une direction (haut,bas,gauche,droite), et essaie de bouger les 4 lignes (ou colonnes) dans le bon sens. Pour cela, je vais faire abstraction totale des lignes et colonnes grace à la fonction Index. Ce que je veux, c'est donner une direction, une colonne/ligne, et qu'ensuite, tout se passe comme si j'avais 4 cases numérotées 0,1,2,3 et je veux pousser vers la gauche (vers le 0). Je ne vais considérer qu'un seul cas. La fonction Pousse_Fusion appelée dans move, est en fait un condensé de deux fonctions qui se ressemblaient beaucoup : Pousse et Fusion. Donc d'abord, je pousse (je mets le parametre fusion à 0). Pousser, c'est à dire que si j'ai par exemple 0201, je veux 2100 (n'oubliez pas, je pousse vers la gauche). Donc j'ai dans Pousse_Fusion : for(i=1;i<WIDTH;i++) { int crt = J->Tab[Index(dir,i,y)]; int pred = J->Tab[Index(dir,i-1,y)]; Index(dir,i,y) et Index(dir,i-1,y) vont me donner les deux premieres cases si i=0, le 2ere et la 3eme pour i=1, etc... Si je vois, pour fusion=0 (donc pousse) que j'ai 0x, alors la case de gauche (pred) devient la somme des deux donc 0+x = x, et la case de droite tombe à 0. Donc 0x -> x0 Dans le cas d'une pousse, je relance l'algo tant que j'ai réussi à faire cela. A la fin, j'ai bien mon 2100 En mode fusion, seul le if change, ainsi si j'ai deux valeurs non nulles collées, je n'en fait qu'une : 22 -> 40 Cependant, cette fois, je ne relance pas l'algo (le if qui met i à 0), car on m'a dit que les règles du 2048 sont les suivantes : Si j'ai : 2110 Je dois avoir, à la fin 2200 et non 4000 (ce que j'aurais si je relançais l'algo) Voila l'idée. le paramètre verif sert à valider ou non le déplacement : si je mets verif à 1, alors je renvoie 1 si je peux déplacer, mais sans déplacer. si je mets verif à 0, alors je modifie le jeu si besoin. La fonction Possible se base dessus. ********************************************** Retour sur la fonction Index : La fonction Index me permet de faire abstraction des lignes/colonnes et des directions : ainsi je ne considère plus qu'un seul cas. Illustrons : Voici mon tableau : 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 soit ma notation : Index(d,{0,1,2,3},L) = {A,B,C,D} correspond à Index(0,0,0) = A, puis Index(0,1,0) = B, puis Index(0,2,0) = C, puis Index(0,3,0) = D, 4 valeurs Donc Index(0,{0,1,2,3},0) = {0,1,2,3} // direction 0 (droite) ligne (dernier parametre) 0, j'ai bien {0,1,2,3} Index(0,{0,1,2,3},2) = {8,9,10,11} // direction 0 (droite), ligne 2, j'ai bien {8,9,10,11} Index(1,{0,1,2,3},0) = {3,2,1,0} // direction 1 (gauche); ligne 0, j'ai bien {3,2,1,0}, donc dans l'autre sens ! Index(2,{0,1,2,3},0) = {0,4,8,12} // direction bas, colonne 0 Index(3,{0,1,2,3},0) = {12,8,4,0} // direction haut, colonne 0 Index(2,{0,1,2,3},2) = {2,6,10,14} // direction bas, colonne 2 etc...