0x2048




这是我在C语言中经典游戏“ 2048”的实现。
构建/运行项目的说明(GitHub可以在这里找到。

我从核心游戏开始,然后用它构建了一个GUI界面。
我希望对我的设计提供一些反馈。如何更好地编写相同的实现。习语,约定或您想到的任何内容。

在头文件中有一些有关功能的信息。希望能提高可读性和理解性。

我知道的改进:


我每次为文本2、4、8创建纹理。画瓷砖。这是在浪费资源,因为我只需创建一次16个磁贴就不必再担心了。
由于我的IDE不会自动执行此操作,因此Java和C样式之间可能存在一些括号不一致的问题。

我不知道有哪些改进:


我是否在任何地方泄漏内存?
样式/会议/性能/内存以及其他所有内容。
技术使我的代码更加通用/通用。如答案之一所示,使用矩阵结构将有助于创建矩形板。

编辑:回购协议会不断更新。请在发布此问题时使用上面的链接查看回购的版本。我也不介意对最新版本发表评论。


game.h

/**
 * @file game.h
 * @author Gnik Droy
 * @brief File containing function declarations for the game gui.
 *
 */
#pragma once
#include "../include/core.h"
#include "SDL2/SDL.h"
#include "SDL2/SDL_ttf.h"

#define SCREEN_WIDTH 500
#define SCREEN_HEIGHT 600

/**
 * @brief Initializes the SDL window.
 *
 * When two pointers to the pointer of gWindow and gRenderer are provided,
 * the function initializes both values with the values of created window
 * and renderer. 
 * 
 * If initialization is failed it may display error to stderr but
 * does not exit.
 * 
 * @param gWindow The window of the game.
 * @param gRenderer The renderer for the game
 * @return If the initialization was successful.
 */
bool initSDL(SDL_Window **gWindow,SDL_Renderer** gRenderer);

/**
 * @brief Closes the SDL window.
 *
 * Frees up resource by closing destroying the SDL window 
 * 
 * @param gWindow The window of the game.
 */
void SDLclose(SDL_Window* gWindow);

/**
 * @brief Draws text centered inside a rect. 
 *
 * When two pointers to the pointer of gWindow and gRenderer are provided,
 * the function initializes both values with the values of created window
 * and renderer. 
 * 
 * If initialization is failed it may display error to stderr but
 * does not exit.
 * 
 * @param gRenderer The renderer for the game
 * @param font The font for the text
 * @param text The text to write
 * @param rect The SDL_Rect object inside which text is written
 * @param color The color of the text
 */
void draw_text(SDL_Renderer* gRenderer,TTF_Font* font,const char* text, SDL_Rect rect, SDL_Color color);

/**
 * @brief Draws white text centered inside a rect. 
 *
 * Same as draw_text(..., SDL_Color White)
 * 
 * @param gRenderer The renderer for the game
 * @param font The font for the text
 * @param text The text to write
 * @param rect The SDL_Rect object inside which text is written
 */
void draw_text_white(SDL_Renderer* gRenderer,TTF_Font* font,const char* text, SDL_Rect rect);


/**
 * @brief Clears the window
 *
 * Fills a color to entire screen.
 * 
 * @param gRenderer The renderer for the game
 */
void SDLclear(SDL_Renderer* gRenderer);


/**
 * @brief Draws black text centered inside the window. 
 *
 * @param gRenderer The renderer for the game
 * @param size The size for the text
 * @param text The text to write
 */
void display_text(SDL_Renderer* gRenderer,const char* text,int size);


/**
 * @brief Draws the game tiles. 
 *
 * It draws the SIZE*SIZE game tiles to the window.
 * 
 * @param gRenderer The renderer for the game
 * @param font The font for the tiles
 * @param matrix The game matrix.
 */
void draw_matrix(SDL_Renderer* gRenderer,unsigned char matrix[][SIZE], TTF_Font* font);

/**
 * @brief Draws the new game button. 
 *
 * It draws the new game button to the bottom corner.
 * 
 * @param gRenderer The renderer for the game
 * @param font The font for the button
 * @param matrix The game matrix. Needed to reset game.
 */
void draw_button(SDL_Renderer* gRenderer,unsigned char matrix[][SIZE], TTF_Font* font);

/**
 * @brief Handles the action of New Game button. 
 *
 * Resets the game board for a new game, if the correct mouse event 
 * had occured.
 * Function is run if left mouse button is released 
 * 
 * @param gRenderer The renderer for the game
 * @param e The mouse event
 * @param matrix The game matrix.
 */
void button_action(SDL_Event e,unsigned char matrix[][SIZE]);

/**
 * @brief Draws the current game score
 *
 * It draws the current game score to the window
 * 
 * @param gRenderer The renderer for the game
 * @param font The font for the tiles
 * @param matrix The game matrix.
 */
void draw_score(SDL_Renderer* gRenderer,unsigned char matrix[][SIZE], TTF_Font* font);

/**
 * @brief Draws everything for the game and renders it to screen. 
 *
 * It calls SDLclear(),draw_matrix(),draw_score() and draw_button()
 * and also renders it to screem.
 * 
 * @param gRenderer The renderer for the game
 * @param font The font for the tiles
 * @param matrix The game matrix.
 */
void render_game(SDL_Renderer* gRenderer,unsigned char matrix[][SIZE], TTF_Font* font);

/**
 * @brief This is the main game loop that handles all events and drawing 
 * 
 * @param gRenderer The renderer for the game
 * @param font The font for the tiles
 * @param matrix The game matrix.
 */
void gameLoop(unsigned char matrix[][SIZE],SDL_Renderer* gRenderer);

/**
 * @brief Handles keyboard presses that correspond with the arrowkeys. 
 * 
 * It transforms the game matrix according to the keypresses.
 * It also checks if the game has been finished, draws game over screen 
 * and resets the board if game over. 
 * 
 * @param gRenderer The renderer for the game
 * @param font The font for the tiles
 * @param matrix The game matrix.
 */
void handle_move(SDL_Event e,unsigned char matrix[][SIZE], SDL_Renderer * gRenderer);


styles.h

/**
 * @file styles.h
 * @author Gnik Droy
 * @brief File containing tile colors and related structs.
 *
 */
#pragma once
/** @struct COLOR
 *  @brief This structure defines a RBGA color
 *  All values are stored in chars.
 * 
 *  @var COLOR::r
 *  The red value 
 *  @var COLOR::g
 *  The green value 
 *  @var COLOR::b 
 *  The blue value
 *  @var COLOR::a 
 *  The alpha value
 * 
 */

//Screen dimension constants
#define SCREEN_WIDTH 500
#define SCREEN_HEIGHT 600
#define SCREEN_PAD 10

//FONT settings
#define FONT_PATH "UbuntuMono-R.ttf"
#define TITLE_FONT_SIZE 200
#define GOVER_FONT_SIZE 100  //Game Over font size
#define CELL_FONT_SIZE 40

struct COLOR{
    char r;
    char g;
    char b;
    char a;
};
struct COLOR g_bg={211, 204, 201, 255};
struct COLOR g_fg={80, 80, 80, 255};
struct COLOR g_button_bg={255, 153, 102,255};
struct COLOR g_score_bg={143, 122, 102,255};

struct COLOR g_COLORS[]={
    {230, 227, 232,255},
    {255, 127, 89,255},
    {224, 74, 69,255},
    {237, 207, 114,255},
    {65, 216, 127,255},
    {54, 63, 135,255},
    {78, 89, 178,255},
    {109, 118, 191,255},
    {84, 47, 132,255},
    {125, 77, 188,255},
    {163, 77, 188,255},
    {176, 109, 196,255},
    {0, 102, 204,255},
    {0, 153, 255,255},
    {51, 153, 255,255},
    {153, 204, 255,255},
    {102, 255, 102,255}
};


core.h

/**
 * @file core.h
 * @author Gnik Droy
 * @brief File containing function declarations for the core game.
 *
 */
#pragma once
#include <stdio.h>

#define SIZE 4
#define BASE 2

typedef char bool;

/**
 * @brief Write the game matrix to the stream.
 *
 * The matrix is written as a comma seperated list of indices.
 * Each row is seperated by a '\n' character.
 * Each empty cell is represented by '-' character.
 * 
 * The indices can be used to calculate the actual integers.
 * 
 * You can use the constant stdout from <stdio.h> for printing to 
 * standard output
 * 
 * @param matrix The game matrix that is to be printed.
 * @param stream The file stream to use.
 */
void print_matrix(unsigned char matrix[][SIZE],FILE* stream);


/**
 * @brief Checks if there are possible moves left on the game board.
 *
 * Checks for both movement and combinations of tiles.
 * 
 * @param matrix The game matrix.
 * @return Either 0 or 1
 */
bool is_game_over(unsigned char matrix[][SIZE]);

/**
 * @brief This clears out the game matrix
 *
 * This zeros out the entire game matrix.
 * 
 * @param matrix The game matrix.
 */
void clear_matrix(unsigned char matrix[][SIZE]);

/**
 * @brief Adds a value of 1 to random place to the matrix.
 *
 * The function adds 1 to a random place in the matrix.
 * The 1 is placed in empty tiles. i.e tiles containing 0.
 * 1 is kept since you can use raise it with BASE to get required value.
 * Also it keeps the size of matrix to a low value.
 * 
 * NOTE: It has no checks if there are any empty places for keeping 
 * the random value.
 * If no empty place is found a floating point exception will occur.
 */
void add_random(unsigned char matrix[][SIZE]);

/**
 * @brief Calculates the score of a game matrix
 *
 * It score the matrix in a simple way.
 * Each element in the matrix is used as exponents of the BASE. And the 
 * sum of all BASE^element is returned.
 * 
 * @return An integer that represents the current score
 */
int calculate_score(unsigned char matrix[][SIZE]);





/**
 * @brief Shifts the game matrix in X direction.
 *
 * It shifts all the elements of the game matrix in the X direction.
 * If the direction is given as 0, it shifts the game matrix in the left
 * direction. Any other non zero value shifts it to the right direction.
 * 
 * @param matrix The game matrix.
 * @param opp The direction of the shift.
 * 
 * @return If the shift was successful
 */
bool shift_x(unsigned char matrix[][SIZE], bool opp);


/**
 * @brief Merges the elements in X direction.
 *
 * It merges consecutive successive elements of the game matrix in the X direction.
 * If the direction is given as 0, it merges the game matrix to the left
 * direction. Any other non zero value merges it to the right direction.
 * 
 * @param matrix The game matrix.
 * @param opp The direction of the shift.
 * 
 * @return If the merge was successful
 */
bool merge_x(unsigned char matrix[][SIZE],bool opp);


/**
 * @brief Moves the elements in X direction.
 *
 * It simply performs shift_x() and merge_x().
 * If either of them were successful, it also calls add_random()
 * 
 * @param matrix The game matrix.
 * @param opp The direction of the move.
 * 
 */
void move_x(unsigned char matrix[][SIZE], bool opp);



/**
 * @brief Shifts the game matrix in Y direction.
 *
 * It shifts all the elements of the game matrix in the Y direction.
 * If the direction is given as 0, it shifts the game matrix in the top
 * direction. Any other non-zero value shifts it to the bottom.
 * 
 * @param matrix The game matrix.
 * @param opp The direction of the shift.
 * 
 * @return If the shift was successful
 */
bool shift_y(unsigned char matrix[][SIZE], bool opp);


/**
 * @brief Merges the elements in Y direction.
 *
 * It merges consecutive successive elements of the game matrix in the Y direction.
 * If the direction is given as 0, it merges the game matrix to the top
 * direction. Any other non zero value merges it to the bottom.
 * 
 * @param matrix The game matrix.
 * @param opp The direction of the shift.
 * 
 * @return If the merge was successful
 */
bool merge_y(unsigned char matrix[][SIZE],bool opp);


/**
 * @brief Moves the elements in Y direction.
 *
 * It simply performs shift_y() and merge_y().
 * If either of them were successful, it also calls add_random()
 * 
 * @param matrix The game matrix.
 * @param opp The direction of the move.
 * 
 */
void move_y(unsigned char matrix[][SIZE],bool opp);


core.c

#include <stdlib.h>
#include <time.h>
#include <math.h>
#include "../include/core.h"


void clear_matrix(unsigned char matrix[][SIZE])
{
    for (unsigned int x=0;x<SIZE;x++)
    {
        for(unsigned int y=0;y<SIZE;y++)
        {
            matrix[x][y]=0;
        }
    }
}

int calculate_score(unsigned char matrix[][SIZE])
{
    int score=0;
    for (unsigned int x=0;x<SIZE;x++)
    {
        for(unsigned int y=0;y<SIZE;y++)
        {
            if(matrix[x][y]!=0)
            {
                score+=pow(BASE,matrix[x][y]);
            }
        }
    }
    return score;
}

void print_matrix(unsigned char matrix[][SIZE],FILE* stream)
{
    for (unsigned int x=0;x<SIZE;x++)
    {
        for(unsigned int y=0;y<SIZE;y++)
        {
            if (matrix[x][y])
            {
                fprintf(stream,"%d," ,matrix[x][y]);
            } else{
                fprintf(stream,"-,");
            }
        }
        fprintf(stream,"\n");
    }
    fprintf(stream,"\n");
}

void add_random(unsigned char matrix[][SIZE])
{
    unsigned int pos[SIZE*SIZE];
    unsigned int len=0;
    for(unsigned int x=0;x<SIZE;x++)
    {
        for (unsigned int y=0;y<SIZE;y++)
        {
            if (matrix[x][y]==0){
               pos[len]=x*SIZE+y;
               len++; 
            }
        }
    }
    unsigned int index=rand() % len;
    matrix[pos[index]/SIZE][pos[index]%SIZE] = 1;
}

bool is_game_over(unsigned char matrix[][SIZE])
{
    for(unsigned int x=0;x<SIZE-1;x++)
    {
        for (unsigned int y=0;y<SIZE-1;y++)
        {
            if (  matrix[x][y]==matrix[x][y+1] ||
                  matrix[x][y]==matrix[x+1][y] ||
                  matrix[x][y]==0)
            {return 0;}
        }
        if( matrix[x][SIZE-1]==matrix[x+1][SIZE-1] || 
            matrix[x][SIZE-1]==0) return 0;
        if( matrix[SIZE-1][x]==matrix[SIZE-1][x+1] ||
            matrix[SIZE-1][x]==0) return 0;
    }
    return 1;
}

bool shift_x(unsigned char matrix[][SIZE], bool opp)
{
    bool moved=0;
    int start=0,end=SIZE,increment=1;
    if (opp)
    {
        start=SIZE-1;
        end=-1;
        increment=-1;
    }
    for (int x=0;x<SIZE;x++)
    {
        int index=start;
        for(int y=start;y!=end;y+=increment)
        {
            if (matrix[x][y]!=0)
            {
                matrix[x][index]=matrix[x][y];
                if(index!=y) {
                    matrix[x][y]=0;
                    moved=1;
                }
                index+=increment;
            }
        }
    }
    return moved;
}
bool merge_x(unsigned char matrix[][SIZE],bool opp)
{
    bool merged=0;
    int start=0,end=SIZE-1,increment=1;
    if (opp)
    {
        start=SIZE-1;
        end=0;
        increment=-1;
    }
    for (int x=0;x<SIZE;x++)
    {
        int index=start;
        for(int y=start;y!=end;y+=increment)
        {
            if(matrix[x][y]!=0)
            {
                if(matrix[x][y]==matrix[x][y+increment])
                {
                    matrix[x][index]=matrix[x][y]+1;
                    matrix[x][y+increment]=0;
                    if(index!=y) matrix[x][y]=0;
                    merged=1;
                    index+=increment;
                }
                else
                {
                    matrix[x][index]=matrix[x][y];
                    if(index!=y) matrix[x][y]=0;
                    index+=increment;
                }
            }
        }

        if(matrix[x][end]!=0)
        {
            matrix[x][index]=matrix[x][end];
            if(index!=end) matrix[x][end]=0;
        }
    }
    return merged;
}
bool merge_y(unsigned char matrix[][SIZE],bool opp)
{
    bool merged=0;
    int start=0,end=SIZE-1,increment=1;
    if (opp)
    {
        start=SIZE-1;
        end=0;
        increment=-1;
    }
    for (int y=0;y<SIZE;y++)
    {
        int index=start;
        for(int x=start;x!=end;x+=increment)
        {
            if(matrix[x][y]!=0)
            {
                if(matrix[x][y]==matrix[x+increment][y])
                {
                    matrix[index][y]=matrix[x][y]+1;
                    matrix[x+increment][y]=0;
                    if(index!=x) matrix[x][y]=0;
                    index+=increment;
                    merged=1;
                }
                else
                {
                    matrix[index][y]=matrix[x][y];
                    if(index!=x) matrix[x][y]=0;
                    index+=increment;
                }
            }
        }
        if(matrix[end][y]!=0)
        {
            matrix[index][y]=matrix[end][y];
            if(index!=end) matrix[end][y]=0;
        }

    }
    return merged;
}
bool shift_y(unsigned char matrix[][SIZE],bool opp)
{
    bool moved=0;
    int start=0,end=SIZE,increment=1;
    if (opp)
    {
        start=SIZE-1;
        end=-1;
        increment=-1;
    }
    for (int y=0;y<SIZE;y++)
    {
        int index=start;
        for(int x=start;x!=end;x+=increment)
        {
            if (matrix[x][y]!=0)
            {
                matrix[index][y]=matrix[x][y];
                if(index!=x)
                { 
                    matrix[x][y]=0;
                    moved=1;
                }
                index+=increment;
            }
        }
    }
    return moved;
}


inline void move_y(unsigned char matrix[][SIZE],bool opp)
{
    //Assigning values insted of evaluating directly to force both operations
    //Bypassing lazy 'OR' evaluation
    bool a=shift_y(matrix,opp),b=merge_y(matrix,opp);
    if( a||b) add_random(matrix);
}

inline void move_x(unsigned char matrix[][SIZE],bool opp)
{
    //Assigning values insted of evaluating directly to force both operations
    //Bypassing lazy 'OR' evaluation
    bool a=shift_x(matrix,opp), b=merge_x(matrix,opp);
    if(a||b)add_random(matrix);
}


game.c

#include "../include/styles.h"
#include "../include/game.h"
#include <time.h>
#include <stdlib.h>

bool initSDL(SDL_Window **gWindow,SDL_Renderer** gRenderer)
{
    bool success = 1;
    TTF_Init();
    if( SDL_Init( SDL_INIT_VIDEO ) < 0 )
    {
        perror( "SDL could not initialize!" );
        success = 0;
    }
    else
    {
        *gWindow = SDL_CreateWindow( "2048", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
        if( gWindow == NULL )
        {
            perror( "Window could not be created!" );
            success = 0;
        }
        else
        {
            *gRenderer = SDL_CreateRenderer( *gWindow, -1, SDL_RENDERER_ACCELERATED );
            if( gRenderer == NULL )
            {
                perror( "Renderer could not be created!" );
                success = 0;
            }
            else
            {
                SDL_SetRenderDrawColor( *gRenderer, g_bg.r,g_bg.g,g_bg.b,g_bg.a );

            }
        }
    }

    return success;
}

void draw_text(SDL_Renderer* gRenderer,TTF_Font* font,const char* text, SDL_Rect rect, SDL_Color color){
    SDL_Surface* surfaceMessage = TTF_RenderText_Blended(font, text, color); 
    SDL_Texture* Message = SDL_CreateTextureFromSurface(gRenderer, surfaceMessage);
    SDL_Rect message_rect;

    TTF_SizeText(font, text, &message_rect.w, &message_rect.h);
    message_rect.x = rect.x+rect.w/2-message_rect.w/2;     
    message_rect.y = rect.y+rect.h/2-message_rect.h/2; 

    SDL_RenderCopy(gRenderer, Message, NULL, &message_rect); 
    SDL_DestroyTexture(Message);
    SDL_FreeSurface(surfaceMessage);
}

void draw_text_white(SDL_Renderer* gRenderer,TTF_Font* font,const char* text, SDL_Rect rect)
{
    SDL_Color White = {255, 255, 255};
    draw_text(gRenderer,font,text,rect,White);
}


void SDLclose(SDL_Window* gWindow)
{
    SDL_DestroyWindow( gWindow );
    gWindow = NULL;
    TTF_Quit();
    SDL_Quit();
}

void SDLclear(SDL_Renderer* gRenderer)
{

    SDL_SetRenderDrawColor( gRenderer, g_bg.r,g_bg.g,g_bg.b,g_bg.a );
    SDL_RenderClear( gRenderer );

}

void display_text(SDL_Renderer* gRenderer,const char* text,int size)
{
    TTF_Font* font =NULL;
    font= TTF_OpenFont(FONT_PATH, size);
    if(font==NULL){
        perror("The required font was not found");
        exit(1);
    }
    SDL_Color black = {g_fg.r,g_fg.g, g_fg.b};
    SDLclear(gRenderer);
    SDL_Rect rect = {SCREEN_PAD ,SCREEN_HEIGHT/4 , SCREEN_WIDTH-2*SCREEN_PAD , SCREEN_HEIGHT/2 };
    draw_text(gRenderer,font,text,rect,black);
    SDL_RenderPresent( gRenderer );
    SDL_Delay(1000);
    TTF_CloseFont(font);
    font=NULL;
}
void draw_matrix(SDL_Renderer* gRenderer,unsigned char matrix[][SIZE], TTF_Font* font)
{
    int squareSize=(SCREEN_WIDTH - 2*SCREEN_PAD)/SIZE-SCREEN_PAD;

    for(int x=0;x<SIZE;x++)
    {
        for(int y=0;y<SIZE;y++)
        {
            SDL_Rect fillRect = { SCREEN_PAD+x*(squareSize+SCREEN_PAD), SCREEN_PAD+y*(squareSize+SCREEN_PAD), squareSize , squareSize };
            struct COLOR s=g_COLORS[matrix[y][x]];
            SDL_SetRenderDrawColor( gRenderer, s.r, s.g, s.b, s.a );        
            SDL_RenderFillRect( gRenderer, &fillRect );
            char str[15]; // 15 chars is enough for 2^16
            sprintf(str, "%d", (int)pow(BASE,matrix[y][x]));

            if(matrix[y][x]==0){
                 str[0]=' ';
                 str[1]='q4312078q';
             }
            draw_text_white(gRenderer,font,str,fillRect);
        }
    }
}



void handle_move(SDL_Event e,unsigned char matrix[][SIZE], SDL_Renderer * gRenderer)
{
    if(is_game_over(matrix))
    {
        display_text(gRenderer,"Game Over",GOVER_FONT_SIZE);
        clear_matrix(matrix);
        add_random(matrix);
        return;
    }
    switch(e.key.keysym.sym)
    {
        case SDLK_UP: 
        move_y(matrix,0);
        break;
        case SDLK_DOWN: 
        move_y(matrix,1);
        break;
        case SDLK_LEFT: 
        move_x(matrix,0);
        break;
        case SDLK_RIGHT: 
        move_x(matrix,1);
        break;
        default:;
    }
}

void draw_button(SDL_Renderer* gRenderer,unsigned char matrix[][SIZE], TTF_Font* font)
{
    char txt[]="New Game";
    SDL_Rect fillRect = { SCREEN_PAD/2 ,
                            SCREEN_WIDTH+SCREEN_PAD ,
                            SCREEN_WIDTH/2-2*SCREEN_PAD ,
                            (SCREEN_HEIGHT-SCREEN_WIDTH)-2*SCREEN_PAD };
    SDL_SetRenderDrawColor( gRenderer,g_button_bg.r, g_button_bg.g, g_button_bg.b,g_button_bg.a );      
    SDL_RenderFillRect( gRenderer, &fillRect );
    draw_text_white(gRenderer,font,txt,fillRect);

}
void button_action(SDL_Event e,unsigned char matrix[][SIZE])
{
    SDL_Rect draw_rect = { SCREEN_PAD/2 ,
                            SCREEN_WIDTH+SCREEN_PAD ,
                            SCREEN_WIDTH/2-2*SCREEN_PAD ,
                            SCREEN_HEIGHT-SCREEN_WIDTH-2*SCREEN_PAD };
    if(e.button.button == SDL_BUTTON_LEFT &&
                e.button.x >= draw_rect.x &&
                e.button.x <= (draw_rect.x + draw_rect.w) &&
                e.button.y >= draw_rect.y &&
                e.button.y <= (draw_rect.y + draw_rect.h))
    {
        clear_matrix(matrix);
        add_random(matrix);
    }
}
void draw_score(SDL_Renderer* gRenderer,unsigned char matrix[][SIZE], TTF_Font* font)
{
    char score[15]; //15 chars is enough for score.
    sprintf(score, "%d", calculate_score(matrix));
    char scoreText[30]="Score:";
    strncat(scoreText,score,15);
    SDL_Rect fillRect = {   SCREEN_WIDTH/2+5,
                            SCREEN_WIDTH+SCREEN_PAD,
                            SCREEN_WIDTH/2-2*SCREEN_PAD,
                            SCREEN_HEIGHT-SCREEN_WIDTH-2*SCREEN_PAD };
    SDL_SetRenderDrawColor( gRenderer,g_score_bg.r,g_score_bg.g,g_score_bg.b,g_score_bg.a );        
    SDL_RenderFillRect( gRenderer, &fillRect );
    draw_text_white(gRenderer,font,scoreText,fillRect);

}
void render_game(SDL_Renderer* gRenderer,unsigned char matrix[][SIZE], TTF_Font* font)
{
    SDLclear(gRenderer);
    draw_matrix(gRenderer,matrix,font);
    draw_score(gRenderer,matrix,font);
    draw_button(gRenderer,matrix,font);
    SDL_RenderPresent( gRenderer );
}

void gameLoop(unsigned char matrix[][SIZE],SDL_Renderer* gRenderer)
{
    TTF_Font* font =NULL;
    font= TTF_OpenFont(FONT_PATH, CELL_FONT_SIZE);
    if(font==NULL){
        perror("The required font was not found");
        exit(1);
    }

    render_game(gRenderer,matrix,font);

    bool quit=0;
    SDL_Event e;
    while (!quit)
    {
        while( SDL_PollEvent( &e ) != 0 )
        {
            //User requests quit
            if( e.type == SDL_QUIT )
            {
                quit = 1;
            }
            else if(e.type==SDL_KEYUP)
            {
                handle_move(e,matrix,gRenderer);
                //Redraw all portions of game
                render_game(gRenderer,matrix,font);
            }
            else if(e.type==SDL_MOUSEBUTTONUP)
            {
                button_action(e,matrix);
                render_game(gRenderer,matrix,font);
            }
        }
    }
    TTF_CloseFont(font);
    //No need to null out font.
}


int main(int argc,char** argv)
{
    //Set up the seed 
    srand(time(NULL));

    //Set up the game matrix.
    unsigned char matrix[SIZE][SIZE];
    clear_matrix(matrix);
    add_random(matrix);

    //Init the SDL gui variables
    SDL_Window* gWindow = NULL;
    SDL_Renderer* gRenderer = NULL;
    if(!initSDL(&gWindow,&gRenderer)){exit(0);};

    display_text(gRenderer,"2048",TITLE_FONT_SIZE);
    gameLoop(matrix,gRenderer);

    //Releases all resource
    SDLclose(gWindow);   
    return 0;
}


评论

这是一个很好的问题,我很喜欢阅读它和评论。请注意:只有直接嵌入到问题中的代码才有资格审核。 (有兴趣查看它的任何人都可以使用GitHub链接,但该链接无效,无法复查。)如果您想查看更新的代码,则在实现更改后发布新问题是完全可以接受的。我们喜欢迭代式评论,尤其是在有趣的问题上。

也许与您的需求无关,但是我可以使用javascript作为前端而使用python作为后端来制作2048吗?

@austingae 2048最初是用JavaScript编写的。这是一款单人游戏,因此没有使用后端。但是您可以轻松地使用python后端,并使用排行榜和其他模式/变体制作(多人游戏)2048。

#1 楼

做得好。这不是一个完整的综述,而是一份(简短的)可能的改进清单,当我浏览您的代码时发现了这些改进。

文档

首先:谢谢!有文档很棒。

请注意,是否将文档放在页眉或源中存在一些争论。我想指出的是,我只会在标题中添加一个@brief描述,并在源代码中添加一个完整的文档。这样一来,就可以快速浏览所有功能,并在找到正确的功能时对其进行详细调查。但是,这是个人喜好,在团队项目中,您将遵循已存在的准则。 doxygen生成的文档无论哪种方式都将保持不变。游戏现在停留在编译时选择的大小。如果要启用其他尺寸的电路板,则必须添加一些逻辑以使该电路板始终保持在矩阵中,因此必须使用变量arr[][SIZE]。另外,像4x6这样有趣的板子目前尚无法实现。如果您确实想采用该方法,请使用类型别名:

typedef unsigned char board_type[][SIZE];


但是,考虑到任意板尺寸,您可能希望在以下位置引入合适的矩阵类型一点:

struct board_type {
    unsigned width;
    unsigned height;
    unsigned char * actual_board;
};


是否使用单个分配的SIZEunsigned char matrix[][SIZE]乘以malloc(sizeof(*actual_board)*SIZE*SIZE)至少对于小尺寸而言并不重要。前者在内存方面更易于处理,后者在访问方面更容易。

如果您想在两者之间进行交换,可以使用一组小的SIZE函数: br />
unsigned char board_get(struct board_type *board, unsigned row, unsigned col) {
    assert(row < board->height);
    assert(col < board->width);
    return board->actual_board[row * board->width + col];
    // or, if actual_board is a `unsigned char**`
    return board->actual_board[row][col];
}

void board_set(struct board_type *board, unsigned row, unsigned col, unsigned char value) {
    assert(row < board->height);
    assert(col < board->width);
    board->actual_board[row * board->width + col] = value;
    // or, if actual_board is a `unsigned char**`
    board->actual_board[row][col] = value;
}



malloc(sizeof(*actual_board)*SIZE)不是整数

inline函数的两个参数都使用pow,这很好。但是,对于简单的整数来说,这是一个完全的矫kill过正。在过去的回顾中,我分享了更多详细信息,但是对于您的游戏而言,简单的移位就足够了:

unsigned long pow_integral(unsigned char base, unsigned char exponent) {
    if(base == 2) {
        return (1lu << exponent);
    } else {
        // exercise; use "double-and-add" method for logarithmic speed
    }
}


没有神奇的数字

像文档一样,这是代码的重要功能。代码中没有魔术数字,每个数字都是正确的,以提供一些自我说明。但是,通常希望对pow进行一些注释,并且Doxygen应该发出一些警告。

double中只有一个魔术数字。请参阅下面的“失明”。

C99具有define


话虽如此,偶尔也有#definemain。由于bool,很明显它们的意思是bool success = 10。但是,您可以只使用bool,而可以使用boolean定义的语言。这在StackExchange上的代码中并不明显,但是在GitHub上。您可能想解决此问题,因为几个编辑器使用8个空格作为制表符,而不是4个空格。提供的字符串,以及存储在true上的错误代码的文字说明。据我所知,没有一个SDL函数设置false,因此#include <stdbool.h>不会报告正确的错误。相反,请使用perrorperrorerrno

早期返回

您的某些函数已经准备好返回代码,例如errno

bool initSDL(SDL_Window **gWindow,SDL_Renderer** gRenderer)
{
    bool success = 1;
    TTF_Init();
    if( SDL_Init( SDL_INIT_VIDEO ) < 0 )
    {
        perror( "SDL could not initialize!" );
        success = 0;
    }
    else
    {
        *gWindow = SDL_CreateWindow( "2048", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
        if( gWindow == NULL )
        {
            perror( "Window could not be created!" );
            success = 0;
        }
        else
        {
            *gRenderer = SDL_CreateRenderer( *gWindow, -1, SDL_RENDERER_ACCELERATED );
            if( gRenderer == NULL )
            {
                perror( "Renderer could not be created!" );
                success = 0;
            }
            else
            {
                SDL_SetRenderDrawColor( *gRenderer, g_bg.r,g_bg.g,g_bg.b,g_bg.a );

            }
        }
    }

    return success;
}


这段代码受厄运金字塔的影响。但是,在所有perror中,我们实际上都没有清理资源,因此我们可以编写以下内容:

bool initSDL(SDL_Window **gWindow,SDL_Renderer** gRenderer)
{
    TTF_Init();
    if( SDL_Init( SDL_INIT_VIDEO ) < 0 )
    {
        fprintf(stderr, "SDL could not initialize: %s\n", SDL_GetError());
        return false;
    }
    *gWindow = SDL_CreateWindow( "2048", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
    if( gWindow == NULL )
    {
        fprintf(stderr, "Window could not be created: %s\n", SDL_GetError());
        return false;
    }
    *gRenderer = SDL_CreateRenderer( *gWindow, -1, SDL_RENDERER_ACCELERATED );
    if( gRenderer == NULL )
    {
        fprintf(stderr, "Renderer could not be created: %s\n", SDL_GetError());
        // What about gWindow?
        // We should probably do something about it
        return false;
    }
    SDL_SetRenderDrawColor( *gRenderer, g_bg.r,g_bg.g,g_bg.b,g_bg.a );
}


资源管理和布尔盲目性

如上面的代码所示,当printf成功但fprintf失败时,SDL_GetError没有被正确销毁。此外,initSDL的调用者无法找出初始化失败的原因。枚举通常是在这种情况下的解决方案,至少在我们不清理的情况下。

也就是说,if中的CreateWindow已关闭。零退出值表示游戏能够运行并退出。但是,如果CreateRenderer失败,则游戏将无法运行,我们应该向操作系统报告该错误:
对于gWindow s始终使用(适当)块

但是,由于另一原因,该行很奇怪:它不遵循您通常的缩进方式。让我们解决一下:

if(!initSDL(&gWindow,&gRenderer)){exit(EXIT_FAILURE);};


有一个流浪的分号。虽然这不是错误,但它表示代码以前是initSDL,然后有些事情变了,然后又变回来了。有条件的代码,因此请确保使代码尽可能的干净。

可读性

编译器不在乎是否编写

br />
但是人类会很难受。使您的代码也易于使用:

if(!initSDL(&gWindow,&gRenderer)){
    exit(EXIT_FAILURE);
}; // <<--- ?


至少可以通过这种方式改进exit(0)main

命名

在计算机科学中,存在两个难题:命名,缓存和一次性错误。在这里,我们重点讨论第一个。

仅在前缀具有含义的情况下使用前缀

initSDLif具有if(!initSDL(&gWindow,&gRenderer))exit(0);前缀(未解释)。其他变量都没有前缀。

如果move_x是SDL对象的通用前缀,那很好,但是,我想这是move_y ame的意思。但是,很奇怪的是,板子本身就没有前缀。

这样说...

按功能而不是形式命名

在整个游戏中,该板子称为gRenderer。但是,gWindow是数学术语或电影标题的术语,但与“标板”功能并不完全匹配。另一方面,g将是一个完美的名字。另外,如果您想实现一个变体,让玩家同时在两个板上玩游戏,那么复数会容易得多。

不要惊讶其他人

gg让我感到惊讶。这两个函数都没有遵循通常的matrix方法,因为它们都不是matrix的方法。 。或者,遵循board方法并调用函数SDLcloseSDLclear。这些名称是完全明确的。

创建与破坏之间的对称性

SDL_<name>中存在某些问题:

a=foo(), b=too(), c=quux()+a*b; if(a<b)c-=t;


鉴于我们作用于局部指针,因此从外部看不到SDL。这可能只是一个小错误。但是,如果要以对称方式使用initSDLclearSDL,我们最终会得到

a = foo();
b = too();
c = quux() + a * b;

if ( a < b ) {
    c -= t;
}


在这里,closeSDL很有意义。但是,由于SDLclose是少数没有随附文档的功能之一,因此尚不清楚预期的行为是什么,因此我坚持使用前者,例如

void SDLclose(SDL_Window* gWindow)
{
    SDL_DestroyWindow( gWindow );
    gWindow = NULL;
    TTF_Quit();
    SDL_Quit();
}


评论


\ $ \ begingroup \ $
感谢您的好评!我没有考虑过移位或pow()的运行时。 SDL_GetError也是一个非常有用且具体的注释。我很确定我已经删除了所有魔术数字。我没有意识到exit(0/1)也可以删除。这真是令人惊喜。我选择了matrix [] [SIZE]格式,因为我永远不会摆弄非正方形板。但是,既然您提到它,实现起来似乎很有趣。再一次谢谢你!
\ $ \ endgroup \ $
– Gnik
18/12/27在15:15

\ $ \ begingroup \ $
不是OP,但是您可以解释一下:“无论是使用单个分配的malloc(sizeof(actual_board)* SIZESIZE)还是S​​IZE乘以malloc(sizeof(* actual_board)* SIZE),至少对于小尺寸而言,不重要,前者在内存方面更易于处理,后者在访问方面更容易。”,特别是为什么后者在访问方面更容易的原因。
\ $ \ endgroup \ $
–马里奥·伊沙克(Mario Ishac)
18/12/27在22:00

\ $ \ begingroup \ $
SDLclose函数肯定包含一个错误。我已在最新修订版中修复了该问题,但仍保留更改。也许我会在几周后对实现进行一些更改后发布更新后的问题。您对代码的理解程度也令人恐惧。非常感谢您抽出宝贵时间来参加本次评论。
\ $ \ endgroup \ $
– Gnik
18-12-28 at 2:19

\ $ \ begingroup \ $
@MarDev当然。第一个将导致TYPE *矩阵。现在,有效索引为\ $ 0,1,\ ldots,\ text {N} ^ 2-1 \ $。这对于重置非常方便,因为您只需要memset,但是现在您需要matrix [row + column * N]或类似的东西。后者导致TYPE **矩阵。我们现在有一个间接寻址,必须使用matrix [i] [j]。如果您想使用头寸,这会容易得多,因为我们不必转换\ $ \ {0,\ ldots \,N-1 \} \ times \ {0,\ ldots \,N-1 \}手动将\ to \ {0,\ ldots,N ^ 2-1 \} \ $与\ $ ind = xN + y \ $。但是,这种可能性不太可能会持续下去。我在答案中添加了示例。
\ $ \ endgroup \ $
– Zeta
18/12/28在9:10

#2 楼

由于其他评论已经达到了大多数要点,因此我仅提及一些尚未涵盖的内容。

避免在#include s中使用相对路径

通常最好从#include文件中省略相对路径名,而是将编译器指向适当的位置。因此,请使用以下代码:

#include "../include/styles.h"
#include "../include/game.h"


编写以下代码:

#include "styles.h"
#include "game.h"


这样可以减少代码对实际文件结构,并将此类详细信息放在一个位置:Makefile或编译器配置文件。使用cmake,我们可以使用include_directories。由于您已经在顶级CMakeLists.txt中获得了该名称,因此只需在该include指令中附加CMake目录。

了解#include的工作方式

在大多数平台上,#include "math.h"之间的区别#include <math.h>是前者在当前目录中排名第一。因此,对于诸如SDL2/SDL.h之类的系统文件,您应该真正使用#include <SDL2/SDL.h>代替。有关更多详细信息,请参见此问题。

在许多情况下,这两种方法都有可能奏效,但根据人类阅读者的惯例,项目中的文件使用""而系统包含(文件不在项目中)使用<>。这是不精确的区分,但是是一种思考的有用方法。

不要重复自己(DRY)

merge_xmerge_y函数几乎相同。我认为将它们组合为一个merge函数是有意义的,它将一个方向作为附加参数。使用shiftmove函数可以采用相同的方法。例如,这是一个组合的shift()函数,它带有一个额外的参数,指示ydir

bool shift(Board board, bool opp, bool ydir)
{
    bool moved=false;
    int start=0,end=SIZE,increment=1;
    if (opp)
    {
        start=SIZE-1;
        end=-1;
        increment=-1;
    }
    for (int a=0;a<SIZE;a++)
    {
        int index=start;
        for(int b=start;b!=end;b+=increment)
        {
            int x = ydir ? b : a;
            int y = ydir ? a : b;
            if (board[x][y]!=0)
            {
                if (ydir) {
                    board[index][y]=board[x][y];
                } else {
                    board[x][index]=board[x][y];
                }
                if(index!=b) {
                    board[x][y]=0;
                    moved=true;
                }
                index+=increment;
            }
        }
    }
    return moved;
}


在可行的地方使用const

Board不是,也不应通过print_board功能进行更改。因此,建议将函数的签名更改为:

void print_board(const Board board, FILE* stream);


可以对is_game_overcalculate_score做类似的更改
>不要泄漏内存

在不泄漏内存的情况下,很难正确使用SDL接口,因为并不总是很容易看出分配了哪些功能以及分配了哪些功能。在此代码中,initSDL创建一个renderer,但从不调用SDL_DestroyRenderer。我建议添加指向渲染器的指针作为closeSDL的参数,并在调用SDL_DestroyRenderer之前确保其为非NULL。 :

inline void move_x(Board board, bool opp)
{
    //Assigning values insted of evaluating directly to force both operations
    //Bypassing lazy 'OR' evaluation
    bool a=shift_x(board,opp), b=merge_x(board,opp);
    if(a||b)add_random(board);
}


它可以更清楚地写为:

inline void move_x(Board board, bool opp)
{
    bool move_or_shift = shift_x(board,opp);
    move_or_shift |= merge_x(board,opp);
    if (move_or_shift) {
        add_random(board);
    }
}


用户的想法

有一些小改进可以使游戏更好。首先是允许用户看到并品尝高分,而不是立即启动新游戏。其次是检测是否可能进行任何移动,而不是在评估之前等待用户尝试移动。

评论


\ $ \ begingroup \ $
很棒的评论!我想澄清的一件事是DRY原则。我试图为shift制作一个函数,而不是shift_x和shift_y。你能告诉我是否可行?我考虑过为清楚起见而重复代码,而不是编写复杂的shift函数。您将如何处理?也适用于“简化代码”部分。我没明白你的意思。您只提供了代码,我认为该功能非常简单。我如何使它更简单?再次感谢您!
\ $ \ endgroup \ $
– Gnik
18-12-29在3:17



\ $ \ begingroup \ $
关于包含。我与样式不一致,但是用“”代替<>书写几乎不是总是更好吗?特别是由于#include“ SDL2 / SDL2.h”如果在源目录中未找到任何内容,则会回退到#include 。因此,用户可以根据需要提供自己的“数学”和“时间”实现。也许仅通过提供库的某些功能来减小文件大小,等等。使用<>有什么好处吗?
\ $ \ endgroup \ $
– Gnik
18/12/29在3:39

\ $ \ begingroup \ $
我已经在答案中添加了答案,以尝试回答您的所有问题。如果仍然不清楚,请再次询问。
\ $ \ endgroup \ $
–爱德华
18/12/29在4:04

#3 楼

添加到@Zeta的出色答案。


空白

您的空白策略非常不一致。有时,您会这样调用函数:

SDL_SetRenderDrawColor( *gRenderer, g_bg.r,g_bg.g,g_bg.b,g_bg.a );


()内有空格,而其他时候,您会这样调用它们:

TTF_SizeText(font, text, &message_rect.w, &message_rect.h);


里面没有空格。类似地,有时您会像上面一样在逗号后放置空格,而有时则不这样做,例如:此处:

display_text(gRenderer,"2048",TITLE_FONT_SIZE);
gameLoop(matrix,gRenderer);


有时您会在算术运算符周围放置空格,但通常不会。

struct COLOR g_COLORS[]={
    {230, 227, 232,255},
    {255, 127, 89,255},
    // etc.
};


>有时,您还将指针与类型相关联,而有时将其与变量相关联:

int squareSize=(SCREEN_WIDTH - 2*SCREEN_PAD)/SIZE-SCREEN_PAD;


最后,在标头中,有时会有两行空行分隔docstring-forward-声明对,有时只有一对。

我鼓励您使用clang-format来保持样式一致。

正确性

类似代码即使它们只是理论上的可移植性错误,也总是存在错误。即使在计算机上工作,也要摆脱编写它的习惯。

特别地,C标准允许int占用4个以上的空间。个字节。相反,您应该在运行时向snprintf询问长度并分配自己,或者使用asprintf(这是GNU扩展名)。要向snprintf询问所需的缓冲区大小,请为其提供一个空指针和零长度,如下所示:

bool initSDL(SDL_Window **gWindow,SDL_Renderer** gRenderer)


如果您愿意使用此模式,将使其更容易GNU扩展,例如:

char score[15]; //15 chars is enough for score.
sprintf(score, "%d", calculate_score(matrix));
char scoreText[30]="Score:";
strncat(scoreText,score,15);


当然,您总是可以将snprintf与固定大小的缓冲区一起使用,并在事情变得太大时才截断。

评论


\ $ \ begingroup \ $
下次我写东西时会考虑clang格式。似乎大多数问题都可以通过使用更好的构建系统/ IDE来避免。同样,在运行时设置缓冲区大小绝对是解决问题的方法。谢谢!
\ $ \ endgroup \ $
– Gnik
18/12/28在11:09