feat: CSV加player列+#RESULT汇总 — 训练中直接看胜率进化
This commit is contained in:
@ -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)
|
||||
|
||||
@ -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
67
scripts/scan_wins.py
Normal 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)
|
||||
Reference in New Issue
Block a user