【Python/tkinter】神経衰弱ゲームの作り方

神経衰弱ゲームの作り方の解説ページアイキャッチ

このページでは、Python で tkinter を用いた「神経衰弱ゲーム」の作り方について解説していきます!

作成できる「神経衰弱ゲーム」は下記のようなものになります。

神経衰弱ゲームの動作を紹介するアニメ

一人用のゲームなのでゲームとしては物足りないかもしれないですが、tkinter やプログラミングに慣れる目的としてはちょうど良い難易度の題材だと思います!

作成する「神経衰弱」ゲーム

今回作成する「神経衰弱」はマウスのクリックで操作を行うゲームとなります。

アプリを起動すると、下の図のように52枚のカード('2''10''A', 'J', 'Q', 'K' の13枚のカードをそれぞれ4枚ずつ用意)が裏返しされた状態で並べられます。

アプリ起動直後の画面

これらのカードは tkinter のキャンバス上に長方形を描画することで表現しています。

いずれかのカードをマウスでクリックすれば、そのカードが選択されてカードがめくられ、数字が表示されます。

カードが1枚選択される様子

さらに、カードを1枚選択した状態で他のカードをマウスでクリックすれば、そのカードも選択されてカードがめくられ、数字が表示されます。

もしこの時に、マウスのクリックにより選択された2枚のカードの数字が異なる場合、約1秒経過後に再びそれらのカードが裏返しされます。それらの裏返しされたカードは再びマウスでクリックして選択することが可能です。

2つのカードが異なる場合の動作

それに対し、マウスでクリックして選択された2枚のカードの数字が同じ場合、それらの選択したカードの色がグレーに変化し、プレイヤーがその2枚のカードを獲得したことになります。

2つのカードの数字が同じ場合の動作

あとは、全てのカードを獲得するためにカードを選択する操作を繰り返していくだけです。

全てのカードを獲得した際には、下の図のように画面に "GAME CLEAR!!!" が表示されます。

全てのカードを獲得したときに表示されるゲームクリア画面

以上が今回作成する神経衰弱ゲームの概要となります。

神経衰弱ゲームを作成するポイント

続いて、神経衰弱ゲームを作成する上でポイントになる点について説明しておきます。

神経衰弱ゲームの作成は簡単そうにも思えますが、この神経衰弱を GUI アプリで作成することになると結構難しいです。

特に難しいのが、キャンバス上に描画した長方形を操作して「カードの数字の表示 / 非表示」を切り替える必要があるところです。

この辺りに焦点を当てながら、神経衰弱を作成する上でのポイントについて解説していきます。

スポンサーリンク

カードを選択する

まずは「カードを選択する」動作を実現するためのポイントについて解説していきます。

選択されたカードの数字を見えるようにする

前述の通り、アプリ起動時にはカードは全て裏返しされた状態なので数字が見えない状態になっています。

ただし、マウスのクリックによりカードが選択された際にはカードがめくられることになるため、カードの数字を見えるようにする必要があります。

選択されたカードをめくる動作を示す図

今回は、このような動作を、キャンバス上に描画した長方形の「塗りつぶし色を変更する」ことで実現したいと思います。

具体的には、まずトランプのカードは「数字(テキスト)」と「長方形」の2つの図形を描画することで表現していきます。

カードが数字と長方形の2つから構成されることを示す図

アプリ起動時にはキャンバス上に数字を描画し、さらにその数字を上書きする形で長方形を描画します。この時、描画する長方形を特定の色で塗りつぶすことで、数字が長方形に覆われて見えないようにすることができます(見えなくなりますが、実際には数字はキャンバス上に存在します)。

長方形で上書きすることで数字が見えなくなる様子

さらにマウスのクリックによりカードが選択された際には、そのカードの長方形を「塗りつぶしなし」に設定します。

これにより、長方形の背面側に隠れていた数字が見えるようになり、選択したカードをめくる動作を表現することができます。

長方形を「塗りつぶしなし」に設定することで数字が見えるようになる様子

このような図形の塗りつぶし色の変更は、キャンバスの itemconfig メソッドにより実現することができます。

図形の塗りつぶし色の変更
# canvasはtkinter.Canvasクラスのインスタンス
canvas.itemconfig(図形ID or タグ名, fill="色名など")

上記の "色名など" のところを "" と空文字列にすれば、itemconfig の第1引数で指定した 図形ID or タグ名 の図形を「塗りつぶしなし」にすることができます。

また、"色名など" のところを例えば "blue" にすれば、itemconfig の第1引数で指定した 図形ID or タグ名 の図形を青色で塗りつぶすことができます。要は fill オプションを指定することにより図形の塗りつぶし色を変更する事ができます。

itemconfig のようなキャンバスに描画した図形を操作するメソッドや、メソッドに指定する 図形IDタグ名 については下記ページにまとめていますので、ご存知ない方は是非読んでみてください。これらのメソッドや 図形IDタグ名 を理解していれば、今回紹介する神経衰弱だけでなく色んなゲームを作成することができるようになります。 

