之前开内部培训,说到实时web应用这一块讲到了SignalR,我说找时间用它做个游戏玩玩,后面时间紧张就一直没安排。这两天闲了又想起这个事,考虑后决定用2天时间写个斗D主,安排了前端同学写客户端,我写游戏逻辑和服务。
这个项目难度并不高,但是游戏逻辑还是挺绕的,联调过程中也发现解决了很多小问题。来园子里整理一篇文章,记录一下。
基础的介绍就免了,毕竟官网跟着走两圈啥都懂了。没基础的可以戳这里,是我之前写的一篇SignalR基础介绍,带有一个极简聊天室。
tips:文章结尾有开源地址,游戏数据都是本地的,改下IP运行起来就可以玩了。
直接上干货,首先是数据模型:
////// 用户信息 /// public class Customer { /// /// 唯一ID /// public string? ID { get; set; } /// /// 昵称 /// public string? NickName { get; set; } /// /// 卡片 /// public List<string> Card { get; set; } } /// /// 房间 /// public class Room { /// /// 房间名 /// public string Name { get; set; } /// /// 房主id /// public string Masterid { get; set; } /// /// 当前出牌人 /// public int Curr { get; set; } /// /// 当前卡片 /// public List<string> CurrCard { get; set; } = new List<string>(); /// /// 当前卡片打出人 /// public string ExistingCardClient { get; set; } /// /// 房间成员列表 /// public List Customers { get; set; } = new List (); }
tips:只是单纯为了斗D主设计的,商用版肯定不能这么搞,参考请慎用。
有了数据模型,自然少不了CRUD:
////// 用户操作 /// public static class CustomerAction { /// /// 用户列表 /// private static List cusList = new List (); /// /// 不存在则新增,存在则修改昵称 /// /// 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); } } /// /// 用户列表 /// /// public static List GetList() { return cusList; } /// /// 获取单个 /// /// /// public static Customer GetOne(string id) { return cusList.Where(x => x.ID == id).FirstOrDefault(); } /// /// 删除用户 /// /// public static void Delete(string id) { cusList.RemoveAll(x => x.ID == id); } /// /// 增加卡片 /// /// /// 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); } /// /// 扣除卡片 /// /// /// /// /// 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; } /// /// 更新(根据ID) /// /// /// public static bool Up4ID(Customer customer) { if (cusList.Count == 0) return false; cusList.RemoveAll(x => x.ID == customer.ID); cusList.Add(customer); return true; } } /// /// 房间操作 /// public static class RoomAction { /// /// 房间列表 /// private static List roomList = new List (); /// /// 新增房间 /// 如果房间已存在则不新增 /// /// public static void Create(Room group) { if (!roomList.Where(x => x.Name == group.Name).Any()) roomList.Add(group); } /// /// 获取列表 /// /// public static List GetList() { return roomList; } /// /// 获取单个 /// /// 房主id /// 房间名称 /// 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; } /// /// 加入房间 /// /// /// 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; } /// /// 删除房间 /// /// 房主id 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; } /// /// 更新(根据房名) /// /// /// public static bool Up4Name(Room room) { if (roomList.Count == 0) return false; roomList.RemoveAll(x => x.Name == room.Name); roomList.Add(room); return true; } /// /// 更新当前出牌人 /// /// /// 传入则强制修改,不传按规则走 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]; } }
因为所有数据都是通过静态属性保存的,所以大部分都是linq操作(原谅我linq水平有限)。
接下来是游戏逻辑:
////// 卡片相关 /// public class WithCard { /// /// 黑桃-S、红桃-H、梅花-C、方块-D /// BG大王,SG小王,14-A,15-2 /// 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" }; /// /// 发牌 /// public 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
string>>() { a,b,c,Cards }; } ///
/// 规则 /// /// /// /// /// 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),多张规则目前支持:王炸、对子、三只、顺子、三带一。目前只做到这里,各位同学可以拿回去自行扩展。
上一些运行图。房主建房并加入:
新玩家加入:
房间人满以后房主开始游戏,随机分配地主:
出牌特效:
游戏结算:
最后附上开源地址(客户端在web分支):https://gitee.com/muchengqingxin/card-game
tips:前端同学在没有UI配合的情况下做到现在这样,必须给个赞。最后提醒大家,不要拿去商用。