こんにちは、みやびのです。

本講座はPythonistaを活用してポーカーゲームを作成する方法について解説するものです。(全5回を予定)

第2回「Pythonistaでポーカーゲームを画面表示する~Pythonista+sceneポーカー作成講座2~」ではポーカーゲームの画面表示の基本処理を作成しました。

第3回となる今回は、Pythonでポーカーのカード交換機能を追加する方法を紹介します。
本記事の内容は以下の通り。

・Pythonistaのポーカー交換機能の追加方法
・Pythonistaのポーカー交換機能の実装例と実行例

Pythonistaのポーカー交換機能の追加方法

今回の修正内容は以下の通りです。

・カードをタッチすると反転する処理の追加
・カードを交換する処理を追加
・ゲームの状態と結果表示
・残り交換回数の表示

カードをタッチすると反転する処理の追加

カードをタッチした時に反転する処理を追加します。
itemにリバースのフラグを追加。

Trueの場合は裏にします。

トランプの裏のカード画像はPythonista側で用意されているのでこちらを使いましょう。
カラーは赤・青・緑の3パターン、柄は5パターン計15パターンあります。

トランプの裏

裏表の設定はset_item()を改造して追加。

引数に「direction」を追加します。
引数に指定する値は以下の通り。

None:そのまま
‘front’:表にする
‘back’:裏にする
‘reverse’:裏表を反転する

カードを交換する処理を追加

交換の方法としては以下の2つがあります。

方法1:交換するカードを「remove()」で除外して新しいカードを「append()」で追加する方法
方法2:交換するカードの情報を新しいカードの情報で上書きする方法

方法1だとdraw_cardsとitemのインデックスがずれてしまうので調整が少々面倒です。
方法2であればカードの内容を上書きするだけですむため、今回は方法2を採用しました。

ゲームの状態と結果表示

交換回数の上限した時に「Game Over」という文字と結果を表示するようにします。
また、プログラムを再起動せずに新しいゲームができるように「New Game」ボタンを追加すると便利です。

上記機能を持たせるためにゲームの状態を新たに追加します。
今回作成する状態は以下の3つです。

standby:ゲーム開始前
run:ゲーム中
pause:ゲーム中断

ゲームの状態は以下のように遷移します。

・ゲーム開始時は「standby」
・「Start」ボタンを押すとゲーム開始
・交換回数に到達したら状態「pause」
・ゲームオーバーの状態で「New Game」ボタンを押すと「standby」に移行し新しいゲームを開始

状態に応じてボタンの文字も変更します。

standby:Start
run:Change
pause:New Game

ボタン名は「button_label.text」を変更すれば修正できます。

# ボタン名の修正
self.button_label.text = 'Start'

また、残り回数を表示してユーザーにあと何回交換できるか見えるようにしましょう。

残り回数は「交換可能回数 – 交換した回数」で計算できます。

# 回数の更新
remaining = self.change_num - self.change_count
self.description_label.text = 'あと' + str(remaining) + '回交換できます。'

Pythonistaのポーカー交換機能の実装例と実行例

setup()メソッドに交換可能回数を追加します。

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

◆setup_object()関数の修正
前回作成したsetup_object()関数を修正します。

ボタンの初期文字を’reset’から’Start’に変更。

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

◆reset_game()の修正
全体的に修正。
各種パラメータのリセットとカードを裏側にする処理を追加しています。

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')

◆set_item()の修正
カードを反転する処理を追加しています。
初期設定時や交換時のために強制的に裏表を書き換える処理も追加しました。

directionが「None」の場合はこれまで通り裏表変更なしの設定です。

# カードを画面表示するためのセットアップ
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)

touch_began()の処理

touch_began()の処理はカードをタッチした場合とボタンをタッチした時の処理の2つです。
ボタンをタッチした場合はゲームの状態によって3つの処理にわかれます。

◆カードをタッチした場合
表の場合は裏に、裏の場合は表に反転させます。

◆ボタンをタッチした場合
Start:裏側のカードを表にしてゲーム開始
Change:裏側のカードを交換
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 == '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.button_label.text = 'New Game'
					self.game = 'pause'		
					
					# 役の更新
					self.hand_label.text = 'GAME OVERn結果:' + self.tg.check_poker_hand()
					self.description_label.text = ''
					
					self.hand_label.position = (self.size.w/2, self.size.h - 270)
				else:
					self.update_text()
			
			else:
				self.reset_draw_cards()
				self.game = 'run'
				self.button_label.text = 'Change'
					
				self.update_text()
		
		if self.game == 'run':
			# カードをひっくり返す
			for i,item in enumerate(self.items):
				if touch_loc in item.frame:
					self.set_item(i, 'reverse')

◆reset_draw_cards()の修正
表に書き換えるようにset_item()のパラメータを変更します。

for i,card in enumerate(self.tg.draw_cards):
  self.set_item(i, 'front')

◆テキスト更新用のupdate_text()の新設
役や交換回数を更新する関数「update_text()」を追加しました。

def update_text(self):
  # 役の更新
  self.hand_label.text = self.tg.check_poker_hand()
			
  # 回数の更新
  remaining = self.change_num - self.change_count
  self.description_label.text = 'あと' + str(remaining) + '回交換できます。'

◆クラス「TrumpGame」の修正
新しいメソッド「change_card()」を追加します。

「change_card()」は引数に指定したidのカードを山札から引いた新しいカードの情報で上書きします。
コードの実装例は以下の通り。

# カード交換
def change_card(self, id):
  self.draw_cards[id] = self.card_list.pop(0)

◆クラス「MyScene」側にも「change_card()」を新設
「MyScene」側にも「change_card()」を追加します。
「tg.change_card()」を呼び出してitemの内容を書き換えればOKです。
交換後のカードは表にしたいのでset_item()の引数には「’front’」を指定します。

# カードの交換処理の作成
def change_card(self, id):
  self.tg.change_card(id)
  self.set_item(id, 'front')

修正後ソースコード全文と実行例

修正後のソースコードの全文は以下の通りです。

※長いので非表示にしています。

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

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

class MyScene (Scene):
	def setup(self):
		# 交換回数
		self.change_num = 3
		# オブジェクト初期設定
		self.setup_object()
		# カードゲーム初期設定
		self.reset_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 == '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.button_label.text = 'New Game'
					self.game = 'pause'		
					
					# 役の更新
					self.hand_label.text = 'GAME OVERn結果:' + self.tg.check_poker_hand()
					self.description_label.text = ''
					
					self.hand_label.position = (self.size.w/2, self.size.h - 270)
				else:
					self.update_text()
			
			else:
				self.reset_draw_cards()
				self.game = 'run'
				self.button_label.text = 'Change'
					
				self.update_text()
		
		if self.game == 'run':
				# カードをひっくり返す
				for i,item in enumerate(self.items):
					if touch_loc in item.frame:
							self.set_item(i, 'reverse')
							
	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
		
		# ボタン
		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, initial_setting=False):
		
		# 交換回数カウント
		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 update_text(self):
		# 役の更新
		self.hand_label.text = self.tg.check_poker_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)
			
		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_flag = True
		# ストレートの有無フラグ
		straight_flag = True
		
		# 数字の昇順に並び替える
		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 flash_flag == True and cards[i]['symbol'] != cards[i - 1]['symbol']:
					flash_flag = False
				# 数字が連続しているかチェック
				if straight_flag == True and cards[i]['number'] != cards[i - 1]['number'] + 1:
					if cards[i]['number'] != 10 or cards[i-1]['number'] != 1:
						straight_flag = False 
		
		# 最終手札チェック
		if straight_flag == True and flash_flag == True:
			if cards[0]['number'] == 1 and cards[4]['number'] == 13:
				# ロイヤルストレートフラッシュ
				hand = 'ロイヤルnストレートフラッシュ'
			else:
				# ストレートフラッシュ
				hand = 'ストレートフラッシュ'
		elif match_number > 2:
			if match_number == 4:
				# 4カード
				hand = '4カード'
			else:
				if pair_count > 0:
					# フルハウス
					hand = 'フルハウス'
				else:
					# 3カード
					hand = '3カード'
		elif flash_flag == True:
			# フラッシュ
			hand = 'フラッシュ'
		elif straight_flag == True:
			# ストレート
			hand = 'ストレート'
		elif pair_count > 0:
			if pair_count > 1:
				# 2ペア
				hand = '2ペア'
			else:
				# 1ペア
				hand = '1ペア'
		else:
			# なし
			hand = 'ぶた'
		
		return hand

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

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

◆実行例

以上、ポーカーの交換処理の追加でした。
今回は処理を詰め込み過ぎですね(汗)。

後日別記事を立てて整理しようと思います。

次回>>第4回:Pythonistaのポーカーゲームにジョーカーの処理を追加する

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