【项目实践】网络对战五子棋

我实现的网络对战五子棋实现的主要功能有:注册、登录、匹配、玩游戏,判定输赢。对于玩家(客户端)来说,肯定要能进行注册,登录,匹配,玩游戏等环节,一轮游戏结束也要知道输赢。这些都是通过rpc接口,去调用服务器上的相关函数接口。

rpc的作用?

  • rpc是在分布式系统中,通常用来解决服务与服务或者主机与主机之间调用的问题。
  • rpc是便于进行远程之间,多主机之间互相调用对方的方式的概念。


rpc的优点

  • 远程调用时,能够像本地调用时一样方便,让调用者感知不到远程调用的逻辑。
  • 通过对rpc的封装,让我们能够像调本地函数一样去调rpc接口,rpc接口本质上是把业务逻辑或核心代码的运行过程全部放在远端服务器上进行,但是在用户看来,像本地调用,这样的话,会让客户端的工作量越来越小,rpc的设计可以让我们的接口代码写起来更从容,更简单一些。

使用rpc框架的原因?

  1. 我们要实现网络通信,就要解决:①网络通信的问题(通过tcp/udp协议搞定);②协议的定制 ;③为了考虑数据格式的问题,就得考虑序列化和反序列化。为了把重点高度集中在五子棋的业务逻辑上,所以才使用了rest_rpc。rest_rpc是一个轻量化的rpc框架,市面上还有一些其他的rpc框架,如百度开源的rpc框架 sofa和 swift。rest_rpc是中国人写的,非常轻,只要下载下来就能使用,但是也有一些基本的要求,它的语法用了c++11和c++17的特性,所以我们要编译成功,就需要使用高版本的编译器(gcc7.1以上)。
  2. rpc底层用了boost库的网络代码,boost库的server I/O解决网络问题,rpc只专注于远程过程调用,我们只写游戏逻辑。

游戏匹配算法

我的游戏匹配算法分两步:用户把自己的id放到匹配池中;服务器根据匹配池的内容把用户id匹配好。
匹配算法:先获得所有的匹配id,它的核心理念基于将所有的待匹配的id,先以vector套vector的方式把他们组织起来,然后再把所有的内容组织好的结构整合在一个vector里,这样,凡是相同胜率的人都会在一起,所以接下来要做的动作就是将所有的用户两两匹配起来,还要考虑一个人会匹配失败的问题。

游戏是通过胜率进行匹配的

  1. 为了让匹配是ok的,把胜率只保留整数部分 [0,100];
  2. 匹配的话,速度一定要快,所以我的匹配算法要实现,匹配的时间复杂度为O(1),即只要一步,就快速的将所有人匹配成功。

大厅信息(Hall.hpp)

class Hall
{

    private:
        PlayerManager pm;//用户管理
        RoomManager rm;//房间管理
        vector<vector<uint32_t> > match_pool;//匹配池
        int match_num;//匹配用户的个数
        pthread_mutex_t match_lock;
        pthread_cond_t match_cond;
};

用户基本信息(Player.hpp)

#define TIMEOUT 20
using namespace std;

typedef enum
{
    OFFLINE = 0,//离线
    ONLINE,//刚刚登陆
    MATCHING,//匹配中
    PLAYING,//游戏中
}status_t;

class Player//玩家用户
{
    private:
        //基本信息
        string name;
        string passwd;
        uint32_t id;

        //游戏信息
        int win;//赢的次数
        int lose;//输的次数
        int tie;//平局的次数

        status_t st;//状态
        uint32_t room_id;//房间号

        //pthread_mutex_t lock;//一把锁
        //pthread_cond_t cond;//条件变量
};

房间信息(Room.hpp)

#define SIZE 5
#define BLACK 'X'//黑子
#define WHITE 'O'//白子

class Room
{
    private:
        uint32_t one;//指定one用户拿的是黑子'X'
        uint32_t two;//指定two用户拿的是白子'O'
        char piece[2];//规定0号下标对应one的棋子,1号下标对应two的棋子
        uint32_t current;//当前该谁走
        char board[SIZE][SIZE];//定义棋盘
        char result;//  X(one用户赢), O(two用户赢), E(平局),N(继续)

        pthread_mutex_t lock;//定义一把锁

};

用户管理信息(PlayerManager.hpp)


#define DEFAULT_ID 1000

class PlayerManager
{
    private:
        unordered_map<uint32_t,Player> player_set;
        uint32_t assign_id;//所有的用户id都是由assign_id分配的(每次加1)
        pthread_mutex_t lock;
};

房间管理信息(RoomManager.hpp)

#define ROOM_ID 1024
class RoomManager
{
    private:
        unordered_map<uint32_t,Room> room_set;
        int assign_id;//给用户分配房间
        pthread_mutex_t lock;
};

具体的匹配算法如下:
首先很明确,匹配是按胜率匹配,可是光有胜率肯定不行,还得知道是谁跟谁匹配,所以在五子棋这个游戏里,用胜率做第一步,用id把两个人匹配起来。定义一个二维的vector,即vector<vector<uint32_t>>的匹配池;

整个数组(大vector)里面有101个小vector,数组的下标代表胜率,每个小vector中放的都是概率相同的id,所以将来在匹配数据池时,插入到数据池中的时候,所有胜率一样的用户是会被放在同一个小vector中,所有胜率相同的用户,他们的id会在同一个vector里。

要匹配,要把数据放到数据池里,实际上就是拿着你的胜率去数组中找胜率对应的下标(不会越界),所以每一个小vector中的所有元素都是同胜率的id。

上面操作只是把用户信息放到了匹配池中,将来服务器端肯定有一个线程在不断地检测我们的匹配池里是不是有用户相匹配,如果匹配池里有用户相匹配,这里肯定要用一个线程进行周期性匹配,要匹配用户的话,就得把用户放在一起,服务器不仅要把用户信息放到匹配池里,还要将用户两两匹配起来,但是怎样把两个用户放在一起呢?

再定义一个vector<uint32_t>,然后遍历上面那个二维vector,从胜率为0的开始,也就是从0号下标开始,如果0号的vector为空,就去遍历胜率为1的vector,如果0号vector不为空,就依次把所有的元素压入新定义的vector中,之后再依次把胜率为1到100的每个vector中的所有元素id都压入到新的vector中。相当于我把之前那个二维vector中的所有元素按顺序从上到下依次压入到新的vector中,此时所有的id都会被放在新的vector中,这次放进来的id所有胜率相同的id是在一起的,所以我在匹配时,可以一次拿两个进行匹配。

具体各部分的接口实现

注册

用户注册时,通过输入自己的昵称和密码,把昵称和密码传给客户端本地的注册接口,然后本地的注册方法再通过rpc 远端调用服务器端的注册接口,在服务器完成用户信息的注册,此时用户的昵称信息保存在服务器上。

用户发起注册请求,客户端首先调用本地的注册(Register)函数,Register函数再内部调用远端服务器的RpcRegister函数,因为服务器提前注册好了这个RpcRegister函数,所以当客户端调用RpcRegister函数时,就会转为调用大厅(Hall)里面的Register函数,Hall里面的Register函数又会去调用用户管理(PlayerManager)里的InsertPlayer函数,InsertPlayer函数是将该用户的信息插入服务器的player_set里(player_set是一个unordered_map,是存储用户信息的数据结构,它构建了用户id和用户信息的映射关系),插入成功后就完成了用户的注册。当注册成功后,服务器会返回客户端一个id,此时用户就获得自己的id号了。

uint32_t InsertPlayer(string &name,string &passwd)//插入用户
        {
            Lock();
            uint32_t _id=assign_id++;//key值
            Player p(name,passwd,_id);//定义一个用户(value值)
            player_set.insert({_id,p});
            Unlock();
            LOG(INFO,"用户插入完毕....");

            return _id;
        }

     

登录

登录时,用户输入id和密码,把户输入id和密码传给本地登录函数,本地登录接口再通过rpc 远端调用服务器端的登录接口,进而完成用户信息的查找,如果用户信息存在,则登录成功。

用户发起登录请求,客户端首先调用本地的Login函数,Login函数内部调用远端服务器的RpcLogin,服务器提前注册好了这个RpcLogin函数,当客户端要调用RpcLogin函数时,就会转为调用Hall里面的Login函数,Hall里面的Login函数又会去调用PlayerManager里的SearchPlayer函数,SearchPlayer函数是在player_set里查找该用户的信息,如果找到了,就把该id的状态设置为上线状态(ONLINE),然后就返回这个用户id,客户端收到这个id时,判断id合法的时候就可以提示用户登录成功。但是如果SearchPlayer没有查找到,就返回对应的错误码(1.未注册 2.密码错误 3.连接超时)。

   uint32_t SearchPlayer(uint32_t id,string &passwd)//查找用户
        {
            Lock();
            auto it=player_set.find(id);
            if(it!=player_set.end())
            {
                Player &p=it->second;//拿到用户所对应的second信息
                if(p.GetPasswd()!=passwd)
                {
                    Unlock();
                    LOG(WARNING,"用户密码错误...");
                    return 2;//认证失败
                }

                p.Online();//把用户状态设置为上线状态
                Unlock();
                LOG(INFO,"用户进入上线状态...");
                return id;
            }
            else
            {
                LOG(WARNING,"用户不存在....");
                Unlock();
                return 1;//用户不存在
            }
        }

匹配

当用户登录成功会跳转至游戏界面,在游戏界面选择匹配,就会调用客户端的匹配(Match)函数,Match函数就会调用PushIdInMatchPool和CheckReady函数。
PushIdInMatchPool函数内部通过rpc去调用远端服务器的RpcPushIdInMatchPool,这个函数是把用户id放到服务端的匹配池里,然后把id的状态变为MATCHING,这里的匹配池是一个vector嵌套vector的结构,根据用户的胜率,把用户插入到match_pool对应胜率的小vector里,当插入完成之后,匹配人数加1,然后会唤醒服务器端的匹配线程进行匹配(MatchService)。

static void *MatchService(void* arg)//提供一个线程不断检测当前用户是否可以匹配
        {
            pthread_detach(pthread_self());//分离该线程
            Hall *hp=(Hall*)arg;
            while(1)
            {
                uint32_t last = 0;
                 hp->LockMatchPool();//加锁
                 while(hp->MatchNum() < 2)//循环检测(防止误唤醒)
                 {
                     LOG(INFO,"服务线程开始等待啦...");
                    hp-> ServiceWait();//等待
                 }
                 LOG(INFO,"服务线程被唤醒了...");
                 vector<uint32_t> id_list;
                 hp->GetAllMatchId(id_list);
                 //两两开始匹配
                 int num=id_list.size();
                 if(num & 1)
                 {
                     last = id_list[id_list.size()-1];
                     num &=(~1);//保证num为偶数
                 }
                 else
                 {
                     last = 0;
                 }
                 for(int i = 0;i <= num;i+=2)
                 {
                     uint32_t play_one = id_list[i];
                     uint32_t play_two = id_list[i+1];
                     hp->GamePrepare(play_one,play_two);//游戏准备
                 }
                 //匹配完毕后,要清空匹配池
                 hp->MatchPoolClear(last);//清空当前匹配池内容
                 hp->UnlockMatchPool();//解锁
            }
        }

匹配线程一直在后台运行,一开始把匹配线程设置为分离状态,让它不断检测匹配池的人数是否大于2,如果小于2,让服务线程等待,如果大于2,就把匹配池里的用户id按照胜率依次拿出来放到一个新的vector(id_list)里面(这样做目的就是尽量把胜率相同的人放在一起),两两进行匹配,匹配之后,开始游戏准备。

游戏准备(GamePrepare):给两个玩家用户创建游戏房间,并把他们的状态设置为PLAYING状态,但是如果之前的人数是奇数的话,就让最后一个人匹配失败,状态重新设置为ONLINE。这一轮的匹配完成之后,匹配线程就需要清空匹配池(MatchPoolClear),清空之后匹配线程又会继续跑起来,看下一轮匹配池里的人数是否大于2,然后再进行下一轮的匹配。

匹配完毕后,客户端会调用CheckReady函数检测用户是否匹配成功。CheckReady函数又会在内部通过rpc去调远端服务器的RpcPlayerReady,RpcPlayerReady又会去调用Hall里的IsPlayerReady,IsPlayerReady再调用PlayerManager里的Ready,Ready去调用Player里的status,最后层层返回用户的状态。如果客户端检测到玩家状态是PLAYING之后,就会告诉用户匹配成功,可以进行游戏了。如果是MATCHING,那就继续隔一段时间检测一次,如果是ONLING,就是匹配失败。

bool PushIdInMatchPool(uint32_t& id)//把用户id放入匹配池中
        {
            LOG(INFO,"把用户id放入匹配池中...");
            pm.SetMatching(id);//把当前用户设置为匹配状态
            int rate=pm.GetRate(id);
            cout << "rate: " << rate << endl;

            LockMatchPool();//加锁
            auto &v = match_pool[rate];//拿到了相同胜率的vector
            auto it = v.begin();
            while(it!=v.end())
            {
                if(*it == id)//说明已经有了
                    return false;
                ++it;
            }
            v.push_back(id);//把用户插入到匹配池中
            IncMatchNum();//递增match_num
            UnlockMatchPool();//解锁
            ServiceWakeup();//唤醒匹配线程

           return true;
            //用户要等匹配线程把它匹配成功
          // return  pm.PlayerWait(id);//让用户进行等待
        }

 

