【Python】マッチ棒クイズ開発 Part20
こんにちは、にわこまです。
今回は、pythonファイルとkvファイルを使ってマッチ棒枠とマッチ棒を動かす画面を作成します。マッチ棒を動かす画面とはPlayFieldWidgetのことです。
誤字脱字や分からない点がございましたらご連絡お願いいたします!
メールまたはTwitterのDMまで!!
マッチ棒クイズ開発の目次はこちら
スポンサードサーチ
マッチ棒枠
マッチ棒枠はマッチ棒を移動先となるものです。薄いグレーでマッチ棒を表現したクラスです。マッチ棒と違い関数は必要ありません。
基本的は定義は全てkvファイルに記述します。
コードの提示
kvファイルでマッチ棒枠クラスを表現するソースコードを以下に示します。
# -*- coding: utf-8 -*-
#:import np numpy
<MatchStickWidget>:
# 省略
<MatchStickFrameWidget>:
size: [0, 0]
ms_w: 25
angle: 0
canvas:
PushMatrix:
Rotate:
group: "rot"
angle: self.angle
origin: (self.x + self.ms_w / 2, self.y + self.ms_w * 6.8 / 2)
Color:
rgb: [0.95, 0.95, 0.95]
Rectangle:
pos: [self.x+self.ms_w*0.2, self.y]
size: [self.ms_w * 0.6, self.ms_w * 5.6]
Ellipse:
pos: [self.x, self.y+self.ms_w * 5.2]
size: [self.ms_w, self.ms_w * 1.6]
PopMatrix:
<RootWidget>:
canvas:
Color:
rgb: [1, 1, 1]
Rectangle:
pos: self.pos
size: self.size
pythonファイルでマッチ棒枠クラスを表現するソースコードを以下に示します。
import kivy
kivy.require('2.0.0')
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.lang import Builder
Builder.load_file('matchstickquiz_V2001.kv')
import numpy as np
class MatchStickWidget(Widget):
# 省略
class MatchStickFrameWidget(Widget):
def __init__(self, ms_w, angle, **kwargs):
super(MatchStickFrameWidget, self).__init__(**kwargs)
self.ms_w = ms_w
self.angle = angle
self.rot = self.canvas.get_group("rot")[0]
class RootWidget(FloatLayout):
pass
class MatchStickQuizApp(App):
def build(self):
root = RootWidget()
root.add_widget(MatchStickFrameWidget(25, 0))
return root
if __name__ == '__main__':
MatchStickQuizApp().run()
pass
上記のコードは以下からダウンロードできます。
【Python】マッチ棒クイズ開発 Part20 ソースコード01
コードの解説 kvファイル
7行目から25行目の「MatchStickFrameWidget」は、マッチ棒枠を表現するクラスです。
8行目の「size」は、マッチ棒枠のサイズを保持する変数です。横幅縦幅それぞれ0を代入しています。
9行目の「ms_w」は、マッチ棒枠の横幅を保持する変数です。初期値として25を代入しています。
10行目の「angle」は、マッチ棒枠の角度を保持する変数です。初期値として0を代入しています。
11行目の「canvas」は、canvasオブジェクトを宣言しています。この宣言以降でインデントを1つ下げるとcanvasへ追加するという扱いになります。
12行目の「PushMatrix:」は、回転する範囲の始まりを宣言しています。後述するPopMatrixとで囲まれたオブジェクトは一緒に回転します。
13行目の「Rotate」は、回転するために必要なオブジェクトです。角度や回転の中心を設定します。
14行目の「group: ‘rot’」は、Rotateオブジェクトにrotグループという属性を付与しています。pythonファイル側からRotateオブジェクトを呼び出す時に使用します。
15行目の「angle」は、角度を保持する変数です。
16行目の「origin」は、回転の中心を保持位する変数です。計算はPart7で解説しています。
17行目の「Color」は、後述するRectangleとEllipseの色を設定するオブジェクトです。
18行目では、色を薄いグレーに設定しています。
19行目の「Rectangle」は、マッチ棒枠の棒部分を表現するオブジェクトです。
20行目の「pos」は、マッチ棒の棒部分の座標を保持する変数です。
21行目の「size」は、マッチ棒の棒部分のサイズを保持する変数です。
22行目の「Ellipse」は、マッチ棒の火薬部分を表現するオブジェクトです。
23行目の「pos」は、マッチ棒の火薬部分の座標を保持する変数です。
24行目の「size」は、マッチ棒の火薬部分のサイズを保持する変数です。
25行目の「PopMatrix」は、回転する範囲の終わりを宣言しています。前述したPushMatrixとで囲まれたオブジェクトは一緒に回転します。
コードの解説 pythonファイル
pythonに書かれているコードは、ほとんどこれまでのPartで解説しているため、新しく記述したコードや気を付けて欲しいコードについて解説します。
16行目から21行目の「MatchStickFrameWidgetクラス」は、マッチ棒枠を表現するクラスです。
17行目から21行目の「init関数」は、マッチ棒枠を初期化しています。
18行目の「super関数」は、Widgetクラスのinit関数を実行しています。
19行目の「ms_w」は、マッチ棒の横幅を保持する変数です。kvファイルにて初期値を25としていますが、こちらに代入した値が優先されます。
20行目の「self.angle」は、マッチ棒の角度を保持する変数です。kvファイルにて初期値を0としていますが、こちらに代入した値が優先されます。
21行目の「self.rot = self.canvas.get_group(“rot”)[0]」は、self.rotにkvファイルで宣言したRotateオブジェクトを代入しています。
get_group関数はcanvas上のオブジェクトでrotというグループが付与されている全てのオブジェクトをリストで取得しています。
rotというグループが付与されているオブジェクトはRotateしかないため、0番目を指定してRotateオブジェクトを取得しています。
動作確認
コードを保存したら、コマンドプロンプトを開きファイルを保存したフォルダまで移動します。移動したら、以下のコマンドを入力し実行します。
python matchstickquiz.py
以下のように左下に薄いグレーでマッチ棒が表示されていれば動作確認完了です。
スポンサードサーチ
マッチ棒を動かす画面
ここでは、マッチ棒枠を表示したりマッチ棒を問題の通りに表示したりします。
以下の画像における薄い赤色の範囲を作成します。
コードの提示
kvファイルでマッチ棒枠クラスを表現するソースコードを以下に示します。
# -*- coding: utf-8 -*-
#:import np numpy
<MatchStickWidget>:
# 省略
<MatchStickFrameWidget>:
# 省略
<PlayFieldWidget>:
ms_w: 25
ms_h: self.ms_w * 6.8
base_x: self.center_x - (45 * self.ms_w) / 2
base_y: self.center_y - (17 * self.ms_w) / 2
frameList: []
msList: []
msDict: {}
qaDict: {}
quesList: []
ansList: []
input_ansList: []
on_size: self.drawFrame(), self.drawQuestion()
canvas:
Color:
rgb: [1, 1, 1]
Rectangle:
pos: self.pos
size: self.size
<RootWidget>:
canvas:
Color:
rgb: [1, 1, 1]
Rectangle:
pos: self.pos
size: self.size
pythonファイルでマッチ棒枠クラスを表現するソースコードを以下に示します。
import kivy
kivy.require('2.0.0')
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.storage.jsonstore import JsonStore
from kivy.lang import Builder
Builder.load_file('matchstickquiz.kv')
import numpy as np
import random
import copy
class MatchStickWidget(Widget):
# 省略
class MatchStickFrameWidget(Widget):
# 省略
class PlayFieldWidget(FloatLayout):
def __init__(self, **kwargs):
super(PlayFieldWidget, self).__init__(**kwargs)
self.onMoveObj = None
def setQA(self, *args):
for ms in list(self.msDict.keys()):
self.remove_widget(ms)
self.msDict = {}
self.onMoveObj = None
ans = random.choice(list(self.QADict.keys()))
self.quesList = random.choice(self.QADict[ans]).split(" ")
self.ansList = ans.split(" ")
self.input_ansList = []
self.drawQuestion()
def calcPos(self, base_x, base_y, k, *args):
# base (xが若い順 -> yが若い順)
base_num_pos = [(base_x, base_y+self.ms_w, 0), (base_x, base_y+self.ms_w+self.ms_h+self.ms_w, 0),
(base_x+self.ms_w+self.ms_h/2-self.ms_w/2, base_y-self.ms_h/2+self.ms_w/2, 90),
(base_x+self.ms_w+self.ms_h/2-self.ms_w/2, base_y+self.ms_w+self.ms_h+self.ms_w/2-self.ms_h/2, 90),
(base_x+self.ms_w+self.ms_h/2-self.ms_w/2, base_y+self.ms_w+self.ms_h+self.ms_w+self.ms_h+self.ms_w/2-self.ms_h/2, 90),
(base_x+self.ms_w+self.ms_h, base_y+self.ms_w, 0), (base_x+self.ms_w+self.ms_h, base_y+self.ms_w+self.ms_h+self.ms_w, 0)]
# sign
plus_sign_pos = [(base_x+self.ms_w+self.ms_h/2-self.ms_w/2, base_y+self.ms_w+self.ms_h+self.ms_w/2-self.ms_h/2, 0),
(base_x+self.ms_w+self.ms_h/2-self.ms_w/2, base_y+self.ms_w+self.ms_h+self.ms_w/2-self.ms_h/2, 90)]
minus_sign_pos = [(base_x+self.ms_w+self.ms_h/2-self.ms_w/2, base_y+self.ms_w+self.ms_h+self.ms_w/2-self.ms_h/2, 90)]
equal_sign_pos = [(base_x+self.ms_w+self.ms_h/2-self.ms_w/2, base_y+self.ms_w+self.ms_h-(self.ms_h/2-self.ms_w)/2-self.ms_h/2, 90),
(base_x+self.ms_w+self.ms_h/2-self.ms_w/2, base_y+self.ms_w+self.ms_h+self.ms_w+(self.ms_h/2-self.ms_w)/2-self.ms_h/2, 90)]
# number
zero_num_pos = base_num_pos[0:3] + base_num_pos[4:]
one_num_pos = base_num_pos[5:]
two_num_pos = [base_num_pos[i] for i in [0, 2, 3, 4, 6]]
three_num_pos = base_num_pos[2:]
four_num_pos = [base_num_pos[i] for i in [1, 3, 5, 6]]
five_num_pos = base_num_pos[1:6]
six_num_pos = base_num_pos[:-1]
seven_num_pos = [base_num_pos[1]] + base_num_pos[4:]
eight_num_pos = base_num_pos
nine_num_pos = base_num_pos[1:]
pos_dict = {"base":base_num_pos, "+":plus_sign_pos, "-":minus_sign_pos, "=":equal_sign_pos,
"0":zero_num_pos, "1":one_num_pos, "2":two_num_pos, "3":three_num_pos, "4":four_num_pos,
"5":five_num_pos, "6":six_num_pos, "7":seven_num_pos, "8":eight_num_pos, "9":nine_num_pos}
return pos_dict[k]
def drawFrame(self, *args):
if(len(self.frameList) == 0):
for idx, k in enumerate(["base", "+", "base", "=", "base"]):
pos_list = self.calcPos(self.base_x+idx*(self.ms_w+self.ms_h+self.ms_w), self.base_y, k)
for pos_t in pos_list:
obj = MatchStickFrameWidget(pos=[pos_t[0], pos_t[1]], ms_w=self.ms_w, angle=pos_t[2])
self.frameList.append(obj)
self.add_widget(obj)
elif(len(self.frameList) != 0):
c = 0 # index of self.frameList
for idx, k in enumerate(["base", "+", "base", "=", "base"]):
pos_list = self.calcPos(self.base_x+idx*(self.ms_w+self.ms_h+self.ms_w), self.base_y, k)
for pos_t in pos_list:
obj = self.frameList[c]
obj.ms_w = self.ms_w
obj.pos = [pos_t[0], pos_t[1]]
obj.angle = pos_t[2]
c += 1
def drawQuestion(self, *args):
if(len(self.msDict) == 0):
for idx, k in enumerate(self.quesList):
pos_list = self.calcPos(self.base_x+idx*(self.ms_w+self.ms_h+self.ms_w), self.base_y, k)
for pos_t in pos_list:
obj = MatchStickWidget(pos=[pos_t[0], pos_t[1]], ms_w=self.ms_w, angle=pos_t[2])
self.msDict[obj] = ([pos_t[0], pos_t[1]], pos_t[0], pos_t[1], pos_t[2])
self.add_widget(obj)
elif(len(self.msDict) != 0):
tempmsList = list(self.msDict.keys())
c = 0 # index of tempmsList
for idx, k in enumerate(self.quesList):
pos_list = self.calcPos(self.base_x+idx*(self.ms_w+self.ms_h+self.ms_w), self.base_y, k)
for pos_t in pos_list:
obj = tempmsList[c]
obj.ms_w = self.ms_w
obj.pos = [pos_t[0], pos_t[1]]
obj.angle = pos_t[2]
self.msDict[obj] = ([pos_t[0], pos_t[1]], pos_t[0], pos_t[1], pos_t[2])
c += 1
def drawAnswer(self, *args):
# 「〇 + ◇ = △」と「〇 - ◇ = △」の場合のみを考える
tempmsList = list(self.msDict.keys())
c = 0 # index of tempmsList
for idx, k in enumerate(self.ansList):
pos_list = self.calcPos(self.base_x+idx*(self.ms_w+self.ms_h+self.ms_w), self.base_y, k)
for pos_t in pos_list:
obj = tempmsList[c]
obj.pos = [pos_t[0], pos_t[1]]
obj.angle = pos_t[2]
self.msDict[obj] = ([pos_t[0], pos_t[1]], pos_t[0], pos_t[1], pos_t[2])
c += 1
def checkAnswerFML(self, *args):
posTupleList = sorted([ms.pos for ms in list(self.msDict.keys())])
self.input_ansList = []
for idx, k in enumerate(self.ansList):
pos_x_min = self.base_x+idx*(self.ms_w+self.ms_h+self.ms_w)
pos_x_max = self.base_x+(idx+1)*(self.ms_w+self.ms_h+self.ms_w)
posList = [t for t in posTupleList if(pos_x_min <= t[0] and t[0] < pos_x_max)]
lng_posList = len(posList) # マッチ棒が何本使われているか数える
if(lng_posList == 1 and posList == [[t[0], t[1]] for t in self.calcPos(pos_x_min, self.base_y, "-")]):
self.input_ansList.append("-")
elif(lng_posList == 2):
for i in ["1", "+", "="]:
if(posList == [[t[0], t[1]] for t in self.calcPos(pos_x_min, self.base_y, i)]):
self.input_ansList.append(i)
elif(lng_posList == 4):
for i in ["4", "7"]:
if(posList == [[t[0], t[1]] for t in self.calcPos(pos_x_min, self.base_y, i)]):
self.input_ansList.append(i)
elif(lng_posList == 5):
for i in ["2", "3", "5"]:
if(posList == [[t[0], t[1]] for t in self.calcPos(pos_x_min, self.base_y, i)]):
self.input_ansList.append(i)
elif(lng_posList == 6):
for i in ["0", "6", "9"]:
if(posList == [[t[0], t[1]] for t in self.calcPos(pos_x_min, self.base_y, i)]):
self.input_ansList.append(i)
elif(lng_posList == 7 and posList == [[t[0], t[1]] for t in self.calcPos(pos_x_min, self.base_y, "8")]):
self.input_ansList.append("8")
if(self.input_ansList == self.ansList):
return True
return False
def checkOnMoveObj(self, obj, *args):
tof_None = True if(self.onMoveObj == None) else False
tof_obj = True if(self.onMoveObj == obj) else False
tof_pos = True if(self.msDict[obj][0] == obj.pos) else False
tof_angle = True if(self.msDict[obj][3] == obj.angle % 180) else False
if(tof_None):
self.onMoveObj = obj
elif(not(tof_None) and not(tof_obj) and (not(tof_pos) or not(tof_angle))):
obj.pos = self.msDict[obj][0]
obj.angle = self.msDict[obj][3]
elif(not(tof_None) and tof_obj and tof_pos and tof_angle):
self.onMoveObj = None
def on_touch_down(self, touch):
for idx, obj in enumerate(self.children):
tof = obj.on_touch_down(touch) if(obj == self.onMoveObj or self.onMoveObj == None) else False
if(tof and idx != 0):
# Here is bringing the clicked object to the foreground.
self.remove_widget(obj)
self.add_widget(obj)
break
elif(tof and idx == 0 and touch.is_double_tap):
# Here is rotating the object of foreground.
self.checkOnMoveObj(obj)
break
def on_touch_up(self, touch):
if(len(self.children) != 0 and type(self.children[0]) == MatchStickWidget):
obj = self.children[0]
obj.on_touch_up(touch)
tofList = list(map(obj.collide_center_circle, self.frameList)) # MatchStickFrameWidget and MatchStickWidget
tof_obj_frame = True in tofList
frame = self.frameList[tofList.index(True)] if(tof_obj_frame) else None
tof_obj_obj = True in list(map(obj.collide_center_circle, list(self.msDict.keys())))
if(tof_obj_obj):
obj.pos = copy.copy(obj.before_move_pos)
elif(not(tof_obj_obj) and tof_obj_frame):
obj.pos = copy.copy(frame.pos)
obj.before_move_pos = copy.copy(frame.pos)
elif(not(tof_obj_obj) and not(tof_obj_frame)):
obj.before_move_pos = copy.copy(obj.pos)
self.checkOnMoveObj(obj)
class RootWidget(FloatLayout):
pass
class MatchStickQuizApp(App):
def build(self):
root = RootWidget()
pfw = PlayFieldWidget()
pfw.drawFrame()
pfw.QADict = JsonStore("./matchstickquiz_QA2.json")
pfw.setQA()
root.add_widget(pfw)
if __name__ == '__main__':
MatchStickQuizApp().run()
pass
上記のコードは以下からダウンロードできます。
【Python】マッチ棒クイズ開発 Part20 ソースコード02
コードの解説 kvファイル
10行目から28行目の「PlayFieldWidget」は、マッチ棒を動かす画面です。
11行目の「ms_w」は、マッチ棒の横幅を保持する変数です。25を代入しています。
12行目の「ms_h」は、マッチ棒の縦幅を保持する変数です。ms_wに6.8をかけた値を代入しています。
13行目の「base_x」と14行目の「base_y」は、問題となる式を真ん中に表示するための左下の座標です。基準となる座標を保持します。
15行目の「frameList」は、マッチ棒を動かす画面に追加されたマッチ棒枠を保持する変数です。
16行目の「msList」は、マッチ棒を動かす画面に追加されたマッチ棒を保持する変数です。
17行目の「msDict」は、キーにマッチ棒、値にマッチ棒の座標や角度を保持する変数です。
18行目の「qaDict」は、Jsonファイルから問題と答えを読み込んで保持する変数です。今までは、QADictという変数でしたがqaDictと変わりました。理由としましては、kvファイルでは大文字から始まる変数を定義することができないからです。
19行目の「quesList」は、現在表示している問題を保持する変数です。
20行目の「ansList」は、現在の問題の答えを保持する変数です。
21行目の「input_ansList」は、プレイヤーが入力した答えを保持する変数です。
22行目の「on_size」は、sizeに変化があった際に実行される関数です。pythonファイルで定義するdrawFrameとdrawQuestionを設定しています。sizeに変化があった際はdrawFrameとdrawQuestionが実行されます。
23行目の「canvas」は、canvasオブジェクトを宣言しています。この宣言以降でインデントを1つ下げるとcanvasへ追加するという扱いになります。
24行目の「Color」は、後述するRectangleの色を設定するオブジェクトです。
25行目では、色を白色に設定しています。
26行目の「Rectangle」は、背景を表現するオブジェクトです。
27行目の「pos」は、背景の座標を保持する変数です。
28行目の「size」は、背景のサイズを保持する変数です。
コードの解説 pythonファイル
pythonに書かれているコードは、ほとんどこれまでのPartで解説しているため、新しく記述したコードや気を付けて欲しいコードについて解説します。
25行目の「self.onMoveObj = None」は、kvファイルで定義してもよい変数ですが、Noneの代入が不可であるためpythonファイルで定義しています。
64行目から81行目の「drawFrame」は、今まで定義してきたdrawFrameとは若干異なります。
66行目から71行目の処理はinit関数に記述していた処理です。その処理をdrawFrame関数に追加した形となります。
また、init関数に記述していた処理は1回だけ実行して欲しいため、if文を追加しています。
動作確認
コードを保存したら、コマンドプロンプトを開きファイルを保存したフォルダまで移動します。移動したら、以下のコマンドを入力し実行します。
python matchstickquiz.py
以下のように問題となる式が表示されていれば動作確認完了です。問題はランダムに選択されるため式が異なっていても問題ありません。
スポンサードサーチ
まとめ
今回は、マッチ棒枠とマッチを動かす画面を作成しました。
マッチ棒を動かす画面では関数が必須であるため、pythonに記述するコードの量はそこまで変わらなかったと思います。
また、大文字から始まる変数を定義できないやNoneを代入できないというkvファイル特有の仕様がありました。
次回Part21では、プレイ画面の右側に表示するボタンの親クラスとポップアップを作成します。
マッチ棒クイズ開発の目次はこちら
最後までお読みいただきありがとうございます。
スポンサードサーチ