今日も見に来てくださって、ありがとうございます。石川さんです。
今回紹介する数字合わせは、先日読んだ書籍に、作業記憶と注意力を鍛え、認知プロセスをスピードアップする練習として紹介されていました。練習を積むと、直観が信じられるようになるとか。作業記憶が鍛えられると一度見たものをもう一度確認しなくても済むようになるそうです。作業記憶と、注意力、鍛えたいですねぇ。
実際は、トランプをつかってやるのですけど、せっかくtkinterでプログラムが作れるので、ちょっと作ってみることにしました。やり方は、神経衰弱と同じです。実行するとカードが4×3の12枚配られた状態になります。クリックするとマークが表示されます。2枚ずつクリックして、数字を一致させます。
できあがりイメージ
ソースコード
from tkinter import Canvas, Tk, BOTH, ALL from tkinter import messagebox from math import sqrt, ceil, floor from random import sample from collections import namedtuple Suit = namedtuple('Suit',['figure','color','text']) Club = Suit("\u2663","#000000","Club") Heart = Suit("\u2665","#FF0000","Heart") Spade = Suit("\u2660","#000000","Spade") Diamond = Suit("\u2666","#FF0000","Diamond") Suits = [Club,Heart,Spade,Diamond] Rank = ["A","2","3","4","5","6","7","8","9","10","J","Q","K"] Card = namedtuple('Card',['suit','rank']) N = 6 class NumberMaching(Tk): def __init__(self): super().__init__() self.title("Number Maching") self.state("zoomed") self.cards = [Card(suit, r) for r in Rank[:N] for suit in Suits[:2]] self.canvas = Canvas(self) self.canvas.pack(expand=True,fill=BOTH) self.refresh_cards() def refresh_cards(self): self.canvas.delete(ALL) w, h = self.winfo_screenwidth(), self.winfo_screenheight() width, height = ceil(sqrt(N*2)), floor(sqrt(N*2)) if N*2 > width * height: height += 1 wm, hm = w // (width*2), h // (height*2) # width margin, height margin self.q = sample(self.cards,len(self.cards)) self.items = [None] * len(self.q) self.answers = [] self.closing = False self.tapped = 0 for i in range(height): for j in range(width): n = i*width + j if n >= N*2: break figure = self.q[n].suit.figure + self.q[n].rank color = self.q[n].suit.color x = j*((w-wm)//width) + wm y = i*((h-wm)//height) + hm item = self.canvas.create_text(x,y,text=figure,fill=color,font=("",60),tags="card") rect = self.get_rectangle(x,y,(w-wm)//width*.9,(h-wm)//height*.9) carditem = self.canvas.create_rectangle(rect,fill="white",tags="card") self.items[n] = [item, carditem, self.q[n].rank] # text, rectangle, rank self.canvas.tag_bind("card","<Button-1>",self.card_tapped) def get_rectangle(self,center_x,center_y,width,height): leftx = center_x - width // 2 topy = center_y - height // 2 rightx = center_x + width // 2 bottomy = center_y + height //2 return (leftx,topy,rightx,bottomy) def card_tapped(self,event): if self.closing: return self.tapped += 1 item = self.canvas.find_closest(event.x,event.y) for n, a in enumerate(self.items): if a[0] == item[0] or a[1] == item[0]: break if n > N * 2: return isopen = False for i in self.answers: if i == n: isopen = True if isopen: if self.answers[-1] == n: if self.items[self.answers[-1]][2] == self.items[answers[-2]][2]: pass #すでに正解しているときは、閉じない else: self.canvas.tag_raise(a[1]) self.answers.pop() else: self.canvas.tag_lower(a[1]) self.answers.append(n) if len(self.answers) % 2 == 0: if self.items[self.answers[-1]][2] == self.items[self.answers[-2]][2]: # 同じ数字が選択されました。 if len(self.answers) == len(self.items): message_string = ("トレーニング終了です。\n" "問題数:" + str(N) + "\n" "タップ数:" + str(self.tapped) + "\n\n" "もう一度、やりますか?") if messagebox.askyesno("Congraturation!", message_string): self.refresh_cards() else: self.destroy() else: self.after(500, self.close_card) # 違う数字が選択されました。 self.closing = True def close_card(self): if len(self.answers) < 2: return self.canvas.tag_raise(self.items[self.answers[-1]][1]) self.answers.pop() self.canvas.tag_raise(self.items[self.answers[-1]][1]) self.answers.pop() self.closing = False if __name__ == '__main__': numberMaching = NumberMaching() numberMaching.mainloop()
説明
もっと簡単にできると思っていましたが、意外と作るのに時間がかかってしまいました。まず、カードのマークをどうしようかなぁ、ということで、グーグル先生に相談してみました。Unicodeにトランプのマークがあったので、それを利用することにしました。初期データは、namedtuple
をつかって作ることにしました。7~14行目と22行目です。あと、カードをシャッフルするのは、random
モジュールからsample
をつかっています。35行目です。順番を入れ替えるだけならshuffle
でも同じように動きます。sample
とshuffle
の違いは、sample
は新しいリストをつくるのに対して、shuffle
は指定されたリストの順序を入れ替える、という点です。
画面のカードを描画する部分は再実行したいときに、呼び出せるようにrefresh_cards()
メソッドにまとめました。28~54行目です。キャンバス上にcreate_text()
でカードのマークを描画して、その上からcreate_rectangle()
で白い長方形を描画してカードを表現しました。これらの項目を作成するときに、"card"
タグをセットして、クリックしたときに何らかの処理ができるよう、card_tapped()
メソッドをバインドしました。
めくったカードの正解、不正解については、めくられたカードを保存するようにリストを使うことにしました。37行目のself.answers = []
がその宣言部分です。カードがめくられるたびに、このanswers
にappend()
で追加していきますが、追加したあとの判定で、めくった枚数が奇数のときはなにもしない、偶数のときは二枚目がめくられたということで、チェックするようにしました。
チェックは、rank
が同じなら数値が一致したということで何もしないのですが、不一致の場合は、カードを元通りにもどす、ということをやるようにしました。すべてめくられたら、終了メッセージを出力しています。
まとめ
もっと簡単にできるのかと思っていましたが、意外と時間がかかってしまった原因は、カードの位置決め部分に汎用性を持たせて、N=6以外にも実行できるようにしたため、ちょっと難しくなってしまった、というところが1点目ですね。あと、当たり判定するためのデータ構造をどうしようか、と、迷いながら進めていたというところが2点目ですね。先に仕様を固めてからつくればよかったですね。小さいプログラムだからと油断し過ぎました。
作業記憶と注意力、鍛えましょう!!!