tkinterキャンバスの図形の操作方法解説ページのアイキャッチTkinterの使い方:Canvasクラスで描画した図形を操作する

選択された図形 ID を取得する

ただし、上記の itemconfig の使用例でも示しているように、itemconfig メソッドを実行するためには 図形ID or タグ名 を引数に指定する必要があります。

で、itemconfig メソッドにより実現したいことは「マウスのクリックにより選択されたカードの長方形の塗りつぶし色の変更」ですので、itemconfig メソッド実行時にはクリックされた長方形の 図形ID or タグ名 が必要になることになります。

このようなマウスでクリックされた図形の 図形ID の取得は、キャンバスのメソッドである find_closest により実現することができます。

図形IDの取得
# canvasはtkinter.Canvasクラスのインスタンス

canvas.find_closest(マウスカーソルのx座標, マウスカーソルのy座標)

具体的には、事前にマウスクリック時にイベント処理が行われるように設定しておき、そのマウスクリック時に実行されるメソッドの中で find_closest を実行してクリックされた長方形の 図形ID を取得します。

さらに itemconfig メソッドにより取得した 図形ID の長方形を「塗りつぶしなし」に設定することで、カードをめくる動作を実現していきます。

カードを元に戻す

また、選択された2枚のカードの数字が異なる場合は、再びカードを裏返して元に戻す必要があります。

前述の通り、カードをめくる動作はカードの長方形を「塗りつぶしなし」に設定することで実現しているのですから、カードを裏返す操作は逆にその長方形を特定の色で塗りつぶすことで実現することができます。

ただし、このカードを裏返して元に戻す際の「タイミング」には注意が必要です。

選択された2枚のカードの数字が異なるからといってマウスで2枚目のカードがクリックされた瞬間にカードを裏返してしまうと、ユーザーがカードの数字を確認する前にカードが裏返されてしまいます。

神経衰弱は、めくられたカードの数字をいかにして記憶するかが勝敗を分けるゲームです。めくったカードの数字が確認できないと当然カードの数字の記憶をする事ができず、神経衰弱ゲームとしての面白さが損なわれてしまいます。

そのため、今回は2枚目のカードがめくられた時に2枚のカードの数字が異なる場合、ユーザーがカードの数字が確認できるように1秒経過した後にカードを裏返すようにしていきたいと思います。

このように、特定の時間が経過した後に処理を開始したいような場合、tkinter では after メソッドを利用することになります。

after メソッドについては下記ページでまとめていますので、after メソッドをご存知ない方は是非読んでみてください。

Tkinterの使い方:after で処理を「遅らせて」or 処理を「定期的」に実行する

今回は1秒経過後に裏返すようにしていきますが、もちろんマウスクリックされた際や OK ボタンを用意してボタンが押された時にカードを裏返して元に戻すようにするのでも良いと思います。

カードを獲得する

また神経衰弱ゲームでは、選択した2枚のカードが同じ数字の場合はプレイヤーがそれらの2枚のカードを獲得することになります。

この時にポイントになるのが、獲得済みのカードが「以降のゲームプレイ時に選択されないようにする」ことです。

これを実現するために、今回は獲得済みのカードに対して下記の処理を行うようにしていきます。

  • 獲得済みのカードはクリックされても反応しないようにする
  • 獲得済みのカードの色を変更して選択できないことをユーザーに示す

今回は上記のように処理を行いますが、もちろん獲得済みのカードをキャンバスから削除するようにすることでも「以降のゲームプレイ時に選択されないようにする」を実現することは可能です(こっちの方が簡単かも)。

獲得済みのカードはクリックされても反応しないようにする

獲得済みのカードが以降のゲームプレイ時に選択されないようにするための処理の1つ目は、獲得済みのカードがクリックされても反応しないようにするための処理です。

このような処理は、マウスでカードがクリックされた際に、そのクリックされたカードが獲得済みのものであれば「何もしない」ようにするだけで実現することができます。

ただ、そのためには、クリックされたカードが獲得済みであるか未獲得であるかを判断できるようにしておく必要があります。

今回は、未獲得のカードの長方形の図形 ID をリストで管理することで、この判断を行えるようにしていきたいと思います。

具体的には、アプリ起動時に全てのカードの図形 ID をリストに格納し、

並べたカードの長方形の図形IDを管理リストに追加する様子

獲得されたカードをそのリストから削除するようにすることで、未獲得のカードのみをリストで管理されるようにします。

獲得されたカードの長方形の図形IDを管理リストから削除する様子

さらにカードがクリックされた際には、そのカードに対応する長方形の図形 ID がそのリスト内に存在しない場合は「何もしない」ようにすることで、獲得済みのカードがクリックされてもアプリが反応しないようにしていきます。

管理リストに図形IDがあるか無いかで処理を切り替える様子

獲得済みのカードの色を変更して選択できないことをユーザーに示す

