こんにちは、みやびのです。
本講座ではPythonistaでポーカーゲームを作る方法を紹介しています。

前回「Pythonistaのポーカーゲームにジョーカーの処理を追加する~Pythonista+sceneポーカー作成講座4~」ではジョーカーを追加する処理を作成しました。

最終回となる今回はベット機能の作成方法について紹介します。

ベットとはコインやチップを賭けて行うギャンブルのルールです。
ベット機能を追加すれば役によって手持ちのコインが増えたり減ったりするのでよりゲームの幅を広げられます。

Pythonistaのポーカーゲームにベット機能を追加する方法

ベット機能について

ベットとはコインやチップを賭けて行うギャンブルのルールです。
ポーカーの場合は役に応じたコインやチップが返却されます。役が成立しなかった場合は賭けたコインは没収です。

本記事では、役に応じたコインを返却する機能を追加します。

ベット機能に必要な処理

◆役ごとの倍率
まずは役ごとの倍率を決めましょう。
ぶた(役なし)は0で、あとは強い役ほど倍率を上げるでOKです。

一例を表にまとめました。
下記を参考にしつつ、倍率を調整してみてください。

倍率
5カード200
ロイヤル100
ストレートフラッシュ50
4カード30
フルハウス10
フラッシュ8
ストレート5
3カード3
2ペア2
ジョーカー2
1ペア1
ぶた0

交換回数にもよりますが、上記は結構ゆるめの倍率です。

◆ベットのUI
ベットを増減させるボタンを設置します。
初期値は1。
ベットの上限は手持ちのコイン、下限は1とします。

◆コインの返却処理
最後の交換が終わったら確定した役に応じてコインを返却します。
ぶたの場合はコインは返却されずに没収です。

コインが没収された結果ベットをコインが下回った場合ベットの額をコインの額に修正しましょう。

◆ゲームの継続
コインの返却処理後に「Continue」ボタンを押すと現在のコインのまま新しいゲームを続けます。

◆ゲームオーバー処理
手持ちのコインが0になった場合、ゲームオーバーとなります。

ゲームオーバー時は「New Game」ボタンを押すことで新しいゲームを始めることができます。

Pythonistaのポーカーゲームにベット機能の実装例と実行例

初期設定

新しい関数「new_game()」を作成します。

def new_game(self):
  # 初期コイン設定
  self.coin = 100
  self.coin_label.text = 'Coin:' + str(self.coin)
  # デフォルトベットの設定
  self.bet = 1
  self.bet_label.text = 'Bet:' + str(self.bet)
  # パラメータリセット
  self.reset_game()

各メソッドの役割は以下の通り。
コインをリセットして新しいゲームを開始する場合:「new_game()」
現在のコインを継続したまま続ける場合:「reset_game()」

◆「setup()」の修正
「reset_game()」の代わりに「new_game()」を呼び出すように修正します。

def setup(self):
  # 交換回数
  self.change_num = 3
  # オブジェクト初期設定
  self.setup_object()
  # カードゲーム初期設定
  self.new_game()

◆setup_object()の修正
各種オブジェクトを追加します。

# UIのバックグラウンドの設定
bg_shape = ui.Path.rounded_rect(0, 0, 800, 300, 8)
		
bg_shape.line_width = 4
shadow = ((0, 0, 0, 0.35), 0, 0, 24)
self.menu_bg = ShapeNode(bg_shape, (1,1,1,0.9), '#15a4ff', shadow=shadow, parent=self)
		
# コインの表示
coin_font = ('Futura', 40)
self.coin_label = LabelNode('Coin:100', coin_font, parent=self, color='green')
self.coin_label.position = (self.size.w/2, 100)
self.coin_label.z_position = 1
		
# ベットの表示
bet_font = ('Futura', 20)
self.bet_label = LabelNode('Bet:1', bet_font, parent=self, color='black')
self.bet_label.position = (self.size.w/2, 50)
self.bet_label.z_position = 1
		
# ベットのupボタン
self.bet_up = SpriteNode('iob:chevron_up_32', position=(self.size.w/2 + 100,50))
self.add_child(self.bet_up)
		
# ベットのdownボタン
self.bet_down = SpriteNode('iob:chevron_down_32', position=(self.size.w/2 - 100,50))
self.add_child(self.bet_down)

◆check_poker_hand()の修正
最終手札チェックの処理を修正し、役とともにコインの倍率(mag)も返すようにします。

