Un 2048 en console

Un 2048 en console

Voir version :

Pas de dépendances

Télécharger :

#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;
}




Commentaires

	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...