C++实现简易(多人弹幕控制主播游戏人物类型,CMD_迷宫小游戏)(一)

  1.原因:

因疫情原因,很多公司的年会取消了,修改为线上。就像线上有什么好玩的,突然想到外国主播,让水友发弹幕来控制游戏人物行动这件事情。感觉还挺好玩的。

就想是怎么做出来的。可以做一个这种类型的小游戏,如果公司年会线上的话,可以让不同团队来控制这个小人物,看看那个团队的统一性,凝聚力最强。(原理是一段时间内,得到命令最多的那个命令,便是角色下一步的行动方向),可以扩展为web类型,或者QT,MFC做出UI都可以。

2.原理:

Game类,绘制游戏界面,逻辑运动处理等。

Client类,发送接收界面的命令,并链接Game联动出画面效果

Common文件,设置公共的消息类型,数据,方法等

Service类,服务器类,响应客户端发出的命令,并作出筛选,发出给所有客户端同步绘制

3.实现:

Game01.h
#pragma once
#include<cstdio>
#include<string>
#include"Common.h"
#define MAP_VALUE 50
using namespace std;
class Game01
{
public:
	Game01();
	~Game01();
	void init(); //初始化地图和坐标数据
	void createMap();  //随机生成地图
	void refreshMap(NodeData *data); //刷新地图,重绘当前位置

	bool MapIsOk();    //判断生成地图是否可用,不可用则完善地图通道,使其可通过
	bool IsSuccess(); // 是否到达终点

	void GetPositionData(int* command);  //获取服务器发送数据,并转换为下一步坐标

	void SetPosittionData(); // 给服务器发送下一步命令

	void PrintfMap(); //输出地图
	void setNewX(int x)
	{
		m_newX = x;
	}

	void setNewY(int y)
	{
		m_newY = y;
	}

	int getNewX()
	{
		return m_newX;
	}

	int getNewY()
	{
		return m_newY;
	}
private:
	int m_newX; //当前X坐标
	int m_newY; //当前Y坐标

	int m_beginX;  //起始X,Y
	int m_beginY;

	int m_endX;  //终点X,Y
	int m_endY; 

	int m_MAP[MAP_VALUE][MAP_VALUE];
};

Game02.cpp
#include "Game01.h"
#include <ctime>
#include"Client.h"

Game01::Game01()
{
	//初始化地图和坐标数据
	m_newX = 0;
	m_newY = 0;
	m_beginX = 0;
	m_beginY = 0;
	m_endX = 0;
	m_endY = 0;

	memset(m_MAP, 0, sizeof(m_MAP));
}
Game01::~Game01()
{
}

void Game01::init() 
{

	createMap();  //生成地图
	//if (MapIsOk())  //完善地图通道
	//{
	//	int command[2] = { 0 };
	//	while (!IsSuccess())   //未到达终点
	//	{
	//		 GetPositionData(command);
	//		 if (command[0] == m_newX && command[1] == m_newY)
	//		 {
	//			 continue;  //与当前坐标一致,则不移动
	//		 }
	//		 else
	//		 {
	//			 m_newX = command[0];  //接受服务器数据重新定位的当前位置
	//			 m_newY = command[1];
	//			 //refreshMap();
	//		 }
	//	}
	//}

}
void Game01::createMap()  //随机生成地图
{
	int64_t new_time = 0;
	int randVlaue = 0;

	int commont[2] = { 0 };

	m_newY = 1;
	m_beginY = 1;
	m_endX = 48;
	m_endY = 49;
	m_MAP[0][1] = 0;  //起始位置
	m_MAP[48][49] = 0;  //终点位置

	while (!IsSuccess())   //未到达终点)
	{
		GetPositionData(commont);
		if (commont[0] == m_newX && commont[1] == m_newY)
		{
			continue;  //与当前坐标一致,则不移动
		}
		else if (commont[0] == m_endX && commont[1] == m_endY)
		{
			m_newX = commont[0];  //接受服务器数据重新定位的当前位置
			m_newY = commont[1];
			m_MAP[m_newX][m_newY] = 2;
		}
		else if ((commont[0] <= 0 || commont[0] >= 49 || commont[1] <= 0 || commont[1] >= 49))
		{
			continue;
		}
		//else if (m_MAP[commont[0]][commont[1]] == 2)
		//{
		//	continue;
		//}
		else
		{
			//if(0)
			m_newX = commont[0];  //接受服务器数据重新定位的当前位置
			m_newY = commont[1];
			m_MAP[m_newX][m_newY] = 2;
			//refreshMap();
		}
	}

	for (int i = 0; i < MAP_VALUE; i++)
	{
		for (int j = 0; j < MAP_VALUE; j++)
		{
			if (i == 0 || i == 49 || (i > 0 && j == 0) || (i > 0 && j == 49)) 
			{
				m_MAP[i][j] = 1;
			}
			else 
			{
				//srand((int)time(0));  // 产生随机种子  把0换成NULL也行
				new_time = random(10000000);//GetSysTimeMicros();
				randVlaue = random(100);

				if(m_MAP[i][j] != 2)
				m_MAP[i][j] = ((new_time * randVlaue) % 37 > 17 ? 0 : 1);  //随机生成迷宫数据  // 取余数 暂且可以预留为 难度标准
				//○
			}
			
		}
	}

	//m_newY = random(49);
	//m_beginY = m_newY;
	//m_endX = random(49);
	//m_endY = 49;
	//m_MAP[0][m_beginY] = 0;  //起始位置
	//m_MAP[m_endX][49] = 0;  //终点位置
	m_newX = 0;
	m_newY = 1;
	m_beginY = 1;
	m_endX = 48;
	m_endY = 49;
	m_MAP[0][1] = 0;  //起始位置
	m_MAP[48][49] = 0;  //终点位置


	m_MAP[m_newX][m_newY] = 3;
	PrintfMap();
}

