一, 游戏规则:
- 蛇的初始长度为 SNAKE_LEN , 蛇按照原来的方向移动, 如果有键盘输入按照键盘改变方向。(反向无效, 例如: 方向为向上时按向下无效)
- 如果吃到食物则变长一个结点,得一分。 如果没有则继续移动
- 移动范围为 COLUMNS*ROWS 个格子
- 结束游戏条件: (1)自食 (2)蛇头碰到墙
二, 数据结构和宏
#define ROWS 15 //行 对应 Y 轴 #define COLUMNS 30 //列 对应 X 轴 #define SNAKE_LEN 4 //蛇的初始长度 #define LEFT 75 #define UP 72 #define RIGHT 77 #define DOWN 80
贪吃蛇用链表来表示:
typedef struct snake{ COORD cor; //坐标 struct snake *next; }Snake;
Snake *head = NULL;
COORD Food; //食物的坐标
用到了一个windows API定义的结构 COORD ,正如其名字coordinate(坐标)一样,这是一个存储二维坐标的结构体。其实也就是坐标:
typedef struct _COORD { SHORT X; SHORT Y; } COORD, *PCOORD;
三, 用函数实现功能模块
0. 主函数中给出了游戏流程:
1 int main(void){ 2 int score = 0, direction = UP; //初始方向向上 3 COORD edge; 4 edge.X = 0; 5 edge.Y = ROWS+1; 6 init_game(); //初始化游戏界面 7 for(; ;){ 8 direction = get_dir(direction); 9 score += move_snake(direction); //贪吃蛇做下一步的动作 10 Sleep(500); //延迟 x ms 11 if(!is_alive()) 12 break; 13 } 14 gotoxy(edge); 15 freeSnake(); 16 printf(" Game Over ! Your score is : %d ", score); 17 return 0; 18 }
1. 首先初始化游戏界面:
/* 初始化游戏界面 */ int init_game(void){ int i,j; COORD snake_body; head = (Snake *)malloc(sizeof(Snake)); head->cor.Y = ROWS/2; //初始化蛇头位置为地图中心(附近) head->cor.X = COLUMNS/2; head->next = NULL; Snake *p = head; for(i = 1; i<SNAKE_LEN; i++){ //初始化蛇身长度为4 p->next = (Snake *)malloc(sizeof(Snake)); p = p->next; p->cor.X = head->cor.X; p->cor.Y = head->cor.Y + i; p->next = NULL; } for(i=0; i<ROWS; i++){ for(j=0; j<COLUMNS; j++){ if(i==0 || i==ROWS-1 || j==0 || j==COLUMNS-1) printf("#"); else printf(" "); } putchar(' '); } p = head; while(p){ gotoxy(p->cor); printf("*"); p = p->next; } creatFood(); gotoxy(Food); printf("@"); return 1; }
用到了gotoxy()函数,TC自带(不知道TC是啥),所以自己写一个,顾名思义就是一个转到x,y坐标的一个函数。
void gotoxy(COORD pt) { HANDLE hout; hout = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleCursorPosition(hout, pt); }
HANDLE(句柄)在windows编程中是一个十分重要的概念。在window编程中,对于一个Object(对象)我们只能通过Handle来访问它。觉得不好理解的同学把句柄当作指针来看待就好了。
GetStdHandle函数获取一个指向特定标准设备的句柄,包括标准输入,标准输出和标准错误。STD_OUTPUT_HANDLE正是代表标准输出(也就是显示屏)的宏。
SetConsoleCursorPosition函数用于设置控制台光标的位置。
2. 获取方向函数 get_dir ( ) :
/* 考虑蛇原来的方向的反方向无效 */ int get_dir(int old_dir){ int new_dir = old_dir; //如果没有键盘被按下则按照 old_dir 返回 if(kbhit()){ //kbhit() 函数功能: 判断有无键盘被按下,如果有返回1,没有返回0 getch(); new_dir = getch(); //getch() 要使用两次 if(abs(new_dir - old_dir) == 2 || abs(new_dir - old_dir) == 8) //反方向无效, 参照键盘方向键的宏的值 new_dir = old_dir; } return new_dir; }
abs()是绝对值函数, 方向的判断可看上下左右的宏值。 左(75)和右(77)差2, 上(72)和下(80)差8.
3. 获取了方向,就要按照方向做下一步的移动。
按照获得的 direction 改变 head->cor 的坐标, 用gotoxy 在新的 head 位置打印蛇符号。
cor_cmp(COORD pt1, COORD pt2) 函数判断蛇头是否碰到食物或墙。
creatFood() 函数随机生成新的食物坐标, 再用 gotoxy 打印食物符号。
int move_snake(int direction){ Snake *p = head; //保存原来的 蛇头 COORD tail = head->cor, temp; //根据键盘改变蛇头的坐标 switch(direction){ case UP: head->cor.Y--; break; case DOWN: head->cor.Y++; break; case RIGHT: head->cor.X++; break; case LEFT: head->cor.X--; break; } gotoxy(head->cor); //打印新的蛇头 printf("*"); for(p = head->next; p != NULL; p = p->next){ //更新蛇身坐标 temp = p->cor; p->cor = tail; // 前面的和后面的坐标互换, p再移到后面的 tail = temp; } /* 如果吃到了食物 */ if(cor_cmp(Food, head->cor)){ //比较坐标函数 //添加头部, 保留尾部 for(p = head->next; p->next != NULL; p = p->next) //找到尾部 ; p->next = (Snake *)malloc(sizeof(Snake)); p->next->cor = tail; p->next->next = NULL; creatFood(); //生成新的食物 gotoxy(Food); printf("@"); return 1; } else{ //head在前已更改, 后面的链表改成前一链表的坐标 gotoxy(tail); printf(" "); return 0; } }
因为需要比较很多次坐标, 所以写成比较函数:
int cor_cmp(COORD pt1, COORD pt2) //可见COORD的方便 { return (pt1.X == pt2.X&&pt1.Y == pt2.Y); }
随机生成食物函数 creatFood ( ) :
COORD creatFood(){ Snake *p = NULL; int in_snake = 0; //判断食物是否落在了蛇身上, 默认为否 0 srand((unsigned int)time(NULL)); do{ in_snake = 0; Food.X = rand() % COLUMNS-1; Food.Y = rand() % ROWS-1; for(p = head; p!= NULL; p = p->next){ //判断食物是否落在了蛇 if(cor_cmp(p->cor, Food)) in_snake = 1; } }while(Food.X == 0 || Food.Y ==0 || in_snake); return Food; }
如果坐标落到墙或蛇身上,则重新生成新的Food。 不过这个程序有 Bug , 吃了几次食物后,食物就从图中消失了, 找不到是什么原因。
最后加上判断蛇的死活函数 is_alive() :
int is_alive(){ int eat_self = 0; Snake *p = head->next->next; //从第三结点与头结点比较坐标 while(p){ if(cor_cmp(head->cor, p->cor)){ eat_self = 1; break; } p = p->next; } if(head->cor.X == 0 || head->cor.X == COLUMNS-1 || head->cor.Y == 0 || head->cor.Y == ROWS-1 || eat_self) return 0; return 1; }
游戏结束, 销毁链表 :
void freeSnake(){ Snake *p = NULL, *q; for(p = head; p!=NULL; p = q){ q = p->next; free(p); } }
最后给上代码 :
1 #include<stdio.h> 2 #include<windows.h> 3 #include<time.h> 4 5 #define ROWS 10 //行 对应 Y 轴 6 #define COLUMNS 10 //列 对应 X 轴 7 #define SNAKE_LEN 4 8 #define LEFT 75 9 #define UP 72 10 #define RIGHT 77 11 #define DOWN 80 12 13 typedef struct snake{ 14 COORD cor; //坐标 15 struct snake *next; 16 }Snake; 17 18 Snake *head; 19 COORD Food; 20 21 //函数声明 22 void gotoxy(COORD pt); 23 COORD creatFood(void); 24 int move_snake(int); 25 void init_game(void); 26 int cor_cmp(COORD pt1, COORD pt2); 27 int is_alive(void); 28 int get_dir(int ); 29 void freeSnake(void); 30 31 int main(void){ 32 int score = 0, direction = UP; //初始方向向上 33 COORD edge; 34 edge.X = 0; 35 edge.Y = ROWS+1; 36 init_game(); //初始化游戏界面 37 for(; ;){ 38 direction = get_dir(direction); 39 score += move_snake(direction); //贪吃蛇做下一步的动作 40 Sleep(500); //延迟 x ms 41 if(!is_alive()) 42 break; 43 } 44 gotoxy(edge); 45 freeSnake(); 46 printf(" Game Over ! Your score is : %d ", score); 47 return 0; 48 } 49 50 void freeSnake(){ 51 Snake *p = NULL, *q; 52 for(p = head; p!=NULL; p = q){ 53 q = p->next; 54 free(p); 55 } 56 } 57 58 int is_alive(){ 59 int eat_self = 0; 60 Snake *p = head->next->next; //从第三结点与头结点比较坐标 61 while(p){ 62 if(cor_cmp(head->cor, p->cor)){ 63 eat_self = 1; 64 break; 65 } 66 p = p->next; 67 } 68 if(head->cor.X == 0 || head->cor.X == COLUMNS-1 || head->cor.Y == 0 || head->cor.Y == ROWS-1 || eat_self) 69 return 0; 70 return 1; 71 } 72 73 /* 考虑蛇原来的方向的反方向无效 */ 74 int get_dir(int old_dir){ 75 int new_dir = old_dir; //如果没有键盘被按下则按照 old_dir 返回 76 if(kbhit()){ //kbhit() 函数功能: 判断有无键盘被按下,如果有返回1,没有返回0 77 getch(); 78 new_dir = getch(); //getch() 要使用两次 79 if(abs(new_dir - old_dir) == 2 || abs(new_dir - old_dir) == 8) //反方向无效, 参照键盘方向键的宏的值 80 new_dir = old_dir; 81 } 82 return new_dir; 83 } 84 //map[ROWS][COLUMNS] = {0}; //地图 85 /* 初始化游戏界面 */ 86 void init_game(void){ 87 int i,j; 88 COORD snake_body; 89 head = (Snake *)malloc(sizeof(Snake)); 90 head->cor.Y = ROWS/2; //初始化蛇头位置 91 head->cor.X = COLUMNS/2; 92 head->next = NULL; 93 Snake *p = head; 94 for(i = 1; i<SNAKE_LEN; i++){ //蛇身长度为4 95 p->next = (Snake *)malloc(sizeof(Snake)); 96 p = p->next; 97 p->cor.X = head->cor.X; 98 p->cor.Y = head->cor.Y + i; 99 p->next = NULL; 100 } 101 for(i=0; i<ROWS; i++){ 102 for(j=0; j<COLUMNS; j++){ 103 if(i==0 || i==ROWS-1 || j==0 || j==COLUMNS-1) 104 printf("#"); 105 else 106 printf(" "); 107 } 108 putchar(' '); 109 } 110 p = head; 111 while(p){ 112 gotoxy(p->cor); 113 printf("*"); 114 p = p->next; 115 } 116 creatFood(); 117 gotoxy(Food); 118 printf("@"); 119 } 120 121 int cor_cmp(COORD pt1, COORD pt2) //坐标比较函数 相等返回1,否则返回0 122 { 123 return (pt1.X == pt2.X && pt1.Y == pt2.Y); 124 } 125 126 void gotoxy(COORD pt) //坐标移动到新的X,Y 127 { 128 HANDLE hout; 129 hout = GetStdHandle(STD_OUTPUT_HANDLE); 130 SetConsoleCursorPosition(hout, pt); 131 } 132 133 COORD creatFood(){ 134 COORD food; 135 Snake *p = NULL; 136 int in_snake = 0; //判断食物是否落在了蛇身上, 默认为否 0 137 srand((unsigned int)time(NULL)); 138 do{ 139 in_snake = 0; 140 food.X = rand() % COLUMNS-1; 141 food.Y = rand() % ROWS-1; 142 for(p = head; p!= NULL; p = p->next){ //判断食物是否落在了蛇 143 if(cor_cmp(p->cor, food)) 144 in_snake = 1; 145 } 146 }while(food.X == 0 || food.Y ==0 || in_snake); 147 Food = food; 148 } 149 150 int move_snake(int direction){