首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C/SDL2 2中的生命游戏

C/SDL2 2中的生命游戏
EN

Code Review用户
提问于 2021-04-18 22:18:09
回答 1查看 217关注 0票数 2

我的第一个项目在C/SDL2 2完成后,完成了一个简短的教程编写Pong。这两个项目是我使用C和SDL2的全部经验,尽管我以前有一些使用Python的基本经验。

我还添加了一条“生命轨迹”,显示最近死亡的灰色细胞。最近死得更深了。

边沿也有1平方宽的死区,以避免冲出。

代码工作并产生所需的结果,尽管我正在寻找关于改进代码的建议。该项目使用的唯一参考资料是一份生活规则游戏清单,因此我确信有许多潜在的改进。

我试图使代码尽可能可读,并在可能的情况下留下注释。(可能太多了)--如果有什么不清楚的地方,请告诉我。

代码语言:javascript
复制
// Controls
// Left or Right mouse click to set cells alive/dead
// R to reset all cells to dead
// Spacebar to pause/unpause
// Right-arrow key to step 1 frame at a time

#include 
#include 

#define GRID_SIZE 40 // Number of grid squares in X/Y direction
#define SQUARE_SIZE 20 // Pixel size of each square
#define GRID_LINE_WIDTH 1 // Pixel size of gap between squares
#define GRID_BORDER SQUARE_SIZE * 2 // Width of border around game grid
#define TIME_STEP_DELAY 150

const char* WINDOW_TITLE = "Game of Life"; // Game window title
const int SCREEN_WIDTH = SQUARE_SIZE * GRID_SIZE + GRID_BORDER; // Game window width
const int SCREEN_HEIGHT = SQUARE_SIZE * GRID_SIZE + GRID_BORDER; // Game window height

SDL_Window* window = NULL; // SDL Window
SDL_Renderer* renderer = NULL; // SDL Renderer

typedef struct Cell // Cell struct holds data regarding position, alive status and time since last alive for individual cell
{
    int x;
    int y;
    bool isAlive;
    int lastAlive;
} Cell;

bool Initialize(void);
void ResetGameGrid(void);
void HandleInput(void);
void DrawGame(void);
void TimeStep(void);
void Shutdown(void);

void AddExampleGlider(void);

Cell GameGrid[GRID_SIZE][GRID_SIZE]; // Current game grid
Cell PrevGameGrid[GRID_SIZE][GRID_SIZE]; // Previous timestep game grid
bool gameRunning = true;
bool gamePaused = true;

int main(int argc, const char* argv[])
{
    atexit(Shutdown); // If app closes, cleanup and shutdown

    if (!Initialize()) // Initialize SDL, create window and fill game grid with dead cells. If this fails, exit app
    {
        exit(1);
    }

    if(GRID_SIZE >= 8)
        AddExampleGlider(); // Adds a basic glider to top left of screen on launch

    while (gameRunning) // Main game loop - loop until gameRunning == false
    {       
        HandleInput(); // Check if user closes the window with X at top-right - close and quit SDL if so
        DrawGame(); // Draw the window and grid

        if(!gamePaused)
            TimeStep(); // Apply rules and step forward
            SDL_Delay(TIME_STEP_DELAY);
    }

    Shutdown(); // If gameRunning == false, shutdown and quit SDL
    return 0;
}

bool Initialize(void)
{
    if (SDL_Init(SDL_INIT_VIDEO) != 0) // Init SDL Video
    {
        printf("Failed to init SDL: %s\n", SDL_GetError());
        return false;
    }
    
    // Create SDL Window
    window = SDL_CreateWindow(WINDOW_TITLE, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
    if (!window)
    {
        return false;
    }

    // Create SDL Renderer
    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    if (!renderer)
    {
        return false;
    }

    ResetGameGrid(); // Fill Game Grid with dead cells
    memcpy(PrevGameGrid, GameGrid, sizeof(GameGrid)); // Previous game grid = current game grid

    return true;
}

void DrawGame()
{
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); // Draw black background
    SDL_RenderClear(renderer);

        for(int i = 0; i < GRID_SIZE; i++) // Loop through each cell in the grid and draw
        {
            for(int j = 0; j < GRID_SIZE; j++)
            {
                SDL_Rect rect = {
                GameGrid[i][j].x,
                GameGrid[i][j].y,
                .w = SQUARE_SIZE - GRID_LINE_WIDTH,
                .h = SQUARE_SIZE - GRID_LINE_WIDTH,
                };

                if(GameGrid[i][j].isAlive == false) // If cell is dead, draw color based on when last alive to show as trail
                {
                    SDL_SetRenderDrawColor(renderer, GameGrid[i][j].lastAlive, GameGrid[i][j].lastAlive, GameGrid[i][j].lastAlive, 255);
                } else {
                    SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);           
                }
                SDL_RenderFillRect(renderer, &rect);
            }
        }

    SDL_RenderPresent(renderer); // Present renderer
}