玩游戏

当玩家的房间创建好之后,玩家用户获得相应的棋子,进行游戏,每落一次子,对该棋盘进行判定看是否有玩家胜出。

玩游戏逻辑如下:
获取棋盘(GetBoard),获取到房间号(GetRoomId),  获取自己所拿棋子(GetPiece),打印棋盘(ShowBoard),然后判断是不是该我走(IsMyTurn),不该我走就等待,该我走时就输入落子位置,首先判断该位置是否合法(PosIsRight),如果合法,就落子(Step),然后获取游戏结果(Judge)。需要注意的是:打印棋盘和判断当前坐标是否合法是不用调用rpc接口的,在本地实现即可,因为我们是先从服务器上获取棋盘的,此时拉取到的数据已经最新数据,就可以直接在本地进行打印棋盘和判断输入位置是否合法。

游戏板块主流程

mian.cpp

#include <rpc_server.h>
#include <string>
#include "Hall.hpp"

using namespace rest_rpc;
using namespace rpc_service;
using namespace std;
  
Hall GameHall;//定义一个全局变量

uint32_t RpcRegister(connection* conn,string name,string passwd)
{
    //std::cout<<"获得一个注册请求:昵称->"<<name<<" 密码-> "<<passwd<< std::endl;
    return GameHall.Register(name,passwd);
}

uint32_t RpcLogin(connection* conn,uint32_t id,string passwd)
{
    return GameHall.Login(id,passwd);
}

bool RpcMatchAndWait(connection* conn,uint32_t id)//1.把自己的id放入匹配池并等待匹配
{
    return GameHall.PushIdInMatchPool(id);
}

int RpcPlayerReady(connection* conn,uint32_t id)//2.用户检查自己是否匹配成功
{
    return GameHall.IsPlayerReady(id);
}

string RpcBoard(connection* conn,uint32_t room_id)//5.获得棋盘
{
   return GameHall.GetPlayerBoard(room_id);
}

uint32_t RpcPlayerRoomId(connection* conn ,uint32_t id)//3.获得对应的房间号
{
    return GameHall.GetPlayerRoomId(id);
}

char RpcPlayerPiece(connection* conn ,uint32_t room_id,uint32_t id)//4.获得自己的棋子
{
    return GameHall.GetPlayerPiece(room_id,id);
}

bool RpcIsMyTurn(connection* conn,uint32_t room_id,uint32_t id)//6.玩游戏时,判断是否该自己走
{
    return GameHall.IsMyTurn(room_id,id);
}

void RpcStep(connection* conn,uint32_t room_id,uint32_t id,int x,int y)//6.用户开始下棋(也就是把特定的下标设置为对应的棋子即可)
{
     GameHall.Step(room_id,id,x,y);
}

char RpcJudge(connection* conn,uint32_t room_id,uint32_t id)//7.判定输赢(判定特定房间里的特定用户是否赢)
{
    return GameHall.Judge(room_id,id);
}

bool RpcPopMatchPool(connection* conn,uint32_t id)//8.把自己的id从匹配池拿走
{
    return GameHall.PopIdMatchPool(id);
}

int main() {
	rpc_server server(9001,4);
    LOG(INFO,"初始化服务器成功...");
    server.register_handler("RpcRegister",RpcRegister);//把注册方法注册进来
    server.register_handler("RpcLogin",RpcLogin);//登录
    server.register_handler("RpcMatchAndWait",RpcMatchAndWait);//匹配并等待
    server.register_handler("RpcPlayerReady",RpcPlayerReady);//检测用户是否已经就绪(就绪是指用户已经准备好开始游戏了)
    server.register_handler("RpcPlayerRoomId",RpcPlayerRoomId);//获得房间号
    server.register_handler("RpcPlayerPiece",RpcPlayerPiece);//获得棋盘的棋子
    server.register_handler("RpcBoard",RpcBoard);//获得棋盘
    server.register_handler("RpcIsMyTurn",RpcIsMyTurn);//是否该我走
    server.register_handler("RpcStep",RpcStep);//玩家下棋
    server.register_handler("RpcJudge",RpcJudge);//判断输赢
    server.register_handler("RpcPopMatchPool",RpcPopMatchPool);//把自己的id从匹配池拿走

    LOG(INFO,"所有方法注册完毕...");
    LOG(INFO,"服务器开始启动...");

    GameHall.InitHall();//初始化大厅
	
	server.run();

	std::string str;
	std::cin >> str;
}

