任务30:后端-用牌库、手牌组件实现洗发牌和手牌管理及protobuf的RepeatedField

上节,我们做到客户端点准备游戏后,请求通过gate转到Map上的GamerReady_Landlords_Handler,然后调用到RoomSystem的CheckGameStart方法,当判断房间内三个玩家都发来消息已经准备好后,调用RoomSystem的GameStart方法。

在GameStart方法中,给本局的room实例添加:

//牌库组件

//游戏控制组件

//并调用GameControllerComponent组件扩展方法开始游戏

\Server\Hotfix\Landlords\System\RoomSystem.cs

/// <summary>
/// 斗地主游戏开始
/// </summary>
public static void GameStart(this Room self)
{
    //更改房间状态 从空闲房间移除 添加到游戏中房间列表
    LandMatchComponent Match = Game.Scene.GetComponent<LandMatchComponent>();
    Match.FreeLandlordsRooms.Remove(self.Id);
    Match.GamingLandlordsRooms.Add(self.Id, self);

    //更改玩家状态
    for(int i=0;i<self.gamers.Length;i++)
    {
        Gamer gamer = self.gamers[i];
        Match.Waiting.Remove(gamer.UserID);
        Match.Playing.Add(gamer.UserID, self);
    }

    //添加开始斗地主游戏需要的组件
    //牌库组件
    self.AddComponent<DeckComponent>();
    //游戏控制组件
    self.AddComponent<GameControllerComponent, RoomConfig>(GateHelper.GetLandlordsConfig(RoomLevel.Lv100));
    
    //手牌缓存组件
    //self.AddComponent<DeskCardsCacheComponent>();
    //出牌控制组件
    //self.AddComponent<OrderControllerComponent>();

    //开始游戏
    self.GetComponent<GameControllerComponent>().StartGame();
}

GameControllerComponentSystem游戏控制组件扩展类

控制着游戏的开始,结束,发牌,洗牌,牌库回收。

轮流发牌:index=3就重置,这样就做到轮流给三个玩家各发一张牌了。一副牌是54张,留了三张地主牌,所以用51做这个循环。

Multiples 是牌局的倍数。

给每位玩家添加了手牌组件

//初始玩家开始状态
            foreach (var _gamer in gamers)
            {
                if (_gamer.GetComponent<HandCardsComponent>() == null)
                {
                    _gamer.AddComponent<HandCardsComponent>();
                }
            }

控制发牌:

/// <summary>
        /// 轮流发牌
        /// </summary>
        /// <param name="self"></param>
        public static void DealCards(this GameControllerComponent self)
        {
            Room room = self.GetParent<Room>();

            //牌库洗牌
            room.GetComponent<DeckComponent>().Shuffle();

            //玩家轮流发牌
            Gamer[] gamers = room.gamers;
            int index = 0;
            for (int i = 0; i < 51; i++)
            {
                if (index == 3)
                {
                    index = 0;
                }

                self.DealTo(gamers[index].UserID);

                index++;
            }

            //发地主牌
            for (int i = 0; i < 3; i++)
            {
                self.DealTo(room.Id);
            }

            self.Multiples = self.Config.Multiples;
        }

选牌与发牌后根据发给每位玩家的手牌,构建Actor_GameStartHandCards_Ntt消息

//发送玩家手牌和其他玩家手牌数
            foreach (var _gamer in gamers)
            {
                ActorMessageSenderComponent actorProxyComponent = Game.Scene.GetComponent<ActorMessageSenderComponent>();
                ActorMessageSender actorProxy = actorProxyComponent.Get(_gamer.CActorID);
                
                actorProxy.Send(new Actor_GameStartHandCards_Ntt()
                {
                    HandCards = To.RepeatedField(_gamer.GetComponent<HandCardsComponent>().GetAll()),
                    GamersCardNum = To.RepeatedField(gamersCardNum)
                });
            }

GameControllerComponentSystem完整代码

\Server\Hotfix\Landlords\System\GameControllerComponentSystem.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ETModel;

namespace ETHotfix
{
    [ObjectSystem]
    public class GameControllerComponentAwakeSystem : AwakeSystem<GameControllerComponent, RoomConfig>
    {
        public override void Awake(GameControllerComponent self, RoomConfig config)
        {
            self.Awake(config);
        }
    }

    public static class GameControllerComponentSystem
    {
        public static void Awake(this GameControllerComponent self, RoomConfig config)
        {
            self.Config = config;
            self.BasePointPerMatch = config.BasePointPerMatch;
            self.Multiples = config.Multiples;
            self.MinThreshold = config.MinThreshold;
        }