void Game01::PrintfMap() //输出地图  0 可移动路径  1 墙壁 2 可通行路径 3 当前位置
{
	system("cls");
	for (int i = 0; i < MAP_VALUE; i++)
	{
		for (int j = 0; j < MAP_VALUE; j++)
		{
			if (m_MAP[i][j] == 1)
			{
				printf("■");
			}
			else if (m_MAP[i][j] == 2)
			{
				//printf("○");
				printf("  ");
			}
			else if (m_MAP[i][j] == 3)
			{
				printf("○");
			}
			else 
			{
				printf("  ");
			}
		}
		printf("\n");
	}
}

void Game01::refreshMap(NodeData *data) //刷新地图,重绘当前位置
{
	char massage = data->massgeData[0];
	//给后出现的客户端统一数据
	//m_newX = data->New_X;
	//m_newY = data->New_Y;
	//int command
	if (data->massage == MassgeType::COMMAND && data->massgeData == "OK")//是否成功
	{
		printf("成功了\n");
	}
	if (data->massage == MassgeType::DIRECTION)
	{
		switch (massage)
		{
		case 'W':
		case 'w':
			if (m_newX > 0 && m_MAP[m_newX - 1][m_newY] != 1) {
				m_newX = m_newX - 1;
				m_MAP[m_newX + 1][m_newY] = 2;
			}
			break;
		case 'S':
		case 's':
			if (m_newX < 49 && m_MAP[m_newX + 1][m_newY] != 1) {
				m_newX = m_newX + 1;
				m_MAP[m_newX - 1][m_newY] = 2;
			}
			break;
		case 'A':
		case 'a':
			if (m_newY > 0 && m_MAP[m_newX][m_newY - 1] != 1) {
				m_newY = m_newY - 1;
				m_MAP[m_newX][m_newY + 1] = 2;
			}
			break;
		case 'D':
		case 'd':
			if (m_newY < 49 && m_MAP[m_newX][m_newY + 1] != 1) {
				m_newY = m_newY + 1;
				m_MAP[m_newX][m_newY - 1] = 2;
			}
			break;
		}
		m_MAP[m_newX][m_newY] = 3;
		PrintfMap();
		IsSuccess();
	}
}

