CB-01: Program.cs 加 --nhn 模式 [NEURAL,ISMCTS,NEURAL] - 3人跑得快 N插花坐排列 - Rotations 数组补全为4种排列 (IIN/NII/INI/NHN) - --nhn 参数: 不生成CSV, 直接评估 CB-02: 胜率解析统一总分法 - TrainingCollector 加 ScoreAccumulator/BotWins/BotGames 静态累加器 - GameOver 无条件调用 AccumulateScore (eval + 训练模式均记录) - stress_summary.txt 输出 #BOT_SCORES: bot=wins/games,wr=XX%,score=YYY - orchestrator eval 解析改用 #BOT_SCORES 格式, 正则回退逻辑移除 管线: orchestrator eval→stress_summary→总分法解析, 闭环可验证
163 lines
8.2 KiB
C#
163 lines
8.2 KiB
C#
using PdkFriendServer.Logic;
|
||
using System;
|
||
using System.Diagnostics;
|
||
using System.IO;
|
||
|
||
namespace hjha_console
|
||
{
|
||
class Program
|
||
{
|
||
public static int RotateInterval = 0; // 0=disabled, N=rotate every N rounds
|
||
private static readonly string[][] Rotations = new[] { // 4种排列,覆盖所有 NHN/NNH/HNN 组合
|
||
new[] { "ISMCTS", "ISMCTS", "NEURAL" },
|
||
new[] { "NEURAL", "ISMCTS", "ISMCTS" },
|
||
new[] { "ISMCTS", "NEURAL", "ISMCTS" },
|
||
new[] { "NEURAL", "ISMCTS", "NEURAL" } // NHN: N插花坐
|
||
};
|
||
|
||
static void Main(string[] args)
|
||
{
|
||
if (args.Length > 0 && args[0] == "--eval") { PdkGameMain.BotTypes = new[] { "ISMCTS", "ISMCTS", "NEURAL" }; TrainingCollector.Enabled = false; args = new[] { "--stress", args.Length > 1 ? args[1] : "10" }; }
|
||
if (args.Length > 0 && args[0] == "--mix") { PdkGameMain.BotTypes = new[] { "ISMCTS", "ISMCTS", "NEURAL" }; TrainingCollector.Enabled = true; args = new[] { "--stress", args.Length > 1 ? args[1] : "10" }; }
|
||
if (args.Length > 0 && args[0] == "--mix-rotate") { PdkGameMain.BotTypes = new[] { "ISMCTS", "ISMCTS", "NEURAL" }; TrainingCollector.Enabled = true; Program.RotateInterval = 10; args = new[] { "--stress", args.Length > 1 ? args[1] : "10" }; }
|
||
if (args.Length > 0 && args[0] == "--mix-ismcts") { PdkGameMain.BotTypes = new[] { "ISMCTS", "ISMCTS", "ISMCTS" }; TrainingCollector.Enabled = true; args = new[] { "--stress", args.Length > 1 ? args[1] : "10" }; }
|
||
if (args.Length > 0 && args[0] == "--bots") { PdkGameMain.BotTypes = args[1].Split(','); TrainingCollector.Enabled = true; args = args.Length > 2 ? new[] { "--stress", args[2] } : new[] { "--stress", "10" }; }
|
||
if (args.Length > 0 && args[0] == "--n2h2v5") { PdkGameMain.BotTypes = new[] { "ISMCTS", "ISMCTS", "NEURAL_V5" }; TrainingCollector.Enabled = false; args = new[] { "--stress", args.Length > 1 ? args[1] : "10" }; }
|
||
if (args.Length > 0 && args[0] == "--nhn") { PdkGameMain.BotTypes = new[] { "NEURAL", "ISMCTS", "NEURAL" }; TrainingCollector.Enabled = false; 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");
|
||
|
||
// 清空总分累加器
|
||
TrainingCollector.ResetAccumulator();
|
||
|
||
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++)
|
||
{
|
||
// 位置轮换: 每N局切换排列
|
||
if (RotateInterval > 0 && (r - 1) % RotateInterval == 0)
|
||
{
|
||
int ri = ((r - 1) / RotateInterval) % Rotations.Length;
|
||
PdkGameMain.BotTypes = Rotations[ri];
|
||
}
|
||
|
||
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;
|
||
var ismctsWriter = new StreamWriter(ismctsLog) { AutoFlush = true };
|
||
Console.SetOut(ismctsWriter);
|
||
main.Test("");
|
||
Console.SetOut(oldOut); // 先恢复再Close (macOS兼容)
|
||
ismctsWriter.Close();
|
||
|
||
// 合并 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");
|
||
var sb = new System.Text.StringBuilder();
|
||
sb.AppendLine($"Rounds: {completed}/{totalRounds}");
|
||
sb.AppendLine($"Errors: {errors}");
|
||
sb.AppendLine($"Total: {totalSw.Elapsed.TotalMinutes:F1}min");
|
||
sb.AppendLine($"Avg: {totalSw.Elapsed.TotalSeconds / totalRounds:F1}s/round");
|
||
// Bot 得分统计(总分法:累积 Score 总和 + 胜局数)
|
||
sb.AppendLine("#BOT_SCORES");
|
||
foreach (var t in TrainingCollector.ScoreAccumulator.Keys)
|
||
{
|
||
int wins = TrainingCollector.BotWins.ContainsKey(t) ? TrainingCollector.BotWins[t] : 0;
|
||
int games = TrainingCollector.BotGames.ContainsKey(t) ? TrainingCollector.BotGames[t] : 0;
|
||
float score = TrainingCollector.ScoreAccumulator[t];
|
||
float wr = games > 0 ? (float)wins / games * 100f : 0f;
|
||
sb.AppendLine($"{t}={wins}/{games},wr={wr:F1}%,score={score:F0}");
|
||
}
|
||
File.WriteAllText("stress_summary.txt", sb.ToString());
|
||
}
|
||
}
|
||
}
|