void Shutdown(void)
{
    if (renderer) // Destroy renderer if one exists
    {
        SDL_DestroyRenderer(renderer);
    }
    if (window) // Destroy window if one exists
    {
        SDL_DestroyWindow(window);
    }
    SDL_Quit(); // Quit SDL
}

void HandleInput(void)
{
    SDL_Event event;

    while (SDL_PollEvent(&event))
    {
        if (event.type == SDL_QUIT) // If top-right X is clicked, quit game
        {
            gameRunning = false;
            break;
        }

        else if (event.type == SDL_MOUSEBUTTONDOWN) // Handle mouse input - click any mouse button to flip alive/dead status
        {
            int x, y;
            SDL_GetMouseState(&x, &y);

            int gridX, gridY; // Convert mouse x/y co-ords to grid numbers
            gridY = (x - SQUARE_SIZE) / SQUARE_SIZE;
            gridX = (y - SQUARE_SIZE) / SQUARE_SIZE;

            if(GameGrid[gridX][gridY].isAlive) // Flip dead to alive and alive to dead
                GameGrid[gridX][gridY].isAlive = false;
            else
                GameGrid[gridX][gridY].isAlive = true;
        }

        if(event.type == SDL_KEYDOWN) // Handle keyboard input
        {
            switch(event.key.keysym.sym)
            {
                case SDLK_r: // Press R to reset all cells to dead
                    ResetGameGrid();
                    break;

                case SDLK_SPACE: // Press Space to pause/unpause
                    if(gamePaused)
                        gamePaused = false;
                    else
                        gamePaused = true;
                    break;

                case SDLK_RIGHT: // Press Right Arrow Key to time step once
                    TimeStep();
                    break;
            }
        }
    }
}

void ResetGameGrid()
{
    for(int i = 0; i < GRID_SIZE; i++) // Loop through each cell of the grid and reset to default values
    {
        for(int j = 0; j < GRID_SIZE; j++)
        {
            GameGrid[i][j].x = SQUARE_SIZE + j * SQUARE_SIZE;
            GameGrid[i][j].y = SQUARE_SIZE + i * SQUARE_SIZE;
            GameGrid[i][j].isAlive = false;
            GameGrid[i][j].lastAlive = 255;
        }
    }
}

void TimeStep(void)
{
    memcpy(PrevGameGrid, GameGrid, sizeof(GameGrid)); // Set value of PrevGameGrid to be that of current GameGrid

    for(int i = 1; i < GRID_SIZE - 1; i++)
        {
            for(int j = 1; j < GRID_SIZE - 1; j++)
            {
                int aliveNeighbors = 0; // Count number of alive neighbors - Leaving a 1x1 border of dead cells around grid

                if(PrevGameGrid[i-1][j-1].isAlive == true)
                    aliveNeighbors++;
                if(PrevGameGrid[i-1][j].isAlive == true)
                    aliveNeighbors++;
                if(PrevGameGrid[i-1][j+1].isAlive == true)
                    aliveNeighbors++;
                if(PrevGameGrid[i][j-1].isAlive == true)
                    aliveNeighbors++;
                if(PrevGameGrid[i][j+1].isAlive == true)
                    aliveNeighbors++;
                if(PrevGameGrid[i+1][j-1].isAlive == true)
                    aliveNeighbors++;
                if(PrevGameGrid[i+1][j].isAlive == true)
                    aliveNeighbors++;
                if(PrevGameGrid[i+1][j+1].isAlive == true)
                    aliveNeighbors++;

                if(PrevGameGrid[i][j].isAlive == true) // Apply logic if cell is currently alive
                {
                    if(aliveNeighbors < 2) // Cell dies due to lonliness if less than 2 alive neighbors
                        GameGrid[i][j].isAlive = false;
                    if(aliveNeighbors > 1 && aliveNeighbors < 4) // Cell stays alive if 2 or 3 alive neighbors
                        GameGrid[i][j].isAlive = true;
                    if(aliveNeighbors > 3) // Cell dies due to overcrowing with 4 or more alive neighbors
                        GameGrid[i][j].isAlive = false;
                }

                if(PrevGameGrid[i][j].isAlive == false) // Apply logic if cell is currently dead
                {
                    if(aliveNeighbors == 3) // Cell comes to life if exactly 3 alive neighbors
                        GameGrid[i][j].isAlive = true;
                }

                if(GameGrid[i][j].isAlive) // "Death Trail" Fade from Grey to White over time as time lastAlive grows
                    GameGrid[i][j].lastAlive = 125;
                else
                    if(GameGrid[i][j].lastAlive < 255) // Cap at 255
                    {
                        GameGrid[i][j].lastAlive += 5;
                        if(GameGrid[i][j].lastAlive > 255)
                            GameGrid[i][j].lastAlive = 255;
                    }
            }
        }
}