Hall.hpp

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <pthread.h>
#include "PlayerManager.hpp"
#include "RoomManager.hpp"

#define MATCH_LEVEL 101 

using namespace std;

class Hall
{
    public:
        Hall()
            :match_pool(MATCH_LEVEL)
            ,match_num(0)
        {}

        int MatchNum()
        {
            return match_num;
        }

        void IncMatchNum()//递增匹配个数
        {
            match_num++;
        }

        void DecMatchNum()
        {
            match_num--;
        }

        void ResetMatchNum()//重置匹配个数
        {
            match_num=0;
        }

        void GetAllMatchId(vector<uint32_t> &id_list)//把匹配池中所有的id都放到id_list中
        {
            for(auto i=MATCH_LEVEL-1;i>=0;i--)
               {
                   auto& v=match_pool[i];//从胜率为100的开始遍历
                   if(v.empty())//为空
                   {
                       continue;
                   }
                   auto it = v.begin();
                   while(it!=v.end())
                   {
                       id_list.push_back(*it);
                       it++;
                   }
               }
        }

        void LockMatchPool()//给匹配池加锁
        {
            pthread_mutex_lock(&match_lock);
        }

        void UnlockMatchPool()//给匹配池解锁
        {
            pthread_mutex_unlock(&match_lock);
        }

        void ServiceWait()//匹配服务线程等待
        {
            pthread_cond_wait(&match_cond,&match_lock);
        }

        void ServiceWakeup()//唤醒服务线程
        {
            pthread_cond_signal(&match_cond);
        }

        uint32_t Register(string &name,string &passwd)
        {
            return pm.InsertPlayer(name,passwd);
        }

        uint32_t Login(uint32_t &id,string &passwd)
        {
            return pm.SearchPlayer(id,passwd);
        }
         
        bool PushIdInMatchPool(uint32_t& id)//把用户id放入匹配池中
        {
            LOG(INFO,"把用户id放入匹配池中...");
            pm.SetMatching(id);//把当前用户设置为匹配状态
            int rate=pm.GetRate(id);
            cout << "rate: " << rate << endl;

            LockMatchPool();//加锁
            auto &v = match_pool[rate];//拿到了相同胜率的vector
            auto it = v.begin();
            while(it!=v.end())
            {
                if(*it == id)//说明已经有了
                    return false;
                ++it;
            }
            v.push_back(id);//把用户插入到匹配池中
            IncMatchNum();//递增match_num
            UnlockMatchPool();//解锁
            ServiceWakeup();//唤醒匹配线程

           return true;
            //用户要等匹配线程把它匹配成功
          // return  pm.PlayerWait(id);//让用户进行等待
        }

        bool PopIdMatchPool(uint32_t &id)//把自己的id从匹配池拿走
        {
            LockMatchPool();
           // MatchPoolClear(id);
            int rate = pm.GetRate(id);//获得自己的胜率
            auto &v=match_pool[rate];//找到匹配池中对应的级别
            for(auto it = v.begin();it!=v.end();it++)//删掉用户自己的id
            {
                if(*it == id)
                {
                    v.erase(it);
                    break;
                }
            }
            DecMatchNum();//匹配池的个数减1
            UnlockMatchPool();
            pm.SetOnline(id);
            return true;
        }

        void MatchPoolClear(uint32_t &id)//清空匹配池
        {
            LOG(INFO,"匹配池被清空...");
            for(int i = MATCH_LEVEL-1;i >= 0;i--)
            {
                auto &v=match_pool[i];
                if(v.empty())
                {
                    continue;
                }
                vector<uint32_t>().swap(v);
            }
            ResetMatchNum();//清空所有id
            if(id>=1000)
            {
                pm.SetOnline(id);
            }
        }

        int IsPlayerReady(uint32_t &id)//检测用户状态是否是ready
        {
            return pm.Ready(id);
        }

        void GamePrepare(uint32_t &one,uint32_t &two)
        {
            pm.SetPlayerStatus(one,two);//设置玩家游戏状态
            uint32_t room_id = rm.CreateRoom(one,two);//创建房间
            pm.SetPlayerRoom(room_id,one,two);//设置玩家的房间号
            cout<< "debug: room_id: "<<room_id <<endl;
            //pm.SignalPlayer(one,two);//唤醒两个用户
        }

