贪吃蛇双人模式设计(2)
敲上瘾-CSDN博客 控制台程序设置_c语言控制程序窗口大小-CSDN博客 贪吃蛇小游戏_贪吃蛇小游戏csdn-CSDN博客
一、功能实现:
- 玩家1使用↓ → ← ↑按键来操作蛇的方向,使用右Shift键加速,右Ctrl键减速
- 玩家2使用W A S D按键来操作蛇的方向,使用左Alt键加速,C键减速
- 任意玩家点击空格键游戏暂停
- 若其中蛇a吃到蛇b的身体,则蛇a将变成食物,然后蛇a以初始状态进行复活
双人模式的主逻辑和单人模式差不多,就不在赘述,接下来就只讲一些要点,下面是头文件声明
头文件声明
二、食物节点的创建
双人模式相比单人模式需要把地图扩大,增加玩家们的博弈范围,除此之外就是把原来的食物个数增加。使玩家更有体验感。食物是用链表来维护的,所以在食物的创建上我们只需要在链表尾插上节点就行,10个为宜。
三、什么双线程
在写双人模式的时候有一个很要命的问题,就是如何让两条蛇的速度互不影响,因为当初我们是靠Sleep函数来控制速度的,而程序是一条一条逐一执行的,需要等一条蛇的程序执行结束,才轮到另一条蛇执行。这样的话它们的速度必然会互相干扰。在不了解双线程之前这个问题是很让人头疼的,几乎无法被解决。现在我们就来了解一下双线程:
通俗简单地说,双线程就像是一个人同时在做两件事情一样。想象一下,你在厨房里煮面条,同时在客厅里看电视。虽然你只有一双手,但你可以在等待面条煮熟的时候,趁机看一会电视。这样,你的时间就得到了更有效的利用,而不是只等在厨房里。
计算机中,双线程也是类似的。处理器就像是你的大脑,能够同时执行多个任务。有了双线程,处理器可以同时处理两个任务,提高了计算机的效率,让它能够更快地完成工作。
以下是一个简单的Windows下使用C语言创建并运行两个线程的示例代码:
#include <stdio.h>
#include <windows.h>
// 第一个线程函数
DWORD WINAPI Thread1Func(LPVOID lpParam)
{for (int i = 0; i < 5; i++){printf("Thread 1: Step %d\n", i);// 线程休眠500毫秒Sleep(500);}return 0;
}
// 第二个线程函数
DWORD WINAPI Thread2Func(LPVOID lpParam)
{for (int i = 0; i < 5; i++){printf("Thread 2: Step %d\n", i);// 线程休眠700毫秒Sleep(700);}return 0;
}
int main()
{// 创建并启动第一个线程HANDLE thread1 = CreateThread(NULL, 0, Thread1Func, NULL, 0, NULL);if (thread1 == NULL){printf("Error creating thread 1\n");return 1;}// 创建并启动第二个线程HANDLE thread2 = CreateThread(NULL, 0, Thread2Func, NULL, 0, NULL);if (thread2 == NULL){printf("Error creating thread 2\n");return 1;}// 等待两个线程结束WaitForSingleObject(thread1, INFINITE);WaitForSingleObject(thread2, INFINITE);// 关闭线程句柄CloseHandle(thread1);CloseHandle(thread2);return 0;
}
1.首先,我们定义了两个线程函数 Thread1Func() 和 Thread2Func(),它们分别代表了两个不同线程的执行内容。这些函数的返回类型是 DWORD,参数类型是 LPVOID,表示线程函数的标准参数格式。
2.在 Thread1Func() 和 Thread2Func() 中,我们使用一个 for 循环输出线程执行的内容,并使用 printf() 函数进行输出。在每次循环结束后,线程使用 Sleep() 函数进行休眠,模拟一些处理过程。
3.在 main() 函数中,我们使用 CreateThread() 函数创建了两个线程。该函数接受多个参数,其中包括线程的安全属性、栈大小、线程函数、线程函数参数等。CreateThread() 返回一个指向新线程的句柄。
4.使用 WaitForSingleObject() 函数等待两个线程的结束。这样做可以确保主线程等待所有其他线程执行完毕后再继续执行。
5.最后,我们使用 CloseHandle() 函数关闭线程句柄,释放资源。
(1)、临界区
在进行多线程编程中,资源是共享的。
临界区通常用于多线程环境中,以确保对共享资源的互斥访问,防止多个线程同时修改该资源而导致数据不一致或错误。在实际应用中,当有共享资源需要被多个线程访问或修改时,我们通常会使用临界区或其他同步机制来保护这些资源。
如果示例代码中的两个线程需要访问共享资源,那么我们会在主函数中初始化临界区,并在线程函数中使用临界区的相关函数(如EnterCriticalSection() 和LeaveCriticalSection())来保护对共享资源的访问。在这种情况下,临界区的初始化和使用将成为必要的步骤。
(2)、锁
共享资源互斥访问是解决多线程在共用同资源时,导致不确定性的错误行为的一种机制。它可以使用锁来实现。当线程1执行到需要使用资源a时,获取到资源a的锁并给它上锁,那么线程2执行到资源a的时候不能使用,需要等待线程1把锁解开才能使用,并且也同样给资源a上锁。
上锁:EnterCriticalSection()
解锁:LeaveCriticalSection()
这样可以保证在同一时间内只有一个程序或线程对共享资源进行修改或操作,从而避免了竞态条件和数据不一致性的问题。
四、双线程处理
将两条蛇分开为两个线程执行,将两条蛇分成两个线程执行,避免速度的相互干扰。
void GameRun2(pSnake pu1, pSnake pu2)
{pLSnake pm = (pLSnake)malloc(sizeof(LSnake));pLSnake psk = pm;if (!psk){exit(-1);}psk->p1 = pu1;psk->p2 = pu2;CRITICAL_SECTION cs;HANDLE thp1 = NULL, thp2 = NULL;// 初始化临界区InitializeCriticalSection(&cs);// 创建线程thp1 = CreateThread(NULL, 0, th1, (LPVOID)psk, 0, NULL);//玩家1thp2 = CreateThread(NULL, 0, th2, (LPVOID)psk, 0, NULL);//玩家2if (!thp1||!thp2){exit(-1);}// 等待线程结束WaitForSingleObject(thp1, INFINITE);WaitForSingleObject(thp2, INFINITE);// 销毁临界区DeleteCriticalSection(&cs);// 关闭线程句柄CloseHandle(thp1);CloseHandle(thp2);
}
thp1(),thp2()的实现主逻辑和单人模式差不多,这里不在细讲。下面主要来解决资源竞争的问题。
首先要思考的就是它们共用那些资源,比如printf函数,SetPos函数。这两个没处理处理好的话会导致在程序执行时打印信息会在屏幕发生错乱。如下:
解决方法也比较简单就是在双线程内每次使用到printf函数和SetPos函数都给它们们上锁,用完后再解锁。并且线程内的所有涉及到这两个函数的位置都需要上锁。
如下:
EnterCriticalSection(&cs);//上锁SetPos(X2 + 6, 12);//坐标设置printf("玩家2 蓝蛇");//打印信息
LeaveCriticalSection(&cs);//解锁
注意在上锁和解锁中要把printf函数和SetPos函数放在一起,这样才能保证在准确的坐标位置打印出信息 。
五、撞到对方身体处理
为了增加玩家的体验与单人模式不同的是当玩家撞到自己的时候,我们不设为游戏结束,示为正常行为,而当玩家1撞到玩家2的身体的时候,玩家1将变成食物,并且玩家1将以初始化的状态在随机位置(不完全随机)复活。而我们将任意蛇撞墙做为游戏结束的条件。接下来我们来分析一下玩家的复活。
六、玩家复活
(1)、蛇身变为食物
在玩家复活前自身需要变成食物,这个操作也比较简单,就是做一个链表的连接,需要把维护食物坐标的链表尾连接上维护蛇身的链表的头,再把食物输出。要注意的是因为玩家1的蛇头撞到玩家2才把玩家1置为食物的,所用玩家1的蛇头不能作为食物,在做链表连接的时候需要从头节点的下一位节点开始。
(2)、玩家的随机复活
虽然说是随机的但不是完全随机,还需要考虑以下这些问题:
- 复活位置横坐标必须是偶数
- 复活位置不能是有食物的位置
- 复活位置不能是对方玩家控制的蛇的位置
- 复活位置不能再地图之外
复活位置横坐标为什么必须是偶数?
因为我们打印的蛇身和食物以及地图边界都是宽字符,宽字符占用两个字符空间的大小,我们在前面已经统一把打印的首位置是横坐标为偶数的位置,所以这里同样要设为偶数,否则就会出现一半是食物一半是蛇的身体的情况。
因为考虑到这一点我们在初始化蛇的时候考虑把蛇的复活状态设置为竖直状态的五个节点,也就是蛇的节点的横坐标的是相同的,而只有纵坐标是不同的而且是依次递增的五个节点。那么我们需要做的就是生成两个随机数x和y,作为蛇的尾坐标,然后只让y++得到五个节点,并检查这五个节点是否满足要求。不满足要求需要重新生成随机数x和y再次检查,直到符合要求后把它做成链表进行蛇身的维护。
void Resurrect(pSnake pt,pSnake pn)//玩家复活
{assert(pt);pSnakeNode pff = pt->_pFood;while (pff->next){pff = pff->next;}pff->next = pt->_pSnake->next;//蛇身变成食物PrintFood(pt->_pFood);//打印食物pt->_pSnake = NULL;//重点!!int x = 0, y = 0;//复活坐标reset:do{x = rand() % (X2 - 4) + 2;//2到X2-1y = rand() % (Y2 - 1) + 1;//1到Y2-1} while (x % 2 != 0);//复活位置横坐标设为偶数for (int i = 0; i < 5; i++){y += i;pSnakeNode ph = pn->_pSnake;while (ph)//检查复活位置是否与对方玩家相撞{if (ph->x == x && ph->y == y)goto reset;ph = ph->next;}pSnakeNode pfd = pt->_pFood;while (pfd)//检查复活位置是否是食物{if (pfd->x == x && pfd->y == y)goto reset;pfd = pfd->next;}if ((y <= 0) || (y+12 >= Y2)//检查复活位置是否是地图外|| (x <= 0) || (x + 12 >= X2 - 2))goto reset;}//检查复活位置合法后做成链表进行维护pSnakeNode pnew = NULL;for (int i = 0; i < 5; i++){pnew = (pSnakeNode)malloc(sizeof(SnakeNode));if (!pnew){exit(-1);}pnew->x = x;pnew->y = y+i;pnew->next = NULL;if ((pt->_pSnake) == NULL){pt->_pSnake = pnew;}else{pnew->next = pt->_pSnake;pt->_pSnake = pnew;}}pnew = pt->_pSnake;while (pnew){SetPos(pnew->x, pnew->y);wprintf(L"%lc", BODY);pnew = pnew->next;}pt->_status = OK;pt->_food_weight = 10;pt->_score = 0;pt->_sleep_time = 200;pt->_dir = RIGHT;
}
相关文章:

贪吃蛇双人模式设计(2)
敲上瘾-CSDN博客控制台程序设置_c语言控制程序窗口大小-CSDN博客贪吃蛇小游戏_贪吃蛇小游戏csdn-CSDN博客 一、功能实现: 玩家1使用↓ → ← ↑按键来操作蛇的方向,使用右Shift键加速,右Ctrl键减速玩家2使用W A S D按键来操作蛇的方向&am…...

mysql什么时候不需要建立索引
WHERE 条件,GROUP BY,ORDER BY 里用不到的字段,索引的价值是快速定位,如果起不到定位的字段通常是不需要创建索引的,因为索引是会占用物理空间的。字段中存在大量重复数据,不需要创建索引,比如性…...

热门开源项目推荐:技术与地址概览
随着开源项目的不断兴起,越来越多的优秀项目涌现出来,为开发者们提供了丰富的资源和灵感。在此,我将为大家推荐几个热门的开源项目,并附上它们的开源地址,以供大家参考和了解。 1. TensorFlow 项目简介: …...

Golang的channel
目录 基本使用 channel 数据结构 阻塞的协程队列 协程节点 构建 channel 写流程 读流程 非阻塞与阻塞 closechan(关闭) 基本使用 创建无缓存 channel c : make(chan int) //创建无缓冲的通道 cc : make(chan int,0) //创建无缓冲的通道 c 创建有缓存 channel c : m…...

DIYGW可视化开发工具:微信小程序与多端应用开发的利器
一、引言 随着移动互联网的飞速发展,微信小程序以其轻便、易用和跨平台的特点受到了广泛关注。然而,微信小程序的开发相较于传统的H5网页开发,在UI搭建和交互设计上存在一定的挑战。为了应对这些挑战,开发者们一直在寻找更加高效…...

docker——基础知识
简介 一、什么是虚拟化和容器化 实体计算机叫做物理机,有时也称为寄主机; 虚拟化:将一台计算机虚拟化为多台逻辑计算机; 容器化:一种虚拟化技术,操作系统的虚拟化;将用户空间软件实…...

SAP MMRV/MMPV 物料账期月结月底月初开关
公告:周一至周五每日一更,周六日存稿,请您点“关注”和“在看”,后续推送的时候不至于看不到每日更新内容,感谢。 这是一条刮刮乐,按住全部选中:点关注的人最帅最美,欢迎࿱…...

五分钟看懂如何解决FP独立站的广告投放问题
在数字化时代的浪潮中,跨境电商的独立站成为了商家们的新宠。与传统的电商平台相比,独立站在品牌建设、市场定位以及客户体验上提供了更多的自由度和创新空间。然而,这些独立站尤其是销售FP产品的站点,在广告投放上遇到了重重障碍…...

学习分享-FutureTask
前言 今天再改简历的时候回顾了之前实习用到的FutureTask,借此来回顾一下相关知识。 FutureTask 介绍 FutureTask 是 Java 并发包(java.util.concurrent)中的一个类,用于封装异步任务。它实现了 RunnableFuture 接口࿰…...

Javaweb02-XML概述
第一章 XML概述 1.XML基本概念 什么是xml? **a.**引入的原因:为了解决不同不同语言之间的数据传输的格式不同 **b.**概念:XML是一种可扩展标记语言,适用于不同数据之间的数据交换 **c.**XML文档:通过元素的嵌套&a…...

Linux shell编程基础
Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言。Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问 Linux 内核的服务。 Shell 脚本&#x…...

2024.6.12 作业 xyt
今日课堂练习:vector构造函数 #include <iostream> #include <vector> using namespace std;void printVector(vector<int> &v) {vector<int>::iterator iter;for(iterv.begin(); iter ! v.end(); iter){cout << *iter <<…...

QTTabBar在重置Internet Explorer后失效
网上常见的办法是: 打开IE浏览器>>设置>>Internet选项>>高级。勾选启用第三方浏览器扩展,重启后生效。 打开IE浏览器-设置–管理加载项,启用QTTabBar。 实际在Win10上使用的时候会遇到点开IE自动跳转到Edge的问题。这时…...

Django之云存储(一)
一、介绍 用户上传的文件以及项目中使用的静态文件,除了保存在本地服务器,还在可以保存在云服务中,比如: 阿里云七牛云(课程选用)亚马逊云等1.1、使用方式 注册账号 七牛云开发者平台 实名认证 创建空间...

推挽与开漏输出
一般来说,微控制器的引脚都会有一个驱动电路,可以配置不同类型的数字和模拟电路接口。输出模式一般会有推挽与开漏输出。 推挽输出 推挽输出(Push-Pull Output),故名思意能输出两种电平,一种是推…...

Sora和快手可灵背后的核心技术 | 3DVAE:通过小批量特征交换实现身体和面部的三维形状变分自动编码器
【摘要】学习3D脸部和身体生成模型中一个解开的、可解释的和结构化的潜在表示仍然是一个开放的问题。当需要控制身份特征时,这个问题尤其突出。在本文中,论文提出了一种直观而有效的自监督方法来训练一个3D形状变分自动编码器(VAE),以鼓励身份特征的解开潜在表示。通过交换不同…...

ArcGIS Pro SDK (三)Addin控件 2 窗格界面类
ArcGIS Pro SDK (三)Addin控件 2 窗格界面类 目录 ArcGIS Pro SDK (三)Addin控件 2 窗格界面类15 ArcGIS Pro 后台选项卡15.1 添加控件15.2 Code15.2.1 选项卡按钮15.2.2 选项卡页 16 ArcGIS Pro 窗体16.1 添加控件16.2 Code 17 A…...

Ubuntu 20.04.6 LTS系统使用命令编辑静态IP地址【笔记】
rootubuntu-machine:/home# cat /etc/issue Ubuntu 20.04.6 LTS \n \l1、切换到root身份 sudo su2、编辑静态IP地址,示例以01-network-manager-all.yaml,个别系统可能是00-network-manager-all.yaml,以安装系统生成的文件为准。 vim /etc/n…...

Python第二语言(八、Python包)
目录 1. 什么是Python包 2. 创包步骤 2.1 new包 2.2 查看创建的包 2.3 拖动文件到包下 3. 导入包 4. 安装第三方包 4.1 什么是第三方包 4.2 安装第三方包-pip 4.3 pip网络优化 1. 什么是Python包 包下有__init__.py就是包,无__init__.py就是文件夹。于Ja…...

Pipeline流水线组件
文章目录 1、新建pipeline流水线2、定义处理器3、定义处理器上下文4、pipeline流水线实现5、处理器抽象类实现6、pipeline流水线构建者7、具体处理器实现8、流水线测试9、运行结果 1、新建pipeline流水线 package com.summer.toolkit.model.chain;import java.util.List; impo…...

闪灵CMS电子商城系统源码v5.0(自带微信小程序)
源码介绍 闪灵CMS电子商城系统源码,双语带手机版,PHPMYSQL进行开发,网站安装简单、快捷。 闪灵CMS系统更新日志 1.修复:修复了开启强制https后,说明文档重定向过多的问题 2.修复:修复了商品名称过长时无…...

基于SSM的旅游民宿预定系统【源码】【运行教程】
基于SSM的旅游民宿预定系统 一、项目介绍1. 游客功能2. 管理员功能3. 高级功能 二、项目技术栈三、项目运行四、项目演示总结 大家好,这里是程序猿代码之路!随着旅游业的快速发展,民宿作为一种独特的住宿方式越来越受到游客的喜爱。为了提升用…...

PgSQL技术内幕 - psql与服务端连接与交互机制
PgSQL技术内幕 - 客户端psql与服务端连接与交互机制 简单来说,PgSQL的psql客户端向服务端发起连接请求,服务端接收到请求后,fork出一个子进程,之后由该子进程和客户端进行交互,处理客户端的SQL等,并将结果返…...

实现开发板三盏灯点亮熄灭
实现开发板三盏灯点亮熄灭 typedef struct {volatile unsigned int MODER; // 0x00volatile unsigned int OTYPER; // 0x04volatile unsigned int OSPEEDR; // 0x08volatile unsigned int PUPDR; // 0x0Cvolatile unsigned int IDR; // 0x10volatile unsigned int OD…...

外汇天眼:盈透证券为客户提供更丰富的欧洲衍生品交易渠道
电子交易巨头盈透证券(纳斯达克代码:IBKR)今日宣布,通过Cboe欧洲期权交易所(CEDX)新增欧洲股票期权和欧洲指数期货及期权。这一新增功能使得盈透证券的客户可以在单一统一平台上,除了股票、期权…...

论文阅读Rolling-Unet,卷积结合MLP的图像分割模型
这篇论文提出了一种新的医学图像分割网络Rolling-Unet,目的是在不用Transformer的前提下,能同时有效提取局部特征和长距离依赖性,从而在性能和计算成本之间找到良好的平衡点。 论文地址:https://ojs.aaai.org/index.php/AAAI/article/view/2…...

Linux Shell命令vim使用
一、引例 以判断引出(学过C其他语言容易接受)。 简单命令说明: -e 测试文件是否存在 -f 测试文件是否为普通文件 -d 测试文件是否为目录 -r 测试当前用户对某文件是否具有“可读”权限 -w 测试当前用户对某文件是否具有“可写”权限…...

如何将 API 管理从 Postman 转移到 Apifox
上一篇推文讲到用 Swagger 管理的 API 怎么迁移到 Apifox,有许多同学反馈说能不能介绍一下 Postman 的迁移以及迁移过程中需要注意的事项。那么今天,它来了! 从 Postman 迁移到 Apifox 的方法有两种: 导出 Postman 集合 &#x…...

用链表实现的C语言队列
一、队列概述 在数据结构中,队列是一种先进先出(FIFO)的线性表。它在许多应用场景中非常有用,例如任务调度、进程管理、资源管理等。队列是一种重要的数据结构,其主要特点是先进先出(FIFO, First In First …...

国产SDI视频均衡驱动器,功能与 LMH0387/LMH0344 一致
视频均衡驱动器,功能与 LMH0387 一致、LMH0344。本期间支持 DVB-ASI,作为驱动器能够选择输出速率,作为均衡接收器能支持100m以上传输距离(线缆类型Belden 1694A)。最大支持3Gbps 速率的信号 2 产品特征 a)…...