#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <windows.h>
#include <time.h>
#include <stdlib.h>
//蛇的狀態,U:上 ;D:下;L:左 R:右
#define U 1
#define D 2
#define L 3
#define R 4
#define EMPTY 0
#define BIT_SELF 1 //咬到蛇身
#define TOUCH_WALL 2 //碰到牆
#define TRUE 1
#define FALSE 0
typedef struct SNAKE //蛇身的一個節點
{
int x;
int y;
struct SNAKE *next;
}snake;
//head用於指向蛇的第一個節點,標識整條蛇,但不屬於蛇的節點
snake head, *temp_ptr, food_node;
//direction 鍵入方向
//game_over_reason 記錄遊戲失敗原因
//speed 移動速度
int direction = R, game_over_reason = EMPTY, speed = 100;
void locateAndPrint(int x, int y); //在光標位置輸出方塊
void locateAndClear(int x, int y); //在光標位置清除方塊
void creatMap(); //創建地圖
void createFood(); //創建食物
void intSnake(int snake_len, int start_x, int start_y); //初始化蛇身
void endGame(); //結束遊戲
void getEnteredDirection(); //獲取鍵入的方向
void snakeMove(); //蛇移動
void startGame(); //遊戲循環
/*在光標位置輸出方塊*/
void locateAndPrint(int x, int y)
{
//定位
COORD pos;
HANDLE hOutput;
pos.X = x;
pos.Y = y;
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(hOutput, pos);
//輸出
printf("■");
}
/*在光標位置清除方塊*/
void locateAndClear(int x, int y)
{
//定位
COORD pos;
HANDLE hOutput;
pos.X = x;
pos.Y = y;
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(hOutput, pos);
//輸出
printf(" ");
}
/*創建地圖*/
void creatMap()
{
//設定黑窗的大小
system("mode con cols=100 lines=30");
//注意這裏x+2 y+1,在黑框中就是這樣約定的,沒辦法
int i;
for (i = 0; i < 58; i += 2)//打印上下邊框
{
locateAndPrint(i, 0);
locateAndPrint(i, 26);
}
for (i = 1; i < 26; i++)//打印左右邊框
{
locateAndPrint(0, i);
locateAndPrint(56, i);
}
}
/*創建食物*/
void createFood()
{
//創建食物成功,跳出循環
int create_food_success_flag = TRUE;
//如果創建不成功,則重新重建
while (1) {
int rand_x, rand_y;
do {
//x隨機數 2-55 y隨機數 3-23
srand((int)time(NULL));
rand_x = rand() % 53 + 2;
rand_y = rand() % 21 + 3;
} while (rand_x % 2 != 0);//x需要是2的偶數,黑框中x和y的方塊有侷限性
//判斷生成的食物是否跟蛇身重疊
temp_ptr = head.next;
while (temp_ptr != NULL) {
if (temp_ptr->x == rand_x && temp_ptr->y == rand_y) {
//重疊,需要重新創建
create_food_success_flag = FALSE;
break;
}
temp_ptr = temp_ptr->next;
}
//food的座標跟蛇身重疊,重新生成food_node
if (FALSE == create_food_success_flag) {
continue;
}
//創建成功,在黑框中打印
create_food_success_flag = TRUE;
food_node.x = rand_x;
food_node.y = rand_y;
locateAndPrint(rand_x, rand_y);
break;
}
}
/*初始化蛇身*/
void intSnake(int snake_len, int start_x, int start_y) {
for (int i = 0; i < snake_len; i++) {
temp_ptr = (snake *)malloc(sizeof(snake));
temp_ptr->x = start_x;
temp_ptr->y = start_y;
start_x += 2;
//頭插法
temp_ptr->next = head.next;
head.next = temp_ptr;
//輸出初始蛇身
locateAndPrint((head.next)->x, (head.next)->y);
}
}
/*遊戲結束*/
void endGame()
{
//記錄得分
int snake_length = 0;
//釋放蛇身 malloc分配的資源
snake * temp_ptr2;//這裏爲了思路清晰,引入第三個變量temp_ptr2,輔助變量temp_ptr2可以用head.next來替代
temp_ptr = head.next;
while (temp_ptr != NULL) {
temp_ptr2 = temp_ptr->next;
free(temp_ptr);
temp_ptr = temp_ptr2;
snake_length++;
}
//清屏並輸出遊戲失敗原因
system("cls");
if (BIT_SELF == game_over_reason) {
printf("蛇頭與蛇身相碰,失敗!\n得分:%d\n", snake_length - 9);
}
else if (TOUCH_WALL == game_over_reason) {
printf("碰到牆壁,失敗!\n得分:%d\n", snake_length - 9);
}
getchar();
}
/*獲取鍵入的方向*/
void getEnteredDirection()
{
if (GetAsyncKeyState(VK_OEM_PLUS))
{
speed -= 25;
}
else if (GetAsyncKeyState(VK_OEM_MINUS))
{
speed += 25;
}
else if (GetAsyncKeyState(VK_UP) && direction != D)
{
direction = U;
}
else if (GetAsyncKeyState(VK_DOWN) && direction != U)
{
direction = D;
}
else if (GetAsyncKeyState(VK_LEFT) && direction != R)
{
direction = L;
}
else if (GetAsyncKeyState(VK_RIGHT) && direction != L)
{
direction = R;
}
}
/*蛇移動*/
void snakeMove()
{
//把原始的head.next的座標值存起來
int temp_x = head.next->x, temp_y = head.next->y;
//判斷方向,修改第一個節點的值
if (direction == R)
{
head.next->x += 2;
}
else if (direction == L) {
head.next->x -= 2;
}
else if (direction == U) {
head.next->y -= 1;
}
else if (direction == D) {
head.next->y += 1;
}
//case1.下一節點爲食物: 蛇身第一節點跟食物節點重合,在蛇身第二個節點新建節點
//case2.下一節點不爲食物:在蛇身第二個節點處新建節點
//故可合併
snake * temp = (snake *)malloc(sizeof(snake));
temp->x = temp_x;
temp->y = temp_y;
temp->next = head.next->next;
head.next->next = temp;
//判斷是否撞牆
if (head.next->x >= 58 || head.next->x <= 0 || head.next->y <= 0 || head.next->y >= 26) {
game_over_reason = BIT_SELF;
endGame();
}
//判斷下一個節點是否食物
if (
food_node.x == head.next->x &&
food_node.y == head.next->y
) {
//下一個節點是食物
//重新新建食物
createFood();
}
else {
//下一個節點不是食物,則移動蛇身
//在蛇前進的第一個位置打印方塊
locateAndPrint(head.next->x, head.next->y);
temp_ptr = head.next;
while (temp_ptr->next->next != NULL) {
//判斷是否咬到自己
if (
temp_ptr->next->next != NULL &&
temp_ptr->next->next->x == (head.next)->x &&
temp_ptr->next->next->y == (head.next)->y
) {
game_over_reason = BIT_SELF;
endGame();
}
temp_ptr = temp_ptr->next;
}
//清除蛇尾的節點
locateAndClear(temp_ptr->next->x, temp_ptr->next->y);
free(temp_ptr->next);//釋放蛇尾節點內存
temp_ptr->next = NULL;
}
}
/*遊戲循環*/
void startGame()
{
//初始方向,默認方向
direction = R;
while (1) {
//判斷鍵盤輸入的方向鍵,但注意“移動方向爲上時按下鍵不起作用”,左右方向同理
getEnteredDirection();
//顯示蛇身
snakeMove();//蛇身移動
Sleep(speed);
}
}
int main()
{
//創建地圖
creatMap();
//初始化蛇身
intSnake(8, 4, 3);
//創建食物
createFood();
//開始遊戲 循環不停的判斷鍵入的方向
startGame();
return 0;
}
六、總結: