.Net Core使用SignalR实现斗地主游戏

2022-04-16 14:38:41

之前开内部培训,说到实时web应用这一块讲到了SignalR,我说找时间用它做个游戏玩玩,后面时间紧张就一直没安排。这两天闲了又想起这个事,考虑后决定用2天时间写个斗D主,安排了前端同学写客户端,我写游戏逻辑和服务。

这个项目难度并不高,但是游戏逻辑还是挺绕的,联调过程中也发现解决了很多小问题。来园子里整理一篇文章,记录一下。

基础的介绍就免了,毕竟官网跟着走两圈啥都懂了。没基础的可以戳这里,是我之前写的一篇SignalR基础介绍,带有一个极简聊天室。

tips:文章结尾有开源地址,游戏数据都是本地的,改下IP运行起来就可以玩了。

直接上干货,首先是数据模型:

/// <summary>    /// 用户信息    /// </summary>    public class Customer    {        /// <summary>        /// 唯一ID        /// </summary>        public string? ID { get; set; }        /// <summary>        /// 昵称        /// </summary>        public string? NickName { get; set; }        /// <summary>        /// 卡片        /// </summary>        public List<string> Card { get; set; }    }    /// <summary>    /// 房间    /// </summary>    public class Room    {        /// <summary>        /// 房间名        /// </summary>        public string Name { get; set; }        /// <summary>        /// 房主id        /// </summary>        public string Masterid { get; set; }        /// <summary>        /// 当前出牌人        /// </summary>        public int Curr { get; set; }        /// <summary>        /// 当前卡片        /// </summary>        public List<string> CurrCard { get; set; } = new List<string>();        /// <summary>        /// 当前卡片打出人        /// </summary>        public string ExistingCardClient { get; set; }        /// <summary>        /// 房间成员列表        /// </summary>        public List<Customer> Customers { get; set; } = new List<Customer>();    }

tips:只是单纯为了斗D主设计的,商用版肯定不能这么搞,参考请慎用。

有了数据模型,自然少不了CRUD:

