import random

# ==== 확률 계산 유틸 ====
def clamp(prob):
    return max(0.05, min(prob, 0.95))

def stat(v): return v / 100  # 0~1 변환

# ==== Player 클래스 ====
class Player:
    def __init__(self, name, pos, shoot=70, handle=70, agility=70, physical=70, sense=70):
        self.name = name
        self.pos = pos
        self.shoot = shoot
        self.handle = handle
        self.agility = agility
        self.physical = physical
        self.sense = sense
        self.points = 0

# ==== Team 클래스 ====
class Team:
    def __init__(self, name, players):
        self.name = name
        self.players = players
        self.score = 0
    def get(self, pos):
        for p in self.players:
            if p.pos == pos:
                return p
        return self.players[0]

def select_rebounder(team):
    weights = []
    for p in team.players:
        # rebounding 스탯 대신, physical + agility + sense의 가중치로만 계산
        weight = p.physical * 0.6 + p.agility * 0.2 + p.sense * 0.2 + random.uniform(0, 10)
        weights.append(weight)
    idx = weights.index(max(weights))
    return team.players[idx]

def offensive_rebound_prob(team, rebound_count, team_off_avg=0, team_def_avg=0):
    base = 0.5 if rebound_count < 3 else 0.3
    # rebounding 스탯은 없으니 PF/C의 physical, sense, agility 평균치로 대체
    bigs = [p for p in team.players if p.pos in ["C", "PF"]]
    if bigs:
        bigs_reb = (sum([p.physical * 0.5 + p.agility * 0.2 + p.sense * 0.3 for p in bigs]) / len(bigs)) / 100
    else:
        bigs_reb = 0
    prob = base + (team_off_avg - team_def_avg) * 0.05 + (bigs_reb * 0.10)
    return clamp(prob)

# ==== 덩크/레이업/풋백 확률 ====
def dunk_prob(player):
    prob = 0.45 + stat(player.physical)*0.15 + stat(player.agility)*0.05
    return clamp(prob)

def layup_prob(player):
    prob = 0.30 + stat(player.physical)*0.10 + stat(player.agility)*0.10 + stat(player.sense)*0.05
    return clamp(prob)
    
# ==== 슈팅 확률 ====
def pullup_2pt_prob(player):
    prob = 0.20 + stat(player.shoot)*0.25 + stat(player.handle)*0.05 + stat(player.agility)*0.02
    return clamp(prob)

def pullup_3pt_prob(player):
    prob = 0.15 + stat(player.shoot)*0.30 + stat(player.handle)*0.05 + stat(player.agility)*0.02
    return clamp(prob)

def catch_and_shoot_prob(player, pass_count=0):
    base_prob = 0.20 + stat(player.shoot)*0.30 + stat(player.agility)*0.02
    base_prob += min(pass_count * 0.02, 0.06)
    return clamp(base_prob)

def post_up_prob(player):
    prob = 0.30 + stat(player.physical)*0.15 + stat(player.sense)*0.05
    return clamp(prob)

# ==== 패스 성공 확률 ====
def pass_prob(handler, pass_count=0):
    prob = 0.85 + stat(handler.sense)*0.02 - min(pass_count*0.02, 0.06)
    return clamp(prob)

def kickout_pass_prob(handler):
    prob = 0.70 + stat(handler.handle)*0.05 + stat(handler.sense)*0.05
    return clamp(prob)

# ==== Pick&Roll 확률 ====
def screen_prob(screener):
    prob = 0.55 + stat(screener.physical)*0.10 + stat(screener.sense)*0.05
    return clamp(prob)

def rollman_pass_prob(handler):
    prob = 0.65 + stat(handler.handle)*0.05 + stat(handler.sense)*0.05
    return clamp(prob)

# ==== 온더볼 메인 ====
def onball(team, opp, pos, pass_count=0, rebound_count=1, log=None):
    if log is None: log = []
    player = team.get(pos)
    if pos == "PG":
        branch = random.choices(['pass', 'iso'], [0.55, 0.45])[0]
        if branch == 'pass':
            t, w = ['SG','C','PF','SF'], [0.36,0.27,0.18,0.19]
            target_pos = random.choices(t, w)[0]
            handler = player
            receiver = team.get(target_pos)
            log.append(f"PG 패스 → {receiver.name} ({target_pos})")
            if random.random() < pass_prob(handler, pass_count):
                made = random.random() < catch_and_shoot_prob(receiver, pass_count+1)
                if made:
                    receiver.points += 2
                    team.score += 2
                    log.append(f"{receiver.name} 캐치&슛 성공! +2점")
                    log.append(f"현재 스코어: {team.name} {team.score} : {opp.name} {opp.score}")
                else:
                    log.append(f"{receiver.name} 캐치&슛 실패 → 리바운드 싸움")
                    rebound(team, opp, log, rebound_count)
            else:
                log.append(f"{receiver.name}에게 패스 실패 → 턴오버/속공")
        else:
            iso_branch = random.choices(
                ['pullup2','pullup3','drive_kick','drive_layup','pnr'],
                [0.25,0.20,0.25,0.20,0.10])[0]
            if iso_branch == 'pullup2':
                prob = pullup_2pt_prob(player)
                made = random.random() < prob
                if made:
                    player.points += 2
                    team.score += 2
                    log.append(f"PG 2점 풀업 ({prob:.2f}) 성공! +2점")
                    log.append(f"현재 스코어: {team.name} {team.score} : {opp.name} {opp.score}")
                else:
                    log.append(f"PG 2점 풀업 ({prob:.2f}) 실패 → 리바운드")
                    rebound(team, opp, log, rebound_count)
            elif iso_branch == 'pullup3':
                prob = pullup_3pt_prob(player)
                made = random.random() < prob
                if made:
                    player.points += 3
                    team.score += 3
                    log.append(f"PG 3점 풀업 ({prob:.2f}) 성공! +3점")
                    log.append(f"현재 스코어: {team.name} {team.score} : {opp.name} {opp.score}")
                else:
                    log.append(f"PG 3점 풀업 ({prob:.2f}) 실패 → 리바운드")
                    rebound(team, opp, log, rebound_count)
            elif iso_branch == 'drive_kick':
                prob = 0.55 + stat(player.handle)*0.20 + stat(player.agility)*0.15 + stat(player.sense)*0.05
                if random.random() < prob:
                    sg = team.get('SG')
                    if random.random() < kickout_pass_prob(player):
                        made = random.random() < catch_and_shoot_prob(sg)
                        if made:
                            sg.points += 3
                            team.score += 3
                            log.append(f"PG Drive&Kick → {sg.name} 오픈 3점 성공! +3점")
                            log.append(f"현재 스코어: {team.name} {team.score} : {opp.name} {opp.score}")
                        else:
                            log.append(f"PG Drive&Kick → {sg.name} 오픈 3점 실패 → 리바운드")
                            rebound(team, opp, log, rebound_count)
                    else:
                        log.append(f"PG 킥아웃 패스 실패 → 턴오버/속공")
                else:
                    log.append("PG 드라이브 실패 → 리바운드 싸움")
                    rebound(team, opp, log, rebound_count)
            elif iso_branch == 'drive_layup':
                prob = 0.45 + stat(player.handle)*0.20 + stat(player.physical)*0.20 + stat(player.agility)*0.10
                if random.random() < prob:
                    if random.random() < dunk_prob(player):
                        player.points += 2
                        team.score += 2
                        log.append("PG 돌파 → 덩크 성공! +2점")
                        log.append(f"현재 스코어: {team.name} {team.score} : {opp.name} {opp.score}")
                    else:
                        if random.random() < layup_prob(player):
                            player.points += 2
                            team.score += 2
                            log.append("PG 돌파 → 레이업 성공! +2점")
                            log.append(f"현재 스코어: {team.name} {team.score} : {opp.name} {opp.score}")
                        else:
                            log.append("PG 돌파 실패 → 리바운드")
                            rebound(team, opp, log, rebound_count)
                else:
                    log.append("PG 돌파 실패 → 리바운드")
                    rebound(team, opp, log, rebound_count)
            elif iso_branch == 'pnr':
                screener = team.get(random.choices(['PF','C'], [0.6,0.4])[0])
                if random.random() < screen_prob(screener):
                    if random.random() < 0.4:
                        prob = 0.55 + stat(player.handle)*0.20 + stat(player.agility)*0.15 + stat(player.sense)*0.05 + 0.10
                        if random.random() < prob:
                            if random.random() < dunk_prob(player):
                                player.points += 2
                                team.score += 2
                                log.append("PG PnR 돌파 → 덩크 성공! +2점")
                                log.append(f"현재 스코어: {team.name} {team.score} : {opp.name} {opp.score}")
                            else:
                                if random.random() < layup_prob(player):
                                    player.points += 2
                                    team.score += 2
                                    log.append("PG PnR 돌파 → 레이업 성공! +2점")
                                    log.append(f"현재 스코어: {team.name} {team.score} : {opp.name} {opp.score}")
                                else:
                                    log.append("PG PnR 돌파 실패 → 리바운드")
                                    rebound(team, opp, log, rebound_count)
                        else:
                            log.append("PG PnR 돌파 실패 → 리바운드")
                            rebound(team, opp, log, rebound_count)
                    else:
                        if random.random() < rollman_pass_prob(player):
                            if random.random() < dunk_prob(screener):
                                screener.points += 2
                                team.score += 2
                                log.append(f"PnR 롤맨({screener.name}) 덩크 성공! +2점")
                                log.append(f"현재 스코어: {team.name} {team.score} : {opp.name} {opp.score}")
                            else:
                                if random.random() < layup_prob(screener):
                                    screener.points += 2
                                    team.score += 2
                                    log.append(f"PnR 롤맨({screener.name}) 레이업 성공! +2점")
                                    log.append(f"현재 스코어: {team.name} {team.score} : {opp.name} {opp.score}")
                                else:
                                    log.append(f"PnR 롤맨({screener.name}) 마무리 실패 → 리바운드")
                                    rebound(team, opp, log, rebound_count)
                        else:
                            log.append(f"PnR 롤맨 패스 실패 → 턴오버/속공")
                else:
                    log.append("스크린 실패 → PG 드라이브")
                    prob = 0.45 + stat(player.handle)*0.20 + stat(player.physical)*0.20 + stat(player.agility)*0.10
                    if random.random() < prob:
                        if random.random() < dunk_prob(player):
                            player.points += 2
                            team.score += 2
                            log.append("스크린 실패 후 PG 드라이브 → 덩크 성공! +2점")
                            log.append(f"현재 스코어: {team.name} {team.score} : {opp.name} {opp.score}")
                        else:
                            if random.random() < layup_prob(player):
                                player.points += 2
                                team.score += 2
                                log.append("스크린 실패 후 PG 드라이브 → 레이업 성공! +2점")
                                log.append(f"현재 스코어: {team.name} {team.score} : {opp.name} {opp.score}")
                            else:
                                log.append("스크린 실패 후 PG 드라이브 실패 → 리바운드")
                                rebound(team, opp, log, rebound_count)
                    else:
                        log.append("스크린 실패 후 PG 드라이브 실패 → 리바운드")
                        rebound(team, opp, log, rebound_count)

