文件概述

main file

test

  1. 主要定义变量都是在common. h 以及 common. c文件中
  2. game_setup. c都是游戏初始化定义
  3. snake. c是==game loop==,main 函数所在文件
  4. game. c是游戏主要逻辑实现函数
  5. test目录下的都是代码测试文件

主要文件代码

common.h

extern int g_game_over; // 1 if game is over, 0 otherwise
extern int g_score; // game score: 1 point for every food eaten
extern enum input_key g_last_input; //record last direction
extern int g_snake_length; // the length of snake
extern int g_name_length; // the lenght of game's name
extern char* g_name; // the name of the game
/** Snake struct. This struct is not needed until part 3!
* Fields:
* - None yet!
*/
typedef struct place
{
size_t x;
size_t y;
} place_t;

typedef struct snake {
// TODO: Define your snake struct! (in ')
// Store any necessary information about your snake here.
node_t *g_snake_head;
} snake_t;
  • 我一开始是在 snake 内部定义回那些全局变量的,但是后面重新修改 game. c代码逻辑就重新将common. h的内容页修改一下,让 snake 结构体没怎么臃肿
  • 我是将这个 ==bord==看成一个 xoy 坐标的,所以直接定义一个 place 结构体,因此 cells这个数组的值也是要用 x 和 y 来表示特定位置的 FLAG

snake.c

switch (argc) {
    case (2):
        snake_grows = atoi(argv[1]);
        if (snake_grows != 1 && snake_grows != 0) {
            printf("snake_grows must be either 1 (grows) or 0 (does not grow)\n");
            return 0;
        }
        status = initialize_game(&cells, &width, &height, &snake, NULL);
        break;
    case (3):
        snake_grows = atoi(argv[1]);
        if (snake_grows != 1 && snake_grows != 0) {
            printf("snake_grows must be either 1 (grows) or 0 (does not grow)\n");
            return 0;
        } else if (*argv[2] == '\0') {
            status = initialize_game(&cells, &width, &height, &snake, NULL);
            break;
        }
        status = initialize_game(&cells, &width, &height, &snake, argv[2]);
        break;
    case (1):
    default:
        printf("usage: snake <GROWS: 0|1> [BOARD STRING]\n");
        return 0;
}

switch获取命令行作为参数,从而实现对应选择。

game loop logic

while(!g_game_over)
{
    enum input_key input = g_last_input;
    usleep(100000);
    input = get_input();
    update(cells, width, height, &snake,input , snake_grows);
    render_game(cells, width, height);
}

这个是对应游戏调用循环逻辑

差不多这个 snake. c 就可以宣告就怎么多了


game.c

place_t new_pos;
new_pos.x = ((place_t*)snake_p->g_snake_head->data)->x;
new_pos.y = ((place_t*)snake_p->g_snake_head->data)->y;


if(input == INPUT_NONE)
{
    input = g_last_input;
}

switch (input)
{
case INPUT_RIGHT:
    new_pos.x = ((place_t*)snake_p->g_snake_head->data)->x + 1;
    g_last_input = INPUT_RIGHT; 
    break;
case INPUT_LEFT:
    new_pos.x = ((place_t*)snake_p->g_snake_head->data)->x - 1;
    g_last_input = INPUT_LEFT;
    break;
case INPUT_UP:
    new_pos.y = ((place_t*)snake_p->g_snake_head->data)->y - 1;
    g_last_input = INPUT_UP;
    break;
case INPUT_DOWN:
    new_pos.y = ((place_t*)snake_p->g_snake_head->data)->y + 1;
    g_last_input = INPUT_DOWN;
    break;
// case INPUT_NONE:
//     new_pos.x = ((place_t*)snake_p->g_snake_head->data)->x + 1;
//     g_last_input = INPUT_RIGHT; 
//     break;
default:
    break;
}

==这个就是方向移动选择逻辑,一开始我是没有写 input = g_last_input,因为我是改了 snake. c 里的,但是现在统一弄到 game. c 中==

