贪吃蛇双人模式设计(2)

2024-06-10 13:36
文章标签 设计 模式 贪吃蛇 双人

本文主要是介绍贪吃蛇双人模式设计(2),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

敲上瘾-CSDN博客
控制台程序设置_c语言控制程序窗口大小-CSDN博客
贪吃蛇小游戏_贪吃蛇小游戏csdn-CSDN博客​​​​​​​

一、功能实现:

  1. 玩家1使用↓ → ← ↑按键来操作蛇的方向,使用右Shift键加速,右Ctrl键减速
  2. 玩家2使用W  A  S  D按键来操作蛇的方向,使用左Alt键加速,C键减速
  3. 任意玩家点击空格键游戏暂停
  4. 若其中蛇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;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)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1048310

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

怎么让1台电脑共享给7人同时流畅设计

在当今的创意设计与数字内容生产领域,图形工作站以其强大的计算能力、专业的图形处理能力和稳定的系统性能,成为了众多设计师、动画师、视频编辑师等创意工作者的必备工具。 设计团队面临资源有限,比如只有一台高性能电脑时,如何高效地让七人同时流畅地进行设计工作,便成为了一个亟待解决的问题。 一、硬件升级与配置 1.高性能处理器(CPU):选择多核、高线程的处理器,例如Intel的至强系列或AMD的Ry

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机

模版方法模式template method

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/template-method 超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。 上层接口有默认实现的方法和子类需要自己实现的方法

SprinBoot+Vue网络商城海鲜市场的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍:CSDN认证博客专家,CSDN平台Java领域优质创作者,全网30w+

【iOS】MVC模式

MVC模式 MVC模式MVC模式demo MVC模式 MVC模式全称为model(模型)view(视图)controller(控制器),他分为三个不同的层分别负责不同的职责。 View:该层用于存放视图,该层中我们可以对页面及控件进行布局。Model:模型一般都拥有很好的可复用性,在该层中,我们可以统一管理一些数据。Controlller:该层充当一个CPU的功能,即该应用程序

迭代器模式iterator

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/iterator 不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素

《x86汇编语言:从实模式到保护模式》视频来了

《x86汇编语言:从实模式到保护模式》视频来了 很多朋友留言,说我的专栏《x86汇编语言:从实模式到保护模式》写得很详细,还有的朋友希望我能写得更细,最好是覆盖全书的所有章节。 毕竟我不是作者,只有作者的解读才是最权威的。 当初我学习这本书的时候,只能靠自己摸索,网上搜不到什么好资源。 如果你正在学这本书或者汇编语言,那你有福气了。 本书作者李忠老师,以此书为蓝本,录制了全套视频。 试

利用命令模式构建高效的手游后端架构

在现代手游开发中,后端架构的设计对于支持高并发、快速迭代和复杂游戏逻辑至关重要。命令模式作为一种行为设计模式,可以有效地解耦请求的发起者与接收者,提升系统的可维护性和扩展性。本文将深入探讨如何利用命令模式构建一个强大且灵活的手游后端架构。 1. 命令模式的概念与优势 命令模式通过将请求封装为对象,使得请求的发起者和接收者之间的耦合度降低。这种模式的主要优势包括: 解耦请求发起者与处理者