原创首发于CSDN,转载请注明出处,谢谢!https://blog.csdn.net/weixin_46959681/article/details/117220108
为何是老古董 Ncurse ?
相比于已经作古的GTK、流行的QT、现今大规模换代升级的安卓嵌入式设备,Ncurse 无疑是个上古时代的产品(老古董中的老古董)。在如今的 Linux 环境下安装 Ncurse 使用最多的方面还是关于内核的 配置和裁剪。 【笔者的博文: 基于3代树莓派内核的编译、覆盖以及运行】
我们使用Nucrse的主要目的是聚焦于经典游戏贪吃蛇的实现,加强对于C语言本身的理解和使用。 对于其他细枝末节的地方不用进行太多的深究,只需在遇到关于Ncurse库的相关问题时查阅资料即可。
在实现贪吃蛇游戏时Ncurse库的优点如下:
- 自身所封装的库比C语言自带的库响应速度更快,如在键盘的输入扫描方面前者的
getch()
对比后者的scanf()
无需敲击回车键结束(测试代码ncurse1.c
); - 借助自身关于图形界面的函数实现游戏进度的刷新 —— 食物的位置随机生成、蛇身节点的赠多、键盘方向输入的感应,图形界面的刷新函数
move(0,0)
、refresh()
。
测试代码: ncurse1.c
/* ncurse1.c */
#include <curses.h> //ncurse的头文件。
int main()
{
char c;
int i = 0;
//ncurse界面的初始化函数。
initscr();
for(i=0;i<=2;i++)
{
c = getch();
printw("\n");
//ncurse下的打印函数。
printw("Input: %c\n",c);
}
//等待用户的输入,没有该函数程序会直接退出,看不到printw()的结果。
getch();
//调用程序退出函数,恢复shell终端的显示,没有该函数终端可能会乱码。
endwin();
return 0;
}
//输入指令测试 gcc ncurse1.c -lcurses
|
---|
![]() |
游戏的规划实现步骤
贪吃蛇游戏实现的整体规划:
- 初始的图形化界面;
- 贪吃蛇的初始状态(身体节点数、初始方向);
- 食物的初始位置设定与之后的随机生成;
- 贪吃蛇的移动和方向响应(身体节点的增加与删除);
- 吃食物检测(是否吃到食物);
- 吃食物长大(节点增加);
- 撞墙以及撞自己死亡后复活(贪吃蛇初始化);
- 全局观念:使用线程让地图刷新与方向转向并行运算。
思绪以及源代码中的小bug
虽然所有的贪吃蛇代码仅仅只有三百行不到的代码量,但就其中的模块封装数量而言涉及地图的初始化、食物的初始化随机生成、贪吃蛇的初始状态、贪吃蛇的移动、蛇身节点的增加与删除、键盘输入方向的响应、贪吃蛇的死亡复活等足足十二个模块的封装。
笔者在跟随陈立臣老师的二十五节小视频课程下来,完整一遍实现了贪吃蛇的代码流程。但在重新复盘时初步和后续依然偶尔也会被功能模块之间的复用关系搞的头晕,直到用草稿纸梳理了一遍模块关系才渐渐的明朗。尽管大家都说贪吃蛇小游戏都很简单写进简历上不来台面,但就笔者个人还是相信,将该游戏从0到1在不参考资料的情况下实现一遍,也能难倒很多人。
在实际的游戏运行过程中依然能发现一些小bug和功能性的不足,例如:
- 食物随机生成的位置与贪吃蛇身子的节点有时会重合;
- 功能方面没有暂停按钮,必须要一直玩游戏,体验感差了一筹。
待解决疑惑
在游戏实际的运行过程中,笔者观察到贪吃蛇食物的生成有时会在地图的行边界(row = 0,20)或列边界(column = 0,20)生成,这样实际运行的过程中会导致无法吃到食物。问题非常的清晰,就是关于食物生成的封装模块函数void initFood()
中随机函数rand()
的边界范围设置出现了问题。这个笔者在在CSDN里找个接近五到六篇博文,但是不论是实际的代码测试或是理论说明都没有很好的解答,后来经过搜索引擎搜索发现原来除了随机函数 rand()
之外,还有一个随机种子函数 srand()
。对于这两个函数目前大致有些眉目,但不是特别透彻,后面会再开一篇专门的博文心得。
- 随机函数 srand()、rand()。
贪吃蛇源代码 snake11.c
/* snake11.c */
#include <curses.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#define UP 1
#define DOWN -1
#define LEFT 2
#define RIGHT -2
struct Snake
{
int row;
int column;
struct Snake *next;
};
struct Snake *head = NULL;
struct Snake *tail = NULL;
struct Snake food;
int key; //方向键值。
int dir; //方向感应。
void initFood()
{
srand((unsigned)time(NULL));
int x = rand()%15+3;
int y = rand()%15+3;
food.row = x;
food.column = y;
}
int allSnakeNode(int i,int j)
{
struct Snake *p = NULL;
p = head;
while(p != NULL){
if(p->row == i && p->column == j){
return 1;
}
p = p->next;
}
return 0;
}
int setFood(int i, int j)
{
if(food.row==i && food.column==j)
{
return 1;
}
return 0;
}
void gameMap()
{
int row;
int column;
move(0,0);
for(row = 0;row < 20;row++){
if(row == 0){
for(column = 0;column < 20;column++){
printw("--");
}
printw("\n");
}
if(row >= 0 && row <= 19){
for(column = 0;column <= 20;column++){
if(column == 0 || column == 20){
printw("|");
}else if(allSnakeNode(row,column)){
printw("[]");
}else if(setFood(row,column)){
printw("##");
}
else{
printw(" ");
}
}
printw("\n");
}
if(row == 19){
for(column = 0;column < 20;column++){
printw("--");
}
printw("\n");
printw("Make by ZBB,key=%d,food.row=%d,food.column=%d\n",key,food.row,food.column);
}
}
}
void addNode()
{
struct Snake *new = (struct Snake *)malloc(sizeof(struct Snake));
new->next = NULL;
switch(dir){
case UP:
new->row = tail->row-1;
new->column = tail->column;
break;
case DOWN:
new->row = tail->row+1;
new->column = tail->column;
break;
case LEFT:
new->row = tail->row;
new->column = tail->column-1;
break;
case RIGHT:
new->row = tail->row;
new->column = tail->column+1;
break;
}
tail->next = new;
tail = new;
}
void initSnake()
{
struct Snake *p = NULL;
//贪吃蛇的初始方向:右。
dir = RIGHT;
while(head != NULL){
p = head;
head = head->next;
free(p);
}
initFood();
//贪吃蛇的初始位置。
head = (struct Snake *)malloc(sizeof(struct Snake));
head->row = 3;
head->column = 4;
head->next = NULL;
tail = head;
//蛇的初始状态有五个节点。
addNode();
addNode();
addNode();
addNode();
}
//移动过程中除了要增加节点,还要删除节点。
void deleteNode()
{
struct Snake *p = NULL;
p = head;
head = head->next;
free(p);
}
int snakeDie()
{
struct Snake *p = NULL;
p = head;
if(tail->row < 0||tail->row == 20||tail->column == 0||tail->column == 20)
{
return 1;
}
while(p->next != NULL){
if(p->row == tail->row && p->column == tail->column){
return 1;
}
p = p->next;
}
return 0;
}
void moveSnake()
{
addNode();
if(setFood(tail->row,tail->column)){
initFood();
}else{
deleteNode();
}
if(snakeDie())
{
initSnake();
}
}
void* refreshWindow()
{
while(1)
{
moveSnake();
gameMap();
refresh();
usleep(105000);
}
}
void turn(int direction)
{
if(abs(dir) != abs(direction))
{
dir =direction;
}
}
void* changeDir()
{
while(1)
{
key = getch();
switch(key)
{
//这四个为ncurses自带的表示方向的宏。
case KEY_DOWN:
turn(DOWN);
break;
case KEY_UP:
turn(UP);
break;
case KEY_LEFT:
turn(LEFT);
break;
case KEY_RIGHT:
turn(RIGHT);
break;
}
}
}
int main()
{
pthread_t t1;
pthread_t t2;
//三行代码初始化游戏。
initscr();
keypad(stdscr,1);
noecho();
initSnake();
gameMap();
pthread_create(&t1,NULL,refreshWindow,NULL);
pthread_create(&t2,NULL,changeDir,NULL);
while(1);
getch();
endwin();
return 0;
}
文章更新记录
- “为何是老古董 Ncurse ?”一节完成。「2021.5.31 13:26」
- “贪吃蛇源代码 snake11.c”一节完成。 「2021.5.31 18:41」
- “思绪以及源代码中的小bug”一节完成。 「2021.5.31 19:04」
- “待解决疑惑”一节完成。 「2021.5.31 19:06」
- 初步竣工。 「2021.5.31 19:11」
- 在“待解决疑惑”一节增加了一些内容。 「2021.5.31 21:55」
- 修改部分内容。 「2021.6.1 10:00」
P.S.1 这篇文章拖更一星期了…… 「2021.5.31 13:27」