libs.functions.tools.score_simulator
libs/functions/tools/score_simulator.py
得点シミュレーター
Returns:
list: ゲーム終了時点の素点リスト
1""" 2libs/functions/tools/score_simulator.py 3 4得点シミュレーター 5 6Returns: 7 list: ゲーム終了時点の素点リスト 8 9""" 10 11import random 12from typing import Union 13 14INITIAL_POINTS: int = 25000 15MAX_ROUNDS: int = 8 16 17# 点数テーブル 18HAN_POINTS: dict[int, dict[str, Union[int, tuple[int, ...]]]] = { 19 1: {"ron_child": 1300, "ron_parent": 2000, "tsumo_child": (300, 500), "tsumo_parent": (500,)}, 20 2: {"ron_child": 2600, "ron_parent": 3900, "tsumo_child": (500, 1000), "tsumo_parent": (1000,)}, 21 3: {"ron_child": 5200, "ron_parent": 7700, "tsumo_child": (1000, 2000), "tsumo_parent": (2000,)}, 22 4: {"ron_child": 8000, "ron_parent": 12000, "tsumo_child": (2000, 4000), "tsumo_parent": (4000,)}, 23 5: {"ron_child": 8000, "ron_parent": 12000, "tsumo_child": (2000, 4000), "tsumo_parent": (4000,)}, 24 6: {"ron_child": 12000, "ron_parent": 18000, "tsumo_child": (3000, 6000), "tsumo_parent": (6000,)}, 25 7: {"ron_child": 12000, "ron_parent": 18000, "tsumo_child": (3000, 6000), "tsumo_parent": (6000,)}, 26 8: {"ron_child": 16000, "ron_parent": 24000, "tsumo_child": (4000, 8000), "tsumo_parent": (8000,)}, 27 9: {"ron_child": 16000, "ron_parent": 24000, "tsumo_child": (4000, 8000), "tsumo_parent": (8000,)}, 28 10: {"ron_child": 24000, "ron_parent": 36000, "tsumo_child": (6000, 12000), "tsumo_parent": (12000,)}, 29 11: {"ron_child": 24000, "ron_parent": 36000, "tsumo_child": (6000, 12000), "tsumo_parent": (12000,)}, 30 12: {"ron_child": 24000, "ron_parent": 36000, "tsumo_child": (6000, 12000), "tsumo_parent": (12000,)}, 31 13: {"ron_child": 32000, "ron_parent": 48000, "tsumo_child": (8000, 16000), "tsumo_parent": (16000,)}, 32 14: {"ron_child": 32000, "ron_parent": 48000, "tsumo_child": (8000, 16000), "tsumo_parent": (16000,)}, 33 15: {"ron_child": 64000, "ron_parent": 96000, "tsumo_child": (16000, 32000), "tsumo_parent": (32000,)}, # ダブル役満扱い 34} 35 36 37def determine_point(is_parent: bool, is_tsumo: bool) -> int | tuple[int, ...]: 38 """ 39 和了打点を決める 40 41 Args: 42 is_parent (bool): 親フラグ 43 is_tsumo (bool): ツモ/被ツモフラグ 44 45 Returns: 46 int | tuple: 打点 47 48 """ 49 rank = 1 50 while rank < 15: 51 success_prob = max([0, 0.6 - 0.02 * rank]) 52 if random.random() > success_prob: 53 break 54 rank += 1 55 56 key = "tsumo_parent" if is_parent and is_tsumo else "tsumo_child" if not is_parent and is_tsumo else "ron_parent" if is_parent else "ron_child" 57 58 return HAN_POINTS[rank][key] 59 60 61def determine_winner(k: int) -> tuple[list[int], list[int]]: 62 """ 63 和了役を抽選し、放銃役候補と分けてリストを返す 64 65 Args: 66 k (int): 和了役に選ばれる人数 67 68 Returns: 69 tuple[list[int], list[int]]: 抽選結果 70 71 """ 72 member: list[int] = list(range(4)) 73 winners: list[int] = random.sample(member, k=k) # 和了役 74 losers: list[int] = [i for i in member if i not in winners] 75 76 return (winners, losers) 77 78 79def should_renchan(winners: list[int], parent: int, tenpai: list[bool], total_rounds: int, renchan_count: int) -> tuple[int, int, int]: 80 """ 81 連チャンの判定を行う 82 83 Args: 84 winners (list[int]): 和了者のリスト(流局時は空リスト) 85 parent (int): 現在の親 86 tenpai (list[bool]): 流局時のテンパイ状況(和了時は空リスト) 87 total_rounds (int): 現在の局数 88 renchan_count (int): 現在の連チャン数 89 90 91 Returns: 92 tuple[int, int, int]: 93 - int: 判定後の局数 94 - int: 判定後の連チャン数 95 - int: 次の親 96 97 """ 98 flg: bool = False 99 if winners: 100 flg = parent in winners # 和了時: 親が和了していれば連チャン 101 elif tenpai: 102 flg = tenpai[parent] # 流局時: 親がテンパイしていれば連チャン 103 104 if flg: 105 renchan_count += 1 106 else: 107 parent = (parent + 1) % 4 108 renchan_count = 0 109 total_rounds += 1 110 111 return (total_rounds, renchan_count, parent) 112 113 114def simulate_game() -> list[int]: 115 """ゲーム進行シミュレーション""" 116 scores: list[int] = [INITIAL_POINTS] * 4 # 配給原点(0:東家 1:南家 2:西家 3:北家) 117 parent: int = 0 # 親番 118 total_rounds: int = 0 # 局数 119 renchan_count: int = 0 # 本場 120 121 while total_rounds < MAX_ROUNDS: 122 member = list(range(4)) # 0:東家 1:南家 2:西家 3:北家 123 wins = [random.random() < 0.23 for _ in member] # 和了判定 124 num_wins = sum(wins) 125 126 if num_wins in [2, 3] and random.random() < 0.01: # ダブロン発生 127 winners, losers = determine_winner(num_wins) 128 discarder = random.choice(losers) # 放銃役 129 130 for winner in winners: 131 is_parent = winner == parent 132 point = determine_point(is_parent, False) 133 assert isinstance(point, int), "point should be a int" 134 scores[winner] += point + 300 * renchan_count 135 scores[discarder] -= point + 300 * renchan_count 136 137 total_rounds, renchan_count, parent = should_renchan(winners, parent, [], total_rounds, renchan_count) 138 139 elif num_wins in [1, 2, 3]: # 通常の和了処理 140 winners, losers = determine_winner(1) 141 winner = winners[0] # 和了役 142 is_parent = winner == parent # 和了役が親か? 143 144 if random.random() > 0.75: # ツモによる点数移動 145 point_data = determine_point(is_parent, True) 146 assert isinstance(point_data, tuple), "point_data should be a tuple" 147 for i in losers: 148 pay = point_data[1] if i == parent else point_data[0] 149 scores[i] -= pay + 100 * renchan_count 150 scores[winner] += pay + 100 * renchan_count 151 else: # 被ツモによる点数移動 152 discarder = random.choice(losers) # 放銃役 153 pay = determine_point(is_parent, False) # type: ignore[assignment] 154 assert isinstance(pay, int), "pay should be an int" 155 scores[discarder] -= pay + 300 * renchan_count 156 scores[winner] += pay + 300 * renchan_count 157 158 total_rounds, renchan_count, parent = should_renchan(winners, parent, [], total_rounds, renchan_count) 159 160 else: # 流局処理 161 tenpai = [random.random() < 0.25 for _ in member] 162 noten = [not x for x in tenpai] 163 payment = [] 164 165 if not (all(tenpai) or all(noten)): 166 for i in tenpai: 167 if i: 168 payment.append(int(3000 / sum(tenpai))) 169 else: 170 payment.append(int(-3000 / sum(noten))) 171 172 for i in member: 173 scores[i] += payment[i] 174 175 total_rounds, renchan_count, parent = should_renchan([], parent, tenpai, total_rounds, renchan_count) 176 177 if any(score < 0 for score in scores): 178 break 179 180 return scores 181 182 183if __name__ == "__main__": 184 final_scores = simulate_game() 185 print("最終素点:", final_scores, "素点合計", sum(final_scores))
INITIAL_POINTS: int =
25000
MAX_ROUNDS: int =
8
HAN_POINTS: dict[int, dict[str, int | tuple[int, ...]]] =
{1: {'ron_child': 1300, 'ron_parent': 2000, 'tsumo_child': (300, 500), 'tsumo_parent': (500,)}, 2: {'ron_child': 2600, 'ron_parent': 3900, 'tsumo_child': (500, 1000), 'tsumo_parent': (1000,)}, 3: {'ron_child': 5200, 'ron_parent': 7700, 'tsumo_child': (1000, 2000), 'tsumo_parent': (2000,)}, 4: {'ron_child': 8000, 'ron_parent': 12000, 'tsumo_child': (2000, 4000), 'tsumo_parent': (4000,)}, 5: {'ron_child': 8000, 'ron_parent': 12000, 'tsumo_child': (2000, 4000), 'tsumo_parent': (4000,)}, 6: {'ron_child': 12000, 'ron_parent': 18000, 'tsumo_child': (3000, 6000), 'tsumo_parent': (6000,)}, 7: {'ron_child': 12000, 'ron_parent': 18000, 'tsumo_child': (3000, 6000), 'tsumo_parent': (6000,)}, 8: {'ron_child': 16000, 'ron_parent': 24000, 'tsumo_child': (4000, 8000), 'tsumo_parent': (8000,)}, 9: {'ron_child': 16000, 'ron_parent': 24000, 'tsumo_child': (4000, 8000), 'tsumo_parent': (8000,)}, 10: {'ron_child': 24000, 'ron_parent': 36000, 'tsumo_child': (6000, 12000), 'tsumo_parent': (12000,)}, 11: {'ron_child': 24000, 'ron_parent': 36000, 'tsumo_child': (6000, 12000), 'tsumo_parent': (12000,)}, 12: {'ron_child': 24000, 'ron_parent': 36000, 'tsumo_child': (6000, 12000), 'tsumo_parent': (12000,)}, 13: {'ron_child': 32000, 'ron_parent': 48000, 'tsumo_child': (8000, 16000), 'tsumo_parent': (16000,)}, 14: {'ron_child': 32000, 'ron_parent': 48000, 'tsumo_child': (8000, 16000), 'tsumo_parent': (16000,)}, 15: {'ron_child': 64000, 'ron_parent': 96000, 'tsumo_child': (16000, 32000), 'tsumo_parent': (32000,)}}
def
determine_point(is_parent: bool, is_tsumo: bool) -> int | tuple[int, ...]:
38def determine_point(is_parent: bool, is_tsumo: bool) -> int | tuple[int, ...]: 39 """ 40 和了打点を決める 41 42 Args: 43 is_parent (bool): 親フラグ 44 is_tsumo (bool): ツモ/被ツモフラグ 45 46 Returns: 47 int | tuple: 打点 48 49 """ 50 rank = 1 51 while rank < 15: 52 success_prob = max([0, 0.6 - 0.02 * rank]) 53 if random.random() > success_prob: 54 break 55 rank += 1 56 57 key = "tsumo_parent" if is_parent and is_tsumo else "tsumo_child" if not is_parent and is_tsumo else "ron_parent" if is_parent else "ron_child" 58 59 return HAN_POINTS[rank][key]
和了打点を決める
Arguments:
- is_parent (bool): 親フラグ
- is_tsumo (bool): ツモ/被ツモフラグ
Returns:
int | tuple: 打点
def
determine_winner(k: int) -> tuple[list[int], list[int]]:
62def determine_winner(k: int) -> tuple[list[int], list[int]]: 63 """ 64 和了役を抽選し、放銃役候補と分けてリストを返す 65 66 Args: 67 k (int): 和了役に選ばれる人数 68 69 Returns: 70 tuple[list[int], list[int]]: 抽選結果 71 72 """ 73 member: list[int] = list(range(4)) 74 winners: list[int] = random.sample(member, k=k) # 和了役 75 losers: list[int] = [i for i in member if i not in winners] 76 77 return (winners, losers)
和了役を抽選し、放銃役候補と分けてリストを返す
Arguments:
- k (int): 和了役に選ばれる人数
Returns:
tuple[list[int], list[int]]: 抽選結果
def
should_renchan( winners: list[int], parent: int, tenpai: list[bool], total_rounds: int, renchan_count: int) -> tuple[int, int, int]:
80def should_renchan(winners: list[int], parent: int, tenpai: list[bool], total_rounds: int, renchan_count: int) -> tuple[int, int, int]: 81 """ 82 連チャンの判定を行う 83 84 Args: 85 winners (list[int]): 和了者のリスト(流局時は空リスト) 86 parent (int): 現在の親 87 tenpai (list[bool]): 流局時のテンパイ状況(和了時は空リスト) 88 total_rounds (int): 現在の局数 89 renchan_count (int): 現在の連チャン数 90 91 92 Returns: 93 tuple[int, int, int]: 94 - int: 判定後の局数 95 - int: 判定後の連チャン数 96 - int: 次の親 97 98 """ 99 flg: bool = False 100 if winners: 101 flg = parent in winners # 和了時: 親が和了していれば連チャン 102 elif tenpai: 103 flg = tenpai[parent] # 流局時: 親がテンパイしていれば連チャン 104 105 if flg: 106 renchan_count += 1 107 else: 108 parent = (parent + 1) % 4 109 renchan_count = 0 110 total_rounds += 1 111 112 return (total_rounds, renchan_count, parent)
連チャンの判定を行う
Arguments:
- winners (list[int]): 和了者のリスト(流局時は空リスト)
- parent (int): 現在の親
- tenpai (list[bool]): 流局時のテンパイ状況(和了時は空リスト)
- total_rounds (int): 現在の局数
- renchan_count (int): 現在の連チャン数
Returns:
tuple[int, int, int]:
- int: 判定後の局数
- int: 判定後の連チャン数
- int: 次の親
def
simulate_game() -> list[int]:
115def simulate_game() -> list[int]: 116 """ゲーム進行シミュレーション""" 117 scores: list[int] = [INITIAL_POINTS] * 4 # 配給原点(0:東家 1:南家 2:西家 3:北家) 118 parent: int = 0 # 親番 119 total_rounds: int = 0 # 局数 120 renchan_count: int = 0 # 本場 121 122 while total_rounds < MAX_ROUNDS: 123 member = list(range(4)) # 0:東家 1:南家 2:西家 3:北家 124 wins = [random.random() < 0.23 for _ in member] # 和了判定 125 num_wins = sum(wins) 126 127 if num_wins in [2, 3] and random.random() < 0.01: # ダブロン発生 128 winners, losers = determine_winner(num_wins) 129 discarder = random.choice(losers) # 放銃役 130 131 for winner in winners: 132 is_parent = winner == parent 133 point = determine_point(is_parent, False) 134 assert isinstance(point, int), "point should be a int" 135 scores[winner] += point + 300 * renchan_count 136 scores[discarder] -= point + 300 * renchan_count 137 138 total_rounds, renchan_count, parent = should_renchan(winners, parent, [], total_rounds, renchan_count) 139 140 elif num_wins in [1, 2, 3]: # 通常の和了処理 141 winners, losers = determine_winner(1) 142 winner = winners[0] # 和了役 143 is_parent = winner == parent # 和了役が親か? 144 145 if random.random() > 0.75: # ツモによる点数移動 146 point_data = determine_point(is_parent, True) 147 assert isinstance(point_data, tuple), "point_data should be a tuple" 148 for i in losers: 149 pay = point_data[1] if i == parent else point_data[0] 150 scores[i] -= pay + 100 * renchan_count 151 scores[winner] += pay + 100 * renchan_count 152 else: # 被ツモによる点数移動 153 discarder = random.choice(losers) # 放銃役 154 pay = determine_point(is_parent, False) # type: ignore[assignment] 155 assert isinstance(pay, int), "pay should be an int" 156 scores[discarder] -= pay + 300 * renchan_count 157 scores[winner] += pay + 300 * renchan_count 158 159 total_rounds, renchan_count, parent = should_renchan(winners, parent, [], total_rounds, renchan_count) 160 161 else: # 流局処理 162 tenpai = [random.random() < 0.25 for _ in member] 163 noten = [not x for x in tenpai] 164 payment = [] 165 166 if not (all(tenpai) or all(noten)): 167 for i in tenpai: 168 if i: 169 payment.append(int(3000 / sum(tenpai))) 170 else: 171 payment.append(int(-3000 / sum(noten))) 172 173 for i in member: 174 scores[i] += payment[i] 175 176 total_rounds, renchan_count, parent = should_renchan([], parent, tenpai, total_rounds, renchan_count) 177 178 if any(score < 0 for score in scores): 179 break 180 181 return scores
ゲーム進行シミュレーション