以前大学时候用MFC写过贪吃蛇,现在完全忘了,哈哈。自从搞跨平台以来对linux比较喜欢,不断学习和练习c语言。本次借助curses库控制终端显示画布实现贪吃蛇。
1.实现主要通过Snake结构体,记录每个节点的x和y坐标。每个节点可以指向下一个节点。这样基于蛇头head节点就可以遍历整条蛇。
2.蛇的移动通过在当前方向前面加一个节点,如果加的位置是食物那么就不用删尾节点。否则删除最后尾节点,就实现了所有节点向前移一位。
3.定义两个线程,一个用了固定节拍,每个节拍按当前方向移动一位蛇,然后执行画布绘图。另一个线程监听用户按键,把上下左右处理成方向变量值,供节拍线程在方向加节点。
编译命令
[root@zlzlinux zhanglianzhu]#
[root@zlzlinux zhanglianzhu]#
[root@zlzlinux zhanglianzhu]#
[root@zlzlinux zhanglianzhu]# cd /zlz/snake
[root@zlzlinux snake]# ll
总用量 32
-rwxr-xr-x 1 root root 22688 7月 19 22:31 snake
-rwxrwxrwx 1 root root 8171 7月 19 22:30 snake.c
[root@zlzlinux snake]# gcc snake.c -o snake -lcurses -lpthread
snake.c: 在函数‘main’中:
snake.c:130:30: 警告:传递‘pthread_create’的第 3 个参数时在不兼容的指针类型间转换 [-Wincompatible-pointer-types]
pthread_create(&tick, NULL, TickDo, NULL);
^~~~~~
In file included from snake.c:7:
/usr/include/pthread.h:236:15: 附注:需要类型‘void * (*)(void *)’,但实参的类型为‘void (*)()’
void *(*__start_routine) (void *),
~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~
snake.c:132:33: 警告:传递‘pthread_create’的第 3 个参数时在不兼容的指针类型间转换 [-Wincompatible-pointer-types]
pthread_create(&control, NULL, ControlDirection, NULL);
^~~~~~~~~~~~~~~~
In file included from snake.c:7:
/usr/include/pthread.h:236:15: 附注:需要类型‘void * (*)(void *)’,但实参的类型为‘void (*)()’
void *(*__start_routine) (void *),
~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~
[root@zlzlinux snake]# ./snake
[root@zlzlinux snake]#
//编译命令
//gcc snake.c -o snake -lcurses -lpthread
#include <unistd.h>
#include <stdio.h>
#include <curses.h>
#include <stdlib.h>
#include <pthread.h>
//按键方向
#define UP 1
#define DOWN -1
#define LEFT 2
#define RIGHT -2
//最大x和y
#define MaxX 70
#define MaxY 30
//统计刷新次数
int refreshNum = 0;
//一个蛇节点的结构体
struct Snake {
int x;
int y;
struct Snake* next;
};
//头节点,通过头遍历整个蛇链条
struct Snake* head = NULL;
int key;
int dir;
//食物节点
struct Snake food;
//随机产生食物
void MakeFood();
/// <summary>
/// 判断当前x和y是否有蛇节点
/// </summary>
/// <param name="x">x</param>
/// <param name="y">y</param>
/// <returns>头返回1,中间返回2,尾返回3</returns>
int HasNode(int x, int y);
/// <summary>
/// 判断当前x和y是否有食物
/// </summary>
/// <param name="x">x</param>
/// <param name="y">y</param>
/// <returns></returns>
int HasFood(int x, int y);
/// <summary>
/// 绘制画布
/// </summary>
void Draw();
/// <summary>
/// 在当前方位前面添加一个节点
/// </summary>
void AddNode();
/// <summary>
/// 初始化蛇
/// </summary>
void InitSnake();
/// <summary>
/// 删除节点
/// </summary>
void DeleteNode();
/// <summary>
/// 判断是蛇否死亡
/// </summary>
/// <returns></returns>
int SnakeDie();
/// <summary>
/// 移动蛇
/// </summary>
void MoveSnake();
/// <summary>
/// 执行节拍
/// </summary>
void TickDo();
/// <summary>
/// 按键转换
/// </summary>
/// <param name="direct">方向</param>
void TransKey(int direct);
/// <summary>
/// 控制方向
/// </summary>
void ControlDirection();
/// <summary>
/// 执行入口
/// </summary>
/// <param name="count"></param>
/// <param name="args"></param>
/// <returns></returns>
int main(int count, char* args[])
{
//定义节拍线程,按节拍移动蛇和重绘
pthread_t tick;
//定义按键控制线程,处理按键
pthread_t control;
//初始化curses
initscr();
keypad(stdscr, 1);
noecho();
//初始化蛇对象
InitSnake();
//绘图
Draw();
//开启节拍线程
pthread_create(&tick, NULL, TickDo, NULL);
//开启控制线程
pthread_create(&control, NULL, ControlDirection, NULL);
//阻塞
while (1);
getch();
endwin();
return 0;
}
//随机产生食物
void MakeFood()
{
//随机坐标
int x = rand() % (MaxX - 2);
int y = rand() % (MaxY - 2);
food.x = x + 1;
food.y = y + 1;
food.next = NULL;
}
/// <summary>
/// 判断当前x和y是否有蛇节点
/// </summary>
/// <param name="x">x</param>
/// <param name="y">y</param>
/// <returns>头返回1,中间返回2,尾返回3</returns>
int HasNode(int x, int y)
{
//从蛇头开始寻找
struct Snake* p = head;
//如果是蛇头返回1
if (head->x == x && head->y == y)
{
return 1;
}
//遍历一直找到蛇尾
while (p != NULL) {
if (p->x == x && p->y == y) {
//是蛇尾返回3
if (p->next == NULL)
{
return 3;
}
//否则是蛇身
return 2;
}
p = p->next;
}
//其他就不是蛇节点
return 0;
}
/// <summary>
/// 判断当前x和y是否有食物
/// </summary>
/// <param name="x">x</param>
/// <param name="y">y</param>
/// <returns></returns>
int HasFood(int x, int y)
{
//是食物坐标就返回1
if (food.x == x && food.y == y) {
return 1;
}
return 0;
}
/// <summary>
/// 绘制画布
/// </summary>
void Draw()
{
int row;
int col;
//移动光标到00开始绘制
move(0, 0);
//绘制边框和蛇
for (row = 0; row <= MaxY; row++) {
//顶部先
if (row == 0) {
//画顶部横线
for (col = 0; col < MaxX; col++) {
printw("-");
}
//右上角输出刷新次数
printw("%d", refreshNum);
refreshNum++;
//换行
printw("\n");
}
//中间的蛇
if (row > 0 && row < MaxY) {
for (col = 0; col <= MaxX; col++) {
//两边的边框
if (col == 0 || col == MaxX) {
printw("|");
}
//蛇头节点
else if (HasNode(col, row) == 1) {
printw("#");
}
//蛇身节点
else if (HasNode(col, row) == 2) {
printw("O");
}
//蛇尾节点
else if (HasNode(col, row) == 3) {
printw("o");
}
//食物
else if (HasFood(col, row)) {
printw("*");
}
//空白
else {
printw(" ");
}
}
printw("\n");
}
//底部线
if (row == MaxY) {
for (col = 0; col < MaxX; col++) {
printw("-");
}
printw("\n");
printw("write by zlz walk in lossing on centos !\n");
}
}
}
/// <summary>
/// 在当前方位前面添加一个节点
/// </summary>
void AddNode()
{
struct Snake* new = (struct Snake*)malloc(sizeof(struct Snake));
switch (dir) {
//在上添加
case UP:
new->x = head->x;
new->y = head->y - 1;
break;
//在下添加
case DOWN:
new->x = head->x;
new->y = head->y + 1;
break;
//在左添加
case LEFT:
new->x = head->x - 1;
new->y = head->y;
break;
//在由添加
case RIGHT:
new->x = head->x + 1;
new->y = head->y;
break;
}
//新节点指向蛇头
new->next = head;
//头节点等于新节点
head = new;
}
/// <summary>
/// 初始化蛇
/// </summary>
void InitSnake()
{
struct Snake* p;
//初始化方向
dir = DOWN;
//如果头不为空,释放之前的蛇链,从头一个个释放
while (head != NULL) {
p = head;
head = head->next;
free(p);
}
//初始化食物
MakeFood();
//申请一个新的头对象
head = (struct Snake*)malloc(sizeof(struct Snake));
//从上中间开始往下走
head->x = MaxX / 2;
head->y = 3;
head->next = NULL;
//默认的节点
AddNode();
AddNode();
AddNode();
}
/// <summary>
/// 删除节点
/// </summary>
void DeleteNode()
{
struct Snake* p;
//记录最后一个节点的前一个节点(倒数第二个)
struct Snake* pPre = NULL;
p = head;
//一直找到next为空的节点,即最后节点
while (p->next != NULL)
{
pPre = p;
p = p->next;
}
//这时候把倒数第二个节点下一个指向空
if (pPre != NULL)
{
pPre->next = NULL;
}
//释放最后的节点
free(p);
}
/// <summary>
/// 判断是蛇否死亡
/// </summary>
/// <returns></returns>
int SnakeDie()
{
struct Snake* p = head->next;
//头碰到边界就死了
if ((head->x < 1) || (head->x > MaxX - 1) || (head->y < 1) || (head->y > MaxY - 1)) {
return 1;
}
//检查每个非头节点是否和头节点重合,重合就死了
while (p->next != NULL) {
//头和身体重叠也死亡
if (p->x == head->x && p->y == head->y) {
return 1;
}
p = p->next;
}
//否则就没死
return 0;
}
/// <summary>
/// 移动蛇
/// </summary>
void MoveSnake()
{
//先到前进方向加一个节点
AddNode();
//有食物就初始化食物(食物被吃)
if (HasFood(head->x, head->y)) {
MakeFood();
}
//如果加完节点的头不是食物,就删蛇最后一个节点(蛇前进一步)
else {
DeleteNode();
}
//死亡了重新开始
if (SnakeDie())
{
InitSnake();
}
}
/// <summary>
/// 执行节拍
/// </summary>
void TickDo()
{
//死循环移动蛇和刷新画布
while (1) {
//移动蛇异常
MoveSnake();
//刷新画布
Draw();
//休眠
usleep(150000);
//算下面板
refresh();
//休眠
usleep(150000);
}
}
/// <summary>
/// 按键转换
/// </summary>
/// <param name="direct">方向</param>
void TransKey(int direct)
{
if (abs(dir) != abs(direct)) {
dir = direct;
}
}
/// <summary>
/// 控制方向
/// </summary>
void ControlDirection()
{
//死循环监测按键
while (1) {
key = getch();
switch (key)
{
case KEY_DOWN:
TransKey(DOWN);
break;
case KEY_UP:
TransKey(UP);
break;
case KEY_LEFT:
TransKey(LEFT);
break;
case KEY_RIGHT:
TransKey(RIGHT);
break;
}
}
}