Files
hjha-server/hjha-console/Program.cs
xiaoou f43e47b0a6 feat: V-learning架构 — 改Q→V
Q-learning问题: pass action Q值稳定偏高 → 永远pass
V-learning修复:
- 网络输出 14(Q-action)→1(V-state): P(win|state)
- NeuralBot: 对每个候选编码出牌后state → 选V值最高的
- pass和出牌都是候选, 公平竞争(不是固定action slot)

TrainingCollector:
- 新增 EncodeAfterPlay(view, tracker, afterHand, playedIds)
- EncodeState 支持 extraSeen 参数(模拟出牌时标记已见)

Python:
- train_v.py: V-learning训练器(load CSV→predict win/loss)
- network.py: OUTPUT_DIM=14→1

验证: 200局0错误, V-model loss 0.81→0.0001
需更多数据覆盖状态空间
2026-07-13 02:52:48 +08:00

129 lines
5.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using PdkFriendServer.Logic;
using System;
using System.Diagnostics;
using System.IO;
namespace hjha_console
{
class Program
{
static void Main(string[] args)
{
if (args.Length > 0 && args[0] == "--mix") { PdkGameMain.BotTypes = new[] { "NEURAL", "NEURAL", "ISMCTS" }; TrainingCollector.Enabled = true; args = new[] { "--stress", args.Length > 1 ? args[1] : "10" }; }
if (args.Length > 0 && args[0] == "--stress")
{
int rounds = args.Length > 1 ? int.Parse(args[1]) : 100;
RunStressTest(rounds);
return;
}
Console.WriteLine("=== HJHA Console Mode - 跑得快 Bot对局 (3人自动) ===");
Console.WriteLine("牌型:关牌玩法 16张/人 | 策略ISMCTS 800sims");
Console.WriteLine("压测: dotnet run --project hjha-console -- --stress 1000");
Console.WriteLine();
var main = new PdkGameMain(null);
main.BotMode = true;
main.Test(args.Length > 0 ? args[0] : "");
}
static void RunStressTest(int totalRounds)
{
var logDir = "stress_logs";
if (!Directory.Exists(logDir)) Directory.CreateDirectory(logDir);
// 清理旧日志
foreach (var f in Directory.GetFiles(logDir, "round_*.txt"))
File.Delete(f);
File.Delete("stress_errors.txt");
File.Delete("stress_summary.txt");
Console.WriteLine($"=== PdkBot Stress Test: {totalRounds} rounds ===");
Console.WriteLine($"Logs: {logDir}/");
Console.WriteLine($"Start: {DateTime.Now:HH:mm:ss}");
Console.WriteLine();
int completed = 0, errors = 0;
var totalSw = Stopwatch.StartNew();
var errorLog = new StreamWriter("stress_errors.txt", true) { AutoFlush = true };
for (int r = 1; r <= totalRounds; r++)
{
var roundSw = Stopwatch.StartNew();
try
{
var main = new PdkGameMain(null);
main.BotMode = true;
// 本轮日志路径
var logPath = $"{logDir}/round_{r:D5}.txt";
if (File.Exists("output.txt")) File.Delete("output.txt");
// 重定向 Console stdout → 本轮 ISMCTS 日志
var ismctsLog = $"{logDir}/round_{r:D5}_ismcts.txt";
var oldOut = Console.Out;
using (var ismctsWriter = new StreamWriter(ismctsLog) { AutoFlush = true })
{
Console.SetOut(ismctsWriter);
main.Test("");
}
Console.SetOut(oldOut);
// 合并 output.txt + ISMCTS 日志 → 本轮日志
using (var combined = new StreamWriter(logPath, false))
{
if (File.Exists(ismctsLog))
{
combined.Write(File.ReadAllText(ismctsLog));
combined.WriteLine();
File.Delete(ismctsLog);
}
if (File.Exists("output.txt"))
{
combined.Write(File.ReadAllText("output.txt"));
}
}
if (File.Exists("output.txt")) File.Delete("output.txt");
completed++;
roundSw.Stop();
if (r % 50 == 0 || r == totalRounds)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {r}/{totalRounds} rounds | " +
$"{errors} errors | avg {totalSw.Elapsed.TotalSeconds / r:F1}s/round | " +
$"last {roundSw.ElapsedMilliseconds}ms");
}
}
catch (Exception ex)
{
errors++;
errorLog.WriteLine($"Round {r}: {ex.GetType().Name}: {ex.Message}");
errorLog.WriteLine(ex.StackTrace);
errorLog.WriteLine("---");
Console.WriteLine($" R{r} ERROR: {ex.GetType().Name}: {ex.Message}");
// 保存本轮部分日志
if (File.Exists("output.txt"))
File.Move("output.txt", $"{logDir}/round_{r:D5}_ERROR.txt");
}
}
totalSw.Stop();
errorLog.Close();
// 汇总
Console.WriteLine();
Console.WriteLine($"=== Stress Test Complete ===");
Console.WriteLine($"Rounds: {completed}/{totalRounds} completed, {errors} errors");
Console.WriteLine($"Total time: {totalSw.Elapsed.TotalMinutes:F1} min");
Console.WriteLine($"Avg per round: {totalSw.Elapsed.TotalSeconds / totalRounds:F1}s");
File.WriteAllText("stress_summary.txt",
$"Rounds: {completed}/{totalRounds}\n" +
$"Errors: {errors}\n" +
$"Total: {totalSw.Elapsed.TotalMinutes:F1}min\n" +
$"Avg: {totalSw.Elapsed.TotalSeconds / totalRounds:F1}s/round\n");
}
}
}