.Net Core使用SignalR實現(xiàn)斗地主游戲
之前開內(nèi)部培訓(xùn),說到實時web應(yīng)用這一塊講到了SignalR,我說找時間用它做個游戲玩玩,后面時間緊張就一直沒安排。這兩天閑了又想起這個事,考慮后決定用2天時間寫個斗D主,安排了前端同學(xué)寫客戶端,我寫游戲邏輯和服務(wù)。
這個項目難度并不高,但是游戲邏輯還是挺繞的,聯(lián)調(diào)過程中也發(fā)現(xiàn)解決了很多小問題。來園子里整理一篇文章,記錄一下。
基礎(chǔ)的介紹就免了,畢竟官網(wǎng)跟著走兩圈啥都懂了。沒基礎(chǔ)的可以戳這里,是我之前寫的一篇SignalR基礎(chǔ)介紹,帶有一個極簡聊天室。
tips:文章結(jié)尾有開源地址,游戲數(shù)據(jù)都是本地的,改下IP運(yùn)行起來就可以玩了。
直接上干貨,首先是數(shù)據(jù)模型:
/// <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>
/// 當(dāng)前出牌人
/// </summary>
public int Curr { get; set; }
/// <summary>
/// 當(dāng)前卡片
/// </summary>
public List<string> CurrCard { get; set; } = new List<string>();
/// <summary>
/// 當(dāng)前卡片打出人
/// </summary>
public string ExistingCardClient { get; set; }
/// <summary>
/// 房間成員列表
/// </summary>
public List<Customer> Customers { get; set; } = new List<Customer>();
}tips:只是單純?yōu)榱硕稤主設(shè)計的,商用版肯定不能這么搞,參考請慎用。
有了數(shù)據(jù)模型,自然少不了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;
//不符合出牌規(guī)則直接失敗
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>
/// 更新(根據(jù)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>
/// 更新(根據(jù)房名)
/// </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>
/// 更新當(dāng)前出牌人
/// </summary>
/// <param name="roomName"></param>
/// <param name="index">傳入則強(qiáng)制修改,不傳按規(guī)則走</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];
}
}因為所有數(shù)據(jù)都是通過靜態(tài)屬性保存的,所以大部分都是linq操作(原諒我linq水平有限)。
接下來是游戲邏輯:
/// <summary>
/// 卡片相關(guān)
/// </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>
/// 發(fā)牌
/// </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++)
{
//隨機(jī)抽取一張牌
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>
/// 規(guī)則
/// </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)
{
//現(xiàn)有牌號
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;
}
}單張規(guī)則和普通斗D主一樣(除了王以外2最大,其次是A),多張規(guī)則目前支持:王炸、對子、三只、順子、三帶一。目前只做到這里,各位同學(xué)可以拿回去自行擴(kuò)展。
上一些運(yùn)行圖。房主建房并加入:

新玩家加入:

房間人滿以后房主開始游戲,隨機(jī)分配地主:

出牌特效:

游戲結(jié)算:

最后附上開源地址(客戶端在web分支):https://gitee.com/muchengqingxin/card-game
tips:前端同學(xué)在沒有UI配合的情況下做到現(xiàn)在這樣,必須給個贊。最后提醒大家,不要拿去商用。
以上所述是小編給大家介紹的.Net Core使用SignalR實現(xiàn)斗地主游戲,希望對大家有所幫助。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
ASP.NET?Core在Linux下為dotnet創(chuàng)建守護(hù)進(jìn)程
本篇主要是怎么樣為我們在Linux或者macOs中部署的dotnet程序創(chuàng)建一個守護(hù)進(jìn)程,來保證我們的程序在異?;蛘呤请娔X重啟的時候仍然能夠正常訪問。需要的朋友可以收藏下,方便下次瀏覽觀看2021-12-12
搭建基礎(chǔ)結(jié)構(gòu)的ABP解決方案介紹
這篇文章介紹了搭建基礎(chǔ)結(jié)構(gòu)的ABP解決方案的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-02-02
ASP.NET Core應(yīng)用啟動Startup類簡介
這篇文章介紹了ASP.NET Core中的應(yīng)用啟動Startup類,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-04-04

