feat: CSV加player列+#RESULT汇总 — 训练中直接看胜率进化

This commit is contained in:
2026-07-13 08:40:14 +08:00
parent 15b9ccb22b
commit 22acc3b2ca
3 changed files with 73 additions and 4 deletions

View File

@ -499,7 +499,7 @@ namespace PdkFriendServer.Logic
float s = GamePack.Info.Score[i];
rewards[i] = s > 0 ? 1f : s < 0 ? -1f : 0f;
}
TrainingCollector.FlushGame(_flushGameCounter++, rewards);
TrainingCollector.FlushGame(_flushGameCounter++, rewards, GamePack.Info.OverPos, BotTypes);
}
if (DeskGameDo != null)

View File

@ -39,19 +39,21 @@ namespace PdkFriendServer.Logic
/// 游戏结束,输出本轮训练数据到文件。
/// rewards: 每个玩家的奖励 (1=赢, 0=第二, -1=第三)
/// </summary>
public static void FlushGame(int gameId, float[] rewards)
public static void FlushGame(int gameId, float[] rewards, int winnerPos, string[] botTypes)
{
if (!Enabled || _records.Count == 0) return;
var path = $"training_data/game_{gameId:D5}.csv";
System.IO.Directory.CreateDirectory("training_data");
using var w = new System.IO.StreamWriter(path);
w.WriteLine("state,action,reward");
w.WriteLine("state,action,reward,player");
foreach (var r in _records)
{
float reward = r.Player >= 0 && r.Player < rewards.Length ? rewards[r.Player] : 0;
var stateStr = string.Join(",", r.State.Select(f => f.ToString("F6")));
w.WriteLine($"\"{stateStr}\",{r.Action},{reward:F3}");
w.WriteLine($"\"{stateStr}\",{r.Action},{reward:F3},{r.Player}");
}
// 游戏结果摘要 — 方便不中断训练即可评估模型进化
w.WriteLine($"#RESULT,winner={winnerPos},bots={string.Join(",", botTypes ?? new[]{"ISMCTS","ISMCTS","ISMCTS"})}");
_records.Clear();
}

67
scripts/scan_wins.py Normal file
View File

@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""扫描 training_data 目录下所有 CSV按 bot 类型统计胜率"""
import sys, os, glob, re
from collections import Counter, defaultdict
def scan(csv_dir='training_data'):
csvs = sorted(glob.glob(os.path.join(csv_dir, 'game_*.csv')))
if not csvs:
print("No CSV files found")
return
total = 0
per_bot = defaultdict(lambda: {'wins': 0, 'games': 0})
per_game = [] # (game_id, winner_pos, bots_str)
for fp in csvs:
with open(fp) as f:
for line in f:
if line.startswith('#RESULT'):
parts = line.strip().split(',')
winner = int(parts[1].split('=')[1])
bots = parts[2].split('=')[1].split(',')
game_id = os.path.splitext(os.path.basename(fp))[0]
per_game.append((game_id, winner, line.strip()))
total += 1
# winner_pos is 1-indexed, bots[winner-1]
for i, bt in enumerate(bots):
per_bot[bt]['games'] += 1
winner_bt = bots[winner - 1] if 0 < winner <= len(bots) else '?'
per_bot[winner_bt]['wins'] += 1
break
if total == 0:
print("No #RESULT lines found. Run with updated hjha-console.")
return
print(f"{total} games scanned")
print(f"{'Bot':<12} {'Wins':>6} {'Rate':>8} {'Games':>6}")
print("-" * 35)
for bt in sorted(per_bot.keys()):
d = per_bot[bt]
rate = d['wins'] / d['games'] * 100 if d['games'] > 0 else 0
print(f"{bt:<12} {d['wins']:>6} {rate:>7.1f}% {d['games']:>6}")
# 也统计按 bot 组合
combo_stats = defaultdict(lambda: {'total': 0, 'winners': Counter()})
for gid, winner, line in per_game:
m = re.search(r'bots=([^,]+(?:,[^,]+)*)', line)
if m:
key = m.group(1)
combo_stats[key]['total'] += 1
combo_stats[key]['winners'][winner] += 1
if len(combo_stats) > 0:
print(f"\nBy bot composition:")
for combo in sorted(combo_stats.keys()):
d = combo_stats[combo]
bots = combo.split(',')
print(f" [{combo}] — {d['total']} games:")
for pos, count in sorted(d['winners'].items()):
bt = bots[pos-1] if 0 < pos <= len(bots) else '?'
print(f" pos{pos}({bt}): {count}/{d['total']} ({count/d['total']*100:.0f}%)")
if __name__ == '__main__':
d = sys.argv[1] if len(sys.argv) > 1 else 'training_data'
scan(d)