さくさく理解する Godot 入門(ただし2Dに限る)応用編 テキサスホールデムポーカー【第1回】
目次
- はじめに
- 画面構成要素
- タイトルバー・テーブル・背景
- 移動可能チップ
はじめに
本稿では、下図のような「テキサスホールデムポーカー」アプリの実装について解説する。
日本人であればテキサスホールデムポーカー(以下、ホールデムと略す)を知らない・プレイしたことがない人がほとんどだと思う。 ホールデムはポーカーのプレイ方法のひとつで、ワールドワイドには最もポピュラーなものだ。 とくに、カジノでポーカーと言えば、ほぼ確実にホールデムなので、将来カジノデビューし、ポーカーをプレイしてみたいと思っている人は、 この機会に覚えてみるのもいいかもしれない。
で、ホールデムでは、最初は上図のように各プレイヤーに2枚の手札が配られる。当然、手札はそのプレイヤーだけが見ることが出来、 他のプレイヤーの手札は見ることが出来ない。手札だけの状態を「プリフロップ」と呼び、ここで最初の賭けが行われる。 賭けが成立すると、デッキから場に3枚のカード、これを共有カード(英語ではコミュニティカード)と呼ぶ、が出される。 この状態を「フロップ」と呼び、2回めの賭けが行われる。ついで4枚目(ターン)、5枚目(リバー)が場に出され、 それぞれ賭けを行う。これら、プリフロップ、フロップ、ターン、リバーをラウンドと呼ぶ。 全ラウンドの賭けが終わったら、各プレイヤーが手をさらす(これをショーダウンと呼ぶ)。 手札2枚と共有カード5枚、合計7枚から5枚を選び、役を作り、 強弱を判定し、最も強い役のプレイヤーが勝ちとなり、場に出されたチップ全てを得ることができる。
誰かが掛け金を上げた(これをレイズと呼ぶ)場合、降りることをフォールドという。同じ掛け金を出すことをコールと呼ぶ。 降りた人はそのハンドでの勝負に参加することはできない。
また、カジノでは「リングゲーム」と言って、参加者が自由にテーブルに出入り可能な方式を取る。 それとは異なり優勝者を決める大会では「トーナメント」と言い、途中でのテーブルの出入りは不可で、 テーブルに持ち込んだチップがなくなると破産(バースト)で試合を抜け、最後まで残った人が優勝・参加費を総取りできるという方式もある。
本アプリでは、トータル6人で行うリングゲームのみとする。手前の一人が人間プレイヤーで、残りの5人はAIだ。つまりAI対戦ということだ。
ただし、本アプリで解説する AI は非常に単純なもので、その強さは限定的だ。
テキサスホールデムポーカーに関するより詳しい解説や戦略については、解説サイトが多くあるのでそれらを参照されたし。
個人的には ポーカー道 をお勧めする。
本アプリのソースコード全体は https://github.com/vivisuke/HoldemPoker を参照のこと。
また、実際にアプリを触ってみたい方は http://vivi.dyndns.org/Godot/HoldemPoker/ 参照してみるといいだろう。
画面構成要素
本章では、画面を構成する要素について説明する。
背景・テーブル・タイトルバーといった静的なオブジェクトについて説明し、 ついで、ポーカーチップ、カード、行動表示パネルなどの動きのあるオブジェクトについて説明する。
チップ・カードは、処理終了時にシグナルを発行するので、外部依存関係が無く、 部品化に適したものになっている(シグナルに関しては後述する)。
■タイトルバー・テーブル・背景
全体背景は、グレイ系で中央を明るくグラデーションをつけた画像(下図)をペイントソフトで作成し、それを表示している。
これまで作成してきたアプリでは単色の背景だったので ColorRect を使用していたが、 単色でなくグラデーションがあるとなんとなく雰囲気が出るような気がして、このような画像を作成・使用した。
画像を表示するために、ノードクラスとしては TextureRect を使用している。
テーブルも下図の画像を用意し、TextureRect を使ってそれを表示している。
タイトルバーは ColorRect を使用し、 _draw() をオーバライドし、角を丸くし、縁取りをし、影を付けている。この部分はこれまで作ってきたアプリと同様だ。
const RADIUS = 5 func _draw(): var style_box = StyleBoxFlat.new() # 影、ボーダなどを描画するための矩形スタイルオブジェクト style_box.bg_color = color # 矩形背景色 style_box.border_color = Color.green style_box.set_border_width_all(2) style_box.set_corner_radius_all(RADIUS) style_box.shadow_offset = Vector2(0, 4) # 影オフセット style_box.shadow_size = 8 # 影(ぼかし)サイズ draw_style_box(style_box, Rect2(Vector2(0, 0), self.rect_size)) # style_box に設定した矩形を描画
■移動可能チップ
チップは、Sprite ノードで、下図の画像を貼り付けたものだ(実際の画像サイズは 16x16px)。
チップは、ラウンド終了時にプレイヤー下部から中央に、ショーダウン後に中央から勝者プレイヤーに移動するアニメーションを行う。
Godot にはアニメーションを行うための専用クラスがあるのだが、筆者にはいまいち使い心地がよくなかったので、 オブジェクトを移動する機構をスクリプトで自作した。
Chip.move_to(移動先, 移動時間) により、現在位置から第一引数の指定移動先位置まで、第2引数の時間(単位:秒)をかけて移動する。
移動処理自体は Chip._process(delta) で行い、 移動が終了した時は、moving_finished シグナルを発行する。
シグナルとは、GUI要素などを依存関係を作らずに部品化するためのもので、 実行時にシグナルが発行されたときに呼ばれる関数を connect() を使って動的に結合することができる。
ボタンオブジェクトをすでに使ったことがある人は、エディタ右側のノードタブで pressed を処理関数に結合したことがあると思う。 実はボタンが押下されると pressed シグナルが発行されるので、それをエディタで処理関数に結合していたというわけだ。
以下に、移動処理の実装コードを示す。
signal moving_finished var moving = false # 移動中フラグ var move_dur = 0.0 # 移動所要時間(単位:秒) var move_elapsed = 0.0 # 移動経過時間(単位:秒) var src_pos = Vector2(0, 0) # 移動元位置 var dst_pos = Vector2(0, 0) # 移動先位置 func move_to(dst : Vector2, dur : float): src_pos = get_position() dst_pos = dst move_dur = dur move_elapsed = 0.0 moving = true func _process(delta): if moving: # 移動処理中 move_elapsed += delta # 経過時間 move_elapsed = min(move_elapsed, move_dur) # 行き過ぎ防止 var r = move_elapsed / move_dur # 位置割合 set_position(src_pos * (1.0 - r) + dst_pos * r) # 位置更新 if move_elapsed == move_dur: # 移動終了の場合 moving = false emit_signal("moving_finished") # 移動終了シグナル発行
move_to() がコールされると、移動中フラグをONにし、現在位置、移動先位置、移動所要時間を保存する。 移動処理自体は、各フレームごとに呼ばれる _process() で行う。経過時間を更新し、オブジェクトの位置を計算し更新する。 目的位置まで移動した場合は、移動中フラグをOFFにし、moving_finished シグナルを発行(emit)している。
TechProjin Godot入門 関連連載リンク
Godotで学ぶゲーム制作
さくさく理解するGodot入門 連載目次
標準C++ライブラリの活用でコーディング力UP!
「競技プログラミング風」標準C++ライブラリ 連載目次