bool Game01::MapIsOk()    //判断生成地图是否可用,不可用则完善地图通道,使其可通过
{
	bool flag = false;
	int randVlaue = random(4);
	while (m_newX != m_endY && m_newY != m_endY)   //未到达终点
	{
		randVlaue = random(4);
		switch (randVlaue % 4)
		{
			case 0:
				break;
			case 1:
				break;
			case 2:
				break;
			case 3:
				break;
		}
	}
	return flag;
}
bool Game01::IsSuccess() // 是否到达终点
{
	bool flag = false;

	if (m_newX == m_endX && m_newY == m_endY) // 到达终点,给服务器发送OK数据
	{
		SetPosittionData();
		flag = true;
	}
	return flag;
}

 void Game01::GetPositionData(int* command)  //获取服务器发送数据,并转换为下一步坐标
{
	//int command[2] = {0};   //0为X,1为Y

	 //调用客户端类的单例  获取到massage   w a s d


	 char massge=' ';
	 //  srand((int)time(0));  // 产生随机种子  把0换成NULL也行
	 int randVlaue = random(100000)%10;
	 if (randVlaue == 0 || randVlaue == 1)
		 massge = 'w';
	 else if (randVlaue == 2 || randVlaue == 3 || randVlaue == 4)
		 massge = 's';
	 else if (randVlaue == 6 || randVlaue == 5)
		 massge = 'a';
	 else// (randVlaue == 3)
		 massge = 'd';


	 //if (randVlaue == 0 )
		// massge = 'w';
	 //else if (randVlaue == 1 /*|| randVlaue == 4*/)
		// massge = 's';
	 //else if (randVlaue ==2)
		// massge = 'a';
	 //else// (randVlaue == 3)
		// massge = 'd';

	 switch (massge)
	 {
		 case 'W':
		 case 'w':
			 command[0] = m_newX;
			 command[1] = m_newY - 1;
			 break;
		 case 'S':
		 case 's':
			 command[0] = m_newX;
			 command[1] = m_newY + 1;
			 break;
		 case 'A':
		 case 'a':
			 command[0] = m_newX -1;
			 command[1] = m_newY;
			 break;
		 case 'D':
		 case 'd':
			 command[0] = m_newX + 1;
			 command[1] = m_newY;
			 break;
	 }
}

void Game01::SetPosittionData() // 给服务器发送下一步命令
{
	//调用客户端类的单例
	NodeData data;
	data.massage = MassgeType::COMMAND;
	data.massgeData = "SUCCESS";
	Client::GetInfrance()->SendData(&data);
}
Client.h
#pragma once
//  从 Windows 头文件中排除极少使用的信息
#define WIN32_LEAN_AND_MEAN  
#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include<stdio.h>
#include<string>
#ifdef _WIN32
#include<WinSock2.h>
#include<Windows.h>

#else
#include<unistd.h>
#include<arpa/inet.h>
#include<string.h>

#define SOCKET int
#define SOCKET_ERROR -1
#endif

#pragma comment(lib,"ws2_32.lib")
#include"Common.h"
using namespace std;

class Client
{
public:
	void Init();

	void Connect();
	void Close();
	void SendData(NodeData *data);
	NodeData* RecvData();
	static Client* GetInfrance();
	static Client* m_client;
private:
	Client() {}
	SOCKET sock;
	sockaddr_in sockadd;
	bool isClose = false;
	NodeData nodeData;
};

Client.cpp
#include "Client.h"
Client* Client::m_client = nullptr;

void Client::Init()
{
	if (!isClose)
	{
		//启动windows socket 
#ifdef _WIN32
		WORD var = MAKEWORD(2, 2);
		WSADATA dar;
		WSAStartup(var, &dar);
#endif
		sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		sockadd.sin_family = AF_INET;
		sockadd.sin_port = htons(6666);
#ifdef _WIN32
		sockadd.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");  //转换IP地址为网络字节数据
#else
		sockadd.sin_addr.s_addr = inet_addr("192.168.126.128"); //192.168.128.1
#endif
	}
}

Client* Client::GetInfrance()
{
	if (m_client == nullptr)
	{
		m_client = new Client();
	}
	return m_client;
}

void Client::Connect()
{
	if (!isClose)
	{
		//连接服务器
		if (SOCKET_ERROR == connect(sock, (const sockaddr*)&sockadd, sizeof(sockaddr_in)))
		{
			printf("错误,连接服务器失败!\n");
		}
		else
		{
			printf("连接服务器成功!\n");
		}
	}

}

void Client::Close()
{
	//关闭套接字
	//getchar();
#ifdef _WIN32
	closesocket(sock);
	WSACleanup();
#else
	close(sock);
#endif
	isClose = true;
}

void Client::SendData(NodeData *data)
{
	if (!isClose)
	{
		if (data != nullptr)
		{
			send(sock, (char*)data, sizeof(NodeData), 0);
		}
	}
}

NodeData* Client::RecvData()
{
	if (!isClose)
	{
		memset(&nodeData,0,sizeof(NodeData));
		//接收发送数据
		if (recv(sock, (char*)&nodeData, sizeof(NodeData), 0) > 0)
		{
			printf("服务端发送的数据,IP:%s\n", nodeData.massgeData.c_str());
			return &nodeData;
		}
	}
	return nullptr;
}
Common.h

#pragma once

#include<string>

#define random(x) rand()%(x)

enum MassgeType
{
	COMMAND=0,   //命令
	DIRECTION,   //方向
};

typedef struct NodeData
{
	MassgeType massage = COMMAND;   //消息类型
	std::string massgeData;   //消息数据
	int New_X;
	int New_Y;
};

Service.cpp  //懒得封装了,直接干在一个文件里面
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
    #include<Windows.h>
    #include<WinSock2.h>

    #pragma comment(lib,"ws2_32.lib")
