[原创]一步一步用C#编写三国杀(三):设计流程
原创文章,转载请保留作者署名!
前面已经说了牌堆的设计,那么现在就正式进入流程,满足我们在(一)中所说的需求。
由于在(二)中已经说了要维护扩展,因此对于之前定义的Scene,则需要定义一个所选择的扩展包,代码如下:

private readonly IPackage[] selectedPackages;
/// <summary>
/// 初始化新的<see cref="Scene"/>类的实例。
/// </summary>
/// <param name="packages">所要加载的包。</param>
public Scene(IEnumerable<IPackage> packages)
{
players[currentToken].HasToken = true;
selectedPackages = packages.ToArray();
}
/// <summary>
/// 初始化新的<see cref="Scene"/>类的实例。
/// </summary>
/// <param name="packages">所要加载的包。</param>
public Scene(IEnumerable<IPackage> packages)
{
players[currentToken].HasToken = true;
selectedPackages = packages.ToArray();
}
由于玩家是轮动的,因此设定一个令牌,只有持有令牌的玩家才能行动。
private int currentToken;
那么定义一个Start方法,开始游戏循环。
首先要根据选择的扩展包生成游戏牌堆。对于游戏牌堆,由于我们之前已经定义好了牌堆基类,因此实现就比较简单了,将扩展包中的游戏牌载入并洗牌即可。

/// <summary>
/// 表示游戏牌牌堆。
/// </summary>
public sealed class GameCardHeap : CardHeap<GameCard>
{
/// <summary>
/// 初始化新的<see cref="GameCardHeap"/>类的实例。
/// </summary>
/// <param name="packages">所要加载的扩展包。</param>
public GameCardHeap(IEnumerable<IPackage> packages)
{
foreach (var package in packages)
{
((List<GameCard>) Items).AddRange(package.GameCards);
}
Items.Shuffle();
}
}
/// 表示游戏牌牌堆。
/// </summary>
public sealed class GameCardHeap : CardHeap<GameCard>
{
/// <summary>
/// 初始化新的<see cref="GameCardHeap"/>类的实例。
/// </summary>
/// <param name="packages">所要加载的扩展包。</param>
public GameCardHeap(IEnumerable<IPackage> packages)
{
foreach (var package in packages)
{
((List<GameCard>) Items).AddRange(package.GameCards);
}
Items.Shuffle();
}
}
回头看我们上面的Start方法,在牌堆创建后,就进入了流程循环,还是直接来代码吧,注释都写的蛮清楚的

/// <summary>
/// 开始游戏。
/// </summary>
public void Start()
{
Player currentPlayer;
GameCardHeap heap = new GameCardHeap(selectedPackages); // 创建牌堆
while(true)
{
currentPlayer = players[currentToken]; // 设置当前有令牌的玩家
// 摸牌阶段
GameCard[] newCards = heap.Pop(2, true);
currentPlayer.Draw(newCards);
// 出牌阶段
while(currentPlayer.HasPlayableCard)
{
// NOTE:为了简单,先实现只杀下家,并只使用杀、闪、桃
int nextToken = currentToken == players.Length - 1 ? 0 : currentToken + 1;
currentPlayer.Play(players[nextToken], currentPlayer.FirstPlayableCard);
if (IsGameEnds())
goto label;
}
// 弃牌阶段
// NOTE:为了简单,先实现只弃从头开始的牌到当前体力值
int disCardCount = currentPlayer.HandCards.Length - currentPlayer.Hp;
if (disCardCount > 0)
{
GameCard[] removeCards = currentPlayer.HandCards.Take(disCardCount).ToArray();
currentPlayer.Discard(removeCards);
}
// 将令牌给下一个人
GiveTokenToNext();
}
label:
Console.WriteLine("游戏结束!");
}
/// 开始游戏。
/// </summary>
public void Start()
{
Player currentPlayer;
GameCardHeap heap = new GameCardHeap(selectedPackages); // 创建牌堆
while(true)
{
currentPlayer = players[currentToken]; // 设置当前有令牌的玩家
// 摸牌阶段
GameCard[] newCards = heap.Pop(2, true);
currentPlayer.Draw(newCards);
// 出牌阶段
while(currentPlayer.HasPlayableCard)
{
// NOTE:为了简单,先实现只杀下家,并只使用杀、闪、桃
int nextToken = currentToken == players.Length - 1 ? 0 : currentToken + 1;
currentPlayer.Play(players[nextToken], currentPlayer.FirstPlayableCard);
if (IsGameEnds())
goto label;
}
// 弃牌阶段
// NOTE:为了简单,先实现只弃从头开始的牌到当前体力值
int disCardCount = currentPlayer.HandCards.Length - currentPlayer.Hp;
if (disCardCount > 0)
{
GameCard[] removeCards = currentPlayer.HandCards.Take(disCardCount).ToArray();
currentPlayer.Discard(removeCards);
}
// 将令牌给下一个人
GiveTokenToNext();
}
label:
Console.WriteLine("游戏结束!");
}
这里使用了一个死循环,但在游戏结束的时候使用goto语句跳出循环。所涉及到的一些方法如下:

private void GiveTokenToNext()
{
if (currentToken == players.Length - 1)
currentToken = 0;
else
currentToken++;
players[currentToken].HasToken = true;
}
private bool IsGameEnds()
{
return players.Any(p => p.IsDead); // 如果选择到IsDead的Player,游戏结束
}
{
if (currentToken == players.Length - 1)
currentToken = 0;
else
currentToken++;
players[currentToken].HasToken = true;
}
private bool IsGameEnds()
{
return players.Any(p => p.IsDead); // 如果选择到IsDead的Player,游戏结束
}
注意,流程的设计中,由于玩家是在游戏逻辑边界内的,因此采用了主动的做法来进行设计。这样对于程序来说,我只要去考虑玩家的摸牌、出牌和弃牌所引起的各种变化即可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探