c_snake_game

第 14 课:状态机 🎮

状态机管理游戏的不同状态(菜单、游戏中、游戏结束)。


14.1 什么是状态机?

状态机是一种管理程序状态的方法:

┌──────────────┐
│  开始菜单     │
└──────┬───────┘
       │ 按 Enter
       ↓
┌──────────────┐
│   游戏中      │←──────┐
└──────┬───────┘       │
       │ 撞墙           │ 按 R
       ↓               │
┌──────────────┐       │
│  游戏结束     │───────┘
└──────┬───────┘
       │ 按 M
       ↓
┌──────────────┐
│  开始菜单     │
└──────────────┘

14.2 定义状态

typedef enum {
    STATE_MENU,       // 开始菜单
    STATE_PLAYING,    // 游戏中
    STATE_PAUSED,     // 暂停
    STATE_GAME_OVER   // 游戏结束
} GameState;

14.3 状态转换

typedef struct {
    GameState state;
    GameState next_state;
} Game;

void change_state(Game* g, GameState new_state) {
    g->next_state = new_state;
}

void update_state(Game* g) {
    if (g->state != g->next_state) {
        // 退出当前状态
        state_exit(g->state);
        
        // 转换到新状态
        g->state = g->next_state;
        
        // 进入新状态
        state_enter(g->state);
    }
}

14.4 状态处理函数

// 每个状态的处理函数
void menu_update(Game* g);
void menu_render(Game* g);
void menu_input(Game* g, int key);

void game_update(Game* g);
void game_render(Game* g);
void game_input(Game* g, int key);

void gameover_update(Game* g);
void gameover_render(Game* g);
void gameover_input(Game* g, int key);

14.5 游戏循环中的状态机

void game_run(Game* g) {
    while (g->running) {
        // 处理输入
        int key = getch();
        
        switch (g->state) {
            case STATE_MENU:
                menu_input(g, key);
                break;
            case STATE_PLAYING:
                game_input(g, key);
                break;
            case STATE_GAME_OVER:
                gameover_input(g, key);
                break;
        }
        
        // 更新状态
        update_state(g);
        
        // 更新逻辑
        switch (g->state) {
            case STATE_MENU:
                menu_update(g);
                break;
            case STATE_PLAYING:
                game_update(g);
                break;
            case STATE_GAME_OVER:
                gameover_update(g);
                break;
        }
        
        // 渲染
        switch (g->state) {
            case STATE_MENU:
                menu_render(g);
                break;
            case STATE_PLAYING:
                game_render(g);
                break;
            case STATE_GAME_OVER:
                gameover_render(g);
                break;
        }
        
        napms(16);
    }
}

14.6 状态处理器结构体

typedef struct {
    void (*update)(Game*);
    void (*render)(Game*);
    void (*input)(Game*, int);
    void (*enter)(Game*);
    void (*exit)(Game*);
} StateHandler;

// 定义各个状态的处理器
StateHandler menu_handler = {
    .update = menu_update,
    .render = menu_render,
    .input = menu_input,
    .enter = menu_enter,
    .exit = menu_exit
};

StateHandler game_handler = {
    .update = game_update,
    .render = game_render,
    .input = game_input,
    .enter = game_enter,
    .exit = game_exit
};

// 使用
StateHandler* current_handler = &menu_handler;

void game_loop(Game* g) {
    while (g->running) {
        int key = getch();
        
        current_handler->input(g, key);
        current_handler->update(g);
        current_handler->render(g);
    }
}

14.7 完整示例

#include <ncurses.h>
#include <stdbool.h>

typedef enum {
    STATE_MENU,
    STATE_PLAYING,
    STATE_GAME_OVER
} State;

typedef struct {
    State state;
    State next_state;
    bool running;
    int x, y;
    int score;
} Game;

// 菜单
void menu_render(Game* g) {
    clear();
    mvprintw(10, 30, "=== SNAKE GAME ===");
    mvprintw(12, 28, "Press ENTER to start");
    mvprintw(14, 30, "Press Q to quit");
    refresh();
}

void menu_input(Game* g, int key) {
    if (key == '\n') {  // Enter
        g->next_state = STATE_PLAYING;
        g->x = 10; g->y = 10; g->score = 0;
    } else if (key == 'q') {
        g->running = false;
    }
}

// 游戏
void game_update(Game* g) {
    // 移动逻辑
}

void game_render(Game* g) {
    clear();
    mvprintw(0, 0, "Score: %d", g->score);
    mvaddch(g->y, g->x, 'O');
    refresh();
}

void game_input(Game* g, int key) {
    if (key == KEY_UP) g->y--;
    else if (key == KEY_DOWN) g->y++;
    else if (key == KEY_LEFT) g->x--;
    else if (key == KEY_RIGHT) g->x++;
    else if (key == 'q') g->next_state = STATE_GAME_OVER;
}

// 游戏结束
void gameover_render(Game* g) {
    clear();
    mvprintw(10, 30, "GAME OVER");
    mvprintw(12, 28, "Final Score: %d", g->score);
    mvprintw(14, 26, "R to restart, M for menu");
    refresh();
}

void gameover_input(Game* g, int key) {
    if (key == 'r') {
        g->next_state = STATE_PLAYING;
        g->x = 10; g->y = 10; g->score = 0;
    } else if (key == 'm') {
        g->next_state = STATE_MENU;
    }
}

// 主循环
void game_run(Game* g) {
    while (g->running) {
        // 状态转换
        if (g->state != g->next_state) {
            g->state = g->next_state;
        }
        
        int key = getch();
        
        switch (g->state) {
            case STATE_MENU:
                menu_input(g, key);
                menu_render(g);
                break;
            case STATE_PLAYING:
                game_input(g, key);
                game_update(g);
                game_render(g);
                break;
            case STATE_GAME_OVER:
                gameover_input(g, key);
                gameover_render(g);
                break;
        }
        
        napms(100);
    }
}

int main(void) {
    initscr();
    cbreak();
    noecho();
    keypad(stdscr, TRUE);
    
    Game game = {
        .state = STATE_MENU,
        .next_state = STATE_MENU,
        .running = true
    };
    
    game_run(&game);
    
    endwin();
    return 0;
}

✅ 本课检查清单


📝 作业

  1. 为贪吃蛇添加暂停状态

  2. 实现一个设置菜单状态

  3. 添加状态转换时的动画效果


下一课:完成与扩展