之前开内部培训,说到实时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>
/// 更新当前出牌人
/// </summary>
/// <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];
}
}

因为所有数据都是通过静态属性保存的,所以大部分都是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),多张规则目前支持:王炸、对子、三只、顺子、三带一。目前只做到这里,各位同学可以拿回去自行扩展。

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

新玩家加入:

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

出牌特效:

游戏结算:

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

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

最新文章

  1. Fzu2124 - 吃豆人 BFS
  2. 003-Tuple、Array、Map与文件操作入门实战
  3. Render OpenCascade Geometry Surfaces in OpenSceneGraph
  4. ansible的使用技巧
  5. &lt;构建之法&gt;第十三章到十七章有感以及这个项目读后感
  6. location.reload
  7. 将linux下的rm命令改造成移动文件至回收站【转】
  8. jQuery修改后代、兄弟样式
  9. SQL更新表的字段
  10. Git操作指南(2) —— Git Gui for Windows的建库、克隆、上传
  11. Model
  12. Linux,activemq-cpp之消息过滤器
  13. conda创建py27虚拟环境安装theano(anaconda3)
  14. Python类和对象
  15. ssl证书专题(2):自签名ssl 证书生成
  16. zlib简单使用说明(转)
  17. Angular4 投影ngContent
  18. China MVP Community Connection 2017
  19. 求数组的相邻子数组的最大值(txt文件存储)
  20. nodejs的process模块如何获取其他进程的pid

热门文章

  1. pwn200,一道不完全考察ret2libc的小小pwn题
  2. 升级 dubbo 小心 default.version
  3. UVA 10004 Bicoloring(DFS染色)
  4. hdu 2999 Stone Game, Why are you always there? (简单SG,有个优化)
  5. 六. Go并发编程--WaitGroup
  6. SpringBoot 中发布ApplicationEventPublisher,监听ApplicationEvent 异步操作
  7. 了解php数据库常用语法增删改查
  8. C++ substr 的两个用法
  9. redis批量操作
  10. OPPO 图数据库平台建设及业务落地