# 最終手札チェック
if straight_count == 4 and flash_count == 4:
	if cards[0]['number'] == 1 and cards[4]['number'] == 13 or royal_flg == True:
		# ロイヤルストレートフラッシュ
		result = {
			'hand' : 'ロイヤルnストレートフラッシュ',
			'mag' : 100
		}
	else:
		# ストレートフラッシュ
		result = {
			'hand' : 'ストレートフラッシュ',
			'mag' : 50
		}
elif match_number > 2:			
	if match_number == 5:
		# 5カード
		result = {
			'hand' : '5カード',
			'mag' : 200
		}
	elif match_number == 4:
		# 4カード
		result = {
			'hand' : '4カード',
			'mag' : 30
		}
	else:
		if pair_count > 0:
			# フルハウス
			result = {
				'hand' : 'フルハウス',
				'mag' : 10
			}
		else:
			# 3カード
			result = {
				'hand' : '3カード',
				'mag' : 3
			}
elif flash_count == 4:
	# フラッシュ
	result = {
			'hand' : 'フラッシュ',
			'mag' : 8
	}
elif straight_count == 4:
	# ストレート
	result = {
			'hand' : 'ストレート',
			'mag' : 5
		}
elif pair_count > 0:
	if pair_count > 1:
		# 2ペア
		result = {
			'hand' : '2ペア',
			'mag' : 2
		}
	else:
		# 1ペア
		result = {
			'hand' : '1ペア',
			'mag' : 1
		}
elif joker_flg:
	result = {
			'hand' : 'ジョーカー',
			'mag' : 2
		}
else:
	# なし
	result = {
			'hand' : 'ぶた',
			'mag' : 0
	}
		
return result

◆touch_began()の修正
ゲームオーバーになった場合の処理とベットの増減の処理を追加します。

def touch_began(self, touch):
	# タップした位置の取得
	touch_loc = self.point_from_scene(touch.location)
		
	if touch_loc in self.button.frame:
		self.button.texture = Texture('pzl:Button2')
		if self.game == 'pause':
			self.reset_game()
		elif self.game == 'game over':
			self.new_game()
		elif self.game == 'run':

役の更新処理は、大幅に修正。役だけでコインを表示するようにします。
また、コインが0になったらゲームオーバー処理に移行です。
	
# 役の更新
result = self.tg.check_poker_hand()
self.hand_label.text = '結果:' + result['hand']
self.description_label.text = ''
					
self.hand_label.position = (self.size.w/2, self.size.h - 270)
self.coin += int(result['mag']) * self.bet
self.coin_label.text = 'Coin:' + str(self.coin)

# 残りコインが0になっていないかチェック
if self.coin > 0:
        # コインを維持したまま次のゲームへ
	self.button_label.text = 'Next'
	if self.bet >= self.coin:
		self.bet = self.coin
		self.bet_label.text = 'Bet:' + str(self.bet)
else:
        # ゲームオーバー処理
	self.hand_label.text = 'Game Over'
	self.button_label.text = 'New game'
	self.game = 'game over'
	
# ゲーム開始
self.reset_draw_cards()
self.game = 'run'
self.button_label.text = 'Change'
					
self.update_text()
# ベットの減算
self.coin -= self.bet
self.coin_label.text = 'Coin:' + str(self.coin)

ベットの増減操作を追加します。
増減操作ができるのはゲームが「standby」の時のみです。

	
elif self.game == 'standby':
  # ベットを増やす
  if touch_loc in self.bet_up.frame and self.bet < self.coin:
    self.bet += 1
    self.bet_label.text = 'Bet:' + str(self.bet)
    # ベットを減らす
    elif touch_loc in self.bet_down.frame and self.bet > 1:
      self.bet -= 1
      self.bet_label.text = 'Bet:' + str(self.bet)

コード全文と実行例

コード全文は以下の通り。

+マークをクリックするとソースコードを表示できます。

ソースコードの表示・非表示切り替え
	
from scene import *
import random
import ui