void AddExampleGlider(void)
{
    GameGrid[2][4].isAlive = true;
    GameGrid[3][5].isAlive = true;
    GameGrid[4][3].isAlive = true;
    GameGrid[4][4].isAlive = true;
    GameGrid[4][5].isAlive = true;
}
EN

回答 1

Code Review用户

回答已采纳

发布于 2021-04-19 14:20:47

  • 我们不需要将xy的和弦存储在Cell本身中。在遍历网格时,我们可以廉价地计算它们。
  • 我们不需要存储以前的游戏网格。在TimeStep中执行更新时,我们可以将lastAlive设置为活动单元格的0,并为所有死单元增加它(也许还可以将其钳制到最大值以防止溢出)。最好考虑将lastAlive映射到DrawGame函数中的颜色的细节,并将Cell中的值保留为一个简单的循环计数器。
  • 在测试布尔条件时,例如if(GameGrid[i][j].isAlive == false),我们不需要与truefalse进行比较,我们可以直接使用布尔值:if(!GameGrid[i][j].isAlive)
  • 类似地,当为布尔表达式赋值时,我们不需要使用if语句,例如if( gamePaused ) gamePaused = false;gamePaused= true;可以是gamePaused = !gamePaused;,如果少于2个活着的邻居GameGrid.isAlive =false,则D24和if(aliveNeighbors < 2) // Cell会因为孤独而死亡;如果( aliveNeighbors >1 && or;aliveNeighbors< 4) // Cell保持活着,如果2或3个活着的邻居GameGrid.isAlive = true;如果(aliveNeighbors> 3) // Cell由于与4个或4个以上存活邻居的过度拥挤而死亡,则GameGrid.isAlive = false;可以是GameGrid[i][j].isAlive = (aliveNeighbors == 2 || aliveNeighbors == 3);
  • 所有的情况都是这样的吗?int x,y;SDL_GetMouseState(&x,&y);int gridX,gridY;//将鼠标x/y共和式转换为网格数gridY = (x - SQUARE_SIZE) / SQUARE_SIZE;gridX = (y - SQUARE_SIZE) / SQUARE_SIZE;if( GameGrid.isAlive ) / Flip死到活着到死的GameGrid.isAlive = false;如果用户在边框中单击,gridX / gridY不是负的吗?忽略窗口外的点击也是明智的,以防万一。请注意,您可以从xevent.button.y获得event.button.xD35
  • 在计算活动邻居时,创建一个偏移数组,然后使用一个循环: struct { int,y;},可能会更整洁;结构Coord偏移集={{ -1,-1 },{ 0,-1 },{ 1,-1 },{ -1,0 },{ 1,0 },{ 1,1 },{ 1,1 },};size_t live = 0;for (size_t o= 0;o != 8;++i) live += GameGrid[i + offsets.x][j + offsets.y].isAlive;
  • 避免全局变量:
    • 最好支持不同宽度和高度的网格,以及不同尺寸的网格。我们可以在运行时将网格内存分配为一维sizeof(Cell) * width * height字节数组,并使用cell_index = y * width + x;访问单元格。
    • WINDOW_TITLETIME_STEP_DELAY这样的东西不需要在全球范围内使用。这些可以是局部变量。
    • 必要时,可以通过指针将局部变量传递给其他函数(例如,gameRunninggamePaused)。
    • 其他全局变量可以组合成一个结构,并传递给需要它们的函数,例如: struct DrawInfo { int squarePixels;int borderPixels;int linePixels;};struct窗口{ SDL_Window*窗口;SDL_Renderer*呈现器;};struct Grid { size_t宽度、高度;struct Cell*网格;};DrawGame(struct Window const* window,struct Grid const* grid,struct DrawInfo drawInfo);
票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/259706

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档