c语言第一个小游戏:贪吃蛇小游戏05

贪吃蛇脱缰自动向右走:脱缰的野蛇

#include <curses.h>

#include <stdlib.h>

struct snake{

int hang;

int lie;

struct snake *next;

};

struct snake *head;

struct snake *tail;

void initNcurse()

{

initscr();

keypad(stdscr,1);

}

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 gamepic()

{

int hang;

int lie;

move(0,0);

for(hang=0;hang<20;hang++){

if(hang == 0){

for(lie=0;lie<20;lie++){

printw("--");

}

printw("\n");

}

if(hang>=0 && hang<=19){

for(lie=0;lie<=20;lie++){

if(lie==0||lie==20){

printw("|");

}else if(hasSnakeNode(hang,lie)){

printw("[]");

}

else{

printw(" ");

}

}

printw("\n");

}

if(hang == 19){

for(lie=0;lie<20;lie++){

printw("--");

}

printw("\n");

}

}

printw("by shijintao");

printw("\n");

}

void addNode()

{

struct snake *new;

new =(struct snake *)malloc(sizeof(struct snake));

new->hang=tail->hang;

new->lie=tail->lie+1;

tail->next = new;

tail = new;

new->next = NULL;

}

void initSnake()

{

struct snake *p;

while(head != NULL){

p=head;

head=head->next;

free(p);

}

head = (struct snake *)malloc(sizeof(struct snake));

head->hang=2;

head->lie=2;

head->next=NULL;

tail = head;

addNode();

addNode();

}

void deleteNode()

{

struct snake *p;

p = head;

head = head->next;

free(p);

}

void moveSnake()

{

addNode();

deleteNode();

if(tail->hang==0||tail->hang==20||tail->lie==20||tail->lie==0){

initSnake();

}

}

int main()

{

int con;

initNcurse();

initSnake();

gamepic();

while(1){

moveSnake();

gamepic();

refresh();

usleep(100000);

}

getch();

endwin();

return 0;

}

这边最主要改动的代码地方在于main函数里面的

主要是我们要先想一下如果只是一味的想让这个蛇自己往右边走(这个右边就是代码效果,也没有设置方向,就是新节点的插入导致一个向右边走的效果),肯定要把监测右方向键给去掉,然后死循环就可以实现l,不断的moveSnake(不断的addNode,deleteNode)

while(1){

moveSnake();

gamepic();

refresh();

usleep(100000);

}

原始代码中,getch() 函数的调用确实会导致屏幕刷新。这是因为 getch() 在等待用户输入时,会隐式触发一次屏幕刷新

1. getch() 的隐式刷新机制

在 ncurses 中,getch() 不仅用于获取用户输入,还会执行以下操作:

  • 刷新屏幕 :在等待按键时,ncurses 会自动将当前缓冲区内容渲染到屏幕上。
  • 处理输入事件 :捕获用户按下的键并返回其 ASCII 码(或特殊键的编码,如方向键)。

因此,当你在循环中调用 getch() 时,即使没有显式调用 refresh(),屏幕也会被刷新。这解释了为什么你之前的代码能正常显示画面。

2. 为什么现在需要显式调用 refresh()

在你的新代码中,循环结构发生了变化:

while(1){

moveSnake(); // 更新蛇位置

gamepic(); // 绘制画面

refresh(); // 显式刷新屏幕

usleep(100000); // 延时控制速度}

这里的关键区别是:蛇的移动不再依赖用户输入 ,而是通过定时器自动更新(usleep())。因此:

  • 如果没有 refresh(),绘制操作(gamepic())的结果只会停留在缓冲区,无法显示。
  • usleep() 是纯延时函数,不会触发屏幕刷新,必须手动调用 refresh()。

3. 两种实现方式的对比

|------------------------------------------|-------------------|
| 旧代码(依赖 getch() | 新代码(自动移动) |
| 蛇仅在用户按键时移动 | 蛇自动持续移动 |
| getch() 隐式刷新屏幕 | 必须显式调用 refresh() |
| 游戏节奏由用户控制 | 游戏节奏由 usleep() 控制 |

4. 最佳实践建议

  • 始终显式调用 refresh() :即使在有 getch() 的场景中,显式刷新能让代码逻辑更清晰,避免依赖隐式机制。
  • 分离输入处理和画面刷新 :在自动移动的游戏中,用户输入(如方向键)和屏幕刷新是独立的操作,需要分别处理。

总结

原始代码之所以能正常显示,是因为 getch() 在等待输入时隐式刷新了屏幕。而在新的自动移动实现中,由于没有依赖 getch(),必须通过 refresh() 手动刷新屏幕才能看到蛇的移动效果。这是 ncurses 编程中的一个常见细节,理解它有助于写出更健壮的代码!

贪吃蛇风骚走位之支持方向变换

那么问题来了,方向的改变要监测键盘的按键需要一个while(1)循环(不断的检测用户的输入) 还有,蛇的脱缰的走也需要一个while(1)循环支持,就是这两个while(1)需要同时跑,那么我们现在单线程,就是目前无法一心二用,所以我们还做不了,需要学习多线程。

Linux多线程概念引入及编码实现

我们做贪吃蛇的时候不是要需要控制按方向键和moveSnake(脱缰的野蛇)且 要同时运行且都是要用while死循环去进行接收吗,那么我们单线程就无法做到了,这个时候要学习多线程,这样我们就是可以同时执行两个死循环。

所以我们先学习一下多线程的基本原理

想让我们先看个单线程的例子

我想通过这个单线程的返回结果,和等下多线程的返回结果做对比就很容易理解多线程的意思了;

因为在单线程环境下,函数是顺序执行的。当调用 FUNC1 时,即使里面有sleep操作,它也只是让执行流程暂停一下,并不会切换到其他函数执行,所以会先输出this is FUNC1,等待sleep时间过后再继续输出this is FUNC1

假如现在这段代码变成了多线程

在多线程环境下,当一个线程执行到类似sleep操作时,它会暂停执行一段时间,这时操作系统会调度其他可运行的线程。

假设存在FUNC1FUNC2两个函数分别在不同线程中执行:

  1. FUNC1所在线程执行到sleep时,该线程会进入休眠状态,此时操作系统会寻找其他可运行的线程,很可能就会找到FUNC2所在线程(如果FUNC2线程处于可运行状态),然后开始执行FUNC2
  2. FUNC2执行到类似sleep操作后,它也会暂停,操作系统又会重新调度,此时FUNC1线程休眠时间结束的话,可能会继续执行FUNC1;如果FUNC1还没结束休眠,且还有其他可运行线程,就可能会执行其他线程。

这种线程间的切换和执行顺序,取决于操作系统的线程调度机制,没有办法精确地预测哪个线程会在特定时刻执行,除非使用线程同步机制(如锁、条件变量、信号量等)来控制线程的执行顺序。例如在 Java 中,可以使用join方法让一个线程等待另一个线程执行完毕;或者使用同步块和waitnotify方法来控制线程间的协作和执行顺序。

通过下面的例子学习一下

运行后是这样的 一直循环这个两句话,就是说不一定每次调用的都是一样的(在多核是同时运行,在单核是他们去争夺cpu资源),但是可以保证他们可以同时运行的(这就够了)

  • #include <pthread.h> :引入 POSIX 线程库的头文件,该库用于在 C 语言中实现多线程编程,后续用到的 pthread_t 类型以及 pthread_create 等函数声明都在这个头文件中。
  • func1 和 func2 是两个线程函数。它们的返回类型为 void * ,这是 POSIX 线程库中线程函数的标准返回类型要求。
  • 每个函数内部都有一个无限循环 while(1) 。在循环中,先通过 printf 输出相应的提示信息("this is func1\n" 或 "this is func2\n" ),然后调用 sleep(1) 让线程暂停 1 秒钟。这使得线线程变量声明 :pthread_t th1; 和 pthread_t th2; 声明了两个 pthread_t 类型的变量,pthread_t 用于标识线程,th1 和 th2 分别用来标识即将创建的两个不同线程。
  • 线程创建
    • pthread_create(&th1,NULL,func1,NULL); 调用 pthread_create 函数创建了一个新线程,该线程的标识符存储在 th1 中。第二个参数 NULL 表示使用默认的线程属性;第三个参数 func1 指明这个新线程要执行的函数是 func1 ;第四个参数 NULL 表示不向 func1 函数传递额外参数 。
    • 同理,pthread_create(&th2,NULL,func2,NULL); 创建了另一个线程,其标识符为 th2 ,执行函数为 func2 。
  • 无限循环与程序结束 :while(1); 是一个无限循环,这里的作用是防止 main 函数执行结束。因为在多线程程序中,如果主线程(main 函数所在线程)执行完毕退出,那么其他子线程也会被操作系统强制终止。通过这个无限循环,让主线程一直保持运行状态,从而使得创建的 th1 和 th2 线程能持续执行它们各自的任务。return 0; 理论上不会执行到,不过按照 main 函数的规范,需要有返回值声明。

整体功能

这段代码实现了一个简单的多线程程序,创建了两个线程分别执行 func1 和 func2 函数,这两个线程会并发地不断打印各自的提示信息并间隔 1 秒。但代码存在一些不足,比如没有对线程的返回值进行处理,也没有合理的方式来终止线程或主线程,实际应用中可以进一步完善,比如添加信号处理机制来结束程序等 。

  • 程会不断重复打印信息并间隔 1 秒。
相关推荐
小森776722 分钟前
(七)深度学习---神经网络原理与实现
人工智能·深度学习·神经网络·算法
chenyuhao202428 分钟前
链表的面试题4之合并有序链表
数据结构·链表·面试·c#
迷茫不知归路40 分钟前
操作系统实验习题解析 上篇
c++·算法·操作系统·实验课设
愚润求学1 小时前
【递归、搜索与回溯】专题一:递归(二)
c++·笔记·算法·leetcode
h汉堡1 小时前
C/C++内存管理
java·c语言·开发语言·c++·学习
水水沝淼㵘1 小时前
嵌入式开发学习日志(数据结构--顺序结构单链表)Day19
linux·服务器·c语言·数据结构·学习·算法·排序算法
yutian06062 小时前
C语言中的宏
c语言·开发语言
June`2 小时前
专题四:综合练习( 找出所有子集的异或总和再求和)
c++·算法·深度优先·剪枝
Magnum Lehar2 小时前
3d游戏引擎的Utilities模块实现下
c++·算法·游戏引擎
JANYI20182 小时前
C语言易混淆知识点详解
java·c语言·算法