/// <summary>    /// 用户操作    /// </summary>    public static class CustomerAction    {        /// <summary>        /// 用户列表        /// </summary>        private static List<Customer> cusList = new List<Customer>();        /// <summary>        /// 不存在则新增,存在则修改昵称        /// </summary>        /// <param name="customer"></param>        public static void Create(Customer customer)        {            Customer curr = null;            if (cusList.Count > 0)                curr = cusList.Where(x => x.ID == customer.ID).FirstOrDefault();            if (curr is null)                cusList.Add(customer);            else            {                curr.NickName = customer.NickName;                Up4ID(curr);            }        }        /// <summary>        /// 用户列表        /// </summary>        /// <returns></returns>        public static List<Customer> GetList()        {            return cusList;        }        /// <summary>        /// 获取单个        /// </summary>        /// <param name="id"></param>        /// <returns></returns>        public static Customer GetOne(string id)        {            return cusList.Where(x => x.ID == id).FirstOrDefault();        }        /// <summary>        /// 删除用户        /// </summary>        /// <param name="id"></param>        public static void Delete(string id)        {            cusList.RemoveAll(x => x.ID == id);        }        /// <summary>        /// 增加卡片        /// </summary>        /// <param name="id"></param>        /// <param name="cards"></param>        public static void InCard(string id, List<string> cards)        {            Customer customer = cusList.Where(x => x.ID == id).FirstOrDefault();            if (customer.Card is null)                customer.Card = cards;            else                customer.Card.AddRange(cards);            Up4ID(customer);        }        /// <summary>        /// 扣除卡片        /// </summary>        /// <param name="id"></param>        /// <param name="cards"></param>        /// <param name="group"></param>        /// <returns></returns>        public static bool OutCard(string id, List<string> cards, Room group)        {            Customer client = cusList.Where(x => x.ID == id).FirstOrDefault();            if (client is null)                return false;            //卡片不匹配直接失败            if (client.Card.Where(x => cards.Contains(x)).ToList().Count != cards.Count)                return false;            //不符合出牌规则直接失败            if (!new Game.WithCard().Rule(group.CurrCard, cards, group.ExistingCardClient is null || group.ExistingCardClient == id))                return false;            foreach (var item in cards)            {                client.Card.Remove(item);            }            group.CurrCard = cards;            group.ExistingCardClient = id;            Up4ID(client);            RoomAction.Up4Name(group);            return true;        }        /// <summary>        /// 更新(根据ID)        /// </summary>        /// <param name="customer"></param>        /// <returns></returns>        public static bool Up4ID(Customer customer)        {            if (cusList.Count == 0)                return false;            cusList.RemoveAll(x => x.ID == customer.ID);            cusList.Add(customer);            return true;        }    }    /// <summary>    /// 房间操作    /// </summary>    public static class RoomAction    {        /// <summary>        /// 房间列表        /// </summary>        private static List<Room> roomList = new List<Room>();        /// <summary>        /// 新增房间        /// 如果房间已存在则不新增        /// </summary>        /// <param name="group"></param>        public static void Create(Room group)        {            if (!roomList.Where(x => x.Name == group.Name).Any())                roomList.Add(group);        }        /// <summary>        /// 获取列表        /// </summary>        /// <returns></returns>        public static List<Room> GetList()        {            return roomList;        }        /// <summary>        /// 获取单个        /// </summary>        /// <param name="masterid">房主id</param>        /// <param name="roomName">房间名称</param>        /// <returns></returns>        public static Room GetOne(string masterid = null, string roomName = null)        {            if (roomList.Count == 0)                return null;            if (masterid != null)                return roomList.Where(x => x.Masterid == masterid).FirstOrDefault();            if (roomName != null)                return roomList.Where(x => x.Name == roomName).FirstOrDefault();            return null;        }        /// <summary>        /// 加入房间        /// </summary>        /// <param name="client"></param>        /// <param name="roomName"></param>        public static bool Join(Customer client, string roomName)        {            if (roomList.Count == 0)                return false;            var room = roomList.Where(x => x.Name == roomName).FirstOrDefault();            if (room is null)                return false;            if (room.Customers.Count == 3)                return false;            room.Customers.Add(client);            Up4Name(room);            return true;        }        /// <summary>        /// 删除房间        /// </summary>        /// <param name="masterid">房主id</param>        public static bool Delete(string masterid)        {            if (roomList.Count == 0)                return false;            var room = roomList.Where(x => x.Masterid == masterid).FirstOrDefault();            if (room == null)                return false;            roomList.Remove(room);            return true;        }        /// <summary>        /// 更新(根据房名)        /// </summary>        /// <param name="room"></param>        /// <returns></returns>        public static bool Up4Name(Room room)        {            if (roomList.Count == 0)                return false;            roomList.RemoveAll(x => x.Name == room.Name);            roomList.Add(room);            return true;        }        /// <summary>        /// 更新当前出牌人        /// </eqKiggHsummary>        /// <param name="roomName"></param>        /// <param name="index">传入则强制修改,不传按规则走</param>        public static Customer ChangeCurr(string roomName, int index = -1)        {            var room = roomList.Where(x => x.Name == roomName).FirstOrDefault();            if (index != -1)                room.Curr = index;            else                room.Curr = (room.Curr + 1) % 3;            Up4Name(room);            return room.Customers[room.Curr];        }    }

因为所有数据都是通过静态属性保存的,所以大eqKiggH部分都是linq操作(原谅我linq水平有限)。

接下来是游戏逻辑:

/// <summary>    /// 卡片相关    /// </summary>    public class WithCard    {        /// <summary>        /// 黑桃-S、红桃-H、梅花-C、方块-D        /// BG大王,SG小王,14-A,15-2        /// </summary>        readonly List<string> Cards = new List<string>()        {            "S-14","S-15","S-3","S-4","S-5","S-6","S-7","S-8","S-9","S-10","S-11","S-12","S-13",            "H-14","H-15","H-3","H-4","H-5","H-6","H-7","H-8","H-9","H-10","H-11","H-12","H-13",            "C-14","C-15","C-3","C-4","C-5","C-6","C-7","C-8","C-9","C-10","C-11","C-12","C-13",            "D-14","D-15","D-3","D-4","D-5","D-6","D-7","D-8","D-9","D-10","D-11","D-12","D-13",            "BG-99","SG-88"        };        /// <summary>        /// 发牌        /// </summary>        public List<List<string>> DrawCard()        {            List<string> a = new List<string>();            List<string> b = new List<string>();            List<string> c = new List<string>();            Random ran = new Random();            //剩3张底牌            for (int i = 0; i < 51; i++)            {                //随机抽取一张牌                string item = Cards[ran.Next(Cards.Count)];                switch (i % 3)                {                    case 0:                        a.Add(item);                        break;                    case 1:                        b.Add(item);                        break;                    case 2:                        c.Add(item);                        break;                }                Cards.Remove(item);            }            return new List<List<string>>()            {                a,b,c,Cards            };        }        /// <summary>        /// 规则        /// </summary>        /// <param name="existingCard"></param>        /// <param name="newCard"></param>        /// <param name="isSelf"></param>        /// <returns></returns>        public bool Rule(List<string> existingCard, List<string> newCard, bool isSelf)        {            //现有牌号            List<int> existingCardNo = existingCard.Select(x => Convert.ToInt32(x.Split('-')[1])).ToList().OrderBy(x => x).ToList();            //新出牌号            List<int> newCardNo = newCard.Select(x => Convert.ToInt32(x.Split('-')[1])).ToList().OrderBy(x => x).ToList();            //上一手是王炸,禁止其他人出牌            if (existingCardNo.All(x => x > 50) && existingCardNo.Count == 2)            {                if (isSelf)                    return true;                else                    return false;            }            //王炸最大            if (newCardNo.All(x => x > 50) && newCard.Count == 2)                return true;            //单张            if (newCardNo.Count == 1)            {                if (existingCardNo.Count == 0)                    return true;                if ((existingCardNo.Count == 1 && newCardNo[0] > existingCardNo[0]) || isSelf)                    return true;            }            //对子/三只            if (newCardNo.Count == 2 || newCardNo.Count == 3)            {                if (existingCardNo.Count == 0 && newCardNo.All(x => x == newCardNo[0]))                    return true;                if (newCardNo.All(x => x == newCardNo[0]) && (isSelf || newCardNo.Count == existingCardNo.Count && newCardNo[0] > existingCardNo[0]))                    return true;            }            if (newCard.Count == 4)            {                //炸                if (newCardNo.All(x => x == newCardNo[0]))                {                    if (existingCardNo.Count == 0 || isSelf)                        return true;                    if (existingCardNo.All(x => x == existingCardNo[0]) && existingCardNo.Count == 4)                    {                        if (newCardNo[0] > existingCardNo[0])                            return true;                    }                    return true;                }                //三带一                {                    List<int> flagA = newCardNo.Distinct().ToList();                    //超过2种牌直接失败                    if (flagA.Count > 2)                        return false;                    //没有上一手牌,或者上一手是自己出的牌                    if (existingCardNo.Count == 0 || isSelf)                        return true;                    int newCardFlag = 0;                    if (newCardNo.Where(x => x == flagA[0]).ToList().Count() > 1)                    {                        newCardFlag = flagA[0];                    }                    else                        newCardFlag = flagA[1];                    List<int> flagB = existingCardNo.Distinct().ToList();                    //上一手牌不是三带一                    if (flagB.Count > 2)                        return false;                    int existingCardFlag = 0;                    if (existingCardNo.Where(x => x == flagB[0]).ToList().Count() > 1)                    {                        existingCardFlag = flagB[0];                    }                    else                        existingCardFlag = flagB[1];                    if (newCardFlag > existingCardFlag)                        return true;                }            }            if (newCard.Count >= 5)            {                bool flag = true;                for (int i = 0; i < newCardNo.Count - 1; i++)                {                    if (newCardNo[i] + 1 != newCardNo[i + 1])                    {                        flag = false;                        break;                    }                }                //顺子                if (flag)                {                    if (existingCardNo.Count == 0 || (newCardNo[0] > existingCardNo[0] && newCardNo.Count == existingCardNo.Count) || isSelf)                        return true;                }            }            return false;        }    }

单张规则和普通斗D主一样(除了王以外2最大,其次是A),多张规则目前支持:王炸、对子、三只、顺子、三带一。目前只做到这里,各位同学可以拿回去自行扩展。

上一些运行图。房主建房并加入:

.Net Core使用SignalR实现斗地主游戏

新玩家加入:

.Net Core使用SignalR实现斗地主游戏

房间人满以后房主开始游戏,随机分配地主:

.Net Core使用SignalR实现斗地主游戏

出牌特效:

.Net Core使用SignalR实现斗地主游戏

游戏结算:

.Net Core使用SignalR实现斗地主游戏

最后附上开源地址(客户端在web分支):https://gitee.com/muchengqingxin/card-game

tips:前端同学在没有UI配合的情况下做到现在这样,必须给个赞。最后提醒大家,不要拿去商用。

以上所述是小编给大家介绍的.Net Core使用SignalR实现斗地主游戏,希望对大家有所帮助。在此也非常感谢大家对我们网站的支持!