「15パズル」クリア時間の計測

学習の目的

  • ・クリア時間の計測

ここでは、ゲームのクリアまでにかかる時間を表示します。

クリア時間の計測

時間の計測をして、最後に表示するプログラムを追加します。

時間の計測は、最初のクリックをしたところからとします。

パズルの時間ってドキドキしますよね。

できるだけ短い時間でクリアするぞ!!

今回修正を加えるのは、プログラム1とプログラム7です。新しいセルも作成します。

現状のプログラム1とプログラム7を書いておきますね。

プログラム1:盤面モデルの作成(修正前)

# プログラム1
def init():
    global tiles,moves
    tiles = [
        [1,2,3,4],
        [5,6,7,8],
        [9,10,11,12],
        [13,14,15,0]
    ]
    moves = 0

プログラム7:ボタン設定・配置(修正前)

# プログラム7
import ipywidgets as widgets
from IPython.display import display, clear_output
import random
# 表示を更新
def update_display():
    clear_output(wait=True)
    grid = []
    for i in range(4):
        row = []
        for j in range(4):
            num = tiles[i][j]
            if num != 0:
                label = str(num)
            else:
                label = ""
            btn = widgets.Button(description=label, layout=widgets.Layout(width='50px', height='50px'))
            def on_click(b, i=i, j=j):
                global moves
                if can_move(i, j):
                    move_tile(i, j)
                    moves = moves + 1
                    update_display()
            btn.on_click(on_click)
            row.append(btn)
        grid.append(row)

    # ステータス表示
    if is_goal():
        status = widgets.HTML(value="You Win!")
    else:
        status = widgets.HTML(value="")
    
    # リセットボタン
    reset_btn = widgets.Button(description="Reset", layout=widgets.Layout(width='100px'))
    def on_reset(b):
        global moves
        shuffle()
        moves = 0
        update_display()
    reset_btn.on_click(on_reset)

    #「」、数字ボタンの表示
    moves_lvl = widgets.HTML(value=f"移動回数: {moves}")
    display(widgets.VBox([
        widgets.GridBox(sum(grid, []), layout=widgets.Layout(grid_template_columns="repeat(4, 50px)")),
        status,
        moves_lvl,
        reset_btn
    ]))

①準備

新しいセルに、以下のプログラムを入れて実行してください。

import time

def format_mmss(sec):
    m = sec // 60
    s = sec % 60
    return f"{m:02d}:{s:02d}"

続いて、プログラム1に、以下の修正を入れて実行してください。

# プログラム1
def init():
    global tiles,moves,start_time #start_timeの追加
    tiles = [
        [1,2,3,4],
        [5,6,7,8],
        [9,10,11,12],
        [13,14,15,0]
    ]
    moves = 0
    start_time = None  # プレイ開始時刻

プレイ時間を計測する変数、start_timeを作成しました。

また、「秒」を、「分:秒」の文字列に変換する関数、format_mmssを作成しました。

start_timeにNoneが入っていますね。これはなんですか?

Noneとは、Pythonの「何もない」を表す特別な値です。

変数は作りたいけど、まだ何を入れるか決めることができない時に使います。

そうか、まだクリックしていない状態では時間計測を始めないから、Noneを入れておくんですね。


②クリック時に、start_timeに時間を設定

プログラム7に、以下の変更を加えます。

※ここで記載されている部分は、プログラムの一部です。

            def on_click(b, i=i, j=j):
                global moves , start_time # 修正点①
                if can_move(i, j):
                    move_tile(i, j)
                    moves = moves + 1 #movesに1を加算する処理を追加
                    if start_time is None:          # 追加
                        start_time = time.time()    # 追加
                    update_display()

クリックした時に実行する関数です。

19行目に、「start_time」の追加を行っています。

これで、この関数内でstart_timeを更新することができます。

23行目と24行目は新しく追加された処理ですね。

ここでは何をやっているんですか?

ここでは、start_timeに入っている値がNoneであるかを確認しています。

Noneであれば、現在の時刻(time.time())を取得して代入します。

そうか、if文で「start_timeがNoneの時」に、これを実行するから、2回目以降のクリックでは実行されないんだね。

その通りです。

Noneを確認するときは、「==」ではなくて、「is」を使います。

Noneの確認には、isを使うお約束があるんですね。


③Reset時は計測をリセット

プログラム7に、以下の変更を加えます。

※ここで記載されている部分は、プログラムの一部です。

前回と同様に、ここでは変更前のプログラムの行番号で記載します。

注意をしてください。

    def on_reset(b):
        global moves, start_time # 修正:start_timeを追加
        shuffle()
        moves = 0
        start_time = None       # 追加:Noneを代入
        update_display()

Resetボタンを押した時に実行する関数の中で、movesの値を0で初期化しています。


④クリアタイムの取得

プログラム7に、以下の変更を加えます。

※ここで記載されている部分は、プログラムの一部になるので注意してください。

元のプログラムの行番号を記載しているので行番号に注意してください。

    if is_goal():
        status = widgets.HTML(value="You Win!")
        if start_time:      # 追加
            elapsed = int(time.time() - start_time) # 追加
        else: # 追加
            elapsed = 0     # 追加
        time_html = widgets.HTML(value=f"時間: {format_mmss(elapsed)}")  # 追加
    else:
        status = widgets.HTML(value="")
        time_html = None  # 追加

上のプログラ31と32行目では、クリア時のみ時間を取得し、スタート時の時間との差を求めることで、時間の計測を行っています。

クリア時でなければ、Noneを入れることで、何も表示しないようにしています。


⑤クリアタイムの表示

プログラム7に、以下の変更を加えます。

※ここで記載されている部分は、プログラムの一部になるので注意してください。

ここは、大きく変更を加えています。元のプログラムの44行目〜50行目を全て、以下のプログラムに置き換えてください。

    #「」、数字ボタンの表示
    moves_lvl = widgets.HTML(value=f"移動回数: {moves}") # 移動回数を表示する部分を作成
    children = [
        widgets.GridBox(sum(grid, []), layout=widgets.Layout(grid_template_columns="repeat(4, 50px)")),
        status,
        moves_lvl,
        reset_btn
    ]
    if time_html is not None:      # ← クリア時だけ追加
        children.insert(2, time_html)  # status のすぐ下あたりに差し込む

    display(widgets.VBox(children))

ここで、クリア時に時間の表示を行うように設定しています。

もし、クリアが難しい場合は、以下の修正も加えてみてください。

プログラム8を以下のように修正し実行すると、簡単にクリアできます。

# プログラム8
# 初期シャッフル&表示
init()
shuffle(1) # シャッフルの回数を設定
update_display()

shuffleの引数の数が、シャッフル時の交換回数になっています。

ここを1にすることで、1つ動かすだけでクリアできる形になります。

実行結果例になります。

Well done!

次のステップに進みましょう!

次のステップに進む

← 前のステップにもどる