class MyScene (Scene):
	def setup(self):
		# 交換回数
		self.change_num = 3
		# オブジェクト初期設定
		self.setup_object()
		# カードゲーム初期設定
		self.new_game()
	
	def touch_began(self, touch):
		# タップした位置の取得
		touch_loc = self.point_from_scene(touch.location)
		
		if touch_loc in self.button.frame:
			self.button.texture = Texture('pzl:Button2')
			if self.game == 'pause':
				self.reset_game()
			elif self.game == 'game over':
				self.new_game()
			elif self.game == 'run':
				# カード交換
				for i,item in enumerate(self.items):
					if item.reverse == True:
						self.change_card(i)
							
				self.change_count += 1			
				if self.change_count == self.change_num:
					self.game = 'pause'		
					
					# 役の更新
					result = self.tg.check_poker_hand()
					self.hand_label.text = '結果:' + result['hand']
					self.description_label.text = ''
					
					self.hand_label.position = (self.size.w/2, self.size.h - 270)
					self.coin += int(result['coin']) * self.bet
					self.coin_label.text = 'Coin:' + str(self.coin)
					
					if self.coin > 0:
						self.button_label.text = 'Next'
						if self.bet >= self.coin:
							self.bet = self.coin
							self.bet_label.text = 'Bet:' + str(self.bet)
					else:
						self.hand_label.text = 'Game Over'
						self.button_label.text = 'New game'
						self.game = 'game over'
						
				else:
					self.update_text()
			
			else:
				self.reset_draw_cards()
				self.game = 'run'
				self.button_label.text = 'Change'
					
				self.update_text()
				# ベットの減算
				self.coin -= self.bet
				self.coin_label.text = 'Coin:' + str(self.coin)
		
		if self.game == 'run':
				# カードをひっくり返す
				for i,item in enumerate(self.items):
					if touch_loc in item.frame:
							self.set_item(i, 'reverse')
		elif self.game == 'standby':
			if touch_loc in self.bet_up.frame and self.bet < self.coin:
				self.bet += 1
				self.bet_label.text = 'Bet:' + str(self.bet)
				
			elif touch_loc in self.bet_down.frame and self.bet > 1:
				self.bet -= 1
				self.bet_label.text = 'Bet:' + str(self.bet)
							
	def touch_ended(self, touch):
		self.button.texture = Texture('pzl:Button1')
	
	# カードを画面表示するためのセットアップ
	def set_item(self, id, direction=None):
		item = self.items[id]
		card = self.tg.draw_cards[id]
		# 引数指定時は書き換え
		if direction is not None:
			# 表にする
			if direction == 'front':
				item.texture = Texture('card:' + card['string'])
				item.reverse = False
			# ひっくり返す
			elif direction == 'reverse':				
				if item.reverse == True:
					item.texture = Texture('card:' + card['string'])
					item.reverse = False
				else:
					item.texture = Texture('card:BackRed1')
					item.reverse = True
			# 裏にする
			else:
				item.texture = Texture('card:BackRed1')
				item.reverse = True
		item.anchor_point = (0.5, 0)
		self.resize_item(item)
		
	
	def setup_object(self):
		# バックグラウンド設定
		self.background_color = '#004f82'
		ground = Node(parent=self)
		
		# 役の表示
		hand_font = ('Futura', 30)
		self.hand_label = LabelNode('', hand_font, parent=self)
		self.hand_label.position = (self.size.w/2, self.size.h - 70)
		self.hand_label.z_position = 1
		
		bg_shape = ui.Path.rounded_rect(0, 0, 800, 300, 8)
		
		bg_shape.line_width = 4
		shadow = ((0, 0, 0, 0.35), 0, 0, 24)
		self.menu_bg = ShapeNode(bg_shape, (1,1,1,0.9), '#15a4ff', shadow=shadow, parent=self)
		
		# コインの表示
		coin_font = ('Futura', 40)
		self.coin_label = LabelNode('Coin:100', coin_font, parent=self, color='green')
		self.coin_label.position = (self.size.w/2, 100)
		self.coin_label.z_position = 1
		
		# ベットの表示
		bet_font = ('Futura', 20)
		self.bet_label = LabelNode('Bet:1', bet_font, parent=self, color='black')
		self.bet_label.position = (self.size.w/2, 50)
		self.bet_label.z_position = 1
		
		# upボタン
		self.bet_up = SpriteNode('iob:chevron_up_32', position=(self.size.w/2 + 100,50))
		self.add_child(self.bet_up)
		
		# downボタン
		self.bet_down = SpriteNode('iob:chevron_down_32', position=(self.size.w/2 - 100,50))
		self.add_child(self.bet_down)
		
		# ボタン
		self.button = SpriteNode('pzl:Button1', position=(self.size.w/2,300))
		self.add_child(self.button)
		
		button_font = ('Futura', 30)
		self.button_label = LabelNode('Start', button_font, parent=self, color='black')
		self.button_label.position = (self.size.w/2,305)
		self.button_label.z_position = 1
		
		# 残り回数を表示する
		description_font = ('Futura', 30)
		self.description_label = LabelNode('', description_font, parent=self)
		self.description_label.position = (self.size.w/2, 200)
		self.description_label.z_position = 1
		
		self.items = []
		self.tg = TrumpGame()
		# カードの初期設定
		for i in range(0,5):
			item = SpriteNode()
			self.items.append(item)
			self.add_child(item)
			item.position = (50 + i * 70, 350)
	
	def reset_game(self):
		
		# 交換回数カウント
		self.change_count = 0
		
		self.game = 'standby'
		self.button_label.text = 'Start'
		
		# 役の更新
		self.hand_label.text = ''
		self.hand_label.position = (self.size.w/2, self.size.h - 70)
		
		self.reset_draw_cards()
		for i in range(0,5):
			self.set_item(i, 'back')
			
	def new_game(self):
		self.coin = 100
		self.coin_label.text = 'Coin:' + str(self.coin)
		self.bet = 1
		self.bet_label.text = 'Bet:' + str(self.bet)
		self.reset_game()
	
	def update_text(self):
		# 役の更新
		self.hand_label.text = self.tg.check_poker_hand()['hand']
			
		# 回数の更新
		remaining = self.change_num - self.change_count
		self.description_label.text = 'あと' + str(remaining) + '回交換できます。'
	
	# カードの交換処理の作成
	def change_card(self, id):
		self.tg.change_card(id)
		self.set_item(id, 'front')
	
	# カードを5枚引く処理
	def reset_draw_cards(self):
		self.tg.reset_draw_cards(5)
		for i,card in enumerate(self.tg.draw_cards):
				self.set_item(i, 'front')
	
	# サイズ調整
	def resize_item(self, item):
		item.size = (65, 87)


class TrumpGame:
	def make_card_list(self):	
		# マークのリスト
		symbol_list = ['Clubs', 'Hearts', 'Spades', 'Diamonds']
		# カードリスト
		card_list = []

		# カードのデータを作成
		for symbol in symbol_list:
			for number in range(1, 14):
				card = {
					'number' : number,
					'symbol' : symbol
				}
				# マークと数字を合体させる
				# 11以上と1は置き換え
				if number == 1:
					card['string'] = symbol + 'A'
				elif number == 11:
					card['string'] = symbol + 'J'
				elif number == 12:
					card['string'] = symbol + 'Q'
				elif number == 13:
					card['string'] = symbol + 'K'
				else:
					# 10以下ならそのまま
					card['string'] = symbol + str(number)

				# カードをリストに追加
				card_list.append(card)
				
		# ジョーカーの追加		
		joker =  {
					'number' : 99,
					'symbol' : 'Joker',
					'string' : 'Joker'
		}
		card_list.append(joker)
			
		self.card_list = card_list
	def shuffle(self):
			# カードをシャッフルする
			random.shuffle(self.card_list)
			
	# 手札を作成する
	def reset_draw_cards(self, number):
		card_list = self.make_card_list()
		self.shuffle()
		self.draw_cards = []

		for i in range(0, number):
			self.draw_cards.append(
				self.card_list.pop(0)
			)	
	
	# カード交換
	def change_card(self, id):
		self.draw_cards[id] = self.card_list.pop(0)
	
	# 役のチェック処理
	def check_poker_hand(self):
		# ペア数
		pair_count = 0
		# 同じ数字のカウント
		match_count = 0
		# 同じ数字の枚数(3カード,4カードチェック用)
		match_number = 0
		# フラッシュカウント
		flash_count = 0
		# ストレートカウント
		straight_count = 0
		
		royal_flg = False
		
		# 数字の昇順に並び替える
		cards = sorted(self.draw_cards, key=lambda x:x['number'])
		
		# チェックループ
		for i in range(1,5):
				# 前の数字が同じかチェック
				if cards[i]['number'] == cards[i - 1]['number']:
					match_count += 1
					# 最終ループチェック
					if i == 4:
						if match_count == 1:
							pair_count += 1
						# 3カード以上の場合
						elif match_count > 1:
							match_number = match_count + 1
				else:
					# 違う数字の場合
					if match_count == 1:
						pair_count += 1
					# 3カード以上の場合
					elif match_count > 1:
						match_number = match_count + 1
					match_count = 0
				# 同じマークが続いているかチェック
				if cards[i]['symbol'] == cards[i - 1]['symbol']:
					flash_count += 1
				# 数字が連続しているかチェック
				if cards[i]['number'] == cards[i - 1]['number'] + 1:
					straight_count += 1
				else:
					if cards[i]['number'] == 10 and cards[i-1]['number'] == 1:
						straight_count += 1
		
		# ジョーカーチェック
		joker_flg = cards[4]['string'] == 'Joker'
		if joker_flg:
			# フラッシュは一律カウントアップ
			flash_count += 1
			# 3カード,4カードの場合
			if match_number >= 3:
				# 3カードを4カード、4カードを5カードへ
				match_number += 1
			# ペアのチェック(ストレートより先にチェックする)
			elif pair_count > 0:
				# ペアを3カードに昇格
				pair_count -= 1
				match_number = 3
			# ストレートチェック1
			elif straight_count == 3:
				straight_count += 1
				# このパターンは必ず10,J,Q,Kなのでジョーカーを加えるとロイヤルフラグ確定
				if cards[0]['number'] == 10:
					straight_count = 4
					royal_flg = True
				# 13がないケース
				elif cards[0]['number'] == 1 and cards[1]['number'] == 10 and cards[3]['number'] == 12:
					straight_count = 4
					royal_flg = True
			# ストレートチェック2
			elif straight_count == 2:
				# 4以下ならストレートが成立
				if cards[3]['number'] - cards[0]['number'] <= 4:
					straight_count = 4

				# ロイヤルの並びのチェック
				elif cards[0]['number'] == 1 and cards[1]['number'] == 10 and cards[3]['number'] == 13:
					straight_count = 4
					royal_flg = True
				# 10がない場合
				elif cards[0]['number'] == 1 and cards[1]['number'] == 11 and cards[3]['number'] == 13:
					straight_count = 4
					royal_flg = True

		# 最終手札チェック
		if straight_count == 4 and flash_count == 4:
			if cards[0]['number'] == 1 and cards[4]['number'] == 13 or royal_flg == True:
				# ロイヤルストレートフラッシュ
				result = {
					'hand' : 'ロイヤルnストレートフラッシュ',
					'coin' : 100
				}
			else:
				# ストレートフラッシュ
				result = {
					'hand' : 'ストレートフラッシュ',
					'coin' : 50
				}
		elif match_number > 2:			
			if match_number == 5:
				# 5カード
				result = {
					'hand' : '5カード',
					'coin' : 200
				}
			elif match_number == 4:
				# 4カード
				result = {
					'hand' : '4カード',
					'coin' : 30
				}
			else:
				if pair_count > 0:
					# フルハウス
					result = {
						'hand' : 'フルハウス',
						'coin' : 10
					}
				else:
					# 3カード
					result = {
						'hand' : '3カード',
						'coin' : 3
					}
		elif flash_count == 4:
			# フラッシュ
			result = {
					'hand' : 'フラッシュ',
					'coin' : 8
			}
		elif straight_count == 4:
			# ストレート
			result = {
					'hand' : 'ストレート',
					'coin' : 5
				}
		elif pair_count > 0:
			if pair_count > 1:
				# 2ペア
				result = {
					'hand' : '2ペア',
					'coin' : 2
				}
			else:
				# 1ペア
				result = {
					'hand' : '1ペア',
					'coin' : 1
				}
		elif joker_flg:
			result = {
					'hand' : 'ジョーカー',
					'coin' : 2
				}
		else:
			# なし
			result = {
					'hand' : 'ぶた',
					'coin' : 0
			}
		
		return result

if __name__ == '__main__':
	run(MyScene(), show_fps=False)

>>ソースコードをGitHubで見る

◆実行例

以上、Pythonistaのポーカーにベット機能を追加する方法でした。
今回で講座は終了です。

今後はわかりにくい点や説明が足りていない点について補足や追記修正していきます。
長いソースコードも読みにくいので対策を検討中です。

最後までお読み頂きありがとうございました。

ポーカー講座TOP>>Pythonista+sceneポーカー作成講座