c-linux贪吃蛇

24 篇文章 0 订阅

以前大学时候用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 719 22:31 snake
-rwxrwxrwx 1 root root  8171 719 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;
		}
	}
}





  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小乌鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值