-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmeta_comps.py
More file actions
160 lines (138 loc) · 5.31 KB
/
meta_comps.py
File metadata and controls
160 lines (138 loc) · 5.31 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
# src/tft_analyzer/meta_comps.py
"""
Loads curated meta compositions and scores them against the current game state.
"""
import json
from pathlib import Path
from typing import Dict, List, Any, Optional
from recommender import evaluate_comp
META_COMPS_FILE = Path(__file__).parent / "data" / "meta_comps.json"
TIER_BONUS = {"S": 10, "A": 7, "B": 4}
def load_meta_comps(path: Path = META_COMPS_FILE) -> List[Dict[str, Any]]:
"""Load meta comp definitions from JSON. Returns empty list if missing."""
if not path.exists():
return []
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
return data.get("comps", [])
def validate_meta_comps(
comps: List[Dict[str, Any]],
champion_data: Dict[str, Dict[str, Any]],
) -> List[Dict[str, Any]]:
"""
Filter out champions not in the current set data.
Comps with fewer than 4 valid champions are dropped entirely.
"""
valid = []
for comp in comps:
filtered_champs = [c for c in comp.get("champions", []) if c in champion_data]
filtered_carries = [c for c in comp.get("carries", []) if c in champion_data]
if len(filtered_champs) < 4:
continue
valid.append({
**comp,
"champions": filtered_champs,
"carries": filtered_carries,
})
return valid
def score_meta_comp(
comp: Dict[str, Any],
current_board: List[str],
champion_data: Dict[str, Dict[str, Any]],
trait_data: Dict[str, Any],
units_gone: Dict[str, Dict[str, int]],
level_odds: Dict[int, float],
pool_sizes: Dict[int, int],
) -> float:
"""
Score a meta comp against the current game state (0-100 scale).
Components:
- Overlap (0-40): % of comp champions already on your board
- Availability (0-30): remaining pool copies × shop odds for missing pieces
- Trait synergy (0-20): evaluate_comp score, normalized
- Meta tier bonus (0-10): S=10, A=7, B=4
"""
champs = comp.get("champions", [])
if not champs:
return 0.0
# --- Overlap (0-40) ---
board_set = set(current_board)
overlap_count = sum(1 for c in champs if c in board_set)
overlap_score = (overlap_count / len(champs)) * 40.0
# --- Availability (0-30) ---
missing = [c for c in champs if c not in board_set]
if missing:
availability_sum = 0.0
for name in missing:
if name not in champion_data:
continue
cost = champion_data[name].get("cost", 1)
tier_str = f"{cost}-cost"
total_pool = pool_sizes.get(cost, 10)
gone = units_gone.get(tier_str, {}).get(name, 0)
remaining = max(0, total_pool - gone)
prob = level_odds.get(cost, 0)
# Normalized availability: (remaining/pool) * odds
availability_sum += (remaining / max(total_pool, 1)) * prob
availability_score = (availability_sum / len(missing)) * 30.0
else:
availability_score = 30.0 # All pieces already owned
# --- Trait synergy (0-20) ---
raw_synergy = evaluate_comp(champs, champion_data, trait_data, units_gone, level_odds)
# Rough normalization: a strong 8-unit comp with good traits scores ~300-600 raw
# We cap at 500 for normalization purposes
synergy_score = min(raw_synergy / 500.0, 1.0) * 20.0
# --- Meta tier bonus (0-10) ---
tier = comp.get("tier", "B")
tier_score = float(TIER_BONUS.get(tier, 4))
return round(overlap_score + availability_score + synergy_score + tier_score, 1)
def get_ranked_meta_comps(
meta_comps: List[Dict[str, Any]],
current_board: List[str],
champion_data: Dict[str, Dict[str, Any]],
trait_data: Dict[str, Any],
units_gone: Dict[str, Dict[str, int]],
level_odds: Dict[int, float],
pool_sizes: Dict[int, int],
) -> List[Dict[str, Any]]:
"""
Score and rank all meta comps. Returns list of:
{"champions": [...], "score": float, "source": "meta", "name": str, "tier": str, "carries": [...]}
sorted by score descending.
"""
scored = []
for comp in meta_comps:
score = score_meta_comp(
comp, current_board, champion_data, trait_data,
units_gone, level_odds, pool_sizes,
)
scored.append({
"champions": comp["champions"],
"score": score,
"source": "meta",
"name": comp.get("name", "Meta Comp"),
"tier": comp.get("tier", "B"),
"carries": comp.get("carries", []),
})
scored.sort(key=lambda c: c["score"], reverse=True)
return scored
def get_combined_comps(
meta_comps: List[Dict[str, Any]],
greedy_comps: List[Dict[str, Any]],
meta_weight: int = 50,
) -> List[Dict[str, Any]]:
"""
Interleave meta and greedy comps, applying meta_weight bias.
meta_weight: 0 = greedy only, 100 = meta only, 50 = balanced.
Adjusts meta scores by (meta_weight/50) and greedy by ((100-meta_weight)/50).
Then merge and sort.
"""
meta_mult = meta_weight / 50.0
greedy_mult = (100 - meta_weight) / 50.0
combined = []
for c in meta_comps:
combined.append({**c, "score": round(c["score"] * meta_mult, 1)})
for c in greedy_comps:
combined.append({**c, "score": round(c["score"] * greedy_mult, 1)})
combined.sort(key=lambda c: c["score"], reverse=True)
return combined