任务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 消息发向了玩家的客户端。