       static void *MatchService(void* arg)//提供一个线程不断检测当前用户是否可以匹配
        {
            pthread_detach(pthread_self());//分离该线程
            Hall *hp=(Hall*)arg;
            while(1)
            {
                uint32_t last = 0;
                 hp->LockMatchPool();//加锁
                 while(hp->MatchNum() < 2)//循环检测(防止误唤醒)
                 {
                     LOG(INFO,"服务线程开始等待啦...");
                    hp-> ServiceWait();//等待
                 }
                 LOG(INFO,"服务线程被唤醒了...");
                 vector<uint32_t> id_list;
                 hp->GetAllMatchId(id_list);
                 //两两开始匹配
                 int num=id_list.size();
                 if(num & 1)
                 {
                     last = id_list[id_list.size()-1];
                     num &=(~1);//保证num为偶数
                 }
                 else
                 {
                     last = 0;
                 }
                 for(int i = 0;i <= num;i+=2)
                 {
                     uint32_t play_one = id_list[i];
                     uint32_t play_two = id_list[i+1];
                     hp->GamePrepare(play_one,play_two);//游戏准备
                 }
                 //匹配完毕后,要清空匹配池
                 hp->MatchPoolClear(last);//清空当前匹配池内容
                 hp->UnlockMatchPool();//解锁
            }
        }

       string GetPlayerBoard(uint32_t &room_id)
       {
           string board;
           //uint32_t room_id=pm.GetPlayerRoomId(id);
           rm.GetBoard(room_id,board);
           return board;
       }

       uint32_t GetPlayerRoomId(uint32_t &id)
       {
          return pm.GetPlayerRoomId(id);
       }

       char GetPlayerPiece(uint32_t &room_id,uint32_t &id)
       {
           return rm.GetPlayerPiece(room_id,id);
       }

       bool IsMyTurn(uint32_t &room_id,uint32_t &id)
       {
           return rm.IsMyTurn(room_id,id);
       }

       void Step(uint32_t &room_id,uint32_t &id,int x,int y)
       {
           rm.Step(room_id,id,x,y);
       }

       char Judge(uint32_t &room_id,uint32_t &id)
       {
           return rm.Judge(room_id,id);
       }

        void InitHall()//初始化大厅
        {
            pthread_mutex_init(&match_lock,NULL);
            pthread_cond_init(&match_cond,NULL);

            pthread_t tid;
            pthread_create(&tid,NULL,MatchService,this);
        }

        ~Hall()
        {
            pthread_mutex_destroy(&match_lock);
            pthread_cond_destroy(&match_cond);
        }

    private:
        PlayerManager pm;
        RoomManager rm;
        vector<vector<uint32_t> > match_pool;//匹配池
        int match_num;//匹配用户的个数
        pthread_mutex_t match_lock;
        pthread_cond_t match_cond;
};

PlayerManager.hpp

#pragma once
#include <iostream>
#include <unordered_map>
#include <string>
#include <utility>
#include <pthread.h>
#include "Player.hpp"
#include "Log.hpp"

#define DEFAULT_ID 1000

using namespace std;

class PlayerManager
{
    public:
        PlayerManager()
            :assign_id(DEFAULT_ID)
        {
            pthread_mutex_init(&lock,NULL);
        }

        void Lock()
        {
            pthread_mutex_lock(&lock);
        }

        void Unlock()
        {
            pthread_mutex_unlock(&lock);
        }

        uint32_t InsertPlayer(string &name,string &passwd)//插入用户
        {
            Lock();
            uint32_t _id=assign_id++;//key值
            Player p(name,passwd,_id);//定义一个用户(value值)
            player_set.insert({_id,p});
            Unlock();
            LOG(INFO,"用户插入完毕....");

            return _id;
        }

        uint32_t SearchPlayer(uint32_t id,string &passwd)//查找用户
        {
            Lock();
            auto it=player_set.find(id);
            if(it!=player_set.end())
            {
                Player &p=it->second;//拿到用户所对应的second信息
                if(p.GetPasswd()!=passwd)
                {
                    Unlock();
                    LOG(WARNING,"用户密码错误...");
                    return 2;//认证失败
                }

                p.Online();//把用户状态设置为上线状态
                Unlock();
                LOG(INFO,"用户进入上线状态...");
                return id;
            }
            else
            {
                LOG(WARNING,"用户不存在....");
                Unlock();
                return 1;//用户不存在
            }
        }

        int GetRate(uint32_t &id)
        {
            Lock();
            int rate = player_set[id].Rate();
            Unlock();
            return rate;
        }

        //bool PlayerWait(uint32_t &id)
        //{
        //   if(player_set[id].Wait()==ETIMEDOUT)
        //   {
        //       return false;
        //   }
        //   return true;
        //}

       //void SignalPlayer(uint32_t &one,uint32_t &two)
       //{
       //    player_set[one].Signal();
       //    player_set[two].Signal();
       //}
    
        uint32_t GetPlayerRoomId(uint32_t &id)
        {
            Lock();
           uint32_t _room_id = player_set[id].Room();
            Unlock();
            return _room_id;
        }

       void SetPlayerStatus(uint32_t &one,uint32_t &two)
       {
           Lock();
           player_set[one].Playing();
           player_set[two].Playing();
           Unlock();
       }

       void SetPlayerRoom(uint32_t &room_id,uint32_t &one,uint32_t &two)
       {
           Lock();
           player_set[one].SetRoom(room_id);
           player_set[two].SetRoom(room_id);
           Unlock();
       }

       void SetMatching(uint32_t &id)
       {
           Lock();
           player_set[id].Matching();
           Unlock();
       }

       void SetOnline(uint32_t &id)
       {
           Lock();
           player_set[id].Online();
           Unlock();
       }

       int Ready(uint32_t &id)//判断用户是否匹配成功
       {
           Lock();
           int st= player_set[id].Status();
           Unlock();
           return st;
       }

       ~PlayerManager()
       {
           pthread_mutex_destroy(&lock);
       }
    private:
        unordered_map<uint32_t,Player> player_set;
        uint32_t assign_id;//所有的用户id都是由assign_id分配的(每次加1)
        pthread_mutex_t lock;
};

Player.hpp

#pragma once
#include <iostream>
#include <pthread.h>
#include <string>
#include <time.h>

#define TIMEOUT 20
using namespace std;

typedef enum
{
    OFFLINE = 0,//离线
    ONLINE,//刚刚登陆
    MATCHING,//匹配中
    PLAYING,//游戏中
}status_t;

class Player//玩家用户
{
    public:
        Player()
        {}
        Player(string &_name,string &_passwd,uint32_t &_id)
            :name(_name)
            ,passwd(_passwd)
            ,id(_id)
        {
            win = 0;
            lose = 0;
            tie = 0;
            st = OFFLINE;
            //pthread_mutex_init(&lock,NULL);
           // pthread_cond_init(&cond,NULL);
        }
        const string& GetPasswd()
        {
            return passwd;
        }

        void Online()//上线
        {
            st=ONLINE;
        }

        void Matching()//匹配
        {
            st=MATCHING;
        }

        void Playing()//游戏中
        {
            st=PLAYING;
        }

        int Status()
        {
            return st;
        }

       int Rate()//胜率
       {
           int total = win+lose;
           if(total == 0)//说明一次都没有玩过
           {
               return 0;
           }

           return win*100/total;//取值范围是0-100
       }

       uint32_t Room()
       {
           return room_id;
       }

       void SetRoom(uint32_t &_room_id)
       {
           room_id = _room_id;
       }

      // int Wait()//等待
      // {
      //    struct timespec ts;
      //    clock_gettime(CLOCK_REALTIME,&ts);
      //    ts.tv_sec+=TIMEOUT;
      //    return pthread_cond_timedwait(&cond,&lock,&ts);
      // }

      // void Signal()//唤醒
      // {
      //     pthread_cond_signal(&cond);
      // }

       ~Player()
        {
            //pthread_mutex_destroy(&lock);
           // pthread_cond_destroy(&cond);
        }
   
    private:
        //基本信息
        string name;
        string passwd;
        uint32_t id;

        //游戏信息
        int win;//赢的次数
        int lose;//输的次数
        int tie;//平局的次数

        status_t st;//状态
        uint32_t room_id;//房间号

        //pthread_mutex_t lock;//一把锁
        //pthread_cond_t cond;//条件变量
};

RoomManager.hpp

#include "Room.hpp"
#include <iostream>
#include <unordered_map>
#include <pthread.h>
#include <string>

#define ROOM_ID 1024

using namespace std;
class RoomManager
{
    public:
        RoomManager()
            :assign_id(ROOM_ID)
        {
            pthread_mutex_init(&lock,NULL);
        }

        void Lock()
        {
            pthread_mutex_lock(&lock);
        }

        void Unlock()
        {
            pthread_mutex_unlock(&lock);
        }

        uint32_t CreateRoom(uint32_t &one,uint32_t &two)
        {
            Room r(one,two);
            Lock();
            uint32_t id=assign_id++;
            room_set.insert({id,r});
            Unlock();
            return id;
        }

        void GetBoard(uint32_t &room_id,string &_board)
        {
            Lock();
            room_set[room_id].Board(_board);
            Unlock();
        }

        char GetPlayerPiece(uint32_t &room_id,uint32_t &id)
        {
            Lock();
            char st = room_set[room_id].Piece(id);
            Unlock();
            return st;
        }

        bool IsMyTurn(uint32_t &room_id,uint32_t &id)
        {
            Lock();
            bool is_my_turn = room_set[room_id].IsMyTurn(id);
            Unlock();
            return is_my_turn;
        }

        void Step(uint32_t &room_id,uint32_t &id,int &x,int &y)
        {
            Lock();
            room_set[room_id].Step(id,x,y);
            Unlock();
        }

        char Judge(uint32_t &room_id,uint32_t &id)
        {
            Lock();
            char res = room_set[room_id].GameResult(id);
            Unlock();
            return res;
        }

        ~RoomManager()
        {
            pthread_mutex_destroy(&lock);
        }

    private:
        unordered_map<uint32_t,Room> room_set;
        int assign_id;//给用户分配房间
        pthread_mutex_t lock;
};

Room.hpp

#pragma once
#include <iostream>
#include <string>

using namespace std;

#define SIZE 5
#define BLACK 'X'//黑子
#define WHITE 'O'//白子


class Room
{
    public:
        Room()
        {}

        Room(uint32_t &id1,uint32_t &id2)
            :one(id1)
            ,two(id2)
        {
            piece[0]='X';
            piece[1]='O';
            memset(board,' ',sizeof(board));//初始化棋盘
            result='N';//结果默认是继续
            current=one;
            pthread_mutex_init(&lock,NULL);
        }

        void Board(string &_board)
        {
            for(auto i = 0;i< SIZE;i++)
            {
                for(auto j = 0;j<SIZE;j++)
                {
                    _board.push_back(board[i][j]);
                }
            }
        }

        char Piece(uint32_t &id)
        {
            int pos =0;
            if(id == one)
            {
                pos = 0;
            }
            else
            {
                pos = 1;
            }

            return piece[pos];
        }

        bool IsMyTurn(uint32_t &id)
        {
            return id ==current ? true : false;
        }

        void Step(uint32_t &id,int &x,int &y)
        {
            if(current == id)
            {
                int pos = (id == one ? 0 : 1);
                board[x][y] = piece[pos];
                current = (id == one ? two : one);
                result=Judge();
            }
        }

        char GameResult(uint32_t &id)
        {
            return result;
        }
        
        char Judge()
        {
            int row = SIZE;
            int col = SIZE;
            for(auto i = 0;i < row;i++)//行
            {
                if(board[i][0]!=' '&& \
                   board[i][0] == board[i][1]&& \
                   board[i][1] == board[i][2]&& \
                   board[i][2] == board[i][3]&& \
                   board[i][3] == board[i][4])
                {
                    return board[i][0];
                }
            }

            for(auto i = 0;i < col;i++)//列
            {
                if(board[0][i]!=' '&& \
                   board[0][i] == board[1][i]&& \
                   board[1][i] == board[2][i]&& \
                   board[2][i] == board[3][i]&& \
                   board[3][i] == board[4][i])
                {
                    return board[0][i];
                }
            }

            //正对角线
            if(board[0][0]!=' '&& \
               board[0][0] == board[1][1]&& \
               board[1][1] == board[2][2]&& \
               board[2][2] == board[3][3]&& \
               board[3][3] == board[4][4])
              {
                    return board[0][0];
              }

           //反对角线
           if(board[0][4]!=' '&& \
              board[0][4] == board[1][3]&& \
              board[1][3] == board[2][2]&& \
              board[2][2] == board[3][1]&& \
              board[3][1] == board[4][0])
             {
                    return board[0][4];
             }

           //判断棋盘是否已满
           for(auto i =0;i < row ;i++)
           {
               for(auto j =0;j < col;j++)
               {
                   if(board[i][j] == ' ')
                       return 'N';
               }
           }
           return 'E';
        }

        ~Room()
        {
            pthread_mutex_destroy(&lock);
        }

    private:
        uint32_t one;//指定one用户拿的是黑子'X'
        uint32_t two;//指定two用户拿的是白子'O'
        char piece[2];//规定0号下标对应one的棋子,1号下标对应two的棋子
        uint32_t current;//当前该谁走
        char board[SIZE][SIZE];//定义棋盘
        char result;//  X(one用户赢), O(two用户赢), E(平局),N(继续)

        pthread_mutex_t lock;//定义一把锁

};

完整源码链接:https://github.com/southern-yan/project/tree/master/Gobang

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值