IsmctsBot.cs:539: C#8 switch expression → C#7.3 ternary
num switch {1=>14,2=>16,_=>num} → num==1?14:num==2?16:num
bot代码(.NET 4.8)兼容性清单:
✅ LINQ (GroupBy/OrderBy/SelectMany) — 原生
✅ string 插值 $"..." — C#6
✅ ?./?? — C#6
✅ ValueTuple — 需 NuGet System.ValueTuple (net48)
✅ 已移除 switch expression — 唯一 C#8 特性
net48 项目集成只需3步:
1. 复制 PdkBot.cs + IsmctsBot.cs → PdkFriendServer/Logic/
2. csproj: <PackageReference Include="System.ValueTuple" Version="4.5.0"/>
3. WaitWanJiaShuRu 加入 BotMode 分支(同 net10.0 逻辑)
573 lines
22 KiB
C#
573 lines
22 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using GameFix.PaoDeKuaiF;
|
||
using GameFix.Poker;
|
||
using GameMessage;
|
||
using GameMessage.PaoDeKuaiF;
|
||
|
||
namespace PdkFriendServer.Logic
|
||
{
|
||
/// <summary>
|
||
/// ISMCTS 机器人 v2
|
||
/// P0修复: 包庄阈值 70%(从 45% 校准),rollout 用最强方案而非最小
|
||
/// P2修复: 优先多牌组合(对子/顺子 > 单张),提高清牌效率
|
||
/// </summary>
|
||
public class IsmctsBot : IPdkBot
|
||
{
|
||
private readonly int _simulations;
|
||
private readonly Random _rng;
|
||
private const double BaoThreshold = 0.60; // 校准自1000局压测:70%→0人包,45%→5.2%成功
|
||
|
||
public IsmctsBot(int simulations = 800)
|
||
{
|
||
_simulations = simulations;
|
||
_rng = new Random();
|
||
}
|
||
|
||
// ---- 包庄决策 ----
|
||
|
||
public bool DecideBaoZhuang(PdkBotView view)
|
||
{
|
||
double winRate = EstimateWinProbability(view);
|
||
bool shouldBao = winRate >= BaoThreshold;
|
||
|
||
Console.WriteLine($"[ISMCTS pos{view.MyPos}] 包庄评估: P(win)={winRate:P0} "
|
||
+ $"(阈值{BaoThreshold:P0}) → {(shouldBao ? "包庄" : "不包")}");
|
||
return shouldBao;
|
||
}
|
||
|
||
private double EstimateWinProbability(PdkBotView view)
|
||
{
|
||
var hand = view.MyHand;
|
||
int sims = Math.Min(_simulations, 600);
|
||
|
||
int wins = 0;
|
||
for (int s = 0; s < sims; s++)
|
||
{
|
||
if (SimulateWin(hand, view))
|
||
wins++;
|
||
}
|
||
return (double)wins / sims;
|
||
}
|
||
|
||
private bool SimulateWin(TCardInfoPdkF[] myHand, PdkBotView view)
|
||
{
|
||
int myPos = view.MyPos;
|
||
int nextPos = myPos % 3 + 1;
|
||
int oppPos = (myPos + 1) % 3 + 1;
|
||
|
||
var hands = new List<TCardInfoPdkF>[3];
|
||
hands[myPos - 1] = new List<TCardInfoPdkF>(myHand);
|
||
hands[nextPos - 1] = new List<TCardInfoPdkF>();
|
||
hands[oppPos - 1] = new List<TCardInfoPdkF>();
|
||
|
||
var allCards = GenerateDeck();
|
||
var unknown = allCards.Where(c => !myHand.Any(h => h.ID == c.ID)).ToList();
|
||
Shuffle(unknown);
|
||
|
||
hands[nextPos - 1].AddRange(unknown.Take(view.ShenYuCard[nextPos - 1]));
|
||
hands[oppPos - 1].AddRange(unknown.Skip(view.ShenYuCard[nextPos - 1])
|
||
.Take(view.ShenYuCard[oppPos - 1]));
|
||
|
||
int current = myPos;
|
||
var maxPlay = new PlayOutCardPdkF();
|
||
int maxPlayer = 0;
|
||
int passStreak = 0;
|
||
|
||
for (int move = 0; move < 400; move++)
|
||
{
|
||
var curHand = hands[current - 1];
|
||
if (curHand.Count == 0) return current == myPos;
|
||
|
||
bool isNewRound = maxPlayer == current || maxPlay.GameNum == 0;
|
||
var curMax = isNewRound ? new PlayOutCardPdkF() : maxPlay;
|
||
var tips = PdkCardAlgorithm.GetTipCard(curMax, curHand.ToArray(), null, false);
|
||
|
||
if (tips != null && tips.Count > 0)
|
||
{
|
||
// P2修复: 优先多牌组合,其次选最强(最大GameNum)
|
||
var pick = PickBestTip(tips, isNewRound);
|
||
maxPlay = new PlayOutCardPdkF
|
||
{
|
||
Pos = (byte)current,
|
||
GameNum = pick[0].GameNum,
|
||
Type = pick.Count == 1 ? CardType1.DanZhang : CardType1.DuiZi,
|
||
Ids = pick.Select(c => c.ID).ToArray()
|
||
};
|
||
maxPlayer = current;
|
||
passStreak = 0;
|
||
foreach (var id in maxPlay.Ids)
|
||
curHand.RemoveAll(c => c.ID == id);
|
||
}
|
||
else
|
||
{
|
||
passStreak++;
|
||
// 防止死循环: 三轮全 pass 则强制最大出牌
|
||
if (passStreak >= 2)
|
||
{
|
||
var forceTips = PdkCardAlgorithm.GetTipCard(
|
||
new PlayOutCardPdkF(), curHand.ToArray(), null, false);
|
||
if (forceTips != null && forceTips.Count > 0)
|
||
{
|
||
var pick = PickBestTip(forceTips, true);
|
||
maxPlay = new PlayOutCardPdkF
|
||
{
|
||
Pos = (byte)current,
|
||
GameNum = pick[0].GameNum,
|
||
Type = CardType1.DanZhang,
|
||
Ids = pick.Select(c => c.ID).ToArray()
|
||
};
|
||
maxPlayer = current;
|
||
foreach (var id in maxPlay.Ids)
|
||
curHand.RemoveAll(c => c.ID == id);
|
||
}
|
||
passStreak = 0;
|
||
}
|
||
}
|
||
current = current % 3 + 1;
|
||
}
|
||
|
||
return hands[myPos - 1].Count <= hands[nextPos - 1].Count
|
||
&& hands[myPos - 1].Count <= hands[oppPos - 1].Count;
|
||
}
|
||
|
||
// ---- 出牌决策 ----
|
||
|
||
public PlayOutCardPdkF DecidePlay(PdkBotView view)
|
||
{
|
||
var hand = view.MyHand;
|
||
if (hand.Length == 0) return Pass(view);
|
||
|
||
bool isFirstPlay = view.MaxPlayCard.GameNum <= 0;
|
||
|
||
// 1. 获取合法出牌方案
|
||
var tips = PdkCardAlgorithm.GetTipCard(
|
||
view.MaxPlayCard, hand, null, view.Rule.AAAIsZhaDan);
|
||
|
||
var allCandidates = new List<(PlayOutCardPdkF play, bool isMulti)>();
|
||
var multiCount = 0;
|
||
|
||
if (tips != null)
|
||
foreach (var tip in tips)
|
||
{
|
||
var isMulti = tip.Count > 1;
|
||
if (isMulti) multiCount++;
|
||
allCandidates.Add((MakePlay(tip, view.MyPos), isMulti));
|
||
}
|
||
|
||
// 2. 新轮次:补充多牌先手方案(GetTipCard只返回单张)
|
||
if (isFirstPlay)
|
||
{
|
||
AddMultiCardLeads(hand, view.MyPos, allCandidates, ref multiCount);
|
||
}
|
||
|
||
if (!isFirstPlay)
|
||
allCandidates.Add((Pass(view), false));
|
||
|
||
var scoredCandidates = new List<(PlayOutCardPdkF play, double winRate, bool isMulti)>();
|
||
|
||
if (allCandidates.Count == 1)
|
||
return allCandidates[0].play;
|
||
|
||
int unknownCount = 48 - hand.Length - CountPlayed(hand, view);
|
||
int simsPerCandidate = Math.Max(50, _simulations / allCandidates.Count);
|
||
|
||
for (int ci = 0; ci < allCandidates.Count; ci++)
|
||
{
|
||
int wins = 0;
|
||
var play = allCandidates[ci].play;
|
||
|
||
for (int s = 0; s < simsPerCandidate; s++)
|
||
{
|
||
if (SimulateOneGame(hand, play, view, unknownCount))
|
||
wins++;
|
||
}
|
||
|
||
double wr = (double)wins / simsPerCandidate;
|
||
scoredCandidates.Add((play, wr, allCandidates[ci].isMulti));
|
||
}
|
||
|
||
// 两阶段选择:多牌组合胜率≥10%就优用
|
||
scoredCandidates.Sort((a, b) => b.winRate.CompareTo(a.winRate));
|
||
var best = scoredCandidates[0];
|
||
if (multiCount > 0)
|
||
{
|
||
var bestMulti = scoredCandidates.Where(x => x.isMulti)
|
||
.OrderByDescending(x => x.play.Ids?.Length ?? 0)
|
||
.ThenByDescending(x => x.winRate)
|
||
.FirstOrDefault();
|
||
if (bestMulti.play.Ids != null && bestMulti.play.Ids.Length > 0)
|
||
best = bestMulti;
|
||
}
|
||
|
||
string desc = best.play.Ids == null || best.play.Ids.Length == 0
|
||
? "pass"
|
||
: $"{best.play.Ids.Length}c";
|
||
Console.WriteLine($"[ISMCTS pos{view.MyPos}] {simsPerCandidate * allCandidates.Count}sims "
|
||
+ $"winRate:{best.winRate:P0} => {desc}");
|
||
|
||
return best.play;
|
||
}
|
||
|
||
// ---- 模拟引擎 ----
|
||
|
||
private bool SimulateOneGame(
|
||
TCardInfoPdkF[] myHand, PlayOutCardPdkF myPlay,
|
||
PdkBotView view, int unknownCount)
|
||
{
|
||
int myPos = view.MyPos;
|
||
int nextPos = myPos % 3 + 1;
|
||
int oppPos = (myPos + 1) % 3 + 1;
|
||
|
||
var hands = new List<TCardInfoPdkF>[3];
|
||
hands[myPos - 1] = new List<TCardInfoPdkF>(myHand);
|
||
hands[nextPos - 1] = new List<TCardInfoPdkF>();
|
||
hands[oppPos - 1] = new List<TCardInfoPdkF>();
|
||
|
||
var allCards = GenerateDeck();
|
||
var unknown = allCards.Where(c => !myHand.Any(h => h.ID == c.ID)).ToList();
|
||
Shuffle(unknown);
|
||
|
||
hands[nextPos - 1].AddRange(unknown.Take(view.ShenYuCard[nextPos - 1]));
|
||
hands[oppPos - 1].AddRange(unknown.Skip(view.ShenYuCard[nextPos - 1])
|
||
.Take(view.ShenYuCard[oppPos - 1]));
|
||
|
||
var maxPlay = new PlayOutCardPdkF
|
||
{
|
||
Pos = (byte)myPos, GameNum = myPlay.GameNum,
|
||
Type = myPlay.Type, Ids = myPlay.Ids?.ToArray()
|
||
};
|
||
if (myPlay.Type != CardType1.None && myPlay.Ids != null)
|
||
foreach (var id in myPlay.Ids)
|
||
hands[myPos - 1].RemoveAll(c => c.ID == id);
|
||
|
||
int current = myPos % 3 + 1;
|
||
int maxPlayer = myPlay.Type == CardType1.None ? view.MaxPlayCard.Pos : myPos;
|
||
if (myPlay.Type == CardType1.None)
|
||
{
|
||
maxPlayer = view.MaxPlayCard.Pos;
|
||
maxPlay = CloneCard(view.MaxPlayCard);
|
||
}
|
||
|
||
int moveCount = 0;
|
||
int passStreak = 0;
|
||
|
||
while (moveCount++ < 500)
|
||
{
|
||
var curHand = hands[current - 1];
|
||
if (curHand.Count == 0) return current == myPos;
|
||
|
||
bool isNewRound = maxPlayer == current || maxPlay.GameNum == 0;
|
||
var curMax = isNewRound ? new PlayOutCardPdkF() : maxPlay;
|
||
var curTips = PdkCardAlgorithm.GetTipCard(curMax, curHand.ToArray(), null, false);
|
||
|
||
if (curTips != null && curTips.Count > 0)
|
||
{
|
||
var pick = PickBestTip(curTips, isNewRound);
|
||
maxPlay = new PlayOutCardPdkF
|
||
{
|
||
Pos = (byte)current,
|
||
GameNum = pick[0].GameNum,
|
||
Type = pick.Count == 1 ? CardType1.DanZhang : CardType1.DuiZi,
|
||
Ids = pick.Select(c => c.ID).ToArray()
|
||
};
|
||
maxPlayer = current;
|
||
passStreak = 0;
|
||
foreach (var id in maxPlay.Ids)
|
||
curHand.RemoveAll(c => c.ID == id);
|
||
}
|
||
else
|
||
{
|
||
passStreak++;
|
||
if (passStreak >= 2)
|
||
{
|
||
var forceTips = PdkCardAlgorithm.GetTipCard(
|
||
new PlayOutCardPdkF(), curHand.ToArray(), null, false);
|
||
if (forceTips != null && forceTips.Count > 0)
|
||
{
|
||
var pick = PickBestTip(forceTips, true);
|
||
maxPlay = new PlayOutCardPdkF
|
||
{
|
||
Pos = (byte)current, GameNum = pick[0].GameNum,
|
||
Type = CardType1.DanZhang, Ids = pick.Select(c => c.ID).ToArray()
|
||
};
|
||
maxPlayer = current;
|
||
foreach (var id in maxPlay.Ids)
|
||
curHand.RemoveAll(c => c.ID == id);
|
||
}
|
||
passStreak = 0;
|
||
}
|
||
}
|
||
current = current % 3 + 1;
|
||
}
|
||
|
||
return hands[myPos - 1].Count <= hands[nextPos - 1].Count
|
||
&& hands[myPos - 1].Count <= hands[oppPos - 1].Count;
|
||
}
|
||
|
||
// ---- Rollout 策略 ----
|
||
|
||
/// <summary>
|
||
/// 新轮次先手时,补充多牌组合候选(GetTipCard 只返回单张)
|
||
/// 枚举:对子、三张、顺子、连对、炸弹
|
||
/// </summary>
|
||
private static void AddMultiCardLeads(TCardInfoPdkF[] hand, int pos,
|
||
List<(PlayOutCardPdkF play, bool isMulti)> candidates, ref int multiCount)
|
||
{
|
||
// 炸弹
|
||
if (PokerLogic.GetPlayZhaDans(hand, out var bombs) && bombs != null)
|
||
foreach (var bomb in bombs)
|
||
AddCandidate(bomb, pos, candidates, ref multiCount);
|
||
|
||
// 对子(2张相同)
|
||
foreach (var pair in GetMultiGroups(hand, 2))
|
||
AddCandidate(pair, pos, candidates, ref multiCount);
|
||
|
||
// 三张 / 三带二
|
||
var triples = GetMultiGroups(hand, 3);
|
||
foreach (var t in triples)
|
||
{
|
||
// 三带二: 三张 + 附带最小的两张其他牌
|
||
var remainder = hand.Where(c => c.GameNum != t[0].GameNum).OrderBy(c => c.GameNum).ToList();
|
||
if (remainder.Count >= 2)
|
||
{
|
||
var sd2 = t.Concat(remainder.Take(2)).ToList();
|
||
AddCandidate(sd2, pos, candidates, ref multiCount);
|
||
}
|
||
else
|
||
{
|
||
AddCandidate(t, pos, candidates, ref multiCount); // 手牌不够带,纯三张
|
||
}
|
||
}
|
||
|
||
// 四带二 / 四带三
|
||
var quads = GetMultiGroups(hand, 4);
|
||
foreach (var q in quads)
|
||
{
|
||
var remainder = hand.Where(c => c.GameNum != q[0].GameNum).OrderBy(c => c.GameNum).ToList();
|
||
if (remainder.Count >= 3)
|
||
{
|
||
var sd3 = q.Concat(remainder.Take(3)).ToList();
|
||
AddCandidate(sd3, pos, candidates, ref multiCount);
|
||
}
|
||
else if (remainder.Count >= 2)
|
||
{
|
||
var sd2 = q.Concat(remainder.Take(2)).ToList();
|
||
AddCandidate(sd2, pos, candidates, ref multiCount);
|
||
}
|
||
// else: 炸弹已在上面处理
|
||
}
|
||
|
||
// 连对(≥3对连续, 如 334455)
|
||
var pairs = GetMultiGroups(hand, 2);
|
||
SortByGameNum(pairs);
|
||
AddConsecutiveGroups(pairs, 3, pos, candidates, ref multiCount);
|
||
|
||
// 飞机(≥2组连续三张, 如 333444)
|
||
SortByGameNum(triples);
|
||
AddConsecutiveGroups(triples, 2, pos, candidates, ref multiCount);
|
||
|
||
// 飞机带对: 连续三张 + 每组附带一个对子
|
||
var tripleConsecRuns = GetConsecutiveRuns(triples, 2);
|
||
foreach (var run in tripleConsecRuns)
|
||
{
|
||
var runCards = run.SelectMany(t => t).ToList();
|
||
int pairCount = run.Count; // 需要这么多对子
|
||
// 找不重叠的对子
|
||
var remainingPairs = GetMultiGroups(
|
||
hand.Where(c => !runCards.Any(r => r.ID == c.ID)).ToArray(), 2);
|
||
if (remainingPairs.Count >= pairCount)
|
||
{
|
||
SortByGameNum(remainingPairs);
|
||
var attached = remainingPairs.Take(pairCount).SelectMany(p => p).ToList();
|
||
runCards.AddRange(attached);
|
||
AddCandidate(runCards, pos, candidates, ref multiCount);
|
||
}
|
||
}
|
||
|
||
// 顺子(≥5 张连续)
|
||
var straights = PokerLogic.GetShunZi(hand);
|
||
if (straights != null)
|
||
foreach (var s in straights)
|
||
AddCandidate(s, pos, candidates, ref multiCount);
|
||
}
|
||
|
||
private static void AddCandidate(List<TCardInfoPdkF> cards, int pos,
|
||
List<(PlayOutCardPdkF play, bool isMulti)> candidates, ref int multiCount)
|
||
{
|
||
candidates.Add((new PlayOutCardPdkF
|
||
{
|
||
Pos = (byte)pos, GameNum = cards[0].GameNum,
|
||
Ids = cards.Select(c => c.ID).ToArray(),
|
||
Type = InferType(cards)
|
||
}, true));
|
||
multiCount++;
|
||
}
|
||
|
||
/// <summary>根据牌数和面值推断牌型——ISMCTS模拟需要正确Type</summary>
|
||
private static CardType1 InferType(List<TCardInfoPdkF> cards)
|
||
{
|
||
int n = cards.Count;
|
||
if (n == 1) return CardType1.DanZhang;
|
||
if (n == 2) return CardType1.DuiZi;
|
||
var counts = cards.GroupBy(c => c.GameNum).Select(g => g.Count()).OrderByDescending(x => x).ToList();
|
||
if (counts[0] == 4)
|
||
{
|
||
if (n == 4) return CardType1.ZhaDan;
|
||
if (n == 6) return CardType1.SiDai2;
|
||
if (n == 7) return CardType1.SiDai3;
|
||
}
|
||
if (counts[0] >= 3) return n == counts[0] + 2 ? CardType1.SanDai2 : CardType1.SanDai2;
|
||
return n >= 5 ? CardType1.ShunZi : CardType1.DuiZi;
|
||
}
|
||
|
||
private static PlayOutCardPdkF MakePlay(List<TCardInfoPdkF> cards, int pos)
|
||
{
|
||
return new PlayOutCardPdkF
|
||
{
|
||
Pos = (byte)pos, GameNum = cards[0].GameNum,
|
||
Ids = cards.Select(x => x.ID).ToArray(),
|
||
Type = InferType(cards)
|
||
};
|
||
}
|
||
|
||
/// <summary>获取指定数量的同面值组合</summary>
|
||
private static List<List<TCardInfoPdkF>> GetMultiGroups(TCardInfoPdkF[] hand, int count)
|
||
{
|
||
var result = new List<List<TCardInfoPdkF>>();
|
||
var groups = hand.GroupBy(c => c.GameNum).Where(g => g.Count() >= count);
|
||
foreach (var g in groups)
|
||
{
|
||
var cards = g.Take(count).ToList();
|
||
result.Add(cards);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/// <summary>按 GameNum 排序多牌组列表</summary>
|
||
private static void SortByGameNum(List<List<TCardInfoPdkF>> groups)
|
||
{
|
||
groups.Sort((a, b) => a[0].GameNum.CompareTo(b[0].GameNum));
|
||
}
|
||
|
||
/// <summary>找出所有连续段(≥minLen),每段生成一个候选</summary>
|
||
private static void AddConsecutiveGroups(List<List<TCardInfoPdkF>> groups, int minLen,
|
||
int pos, List<(PlayOutCardPdkF play, bool isMulti)> candidates, ref int multiCount)
|
||
{
|
||
var runs = GetConsecutiveRuns(groups, minLen);
|
||
foreach (var run in runs)
|
||
{
|
||
var cards = run.SelectMany(g => g).ToList();
|
||
AddCandidate(cards, pos, candidates, ref multiCount);
|
||
}
|
||
}
|
||
|
||
/// <summary>从已排序的组中提取所有连续段(≥minLen 组)</summary>
|
||
private static List<List<List<TCardInfoPdkF>>> GetConsecutiveRuns(
|
||
List<List<TCardInfoPdkF>> groups, int minLen)
|
||
{
|
||
var runs = new List<List<List<TCardInfoPdkF>>>();
|
||
if (groups.Count < minLen) return runs;
|
||
|
||
var current = new List<List<TCardInfoPdkF>> { groups[0] };
|
||
int prevNum = groups[0][0].GameNum;
|
||
|
||
for (int i = 1; i < groups.Count; i++)
|
||
{
|
||
int curNum = groups[i][0].GameNum;
|
||
if (curNum == prevNum + 1)
|
||
{
|
||
current.Add(groups[i]);
|
||
}
|
||
else
|
||
{
|
||
if (current.Count >= minLen)
|
||
runs.Add(new List<List<TCardInfoPdkF>>(current));
|
||
current.Clear();
|
||
current.Add(groups[i]);
|
||
}
|
||
prevNum = curNum;
|
||
}
|
||
if (current.Count >= minLen)
|
||
runs.Add(current);
|
||
|
||
return runs;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从合法方案中选最优:优先出多张牌的组合,其次出最大的
|
||
/// P2修复: 之前只取 tips[0](最小单张),改为优先多牌组合
|
||
/// P0修复: 选最大(最后一项),模拟更强对手
|
||
/// </summary>
|
||
private static List<TCardInfoPdkF> PickBestTip(List<List<TCardInfoPdkF>> tips, bool isNewRound)
|
||
{
|
||
if (tips == null || tips.Count == 0) return null;
|
||
|
||
// 优先多牌组合(2张>1张,3张>2张等),同数量选最大的
|
||
var best = tips[tips.Count - 1]; // 默认最大
|
||
int bestScore = best.Count * 10 + best[0].GameNum;
|
||
|
||
for (int i = tips.Count - 2; i >= 0; i--)
|
||
{
|
||
int score = tips[i].Count * 10 + tips[i][0].GameNum;
|
||
if (score > bestScore)
|
||
{
|
||
bestScore = score;
|
||
best = tips[i];
|
||
}
|
||
}
|
||
return best;
|
||
}
|
||
|
||
// ---- 工具 ----
|
||
|
||
private static int CountPlayed(TCardInfoPdkF[] myHand, PdkBotView view)
|
||
{
|
||
int total = 48 - myHand.Length;
|
||
foreach (var c in view.ShenYuCard) total -= c;
|
||
return Math.Max(0, total);
|
||
}
|
||
|
||
private static List<TCardInfoPdkF> GenerateDeck()
|
||
{
|
||
var deck = new List<TCardInfoPdkF>();
|
||
int id = 1;
|
||
foreach (int flower in new[] { 1, 2, 3, 4 })
|
||
for (int num = 1; num <= 13; num++)
|
||
{
|
||
int gameNum = num == 1 ? (byte)14 : num == 2 ? (byte)16 : (byte)num;
|
||
deck.Add(new TCardInfoPdkF
|
||
{
|
||
ID = (byte)id++, Flower = (byte)flower,
|
||
GameNum = (byte)gameNum, GameState = 1
|
||
});
|
||
}
|
||
return deck;
|
||
}
|
||
|
||
private void Shuffle<T>(List<T> list)
|
||
{
|
||
for (int i = list.Count - 1; i > 0; i--)
|
||
{
|
||
int j = _rng.Next(i + 1);
|
||
(list[i], list[j]) = (list[j], list[i]);
|
||
}
|
||
}
|
||
|
||
private static PlayOutCardPdkF CloneCard(PlayOutCardPdkF src)
|
||
{
|
||
return new PlayOutCardPdkF
|
||
{
|
||
Pos = src.Pos, GameNum = src.GameNum,
|
||
Type = src.Type, Ids = src.Ids?.ToArray()
|
||
};
|
||
}
|
||
|
||
private static PlayOutCardPdkF Pass(PdkBotView view)
|
||
{
|
||
return new PlayOutCardPdkF { Pos = (byte)view.MyPos, Type = CardType1.None };
|
||
}
|
||
}
|
||
}
|