コンテンツにスキップ

TreeSearchPlayer

TreeSearchPlayer

TreeSearchPlayer(username: str, max_plies: int = 1, max_nodes: int | None = None)

Bases: Player

合法手を総当たりで評価する木探索プレイヤーの基底クラス。

自分の各合法手をどう評価するか(_score_command)は具体的な評価アルゴリズムを 実装するサブクラス(jpoke.players.minimax_player.MinimaxPlayer 等)に委ねる。 本クラス単体はインスタンス化できるが、choose_command() 実行時に _score_command が呼ばれると NotImplementedError になる。 max_plies を2以上にすると、相手の応手も含めた複数ターン先までを直接の関数再帰で評価する。

利用者は MinimaxPlayer 等を継承し、以下のフックメソッドを必要な分だけオーバーライドする。 いずれも既定実装があり、オーバーライド不要ならそのまま使える。

  • evaluate(battle): 葉ノードの盤面評価。既定は残りHP割合差。
  • fallback(battle): 探索できない・再入時の代替方策。既定はランダム。
  • estimate_opponent(battle): 探索の最上位(choose_command/evaluate_commands)のたびに呼ばれる推定フック。既定は項目別フック estimate_opponent_team / estimate_opponent_selection に委譲するテンプレートメソッド。
  • estimate_opponent_team(battle): 相手ポケモンのモデル(技・特性・アイテム)に推定値を書き込むフック。既定は何もしない。
  • estimate_opponent_selection(battle): 相手の選出インデックスの推定を返すフック。既定は None(推定しない)。
  • configure_sim(sim): 各分岐の sim.step() 実行前に呼ばれるフック。既定は何もしない。

相手の情報が未公開の局面では、相手の合法手が空リストになり探索できない。 この場合、既定では探索を行わず即座に fallback に委譲する。 estimate_opponent_team / estimate_opponent_selection をオーバーライドすると、 相手ポケモンの技やアイテム・選出候補などを推定して補うことができる (estimate_opponent はこれらを呼び出す入口として毎回呼ばれる)。

属性:

名前 タイプ デスクリプション
max_plies int

探索する手数。1以上。 1手ごとに len(my_commands) * len(opponent_commands) 倍に分岐が増える。

max_nodes int | None

展開できるノード数の上限。 None なら無制限。到達すると以降の展開を打ち切り、その時点で見つかっている最善手を返す。

nodes_expanded int

直近の探索で展開したノード数。診断用。

ソースコード位置: src/jpoke/players/tree_search_player.py
59
60
61
62
63
64
65
66
67
def __init__(self,
             username: str,
             max_plies: int = 1,
             max_nodes: int | None = None):
    super().__init__(username=username)
    self.max_plies: int = max_plies
    self.max_nodes: int | None = max_nodes
    self.nodes_expanded: int = 0
    self._searching: bool = False

evaluate

evaluate(battle: Battle) -> float

葉ノード(盤面)の評価値を返す。

値が大きいほど自分に有利。 既定は自分と相手の残りHP割合の差。決着がついている場合は勝敗を最優先する(±inf)。

ソースコード位置: src/jpoke/players/tree_search_player.py
69
70
71
72
73
74
75
76
77
78
79
80
81
def evaluate(self, battle: Battle) -> float:
    """葉ノード(盤面)の評価値を返す。

    値が大きいほど自分に有利。
    既定は自分と相手の残りHP割合の差。決着がついている場合は勝敗を最優先する(±inf)。
    """
    winner = battle.judge_winner()
    opponent = battle.opponent(self)
    if winner is self:
        return float("inf")
    if winner is opponent:
        return float("-inf")
    return total_hp_ratio(battle, self) - total_hp_ratio(battle, opponent)

fallback

fallback(battle: Battle) -> Command

探索の再入時(割り込み交代など)や、相手の合法手が空で推定できない場合に使われる既定の方策。

デフォルトでは合法手からランダムに1つ選ぶ。 battle.decision_random(行動選択専用の乱数系列)を使うため、Battle(seed=...) で固定した対戦全体の再現性を壊さない。

ソースコード位置: src/jpoke/players/tree_search_player.py
83
84
85
86
87
88
89
90
def fallback(self, battle: Battle) -> Command:
    """探索の再入時(割り込み交代など)や、相手の合法手が空で推定できない場合に使われる既定の方策。

    デフォルトでは合法手からランダムに1つ選ぶ。
    `battle.decision_random`(行動選択専用の乱数系列)を使うため、`Battle(seed=...)` で固定した対戦全体の再現性を壊さない。
    """
    commands = self._available_commands_with_recovery(battle, self)
    return battle.decision_random.choice(commands)

estimate_opponent

estimate_opponent(battle: Battle) -> None

探索の最上位(choose_command/evaluate_commands)のたびに呼ばれる推定フック。

推定対象の相手は battle.opponent(self) で取得する (他のフック evaluate/fallback/configure_sim と同様、フレームワークの フックは battle のみを受け取る規約に合わせている)。

既定実装は項目別フック estimate_opponent_team を呼んだ後、 estimate_opponent_selection の返り値を取得し、None でなければ battle.player_states[opponent].selected_indexes へ未包含のインデックスのみ 追記してマージする(公開済みインデックスは維持・削除しない)。

項目を横断する推定を書きたい場合はこのメソッド自体をオーバーライドしてもよい (その場合は項目別フックは呼ばれなくなるので、必要なら super() を呼ぶこと)。

観測(battle)は毎ターン再構築されるため、推定は毎回書き込む必要がある。 ただし公開済みの情報(revealed な技・選出)を上書きせず、未公開分のみ 補うこと(べき等性ガイドライン)。

引数:

名前 タイプ デスクリプション デフォルト
battle Battle

探索中のシミュレーション用 Battle

必須
ソースコード位置: src/jpoke/players/tree_search_player.py
 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
def estimate_opponent(self, battle: Battle) -> None:
    """探索の最上位(`choose_command`/`evaluate_commands`)のたびに呼ばれる推定フック。

    推定対象の相手は `battle.opponent(self)` で取得する
    (他のフック `evaluate`/`fallback`/`configure_sim` と同様、フレームワークの
    フックは `battle` のみを受け取る規約に合わせている)。

    既定実装は項目別フック `estimate_opponent_team` を呼んだ後、
    `estimate_opponent_selection` の返り値を取得し、`None` でなければ
    `battle.player_states[opponent].selected_indexes` へ未包含のインデックスのみ
    追記してマージする(公開済みインデックスは維持・削除しない)。

    項目を横断する推定を書きたい場合はこのメソッド自体をオーバーライドしてもよい
    (その場合は項目別フックは呼ばれなくなるので、必要なら `super()` を呼ぶこと)。

    観測(battle)は毎ターン再構築されるため、推定は毎回書き込む必要がある。
    ただし公開済みの情報(revealed な技・選出)を上書きせず、未公開分のみ
    補うこと(べき等性ガイドライン)。

    Args:
        battle: 探索中のシミュレーション用 Battle
    """
    opponent = battle.opponent(self)
    self.estimate_opponent_team(battle)
    estimated_selection = self.estimate_opponent_selection(battle)
    if estimated_selection is not None:
        state = battle.player_states[opponent]
        for i in estimated_selection:
            if i not in state.selected_indexes:
                state.selected_indexes.append(i)

estimate_opponent_team

estimate_opponent_team(battle: Battle) -> None

相手ポケモンのモデルに技・特性・アイテムの推定値を書き込むフック。

推定対象の相手は battle.opponent(self) で取得する。取得した battle.get_active(opponent)battle.get_team(opponent) が返す Pokemon インスタンスへ直接書き込む。既定では何もしない。

観測(battle)は毎ターン再構築されるため、推定は毎回書き込む必要がある。 ただし公開済みの情報(revealed な技等)を上書きせず、未公開分のみ 補うこと(べき等性ガイドライン)。

引数:

名前 タイプ デスクリプション デフォルト
battle Battle

探索中のシミュレーション用 Battle

必須
ソースコード位置: src/jpoke/players/tree_search_player.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def estimate_opponent_team(self, battle: Battle) -> None:
    """相手ポケモンのモデルに技・特性・アイテムの推定値を書き込むフック。

    推定対象の相手は `battle.opponent(self)` で取得する。取得した
    `battle.get_active(opponent)` や `battle.get_team(opponent)` が返す
    Pokemon インスタンスへ直接書き込む。既定では何もしない。

    観測(battle)は毎ターン再構築されるため、推定は毎回書き込む必要がある。
    ただし公開済みの情報(revealed な技等)を上書きせず、未公開分のみ
    補うこと(べき等性ガイドライン)。

    Args:
        battle: 探索中のシミュレーション用 Battle
    """
    pass

estimate_opponent_selection

estimate_opponent_selection(battle: Battle) -> list[int] | None

相手の選出インデックス(state.team 基準、0始まり)の推定を返すフック。

推定対象の相手は battle.opponent(self) で取得する。返したリストは 公開済みの選出とマージされる(書き込み先の player_states[opponent].selected_indexes はフレームワーク側が扱うため、 利用者が直接書き込む必要はない)。n_selected を超える個数を返しても 検証はされない。既定は None(推定しない)。

