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

こんにちは、にわこまです。

今回は、マッチ棒を動かす関数とマッチ棒を1本した動かせないようにする関数を作成します。マッチ棒を動かせるようになることでよりゲームらしくなります。

 

誤字脱字や分からない点がございましたらご連絡お願いいたします!

メールまたはTwitterのDMまで!!

 

 

 

スポンサードサーチ


マッチ棒を動かす関数

マッチ棒クラスで作成したon_touch_down関数とon_touch_up関数を作成します。

 

on_touch_down関数では、マッチ棒クラスのon_touch_down関数を実行します。さらに、対象のマッチ棒クラスを最前面に描画するようにします。

最前面に描画されていなけば、他のマッチ棒と重なっている時にそのマッチ棒を上手く指定できません。

 

on_touch_up関数では、マッチ棒クラスのon_touch_up関数を実行します。さらに、当たり判定や移動する前の座標の保持や更新を行います。

  

 

コードの提示

マッチ棒を動かす関数のソースコードを以下に示します。

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
from kivy.storage.jsonstore import JsonStore

import numpy as np
import random
import copy

class MatchStickWidget(Widget):
    # 省略

class MatchStickFrameWidget(Widget):
    # 省略

class PlayFieldWidget(FloatLayout):
    def __init__(self, **kwargs):
        # 省略
    def on_size(self, *args):
        # 省略
    def setQA(self, *args):
        # 省略
    def calcPos(self, base_x, base_y, k, *args):
        # 省略
    def drawQuestion(self, *args):
        # 省略
    def drawAnswer(self, *args):
        # 省略
    def checkAnswerFML(self, *args):
        # 省略
    def on_touch_down(self, touch):
        for idx, obj in enumerate(self.children):
            tof = obj.on_touch_down(touch)
            if(tof and idx != 0):
                # Here is bringing the clicked object to the foreground.
                self.remove_widget(obj)
                self.add_widget(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))
            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)

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

class MatchStickQuizApp(App):
    def build(self):
        root = RootWidget()
        pfw = PlayFieldWidget()
        pfw.QADict = JsonStore("./matchstickquiz_QA2.json")
        pfw.setQA()
        root.add_widget(pfw)
        return root

if __name__ == '__main__':
    MatchStickQuizApp().run()
    pass

 

上記のコードは以下からダウンロードできます。

  

 

コードの解説

14行目の「import copy」は、copyライブラリをインポートしています。移動前の座標関係で使用します。

 

 

37行目から44行目の「on_touch_down関数」は、マッチ棒の回転や掴みに関係する関数です。

 

38行目の「for文」は、PlayFieldWidgetに追加されているウィジェットの数だけ繰り返します。

enumerate関数で範囲を指定しているため、idxには添え字が、objにはオブジェクトが代入されます。

 

39行目の「tof」は、objのon_touch_down関数を実行して返された真理値が代入される変数です。

 

40行目の「if文」は、「tofがTrueかつobjが最前面に描画されていない」ということを判別する条件式です。Trueであれば、42行目から44行目を実行します。

 

42行目の「remove_widget」と43行目の「add_widget」は、それぞれPlayFieldWidgetからobjを削除し、追加しています。

kivyのcanvasでは一番最後に追加されたウィジェットが最前面に描画されるため、削除し追加しなおすことで、objを最前面に描画するようにしています。

 

44行目の「break文」は、for文から抜け出すための文です。

 

 

45行目から59行目の「on_touch_up関数」は、マッチ棒の座標の確定や移動前の座標に関係する関数です。

 

46行目の「if文」は、「self.childrenが空でないかつ0番目のウィジェットがMatchStickWidgetである」ということを判別する条件式です。

 

47行目の「obj」は、0番目のウィジェットを代入する変数です。

 

48行目では、objのon_touch_up関数を実行しています。

 

49行目の「tofList」は、objとそれぞれのマッチ棒枠とぶつかっているかをまとめた変数です。

 

50行目の「tof_obj_frame」は、tofListにTrueが含まれているときTrueが代入される変数です。つまり、objとマッチ棒枠がぶつかっていればこの変数にTrueが代入されます。

 

51行目の「frame」は、ぶつかっているマッチ棒枠を代入する変数です。ぶつかっていなければNoneが代入されます。

 

52行目の「tof_obj_obj」は、objがその他のマッチ棒とぶつかっているときTrueが代入されます。

 

53行目の「if文」は、「objがその他のマッチ棒とぶつかっている」ということを判別する条件式です。Trueであれば、54行目を実行します。

 

54行目の「obj.pos」は、obj.before_move_posが代入されます。つまり、他のマッチ棒とぶつかっているため、移動前の位置に戻すということを行っています。

 

55行目の「if文」は、「objが他のマッチ棒とはぶつかっていないがマッチ棒枠とはぶつかっている」ということを判別する条件式です。Trueであれば、56行目と57行目を実行します。

 

56行目の「obj.pos」は、ぶつかっているマッチ棒枠の座標が代入されます。つまり、マッチ棒枠にカッチリはめるというようなことを行っています。

 

57行目の「obj.before_move_pos」は、ぶつかっているマッチ棒枠の座標が代入されます。つまり、移動前の座標を更新しています。

 

58行目の「if文」は、「objがマッチ棒ともマッチ棒枠ともぶつかっていない」ということを判別する条件式です。Trueであれば59行目を実行します。

 

59行目の「obj.before_move_pos」は、現在のマッチ棒の座標が代入されます。つまり、移動前の座標を更新しています。

 

 

 

動作確認

コードを保存したら、コマンドプロンプトを開きファイルを保存したフォルダまで移動します。移動したら、以下のコマンドを入力し実行します。

python matchstickquiz.py

 

まず、以下のようにマッチ棒が表示されることを確認します。式はランダムで選択されるため、以下に示している画像と異なっていても問題ありません。

 

次に、1本のマッチ棒を動かせることを確認します。

 

次に、2本目のマッチ棒も動かせることを確認します。本開発では1本しか動かせないようにしたいため、この動作については修正する必要があります。

 

最後に、マッチ棒の当たり判定について2点確認します。

1点目は、以下の画像のように同じ座標にあっても角度が異なれば重ねられることを確認します。

2点目は、同じ座標同じ角度に移動させようとすると移動前の座標に戻されることを確認します。

 

 

スポンサードサーチ


マッチ棒を1本しか動かせないようにする関数

先程の動作確認で2本のマッチ棒を動かすことができることを確認しました。しかし本開発では1本しか動かせないようにしたいため、そのための関数を作成します。

 

動かしているマッチ棒の情報を保持し、保持している情報と同じである場合や情報を保持していないという場合のみ動かせるような処理を作成します。

また、1度動かしたマッチ棒が元の位置に戻ったら他のマッチ棒を動かせるような処理も作成します。

 

 

コードの提示

マッチ棒を1本しか動かせないようにする関数のソースコードを以下に示します。

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
from kivy.storage.jsonstore import JsonStore

import numpy as np
import random
import copy

class MatchStickWidget(Widget):
    # 省略

class MatchStickFrameWidget(Widget):
    # 省略

class PlayFieldWidget(FloatLayout):
    def __init__(self, **kwargs):
        # 省略
    def on_size(self, *args):
        # 省略
    def setQA(self, *args):
        # 省略
    def calcPos(self, base_x, base_y, k, *args):
        # 省略
    def drawQuestion(self, *args):
        # 省略
    def drawAnswer(self, *args):
        # 省略
    def checkAnswerFML(self, *args):
        # 省略
    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)) 
            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):
    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

class MatchStickQuizApp(App):
    def build(self):
        root = RootWidget()
        pfw = PlayFieldWidget()
        pfw.QADict = JsonStore("./matchstickquiz_QA2.json")
        pfw.setQA()
        root.add_widget(pfw)
        return root

if __name__ == '__main__':
    MatchStickQuizApp().run()
    pass

 

上記のコードは以下からダウンロードできます。

 

 

コードの解説

37行目から48行目の「checkOnMoveObj関数」は、マッチ棒を1本しか動かせないようにする関数です。

第2引数の「obj」には、動かしてたいマッチ棒を代入します。

 

38行目の「tof_None」は、動かしているマッチ棒を保持する変数onMoveObjがNoneであるときTrueが代入されます。NoneでなければFalseが代入されます。

 

39行目の「tof_obj」は、保持しているオブジェクトと引数のobjが同じであるときTrueが代入されます。同じでなければFalseが代入されます。

 

40行目の「tof_pos」は、msDictとに格納されているobjの座標と現在のobjの座標が同じであるときTrueが代入されます。同じでなければFalseが代入されます。

 

41行目の「tof_angle」は、msDictに格納されているobjの角度と現在のobjの角度が同じであるときTrueが代入されます。同じでなければFalseが代入されます。

 

42行目の「if文」は、「現在動いているマッチ棒が無い」ということを判別する条件式です。Trueであれば43行目を実行します。

 

43行目では、onMoveObjにobjを代入することで動かしているマッチ棒を保持しています。

 

44行目の「if文」は、「現在動かしているマッチ棒があるがobjとは異なる。さらに座標または角度が変化している」ということを判別する条件式です。Trueであれば、45行目と46行目を実行します。

 

45行目では、objの座標を元の座標に戻しています。

46行目では、objの角度を元の角度に戻しています。

 

47行目の「if文」は、「現在動かしているマッチ棒がありobjと同じである。さらに座標と角度が同じである」ということを判別する条件式です。つまり、動かしていたマッチ棒が元の場所に戻ってきたということを判別しています。Trueであれば、48行目を実行します。

 

48行目では、onMoveObjにNoneを代入することでonMoveObjの開放を行っています。

 

 

57行目から60行目はクリックされたときにcheckOnMoveObj関数を実行するための処理です。

 

57行目の「if文」は、「onMoveObjにobjまたはNoneが代入されいるかつ最前面に描画されているかつダブルクリックされた」ということを判別する条件式です。Trueであるとき59行目と60行目を実行します。

 

59行目では、checkOnMoveObj関数を実行しています。

 

60行目の「break文」は、for文から抜け出すための文です。

 

 

76行目では、chekcOnMoveObj関数を実行しています。

 

 

動作確認

コードを保存したら、コマンドプロンプトを開きファイルを保存したフォルダまで移動します。移動したら、以下のコマンドを入力し実行します。

python matchstickquiz.py

 

まず、以下のようにマッチ棒が表示されることを確認します。式はランダムで選択されるため、以下に示している画像と異なっていても問題ありません。

 

次に、マッチ棒を動かせることを確認します。さらに、2本目のマッチ棒を動かせないことを確認します。

  

 

スポンサードサーチ


まとめ

今回は、マッチ棒を動かす関数と1本しか動かせないようにする関数を作成しました。

これで、実際にマッチ棒を動かすウィジェットであるPlayFieldWidgetは完成です。

 

次回Part15では、問題文のレイアウトとボタンの親クラスを作成します。

 

 

 

最後までお読みいただきありがとうございます。


スポンサードサーチ