[!NOTE] 因为头部节点这里的data已经发生变化,已经移动了,所以如果还这样做的话,不能单纯使用遍历链表的方式取data数据然后使用cells来标记蛇的位置

if(new_pos.x == 0 || 
new_pos.x >= width || 
new_pos.y == 0 || 
new_pos.y >= height || 
(cells[new_pos.y * width + new_pos.x] & FLAG_SNAKE) == FLAG_SNAKE)
{
    g_game_over = 1;
    return;
}

==移动方向后要判断蛇是否已经是可以判断是结束了的条件了(因为我是选择了用 x 和 y 坐标,所以我是判断大小,而不是用那个地方是否标记为 FLAG_SNAKE 还是 FALG_WALL)==

接下来就是主要的蛇的移动和生长逻辑实现:

  • 前提:都是使用链表,因此,要使用到链表中的data这个成员
  1. 一开始我是打算以如下方式实现的:让蛇后一节代替前一节,data 数据也是,具体如下所示: idea

但是没有想到如何实现,这个蛇的每一节的 data 一开始存储数据就要先存储初始坐标值的话(会很麻烦也不知道要怎么实心)->比如: 蛇在墙转头吃了 food,然后在蛇尾生长呢?

  1. 方案二: 移动靠增加头部节点,然后根据情况删除尾部节点(吃了食物后就不用删除,没吃就删除)用 linked_list. c 中的函数中的 insert_first以及remove_last 来实现->因为每一次变化都是根据当时的情况每次更新的,所以后续的只需要考虑头部以及尾部节点就可以了。
bool eaten_food = (cells[new_pos.y * width + new_pos.x] & FLAG_FOOD) == FLAG_FOOD;

insert_first(&(snake_p->g_snake_head), &new_pos, sizeof(place_t));
if(growing)
{
    if(eaten_food)
    {
        cells[new_pos.y * width + new_pos.x] ^= FLAG_FOOD;
        g_score+=1;
        
        place_food(cells,width,height);

        // 检查是否有食物重置到蛇身上
        node_t *curr = snake_p->g_snake_head;
        size_t curr_index;
        bool is_food = false;
        
        while(curr)
        {
            curr_index = ((place_t*)curr->data)->x + ((place_t*)curr->data)->y * width;
            is_food = (cells[curr_index] & FLAG_FOOD) == FLAG_FOOD;
            curr = curr->next;
            if(is_food)
            {
                place_food(cells,width,height);
                curr = snake_p->g_snake_head;
            }
        }
    }
    else
    {
        place_t* tail = (remove_last(&(snake_p->g_snake_head)));
        size_t tail_index = tail->x + tail->y * width;
        cells[tail_index] ^= FLAG_SNAKE;
        free(tail);
    }
}
else
{
    if(eaten_food)
    {
        cells[new_pos.y * width + new_pos.x] ^= FLAG_FOOD;
        g_score+=1;
        place_food(cells,width,height);
    }
    place_t* tail = (remove_last(&(snake_p->g_snake_head)));
    size_t tail_index = tail->x + tail->y * width;
    cells[tail_index] ^= FLAG_SNAKE;
    free(tail);
}
cells[new_pos.y * width + new_pos.x] ^= FLAG_SNAKE;

这样就实现了基本的 updatte

释放内存

void teardown(int* cells, snake_t* snake_p) {
    free(cells);
    if (snake_p->g_snake_head != NULL) {
        node_t* curr = snake_p->g_snake_head;
        while(curr->next){
            curr = curr->next;
            if (curr->prev->data != NULL) {
                free(curr->prev->data);
            }
            free(curr->prev);
        }
        if (curr->data != NULL) {
            free(curr->data);
        }
        free(curr);
    }
}

gameover

==基本 snakeproject 就这样结束了,不过这个只是个大概框架,还有许多地方可以扩展==