観測(battle)は毎ターン再構築されるため、推定は毎回返す必要がある。 ただし公開済みの選出インデックスは呼び出し元でマージされるため、 未公開分のみ返せばよい(べき等性ガイドライン)。

引数:

名前 タイプ デスクリプション デフォルト
battle Battle

探索中のシミュレーション用 Battle

必須

戻り値:

タイプ デスクリプション
list[int] | None

推定した選出インデックスのリスト。推定しないなら None

ソースコード位置: src/jpoke/players/tree_search_player.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
def estimate_opponent_selection(self, battle: Battle) -> list[int] | None:
    """相手の選出インデックス(`state.team` 基準、0始まり)の推定を返すフック。

    推定対象の相手は `battle.opponent(self)` で取得する。返したリストは
    公開済みの選出とマージされる(書き込み先の
    `player_states[opponent].selected_indexes` はフレームワーク側が扱うため、
    利用者が直接書き込む必要はない)。`n_selected` を超える個数を返しても
    検証はされない。既定は `None`(推定しない)。

    観測(battle)は毎ターン再構築されるため、推定は毎回返す必要がある。
    ただし公開済みの選出インデックスは呼び出し元でマージされるため、
    未公開分のみ返せばよい(べき等性ガイドライン)。

    Args:
        battle: 探索中のシミュレーション用 Battle

    Returns:
        推定した選出インデックスのリスト。推定しないなら `None`。
    """
    return None

configure_sim

configure_sim(sim: Battle) -> None

battle.copy() 直後・sim.step() 実行前に呼ばれるフック。

既定では何もしない。オーバーライドして、探索中だけ有効にしたい オプション(命中固定・平均ダメージなど)を sim に設定する。

ソースコード位置: src/jpoke/players/tree_search_player.py
160
161
162
163
164
165
166
def configure_sim(self, sim: Battle) -> None:
    """`battle.copy()` 直後・`sim.step()` 実行前に呼ばれるフック。

    既定では何もしない。オーバーライドして、探索中だけ有効にしたい
    オプション(命中固定・平均ダメージなど)を sim に設定する。
    """
    pass

evaluate_commands

evaluate_commands(battle: Battle) -> dict[Command, float]

現在の盤面での自分の各合法手の評価値一覧を返す(デバッグ・読み筋確認用)。

_searching やノードカウンタなど、探索本体(choose_command)の状態を 変更しない副作用なしのメソッド。相手の合法手が未公開で空 (かつ estimate_opponent も推定できず、推定後もコマンドが空)の 場合は空の辞書を返す。

注意: 呼び出し中は max_nodes によるノード数上限を一時的に無効化し、 自分の全合法手 × 相手の全合法手を max_plies の深さまで打ち切りなく 評価する(choose_command の探索とは異なりノード数では打ち切らない)。 そのため実行コストは max_plies が大きいほど大きくなりうる。 choose_command の呼び出しごと(毎ターン)にこのメソッドも呼ぶような デバッグ表示などに組み込む場合は、探索コストが max_nodes で 抑えられないことに注意すること。

ソースコード位置: src/jpoke/players/tree_search_player.py
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
def evaluate_commands(self, battle: Battle) -> dict[Command, float]:
    """現在の盤面での自分の各合法手の評価値一覧を返す(デバッグ・読み筋確認用)。

    `_searching` やノードカウンタなど、探索本体(choose_command)の状態を
    変更しない副作用なしのメソッド。相手の合法手が未公開で空
    (かつ estimate_opponent も推定できず、推定後もコマンドが空)の
    場合は空の辞書を返す。

    注意: 呼び出し中は `max_nodes` によるノード数上限を一時的に無効化し、
    自分の全合法手 × 相手の全合法手を `max_plies` の深さまで打ち切りなく
    評価する(`choose_command` の探索とは異なりノード数では打ち切らない)。
    そのため実行コストは `max_plies` が大きいほど大きくなりうる。
    `choose_command` の呼び出しごと(毎ターン)にこのメソッドも呼ぶような
    デバッグ表示などに組み込む場合は、探索コストが `max_nodes` で
    抑えられないことに注意すること。
    """
    opponent = battle.opponent(self)
    my_commands, opponent_commands = self._toplevel_commands(battle)
    if not opponent_commands:
        return {}

    saved_nodes, saved_max_nodes = self.nodes_expanded, self.max_nodes
    self.nodes_expanded, self.max_nodes = 0, None
    try:
        return self._score_commands(
            battle, my_commands, opponent, opponent_commands, self.max_plies,
            respect_node_limit=False,
        )
    finally:
        self.nodes_expanded, self.max_nodes = saved_nodes, saved_max_nodes