fix: V-learning 出牌率 0%→81% — MakePlay Type=None导致所有候选被当pass
根因: MakePlay(Type=CardType1.None) → play.Type==None 对所有候选为true → afterHand保持原样(未出牌) → 所有候选V值相同 → 始终选第一个(pass) 修复: - play.Type→play.Ids.Length判断是否真pass - EncodeAfterPlay更新ShenYuCard(出牌后剩余-1) - EncodeAfterPlay设MaxPlayCard为出的牌 - isPass用Ids==null而非Type 效果: Play 117 Pass 28(81%出牌) vs ISMCTS -108/10局
This commit is contained in:
@ -88,13 +88,30 @@ namespace PdkFriendServer.Logic
|
||||
// 3. V-learning: 对每个候选,编码出牌后的状态,选 V 值最高的
|
||||
float bestV = float.MinValue;
|
||||
int bestIdx = 0;
|
||||
for (int i = 0; i < candidates.Count; i++)
|
||||
var debugVals = new System.Text.StringBuilder();
|
||||
for (int i = 0; i < Math.Min(candidates.Count, 8); i++)
|
||||
{
|
||||
var play = candidates[i].play;
|
||||
// 构建出牌后的手牌
|
||||
var afterHand = play.Type == CardType1.None
|
||||
? hand
|
||||
: hand.Where(c => play.Ids == null || !play.Ids.Contains(c.ID)).ToArray();
|
||||
var afterHand = play.Ids != null && play.Ids.Length > 0
|
||||
? hand.Where(c => !play.Ids.Contains(c.ID)).ToArray()
|
||||
: hand;
|
||||
var afterState = EncodeAfterPlay(view, afterHand, play);
|
||||
var v = RunInference(afterState);
|
||||
float val = v.Length > 0 ? v[0] : 0;
|
||||
|
||||
string label = play.Ids != null && play.Ids.Length > 0
|
||||
? $"{play.Ids.Length}c r{play.GameNum}" : "pass";
|
||||
debugVals.Append($" {label}={val:F2}");
|
||||
|
||||
if (val > bestV) { bestV = val; bestIdx = i; }
|
||||
}
|
||||
// 继续评估剩余候选(不输出日志)
|
||||
for (int i = 8; i < candidates.Count; i++)
|
||||
{
|
||||
var play = candidates[i].play;
|
||||
var afterHand = play.Ids != null && play.Ids.Length > 0
|
||||
? hand.Where(c => !play.Ids.Contains(c.ID)).ToArray()
|
||||
: hand;
|
||||
|
||||
// 编码出牌后状态
|
||||
var afterState = EncodeAfterPlay(view, afterHand, play);
|
||||
@ -109,9 +126,10 @@ namespace PdkFriendServer.Logic
|
||||
}
|
||||
|
||||
var chosen = candidates[bestIdx];
|
||||
bool isPass = chosen.play.Type == CardType1.None;
|
||||
Console.WriteLine($"[NeuralBot pos{view.MyPos}] {candidates.Count}c → " +
|
||||
$"{(isPass ? "pass" : $"{chosen.play.Ids?.Length ?? 1}c rank={chosen.play.GameNum}")} V={bestV:F3}");
|
||||
bool isPass = chosen.play.Ids == null || chosen.play.Ids.Length == 0;
|
||||
Console.WriteLine($"[NeuralBot pos{view.MyPos}] {candidates.Count}c → "
|
||||
+ $"{(isPass ? "pass" : $"{chosen.play.Ids?.Length ?? 1}c r{chosen.play.GameNum}")} V={bestV:F3}"
|
||||
+ (debugVals.Length > 0 ? $" [{debugVals}]" : ""));
|
||||
return chosen.play;
|
||||
}
|
||||
|
||||
|
||||
@ -64,12 +64,23 @@ namespace PdkFriendServer.Logic
|
||||
public static float[] EncodeAfterPlay(PdkBotView view, CardTracker tracker,
|
||||
TCardInfoPdkF[] afterHand, int[] playedIds)
|
||||
{
|
||||
// 更新剩余张数:我打出了牌
|
||||
int playedCount = playedIds?.Length ?? 0;
|
||||
byte[] afterShenYu = new byte[view.ShenYuCard.Length];
|
||||
Array.Copy(view.ShenYuCard, afterShenYu, view.ShenYuCard.Length);
|
||||
afterShenYu[view.MyPos - 1] = (byte)Math.Max(0, afterShenYu[view.MyPos - 1] - playedCount);
|
||||
|
||||
var afterView = new PdkBotView
|
||||
{
|
||||
MyHand = afterHand,
|
||||
MyPos = view.MyPos,
|
||||
ShenYuCard = view.ShenYuCard,
|
||||
MaxPlayCard = view.MaxPlayCard,
|
||||
ShenYuCard = afterShenYu,
|
||||
// 出牌后:如果我不是pass,MaxPlayCard改成我出的牌
|
||||
MaxPlayCard = playedIds != null && playedIds.Length > 0
|
||||
? new PlayOutCardPdkF { Pos = (byte)view.MyPos,
|
||||
GameNum = view.MyHand.FirstOrDefault(c => playedIds.Contains(c.ID)).GameNum,
|
||||
Type = CardType1.DanZhang, Ids = playedIds.Select(id => (byte)id).ToArray() }
|
||||
: view.MaxPlayCard,
|
||||
Rule = view.Rule,
|
||||
};
|
||||
return EncodeState(afterView, tracker, playedIds);
|
||||
|
||||
Reference in New Issue
Block a user