游戏说明: linux环境下基于Ncurses图形库的C语言小游戏。
Ncurses介绍: Ncurses(new curses)是一套编程库,它提供了一系列的函数以便使用者调用它们去生成基于文本的用户界面。
Ncurses是一个能提供功能键定义(快捷键),屏幕绘制以及基于文本终端的图形互动功能的动态库。Ncurses用得最多的地方是linux内核编译之前的内核配置,Ncurses早已淡出舞台,甚至体验感完爆Ncurses的C图形库GTK、C++图形库QT也区趋于落伍嵌入式设备上的Android系统。
一、准备工作
在ubuntu上面安装Ncurses库,输入以下指令:
sudo apt-get install libncurses5-dev
二、Ncurses的上下左右键值获取
1 #include <curses.h>
2
3 int main()
4 {
5 initscr();//ncurses界面初始化函数
6 keypad(stdscr,1);//从stdscr中接受功能建,1表示接收
7
8 while(1){
9
10 int key = getch();//等待用户输入,如果没有这句话,程序就退出了,看不到运行结果
11
12 switch(key){
13 case KEY_DOWN://KEY_DOWN是Ncurses的一些宏定义
14 printw("DOWN\n");//在ncurses模式下的printf
15 break;
16 case KEY_UP:
17 printw("UP\n");
18 break;
19 case KEY_LEFT:
20 printw("LEFT\n");
21 break;
22 case KEY_RIGHT:
23 printw("RIGHT\n");
24 break;
25 }
26 }
27 endwin();//程序退出,调用该函数来恢复shell终端的显示,如果没有这句话,shell终端字乱码坏掉
28
29 return 0;
30 }
编译ncurse程序 -lcurses
gcc ncurses.c -lcurses
运行结果
三、地图规划
主要代码:
#include <curses.h>
void initNcurses()
{
initscr();//ncurses界面初始化函数
keypad(stdscr,1);//从stdscr中接受功能建,1表示接收
}
void gameMap()
{
int hang;
int lie;
for(hang=0;hang<=21;hang++){
if(hang==0||hang==21){
for(lie=0;lie<=21;lie++){
if(lie==0||lie==21){
printw("-");
}else{
printw("---");
}
}
printw("\n");
}
if(hang>0&&hang<21){
for(lie=0;lie<=21;lie++){
if(lie==0||lie==21){
printw("|");
}
else{
printw(" ");
}
}
printw("\n");
}
}
printw("By DaTou");
}
int main()
{
initNcurses();
gameMap();
getch();
endwin();//没有这行会破坏shell终端
return 0;
}
四、贪吃蛇及食物的构成
以“ [ 空格 ] ”3个字符为贪吃蛇的一个节点、初始化长度为4个节点。
以“ *** ”3个字符构成食物,并且在地图范围坐标(1,1)-(20,20)内随机显示
#include<curses.h>
#include <stdlib.h>
#include <unistd.h>
struct Snake
{
int hang;
int lie;
struct Snake*next;
};
struct Snake* head = NULL;//将蛇的头节点定义为全局变量
struct Snake* tail = NULL;//将蛇的尾节点定义为全局变量
struct Snake food;
void initFood()
{
int x = rand()%20 + 1 ;
int y = rand()%20 + 1 ;//%20 + 1是为了保证使rand出现的随机数尽量的出现在地图范围内
food.hang = x;
food.lie = y;
}
void initNcurses()
{
initscr();//ncurses界面初始化函数
keypad(stdscr,1);//从stdscr中接受功能建,1表示接收
noecho();//不要把无关的东西打印在界面上
}
int hasSnakeNode(int i,int j)
{
struct Snake *p;
p = head;
while(p != NULL){
if(p->hang == i && p->lie == j){
return 1;
}
p = p->next;
}
return 0;
}
void gameMap()
{
int hang;
int lie;
move(0,0);//在每次调用地图的时候用move函数将光标移动到地图的第一个方格的位置
for(hang=0; hang<=21; hang++){
if(hang == 0 || hang == 21){
for(lie=0; lie<=21; lie++){
if(lie == 0||lie == 21){
printw("-");
}else{
printw("---");
}
}
printw("\n");
}
if(hang > 0 && hang < 21){
for(lie=0; lie<=21; lie++){
if(lie == 0||lie == 21){
printw("|");
}
else if(hasSnakeNode(hang,lie)){
printw("[ ]");
}
else{
printw(" ");
}
}
printw("\n");
}
}
printw("By DaTou,key=%d",key);
}
void addSnake()
{
struct Snake* new = (struct Snake*)malloc(sizeof(struct Snake));
new->hang = tail->hang;
new->lie = tail->lie+1;
new->next = NULL;
tail->next = new;
tail = new;
}
void initSnake()//蛇的初始化函数
{
struct Snake *p;
dir = RIGHT;
while(head != NULL){
p = head;
head = head->next;//将链表的每一个节点都释放掉,指针后移
free(p);
}//添加这个循环的目的是在一次游戏结束后释放当局游戏创建的链表,避免内存泄露
initFood();
head = (struct Snake*)malloc(sizeof(struct Snake));
head->hang = 2;
head->lie = 2;
head->next = NULL;
tail = head;
addSnake();
addSnake();
addSnake();
}
void deleSnake()
{
struct Snake *p;
p = head;
head = head->next;
free(p);
}
void moveSnake()
{
addSnake();//在蛇的尾部添加节点并且删除节点后然后判断尾节点的行和列是否达到边界
deleSnake();
if(tail->hang == 0 || tail->lie == 21 || tail->lie == 0 || tail->hang == 21){
initSnake();
}
}
int main()
{
initNcurses();
initSnake();
gameMap();
while(1){
moveSnake();
map();//删除后再次刷新地图显示移动后的界面
refresh();//刷新界面函数
//sleep(1);//每隔一秒蛇移动一下,并刷新界面,这行控制蛇的移动速率
usleep(100000);//sleep以秒为单位有点慢,usleep以微秒为单位此处控制蛇的移动速度
}
getch();
endwin();//没有这行会破坏shell终端
return 0;
}
五、贪吃蛇的移动
通过键盘方向键值改变贪吃蛇的运动方向
#define UP 1
#define DOWN -1
#define LEFT 2
#define RIGHT -2
int key;
int dir;
int ifSnakeDie()
{
struct Snake *p;
p = head;
if(tail->hang == 0 || tail->lie == 21 || tail->lie == 0 || tail->hang == 21){
return 1;
}
while(p->next != NULL){
if(p->hang == tail->hang && p->lie == tail->lie){
return 1;
}
p = p->next;
}
return 0;
}
void* moveSnake()
{
addSnake();//在蛇的尾部添加节点并且删除节点后然后判断尾节点的行和列是否达到边界
if(hasFood(tail->hang,tail->lie)){
initFood();
}
else{
deleSnake();
}
if(ifSnakeDie()){
initSnake();
}
}
void turn(int direction)//这个函数的作用是当蛇在竖直方向上运动时使上下键无效
//在水平方向运动时,左右方向键无效
{
if(abs(dir) != abs(direction)){
dir = direction;
}
}
void* changeDir()
{
while(1){
key = getch();
switch(key){
case KEY_DOWN:
turn(DOWN);
break;
case KEY_UP:
turn(UP);
break;
case KEY_RIGHT:
turn(RIGHT);
break;
case KEY_LEFT:
turn(LEFT);
break;
}
}
}
六、线程的引入
在此游戏中地图是每隔100毫秒刷新一次,需要一个循环;在用方向键控制蛇的移动时,也需要一个循环,于是引入用linux线程即可解决该问题。
我们将一个程序里的执行路线叫做线程(thread)。更准确的定义是:线程是一个进程内部的控制序列。
创建线程函数原型
pthread_t th;
pthread_create(&th,NULL,thread,&arg);
第一个参数th是线程的描述符,就是上边定义的pthread_t th中的th
第二个参数一般就写NULL
第三个参数是做好的准备被调用的函数,最后一个参数是函数的参数
网络线程历程:
#include<stdio.h>
#include<pthread.h>
void*thread(void *arg)
{
printf("this is a thread and arg=%d.\n",*(int*)arg);
*(int*)arg=0;
return arg;
}
int main(int argc,char *argv[])
{
pthread_t th;
int ret;
int arg=10;
int *thread_ret=NULL;
ret=pthread_create(&th,NULL,thread,&arg);//pthread_create这个函数是线程的创建函数
//第一个参数th是线程的描述符,就是上边定义的pthread_t th中的th
//第二个参数一般就写NULL
//第三个参数是做好的准备被调用的函数,最后一个参数是函数的参数
if(ret!=0){
printf("Creat thread error!\n");
return -1;
}
printf("this is the mian process.\n");
pthread_join(th,(void**)&thread_ret);
//int pthread_join(pthread_t thread, void **value_ptr);
//thread:等待退出线程的线程号。
//value_ptr:退出线程的返回值。
return 0;
}
自己实现线程的创建
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* func1()
{
while(1){
printf("this is func1.\n");
sleep(1);
}
}
void* func2()
{
while(1){
printf("this is func2.\n");
sleep(1);
}
}
int main()
{
pthread_t th1;
pthread_t th2;
pthread_create(&th1,NULL,func1,NULL);
pthread_create(&th2,NULL,func2,NULL);//pthread_create这个函数是线程的创建函数
//第一个参数th是线程的描述符,就是上边定义的pthread_t th中的th,就相当于给第三那个参数的一个ID吧
//第二个参数一般就写NULL
//第三个参数是做好的准备被调用的函数,最后一个参数是函数的参数
while(1);
// pthread_join(th1,(void **)&pthread_th1);
// pthread_join(th2,(void **)&pthread_th2);//这种方法也可以使主线程等待指定线程退出后才退出
//th2是线程的ID
//int pthread_join(pthread_t thread, void **value_ptr);
//thread:等待退出线程的线程号。
//value_ptr:退出线程的返回值。
return 0;
}
七、整体代码
#include <curses.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 hang;
int lie;
struct Snake*next;
};
struct Snake* head = NULL;//将蛇的头节点定义为全局变量
struct Snake* tail = NULL;//将蛇的尾节点定义为全局变量
struct Snake food;
int key;
int dir;
void initFood()
{
int x = rand()%20 + 1;
int y = rand()%20 + 1;//%20 + 1是为了保证使rand出现的随机数尽量的出现在地图范围内
food.hang = x;
food.lie = y;
}
void initNcurses()
{
initscr();//ncurses界面初始化函数
keypad(stdscr,1);//从stdscr中接受功能建,1表示接收
noecho();//不要把无关的东西打印在界面上
}
int hasFood(int i,int j)
{
if(food.hang == i && food.lie == j){
return 1;
}
return 0;
}
int hasSnakeNode(int i,int j)
{
struct Snake *p;
p = head;
while(p != NULL){
if(p->hang == i && p->lie == j){
return 1;
}
p = p->next;
}
return 0;
}
void gameMap()
{
int hang;
int lie;
move(0,0);//在每次调用地图的时候用move函数将光标移动到地图的第一个方格的位置
for(hang=0; hang<=21; hang++){
if(hang == 0 || hang == 21){
for(lie=0; lie<=21; lie++){
if(lie == 0||lie == 21){
printw("-");
}else{
printw("---");
}
}
printw("\n");
}
if(hang > 0 && hang < 21){
for(lie=0; lie<=21; lie++){
if(lie == 0||lie == 21){
printw("|");
}
else if(hasSnakeNode(hang,lie)){
printw("[ ]");
}
else if(hasFood(hang,lie)){
printw("***");
}
else{
printw(" ");
}
}
printw("\n");
}
}
printw("By DaTou,key=%d",key);
}
void addSnake()
{
struct Snake* new=(struct Snake*)malloc(sizeof(struct Snake));
new->next = NULL;
switch(dir){
case UP:
new->hang = tail->hang-1;
new->lie = tail->lie;
break;
case DOWN:
new->hang = tail->hang+1;
new->lie = tail->lie;
break;
case LEFT:
new->hang = tail->hang;
new->lie = tail->lie-1;
break;
case RIGHT:
new->hang = tail->hang;
new->lie = tail->lie+1;
break;
}
tail->next = new;
tail = new;
}
void initSnake()//蛇的初始化函数
{
struct Snake *p;
dir = RIGHT;
while(head != NULL){
p = head;
head = head->next;//将链表的每一个节点都释放掉,指针后移
free(p);
}//添加这个循环的目的是在一次游戏结束后释放当局游戏创建的链表,避免内存泄露
initFood();
head = (struct Snake*)malloc(sizeof(struct Snake));
head->hang = 2;
head->lie = 2;
head->next = NULL;
tail = head;
addSnake();
addSnake();
addSnake();
}
void deleSnake()
{
struct Snake *p;
p = head;
head = head->next;
free(p);
}
int ifSnakeDie()
{
struct Snake *p;
p = head;
if(tail->hang == 0 || tail->lie == 21 || tail->lie == 0 || tail->hang == 21){
return 1;
}
while(p->next != NULL){
if(p->hang == tail->hang && p->lie == tail->lie){
return 1;
}
p = p->next;
}
return 0;
}
void* moveSnake()
{
addSnake();//在蛇的尾部添加节点并且删除节点后然后判断尾节点的行和列是否达到边界
if(hasFood(tail->hang,tail->lie)){
initFood();
}
else{
deleSnake();
}
if(ifSnakeDie()){
initSnake();
}
}
void* refreshUi()//封装的界面刷新函数
{
while(1){
moveSnake();
gameMap();//删除后再次刷新地图显示移动后的界面
refresh();//刷新界面函数
//sleep(1);//每隔一秒蛇移动一下,并刷新界面,这行控制蛇的移动速率
usleep(100000);//sleep以秒为单位有点慢,usleep以微秒为单位此处控制蛇的移动速度
}
}
void turn(int direction)//这个函数的作用是当蛇在竖直方向上运动时使上下键无效
//在水平方向运动时,左右方向键无效
{
if(abs(dir) != abs(direction)){
dir = direction;
}
}
void* changeDir()
{
while(1){
key = getch();
switch(key){
case KEY_DOWN:
turn(DOWN);
break;
case KEY_UP:
turn(UP);
break;
case KEY_RIGHT:
turn(RIGHT);
break;
case KEY_LEFT:
turn(LEFT);
break;
}
}
}
int main()
{
pthread_t t1;
pthread_t t2;
initNcurses();
initSnake();
gameMap();
pthread_create(&t1,NULL,refreshUi,NULL);
sleep(1);
pthread_create(&t2,NULL,changeDir,NULL);
while(1);
getch();
endwin();//没有这行会破坏shell终端
return 0;
}
编译的时时候记得要加-lcurses 、-lpthread
gcc snake.c -lcurses -lpthread
运行效果: