[C Entry, c Entry
Because I have already written about the implementation of food, I don't know whether to write about the implementation of the world or the implementation of snakes first. Because the world is a window, you can immediately see the food in the world. For most people, it would be better if they can see the effect immediately after writing the code. However, I finally chose to write the implementation Note of the snake first. If I write the implementation of the world first, I will not be able to complete it according to my current ideas. Because there is no snake, some of the Code in the world is incomplete. After reading the effect of food, I still have to write the implementation of the snake, and then I have to modify some of the Code in the world to view the effect of the snake. I can't afford it. Therefore, I plan to write down the implementation of food and snakes, and finally view the Running Effect in a unified manner.
Snakes and food have to be created in the world, so the code is similar.
Snake * SNK_CreateSnake(World *world, int size, int x, int y){ Snake *snake; if (world == 0) return 0; if ((snake = (Snake *)SDL_malloc(sizeof(Snake))) == 0) return 0; INIT_SNAKE(world, size, x, y); SNK_GrowSnake(snake); return snake;}
Macro INIT_SNAKE is used to initialize the Snake struct.
The SNK_GrowSnake function is used to add the length of a snake. Because a snake is created with only a snake header, I must add a snake tail to it again. If there is only a snake head, it can also run. This is just a simulation, not a real life form. But this is a malformed snake. I still want it to be normal and conform to conventional thinking.
The snake body section, so we can see that I used a one-way linked list in the header file to represent the snake body. When I destroy a snake, I need to traverse the entire linked list and release each body node in turn.
void SNK_DestroySnake(Snake *snake){ struct Body *body; if (snake != 0) { if ((body = snake->body)) REMOVE_BODY(body); SDL_free(snake); snake = 0; }}
Macro REMOVE_BODY is used to traverse a table and release the body node. I define it as a macro, which seems to be a bit of a review. This is mainly because I have to define an APPEND_BODY macro to add a snake's body node. Therefore, to correspond to adding a node, I have defined the macro to remove the node.
The position of the mobile snake is divided into two parts: the mobile Snake Head and the mobile snake body. The main reason is that I didn't regard the snake head as a part of the body, because the body can grow, but the snake head cannot grow.
void SNK_MoveSnake(Snake *snake){ struct Body *body; if (snake != 0) { MOVE_SNAKE(snake); for (body = snake->body; body; body = body->next) { MOVE_SNAKE(body); body->direction = (body->next != 0) ? body->next->direction : snake->direction; } }}
Snake indicates the snake Head, MOVE_SNAKE (snake) indicates the position of the snake head to be moved, and MOVE_SNAKE (body) indicates the position of the body to be moved.
You need to move the body through a calendar table, but I don't know if anyone can understand it? The direction of the current body node is equal to the direction of the next body node. What does this mean?
My analysis on snakes is that snakes only append nodes at the end. If the snake-> body points to the first node first and the first node points to the second node second, then I will traverse the third node twice from snake> body, and traverse the third node three times from snake> body. So I changed this inefficient behavior. I asked snake-> body to always point to the last body node, so when a new body node is appended, you can directly append it, instead of having to traverse a table.
Therefore, this for loop is actually traversed from the end of the snake to the head of the snake. When the direction of the Snake Head changes, the body changes with the head of the snake, and the tail changes with the body. This is the key for snakes to turn freely.
The next step is to draw a snake. like food, I use a series of rectangles to represent a snake.
void SNK_DrawSnake(Snake *snake){ SDL_Rect rect; struct Body *body; if (snake != 0) { rect.x = snake->x; rect.y = snake->y; rect.w = rect.h = snake->size; if (((snake->world != 0) ? (snake->world->render != 0) : 0)) { SDL_SetRenderDrawColor(snake->world->render, snake->color.r, snake->color.g, snake->color.b, snake->color.a); SDL_RenderDrawRect(snake->world->render, &rect); for (body = snake->body; body; body = body->next) { rect.x = body->x; rect.y = body->y; SDL_RenderDrawRect(snake->world->render, &rect); } } }}
The growth of snakes has two meanings: when there is no tail, the growth is the tail. When there is a tail, the body grows.
void SNK_GrowSnake(Snake *snake){ struct Body *body; if (snake != 0) { if ((body = (struct Body *)SDL_malloc(sizeof(struct Body))) == 0) return; if (snake->body == 0) { APPEND_BODY(snake, body); } else { APPEND_BODY(snake->body, body); } }}
The next step is to check the collision function, which has two main purposes: 1. When the rect parameter is the position of the Snake head, it is used to check whether the Snake Head is biting its own body. 2. When the rect parameter is a food location, it is used to detect whether the body has encountered food. If you bite yourself or encounter food, 1 is returned; otherwise, 0 is returned.
int SNK_HasIntersection(Snake *snake, SDL_Rect rect){ SDL_Rect bodyrect; struct Body *body; if (snake != 0) { bodyrect.w = bodyrect.h = snake->size; for (body = snake->body; body; body = body->next) { bodyrect.x = body->x; bodyrect.y = body->y; if (SDL_HasIntersection(&bodyrect, &rect) != 0) return 1; } } return 0;}
In the header file, I defined two statuses of a snake: Dead or movable. This function is used to detect the status of a snake. If the returned result is "Snake _ died", it indicates that the snake is dead. If the returned result is "Snake _ movable", it indicates that the snake is in a normal state and can be moved freely. If the returned value is "0", it indicates that the snake has reached the boundary of. I have not implemented the function of returning a snake from one side to the other, nor have I specified that the snake will die when it hits the wall. Keep everything as simple as possible!
int SNK_GetSnakeStatus(Snake *snake){ SDL_Rect headrect; if (((snake != 0) ? (snake->world != 0) : 0)) { headrect.w = (snake->x > 0 && snake->x < snake->world->w); headrect.h = (snake->y > 0 && snake->y < snake->world->h); if (headrect.w && headrect.h) { headrect.x = snake->x; headrect.y = snake->y; headrect.w = headrect.h = snake->size; if (SNK_HasIntersection(snake, headrect) != 0) return SNAKE_DIED; return SNAKE_MOVABLE; } else { switch (snake->direction) { case SNAKE_UP: headrect.x = (snake->y > 0); break; case SNAKE_DOWN: headrect.x = ((snake->y + snake->size) < snake->world->h); break; case SNAKE_LEFT: headrect.x = (snake->x > 0); break; case SNAKE_RIGHT: headrect.x = ((snake->x + snake->size) < snake->world->w); break; } return ((headrect.x != 0) ? SNAKE_MOVABLE : 0); } } return 0;}
Here, the switch statement only enters when the snake encounters the boundary of the world. This section mainly aims to implement a function: When the snake encounters the boundary of the world, the snake cannot move forward any more, but the snake can turn again.
Not finished, to be continued!