0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【アーカイブ②】実装プロセスと当時のコード記録

0
Last updated at Posted at 2025-11-19

はじめに

お疲れ様でございます。ハム二郎です:bow_tone1:

前回アーカイブに引き続き、短歌対戦アプリの開発記録の後半部分をまとめます。

再度お知らせしますが、本記事は、プロジェクト初期におけるプロトタイピングの記録(モノリス構造による実装) 及び、アーキテクチャ導入前の「課題の可視化」と「比較検証用のアーカイブ」としています。

そのため、技術的な評価を目的とされる場合は、本記事をスキップし、学習記録トラッカーアプリの記事を優先的にご参照ください。

誤りがあればぜひコメントでご指摘いただけますと幸いです。

目次

コード内容

まとめ

カードボタンクラス

class CardButton(Button):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.color = (0, 0, 0, 1)  
        self.font_size = 20
        self.background_color = (0, 0, 0, 0) 
        self.background_normal = ''  # デフォルト背景を無効化

class CardButton(Button):
def __init__(self, **kwargs):
super().__init__(**kwargs)

Kivyの標準ボタン(Button)を継承し、自作のカスタムボタン CardButton を定義しつつ、ボタンの初期化処理を行い初期設定をセットアップしています。

さらに、その下では属性追加を行い、「背景を透明にし、黒文字で、自分でデザインできるカード状ボタン」を作っています。

        with self.canvas.before:
            Color(0, 0, 0, 1) 
            self.border_rect = Rectangle(pos=self.pos, size=self.size)
        self.bind(pos=self.update_rect, size=self.update_rect) 
        self.padding = (10, 10)
        self.border_radius = 15
        self.elevated = False

with self.canvas.before:
Color(0, 0, 0, 1)

前述の通り、「このウィジェットの背景レイヤーに、図形や色を描く命令を書きます」という宣言をしています。


self.border_rect = Rectangle(pos=self.pos, size=self.size)
Rectangle(pos=self.pos, size=self.size) により CardButton とまったく同じ位置・サイズの枠線矩形を描画した Rectangle オブジェクトを self.border_rect という属性として保存します。
なお、保存する理由としては、カードが動いたとき四角形も動かす必要があり、その際に更新を行うためです。


self.bind(pos=self.update_rect, size=self.update_rect)
前述の通り、ボタンの変化に図形の動きを追従させています。


self.padding = (10, 10)
ボタン内の文字(text)の余白を左右上下に10pxずつ付けています。


self.border_radius = 15
ボタンの角を丸める“半径”を15に設定します。


self.elevated = False
カードが押された時のアニメーションを管理しています。通常状態は浮き上がっていなよう設定し、= Trueとなれば浮き上がる状態に設定しています。

        with self.canvas.before:
            Color(0.98, 0.96, 0.92, 1) 
            self.bg = RoundedRectangle(radius=[15], pos=self.pos, size=self.size)
            Color(0, 0, 0, 1)
            self.border = RoundedRectangle(radius=[15], pos=self.pos, size=self.size)

with self.canvas.before:
前述の通り、このウィジェットの 背景レイヤー に図形を描く宣言をしています。


Color(0.98, 0.96, 0.92, 1)
図形をほぼ白に近い淡いベージュ(和紙・古文書っぽい色)に設定します。


self.bg = RoundedRectangle(radius=[15], pos=self.pos, size=self.size)
角丸の背景(bg = background)を描きます。角丸半径 15、カスタム色、位置・サイズはボタン本体と同じに設定しています。

RoundedRectangle … Rectangle の角を丸くした図形。

Color(0, 0, 0, 1)
Kivy は必ず Color → 図形 の順で描くため、色を黒色に再指定しています。


self.border = RoundedRectangle(radius=[15], pos=self.pos, size=self.size)
背景と同様に枠線を描きます。
色が黒なので周囲に黒枠ができるようにしています。

        self.bind(pos=self.update_graphics, size=self.update_graphics)
        self.bind(on_press=self.on_press_effect, on_release=self.on_release_effect)

前半部分は前述の通りです。
self.bind(on_press=self.on_press_effect, on_release=self.on_release_effect)
この後半部分では、「押す=強調 / 離す=元に戻す」という UI 演出を実行するためのイベントを登録しています。

bind()bind(イベント名 = イベント時に呼ぶ関数)

    def update_rect(self, *args):
        self.border_rect.pos = self.pos
        self.border_rect.size = self.size

前述の通り、ボタンの動きにあわせて実際の枠線(border_rect)を追従させます。

    def update_graphics(self, *args):
        self.bg.pos = self.pos
        self.bg.size = self.size
        self.border.pos = self.pos
        self.border.size = self.size

前述と同様に、自身(Widget)の pos/size が変更された場合、背景(bg) と枠線(border) の pos/size を同期させるよう設定しています。見た目のズレを防いでいます。

    def on_press_effect(self,*args):
        self.elevated = True
        self.canvas.before.clear()
        with self.canvas.before:
            Color(0.9, 0.88, 0.85, 1)
            self.bg = RoundedRectangle(radius=[15], pos=self.pos, size=self.size)
            Color(0, 0, 0, 1)
            self.border = RoundedRectangle(radius=[15], pos=self.pos, size=self.size)

def on_press_effect(self,args):
先に self.bind(on_press=self.on_press_effect) を記述していたため、ボタンが押された瞬間に自動でこの関数が呼ばれ、以下の通りUI演出が行われます。

self.elevated = True
前述した通り、ボタンが押されて浮き上がっている状態に設定しています。

self.canvas.before.clear()
Canvas の図形は「上書き」できないため、背景レイヤーの描画内容を一度すべて消します。

:bulb: clear に()を付ける理由
Python では、関数(メソッド)は オブジェクトとして存在するため、ただの「値」として扱うことができます。
よって、clear のみでは clear という関数オブジェクトとして扱われますが、()を付け clear() とすることで「clear を実行する」という意味になります。

以降は前述した内容と同様に、ボタンの位置やサイズに合わせ、影を帯びた押下用の背景(bg)及び、押下後の黒い角丸枠(border)を描いています。

スロットカラムクラス

class SlotColumn(BoxLayout):
    def __init__(self, length, color, label_text, **kwargs):
        super().__init__(orientation="vertical", **kwargs)
        self.length = length
        self.color = color
        self.slots = []
        self.enabled = True

class SlotColumn(BoxLayout):
def __init__(self, length, color, label_text, **kwargs):
super().__init__(orientation="vertical", **kwargs)

Kivyの BoxLayout を継承し、専用のスロットの縦列 SlotColumn を定義しつつ、レイアウトの初期化処理を行い初期設定をセットアップしています。
また、セットアップ時に呼び出し側から length(スロット数)color(色)label_text(句のラベル:上の句など)を受け取ります。
さらに、BoxLayout のデフォルトは horizontal(横並び) であるため、親クラスの初期化を縦方向レイアウト(orientation="vertical") で呼んでいます。

以降はこの SlotColumn の「文字数」「色」「空リスト」を属性追加し、self.enabled = True にて有効状態に設定しています。

        self.add_widget(Label(text=label_text, size_hint=(1, None), height=30))
        for i in range(length):
            slot = Slot(color)
            self.add_widget(slot)
            self.slots.append(slot)

self.add_widget(Label(text=label_text, size_hint=(1, None), height=30))
SlotColumn はウィジェットを上から順に縦に並べるため、 [ Label ] が一番上に配置されることを利用し、「上の句」などのタイトルをラベルとして追加しています。

Label()()内は、Label クラスのインスタンスを作るための構成(初期設定)であり、これらの引数は __int__ を通し最終的にラベルウィジェットの属性として保存されます。
ここでは、「表示する文字列」「横幅は親に追従 / 高さは固定」「高さ30px」を追加しています。

なお、Label はクラス名そのものです。よってここでは、()の付加によるメソッドの実行ではなく、Label()Label クラスのインスタンス(=ラベルウィジェット)を作っています。