#else
    #include<unistd.h>
    #include<arpa/inet.h>
    #include<string.h>

    #define SOCKET int
    #define SOCKET_ERROR -1
    #define INVALID_SOCKET (SOCKET)(~0)
#endif
#include<vector>
#include<map>
#include<string>

#define random(x) rand()%(x)
using namespace std;
enum MassgeType
{
	COMMAND = 0,   //命令
	DIRECTION,   //方向
};

typedef struct NodeData
{
	MassgeType massage = COMMAND;   //消息类型
	std::string massgeData;   //消息数据
	int New_X;
	int New_Y;
};


std::vector<SOCKET> g_socket;   //总的连接客户端
//std::vector<SOCKET> g_socket_Client;  //发送数据的客户端
std::map<std::string, int> MessageList;

int MessageData(SOCKET socket)
{
	static long time_stamp = GetCurrentTime();//getCurrentTime为上面介绍的函数;
	static int count1, count2, count3, count4 = 0;
	static int new_X, new_Y = 0;   //第一个客户端当前的XY坐标
	//==========================================================
	//要执行的代码
		//缓冲区
	char szRecv[1024] = { 0 };
	int nLen = recv(socket, szRecv, sizeof(NodeData), 0);
	NodeData *data = (NodeData*)szRecv;
	if (nLen <= 0)
	{
		printf("客户端已退出\n");
		return -1;
	}

	if (socket == g_socket[0])
	{
		new_X = data->New_X;
		new_Y = data->New_Y;
	}
	//if (g_socket_Client.end() == find(g_socket_Client.begin(), g_socket_Client.end(), socket))  //当前集合中不存在时,加入
	//{
	//	g_socket_Client.push_back(socket);
	//}
	
	if (data->massage == DIRECTION)
	{
		//消息发送接受处理
		std::string sss = data->massgeData;
		
		switch (sss[0])
		{
		case 'W':
		case 'w':
			MessageList.insert(pair<std::string, int>(sss, count1++));
			break;
		case 'S':
		case 's':
			MessageList.insert(pair<std::string, int>(sss, count2++));
			break;
		case 'A':
		case 'a':
			MessageList.insert(pair<std::string, int>(sss, count3++));
			break;
		case 'D':
		case 'd':
			MessageList.insert(pair<std::string, int>(sss, count4++));
			break;
		}
		//printf("客户端发送的数据,IP:%d , Data:%s, X:%d, Y:%d\n", socket, data->massgeData.c_str(), data->New_X,data->New_Y);
	}
	else if (data->massage == COMMAND && data->massgeData=="SUCCESS")
	{
		NodeData recvData;
		recvData.massage = COMMAND;
		recvData.massgeData = "OK";
		recvData.New_X = new_X;
		recvData.New_Y = new_Y;
		for (int i = 0; i < g_socket.size(); i++)
		{
			send(g_socket[i], (char*)&recvData, sizeof(NodeData), 0);
		}
		printf("服务端发送的数据,IP:%d , Data:%s\n", socket, recvData.massgeData.c_str());
		return 1;
	}

	//==========================================================

	//计算睡眠时间(毫秒),保证每隔500ms循环一次,发送一次客户端需求最多的命令 是客户端实行
	int dis = GetCurrentTime() - time_stamp;
	if (dis >= 500 && MessageList.size() > 0) {
		//求四个方向最多的数 即为下一步方向
		NodeData recvData;
		recvData.massage = DIRECTION;
		recvData.New_X = new_X;
		recvData.New_Y = new_Y;
		int num = max(max(max(count1, count2), count3), count4);
		if (num == count1)
		{
			recvData.massgeData = "W";
		}
		else if (num == count2)
		{
			recvData.massgeData = "S";
		}
		else if (num == count3)
		{
			recvData.massgeData = "A";
		}
		else if (num == count4)
		{
			recvData.massgeData = "D";
		}

	//	if (g_socket.size() >= 10)  //压测代码
	//	{
			for (int i = 0; i < g_socket.size(); i++)
			{
				send(g_socket[i], (char*)&recvData, sizeof(NodeData), 0);
				printf("服务端发出的数据,IP:%d , Data:%s\n", g_socket[i], recvData.massgeData.c_str());
			}
		//}
		
		count1 = 0;
		count2 = 0;
		count3 = 0;
		count4 = 0;
		MessageList.clear();
		time_stamp = GetCurrentTime();//getCurrentTime为上面介绍的函数;
	}

	return 1;
}