また、どのカードが獲得済みであるかどうかがユーザーが視覚的に判断できるよう、獲得済みのカードの色を変更するようにもしていきます。

前述の通り、カードは長方形により表現されていますので、カードが獲得されたタイミングでその長方形の塗りつぶし色を itemconfig メソッドで変更することで、この獲得済みのカードの色の変更は実現することができます。

ただ、単に塗りつぶし色を変更するだけだと獲得済みのカードの数字が見えなくなってしまいます。これは、長方形の方が数字よりも前面に描画されているからです。

長方形を塗りつぶすと数字が見えなくなってしまう様子

もちろん、数字が見えなくてもカードの色さえ変更すればユーザーに獲得済みのカードであることを視覚的に伝えることはできます。ただ、数字が見えた方がより親切かなぁと思いますので、今回は獲得済みのカードの数字も見えるようにしていきたいと思います。

前述の通り、数字が見えなくなるのは長方形よりも数字が背面側に描画されていることが理由ですので、カードが獲得済みになった際に長方形を塗りつぶした後、さらに長方形を数字よりも背面側に移動させるようにすれば、長方形を塗りつぶしても数字が見えるようにすることができます。

長方形を背面に移動することで数字が見えるようになる様子

このような、キャンバスに描画した図形を前面や背面に移動する動作は、キャンバスの lift メソッドや lower メソッドにより実現することができます。

特に図形を背面に移動する際には lower メソッドを利用します。このメソッドでは、第1引数で指定した 図形ID or タグ名 の図形を、第2引数で指定した 図形ID or タグ名 の図形の背面側に移動することができます。

図形の背面の移動
# canvasはtkinter.Canvasクラスのインスタンス

canvas.lower(図形ID or タグ名, 図形ID or タグ名)

ですので、獲得済みとなったカードの長方形の 図形ID を第1引数に指定し、数字の 図形ID or タグ名 を第2引数に指定してやることで、長方形を数字の背面側に移動することができます。

スポンサーリンク

神経衰弱ゲームのサンプルスクリプト

ここまで説明してきたポイントを踏まえて作成した神経衰弱ゲームのサンプルスクリプトは下記のようになります。

神経衰弱
import tkinter
import random

CARD_WIDTH = 50
CARD_HEIGHT = 70


class Concentration:
	def __init__(self, master):
		self.master = master

		# 選択中のカード
		self.first_card_id = None
		self.second_card_id = None

		# キャンバスのサイズ
		self.width = CARD_WIDTH*13  # 横に13枚並べるためのサイズ
		self.height = CARD_HEIGHT*4  # 縦に4枚並べるためのサイズ

		# 未獲得のカードを管理するリスト
		self.remain_card_ids = []

		self.createWidgets()  # キャンバス作成
		self.createCards()  # カードを作成
		self.layoutCards()  # カードを並べる
		self.setEvents()  # イベントの受付設定

	def createWidgets(self):
		'''アプリに必要なウィジェットを作成する'''

		self.canvas = tkinter.Canvas(
			self.master,
			width=self.width,
			height=self.height,
			bg="white",
			highlightthickness=0
		)
		self.canvas.pack()

	def createCards(self):
		'''神経衰弱に使用するカードを作成する'''

		numbers = [
			"A", "2", "3", "4", "5", "6", "7",
			"8", "9", "10", "J", "Q", "K"
		]

		# 13枚*4のカードをcardsに保持
		self.cards = [number for _ in range(4) for number in numbers]

		# カードの並びをシャッフルする
		random.shuffle(self.cards)

	def layoutCards(self):
		'''カードをキャンバス上に並べる'''

		for i, number in enumerate(self.cards):

			# 水平方向と垂直方向の位置
			h = i % 13
			v = i // 13

			# カードを表現する長方形の座標を計算
			x1 = h * CARD_WIDTH
			x2 = (h + 1) * CARD_WIDTH
			y1 = v * CARD_HEIGHT
			y2 = (v + 1) * CARD_HEIGHT

			# カードの中心に数字を描画
			self.canvas.create_text(
				x1 + CARD_WIDTH / 2, y1 + CARD_HEIGHT / 2,
				text=number,
				font=("", 40)
			)

			# 長方形を数字の上に描画して数字を隠す
			fig_id = self.canvas.create_rectangle(
				x1, y1, x2, y2,
				fill="blue",
				tag=number
			)

			# 未獲得のカードとしてリストに追加
			self.remain_card_ids.append(fig_id)

	def setEvents(self):
		'''アプリに必要なイベントの設定を行う'''

		# クリック時にfaceupCardが実行されるように設定
		self.canvas.bind("<ButtonPress>", self.selectCard)

	def selectCard(self, event):
		'''選択されたカードに対する処理'''

		# クリックされたカードに対応する図形IDを取得
		card_fig_ids = self.canvas.find_closest(event.x, event.y)
		card_fig_id = card_fig_ids[0]

		# クリックされた図形が未獲得カードでない場合は何もしない
		if not card_fig_id in self.remain_card_ids:
			return

		# 同じ図形がクリックされた場合は何もしない
		if card_fig_id == self.first_card_id:
			return

		# 取得したIDの図形を塗りつぶし無しにする(数字が見えるようになる)
		self.canvas.itemconfig(card_fig_id, fill="")

		if self.first_card_id is None:
			# 1枚目に選択したカードとして覚えておく
			self.first_card_id = card_fig_id
		else:
			# 2枚目に選択したカードとして覚えておく
			self.second_card_id = card_fig_id

			# 図形IDから表向きにされたカードの数字を取得する
			first_number = self.canvas.gettags(self.first_card_id)[0]
			second_number = self.canvas.gettags(self.second_card_id)[0]

			if first_number == second_number:
				# 選んだカードが同じ数字だった場合

				self.earnCards()
			else:
				# 選んだカードが異なる数字だった場合

				# 一時的にクリックを無効にする
				self.canvas.unbind("")

				# 1000ミリ秒後にカードを裏向きにする
				self.master.after(1000, self.reverseCards)

	def reverseCards(self):
		'''めくったカードを元に戻す'''

		# カードを表す長方形に色をつける(数字が隠れる)
		self.canvas.itemconfig(self.first_card_id, fill="blue")
		self.canvas.itemconfig(self.second_card_id, fill="blue")

		# 選択中のカードの図形IDをNoneに設定
		self.first_card_id = None
		self.second_card_id = None

		# 再度クリック時にselectCardが実行されるように設定
		self.canvas.bind("<ButtonPress>", self.selectCard)

	def earnCards(self):
		'''めくったカードを獲得済みにする'''

		# 揃ったカードの色をグレーにする
		self.canvas.itemconfig(self.first_card_id, fill="gray")
		self.canvas.itemconfig(self.second_card_id, fill="gray")

		# カードの長方形を最背面に移動する(数字が見えるようになる)
		self.canvas.lower(self.first_card_id, "all")
		self.canvas.lower(self.second_card_id, "all")

		# 未獲得カードのリストから獲得されたカードを削除する
		self.remain_card_ids.remove(self.first_card_id)
		self.remain_card_ids.remove(self.second_card_id)

		# 選択中のカードの図形IDをNoneに設定
		self.first_card_id = None
		self.second_card_id = None

		# 未獲得カードのリストに要素がなくなったらゲームクリア
		if len(self.remain_card_ids) == 0:
			self.canvas.create_text(
				self.width / 2, self.height / 2,
				font=("", 80),
				text="GAME CLEAR!!!",
				fill="red"
			)


app = tkinter.Tk()
Concentration(app)
app.mainloop()

スクリプトを起動すれば、下の図のような画面が表示され、カードをクリックすることで神経衰弱ゲームがプレイできることが確認できると思います。

神経衰弱ゲームの動作を紹介するアニメ

カードを全て獲得した場合は、下の図のように "GAME CLEAR!!!" が表示されます。

全てのカードを獲得したときに表示されるゲームクリア画面

サンプルスクリプトの解説

最後に 神経衰弱ゲームのサンプルスクリプト で紹介したスクリプトについて解説していきます。

このスクリプトでは Concentration クラスを用意し、この Concentration クラスが神経衰弱ゲームを実現するクラスとなります。

ここからは、Concentration クラスの各メソッドがどのような処理を行なっているのかについて解説していきます。

__init__

Concentration クラスのコンストラクタである __init__ では、神経衰弱を実現するために必要になる各データ属性の初期化とメソッドの実行を行なっています。

特に神経衰弱を実現していく上でポイントになる下記の3つのデータ属性の役割について解説しておきます。

  • first_card_id
  • second_card_id
  • remain_card_ids

他のデータ属性の意味合いはスクリプト中のコメントに記載しておりますので、そちらを参照していただければと思います。

first_card_idsecond_card_id

first_card_idsecond_card_id はそれぞれ1枚目に選択されたカードの長方形の図形 ID および2枚目に選択されたカードの長方形の図形 ID を記憶しておくために使用するデータ属性となります。

さらに、1枚もカードが選択されていない際には first_card_id に None を設定しておくようにすることで、これらのデータ属性は単純に図形 ID を知りたいときだけでなく、選択中のカードの枚数を知る目的でも利用できるデータ属性とする事ができます。

具体的には、選択中のカードの枚数は下記から判断できます。

  • first_card_idNone:選択中のカードは0枚
  • first_card_idNone 以外:選択中のカードは1枚

つまり、first_card_idNone の状態でカードがクリックされた場合、そのクリックは1枚目のカードを選択するために行われたと判断できますし、None 以外の状態でカードがクリックされた場合、そのクリックは2枚目のカードを選択するために行われたと判断する事ができます。

少し補足しておくと、2枚のカードが選択された時には選択中のカードは再度裏返されて元に戻される、もしくは選択中のカードは獲得されることになります。つまり、2枚のカードが選択された直後には必ずカードの選択が解除されることになります。

したがって、2枚以上のカードが選択中の状態でカードが選択されることはありません。

ですので、first_card_idsecond_card_id の2つのデータ属性のみでゲーム内で同時に選択されるカードを管理する事が可能ですし、first_card_idNone 以外の場合にカードが選択された場合、そのカードは2枚目のカードとして選択されたと言い切る事ができます。

remain_card_ids

remain_card_ids は未獲得のカードの長方形の図形 ID を管理するために使用するリストになります。

具体的には、キャンバスに長方形を描画した際に全ての長方形の図形 ID を remain_card_ids に格納しておき、カードが獲得されるたびに獲得されたカードの長方形の図形 ID を remain_card_ids から削除するようにします。これにより未獲得のカードの長方形の図形 ID のみをリストで管理する事ができます。

このようなリストを保持しておくことで、下記のような判断が行えるようになります。

  • remain_card_ids の要素数が 0:全てのカードが獲得された(ゲームクリア)
  • クリックされたカードの長方形の図形 ID が remain_card_ids 内に存在しない:すでにそのカードは獲得済み

スポンサーリンク

createWidgets

createWidgets では神経衰弱を実現するために必要になるウィジェットの作成と配置を行います。

といっても、今回用意するウィジェットはキャンバスのみとなります。

createCards

createCards では神経衰弱プレイ時に使用するカードの準備を行います。

要はジョーカーを除くトランプの52枚のカード('2''10''A', 'J', 'Q', 'K' の13枚のカードを4セット)の数字を格納したリスト cards の作成を行なっています。

ただし、単にリストを作成を行するだけだと数字の並びが固定されてしまいますので、最後に random.shuffle(self.cards) によりリスト cards の並びがランダムになるようにしています。

今回作成する神経衰弱ではカードの数字を利用しますが、カードにマークを表示したいような場合は数字とマーク両方を格納したリストを作成することになります。

layoutCards

layoutCards では、createCards メソッドで作成したカードをキャンバス上に並べて描画する処理を行います。

今回は52枚のカードを横方向に13枚・縦方向に4枚並べられるように座標の計算を行い、さらにキャンバスの create_text メソッドによってカードの「数字」を描画し、create_rectangle メソッドによってカードの「長方形」の描画を行なっています。

MEMO

今回は簡単にするために長方形を敷き詰める形で描画していますが、スペースを空けて描画した方がもうちょっと見た目は良くなると思います

create_text メソッドで描画する数字を決めるときに、createCards メソッドで作成したカードのリスト cards を利用し、このリスト cards の先頭から順番に数字を取得しながら数字の描画を行なっています。

また、create_rectangle メソッド実行時にはオプション fill="blue" を指定しているため、描画される長方形は青色で塗りつぶしされることになります。

create_text メソッドや create_rectangle メソッドのような図形を描画するメソッドや指定可能なオプションについては下記ページでまとめていますので、興味のある方は是非読んでみてください。

tkinterキャンバスに図形を描画する方法解説ページのアイキャッチTkinterの使い方:Canvasクラスで図形を描画する

また、この layoutCards のポイントは2つあります。

create_text と create_rectangle の実行順序

1つ目のポイントは create_text と create_rectangle の実行順序です。

tkinter のキャンバスでは、後から描画した図形の方が前面側に描画されることになります。layoutCards メソッドでは create_text の後に create_rectangle を実行するようにしているため、数字よりも長方形の方が前面側に描画され、数字は前面側に存在する長方形で隠れることになります(長方形が塗りつぶされているため)。

数字を取得するための長方形へのタグ付け

2つ目のポイントは長方形へのタグ付けです。

神経衰弱ゲームを実現するためには、クリックして選択された2枚のカードの数字が一致するかどうかの判断を行いながら処理を進めていく必要があります。

で、この処理を行うためにはクリックして選択されたカードの数字を何らかの方法で取得できるようにしておく必要があります。

そのために、create_rectangle 実行時にはオプション tag=number を指定するようにしています。number は長方形の背面側に描画された数字となりますので、このオプション指定により描画される長方形の背面に存在する数字がタグ付けされることになります。

さらに、キャンバスのメソッドである gettags メソッドを実行することで、引数に指定した ID の図形に付けられているタグを取得することができます。

したがって、クリックされたカードの長方形の図形 ID を引数に指定して gettags メソッドを実行すれば、その図形に付けられたタグ、すなわち長方形の背面に存在する数字を取得することができることになります。

この gettags メソッドを利用して数字を取得する処理については、後述の selectCard メソッドで行っています。

MEMO

今回はタグを利用して数字の取得を行なっていますが、辞書などで図形 ID と数字を関連づけて管理しておき、その辞書から図形 ID をキーとして数字を取得するようにするような方法も考えられます

スポンサーリンク

setEvents

setEvents では神経衰弱ゲームを実現する上で必要になるイベント処理の設定を行なっています。

今回はマウスのクリックのイベントのみを受け付けるようにし、クリックが実行された際には次に解説する selectCard が実行されるように bind メソッドを実行しています。

イベント処理について詳しく知りたい方は、下記ページで解説していますので別途参照していただければと思います。

イベント処理解説ページのアイキャッチTkinterの使い方:イベント処理を行う

selectCard

ここから紹介していく selectCardearnCardsreverseCards神経衰弱ゲームを作成するポイント で解説した内容を実現しているメソッドであり、これらのメソッドは神経衰弱を実現する上で重要なメソッドなので詳しく解説していきたいと思います。

まず selectCard はカードを選択する処理を実現するメソッドになります。前述の通り、このメソッドはマウスでクリックされたときに実行されます。

selectCard メソッドの引数 event の意味合いについては、先ほども紹介した下記のイベント処理の解説ページで説明していますので、詳しく知りたい方は下記ページをご参照いただければと思います。

イベント処理解説ページのアイキャッチTkinterの使い方:イベント処理を行う

図形 ID の取得

selectCard メソッドでは、まず最初に下記でクリックされた図形の ID の取得を行っています。

図形IDの取得
# クリックされたカードに対応する図形IDを取得
card_fig_ids = self.canvas.find_closest(event.x, event.y)
card_fig_id = card_fig_ids[0]

選択された図形 ID を取得する でも解説したように、指定した座標に一番近い図形の ID はキャンバスの find_closest メソッドにより取得することができます。

find_closest メソッドの引数にクリックされた時のマウスの座標 (event.x, event.y) を指定することで、クリックされた座標に一番近い図形の ID の取得を実現しています。

ただ、find_closest メソッドで取得できるのは単なる図形 ID ではなく、図形 ID のタプルであることに注意してください。このため selectCard では、find_closest メソッドの返却値 card_fig_ids ではなく、タプルの先頭要素 card_fig_ids[0] を図形 ID として使用して処理を行うようにしています。

また、もし取得した図形 ID がリスト remain_card_ids に存在しない場合は、その図形はカードを表現する長方形の図形 ID ではない or そのカードはすでに獲得済みということになりますので、その場合は return のみ行なってメソッドを終了するようにしています。

選択されたカードが獲得済みの場合
# クリックされた図形が未獲得カードでない場合は何もしない
if not card_fig_id in self.remain_card_ids:
	return

さらに、取得した図形 ID が first_card_id と一致する場合は、そのカードは1枚目に選択されたカードと同じカードですので、この場合も return のみ行なってメソッドを終了するようにしています。

選択されたカードが1枚目と同じ場合
# 同じ図形がクリックされた場合は何もしない
if card_fig_id == self.first_card_id:
	return

カードをめくる(数字を表示する)

図形 ID 取得後は、下記の処理によりクリックされたカードの長方形を「塗りつぶしなし」に設定しています。

長方形を塗りつぶしなしに設定
# 取得したIDの図形を塗りつぶし無しにする(数字が見えるようになる)
self.canvas.itemconfig(card_fig_id, fill="")

選択されたカードの数字を見えるようにする で説明したように、キャンバスに描画した図形の塗りつぶし色は、fill オプションを指定して itemconfig メソッドを実行することにより変更することができます。

ここでカードを「塗りつぶしなし」に設定することで、カードの背面に存在する数字が見えるようになり、カードをめくったような動作を実現することができます。

長方形を「塗りつぶしなし」に設定することで数字が見えるようになる様子

何枚目のカードが選択されたかを判断

続いて、マウスのクリックにより選択されたカードが何枚目のものであるかの判断を行います。

__init__ でも解説したように、現在何枚のカードが選択されているかはデータ属性 first_card_idNone or None 以外のどちらであるかを調べることで判断することができます。

具体的には、selectCard メソッドの下記部分で現在選択中のカードが0枚 or 1枚のどちらであるか判断し、0枚である場合はクリックされたカードを「1枚目に選択されたカード」として長方形の図形 ID を first_card_id に、1枚である場合はクリックされたカードを「2枚目に選択されたカード」として長方形の図形 ID を second_card_id に設定するようにしています。

選択されたカードの枚数に応じた処理
if self.first_card_id is None:
	# 1枚目に選択したカードとして覚えておく
	self.first_card_id = card_fig_id
else:
	# 2枚目に選択したカードとして覚えておく
	self.second_card_id = card_fig_id

2枚のカードの数字の取得

さらに、選択されたカードが2枚目の場合は、選択中の2枚のカードの数字が同じであるかどうかの判断を行なっています。

layoutCards で解説したように、カードの長方形にはそのカードの数字がタグ付けされていますので、そのタグを取得すればカードの数字を取得することができることになります。

具体的には、このカードの数字の取得は selectCard メソッドの下記部分で行っています。

選択されたカードの数字の取得
# 図形IDから表向きにされたカードの数字を取得する
first_number = self.canvas.gettags(self.first_card_id)[0]
second_number = self.canvas.gettags(self.second_card_id)[0]

gettags も返却値がタグそのものではなく、タグのタプルであることに注意してください。

2枚のカードの数字に応じた処理

さらに、取得したカードの数字が同じであるかどうかを判断し、同じである場合は後述の earnCards メソッドによりカードを獲得する処理を行います。

その一方で、取得したカードの数字が異なる場合は、一旦マウスクリックイベントの受付を停止し、1秒後に reveseCards メソッドを実行して選択してめくられたカードを再び裏返して元に戻す処理を行っています。

これらの処理は selectCards メソッドの下記部分で行っています。

選択された2枚のカードの数字に応じた処理
if first_number == second_number:
	# 選んだカードが同じ数字だった場合

	self.earnCards()
else:
	# 選んだカードが異なる数字だった場合

	# 一時的にクリックを無効にする
	self.canvas.unbind("ButtonPress")

	# 1000ミリ秒後にカードを裏向きにする
	self.master.after(1000, self.reverseCards)

わざわざ after メソッドを利用して1秒後に reveseCards メソッドを実行するようにしている理由は カードを元に戻す で解説した通りですし、カードを元に戻すまでは次のカードが選択されないよう、マウスクリックのイベントの受付を停止するようにしています。

マウスクリックのイベントの受付の再開は、reveseCards メソッドでカードを再度裏返す際に行います。

earnCards

earnCards はカードを獲得する動作を実現するためのメソッドで、選択した2枚のカードの数字が同じだった場合に selectCards から呼び出されるメソッドになります。主に、神経衰弱ゲームを作成するポイントカードを獲得する で解説した内容の動作を行います。

カードを獲得する動作といっても、今回はポイント加算などは行わず、単に獲得されたカードが今後選択できないようにするための処理と、カードが「獲得済み」であることが分かるようにカードの色の変更のみを行います。

earnCards メソッドでは、まずカードが獲得済みであることが分かるように、獲得された2枚のカードの長方形の塗りつぶし色を下記で "gray" に変更します。

獲得済みカードの塗りつぶし色変更
# 揃ったカードの色をグレーにする
self.canvas.itemconfig(self.first_card_id, fill="gray")
self.canvas.itemconfig(self.second_card_id, fill="gray")

ただし、カードを獲得する で解説したように単に塗りつぶし色を変更するだけだと長方形の背面側にある数字が見えなくなってしまいますので、下記で長方形を最背面に移動する処理を行なっています。

カードの長方形の背面への移動
# 数字の裏側にカードの長方形を移動する
self.canvas.lower(self.first_card_id, "all")
self.canvas.lower(self.second_card_id, "all")

上記により長方形が数字の背面に移動し、数字が見えるようになります。

長方形を背面に移動することで数字が見えるようになる様子

さらに、下記で未獲得のカードの長方形の ID を管理するリスト remain_card_ids から獲得されたカードの長方形の図形 ID を削除しています。

獲得済みカードの管理リストからの削除
# 未獲得カードのリストから獲得されたカードを削除する
self.remain_card_ids.remove(self.first_card_id)
self.remain_card_ids.remove(self.second_card_id)

selectCard で解説した通り、selectCard メソッドはマウスでクリックされたカードの長方形の図形 ID が remain_card_ids に存在しない場合、カードを選択する処理が行われないようになっています。

そのため、上記の処理実行以降は獲得済みのカードの長方形がクリックされても何も処理が行われないようになり、獲得済みのカードが再度選択されてしまうことを防ぐことができます。

また、カード獲得によってプレイヤーのカードの選択が解除されるよう、下記のように選択中のカードを None に設定するようにしています。

カードの選択解除
# 選択中のカードの図形IDをNoneに設定
self.first_card_id = None
self.second_card_id = None

最後に、もし未獲得のカードがなくなった場合、すなわちリスト remain_card_ids の要素数が 0 になった場合はゲームクリアしたことになりますので、この場合には下記のようにキャンバスの create_textメソッドで "GAME CLEAR!!!" の文字列を描画するようにしています。

ゲームクリアの表示
# 未獲得カードのリストに要素がなくなったらゲームクリア
if len(self.remain_card_ids) == 0:
	self.canvas.create_text(
		self.width / 2, self.height / 2,
		font=("", 80),
		text="GAME CLEAR!!!",
		fill="red"
	)

スポンサーリンク

reverseCards

reverseCards は選択してめくられたカードを再び裏返して元に戻す動作を実現するためのメソッドで、選択された2枚のカードの数字が同じでなかった場合に selectCards から 1000 ms 後に呼び出されるメソッドになります。

reverseCards メソッドでは最初にカードの長方形を選択前の状態、すなわちカードの長方形の塗りつぶし色を "blue" に設定しています。

選択カードの塗りつぶし色の変更
# カードを表す長方形に色をつける(数字が隠れる)
self.canvas.itemconfig(self.first_card_id, fill="blue")
self.canvas.itemconfig(self.second_card_id, fill="blue")

さらに、カードを元に戻した事によってプレイヤーのカードの選択が解除されるよう、下記のように選択中のカードを None に設定するようにしています。

カードの選択解除
# 選択中のカードの図形IDをNoneに設定
self.first_card_id = None
self.second_card_id = None

最後に、selectCardsunbind メソッドにより一旦停止されたマウスクリックイベントの受付を、下記で再度受付を行うよう bind メソッドを実行しています。

マウスクリックイベントの受付再設定
# 再度クリック時にselectCardが実行されるように設定
self.canvas.bind("<ButtonPress>", self.selectCard)

以上が神経衰弱ゲームの作り方の解説となります。

今回作成した神経衰弱は完全に一人プレイ用のものでしたが、コンピュータと対戦できるように発展させることでより面白いゲームに仕立てていく事ができます。

この時のコンピュータ側の動作の実現方法については、例えば下記の「Pythonで作って学べるゲームのアルゴリズム入門」が参考になると思います。

この参考書でも神経衰弱のゲームの作り方が解説されているのですが、この参考書では神経衰弱ゲームにおけるコンピュータ側の動作を実現するための方法(コンピュータの動作アルゴリズム)についても重点的に解説されています。

他にも三目並べやオセロゲームにおけるコンピュータ側の動作アルゴリズムについても解説されておりますので、一人プレイ用のゲームだけでなく、コンピュータと対戦可能なゲームを作成してみたい人にオススメの入門書になります(アルゴリズムの考え方の入門書としてもオススメです)。

まとめ

このページでは、Python で tkinter を用いた「神経衰弱ゲーム」の作り方について解説しました!

カードの数字を表示したり隠したりするあたりの処理がポイントであり、神経衰弱ゲームの作成で一番難しいところだと思います。

神経衰弱ゲームは簡単に作れそうにも見えますが、意外といろんなことを考えながら実装する必要があって驚いた方もおられるのでは無いでしょうか?

今回紹介した神経衰弱ゲーム同様に、アプリやゲームは簡単そうに見えても実際に作ってみると結構難易度が高いことも多いです。私も結構この神経衰弱を作る時に悩みました…。

何が言いたいかというと、簡単そうに思えるアプリやゲームでも実際に作ってみることで多くの事を学ぶ事ができます。

自身で実際に作りながら悩んだり調べたりしていくことでプログラミングの力を伸ばす事ができますので、是非色んなアプリやゲームを実際に作ってみることに挑戦してみてください!

特にトランプゲームを作成するためのヒントはこのページにもあると思いますので、神経衰弱の作り方を理解したら、次は違うトランプゲームの作成に挑戦してみましょう!

オススメ参考書

簡単なアプリやゲームを作りながら Python について学びたいという方には、下記の Pythonでつくる ゲーム開発 入門講座 がオススメです!ちなみに私が Python を始めるときに最初に買った書籍です!

下記ようなゲームを作成しながら Python の基本が楽しく学べます!素材もダウンロードして利用できるため、作成したゲームの見た目にも満足できると思います。

  • すごろく
  • おみくじ
  • 迷路ゲーム
  • 落ち物パズル
  • RPG

また本書籍は下記のような構成になっているため、Python 初心者でも内容を理解しやすいです。

  • プログラミング・Python の基礎から解説
  • 絵を用いた解説が豊富
  • ライブラリの使い方から解説(tkitner と Pygame)
  • ソースコードの1行1行に注釈

ゲーム開発は楽しくプログラミングを学べるだけでなく、ゲームで学んだことは他の分野のプログラミングにも活かせるものが多いですし(キーボードの入力受付のイベントや定期的な処理・画像や座標を扱い方等)、逆に他の分野のプログラミングで学んだ知識を活かしやすいことも特徴だと思います(例えばコンピュータの動作に機械学習を取り入れるなど)。

プログラミングを学ぶのにゲーム開発は相性抜群だと思います。

Python の基礎や tkinter・Pygame の使い方をご存知なのであれば、下記の 実践編 をいきなり読むのもアリです。

実践編 では「シューティングゲーム」や「アクションゲーム」「3D カーレース」等のより難易度の高いゲームを作りながらプログラミングの力をつけていくことができます!

また、単にゲームを作るのではなく、対戦相手となるコンピュータの動作のアルゴリズムにも興味のある方は下記の「Pythonで作って学べるゲームのアルゴリズム入門」がオススメです。

この本はゲームのコンピュータ(AI)の動作アルゴリズム(思考ルーチン)に対する入門解説本になります。例えばオセロゲームにおけるコンピュータが、どのような思考によって石を置く場所を決めているか等の基本的な知識を得ることが出来ます。

プログラミングを挫折せずに続けていくためには楽しさを味わいながら学習することが大事ですので、特にゲームに興味のある方は、この辺りの参考書と一緒に Python を学んでいくのがオススメです!

同じカテゴリのページ一覧を表示