-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpatch_v3.py
More file actions
179 lines (157 loc) · 17.3 KB
/
patch_v3.py
File metadata and controls
179 lines (157 loc) · 17.3 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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import re, ast, json
from collections import Counter
PTCGP = 'ptcgp.py'
with open(PTCGP, 'r') as f:
content = f.read()
changes = []
# PATCH 1: Memory tables in init_db
old1 = ' c.execute("""CREATE TABLE IF NOT EXISTS decks(\n id INTEGER PRIMARY KEY, generation INTEGER, deck_ids TEXT,\n fitness REAL, deck_name TEXT, is_elite INTEGER DEFAULT 0)""")\n conn.commit(); conn.close()'
new1 = ' c.execute("""CREATE TABLE IF NOT EXISTS decks(\n id INTEGER PRIMARY KEY, generation INTEGER, deck_ids TEXT,\n fitness REAL, deck_name TEXT, is_elite INTEGER DEFAULT 0)""")\n c.execute("""CREATE TABLE IF NOT EXISTS deck_memory(\n deck_hash TEXT PRIMARY KEY, deck_ids TEXT, fitness REAL,\n deck_name TEXT, times_tested INTEGER DEFAULT 1,\n last_gen INTEGER, first_seen INTEGER)""")\n c.execute("""CREATE TABLE IF NOT EXISTS matchup_memory(\n deck_a_name TEXT, deck_b_name TEXT,\n wins_a INTEGER DEFAULT 0, total INTEGER DEFAULT 0,\n PRIMARY KEY(deck_a_name, deck_b_name))""")\n conn.commit(); conn.close()'
if old1 in content: content = content.replace(old1, new1, 1); changes.append("PASS Memory tables")
else: changes.append("FAIL Memory tables")
# PATCH 2: Memory helpers before run_training
mem_code = '''
def deck_hash(deck):
return ",".join(sorted(deck))
def load_memory(db):
if not os.path.exists(db): return {}
conn = sqlite3.connect(db); c = conn.cursor()
try:
c.execute("SELECT deck_hash, fitness FROM deck_memory")
mem = {row[0]: row[1] for row in c.fetchall()}
except: mem = {}
conn.close(); return mem
def save_to_memory(db, deck, fitness, name, gen):
h = deck_hash(deck)
conn = sqlite3.connect(db); c = conn.cursor()
try:
c.execute("""INSERT INTO deck_memory(deck_hash,deck_ids,fitness,deck_name,times_tested,last_gen,first_seen)
VALUES(?,?,?,?,1,?,?) ON CONFLICT(deck_hash) DO UPDATE SET
fitness=MAX(fitness,excluded.fitness),times_tested=times_tested+1,last_gen=excluded.last_gen""",
(h, json.dumps(deck), fitness, name, gen, gen))
conn.commit()
except: pass
conn.close()
def get_best_matchup(db, deck_name):
conn = sqlite3.connect(db); c = conn.cursor()
try:
c.execute("""SELECT deck_b_name, CAST(wins_a AS REAL)/total as wr, total
FROM matchup_memory WHERE deck_a_name=? AND total>=5
ORDER BY wr DESC LIMIT 3""", (deck_name,))
rows = c.fetchall()
except: rows = []
conn.close(); return rows
def get_worst_matchup(db, deck_name):
conn = sqlite3.connect(db); c = conn.cursor()
try:
c.execute("""SELECT deck_b_name, CAST(wins_a AS REAL)/total as wr, total
FROM matchup_memory WHERE deck_a_name=? AND total>=5
ORDER BY wr ASC LIMIT 3""", (deck_name,))
rows = c.fetchall()
except: rows = []
conn.close(); return rows
META_SEEDS = {
"Palkia ex": ["Palkia ex","Origin Forme Palkia","Misty","Irida","Cynthia","Professor\'s Research","Pokeball"],
"Charizard ex": ["Charizard ex","Charmeleon","Charmander","Moltres ex","Giovanni","Professor\'s Research","Pokeball"],
"Mewtwo ex": ["Mewtwo ex","Gardevoir","Kirlia","Ralts","Professor\'s Research","Pokeball","Giovanni"],
"Pikachu ex": ["Pikachu ex","Zapdos ex","Raichu","Pikachu","Professor\'s Research","Pokeball","Giovanni"],
"Gengar ex": ["Gengar ex","Haunter","Gastly","Mewtwo ex","Sabrina","Professor\'s Research","Pokeball"],
"Lucario ex": ["Lucario ex","Riolu","Machamp ex","Machoke","Machop","Brock","Professor\'s Research"],
"Starmie ex": ["Starmie ex","Staryu","Misty","Blastoise ex","Wartortle","Squirtle","Professor\'s Research"],
"Ho-Oh ex": ["Ho-Oh ex","Moltres ex","Giovanni","Professor\'s Research","Pokeball","Cape of Toughness"],
"Celebi ex": ["Celebi ex","Serperior","Servine","Snivy","Erika","Professor\'s Research","Pokeball"],
"Entei ex": ["Entei ex","Arcanine ex","Growlithe","Giovanni","Professor\'s Research","Pokeball"],
"Buzzwole ex": ["Buzzwole ex","Machamp ex","Machoke","Machop","Brock","Professor\'s Research"],
"Dialga ex": ["Dialga ex","Dawn","Cynthia","Professor\'s Research","Pokeball","Cape of Toughness"],
"Teal Mask Ogerpon ex": ["Teal Mask Ogerpon ex","Serperior","Servine","Snivy","Erika","Professor\'s Research"],
"Melmetal ex": ["Melmetal ex","Meltan","Cyrus","Dawn","Professor\'s Research","Pokeball","Cape of Toughness"],
}
def fetch_meta_decks():
name_to_id = {}
for cid, card in CARD_DB.items():
name_to_id.setdefault(card.name.lower(), cid)
seeded = []
for archetype, card_names in META_SEEDS.items():
deck = []
for cname in card_names:
cid = name_to_id.get(cname.lower())
if cid:
copies = 2 if cname in ["Professor\'s Research","Pokeball","Cynthia"] else 1
for _ in range(copies):
if deck.count(cid) < MAX_COPIES and len(deck) < DECK_SIZE:
deck.append(cid)
deck = build_legal_deck(deck)
v, _ = validate_deck(deck)
if v:
seeded.append({"deck": deck, "fitness": 0.6, "name": f"{archetype} Deck", "is_elite": 1})
print(f" Seeded: {archetype}")
return seeded
'''
marker = '\ndef run_training('
if marker in content: content = content.replace(marker, mem_code + marker, 1); changes.append("PASS Memory helpers")
else: changes.append("FAIL Memory helpers marker")
# PATCH 3: Load memory + meta seeds in training init
old3 = ' gen_times = []\n for generation in range(1, max_gens + 1):'
new3 = ' deck_mem = load_memory(db)\n print(f" Memory: {len(deck_mem)} previously tested decks loaded.")\n gen_times = []\n for generation in range(1, max_gens + 1):'
if old3 in content: content = content.replace(old3, new3, 1); changes.append("PASS Memory load")
else: changes.append("FAIL Memory load")
old4 = ' # Init population\n pop = []\n all_ids = list(CARD_DB.keys())\n attempts = 0\n while len(pop) < CTRL.population_size and attempts < CTRL.population_size * 20:\n attempts += 1\n d = random_deck()\n v, _ = validate_deck(d)\n if v: pop.append({\'deck\': d, \'fitness\': 0.5, \'name\': deck_name(d), \'is_elite\': 0})'
new4 = ' pop = []\n print(" Seeding with known meta archetypes...")\n meta_seeds = fetch_meta_decks()\n pop.extend(meta_seeds[:min(len(meta_seeds), CTRL.population_size // 3)])\n all_ids = list(CARD_DB.keys())\n attempts = 0\n while len(pop) < CTRL.population_size and attempts < CTRL.population_size * 20:\n attempts += 1\n d = random_deck()\n v, _ = validate_deck(d)\n if v: pop.append({\'deck\': d, \'fitness\': 0.5, \'name\': deck_name(d), \'is_elite\': 0})\n print(f" Pop: {len(meta_seeds)} meta + {len(pop)-len(meta_seeds)} random = {len(pop)} total")'
if old4 in content: content = content.replace(old4, new4, 1); changes.append("PASS Meta seeding")
else: changes.append("FAIL Meta seeding")
# PATCH 4: Memory-aware eval
old5 = " pool = [d['deck'] for d in pop]\n for i, de in enumerate(pop):\n de['fitness'] = evaluate_deck(de['deck'], pool)\n de['name'] = deck_name(de['deck'])\n if verbose and i % 10 == 0:\n print(f\" Gen {generation} eval {i+1}/{len(pop)}: {de['name']} = {de['fitness']:.3f}\")"
new5 = " pool = [d['deck'] for d in pop]\n for i, de in enumerate(pop):\n h = deck_hash(de['deck'])\n if h in deck_mem and de.get('fitness', 0) > 0.3:\n de['fitness'] = deck_mem[h] * random.uniform(0.97, 1.03)\n tag = '📦'\n else:\n de['fitness'] = evaluate_deck(de['deck'], pool)\n deck_mem[h] = de['fitness']\n save_to_memory(db, de['deck'], de['fitness'], deck_name(de['deck']), generation)\n tag = '🔬'\n de['name'] = deck_name(de['deck'])\n if verbose and i % 10 == 0:\n print(f\" Gen {generation} eval {i+1}/{len(pop)}: {tag} {de['name']} = {de['fitness']:.3f}\")"
if old5 in content: content = content.replace(old5, new5, 1); changes.append("PASS Memory eval")
else: changes.append("FAIL Memory eval")
# PATCH 5: Deck detail API + clickable cards
old6 = " @app.route('/api/stop', methods=['POST'])\n def stop():\n CTRL.running = False\n print(\" [Control] Stop requested — will stop after current generation.\")\n return redirect('/')"
new6 = " @app.route('/api/deck/<int:deck_id>')\n def deck_detail(deck_id):\n from flask import jsonify\n conn = sqlite3.connect(db); c = conn.cursor()\n c.execute('SELECT deck_ids,fitness,deck_name FROM decks WHERE id=?', (deck_id,))\n row = c.fetchone()\n if not row: return jsonify({'error':'not found'}), 404\n ids = json.loads(row[0]); counts = Counter(ids)\n cards = [{'count':v,'name':CARD_DB[k].name,'type':CARD_DB[k].card_type.value,'set':CARD_DB[k].set_name}\n for k,v in sorted(counts.items()) if k in CARD_DB]\n best_vs = get_best_matchup(db, row[2])\n worst_vs = get_worst_matchup(db, row[2])\n conn.close()\n return jsonify({'name':row[2],'fitness':row[1],'cards':cards,\n 'best_vs':[{'name':r[0],'wr':round(r[1]*100,1),'games':r[2]} for r in best_vs],\n 'worst_vs':[{'name':r[0],'wr':round(r[1]*100,1),'games':r[2]} for r in worst_vs]})\n\n @app.route('/api/stop', methods=['POST'])\n def stop():\n CTRL.running = False\n print(\" [Control] Stop requested — will stop after current generation.\")\n return redirect('/')"
if old6 in content: content = content.replace(old6, new6, 1); changes.append("PASS Deck API route")
else: changes.append("FAIL Deck API route")
# PATCH 6: Modal CSS
old7 = '.Darkness{background:#1a1a1a;color:#aaaaaa}\n</style>'
new7 = '.Darkness{background:#1a1a1a;color:#aaaaaa}\n.modal-bg{display:none;position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:100;align-items:center;justify-content:center}\n.modal-bg.open{display:flex}\n.modal{background:#13132a;border:1px solid #4a3f8a;border-radius:16px;width:min(680px,95vw);max-height:85vh;overflow-y:auto}\n.mh{background:#1e1e3e;padding:14px 20px;display:flex;justify-content:space-between;align-items:center;border-radius:16px 16px 0 0;position:sticky;top:0;z-index:1}\n.mh h2{color:#c8b8ff;font-size:1.05rem}\n.mclose{background:none;border:none;color:#8888bb;font-size:1.4rem;cursor:pointer}\n.mclose:hover{color:#fff}\n.mbody{padding:18px}\n.msect{margin-bottom:16px}\n.msect h3{color:#a78bfa;font-size:.72rem;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px;border-bottom:1px solid #2a2a50;padding-bottom:4px}\n.mu-row{display:flex;gap:10px;padding:5px 0;border-bottom:1px solid #111128;align-items:center}\n.mu-name{flex:1;font-size:.85rem}\n.good{color:#22c55e;font-weight:700}.bad{color:#ef4444;font-weight:700}\n</style>'
if old7 in content: content = content.replace(old7, new7, 1); changes.append("PASS Modal CSS")
else: changes.append("FAIL Modal CSS")
# PATCH 7: Modal HTML + JS before </body>
old8 = '<div class="note">Auto-refreshes every 10 seconds · Generation {{gens}} · {{card_count}} cards in pool</div>\n</body></html>'
new8 = '<div class="note">Auto-refreshes every 10 seconds · Generation {{gens}} · {{card_count}} cards in pool</div>\n<div class="modal-bg" id="modal" onclick="if(event.target===this)closeModal()">\n<div class="modal"><div class="mh"><h2 id="m-title">Deck Details</h2><button class="mclose" onclick="closeModal()">✕</button></div>\n<div class="mbody" id="m-body"><p style="color:#666;text-align:center;padding:30px">Loading...</p></div></div></div>\n<script>\nfunction openDeck(id){\n document.getElementById("modal").classList.add("open");\n document.getElementById("m-body").innerHTML=\'<p style="color:#8888bb;text-align:center;padding:30px">Loading...</p>\';\n fetch("/api/deck/"+id).then(r=>r.json()).then(d=>{\n document.getElementById("m-title").textContent=d.name+" \xb7 "+(d.fitness*100).toFixed(1)+"% win rate";\n let h=\'<div class="msect"><h3>Deck Composition</h3><table style="width:100%"><tr><th style="text-align:left;color:#8888bb;padding:3px 8px;font-size:.68rem">Count</th><th style="text-align:left;color:#8888bb;padding:3px 8px;font-size:.68rem">Card</th><th style="text-align:left;color:#8888bb;padding:3px 8px;font-size:.68rem">Type</th><th style="text-align:left;color:#8888bb;padding:3px 8px;font-size:.68rem">Set</th></tr>\';\n d.cards.forEach(c=>{h+=`<tr><td style="padding:4px 8px;color:#a78bfa;font-weight:700">${c.count}x</td><td style="padding:4px 8px;color:#e0e0f0">${c.name}</td><td style="padding:4px 8px;color:#8888bb;font-size:.78rem">${c.type}</td><td style="padding:4px 8px;color:#555577;font-size:.72rem">${c.set}</td></tr>`;});\n h+=\'</table></div>\';\n if(d.best_vs&&d.best_vs.length){h+=\'<div class="msect"><h3>Best Matchups (decks it beats)</h3>\';d.best_vs.forEach(m=>{h+=`<div class="mu-row"><span class="mu-name">${m.name}</span><span class="good">${m.wr}%</span><span style="color:#444466;font-size:.72rem">${m.games} games</span></div>`;});h+=\'</div>\';}\n if(d.worst_vs&&d.worst_vs.length){h+=\'<div class="msect"><h3>Worst Matchups (watch out for)</h3>\';d.worst_vs.forEach(m=>{h+=`<div class="mu-row"><span class="mu-name">${m.name}</span><span class="bad">${m.wr}%</span><span style="color:#444466;font-size:.72rem">${m.games} games</span></div>`;});h+=\'</div>\';}\n if(!d.best_vs||!d.best_vs.length)h+=\'<p style="color:#444466;font-size:.8rem;margin-top:8px">Matchup data appears after more training — run longer for detailed stats.</p>\';\n document.getElementById("m-body").innerHTML=h;\n }).catch(()=>{document.getElementById("m-body").innerHTML=\'<p style="color:#ef4444">Error loading deck.</p>\';});\n}\nfunction closeModal(){document.getElementById("modal").classList.remove("open");}\ndocument.addEventListener("keydown",e=>{if(e.key==="Escape")closeModal();});\n</script>\n</body></html>'
if old8 in content: content = content.replace(old8, new8, 1); changes.append("PASS Modal HTML")
else: changes.append("FAIL Modal HTML")
# PATCH 8: Include db_id in deck query
old9 = ' c.execute("SELECT deck_ids,fitness,deck_name,is_elite FROM decks WHERE generation=(SELECT MAX(generation) FROM decks) ORDER BY fitness DESC LIMIT 10")\n rows = c.fetchall()\n decks = []\n for i, row in enumerate(rows):\n ids = json.loads(row[0])'
new9 = ' c.execute("SELECT id,deck_ids,fitness,deck_name,is_elite FROM decks WHERE generation=(SELECT MAX(generation) FROM decks) ORDER BY fitness DESC LIMIT 10")\n rows = c.fetchall()\n decks = []\n for i, row in enumerate(rows):\n ids = json.loads(row[1])'
if old9 in content: content = content.replace(old9, new9, 1); changes.append("PASS Deck query id")
else: changes.append("FAIL Deck query id")
old10 = " types = list({CARD_DB[cid].pokemon_type.value for cid in ids\n if cid in CARD_DB and CARD_DB[cid].pokemon_type and CARD_DB[cid].pokemon_type.value not in (\"None\",\"Colorless\")})[:3]\n counts = Counter(ids)\n cards = [{'count':v,'name':CARD_DB[k].name,'type':CARD_DB[k].card_type.value,'set':CARD_DB[k].set_name}\n for k,v in sorted(counts.items()) if k in CARD_DB]\n decks.append({'rank':i+1,'name':row[2],'wr':round(row[1]*100,1),'elite':row[3],'types':types,'cards':cards})"
new10 = " types = list({CARD_DB[cid].pokemon_type.value for cid in ids\n if cid in CARD_DB and CARD_DB[cid].pokemon_type and CARD_DB[cid].pokemon_type.value not in (\"None\",\"Colorless\")})[:3]\n counts = Counter(ids)\n cards = [{'count':v,'name':CARD_DB[k].name,'type':CARD_DB[k].card_type.value,'set':CARD_DB[k].set_name}\n for k,v in sorted(counts.items()) if k in CARD_DB]\n decks.append({'rank':i+1,'db_id':row[0],'name':row[3],'wr':round(row[2]*100,1),'elite':row[4],'types':types,'cards':cards})"
if old10 in content: content = content.replace(old10, new10, 1); changes.append("PASS Deck row indices")
else: changes.append("FAIL Deck row indices")
# PATCH 9: Make deck cards clickable
old11 = """ deck_items += f\"\"\"<div class=\"de {elite_cls}\">
<div class=\"rank\">#{d['rank']}</div>
<div class=\"dn\">{d['name']}</div>
<div class=\"wb\"><div class=\"wf\" style=\"width:{d['wr']}%\"></div></div>
<span class=\"wp\">{d['wr']}% win rate</span>
<div class=\"tags\">{tags}</div>
</div>\"\"\""""
new11 = """ deck_items += f\"\"\"<div class=\"de {elite_cls}\" onclick=\"openDeck({d['db_id']})\" style=\"cursor:pointer\" title=\"Click for details\">
<div class=\"rank\">#{d['rank']} <span style=\"float:right;font-size:.65rem;color:#4a4a7a\">click ↗</span></div>
<div class=\"dn\">{d['name']}</div>
<div class=\"wb\"><div class=\"wf\" style=\"width:{d['wr']}%\"></div></div>
<span class=\"wp\">{d['wr']}% win rate</span>
<div class=\"tags\">{tags}</div>
</div>\"\"\""""
if old11 in content: content = content.replace(old11, new11, 1); changes.append("PASS Clickable cards")
else: changes.append("FAIL Clickable cards")
# Write
with open(PTCGP, 'w') as f:
f.write(content)
try:
ast.parse(content)
print("SYNTAX OK")
except SyntaxError as e:
print(f"SYNTAX ERROR line {e.lineno}: {e.msg}")
for c in changes:
print(c)