-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathOpenings.py
More file actions
142 lines (115 loc) · 4.21 KB
/
Copy pathOpenings.py
File metadata and controls
142 lines (115 loc) · 4.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import re
import pandas as pd
# --------------------------------------------------
# CONFIG
# --------------------------------------------------
PGN_FILES = [
("MAF13-white.pgn", "White"), # you are White in this file
("MAF13-black.pgn", "Black"), # you are Black in this file
]
OUTPUT_CSV = "opening_stats_by_color.csv"
# --------------------------------------------------
# HELPERS
# --------------------------------------------------
def parse_pgn_headers(pgn_text: str):
"""
Very simple PGN header parser: returns a dict of tag -> value.
Assumes headers appear as [Tag \"Value\"] lines at the top.
"""
headers = {}
for line in pgn_text.splitlines():
line = line.strip()
if not line.startswith("["):
# headers finished
break
m = re.match(r'\[(\w+)\s+"(.*)"\]', line)
if m:
tag, val = m.group(1), m.group(2)
headers[tag] = val
return headers
def extract_opening_name(eco_url: str) -> str:
"""
From ECOUrl like:
https://www.chess.com/openings/Nimzowitsch-Larsen-Attack-Indian-Variation...4.f4-c5-5.Nf3-Nc6
return:
Nimzowitsch Larsen Attack Indian Variation
"""
if not eco_url:
return ""
# Take everything after the last '/'
last = eco_url.split("/")[-1]
# Remove move suffix after '...' if present
main = last.split("...")[0]
# Replace '-' and URL spaces
main = main.replace("-", " ").replace("%20", " ")
return main.strip()
def result_from_perspective(result_tag: str, you_are: str) -> str:
"""
Map PGN result + which side you are to 'win'/'loss'/'draw'/'other'.
you_are is 'White' or 'Black'.
"""
if result_tag == "1-0":
return "win" if you_are == "White" else "loss"
elif result_tag == "0-1":
return "win" if you_are == "Black" else "loss"
elif result_tag == "1/2-1/2":
return "draw"
else:
return "other"
# --------------------------------------------------
# MAIN EXTRACTION
# --------------------------------------------------
records = []
for path, your_color in PGN_FILES:
try:
with open(path, encoding="utf-8") as f:
content = f.read()
except FileNotFoundError:
print(f"PGN file not found: {path} (skipping)")
continue
# Split games by blank line between games; robust split uses '\n\n[' pattern
# but we handle edge cases by using regex.
games_raw = re.split(r'\n\n(?=\[Event )', content.strip())
for game_txt in games_raw:
if not game_txt.strip():
continue
headers = parse_pgn_headers(game_txt)
eco = headers.get("ECO", "")
eco_url = headers.get("ECOUrl", "")
result_tag = headers.get("Result", "")
opening_name = extract_opening_name(eco_url)
perspective_result = result_from_perspective(result_tag, your_color)
records.append({
"file": path,
"your_color": your_color,
"eco": eco,
"opening_name": opening_name,
"raw_result": result_tag,
"perspective_result": perspective_result,
})
# Convert to DataFrame
df = pd.DataFrame(records)
# Filter valid results
valid = df[df["perspective_result"].isin(["win", "loss", "draw"])].copy()
# --------------------------------------------------
# STATS BY OPENING + COLOR
# --------------------------------------------------
group = (
valid
.groupby(["your_color", "opening_name", "eco", "perspective_result"])
.size()
.unstack(fill_value=0)
)
# Ensure columns exist
for col in ["win", "loss", "draw"]:
if col not in group.columns:
group[col] = 0
group["total"] = group[["win", "loss", "draw"]].sum(axis=1)
group["win_rate"] = group["win"] / group["total"].replace(0, pd.NA)
opening_stats = group.reset_index()
# Save to CSV
opening_stats.to_csv(OUTPUT_CSV, index=False)
print(f"Saved opening stats per color to: {OUTPUT_CSV}\n")
# Print a few lines for sanity check
print("=== SAMPLE OPENING STATS ===")
print(opening_stats.sort_values(["your_color", "total"], ascending=[True, False]).head(20))