for i in range(length):
Python のループ構文は for 変数(処理) in 集合: なので、range(length) の中の数を 0 から length−1 までを「i」に順番に入れてそのたびに処理を実行するという意味です。


slot = Slot(color)
self.add_widget(slot)
self.slots.append(slot)

Slot クラスから背景色に color を使う新しいスロットオブジェクトを1つ生成し、変数 slot に入れています。
そして、今作った slot を SlotColumn(縦レイアウト)の中に追加し、画面に表示します。
その後、追加した slot を SlotColumn が持つスロット一覧リストに保存しロジック面で管理しています。

append は Python のリストの基本メソッドであり、「リストの末尾に要素を追加する」という動作を示します。

    def enable(self):
        self.enabled = True
        for s in self.slots:
            s.opacity = 1.0

def enable(self):
SlotColumn を「有効化する」動作を定義しています。
self.slots に入っている slot オブジェクトを 1つずつ取り出して、 s として取り扱い、すべてのスロットに対して同じ処理を繰り返すことを可能にします。

スロットは [s1, s2, s3, s4, s5] のように複数あり、一つずつ書くのは不可能です。そこで、まとめて enable / disable により全体に対する UI 操作を行うため、スロットをリスト(self.slots)にて管理し、ループで一括処理できるようにしています。


s.opacity = 1.0
全てのスロットの透明度(opacity)を 1.0(完全に見える状態)にします。

    def disable(self):
        self.enabled = False
        for s in self.slots:
            s.opacity = 0.3

def disable(self):
SlotColumn を「有効化する」動作を定義しています。


self.enabled = False
SlotColumn の状態フラグ(enabled)を False にし、「この列は今は操作不可である」と設定しています。


for s in self.slots: s.opacity = 0.3
self.slots に入っている slot オブジェクトを 1つずつ取り出して、 s として取り扱い、全ての Slot を半透明(薄く表示)にして「使えない」状態の見た目にします。
短歌アプリでは順番に句を置いていくため、下記のようにUI操作にてフェーズ管理をしています。

状態
上の句(5音) enable(置ける)
中の句(7音) disable(まだ置けない)
下の句(5音) disable(さらに後)

:warning: 個別スロット(札1枚分の置き場)

class Slot(Widget):
    def __init__(self, color, **kwargs):
        super().__init__(**kwargs)
        self.card = None
        with self.canvas:
            Color(*color)
            self.rect = Rectangle(pos=self.pos, size=(80, 80))
        self.bind(pos=self.update_rect, size=self.update_rect)

    def update_rect(self, *args):
        self.rect.pos = self.pos
        self.rect.size = self.size

実装時、スロット列を管理するクラスを作成した後に、個別スロット用のクラスも同じ Slot という名前で定義し、クラス定義を上書き(オーバーライド)してしまうという初歩的なミスを犯しました。
これにより、期待していたインスタンス変数が初期化されず、描画崩れが発生しました。
(※次回以降では、適切な命名規則とファイル分割によりこの問題を解決しています)

スクリーンクラス

class TankaScreen(Screen):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.layout = FloatLayout()
    self.add_widget(self.layout)
        self.active_index = 0 
        self.colors = {
            1: [1, 1, 1, 1],
            2: [0.7, 1, 0.7, 1],
            3: [0.7, 0.7, 1, 1],
            4: [1, 1, 0.6, 1],
            5: [1, 0.6, 0.6, 1]
        }# 山札
        self.deck = {
            1: [""],
            2: ["〇〇"],
            3: ["〇〇〇"],
            4: ["〇〇〇〇"],
            5: ["〇〇〇〇〇"]
        }

class TankaScreen(Screen): def __init__(self, **kwargs): super().__init__(**kwargs)
Kivyの標準画面(Screen)を継承し、自作のメイン画面 TankaScreen を定義しつつ、画面の初期化処理を行い初期設定をセットアップしています。


self.layout = FloatLayout() self.add_widget(self.layout)
Screen は単なる画面枠であり、
「中に自由に UI を置くための入れ物(レイアウト)」が必要です。

UI を置くための「入れ物」を作成するため、 FloatLayout クラスをオブジェクト化し、self.layout に保存することで入れ物として使用できます。


self.active_index = 0
今どの句(5・7・5・7・7)の列にカードを置くかを示すための番号(インデックス)を =0 によって初期化しています。
index 0 → 5文字句
index 1 → 7文字句
index 2 → 5文字句
index 3 → 7文字句
index 4 → 7文字句


self.colors = { … }
カードの文字数に応じて色を決めるための辞書を作っています。
左側の数字(1, 2, 3, 4, 5)はキー(key)であり、数に応じた文字数のカードを示します。
右側のリストは RGBA の色データです。
background_color=self.colors[length] にて length に代入する形で使用します。


self.deck = { … }
文字数(1~5)をキーとして、その文字数に対応する札の言葉リストをまとめた辞書を作り、TankaScreen の属性として保存しています。つまり、山札の役割を果たします。

これは後述する length = random.randint(1, 5)
text = random.choice(self.deck[length]) にて、使用します。具体的には、length の値に応じて self.deck を参照し、そこからランダムに選んで引きます。

俳句盤面

        self.slot_layout = BoxLayout(orientation='horizontal', size_hint=(None, None),
                                     pos=(60, 250), size=(700, 300), spacing=10)
        self.layout.add_widget(self.slot_layout)

        tanka_pattern = [5, 7, 5, 7, 7]
        self.slot_columns = []

        for i, n in enumerate(tanka_pattern):
            col = SlotColumn(n, self.colors[min(n, 5)], f"{n}文字句")
            self.slot_layout.add_widget(col)
            self.slot_columns.append(col)

self.slot_layout = BoxLayout(...)
俳句盤面(5列構成)を横に並べるレイアウト BoxLayout を作っています。() の中では、「横並び、画面左から60pxで下から250pxの位置、幅700で高さ300の大きさ、10px の余白」を設定しています。
なお、size_hint=(None, None) では親レイアウトにサイズを任せず、自前でサイズを持つよう設定しています。size=(700, 300) を使うために必要です。


self.layout.add_widget(self.slot_layout)
先ほど作った BoxLayout を、画面の下地レイアウト(FloatLayout)に追加します。


tanka_pattern = [5, 7, 5, 7, 7]
短歌の構造(5文字・7文字・5文字・7文字・7文字)を表す一覧です。ここで後のループにて「各列に何スロット置くか」の基準を作っています。


self.slot_columns = []
句の順番制御や active_column の管理を行うため、後で SlotColumn を全て保存する用のリストです。


for i, n in enumerate(tanka_pattern):
先程作成した tanka_pattern = [5, 7, 5, 7, 7]

  • i → インデックス(0,1,2,3,4)
  • n → 値(5,7,5,7,7)

という形で1つずつ取り出します。


col = SlotColumn(n, self.colors[min(n, 5)], f"{n}文字句")
SlotColumn を1つ作り、そのインスタンスを col という変数に入れています。
その SlotColumn は n 個のスロットを持ち、色は文字数に応じた色で、ラベルは「5文字句/7文字句」のように付けられるよう設定しています。

n はfor文で tanka_pattern = [5, 7, 5, 7, 7] の中の値を1つずつ取り出したものであり、n は 5 → 7 → 5 → 7 → 7 と変化します。つまり、列(SlotColumn)が何スロット必要かを示しています。
具体的に、col = SlotColumn(n, 色, ラベル) で SlotColumn を呼ぶと、__init__(self, length, color, label_text) へ順番通りに値が渡されます。つまり、length ← n となります。

Python は「位置引数」を順番に渡す関数やクラスの引数は、左から順に対応します。
これを「位置引数(positional arguments)」と呼びます。

同様に self.colors[min(n, 5)] color ← self.colors[min(n,5)] となり、n の文字数に応じて色を決めます。

また、f"{n}文字句label_text ← f"{n}文字句" となり、ラベル文字列を動的に作っています。前回も登場しましたが、f"文字列 {変数} 文字列"f は「f-string(フォーマット文字列)」を使うための記号であり、Python が提供する「文字列を簡単に埋め込むための機能」です。変数を {} の中に直接書くだけで変数nが展開され、その値が埋め込まれた文字列を作ることができます。


self.slot_layout.add_widget(col)
col(= SlotColumn のインスタンス)を slot_layout(= BoxLayout)の子ウィジェットとして追加しています。colcol = SlotColumn(n, self.colors[min(n, 5)], f"{n}文字句") によって作られた SlotColumn クラスから作られたオブジェクトです。

slot_layout は self.slot_layout = BoxLayout(orientation='horizontal', ...) であり、横並びのレイアウトですので、中に入れたものを横に並べて配置します。これにより、ここに col が追加されることにより、画面上に新しい SlotColumn(縦のスロット群)が表示され、slot_layout 内に「横並びの 1 列」として配置されます。


self.slot_columns.append(col)
先ほど self.slot_columns = [] で作成した slot_columns というリストにSlotColumn(=1句分のスロット群)を保存しています。

手札(下段)

        self.hand = []
        x = 100
        for i in range(5):
            length = random.randint(1, 5)
            text = random.choice(self.deck[length])
            card = Button(text=f"{text}\n({length})",
                          background_color=self.colors[length],
                          pos=(x, 100), size_hint=(None, None), size=(100, 120))
            card.bind(on_press=self.on_card_select)
            self.layout.add_widget(card)
            self.hand.append(card)
            x += 120

self.hand = []
 x = 100

後から手札管理やカード操作を行うため、手札カードを保存するための空リストを作ります。また、カードを横に並べるための初期位置(X座標)を 100px にセットします。


for i in range(5):
length = random.randint(1, 5)
text = random.choice(self.deck[length])

まず、for構文でのループにより手札を5枚作ります。
次に、1〜5 の整数からランダムに1つ選んで返し、その結果を length(カードの文字数) に代入します。

random.randint(a, b) は Python 標準ライブラリの random モジュールの関数であり、a 以上 b 以下の整数を完全ランダムで1つ返します。

そして、text = random.choice(self.deck[length]) にて代入された length の値に応じて山札である self.deck を参照し、そこからランダムに選んで引きます。


card = Button(...)
手札として画面に表示するボタンを作っています。
各引数では、以下の通りボタンの内容を設定しています。
text=f"{text}\n({length})" では上段に単語、下段に文字数を表示します。\n は 「ここで改行せよ」という意味の特別な記号であり、これにより上下に分けられています。
background_color=self.colors[length] では文字数別の色でカード背景を決定します。TankaScreeninit 内で1〜5 の整数に対応した色を self.colors にて設定しており、ここが対応付けられます。
pos=(x, 100) では画面内の位置(絶対座標)を設定しています。x は横位置(後述のコードで毎回 120 足されて横並びになる)、y=100 は固定で下段に配置しています。
size_hint=(None, None) は前述の通り自動サイズ調整を無効かしています。
size=(100, 120) はカードの大きさを設定しています。


card.bind(on_press=self.on_card_select)
bind は「プロパティの変化を監視する関数」のため、カードが押された時に on_card_select() が呼び出されるよう設定します。


self.layout.add_widget(card)
作ったカードを画面(FloatLayout)に追加して表示しています。


self.hand.append(card)
生成したカードを作成済の手札リストである self.hand に保存します。


x += 120
次のカードの横位置を 120px 右にずらし、カード1枚ずつ間隔をつけて横に並べるようにしています。

:bulb: () の意味(おさらい)
上記における () は 関数(メソッド)を実行するためのカッコであり、add_widget は関数の名前、(card) は関数に渡す引数を示します。これにより、card を追加するという意味になります。() が無ければ関数そのものを指すだけで実行されません。
なお、引数を渡さない場合、例えば self.canvas.before.clear() では canvas.beforeclear メソッドを実行するという意味になります。add_widget の場合は必ずウィジェット(Widget オブジェクト)を引数に要求する関数のため、引数を渡さない場合はエラーになります。

札を出す動作

    def on_card_select(self, instance):
        active_col = self.slot_columns[self.active_index]
        for slot in active_col.slots:
            if not slot.card:
                slot.card = instance
                instance.pos = slot.pos
                break
        self.active_index = (self.active_index + 1) % len(self.slot_columns)
        self.update_active_column()

def on_card_select(self, instance):
先に card.bind(on_press=self.on_card_select) にてバインドしていたため、カードが押されたとき、押されたそのカードが instance として渡ってきます。


active_col = self.slot_columns[self.active_index]
先ほど作成した self.slot_columns.append(col) により slot_columns では、[ SlotColumn(5), SlotColumn(7), SlotColumn(5), SlotColumn(7), SlotColumn(7) ] のようにリスト化されています。

リスト[インデックス] … Python のリストの基本構文であり、「その番号の要素」を取り出す処理を行う。

active_index は現在操作中の句を表す番号のため、上記コードでは今選択中の句(SlotColumn)を active_col として取り出す処理を行っています。


for slot in active_col.slots:
for 変数 in リスト: はリストの中身を1つずつ取り出して変数(ここでは slot)に入れ、その都度処理を行います。つまり、active_col(=現在の句)の中にある全ての Slot を、1つずつ取り出すループを行います。
なお、句の中のすべての Slot はSlotColumn にて slots にまとめて保持しているため、その句の中のスロットにアクセスするには active_col.slots である必要があります。


if not slot.card:
slot.card はそのスロットに既にカードが置かれているかどうかを表すため、空いているスロットを探す条件となります。


slot.card = instance
Slot クラスの冒頭にて、self.card = None と定義しているため、ここでスロットはまだ何も置かれていない状態です。
また、先に def on_card_select(self, instance): と定義しているため、instance は「押されたカード Button」を示します。
つまり、「このスロットは、このカードを保持している」という情報を Slot オブジェクトに保存しています。


instance.pos = slot.pos
押されたカードの位置を、スロットの位置に移動させています。


break
「今いるループ(for / while)を即座に終了して抜ける」 という命令です。
これをしなければ、空きスロットを見つけてカードを置いた後も残りの slot すべてをループし続けてしまいます。


self.active_index = (self.active_index + 1) % len(self.slot_columns)
次の句へ進むために active_index を1つ増やし、最後まで行ったら0に戻します。

% は剰余演算子であり、a % b ではa を b で割った “余り” を返します。

len() は「要素数を返す関数」であり、self.slot_columns には短歌の5つの句(SlotColumn)が入っているため、5を返します。


self.update_active_column()
現在アクティブな句の UI 状態を更新するためのメソッドを呼び出しています。

短歌完成処理

    def complete_tanka(self, instance):
        tanka = ""
        for col in self.slot_columns:
            for s in col.slots:
                if s.card:
                    tanka += s.card.text.split("\n")[0]
        if not tanka:
            self.result_label.text = "札を出してください。"
            return

        score, yaku = self.evaluate_tanka(tanka)
        self.result_label.text = f"短歌『{tanka}』 得点:{score}\n役:{yaku or 'なし'}"


def complete_tanka(self, instance):


tanka = ""
最初は空文字の短歌にし、短歌を作るための文字列を初期化します。


for col in self.slot_columns:
短歌を構成する5つの句のリストである self.slot_columns から1つずつ句を取り出して col に代入していくループです。このループで句(columns)を 1句目 → 2句目 → … →5句目 と順番に見ます。


for s in col.slots:
その句に含まれるスロット一覧である col.slots から1つずつ取り出して s に代入していくループです。このループで各句の中のスロットを1つずつ見ます。


if s.card:
s.card は「その Slot(s)に置かれているカード(札)」を示します。
Python では None は “偽 (False)” として扱われる値のため、このコードは「札が置いてあるスロットだけ処理する」という意味になります。


tanka += s.card.text.split("\n")[0]
tanka += Xtanka = tanka + X の省略形であり、今の tanka の末尾に X を追加(結合)します。

また、手札(self.card.text)は Button(text=f"{text}\n({length})") としており、これを .split("\n") で改行し分割します。(例:"〇〇\n(3)".split("\n") → ["〇〇", "(3)"]
そして [0] により分割したリストの0番目(=上の句の文字)だけを取り出します。
つまり、カードの文字そのものだけを取って、短歌に連結しています。


if not tanka:
self.result_label.text = "札を出してください。"
return

札が置かれていない場合、結果表示用のラベルである result_label により「札を出してください」と表示させます。
Pythonで return を書くとその場で関数の実行を終了し、それ以降の処理は一切実行されないことを利用し、tanka が空のまま評価することを防いでいます。


score, yaku = self.evaluate_tanka(tanka)
evaluate_tanka(tanka) を実行すると、後述の役演出から「スコア」と「文字列」の2つが返ってくるため、アンパック代入にて左辺の2つの変数に割り当てます。

Python の アンパック代入(tuple unpacking)とは「複数の値を、一度に複数の変数へ割り当てる構文」 のことです。
具体的には、右辺にある複数の値(タプル・リストなど)を、左辺の複数の変数にバラして(unpack して)代入します。


self.result_label.text = f"短歌『{tanka}』 得点:{score}点\n役:{yaku or 'なし'}"
結果表示用のラベルである result_label 及び f-string により「短歌『○○○○』 得点:○○点 役:○○」を表示させます。
なお、tanka は短歌文字列、scoreevaluate_tanka の戻り値、yaku は後述にて成立した役のリストです。

役演出

    def evaluate_tanka(self, tanka):
        roles = {
            "": 30, "": 20, "": 25, "": 15,
            "": 20, "": 30, "": 25, "": 20
        }
        score = random.randint(50, 80)
        yaku_hit = [k for k in roles if k in tanka]
        for k in yaku_hit:
            score += roles[k]
        return score, yaku_hit

def evaluate_tanka(self, tanka):
TankaScreen クラスの中に
「evaluate_tanka(短歌を評価する)」という名前のメソッドを定義しています。score, yaku = self.evaluate_tanka(tanka) からtankaを受け取り評価します。


roles = {…}
「役(やく)」の一覧を辞書(dict)として定義しています。


score = random.randint(50, 80)
最初に 基本点を50〜80点の乱数で決めています。ここは、後でAI評価を加えていけたらと思います。


yaku_hit = [k for k in roles if k in tanka]
リスト内包表記により、短歌に登場した役だけを集めたリストを作成しています。つまり「役の検出処理」を1行で行っています。

リスト内包表記とは、[new_item for item in collection if 条件] の形であり、今回の場合は下記の通りになります。

部分 意味
k 新しいリストに入れる値(=new_item)
for k in roles roles のキーを 1 つずつ取り出す(=for item in collection)
if k in tanka そのキーが tanka に含まれていれば入れる


for k in yaku_hit:
score += roles[k]

yaku_hit の中にある単語を1つずつ取り出してkに代入し、roles{…}の点数に応じた点数を加算します。
なお、score += roles[k]score = score + roles[k] の省略形です。


return score, yaku_hit
score(合計点)と yaku_hit(成立した役リスト)の2つを1セットとして呼び出し側に返します。つまり、score, yaku = self.evaluate_tanka(tanka) にタプルで返され、アンパック代入によりそれぞれ個別で受取ります。

最終実行

if __name__ == "__main__":
    TankaApp().run()    

このファイルが「メインの実行ファイル」として直接実行されたときだけ、TankaApp を起動するよう設定しています。

  • そのファイルが 直接実行された場合
    __name__= __main__
  • 他のファイルから import された場合
    __name__ = "ファイル名"

さいごに

ここまで読んでいただき、誠にありがとうございました。

次回からは学習記録トラッカーアプリの開発内容をまとめますので、そちらもお読みいただければと思います。

それではまた。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?