int main()
{
#ifdef _WIN32
    //启动windows socket 
    WORD var = MAKEWORD(2, 2);
    WSADATA dar;
    WSAStartup(var, &dar);
#endif
    SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    sockaddr_in sockadd;
    sockadd.sin_family = AF_INET;
    sockadd.sin_port = htons(6666);
    SOCKET maxsock = sock;
#ifdef _WIN32
    sockadd.sin_addr.S_un.S_addr = INADDR_ANY;     //inet_addr("127.0.0.1");  转换IP地址为网络字节数据
#else
    sockadd.sin_addr.s_addr = INADDR_ANY;
#endif
    //绑定socket
    if (SOCKET_ERROR == bind(sock, (sockaddr*)&sockadd, sizeof(sockaddr_in)))
    {
        printf("错误,绑定端口失败!\n");
    }
    else
    {
        printf("绑定端口成功!\n");
    }
    //开启监听
    if (SOCKET_ERROR == listen(sock, 10))
    {
        printf("错误,监听端口失败!\n");
    }
    else
    {
        printf("监听端口成功!\n");
    }

    while (true)
    {
        //开启select 等待消息
        fd_set dataRead;
        fd_set dataWrite;
        fd_set dataError;
        timeval t_val = { 1,0 };

        //初始化
        FD_ZERO(&dataRead);
        FD_ZERO(&dataWrite);
        FD_ZERO(&dataError);

        //绑定到sock
        FD_SET(sock, &dataRead);
        FD_SET(sock, &dataWrite);
        FD_SET(sock, &dataError);

        //将socket集合中的socket添加到可读集合中
        for (int i = 0; i < (int)g_socket.size(); i++)
        {
            FD_SET(g_socket[i], &dataRead);
            if (maxsock < g_socket[i])
                maxsock = g_socket[i];
        }
        int ret = select(maxsock + 1, &dataRead, &dataWrite, &dataError, &t_val);
        if (ret < 0)
        {
            //有错误
            printf("错误,select出错...\n");
            //            int nErr = WSAGetLastError();
              //          printf("select: %d, %s\n", nErr, strerror(errno));
                        //break;
        }

        //判断sock是否可读,有则,游客户端接入
        if (FD_ISSET(sock, &dataRead))
        {
            FD_CLR(sock, &dataRead);
            //accept 等待接收客户端连接
            sockaddr_in sockadd_Client = {};
            int clientLen = sizeof(sockaddr_in);
            //接收客户端信息的socket
            SOCKET _cSocket = INVALID_SOCKET;
#ifdef _WIN32
            _cSocket = accept(sock, (sockaddr*)&sockadd_Client, &clientLen);
#else
            _cSocket = accept(sock, (sockaddr*)&sockadd_Client, (socklen_t*)&clientLen);
#endif
            if (INVALID_SOCKET == _cSocket)
            {
                printf("错误,接收到无效客户端socket...\n");
            }
            printf("新客户段接入,socketID:%d,IP:%s \n", (int)_cSocket, inet_ntoa(sockadd_Client.sin_addr));
            char buff[128] = { 0 };
            strcpy(buff, inet_ntoa(sockadd_Client.sin_addr));

            for (int i = 0; i < (int)g_socket.size(); i++)
            {
                send(g_socket[i], buff, 128, 0);
            }
            g_socket.push_back(_cSocket);
        }

		//消息处理
		for (int i = 0; i < (int)dataRead.fd_count; i++)
		{
			if (-1 ==  MessageData(dataRead.fd_array[i]))  //客户端无消息
			{
				auto iter = find(g_socket.begin(), g_socket.end(), dataRead.fd_array[i]);
				if (iter != g_socket.end())  //但是sockrt在连接列表里面  则移除
				{
					g_socket.erase(iter);
				}
			}
		}

       // printf("未处理网络请求\n");

    }
    getchar();
#ifdef _WIN32
    //关闭套接字
    closesocket(sock);
    //接收发送数据
    WSACleanup();
#else
    close(sock);
#endif
    return 0;

}

4.运行

5:问题

 还存在很多问题,不过大体是实现了,测试10多个客户端也OK,上百个,电脑性能不行,怕GG,不过应该没啥问题。

1.多个客户端一起运行时,先运行的人物位置和后运行的人物位置不同步。

2.地图生成虽然是随机的,但是随机函数存在一定问题,所以地图经常是很相似,甚至不变的。

3.未实现客户端退出命令等,还是输入命令必须是asdw ,其他不识别。可以通过做UI时 button的点击等修改,也懒得完善了。

4.当然还有一些其他的问题,目前还没有发现,毕竟我不是测试。。。。。。。后面可能会有GAme02,Game03等。

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值