def rebound(team, opp, log, rebound_count):
    off_prob = offensive_rebound_prob(team, rebound_count)
    if random.random() < off_prob:
        rebounder = select_rebounder(team)
        log.append(f"공격 리바운드! ({rebounder.name}) Putback 시도")
        if random.random() < dunk_prob(rebounder):
            rebounder.points += 2
            team.score += 2
            log.append(f"{rebounder.name} Putback 덩크 성공! +2점")
            log.append(f"현재 스코어: {team.name} {team.score} : {opp.name} {opp.score}")
        else:
            if random.random() < layup_prob(rebounder):
                rebounder.points += 2
                team.score += 2
                log.append(f"{rebounder.name} Putback 레이업 성공! +2점")
                log.append(f"현재 스코어: {team.name} {team.score} : {opp.name} {opp.score}")
            else:
                log.append(f"{rebounder.name} Putback 실패 → 리바운드 루프")
                rebound(team, opp, log, rebound_count+1)
    else:
        log.append("수비 리바운드! 공격권 전환")

# ==== 샘플 Cavs/Warriors 라인업 ====
cavs = Team("Cavs", [
    Player("Kyrie", "PG", 78, 85, 80, 65, 80),
    Player("JR Smith", "SG", 77, 65, 75, 65, 68),
    Player("LeBron", "SF", 79, 82, 88, 95, 94),
    Player("Love", "PF", 80, 58, 68, 83, 75),
    Player("Thompson", "C", 66, 54, 70, 90, 70)
])

warriors = Team("Warriors", [
    Player("Curry", "PG", 87, 95, 92, 60, 85),
    Player("Klay", "SG", 84, 74, 75, 65, 75),
    Player("Barnes", "SF", 76, 65, 78, 84, 74),
    Player("Green", "PF", 71, 75, 76, 86, 81),
    Player("Bogut", "C", 55, 58, 58, 92, 70)
])

# ==== 시뮬레이션 실행 ====
for i in range(10):  # 10포제션
    log = []
    onball(cavs, warriors, "PG", log=log)
    print(f"[Cavs 포제션 #{i+1}]")
    for l in log: print(l)
    print(f"점수: {cavs.score}\n")

    log = []
    onball(warriors, cavs, "PG", log=log)
    print(f"[Warriors 포제션 #{i+1}]")
    for l in log: print(l)
    print(f"점수: {warriors.score}\n")

Embed on website

To embed this project on your website, copy the following code and paste it into your website's HTML: