游戏说明:
linux 环境下的,基于Ncursse 图形图的C语言小游戏
基础要求:
C语言基础
Linux 基本操作:
如何编写代码 如何编译代码 如何运行程序 如何创建文件夹
为什么需要应用ncurse 库:
C语言的键盘输入 函数 没法满足实时性 -- scanf gets getchar -- 都需要 回车确认
curse 输入和输出:
键盘响应快
#include<curses.h>
initscr();
printw("This is a curse window.\n"); // ncurse 模式的printf
getch(); //等待用户输入 ,if 没这句话程序会自动退出,看不到运行结果
endwin(); //程序退出-- 调用函数来恢复shell终端的显示,没有这句话,shell终端就会乱码
nurse 的上下左右 键:
#define KEY_DOWN 0402
#define KEY_UP 0403
#define KEY_LEFT 0404
#define KEY_RIGHT 0405
上面四个键是功能键 -- 使用的时候需要我们 引入 keypad函数
keypad(stdscr,1);
#include <curses.h>int main()
{
int key;
initscr();
keypad(stdscr, 1);
while (1)
{
key = getch();
switch (key)
{
// case 0402:
case KEY_DOWN:
printw("DOWN\n");
break;
case KEY_UP:
printw("UP\n");
break;
case KEY_LEFT:
printw("LEFT\n");
break;
case KEY_RIGHT:
printw("RIGHT\n");
break;
}
}
endwin();return 0;
}
======================================
贪吃蛇地图:
20 * 20 的格子 :
地图上界"--"
地图左界"|"
苹果(食物)"##"
蛇身 "[][][[]"
void myPrintMap()
{
int line, row;for (line = 0; line < 20; ++line)
{
if (line == 0)
{
for (row = 0; row < 20; row++)
printw("--");
printw("\n");
}if (line >= 0 && line < 20)
{
for (row = 0; row <= 20; row++)
{
if (row == 0)
printw("|");
else if (row == 20)
printw("|\n");
else
printw(" ");
}
if (line == 19)
{
for (row = 0; row < 20; row++)
{
if (row == 19)
printw("--\nby mxjun");
else
printw("--");
}
}
}
}
}
=========================================
贪吃蛇身子 节点 -- 结构体--使用链表实现
//扫描匹配到贪吃蛇的身体节点 --行列都匹配
不打空格 ,打出蛇身子
-- printw("[]") --显示出来//判断是否需要显示蛇的身子
int haveSnack(int line,int row)
{
struct snack * p=&s1;while(p!=NULL)
{
if(p->line==line && p->row==row)
return 1;
p=p->next;
}
return 0;}
=======================================
链表添加蛇的节点:
void addSnack()
{
//尾插法实现
struct Snack *new = (struct Snack*)malloc(sizeof(struct Snack));
new->line=tail->line;
new->row=tail->row+1;
new->next=NULL; //作为新的尾巴
tail->next=new;
tail=new;
}void initSnack()
{
head = (struct Snack*)malloc(sizeof(struct Snack));
head->line=2;
head->row=2;
head->next=NULL;
tail=head;addSnack();
addSnack();
addSnack();}
=========================================
实现贪吃蛇的向右移动:
1.右移:
原理 删除第一个, 在最右边一个节点就没添加一个
void delSnack()
{
struct Snack* new;
new=head;
head=head->next;
free(new);
}void moveRight()
{
addSnack();
delSnack();}
===============================
贪吃蛇撞墙 ;void initSnack()
{
struct Snack*new=NULL;
while(head!=NULL)
{
new=head;
head=head->next;
free(new);}
head = (struct Snack*)malloc(sizeof(struct Snack));
head->line=2;
head->row=2;
head->next=NULL;
tail=head;addSnack();
addSnack();
addSnack();}
void moveRight()
{
addSnack();
delSnack();
//判断是否撞到墙
if(tail->row==0 || tail->row==20)
{
initSnack();}
}
usleep () -- 休眠
refresh() -- 刷新
========================
贪吃蛇的自行游走
-- while -- 一直执行, move函数 usleep() -- 控制时间
warning: implicit declaration of function ‘usleep’ [-Wimplicit-function-declaration]
156 | usleep(100000);
// 这种警报 可以直接man usleep 查看他的头文件
===============================
方向变化 -- 多线程
引入 -- .界面一边在刷新,我们也要求一边能接收我们的按键 -- 多线程 --让两个while 同时进行
怎么实现多线程:
Linux 线程:
创建线程的代码:
#include<pthread.h>
ret = pthread_create(&th,NULL,thread,&arg ); // thread --可以是函数 -- 我们要执行的
#include<stdio.h>
#include<pthread.h>void* func1()
{
while(1)
{
puts("func1 is running");
sleep(1);
}}
void* func2()
{
while(1)
{
puts("func2 is running");
sleep(1);
}}
int main()
{
pthread_t th1;
pthread_t th2;
pthread_create(&th1,NULL,func1,NULL);
pthread_create(&th2,NULL,func2,NULL);
while(1);}
===================================
蛇的转弯
定义全局 的方向变量dir
在键入 方向键的时候进行修改 -- 然后在moveSncak()的addSnack() 的时候 方向 被改变
===================================
绝对值优化蛇的不合理走位;
abs 函数 -- 去绝对值
initNcurses() -- noecho -- 不要把无关信息显示在界面上
#define UP 1
#define DOWN 2
#define LEFT 3
#define RIGHT 4oid turn(int newDir)
{
if(abs(newDir)!=abs(dir))
dir=newDir;}
void *inputKey()
{while (1)
{
key = getch();
switch (key)
{
case KEY_DOWN:
turn(DOWN);
break;
case KEY_UP:
turn(UP);
break;
case KEY_LEFT:
turn(LEFT);
break;
case KEY_RIGHT:
turn(RIGHT);
break;
}
}
}
=====================================
苹果 -- 食物
食物可以使用之前的snack 结构体里面的坐标表示
static -- 静态变量 -- 函数被再次调用打的时候他的值不会发生变化
rand() --实现食物随机生成: % 20 -- 限制范围
==================================
蛇能吃自己 -- 链表尾巴 和 身体的重合
//这里我们再明确一点 ,由于我们使用的是尾插法,so我们得到的链表尾巴就是蛇头
代码整体实现
#include <curses.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>#define UP 1
#define DOWN -1
#define LEFT 2
#define RIGHT -2struct Snack //定义蛇类
{int line;int row;struct Snack *next; //链表实现贪吃蛇变长
};
// 定义蛇头,蛇尾
struct Snack *head = NULL;
struct Snack *tail = NULL;
struct Snack food; //定义食物类
int key;
int dir; //记录方向void initNcurse() //对Ncurse初始化
{initscr();keypad(stdscr, 1);noecho(); //避免键入方向键的时候屏幕错乱,返回值
}void initFood() //初始化食物
{
int x=rand()%20; //随机生成0-19的位置
int y=rand()%20;food.line=x;
food.row=y;}
int haveFood(int line, int row) //判断这个格子上是否存在食物
{if(food.line==line && food.row==row)return 1;return 0;
}int haveSnack(int line, int row)//判断这个格子上是否存在蛇
{struct Snack *p = head;while (p != NULL){if (p->line == line && p->row == row)return 1;p = p->next;}return 0;
}void addSnack() //增加蛇的长度
{// 尾插法实现struct Snack *new = (struct Snack *)malloc(sizeof(struct Snack));switch (dir) //依据当前的方向判断{case UP:new->line = tail->line-1;new->row = tail->row;new->next = NULL; // 作为新的尾巴break;case DOWN:new->line = tail->line+1;new->row = tail->row;new->next = NULL; // 作为新的尾巴break;case LEFT:new->line = tail->line;new->row = tail->row-1;new->next = NULL; // 作为新的尾巴break;case RIGHT:new->line = tail->line;new->row = tail->row+1;new->next = NULL; // 作为新的尾巴break;}tail->next = new;tail = new;
}void initSnack() //初始化蛇 - 死后 复活
{dir=RIGHT;initFood();struct Snack *new = NULL;while (head != NULL){new = head;head = head->next;free(new);}head = (struct Snack *)malloc(sizeof(struct Snack));head->line = 2;head->row = 2;head->next = NULL;tail = head;addSnack();addSnack();addSnack();
}void myPrintMap() //打印地图
{int line, row;move(0, 0); //每次新的地图都移动到(0,0),覆盖之前的地图实现实时性for (line = 0; line < 20; ++line){if (line == 0){for (row = 0; row < 20; row++)printw("--");printw("\n");}if (line >= 0 && line < 20){for (row = 0; row <= 20; row++){if (row == 0)printw("|");else if (row == 20)printw("|\n");else if (haveSnack(line, row))printw("[]");else if (haveFood(line, row))printw("##");elseprintw(" ");}if (line == 19){for (row = 0; row < 20; row++){if (row == 19)printw("--\nby mxjun");elseprintw("--");}}}}
}void delSnack()
{struct Snack *new;new = head;head = head->next;free(new);
}
int ifSnackDead() //判断边界
{if(tail->line<0 || tail->line==20 || tail->row==0 || tail->row==20)return 1;struct Snack *p=head;while(p->next!=NULL){if(p->line==tail->line && p->row==tail->row)return 1;p=p->next;}
return 0;}void moveSnack()
{ if(tail->line==food.line && tail->row==food.row) //吃到食物蛇变长{addSnack();initFood();}else //不然只是移动{addSnack();delSnack();}// 判断是否撞到墙if (ifSnackDead()){initSnack();//死了重开}
}void *refreshInterface() //线程之一,实现蛇的自动行走
{while (1){moveSnack();myPrintMap();refresh();usleep(100000);}
}void turn(int newDir) //防止蛇出行头变为尾巴这样的逆天走位
{if(abs(newDir)!=abs(dir))dir=newDir;}void *inputKey() //第二个线程,时刻等待键盘输入
{while (1){key = getch();switch (key){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 th1;pthread_t th2;
//初始化蛇和NcurseinitNcurse();initSnack();pthread_create(&th1, NULL, refreshInterface, NULL);pthread_create(&th2, NULL, inputKey, NULL);while (1) //避免程序退出{}getch();endwin(); //结束使用ncurse,退出程序,避免乱码getch();return 0;
}