ホームページ  >  記事  >  バックエンド開発  >  Python を使用して家主の最終局面を解決するコツを教えます

Python を使用して家主の最終局面を解決するコツを教えます

零下一度
零下一度オリジナル
2017-07-02 10:47:212903ブラウズ

Doudizhu は誰にとってもよく知られているはずです。次の記事では主に Python を使用して Doudizhu のエンドゲームを解決するための関連情報を紹介します。友達の皆さん、以下を見てみましょう。

前書き

誰もがLandlordsをプレイしたことがあると思うので、ルールは再び紹介されません。

友達のサークルで見たエンドゲームの画像に直接アクセスしてください:

この質問を初めて見たとき、私は農家の勝利戦略を見つけたと思うたびに手動で解決しようとしました。結局、農民たちは逃げることができないことが判明しました。手動クラッキングではすべての可能性を網羅することはできないため、この問題の解決に役立つのはコードを使用することだけです。

この記事では、そのような問題をコードを通じて解決する方法について簡単に説明します。最後に、エンドゲームの最終結果が発表され、コードはオープンソースとして公開され、誰もが文句を言うことができます。

ミニマックス

コードの核となるアイデアはミニマックスです。ミニマックスは、それぞれ最小と最大を意味する mini と max の 2 つの部分に分類できます。

直感的な理解とは何ですか?これは、A と B の 2 人がチェスをしているようなものです。 A は N 点でチェスを動かすことができるので、A の動きのボード評価スコアが最も高くなるが、B がプレイする番になると、間違いなくその方向に動くとします。したがって、A の次のステップは B によって設定された軌道に従わなければならず、最初のステップのこのステップで A が推定した最高​​のボード スコアに到達することはできません。

それはカードゲームでも同じです。農家がどのように対処してもカードを手に入れることができなければ、その農家には勝利の戦略があると言えます。そうでなければ、農家は負けます。 。

コアロジック

関数hand_outを使用して、人のカードプレイプロセスをシミュレートできます。実生活では、カードをプレイしたい場合、自分の手札 (me_pokers) にあるすべてのカードを知っている必要があり、また、前のハンド (last_hand) でプレイされたカードも知る必要があります。この関数を使用して 2 人のプレイをシミュレートしたい場合は、対戦相手の現在のカード (つまり、environment_pokers) をすべて知る必要もあります。

この 関数 の戻り値は、my_pokers がカードをプレイする番になったときに私が勝つことができるかどうかです。勝てる場合は true を返し、勝てない場合は false を返します。


def hand_out(me_pokers, enemy_pokers, last_hand)

私がカードをプレイする番が来て、私の手札がすべて出されれば、私はすぐに勝ったことが分かります。そうでなければ、相手のカードがすべて出されれば、私は勝ちました。そうでないなら、私は失敗しました。


if not me_pokers:
 return True
if not enemy_pokers:
 return False

今度は私がカードをプレイする番なので、まず今プレイできるすべてのハンドの組み合わせを知る必要があります。注: この組み合わせには、チェックする (つまり、プレイしない) 戦略が含まれます。


all_hands = get_all_hands(me_pokers)

ここで、考えられるすべてのハンドの組み合わせを反復したいと思います。

まず第一に、相手が最後のハンドでどのようなカードを出したかを知る必要があります。

  • 対戦相手が前のハンドでチェックインを選択した場合、または前のハンドにカードを持っていなかった場合、このラウンドではチェックインしてはなりませんが、私はどのカードをプレイしても構いません

  • 対戦相手が前のハンドにカードがあった場合、それより大きなカードをプレイするか、このラウンドで (カードをプレイせずに) カードを直接チェックすることを選択する必要があります

重要なポイントは、カードをプレイした後、または選択した後です。カードをチェックするには、再帰 Call を使用して、対戦相手の次の行動をシミュレートする必要があります。相手の次のカードプレイが勝てない場合、今回の私のカードプレイは間違いなく勝ちます。そうでない場合、私が行うすべてのカードプレイの選択で、相手が勝つことができれば、私は負けます。

コード全体は次のとおりです:


def hand_out(me_pokers, enemy_pokers, last_hand, cache):
 if not me_pokers:
  # 我全部过牌,直接获胜
  return True
 if not enemy_pokers:
  # 对手全部过牌,我失败
  return False
 # 获取我当前可以出的所有手牌组合,包括过牌
 all_hands = get_all_hands(me_pokers)
 # 遍历我的所有出牌组合,进行模拟出牌
 for hand in all_hands:
  # 如果上一轮对手出了牌,则这一轮我必须要出比对手更大的牌 或者 对手上一轮选择过牌,那么我只需出任意牌,但是不能过牌
  if (last_hand and can_comb2_beat_comb1(last_hand, hand)) or (not last_hand and hand['type'] != COMB_TYPE.PASS):
   # 模拟对手出牌,如果对手不能取胜,则我必胜
   if not hand_out(enemy_pokers, make_hand(me_pokers, hand), hand, cache):
    return True
  # 如果上一轮对手出了牌,但我这一轮选择过牌
  elif last_hand and hand['type'] == COMB_TYPE.PASS:
   # 模拟对手出牌,如果对手不能取胜,则我必胜
   if not hand_out(enemy_pokers, me_pokers, None, cache):
    return True
 # 如果之前的所有出牌组合均不能必胜,则我必败
 return False

Build

上記のコアロジックを明確にすると、クラッカーの構築は非常に簡単になります。

まず、カードのサイズを表すために数字を使用する必要があります。ここでは、3 を表すには 3、J を表すには 11、Q を表すには 12 などを使用します...

次に、次のことを行う必要があります。ハンド内のカードのすべての組み合わせを見つけるには、ここで get_all_hands 関数が必要になります。具体的な実装は比較的複雑ですが、非常に単純なので、ここでは詳しく説明しません。 get_all_hands函数,具体实现比较繁琐但是很简单,就不在此赘述。

然后,我们还需要一个牌力判断函数can_comb2_beat_comb1(comb1, comb2) ,这个函数用于比较两组手牌的牌力,看是否comb2可以击败comb1。唯一需要注意的一点,在斗地主的规则中,除了炸弹外,其他所有牌力均等,只有牌型一样时才能去比较。

最后,我们需要一个模拟出牌函数make_hand(pokers, hand)

次に、カード強度判定関数 can_comb2_beat_comb1(comb1, comb2) も必要です。この関数は、2 つのハンドのセットのカード強度を比較して、comb2 が comb1 に勝つことができるかどうかを確認するために使用されます。ただ注意が必要なのは、Landlordのルールではボム以外のカードは全て同じ強さであり、カードの種類が同じ場合のみ比較できるということです。 🎜🎜最後に、シミュレートされたカード プレイ関数 make_hand(pokers, hand) が必要です。これは、ポーカーを手に持ってハンドをプレイした後に残りのハンドを見つけるために使用されます。実装も非常に簡単です。プレイされたカードを取り除くだけです。 🎜

効率

1 組のカードには膨大な数のハンドが存在するため、再帰的分岐の数は膨大になります。したがって、時間オーバーヘッドは非常に大きく、階乗レベル O(N!) になります。スターリングの公式によれば、それは約 O(N^N) です。

繰り返されるカードが多数ある可能性があり、その結果、再帰呼び出しが何度も繰り返されるためです。したがって、キャッシュを追加すると、効率が大幅に向上します。

つまり、自分の手、敵の手、前のラウンドの手の説明(str(me_pokers)+str(enemy_pokers)+str(last_hand))がキーとなり、その結果がキャッシュ辞書に格納されます。次回同じ状況が発生した場合は、再度計算を繰り返すことなく、キャッシュ ディクショナリから直接取得できます。時間計算量は指数関数 O(C^N) に最適化されます。

結果

コード計算の結果は、農家には勝利戦略がないということです。言い換えれば、地主が遊び方を知っている限り、農民は勝つことができないのです。クラスの固定化はすでにこのようになっているのでしょうか...

オープンソース

コードはGithub:doudizhu_solverに配置されています。または、MITライセンスに基づいてローカルにダウンロードして、好きなようにプレイすることもできます。

以上がPython を使用して家主の最終局面を解決するコツを教えますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。