本文目的是了解curses光标库的主要函数以及如何使用它制作一个贪吃蛇小游戏。
(一)curses库的简要介绍
1.安装curses库
- 执行命令
sudo apt-get install libncurses5-dev
安装curses库 - 查看curses库的头文件安装路径
使用命令whereis curses
查看curses的头文件路径
2.curses库的介绍
- curses常用函数
函数名 | 功能 |
---|---|
initscr() | 初始化curses库和ttty(在开始curses编程之前,必须使用initscr()这个函数来开启curses模式) |
endwin() | 关闭curses并重置tty(结束curses编程时,最后调用的一个函数) |
move(y,x) | 将游标移动至 x,y 的位置 |
getyx(win,y,x) | 得到目前游标的位置(请注意!是 y,x 而不是&y,&x) |
clear() and erase() | 将整个萤幕清除(请注意配合refresh() 使用) |
echochar(ch) | 显示某个字符 |
addch(ch) | 在当前位置画字符ch |
mvaddch(y,x,ch) | 在(x,y) 上显示某个字元。相当於呼叫move(y,x);addch(ch); |
addstr(str) | 在当前位置画字符串str |
mvaddstr(y,x,str) | 在(x,y) 上显示一串字串。相当於呼叫move(y,x);addstr(str); |
printw(format,str) | 类似 printf() ,以一定的格式输出至萤幕 |
mvprintw(y,x,format,str) | 在(x,y) 位置上做 printw 的工作。相当於呼叫move(y,x);printw(format,str); |
getch() | 从键盘读取一个字元。(注意!传回的是整数值) |
getstr() | 从键盘读取一串字元。 |
scanw(format,&arg1,&arg2…) | 如同 scanf,从键盘读取一串字元。 |
beep() | 发出一声哔声 |
box(win,ch1,ch2) | 自动画方框 |
refresh() | 使屏幕按照你的意图显示。比较工作屏幕和真实屏幕的差异,然后refresh通过终端驱动送出那些能使真实屏幕与工作屏幕一致的字符和控制码。(工作屏幕就像磁盘缓存,curses中的大部分的函数都只对它进行修改) |
standout() | 启动standout模式(一般使屏幕发色) |
standend() | 关闭standout模式 |
(二)使用curses库编写贪吃蛇小游戏
1.源程序
- main.c
//用方向键控制蛇的方向
#include"Greedy_snake.h"
int main()
{
initscr();
initGame();
signal(SIGALRM, show);
getOrder();
endwin();
return 0;
}
- Greedy_snake.h
#ifndef __GREEDY_SNAKE_H
#define __GREEDY_SNAKE_H
#include <stdio.h>
#include <stdlib.h>
#include <curses.h>
#include <signal.h>
#include <sys/time.h>
#include <time.h>
#define NUM 60
struct direct //用来表示方向的
{
int cx;
int cy;
};
typedef struct node //链表的结点
{
int cx;
int cy;
struct node *back;
struct node *next;
}node;
void initGame(); //初始化游戏
int setTicker(int); //设置计时器
void show(); //显示整个画面
void showInformation(); //显示游戏信息(前两行)
void showSnake(); //显示蛇的身体
void getOrder(); //从键盘中获取命令
void over(int i); //完成游戏结束后的提示信息
void creatLink(); //(带头尾结点)双向链表以及它的操作
void insertNode(int x, int y);
void deleteNode();
void deleteLink();
int ch; //输入的命令
int hour, minute, second; //时分秒
int length, tTime, level; //(蛇的)长度,计时器,(游戏)等级
struct direct dir, food; //蛇的前进方向,食物的位置
node *head, *tail; //链表的头尾结点
#endif
- Greedy_snake.c
#include"Greedy_snake.h"
void initGame()
{
cbreak(); //把终端的CBREAK模式打开
noecho(); //关闭回显
curs_set(0); //把光标置为不可见
keypad(stdscr, true); //使用用户终端的键盘上的小键盘
srand(time(0)); //设置随机数种子
//初始化各项数据
hour = minute = second = tTime = 0;
length = 1;
dir.cx = 1;
dir.cy = 0;
ch = 'A';
food.cx = rand() % COLS;
food.cy = rand() % (LINES-2) + 2;
creatLink();
setTicker(20);
}
//设置计时器
int setTicker(int n_msecs)
{
struct itimerval new_timeset;
long n_sec, n_usecs;
n_sec = n_msecs / 1000 ;
n_usecs = ( n_msecs % 1000 ) * 1000L ;
new_timeset.it_interval.tv_sec = n_sec;
new_timeset.it_interval.tv_usec = n_usecs;
n_msecs = 1;
n_sec = n_msecs / 1000 ;
n_usecs = ( n_msecs % 1000 ) * 1000L ;
new_timeset.it_value.tv_sec = n_sec ;
new_timeset.it_value.tv_usec = n_usecs ;
return setitimer(ITIMER_REAL, &new_timeset, NULL);
}
void showInformation()
{
tTime++;
if(tTime >= 1000000)
tTime = 0;
if(1 != tTime % 50)
return;
move(0, 3);
//显示时间
printw("time: %d:%d:%d %c", hour, minute, second);
second++;
if(second > NUM)
{
second = 0;
minute++;
}
if(minute > NUM)
{
minute = 0;
hour++;
}
//显示长度,等级
move(1, 0);
int i;
for(i=0;i<COLS;i++)
addstr("-");
move(0, COLS/2-5);
printw("length: %d", length);
move(0, COLS-10);
level = length / 3 + 1;
printw("level: %d", level);
}
//蛇的表示是用一个带头尾结点的双向链表来表示的,
//蛇的每一次前进,都是在链表的头部增加一个节点,在尾部删除一个节点
//如果蛇吃了一个食物,那就不用删除节点了
void showSnake()
{
if(1 != tTime % (30-level))
return;
//判断蛇的长度有没有改变
bool lenChange = false;
//显示食物
move(food.cy, food.cx);
printw("@");
//如果蛇碰到墙,则游戏结束
if((COLS-1==head->next->cx && 1==dir.cx)
|| (0==head->next->cx && -1==dir.cx)
|| (LINES-1==head->next->cy && 1==dir.cy)
|| (2==head->next->cy && -1==dir.cy))
{
over(1);
return;
}
//如果蛇头砬到自己的身体,则游戏结束
if('*' == mvinch(head->next->cy+dir.cy, head->next->cx+dir.cx) )
{
over(2);
return;
}
insertNode(head->next->cx+dir.cx, head->next->cy+dir.cy);
//蛇吃了一个“食物”
if(head->next->cx==food.cx && head->next->cy==food.cy)
{
lenChange = true;
length++;
//恭喜你,通关了
if(length >= 50)
{
over(3);
return;
}
//重新设置食物的位置
food.cx = rand() % COLS;
food.cy = rand() % (LINES-2) + 2;
}
if(!lenChange)
{
move(tail->back->cy, tail->back->cx);
printw(" ");
deleteNode();
}
move(head->next->cy, head->next->cx);
printw("*");
}
void show()
{
signal(SIGALRM, show); //设置中断信号
showInformation();
showSnake();
refresh(); //刷新真实屏幕
}
void getOrder()
{
//建立一个死循环,来读取来自键盘的命令
while(1)
{
ch = getch();
if(KEY_LEFT == ch)
{
dir.cx = -1;
dir.cy = 0;
}
else if(KEY_UP == ch)
{
dir.cx = 0;
dir.cy = -1;
}
else if(KEY_RIGHT == ch)
{
dir.cx = 1;
dir.cy = 0;
}
else if(KEY_DOWN == ch)
{
dir.cx = 0;
dir.cy = 1;
}
setTicker(20);
}
}
void over(int i)
{
//显示结束原因
move(0, 0);
int j;
for(j=0;j<COLS;j++)
addstr(" ");
move(0, 2);
if(1 == i)
addstr("Crash the wall. Game over");
else if(2 == i)
addstr("Crash itself. Game over");
else if(3 == i)
addstr("Mission Complete");
setTicker(0); //关闭计时器
deleteLink(); //释放链表的空间
}
//创建一个双向链表
void creatLink()
{
node *temp = (node *)malloc( sizeof(node) );
head = (node *)malloc( sizeof(node) );
tail = (node *)malloc( sizeof(node) );
temp->cx = 5;
temp->cy = 10;
head->back = tail->next = NULL;
head->next = temp;
temp->next = tail;
tail->back = temp;
temp->back = head;
}
//在链表的头部(非头结点)插入一个结点
void insertNode(int x, int y)
{
node *temp = (node *)malloc( sizeof(node) );
temp->cx = x;
temp->cy = y;
temp->next = head->next;
head->next = temp;
temp->back = head;
temp->next->back = temp;
}
//删除链表的(非尾结点的)最后一个结点
void deleteNode()
{
node *temp = tail->back;
node *bTemp = temp->back;
bTemp->next = tail;
tail->back = bTemp;
temp->next = temp->back = NULL;
free(temp);
temp = NULL;
}
//删除整个链表
void deleteLink()
{
while(head->next != tail)
deleteNode();
head->next = tail->back = NULL;
free(head);
free(tail);
}
3.编译生成可执行文件
- 首先编译Greedy_snake.c生成Greedy_snake.o文件
使用命令gcc -c Greedy_snake.c
- 链接Greedy_snake.o文件和curses文件生成main可执行文件
使用命令gcc main.c Greedy_snake.o -l curses -o main
- 遇到的问题
- 链接生成可执行文件时使用命令
gcc main.c Greedy_snake.o -o main
出错
/tmp/ccDJGfKa.o:在函数‘main’中: main.c:(.text+0x5):对‘initscr’未定义的引用 main.c:(.text+0x32):对‘endwin’未定义的引用 Greedy_snake.o:在函数‘initGame’中: Greedy_snake.c:(.text+0x5):对‘cbreak’未定义的引用 Greedy_snake.c:(.text+0xa):对‘noecho’未定义的引用 Greedy_snake.c:(.text+0x14):对‘curs_set’未定义的引用 Greedy_snake.c:(.text+0x1b):对‘stdscr’未定义的引用 Greedy_snake.c:(.text+0x28):对‘keypad’未定义的引用 Greedy_snake.c:(.text+0x9a):对‘COLS’未定义的引用 Greedy_snake.c:(.text+0xb2):对‘LINES’未定义的引用 ························································· Greedy_snake.c:(.text+0x807):对‘waddnstr’未定义的引用 Greedy_snake.c:(.text+0x816):对‘stdscr’未定义的引用 Greedy_snake.c:(.text+0x82a):对‘waddnstr’未定义的引用 collect2: error: ld returned 1 exit status
- 原因:没有指定链接curses库,导致使用curses库中的函数时显示未定义的引用。
- 链接生成可执行文件时使用命令
(三)总结
通过调用curses光标库实现简单的贪吃蛇小游戏,我了解了程序是如何借助第三方库函数完成代码设计的以及优秀的代码库在编程中的重要性。
感谢以下文章及链接的启发:
Linux 环境下C语言编译实现贪吃蛇游戏
https://baike.baidu.com/item/curses/1630775?fr=aladdin