贪吃蛇双人模式设计(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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/25471.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

matlab演示地月碰撞

代码 function EarthMoonCollisionSimulation()% 初始化参数earth_radius 6371; % 地球半径&#xff0c;单位&#xff1a;公里moon_radius 1737; % 月球半径&#xff0c;单位&#xff1a;公里distance 384400; % 地月距离&#xff0c;单位&#xff1a;公里collision_tim…

Astar路径规划算法复现-python实现

# -*- coding: utf-8 -*- """ Created on Fri May 24 09:04:23 2024"""import os import sys import math import heapq import matplotlib.pyplot as plt import time 传统A*算法 class Astar:AStar set the cost heuristics as the priorityA…

Kafka集成flume

1.flume作为生产者集成Kafka kafka作为flume的sink&#xff0c;扮演消费者角色 1.1 flume配置文件 vim $kafka/jobs/flume-kafka.conf # agent a1.sources r1 a1.sinks k1 a1.channels c1 c2# Describe/configure the source a1.sources.r1.type TAILDIR #记录最后监控文件…

基于python-CNN深度学习的水瓶是否装满水识别-含数据集+pyqt界面

代码下载地址&#xff1a; https://download.csdn.net/download/qq_34904125/89374853 本代码是基于python pytorch环境安装的。 下载本代码后&#xff0c;有个requirement.txt文本&#xff0c;里面介绍了如何安装环境&#xff0c;环境需要自行配置。 或可直接参考下面博文…

绘唐2.5一键追爆款2.5免费版

免费分享给您 小说推文工具是一种用于在社交媒体上宣传和推广小说的工具。它可以帮助作者将小说的内容和相关信息以推文的形式快速发布在各种社交媒体平台上&#xff0c;吸引读者的注意力并增加小说的曝光度。 以下是一些小说推文工具可能具备的功能&#xff1a; 1. 编辑和排…

【linux】进程控制——进程创建,进程退出,进程等待

个人主页&#xff1a;东洛的克莱斯韦克-CSDN博客 祝福语&#xff1a;愿你拥抱自由的风 相关文章 【Linux】进程地址空间-CSDN博客 【linux】详解linux基本指令-CSDN博客 目录 进程控制概述 创建子进程 fork函数 父子进程执行流 原理刨析 常见用法 出错原因 进程退出 概…

MyBatis-Plus学习总结

一.快速入门 (一)简介 MyBatis-Plus (opens new window)&#xff08;简称 MP&#xff09;是一个 MyBatis (opens new window) 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 (二)快速入门 1.准备数据库脚本 2.准备bo…

六、Docker Swarm、Docker Stack和Portainer的使用

六、Docker swarm和Docker stack的使用 系列文章目录1.Docker swarm1.简介2.docker swarm常用命令3.docker node常用命令4.docker service常用命令5.实战案例6.参考文章 2.Docker stack1.简介3.Docker stack常用命令4.实战案例5.常见问题及调错方式1.查看报错信息并尝试解决&am…

SpringBootWeb 篇-深入了解 Redis 五种类型命令与如何在 Java 中操作 Redis

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 Redis 概述 1.1 Redis 下载与安装 2.0 Redis 数据类型 3.0 Redis 常见五种类型的命令 3.1 字符串操作命令 3.2 哈希操作命令 3.3 列表操作命令 3.4 集合操作命令 …

7-43 排列问题

排列问题 分数 10 全屏浏览 切换布局 作者 雷丽兰 单位 宜春学院 全排列问题 输出自然数1至n中n个数字的全排列&#xff08;1≤n≤9&#xff09;&#xff0c;要求所产生的任一数字序列中不允许出现重复的数字。 输入格式: 一个自然数 输出格式: 由1到n中n个数字组成的…

Tessy学习系列(三):单元测试——官方例程isValueInRange

一、工程创建 &#xff08;1&#xff09;新建工程 注意&#xff1a;工程名称以及路劲不能包含空格和中文 &#xff08;2&#xff09;新建测试集与单元测试模块 新建测试集 新建单元测试模块 设置测试模块为单元测试模块并选择GNU GCC编译器如果需要其他的编译器&#xff0c;…

关于选择,关于处事

一个人选择应该选择的是勇敢&#xff0c;选择不应该选择的是无奈。放弃&#xff0c;不该放弃的是懦夫&#xff0c;不放弃应该放弃的是睿智。所以&#xff0c;碰到事的时候要先静&#xff0c;先不管什么事&#xff0c;先静下来&#xff0c;先淡定&#xff0c;先从容。在生活里要…

【线性代数】向量空间,子空间,向量空间的基和维数

向量空间 设V为n维向量的集合&#xff0c;如果V非空&#xff0c;且集合V对于向量的加法以及数乘两种运算封闭&#xff0c;那么就称集合V为向量空间 x&#xff0c;y是n维列向量。 x 向量组等价说明可以互相线性表示 向量组等价则生成的向量空间是一样的 子空间 例题18是三位向…

Docker Swarm持久化

Docker Swarm持久化 1 简介 Docker Swarm持久化有bind、volume和NFS三种方式&#xff0c;bind和volume两种方式适合挂载单个宿主机&#xff0c;不适合集群&#xff1b;NFS适合集群服务&#xff0c;但需要安装NFS系统。 注意&#xff1a;Docker Swarm需要先安装集群。 由Doc…

python-数字黑洞

[题目描述] 给定一个三位数&#xff0c;要求各位不能相同。例如&#xff0c;352是符合要求的&#xff0c;112是不符合要求的。将这个三位数的三个数字重新排列&#xff0c;得到的最大的数&#xff0c;减去得到的最小的数&#xff0c;形成一个新的三位数。对这个新的三位数可以重…

【数据结构】【版本1.0】【线性时代】——顺序表

快乐的流畅&#xff1a;个人主页 个人专栏&#xff1a;《算法神殿》《数据结构世界》《进击的C》 远方有一堆篝火&#xff0c;在为久候之人燃烧&#xff01; 文章目录 引言一、顺序表的概念1.1 最基础的数据结构&#xff1a;数组1.2 数组与顺序表的区别 二、静态顺序表三、动态…

error while loading shared libraries 找不到动态库问题如何解决

在使用 c 或 c 开发应用时&#xff0c;在启动程序时&#xff0c;有时会遇到这个错误&#xff0c;找不到动态库。这个时候&#xff0c;我们使用 ldd 来查看&#xff0c;发现可执行文件依赖的动态库显示为 not found。 1 实验代码 使用如下 3 个文件做实验。 hello.h 中声明了函…

【Vue】修改数量

文章目录 底部总价展示完整代码 注意&#xff1a;前端 vuex 数据&#xff0c;后端数据库数据都要 注册点击事件 页面中dispatch action 提供action函数 提供mutation处理函数 底部总价展示 提供getters 动态渲染 完整代码 main.js import Vue from vue import App from…

Linux:基础开发工具

文章目录 Linux 软件包管理器 yum什么是软件包关于rzsz查看软件包安装软件卸载软件安装扩展源 Linux 编辑器 vimvim的基本概念正常/普通/命令模式(Normal mode)插入模式(Insert mode)底行模式(last line mode) vim的基本操作[命令模式]切换至[插入模式][插入模式]切换至[命令模…

【CW32F030CxTx StartKit开发板】开发资料

本来是参加21ic的评测活动&#xff0c;不知道为什么评测文章一直被提示有不良内容&#xff0c;所以只好先在此记录一下相关的资料。 此次测试的是CW32F030CxTxStartKit 评估板。该开发板为用户提供一种经济且灵活的方式使用 CW32F030CxTx 芯片构建系统原型&#xff0c;可进行性…