フランスの大学院で先生の仕事を行いながら、生徒のためにいつも新たな教え方を探しています。僕はプログラミングのレッソンを差し上げるので、そのお返しに生徒は応用問題をしなきゃいけません。

会社に仕事でも、学校の教育でも、先輩からもらったの感謝は第一な事って僕が絶対に思います。今回は先生として、その先輩になるだからこそ、感謝の正しい伝え方を探しに行きました。生徒の努力がちゃんと認めたら、自然に頑張ります。

生徒は得点を持ったなければならなくても、その先、応用問題がよくできたかどうかは伝えたい。でも結局、それは十分じゃないだと思いました。最初は、応用問題に関して生徒の努力をはっきり感謝したい。そうしたら頑張らなかった生徒は悔しいくて盛り上がって、よく頑張った生徒はもっと頑張ります。


得点のかわりは、何?

勇気付けられる物を考えたら「音ゲー」を思いだした、すなわち、リズムゲームです。一曲を遊んだ後で、リザルト画面は文字スコアで性能を見せます。

あまりできなくたら「B」スコアを当たって、普通にクリアしたら「A」スコアがあって、もっと上手なできたら「S」スコアがあって、それに完璧だったら「SSS」スコアもあります。これで性能はわかりやすいし、なら次回はもっと頑張りたいの感じがします。

これを思い出したとたん、文字スコアの利用をすぐに決めました。

「チューニズム」アケードゲームのリザルト画面(©SEGA)️

上のイメージ通り、文字スコアは一番大きいテキストですね。その他は、細かいことについてのデータが見せます。そのリザルト画面は基本的に明るくて、かっこくて、そして前回からの伸びがわかります。

文字スコアだけじゃなく、応用問題についての細かい説明がいいでしょう。例えばソースコードに関して評価が差し上げますね。応用問題の目的がちゃんとできたかどうか、構文規則が悪くかどうか、バグが多いかどうか。

「maimai」アケードゲームのリザルト画面(©SEGA)️

その上maimaiの画面は、プレイの時間と場所が見えます。じゃあ、応用問題の名前と日付も書きましょうか。


リザルト画面のやり方

最初はどこかで生徒の評価を書きなければなりません。簡単な記入のために、エクセル表計算のほうがいいだっと思った。その表計算は後でPythonで読みやすいし、そして余白イメージにデータを印刷するつもりです。


テンプレートのイメージ

さって、その印刷されられるイメージを作りましょう。いつも通り僕はAffinity Designerを使いますが、別のソフトでも大丈夫です。

後でPythonはイメージにテキストを印刷するので、空欄は必要でしょう。なんかリズムゲームのように感じがしたいから、カラフルと読みやすいのデザインでやりました。このファイルはtemplate.pngというにしましょう。

例のために日本語に変えちゃった

テキストを印刷ために、空欄の座標を書いておきましょう。イメージ本体のサイズは2048px*1024pxで、応用問題の名前の空欄は1296*196から始まるし、日付の空欄は1298*902から始まるし、などなど。

そして、全部の可能の文字スコアは別のイメージでおいて置きましょう。

文字スコアのイメージ

表計算の用意

評価はある表計算にまとめるつもりので、準備しましょう。上側には応用問題の名前、日付、関してレッソンの名前を書いておきます。次は、一行一人で生徒の評価が書いておきます。

例の表計算

Pythonを使うときのため、データはどこに置いたを覚えましょう。応用問題の名前は「B1」セル、日付は「D1」セル、レッソンは「F1」セルで置いておきました。そして二番の列のあとは、生徒の評価。一番目の生徒のランクは「C3」セル、構文規則ランクは「D3」セル、などなど。

このファイルはnotes.xslxというにしましょう。


Pythonで読むと印刷

テンプレートイメージと表計算が終わったと、Pythonで生徒の結果のイメージが印刷できます。

import os
import argparse

from PIL import Image, ImageDraw, ImageFont
from openpyxl import load_workbook

#色んなサイズでフォントの準備
font_bold_60 = ImageFont.truetype("MPLUS1p-Bold.ttf", 60)
font_bold_52 = ImageFont.truetype("MPLUS1p-Bold.ttf", 52)
font_bold_28 = ImageFont.truetype("MPLUS1p-Bold.ttf", 28)
font_medium_32 = ImageFont.truetype("MPLUS1p-Medium.ttf", 28)

#argparseのおかげで使いやすいになります
parser = argparse.ArgumentParser(description="ある応用問題の結果を印刷差し上げます。")
parser.add_argument("spreadsheet", help="表計算ファイルの名前")
args = parser.parse_args()

