# ===================== IDL‑Basketball Simulation v7 (Rebound Weights · 3P Boost · Post Kick‑out · Lay/Dunk Nerf) =====================
import random
from dataclasses import dataclass
from typing import List, Dict, Callable, Optional
# ---------- Utility ----------
def clamp(p: float) -> float:
return max(0.05, min(p, 0.95))
def stat(v: int) -> float:
return v / 100.0
# weighted choice for rebounders
POSITION_REB_WEIGHT = {
'C': 1.25,
'PF': 1.10,
'SF': 1.0,
'SG': 0.9,
'PG': 0.75
}
def weighted_rebound_choice(players):
weights = []
for p in players:
base = (p.physical * 0.6 + p.agility * 0.3 + p.sense * 0.1)
pos_weight = POSITION_REB_WEIGHT.get(p.pos, 1.0)
weights.append(base * pos_weight)
return random.choices(players, weights)[0]
# ---------- Data Models ----------
@dataclass
class Player:
name: str
pos: str
shoot: int = 70
handle: int = 70
agility: int = 70
physical: int = 70
sense: int = 70
points: int = 0
assists: int = 0
two_att: int = 0
two_made: int = 0
three_att: int = 0
three_made: int = 0
off_reb: int = 0
def_reb: int = 0
turnovers: int = 0
pass_boost: float = 0.0
@dataclass
class Team:
name: str
players: List[Player]
score: int = 0
def get(self, pos: str) -> Player:
for p in self.players:
if p.pos == pos:
return p
return random.choice(self.players)
# ---------- Logger ----------
class Logger:
def __init__(self):
self.lines: List[str] = []
def log(self, text: str):
self.lines.append(text)
# ---------- Probability Maps ----------
SHOT_PROB: Dict[str, Callable[[Player], float]] = {
'2pull': lambda p: clamp(0.10 + stat(p.shoot) * 0.20 + stat(p.handle) * 0.05),
'3pull': lambda p: clamp(0.08 + stat(p.shoot) * 0.20 + stat(p.handle) * 0.04),
'layup': lambda p: clamp(0.25 + stat(p.physical) * 0.08 + stat(p.agility) * 0.08 + stat(p.sense) * 0.04),
'dunk': lambda p: clamp(0.35 + stat(p.physical) * 0.10),
'stepback3': lambda p: clamp(0.10 + stat(p.shoot) * 0.22 + stat(p.handle) * 0.12 + stat(p.sense) * 0.05),
}
SHOT_POINTS = {'2pull': 2, 'layup': 2, 'dunk': 2, '3pull': 3, 'stepback3': 3}
PASS_TARGETS = {
'PG': [('SG', 0.36), ('C', 0.27), ('PF', 0.18), ('SF', 0.19)],
'SG': [('PG', 0.55), ('SF', 0.45)],
'SF': [('PG', 0.50), ('SG', 0.50)],
'PF': [('PG', 0.60), ('SF', 0.40)],
'C': [('PG', 0.65), ('SF', 0.35)]
}
POSITION_DECISION = {
'PG': [('pass', 0.57), ('2pull', 0.08), ('3pull', 0.06), ('stepback3', 0.02), ('layup', 0.07), ('pnr', 0.12), ('handoff', 0.08)],
'SG': [('pass', 0.58), ('2pull', 0.10), ('3pull', 0.18), ('stepback3', 0.06), ('layup', 0.08)],
'SF': [('pass', 0.45), ('2pull', 0.20), ('3pull', 0.20), ('layup', 0.15)],
'PF': [('pass', 0.20), ('post', 0.45), ('2pull', 0.25), ('3pull', 0.10)],
'C': [('pass', 0.20), ('post', 0.55), ('2pull', 0.25)]
}
# ---------- Core Handlers ----------
def handle_success(team, opp, player, pts, lg, last_passer: Optional[Player] = None):
team.score += pts
player.points += pts
if last_passer and last_passer != player:
last_passer.assists += 1
lg.log(f"어시스트: {last_passer.name}")
lg.log(f"{player.name} 득점 +{pts}! → {team.name} {team.score} - {opp.score} {opp.name}")
def handle_miss(team, opp, lg, rebound_count=0):
off_prob = clamp(0.18 - rebound_count * 0.06)
if rebound_count >= 2:
lg.log("공격 기회 소멸 (24초 바이올레이션)")
return False
if random.random() < off_prob:
rb = weighted_rebound_choice(team.players)
rb.off_reb += 1
lg.log(f"공격 리바운드 {rb.name}")
return True
rb = weighted_rebound_choice(opp.players)
rb.def_reb += 1
lg.log(f"수비 리바운드 {rb.name} → 턴 종료")
return False
# ---------- Shooting Helpers ----------
def _record_shot(player: Player, pts: int, made: bool):
if pts == 2:
player.two_att += 1
if made:
player.two_made += 1
else:
player.three_att += 1
if made:
player.three_made += 1
def attempt_shot(team, opp, player, kind, lg, last_passer=None):
pts = SHOT_POINTS[kind]
prob = SHOT_PROB[kind](player)
if player.pass_boost > 0:
prob += player.pass_boost
prob = clamp(prob)
player.pass_boost = 0.0 # 한 번만 적용
made = random.random() < prob
_record_shot(player, pts, made)
if made:
lg.log(f"{player.name} {kind} 성공 ({prob:.2f})")
handle_success(team, opp, player, pts, lg, last_passer)
return False
lg.log(f"{player.name} {kind} 실패 ({prob:.2f})")
return handle_miss(team, opp, lg)
def attempt_shot_open3(team, opp, shooter, lg, last_passer):
# 오픈 찬스 3점 – 성공률 +5%
pts = 3
base_prob = SHOT_PROB['3pull'](shooter) + 0.05
prob = clamp(base_prob)
made = random.random() < prob
_record_shot(shooter, pts, made)
if made:
lg.log(f"{shooter.name} 코너 오픈 3점 성공 ({prob:.2f})")
handle_success(team, opp, shooter, pts, lg, last_passer)
return False
lg.log(f"{shooter.name} 코너 오픈 3점 실패 ({prob:.2f})")
return handle_miss(team, opp, lg)
# ---------- Actions ----------
def attempt_post(team, opp, player, lg, last_passer=None):
prob = clamp(0.40 + stat(player.physical) * 0.15 + stat(player.sense) * 0.05)
lg.log(f"{player.name} 포스트업 ({prob:.2f})")
if random.random() < prob:
handle_success(team, opp, player, 2, lg, last_passer)
return False
# 실패 – 30% 확률 킥아웃 3점 찬스
if random.random() < 0.30:
targets = [p for p in team.players if p.pos in ('PG', 'SG', 'SF') and p != player]
shooter = random.choice(targets) if targets else player
lg.log("포스트 킥아웃!")
return attempt_shot_open3(team, opp, shooter, lg, last_passer=player)
lg.log("포스트업 실패")
return handle_miss(team, opp, lg)
def attempt_pass(team, opp, player, lg, depth=0, last_passer=None):
targets, wts = zip(*PASS_TARGETS.get(player.pos, [('PG', 1.0)]))
pos = random.choices(targets, wts)[0]
recv = team.get(pos)
prob = clamp(0.85 + stat(player.sense)*0.05 + stat(player.handle)*0.03 - depth*0.02)
lg.log(f"{player.name}→{recv.name} 패스 ({prob:.2f})")
if random.random() < prob:
recv.pass_boost = 0.03 + stat(player.sense) * 0.015 # 기본 5% + 패서 센스 비중
return onball(team, opp, recv.pos, lg, depth+1, last_passer=player)
player.turnovers += 1
lg.log("패스 실패 → 턴오버")
return False
def attempt_handoff(team, opp, player, lg, last_passer=None):
receiver = team.get(random.choice(['SG', 'SF', 'PF']))
lg.log(f"{player.name} 핸드오프 → {receiver.name}")
prob = clamp(0.78 + stat(player.sense)*0.05)
if random.random() < prob:
lg.log("핸드오프 성공 → 슛 기회")
return attempt_shot(team, opp, receiver, '2pull', lg, last_passer=player)
player.turnovers += 1
lg.log("핸드오프 실패 → 턴오버")
return False
def attempt_pnr(team, opp, player, lg, last_passer=None):
screener = team.get(random.choice(['PF', 'C']))
lg.log(f"PnR (스크리너 {screener.name}) 시도")
success = clamp(0.58 + stat(screener.sense)*0.05)
if random.random() < success:
if random.random() < 0.30:
return attempt_shot(team, opp, player, 'layup', lg, last_passer)
lg.log("롤맨에게 패스")
return attempt_shot(team, opp, screener, 'layup', lg, last_passer=player)
lg.log("PnR 실패 → 2pull")
return attempt_shot(team, opp, player, '2pull', lg, last_passer)
# ---------- Dispatcher ----------
def onball(team, opp, pos, lg, depth=0, last_passer=None):
player = team.get(pos)
decision = POSITION_DECISION[pos]
action = random.choices([d[0] for d in decision], [d[1] for d in decision])[0]
if action == 'pass':
return attempt_pass(team, opp, player, lg, depth, last_passer)
if action in SHOT_PROB:
keep = attempt_shot(team, opp, player, action, lg, last_passer)
elif action == 'post':
keep = attempt_post(team, opp, player, lg, last_passer)
elif action == 'pnr':
keep = attempt_pnr(team, opp, player, lg, last_passer)
elif action == 'handoff':
keep = attempt_handoff(team, opp, player, lg, last_passer)
else:
keep = False
if keep:
return onball(team, opp, pos, lg, depth, last_passer)
return False
# ---------- Simulation ----------
def simulate_game(team_a, team_b, quarters=4, poss_per_q=25):
lg = Logger()
for q in range(1, quarters + 1):
lg.log(f"\n=== {q}쿼터 ===")
for _ in range(poss_per_q):
onball(team_a, team_b, random.choices(['PG', 'SG', 'SF', 'PF', 'C'], [0.28, 0.27, 0.25, 0.10, 0.10])[0], lg)
onball(team_b, team_a, random.choices(['PG', 'SG', 'SF', 'PF', 'C'], [0.28, 0.27, 0.25, 0.10, 0.10])[0], lg)
lg.log(f"{q}Q 종료: {team_a.name} {team_a.score} - {team_b.score} {team_b.name}")
lg.log(f"\n=== 최종 스코어: {team_a.name} {team_a.score} - {team_b.score} {team_b.name} ===")
return lg
# …위쪽은 그대로 두시고…
# 팀 풀 정의
TEAM_POOL = {
'IVE': Team('IVE', [
Player('리즈', 'PG', 81, 88, 84, 72, 89), Player('가을', 'SG', 77, 74, 81, 66, 70),
Player('장원영', 'SF', 94, 86, 87, 89, 84), Player('이서', 'PF', 74, 68, 77, 83, 72),
Player('안유진', 'C', 75, 70, 78, 92, 85)
]),
'aespa' : Team('aespa', [
Player('윈터', 'PG', 81, 86, 82, 66, 83), Player('닝닝', 'SG', 86, 78, 79, 76, 80),
Player('카리나', 'SF', 75, 76, 78, 90, 77), Player('지젤', 'PF', 65, 66, 70, 85, 72),
Player('Bogut', 'C', 70, 70, 70, 70, 70)
])
}
# ─── Main 함수 정의 ──────────────────
def main():
IVE = TEAM_POOL['IVE']
aespa = TEAM_POOL['aespa']
log = simulate_game(IVE, aespa)
for line in log.lines:
print(line)
print("\n=== 선수 기록 ===")
for tm in (IVE, aespa):
print(tm.name)
for p in tm.players:
print(f"{p.name}({p.pos}) PTS:{p.points} AST:{p.assists} 2P {p.two_made}/{p.two_att} 3P {p.three_made}/{p.three_att} ORB:{p.off_reb} DRB:{p.def_reb} TO:{p.turnovers}")
# ─── myCompiler 에서는 guard 없이 바로 호출 ──────────────────
main()
To embed this project on your website, copy the following code and paste it into your website's HTML: