import random, itertools
from collections import defaultdict, Counter

# ───────────────  로스터  ───────────────
# team, player, Speed, Cornering, Explosive acceleration, Driving skill
ROSTER = [
    # === aespa ===
    ("aespa","카리나",75,65,80,60), ("aespa","지젤",50,55,65,55),
    ("aespa","윈터",70,75,50,75),   ("aespa","닝닝",70,65,60,70),

    # === IVE ===
    ("IVE","안유진",70,60,70,65), ("IVE","가을",60,65,55,45),
    ("IVE","레이",55,55,60,55),   ("IVE","장원영",80,55,70,50),
    ("IVE","리즈",65,50,60,70),   ("IVE","이서",60,55,65,55),

    # === NewJeans ===
    ("NewJeans","민지",70,70,65,45), ("NewJeans","하니",65,75,65,60),
    ("NewJeans","다니엘",55,65,55,55), ("NewJeans","해린",75,75,60,50),
    ("NewJeans","혜인",50,60,55,55),

    # === LE SSERAFIM ===
    ("LE SSERAFIM","김채원",60,75,60,70), ("LE SSERAFIM","사쿠라",55,55,65,50),
    ("LE SSERAFIM","허윤진",60,55,55,65), ("LE SSERAFIM","카즈하",65,75,70,45),
    ("LE SSERAFIM","홍은채",65,65,60,40),

    # === (G)I-dle ===
    ("i-dle","미연",75,60,65,70), ("i-dle","민니",65,55,65,70),
    ("i-dle","소연",45,55,50,75), ("i-dle","우기",65,65,60,65),
    ("i-dle","슈화",60,55,65,60),

    # === fromis_9 ===
    ("fromis_9","송하영",55,75,60,70), ("fromis_9","박지원",70,55,50,80),
    ("fromis_9","이채영",70,60,70,55), ("fromis_9","이나경",75,60,60,60),
    ("fromis_9","백지헌",65,55,75,40),

    # === ITZY ===
    ("ITZY","유나",75,70,80,55), ("ITZY","예지",65,75,60,65),
    ("ITZY","리아",60,45,60,70), ("ITZY","채령",45,75,65,60),
    ("ITZY","류진",60,70,60,60),

    # === NMIXX ===
    ("NMIXX","릴리",55,55,55,80), ("NMIXX","해원",60,65,50,70),
    ("NMIXX","설윤",80,60,65,65), ("NMIXX","배이",60,55,70,55),
    ("NMIXX","지우",70,70,55,60), ("NMIXX","규진",60,75,60,55),

    # === KISS OF LIFE ===
    ("KISS OF LIFE","쥴리",50,70,70,65), ("KISS OF LIFE","벨",70,65,65,70),
    ("KISS OF LIFE","나띠",60,75,75,65), ("KISS OF LIFE","하늘",65,60,60,65),

    # === ILLIT ===
    ("ILLIT","윤아",55,60,55,60), ("ILLIT","민주",75,60,60,65),
    ("ILLIT","모카",60,65,55,55), ("ILLIT","원희",65,60,55,55),
    ("ILLIT","이로하",60,75,60,50),

    # === STAYC ===
    ("STAYC","수민",50,60,50,50), ("STAYC","시은",45,50,60,65),
    ("STAYC","아이사",60,65,75,55), ("STAYC","세은",65,50,55,50),
    ("STAYC","윤",65,60,60,60), ("STAYC","재이",65,55,65,50),

    # === Hearts2Hearts ===
    ("Hearts2Hearts","이안",75,65,50,55), ("Hearts2Hearts","지우",60,70,70,55),
    ("Hearts2Hearts","스텔라",50,55,60,65), ("Hearts2Hearts","유하",65,70,55,65),
    ("Hearts2Hearts","주은",45,70,50,55),

    # === KiiiKiii ===
    ("KiiiKiii","이솔",65,60,60,55), ("KiiiKiii","수이",60,70,60,55),
    ("KiiiKiii","지유",55,55,55,70), ("KiiiKiii","하음",65,65,60,60),
    ("KiiiKiii","키야",55,70,65,55),

    # === H1-KEY ===
    ("H1-KEY","서이",70,65,65,65), ("H1-KEY","리이나",70,60,70,60),
    ("H1-KEY","휘서",60,70,60,70), ("H1-KEY","옐",60,55,85,55),

    # === BABYMONSTER ===
    ("BABYMONSTER","루카",50,75,65,50), ("BABYMONSTER","라미",45,60,60,70),
    ("BABYMONSTER","로라",65,60,60,65), ("BABYMONSTER","아현",70,70,65,75),
    ("BABYMONSTER","아사",65,70,65,55), ("BABYMONSTER","파리타",60,55,75,60),

    # === FIFTY FIFTY ===
    ("FIFTY FIFTY","키나",55,55,55,60), ("FIFTY FIFTY","문샤넬",60,50,50,75),
    ("FIFTY FIFTY","예원",55,55,55,65), ("FIFTY FIFTY","하나",65,75,65,55),

    # === MEOVV ===
    ("MEOVV","수인",60,70,65,55), ("MEOVV","가원",40,60,65,70),
    ("MEOVV","안나",75,65,60,55), ("MEOVV","나린",60,65,60,60),
    ("MEOVV","엘라",60,60,55,55),

    # === tripleS ===
    ("tripleS","김채연",60,55,75,55),
    ("tripleS","김유연",70,35,60,40), ("tripleS","김나경",65,65,60,60),
    ("tripleS","신위",70,55,65,60), ("tripleS","이지우",65,65,70,65),

    # === Kep1er ===
    ("Kep1er","최유진",55,60,60,60), ("Kep1er","샤오팅",65,70,65,50),
    ("Kep1er","김채현",60,65,60,70), ("Kep1er","김다연",55,75,55,55),
    ("Kep1er","휴닝바히에",50,55,60,55),

    # === QWER ===
    ("QWER","마젠타",50,40,60,35), ("QWER","쵸단",65,50,65,45),
    ("QWER","시연",55,55,60,70),    ("QWER","히나",55,45,60,40),

    # === RESCENE ===
    ("RESCENE","원이",65,55,60,60), ("RESCENE","리브",60,60,60,55),
    ("RESCENE","미나미",50,65,55,70), ("RESCENE","메이",60,55,55,55),
    ("RESCENE","제나",60,55,65,55),

    # === LIGHTSUM ===
    ("LIGHTSUM","상아",55,55,55,55), ("LIGHTSUM","초원",55,60,80,75),
    ("LIGHTSUM","나영",60,65,60,70), ("LIGHTSUM","히나",60,50,50,45),
    ("LIGHTSUM","주현",55,70,60,60), ("LIGHTSUM","유정",55,55,55,55),

    # === izna ===
    ("izna","코코",60,70,60,50), ("izna","마이",50,55,55,50),
    ("izna","윤지윤",55,55,60,60), ("izna","유사랑",65,65,60,55),
    ("izna","방지민",60,65,60,55), ("izna","정세비",65,65,70,55),

    # === OH MY GIRL ===
    ("OHMYGIRL","효정",50,55,60,60), ("OHMYGIRL","미미",40,70,55,55),
    ("OHMYGIRL","유아",65,75,50,55), ("OHMYGIRL","승희",50,60,55,60),
    ("OHMYGIRL","아린",65,55,70,55),

    # === ARTMS ===
    ("ARTMS","진솔",55,60,60,50), ("ARTMS","하슬",50,50,50,65),
    ("ARTMS","김립",55,60,60,60), ("ARTMS","희진",70,65,60,55),
    ("ARTMS","최리",60,60,55,50),

    # === PURPLE KISS ===
    ("PURPLEKISS","고은",55,50,50,65), ("PURPLEKISS","도시",50,65,55,45),
    ("PURPLEKISS","유키",55,60,50,45), ("PURPLEKISS","채인",55,60,55,60),

    # === EVERGLOW ===
    ("EVERGLOW","EU",55,60,60,45), ("EVERGLOW","시현",60,55,60,60),
    ("EVERGLOW","미아",55,60,60,60), ("EVERGLOW","아이샤",55,55,60,45),
    ("EVERGLOW","이런",70,55,55,50),

    # === Dreamcatcher ===
    ("Dreamcatcher","지유",65,55,60,55), ("Dreamcatcher","수아",50,65,60,50),
    ("Dreamcatcher","시연",50,50,50,70), ("Dreamcatcher","유현",50,50,50,65),
    ("Dreamcatcher","다미",45,60,60,45),

    # === Weeekly ===
    ("Weeekly","수진",50,55,50,55), ("Weeekly","소은",55,50,50,60),
    ("Weeekly","재희",65,50,50,45), ("Weeekly","지한",65,60,55,65),
    ("Weeekly","조아",55,50,50,45),

    # === BDC ===
    ("BDC","이현",65,65,65,65), ("BDC","쿠미",60,60,60,50),
    ("BDC","미아",55,60,55,60), ("BDC","베니",45,70,60,50),
]

EAST = ["IVE","fromis_9","BABYMONSTER","OHMYGIRL","LE SSERAFIM",
        "STAYC","Hearts2Hearts","ILLIT","H1-KEY","LIGHTSUM",
        "izna","PURPLEKISS","MEOVV","Dreamcatcher","QWER"]

WEST = ["NMIXX","ITZY","aespa","i-dle","NewJeans",
        "KISS OF LIFE","KiiiKiii","tripleS","RESCENE","ARTMS",
        "EVERGLOW","Kep1er","BDC","Weeekly","FIFTY FIFTY"]

TEAMS = EAST + WEST

TEAM_PLAYERS = defaultdict(list)
for row in ROSTER:
    TEAM_PLAYERS[row[0]].append(row)

SCALE_SEC   = 1.0
BASE_CONST  = 88.0
BASE_SLOPE  = 1.5
MIN_RECORD  = 70.0

def war_to_lap(leader_war: float, player_war: float) -> float:
    base = BASE_CONST - (leader_war - 6.0) * BASE_SLOPE
    if base < MIN_RECORD:
        base = MIN_RECORD
    return base + (leader_war - player_war) * SCALE_SEC

def sec2time(sec: float) -> str:
    m, s = divmod(sec, 60)
    return f"{int(m)}:{s:06.3f}"

player_stats = defaultdict(lambda: {
    'team':'', 'conf':'',
    'sets':0, 'pt':0.0,
    'lap_sum':0.0, 'rank_sum':0.0
})
fast_laps = []

def lin_map(x,a=20,b=80,y0=0,y1=1):
    return y0 + (x-a)*(y1-y0)/(b-a)

def jitter(v,p=0):
    return v * random.uniform(1-p,1+p)

# ─────────────── 2) 팀/선수 덤핑 세팅 (Medium) ───────────────
TEAM_DUMPING_RANGE = 0.05   # 팀 ±5%
PLAYER_DUMPING_RANGE = 0.08 # 선수 ±8%

team_dumping = {t: random.uniform(-TEAM_DUMPING_RANGE, TEAM_DUMPING_RANGE) for t in TEAMS}
player_dumping = {}

for t in TEAMS:
    players = [p for p in TEAM_PLAYERS[t]]
    dumping_players = random.sample(players, k=random.randint(1,3))  # 1~3명 랜덤
    for p in players:
        if p in dumping_players:
            player_dumping[p[1]] = random.uniform(-PLAYER_DUMPING_RANGE, PLAYER_DUMPING_RANGE)
        else:
            player_dumping[p[1]] = 0.0

# ─────────────── 3) 시즌 시뮬 (덤핑 적용) ───────────────
def sim_season(players, pa=650, seed=None):
    if seed is not None: random.seed(seed)
    out=[]
    for tm,pl,c,g,h,e in players:
        team_df = team_dumping[tm]
        player_df = player_dumping[pl]
        # 덤핑 적용
        c = c * (1+team_df) * (1+player_df)
        g = g * (1+team_df) * (1+player_df)
        h = h * (1+team_df) * (1+player_df)
        e = e * (1+team_df) * (1+player_df)
        avg=jitter(lin_map(c, y0=.200, y1=.330))
        bb =jitter(lin_map(e, y0=.04 , y1=.15))
        hr =jitter(lin_map(h, y0=.005, y1=.06))
        db =jitter(lin_map(g, y0=.03 , y1=.09))
        tp =jitter(lin_map(g, y0=.002, y1=.010))
        BB=int(bb*pa); AB=pa-BB
        HR=int(hr*AB); DB=int(db*AB); TP=int(tp*AB)
        H =int(avg*AB); S=max(0, H-DB-TP-HR)
        TB=S+2*DB+3*TP+4*HR
        wRC=int(((1.2*(H+BB)/pa)/.330 + (0.8*TB/AB)/.370 - 1)*100)
        WAR=round((wRC-100)/10*pa/650,1)
        out.append((tm,pl,WAR))
    return sorted(out, key=lambda x:x[2], reverse=True)

POINT=[25,18,15,12,10,8,6,4,2,1]

def play_set(A,B,seed=None,game_no=0):
    global fast_laps, player_stats
    res = sim_season(TEAM_PLAYERS[A] + TEAM_PLAYERS[B], seed=seed)
    leader_war = res[0][2]
    sc = Counter({A:0,B:0})

    for idx,(tm,pl,war) in enumerate(res):
        lap = war_to_lap(leader_war, war)
        rk  = idx + 1

        ps = player_stats[pl]
        ps['team'] = tm
        ps['conf'] = 'E' if tm in EAST else 'W'
        ps['sets'] += 1
        ps['lap_sum']  += lap
        ps['rank_sum'] += rk

        if idx < 6:
            gain = POINT[idx]
            ps['pt'] += gain
            sc[tm]  += gain

        fast_laps.append((lap, tm, pl, A, B, game_no))
    return (A if sc[A] > sc[B] else B), sc

def play_bo3(A,B,seed=None,game_no=0):
    w = Counter({A:0,B:0}); nxt = seed
    while max(w.values()) < 2:
        winner,_ = play_set(A,B,seed=nxt,game_no=game_no)
        w[winner]+=1
        if seed is not None: nxt += 1
    return w

def make_schedule():
    games=[]
    for conf in (EAST, WEST):
        for a,b in itertools.combinations(conf,2):
            games += [(a,b),(a,b)]
    for i,a in enumerate(EAST):
        for j,b in enumerate(WEST):
            if i==j: continue
            games.append((a,b))
    return games

SCHEDULE = make_schedule()

def run_league(seed_base=None):
    tbl={t:{'W':0,'L':0,'SF':0,'SA':0} for t in TEAMS}
    seed = seed_base
    game_no = 1
    for A,B in SCHEDULE:
        wins = play_bo3(A,B,seed=seed,game_no=game_no)
        tbl[A]['SF']+=wins[A]; tbl[A]['SA']+=wins[B]
        tbl[B]['SF']+=wins[B]; tbl[B]['SA']+=wins[A]
        if wins[A]>wins[B]:   tbl[A]['W']+=1; tbl[B]['L']+=1
        else:                 tbl[B]['W']+=1; tbl[A]['L']+=1
        game_no += 1
        if seed_base is not None: seed += 3
    return tbl

def rank(conf,tbl):
    return sorted([(t,tbl[t]) for t in conf],
                  key=lambda x:(-x[1]['W'], -(x[1]['SF']-x[1]['SA'])))

def run():
    league = run_league()

    print("\n=== EAST CONFERENCE ===")
    print("Rank Team            W  L  SetDiff")
    for i,(t,s) in enumerate(rank(EAST, league),1):
        diff = s['SF']-s['SA']
        print(f"{i:2d}  {t:15} {s['W']:2d} {s['L']:2d}  {diff:+3d}")

    print("\n=== WEST CONFERENCE ===")
    print("Rank Team            W  L  SetDiff")
    for i,(t,s) in enumerate(rank(WEST, league),1):
        diff = s['SF']-s['SA']
        print(f"{i:2d}  {t:15} {s['W']:2d} {s['L']:2d}  {diff:+3d}")

    sets_per_team = league[EAST[0]]['W'] + league[EAST[0]]['L']
    print(f"\n(팀당 경기 수: {sets_per_team} G)")

    top5 = sorted(fast_laps, key=lambda x: x[0])[:5]
    print("\n=== FASTEST LAPS (Top 5) ===")
    print("Rank Lap      Player          Team   Versus")
    for i,(lap, tm, pl, a, b, gno) in enumerate(top5,1):
        print(f"{i:2d}  {sec2time(lap):>8}  {pl:14} {tm:6}  {a} vs {b}")

    perfs = []
    for pl, st in player_stats.items():
        sets = st['sets']
        perfs.append({
            'player': pl,
            'team': st['team'],
            'conf': st['conf'],
            'avg_pt': st['pt']/sets,
            'avg_lap': st['lap_sum']/sets,
            'avg_rk': st['rank_sum']/sets
        })

    def top30(metric, higher_is_better):
        return sorted(perfs, key=lambda x: (-x[metric] if higher_is_better else x[metric]))[:30]

    def print_top(metric,label,higher):
        for conf_code,title in (('E','East'),('W','West')):
            print(f"\n=== {label} – Top 30 ({title}) ===")
            ranker = top30(metric,higher)
            idx=1
            for p in ranker:
                if p['conf']!=conf_code: continue
                if metric=='avg_lap':
                    val = sec2time(p['avg_lap'])
                else:
                    val = f"{p[metric]:5.2f}"
                print(f"{idx:2d} {p['player']:14} {p['team']:10} {val}")
                idx+=1
                if idx>30: break

    print_top('avg_pt','AVG POINTS / SET',True)
    print_top('avg_lap','AVG LAP TIME',False)
    print_top('avg_rk','AVG RANK',False)

run()

Embed on website

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