【Python】マッチ棒クイズ開発 Part7

こんにちは、にわこまです。
今回は、回転に関する関数とマッチ棒同士の重なりを検出する関数の作成を行います。回転は横棒を縦棒に変化させる時に使用します。重なりの検出は同じ位置に置かさせないために使用します。
誤字脱字や分からない点がございましたらご連絡お願いいたします!
メールまたはTwitterのDMまで!!
マッチ棒クイズ開発の目次はこちら
スポンサードサーチ
回転のための関数

マッチ棒クイズでは、マッチ棒を動かすだけでなくマッチ棒を回転させることも必要です。
縦から横に、横から縦に回転させることが必要です。(掛け算があれば、斜めも必要。)
そのため、ダブルクリックをした際に回転するようにします。また、1回に回転する角度は45度としました。
コードの提示
マッチ棒を回転させるソースコードを以下に示します。
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.graphics import Color, Rectangle, Ellipse
from kivy.graphics import Rotate # Added
from kivy.graphics.context_instructions import PushMatrix, PopMatrix # Added
class MatchStickWidget(Widget):
def __init__(self, ms_w, angle, **kwargs):
super(MatchStickWidget, self).__init__(**kwargs)
self.size = [0, 0]
self.past_pos = (0, 0)
self.ms_w = ms_w
self.create_property("angle", value=angle) # Added
self.origin = (self.x + self.ms_w / 2, self.y + self.ms_w * 6.8 / 2) # Added
# Stick
self.posStick = [self.x+self.ms_w*0.2, self.y]
self.sizeStick = [self.ms_w * 0.6, self.ms_w * 5.6]
# Powder
self.posPowder = [self.x, self.y+self.ms_w*5.2]
self.sizePowder = [self.ms_w, self.ms_w*1.6]
with self.canvas:
PushMatrix() # Added
self.rot = Rotate() # Added
self.rot.angle = self.angle # Added
self.rot.origin = self.origin # Added
Color(rgb=[0.96, 0.87, 0.7])
self.stick = Rectangle(pos=self.posStick, size=self.sizeStick)
Color(rgb=[0.8, 0.1, 0])
self.powder = Ellipse(pos=self.posPowder, size=self.sizePowder)
PopMatrix() # Added
self.bind(pos=self.drawMS)
self.bind(angle=self.rotateTheta) # Added
def rotateTheta(self, *args):
self.rot.angle = self.angle # Added
def on_touch_down(self, touch, *args):
tof = self.collide_point(*touch.pos)
if(touch.is_double_tap and tof):
self.angle += 45
return True
elif(not(touch.is_double_tap) and tof):
touch.grab(self)
self.past_pos = touch.pos
return True
return False
def on_touch_up(self, touch, *args):
# 省略
def on_touch_move(self, touch, *args):
# 省略
def drawMS(self, *args):
# rot
self.origin = (self.x + self.ms_w / 2, self.y + self.ms_w * 6.8 / 2) # Added
self.rot.origin = self.origin # Added
# stick
self.posStick = [self.x+self.ms_w*0.2, self.y]
self.stick.pos = self.posStick
# powder
self.posPowder = [self.x, self.y+self.ms_w*5.2]
self.powder.pos = self.posPowder
class RootWidget(FloatLayout):
# 省略
class MatchStickQuizApp(App):
def build(self):
root = RootWidget()
root.add_widget(MatchStickWidget(25, 0, pos=[100, 100]))
return root
if __name__ == '__main__':
MatchStickQuizApp().run()
pass
上記のコードは以下からダウンロードできます。
コードの解説
8行目の「import文」は、Rotateをインポートしています。マッチ棒を回転させるためのクラスです。
9行目の「import文」は、PushMatrixとPopMatrixをインポートしています。これらは、回転させる範囲を指定するためのクラスです。PushMatrixからPopMatrixで囲まれた範囲を1つの回転の範囲としています。
12行目の「__init__関数」の引数にangleが追加されました。angleはマッチ棒の初期角度を指定するための変数です。
17行目の「create_property」は、MatchStickWidgetクラスに新しいプロパティを生成しています。プロパティにすることで、後述する「bind」という機能を使用することができます。
第1引数に指定された文字列がプロパティ名となります。つまり、「angle」がプロパティ名です。
第2引数の「value=angle」は、angleプロパティにangle(初期角度)を代入しています。
18行目の「self.origin」は、マッチ棒の回転の原点です。
26行目の「PushMatrix」は、回転の範囲の始まりを示しています。PushMatrixからPopMatrixが回転の範囲となります。
27行目の「self.rot」は、回転を実装するためのクラスであるRotateが代入された変数です。Rotateクラスの角度や原点が変化することで他のgraphicsも変化します。
28行目の「self.rot.angle」は、Rotateクラスのangleに角度を代入しています。
29行目の「self.rot.origin」は、Rotateクラスのoriginに原点を代入しています。
34行目の「PopMatrix」は、回転の範囲の終わりを示しています。PushMatrixからPopMatrixが回転の範囲となります。
36行目の「self.bind(angle=self.rotateTheta)」は、angleプロパティが変化したときに後述するrotateTheta関数が実行されるように紐づけています。
37行目の「rotateTheta関数」は、angleプロパティが変化したときに実行される関数です。
38行目は、Rotateクラスのangleにangleプロパティを代入しています。
40行目の「tof」は、colloide_point(*touch.pos)のTrueまたはFalseが代入されています。
41行目の「if文」は、ダブルタップかつ当たり判定範囲内であることを判別する条件式です。Trueであれば42行目43行目の処理を実行します。
42行目の「self.angle」は、45度プラスされています。
43行目の「return文」は、Trueを返しています。
44行目の「if文(elif文)」は、ダブルタップがされないかつ当たり判定範囲内であることを判別する条件式です。Trueであった場合の処理はPart7で行ったため省略します。
55行目の「self.origin」は、再計算された回転の原点が代入されています。
56行目の「self.rot.origin」は、self.originが代入されています。
動作確認

コードを保存したら、コマンドプロンプトを開きファイルを保存したフォルダまで移動します。移動したら、以下のコマンドを入力し実行します。
python matchstickquiz.py
以下のように表示され、ダブルクリックすると回転すれば動作確認完了です。
ダブルクリック前の状態を以下に示します。

ダブルクリック後の状態を以下に示します。

スポンサードサーチ
重なりを検出

重なりを検出する関数は、マッチ棒が同じ位置に同じ角度で存在することを検出する関数です。
しかし、全く同じ位置というのは難しいため、それぞれの回転の原点間の距離がマッチ棒の横幅(ms_w)以下である場合を重なっているとしました。
また、同じ角度とはangleを180で割った時の余りが同じであるとしました。


コードの提示
マッチ棒の重なりを検知する関数のソースコードを以下に示します。
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.graphics import Color, Rectangle, Ellipse
from kivy.graphics import Rotate
from kivy.graphics.context_instructions import PushMatrix, PopMatrix
import numpy as np
class MatchStickWidget(Widget):
def __init__(self, ms_w, angle, **kwargs):
# 省略
def rotateTheta(self, *args):
# 省略
def on_touch_down(self, touch, *args):
# 省略
def on_touch_up(self, touch, *args):
# 省略
def on_touch_move(self, touch, *args):
# 省略
def drawMS(self, *args):
# 省略
def collide_center_circle(self, wid, *args):
diff_x = self.rot.origin[0] - wid.rot.origin[0]
diff_y = self.rot.origin[1] - wid.rot.origin[1]
distance = np.sqrt(diff_x**2 + diff_y**2)
if(self != wid and distance <= self.sizeStick[0] and self.angle % 180 == wid.angle % 180):
return True
return False
from kivy.graphics import Line
class RootWidget(FloatLayout):
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
with self.canvas.before:
Color(rgb=[1, 1, 1])
self.bg_rect = Rectangle(pos=self.pos, size=self.size)
def on_size(self, *args):
self.bg_rect.size = self.size
def draw_center_circle(self, msList, *args):
with self.canvas.after:
Color(rgb=[0.12, 0.56, 1])
for ms in msList:
Line(circle=(ms.origin[0], ms.origin[1], ms.sizeStick[0]/2))
class MatchStickQuizApp(App):
def build(self):
root = RootWidget()
ms1 = MatchStickWidget(25, 45, pos=[100, 100])
ms2 = MatchStickWidget(25, 45, pos=[100, 115])
ms3 = MatchStickWidget(25, 0, pos=[400, 100])
ms4 = MatchStickWidget(25, 0, pos=[425, 100])
ms5 = MatchStickWidget(25, 0, pos=[650, 100])
ms6 = MatchStickWidget(25, 45, pos=[650, 100])
root.add_widget(ms1)
root.add_widget(ms2)
root.add_widget(ms3)
root.add_widget(ms4)
root.add_widget(ms5)
root.add_widget(ms6)
print("Are ms1 overlapping ms2? : ", ms1.collide_center_circle(ms2))
print("Are ms3 overlapping ms4? : ", ms3.collide_center_circle(ms4))
print("Are ms5 overlapping ms6? : ", ms5.collide_center_circle(ms6))
root.draw_center_circle([ms1, ms2, ms3, ms4, ms5, ms6])
return root
if __name__ == '__main__':
MatchStickQuizApp().run()
pass
上記のコードは以下からダウンロードできます。
コードの解説
11行目の「import文」は、numpyライブラリをnpとしてインポートしています。numpyライブラリは原点間の計算時に使用します。
26行目から32行目の「collide_center_circle関数」は、マッチ棒の重なりを検出する関数です。
第2引数に対象のオブジェクトを代入します。
27行目の「diff_x」は、対象元と対象先のx軸方向(横方向)の差が代入されている変数です。
28行目の「diff_y」は、対象元と対象先のy軸方向(縦方向)の差が代入されている変数です。
29行目の「distance」は、原点間の距離が代入されている変数です。直角三角形の斜辺を求める公式で計算しています。
np.sqrt()は、引数を2分の1乗(ルート、√)しています。
30行目の「if文」は、マッチ棒が重なっているか判別する条件式です。
「self != wid」は、第2引数のwidに代入されたオブジェクトが自分自身でないことを判別しています。
「distance <= self.sizeStick[0]」は、算出した距離がマッチ棒の横幅以下であることを判別しています。
「self.angle % 180 == wid.angle % 180」は、対象元の角度と対象先の角度が同じであることを判別しています。
31行目の「return文」は、Trueを返しています。
31行目の「return文」は、Falseを返しています。
動作確認

コードを保存したら、コマンドプロンプトを開きファイルを保存したフォルダまで移動します。移動したら、以下のコマンドを入力し実行します。
python matchstickquiz.py
以下のように表示されば動作確認完了です。
Are ms1 overlapping ms2? : True
Are ms3 overlapping ms4? : False
Are ms5 overlapping ms6? : False
以下にウィンドウの実行結果を示します。
青色の円は原点からの直径ms_wの円です。この円同士が接触すると重なっているという判定になります。
しかし、角度がことなると重なっているという判定にはなりません。
左のマッチ棒たちは、青色の円が接触しており角度も同じであるため重なっているという判定になっています。Trueが出力されています。
真ん中のマッチ棒たちは、火薬部分が接触していますが青色の円が接触していないため重なっているという判定にはなりません。Falseが出力されています。
右のマッチ棒たちは、青色の円が全く同じ位置にありますが、角度がことなるため重なっているという判定にはなりません。Falseが出力されています。

スポンサードサーチ
まとめ

今回は、回転に関する関数の作成とマッチ棒同士の重なりの検出する関数の作成を行いました。
次回Part8では、当たり判定に関する関数の作成を行います。マッチ棒をクリックしたのみだけ動かせられる、回転させられるといった処理になるようにします。
当たり判定の関数はマッチ棒部分と火薬部分の2つの関数を作成します。
これら2つの関数は数学的要素があり難しめです。数学が苦手な方は、コードだけダウンロードしてPart10をお楽しみください。
マッチ棒クイズ開発の目次はこちら
最後までお読みいただきありがとうございます。
スポンサードサーチ