
ジャンケン 相手の手を読むNPC
前回、ジャンケンする時の人間の思考から、「連続して同じ手を出しにくいNPC」を作って遊びました。
✅グー・チョキ・パーで出しやすさが違う
✅連続して同じ手は出しにくい
▢相手の出し手の割合を見て、多い確率の手に勝つ手を出そうとする etc..
今回は「相手の手を読むNPC」を作って遊ぼうと思います。
アドバイザーはGemini2.0です。
今日のハイライト


相手が出していない手に勝とうとするAI
前回は、自分が出していない手を出そうとするAIを作りましたが、これをプレイヤーの履歴を読んで、出していない手を予測し、それに勝とうとする思考パターンを作ります。

プレイヤーの履歴を参照して、出してない手に勝つ手を出すNPC
CPU_MindHackを作り直近の履歴を参照
・CPUの子ノードにCPU_MindHack(Control)を追加
・CPU_MindHackのスクリプトを作る
・プレイヤーの履歴を参照して出していない手を選び、勝つ手を決める
CPU_MindHackとCPU_Historyとのコードの差分
CPU_MindHackの初期設定
# 過去の選択履歴を保存する配列
var history = [] # 履歴の配列
var weight_ratio = 0.8 # 同じ数が出現するたびにかける重みづけの割合
var history_num = 5 # 過去何回までを参照するか
var correct_answer = 0 # 予測があっていた回数
CPU_MindHackの「出す手を決定」
# 出す手を決定
for i in range(len(candidates)):
current_weight += weights[i]
if random_value <= current_weight:
var player_next_hand = candidates[i] # プレイヤーの出す手を決定
var text_direct = {1:3, 2:1, 3:2} # 勝つ手を定義
Global.cpu_hand = text_direct[player_next_hand]
print("player:", str(Global.player_hand)," predict:", str(player_next_hand), " cpu:", str(Global.cpu_hand), " weight:", str(weights)) # デバッグ表示(出す手と重みづけ)
if player_next_hand == Global.player_hand:
correct_answer += 1
if Global.total > 0:
print("Correct! ", str(100*correct_answer/Global.total),"%")
history.append(Global.player_hand) # プレイヤーの手をを履歴に追加
CPUのスクリプトを修正
# CPUの手をランダムに選択
func select_hand():
if not selected:
$CPU_MindHack.choose_number() # CPU_MindHackシーンへ
selected = true
じゃんけん開始!
NPCはプレイヤーの履歴を見て出し手を考えるので、最初、重みづけの割合を0.5にしていたら、こちらがわざと同じ手を出すようにすると、その手を除外するので誘導して勝率が上がりませんでした。
履歴をさかのぼる回数や、重みづけの割合など、色々調整して、重みづけの割合を調整したら、同じ手を連続して出しても完ぺきには除外せず、適度に反撃します。
この思考パターンは、完全ランダムに近いですが、時々、こちらの手を読むので、こちらが平均的に出すとかなり強いです。初心者殺しですね。
NPCの履歴を見て出し手が少ないもので負けない手を選ぶと勝てるかも?


相手が出しやすい手に勝つ手を出すNPC
プレイしてみて、人間は出す手が偏りやすい同じ手を連続して出す傾向が強くなることが分かりました。
特にCPUの履歴を見て出してない手に勝つ手を出そうとすると、かなり偏ってくるので、出し手が多いものほど重みづけの割合をアップさせたCPUを作って対戦してみます。

プレイヤーが多く出す手を狙うNPC
CPU_Fortuneを作り、直近の履歴を参照
・CPUの子ノードにCPU_Fortune(Control)を追加
・CPU_Fotuneのスクリプトを作る
・プレイヤーの履歴を参照して良く出す手に勝つ手を出す
CPU_FortuneはCPU_MindHackの数値違い
処理は同じで、重みづけの割合と過去の履歴の回数だけ変えます。
# 過去の選択履歴を保存する配列
var history = [] # 履歴の配列
var weight_ratio = 1.5 # プレイヤーが出しやすい手の重みづけの割合をUP
var history_num = 3 # 直近3回だけ見るように修正
var correct_answer = 0 # 予測があっていた回数
じゃんけん開始!
NPCはプレイヤーが出しやすい手に勝つ手を出しますが、あまり過去の履歴を参照し過ぎると、同じ手を繰り返しだすことになって、プレイヤー側が対応しやすくなるので、参照回数を3回に限定しました。
プレイヤーが連続で同じ手を出さなければ、完全ランダムに近くなるため、プレイヤーが最初にポイントして差が開くとNPCは追いつくことができませんが、接戦やNPCが先にポイントを稼ぐと結構強いです。


数値調整や他の条件を加味すると、もっと強くできるかもしれません。
興味がある方は調整してみては?
次は最強ジャンケンリーグでNPC同士を戦わせてみようかな?
誰かに楽しんでもらえたら最高です。