        /// <summary>
        /// 准备开始游戏
        /// </summary>
        /// <param name="self"></param>
        public static void StartGame(this GameControllerComponent self)
        {
            Room room = self.GetParent<Room>();
            Gamer[] gamers = room.gamers;

            //初始玩家开始状态
            foreach (var _gamer in gamers)
            {
                if (_gamer.GetComponent<HandCardsComponent>() == null)
                {
                    _gamer.AddComponent<HandCardsComponent>();
                }
            }
            
            GameControllerComponent gameController = room.GetComponent<GameControllerComponent>();
            //洗牌发牌
            gameController.DealCards();

            List<GamerCardNum> gamersCardNum = new List<GamerCardNum>();
            Array.ForEach(gamers, (g) =>
            {
                HandCardsComponent handCards = g.GetComponent<HandCardsComponent>();
                //重置玩家身份
                handCards.AccessIdentity = Identity.None;
                //重置玩家手牌数
                gamersCardNum.Add(new GamerCardNum()
                {
                    UserID = g.UserID,
                    Num = g.GetComponent<HandCardsComponent>().GetAll().Length
                });
            });

            //向客户端发送玩家手牌和玩家手牌数
            foreach (var _gamer in gamers)
            {
                ActorMessageSenderComponent actorProxyComponent = Game.Scene.GetComponent<ActorMessageSenderComponent>();
                ActorMessageSender actorProxy = actorProxyComponent.Get(_gamer.CActorID);
                
                actorProxy.Send(new Actor_GameStartHandCards_Ntt()
                {
                    HandCards = To.RepeatedField(_gamer.GetComponent<HandCardsComponent>().GetAll()),
                    GamersCardNum = To.RepeatedField(gamersCardNum)
                });
            }

            //随机先手玩家
            //gameController.RandomFirstAuthority();
            Log.Info($"房间{room.Id}开始游戏");
        }

        /// <summary>
        /// 轮流发牌
        /// </summary>
        /// <param name="self"></param>
        public static void DealCards(this GameControllerComponent self)
        {
            Room room = self.GetParent<Room>();

            //牌库洗牌
            room.GetComponent<DeckComponent>().Shuffle();

            //玩家轮流发牌
            Gamer[] gamers = room.gamers;
            int index = 0;
            for (int i = 0; i < 51; i++)
            {
                if (index == 3)
                {
                    index = 0;
                }
                self.DealTo(gamers[index].UserID);
                index++;
            }

            //发地主牌
            for (int i = 0; i < 3; i++)
            {
                self.DealTo(room.Id);
            }
            self.Multiples = self.Config.Multiples;
        }

        /// <summary>
        /// 发牌
        /// </summary>
        /// <param name="id"></param>
        public static void DealTo(this GameControllerComponent self, long id)
        {
            Room room = self.GetParent<Room>();
            Card card = room.GetComponent<DeckComponent>().Deal();
            
            foreach (var gamer in room.gamers)
            {
                if (id == gamer.UserID)
                {
                    gamer.GetComponent<HandCardsComponent>().AddCard(card);
                    break;
                }
            }
           
        }

    }
}

上面用到了牌库组件与其扩展类和手牌组件与其扩展类,往下看.

DeckComponent牌库组件与其扩展类

DeckComponent被添加时awake方法调用了创建一副牌,用4中花色和13个牌分值,创建两个嵌套循环即可创建52张牌Card(不包括大小王),添加到List<Card> library中,然后大小王分别创建也加入到library。

创建一副牌:

/// <summary>
        /// 创建一副牌
        /// </summary>
        private static void CreateDeck(this DeckComponent self)
        {
            //创建普通扑克
            for (int color = 0; color < 4; color++)
            {
                for (int value = 0; value < 13; value++)
                {
                    Card card = Card.Create(value, color);
                    self.library.Add(card);
                }
            }

            //创建大小王扑克
            self.library.Add(Card.Create((int)Weight.SJoker, (int)Suits.None));
            self.library.Add(Card.Create((int)Weight.LJoker, (int)Suits.None));
        }

洗牌的实现:遍历创建的这副牌self.library,用一个新的零时List<Card> newCards,将原牌库中的牌一一加到newCards中随机的位置,完成后,清空原来的library,最后用AddRange把newCards中的牌添加到library,完成了洗牌。

newCards.Insert(random.Next(newCards.Count + 1), card);

self.library.AddRange(newCards);

牌库洗牌:

/// <summary>
        /// 牌库洗牌
        /// </summary>
        public static void Shuffle(this DeckComponent self)
        {
            if (self.CardsCount == 54)
            {
                Random random = new Random();
                List<Card> newCards = new List<Card>();
                foreach (var card in self.library)
                {
                    newCards.Insert(random.Next(newCards.Count + 1), card);
                }

                self.library.Clear();
                self.library.AddRange(newCards);
            }
        }

添加DeckComponentSystem,此类的完整代码

\Server\Hotfix\Landlords\System\DeckComponentSystem.cs

using System;
using System.Collections.Generic;
using ETModel;

namespace ETHotfix
{
    [ObjectSystem]
    public class DeckComponentAwakeSystem : AwakeSystem<DeckComponent>
    {
        public override void Awake(DeckComponent self)
        {
            self.Awake();
        }
    }

    public static class DeckComponentSystem
    {
        public static void Awake(this DeckComponent self)
        {
            self.CreateDeck();
        }

        /// <summary>
        /// 牌库洗牌
        /// </summary>
        public static void Shuffle(this DeckComponent self)
        {
            if (self.CardsCount == 54)
            {
                Random random = new Random();
                List<Card> newCards = new List<Card>();
                foreach (var card in self.library)
                {
                    newCards.Insert(random.Next(newCards.Count + 1), card);
                }

                self.library.Clear();
                self.library.AddRange(newCards);
            }
        }

        /// <summary>
        /// 牌库发牌
        /// </summary>
        /// <returns></returns>
        public static Card Deal(this DeckComponent self)
        {
            Card card = self.library[self.CardsCount - 1];
            self.library.Remove(card);
            return card;
        }

        /// <summary>
        /// 向牌库中添加牌
        /// </summary>
        /// <param name="card"></param>
        public static void AddCard(this DeckComponent self, Card card)
        {
            self.library.Add(card);
        }

        /// <summary>
        /// 创建一副牌
        /// </summary>
        private static void CreateDeck(this DeckComponent self)
        {
            //创建普通扑克
            for (int color = 0; color < 4; color++)
            {
                for (int value = 0; value < 13; value++)
                {
                    Card card = Card.Create(value, color);
                    self.library.Add(card);
                }
            }

            //创建大小王扑克
            self.library.Add(Card.Create((int)Weight.SJoker, (int)Suits.None));
            self.library.Add(Card.Create((int)Weight.LJoker, (int)Suits.None));
        }
    }
}

添加DeckComponent,此类的完整代码

\Server\ET.Core\Landlords\Component\Map\DeckComponent.cs

using System.Collections.Generic;

namespace ETModel
{
    /// <summary>
    /// 牌库组件
    /// </summary>
    public class DeckComponent : Component
    {
        //牌库中的牌
        public readonly List<Card> library = new List<Card>();

        //牌库中的总牌数
        public int CardsCount { get { return this.library.Count; } }

        public override void Dispose()
        {
            if(this.IsDisposed)
            {
                return;
            }

            base.Dispose();

            library.Clear();
        }
    }
}

HandCardsComponent手牌组件与其扩展类

添加HandCardsComponentSystem,此类的完整代码

\Server\Hotfix\Landlords\System\HandCardsComponentSystem.cs

using ETModel;

namespace ETHotfix
{
    public static class HandCardsComponentSystem
    {
        /// <summary>
        /// 获取所有手牌
        /// </summary>
        /// <param name="self"></param>
        /// <returns></returns>
        public static Card[] GetAll(this HandCardsComponent self)
        {
            return self.library.ToArray();
        }

        /// <summary>
        /// 向手牌中添加牌
        /// </summary>
        /// <param name="card"></param>
        public static void AddCard(this HandCardsComponent self, Card card)
        {
            self.library.Add(card);
        }

        /// <summary>
        /// 出牌后从手牌移除
        /// </summary>
        /// <param name="self"></param>
        /// <param name="card"></param>
        public static void PopCard(this HandCardsComponent self, Card card)
        {
            self.library.Remove(card);
        }

    }
}

添加HandCardsComponent,此类的完整代码

\Server\ET.Core\Landlords\Component\Map\HandCardsComponent.cs

using System.Collections.Generic;

namespace ETModel
{
    public class HandCardsComponent : Component
    {
        //所有手牌
        public readonly List<Card> library = new List<Card>();

        //身份
        public Identity AccessIdentity { get; set; }

        //手牌数
        public int CardsCount { get { return library.Count; } }

        public override void Dispose()
        {
            if(this.IsDisposed)
            {
                return;
            }

            base.Dispose();

            this.library.Clear();
            AccessIdentity = Identity.None;
        }
    }
}

Card牌类与MapHelper提供的容器转换辅助方法

添加Card类

\Server\ET.Core\Landlords\Other\Card.cs

namespace ETModel
{
    /// <summary>
    /// 参考Unit类
    /// </summary>
    public partial class Card
    {
        public static Card Create(int weight, int suits)
        {
            Card card = new Card();
            card.CardWeight = weight; //点数
            card.CardSuits = suits; //花色
            return card;
        }

        public bool Equals(Card other)
        {
            return this.CardWeight == other.CardWeight && this.CardSuits == other.CardSuits;
        }

        /// <summary>
        /// 获取卡牌名
        /// </summary>
        /// <returns></returns>
        public string GetName()
        {
            return this.CardSuits == (int)Suits.None ? ((Weight)this.CardWeight).ToString() : $"{((Suits)this.CardSuits).ToString()}{((Weight)this.CardWeight).ToString()}";
        }
    }

}

添加MapHelper类

\Server\Hotfix\Helper\MapHelper.cs

using System.Collections.Generic;
using ETModel;
using Google.Protobuf.Collections;
using System.Net;

namespace ETHotfix
{
    public static class MapHelper
    {
        public static Session GetGateSession()
        {
            StartConfigComponent config = Game.Scene.GetComponent<StartConfigComponent>();
            IPEndPoint gateIPEndPoint = config.GateConfigs[0].GetComponent<InnerConfig>().IPEndPoint;
            //Log.Debug(gateIPEndPoint.ToString());
            Session gateSession = Game.Scene.GetComponent<NetInnerComponent>().Get(gateIPEndPoint);
            return gateSession;
        }

    }

    /// <summary>
    /// 容器转换辅助方法
    /// </summary>
    public static class To
    {
        //数组-RepeatedField
        public static RepeatedField<T> RepeatedField<T>(T[] cards)
        {
            RepeatedField<T> a = new RepeatedField<T>();
            foreach (var b in cards)
            {
                a.Add(b);
            }
            return a;
        }

        //列表-RepeatedField
        public static RepeatedField<T> RepeatedField<T>(List<T> cards)
        {
            RepeatedField<T> a = new RepeatedField<T>();
            foreach (var b in cards)
            {
                a.Add(b);
            }
            return a;
        }

        //重复字段-RepeatedField
        public static T[] Array<T>(RepeatedField<T> cards)
        {
            T[] a = new T[cards.Count];
            for (int i = 0; i < cards.Count; i++)
            {
                a[i] = cards[i];
            }
            return a;
        }

        //重复字段-列表
        public static List<T> List<T>(RepeatedField<T> cards)
        {
            List<T> a = new List<T>();
            foreach (var b in cards)
            {
                a.Add(b);
            }
            return a;
        }
    }
}

本节消息定义OuterMessage.proto中增加消息定义

\Proto\OuterMessage.proto

//牌类消息
message Card
{
    int32 CardWeight = 1;
    int32 CardSuits = 2;
}
//牌分值消息
message GamerCardNum
{
    int64 UserID = 1;
    int32 Num = 2;
}
//游戏开始玩家手牌消息
message Actor_GameStartHandCards_Ntt // IActorMessage
{
    int32 RpcId = 90;
    int64 ActorId = 93;
    repeated Card HandCards = 1;
    repeated GamerCardNum GamersCardNum = 2;
}

去前端项目中生成一下消息文件,复制到服务器。

请注意下Actor_GameStartHandCards_Ntt 中的

repeated Card HandCards = 1;
repeated GamerCardNum GamersCardNum = 2;

扩展阅读:https://www.taikr.com/article/3934

回顾:第18节时,大家已经接触过Actor_GamerEnterRoom_Ntt 中的repeated GamerInfo Gamers = 1;

当时是这样构建GameInfo和往数组中添加他们的:

本节:在GameControllerComponentSystem向客户端发送玩家手牌和玩家手牌数时是用MapHelper中提供的数组转RepeatedField方法

actorProxy.Send(new Actor_GameStartHandCards_Ntt()
    {
        HandCards = To.RepeatedField(_gamer.GetComponent<HandCardsComponent>().GetAll()),
        GamersCardNum = To.RepeatedField(gamersCardNum)
    });

运行本节完成的项目,在前端项目从Release中开启三个游戏窗口

分别登录账号,进入房间都点"准备"游戏后,可以看到服务端完成了洗牌,并把每个玩家的手牌通过Actor_GameStartHandCards_Ntt 消息发向了玩家的客户端。

 

posted @ 2023-02-16 14:35  Domefy  阅读(37)  评论(0编辑  收藏  举报