#ファイルを開く
excel = load_workbook(args.spreadsheet)
#一番目の表計算を使う
sheet = excel.active

#データを読んでおきます
exercice_name = sheet["B1"].value
exercice_date = sheet["D1"].value
exercice_lesson = sheet["F1"].value

#3行目から表計算を読んで、10列目まで
for row in sheet.iter_rows(min_row=3, max_col=10, values_only=True):
    #一つ行からデータを引き出す
    lastname, firstname, rank, syntax_rank, syntax_note, objective_rank, objective_note, quality_rank, quality_note, notes = row

    #ランクがない場合は多分、結果の打ち込みがまだ
    if rank is None:
        print(f"空きランク、スキップ...")
        continue

    print(f"--> {lastname} {firstname} ● ランク {rank}")

    #テンプレートのイメージを開く
    with Image.open("template.png") as im:
        draw = ImageDraw.Draw(im)

        # テキストの印刷
        draw.text((92, 80), f"{lastname}\n{firstname}", font=font_bold_52, fill=(0,0,0))
        #anchor="mm"の意味は、中央揃えで書く
        draw.text((1296, 196), exercice_name, font=font_bold_60, anchor="mm", fill=(255,255,255))
        draw.text((1298, 902), exercice_date.strftime("%d/%m/%Y"), font=font_bold_28, fill=(0,0,0))
        draw.text((1610, 902), exercice_lesson, font=font_bold_28, fill=(0,0,0))
        #ここからは複数行テキスト
        draw.multiline_text((500, 384), syntax_note or "", font=font_medium_32, anchor="lm", fill=(0,0,0))
        draw.multiline_text((500, 544), objective_note or "", font=font_medium_32, anchor="lm", fill=(0,0,0))
        draw.multiline_text((500, 704), quality_note or "", font=font_medium_32, anchor="lm", fill=(0,0,0))
        draw.multiline_text((280, 870), notes or "", font=font_medium_32, anchor="lm", fill=(255,255,255))

        #文字ランクのイメージのサイズが違っているので、
        #中央揃えのために、正しいx位置を書いておこう
        ranks_x_position = {
            "B": 1496,
            "A": 1466,
            "S": 1490,
            "SS": 1384,
            "SSS": 1278
        }

        #文字ランクのイメージの印刷
        with Image.open(f"rank_{rank}.png") as im_rank:
            im.paste(im_rank, (ranks_x_position[rank], 486), im_rank)

        #構文規則ランクのイメージの印刷
        with Image.open(f"rank_{syntax_rank}.png") as im_syntax_rank:
            im_syntax_rank.thumbnail((94, 94))
            im.paste(im_syntax_rank, (380 if syntax_rank != "A" else 375, 340), im_syntax_rank)

        #目的ランクのイメージの印刷
        with Image.open(f"rank_{objective_rank}.png") as im_objective_rank:
            im_objective_rank.thumbnail((94, 94))
            im.paste(im_objective_rank, (380 if objective_rank != "A" else 375, 500), im_objective_rank)

        #コード質のイメージの印刷
        with Image.open(f"rank_{quality_rank}.png") as im_quality_rank:
            im_quality_rank.thumbnail((94, 94))
            im.paste(im_quality_rank, (380 if quality_rank != "A" else 375, 660), im_quality_rank)

        #最後はイメージを保存する
        im.save(f"{firstname} {lastname} - {exercice_name}.png")

スクリプトを実行する前に、フォントのファイルをダウンロードしなきゃいけません。たとえばM+1フォントはGoogle Fontsからみつけます。そして、スクリプトはprinter.pyで呼ばれたら、notes.xlsxの表計算を使いたら、ターミナルで実行のはこういうになります。

$ python printer.py notes.xlsx

数秒の後で、一人生徒で一枚のイメージが保存されました。

できたイメージはこういうです。

生徒たちに影響

多分、一番知りたいことでしょうか。基本的に、生徒が自分の能力をちゃんとわかるように、そして後でどうしたらいい、役に立っただと思います。

それなのに、次の応用問題をもらったときに、たまに同じミスや悪いやり方を見つかりました。それはねえ、原因はいろいろだと思う。おそらく僕の説明が明らかなかったとか、締め切りの先に生徒が忙しい過ぎたとか、だからこそ責任は生徒だけじゃなく、学校や先生も責任があると思います。

とりあえず、結果の新しい伝え方法がみつけてうれしいです。せめて生徒が応用問題の結果にもっと気になって、次の応用問題に関して役に立って、よかったです。


読んでありがたいです。そのトピックで話したいなら、ツイッターは@komanakun、Wantedlyはこちらです。