「15パズル」の作成⑥「実行」

学習の目的

  • 1.ボタン設定・配置

  • 2.実行

いよいよ、ゲームの完成です。

張り切っていきましょう!!

1.ボタン設定・配置

それでは、プログラムを作成しましょう。

先ほど利用したボタンを並べて、「15パズル」を表現します。

ボタンで扱うんですね!

ボタンを押して動かすわけか。

ボタン設定・配置

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

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]
            label = str(num) if num != 0 else ""
            btn = widgets.Button(description=label, layout=widgets.Layout(width='50px', height='50px'))
            def on_click(b, i=i, j=j):
                if can_move(i, j):
                    move_tile(i, j)
                    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):
        shuffle()
        update_display()
    reset_btn.on_click(on_reset)

    #「」、数字ボタンの表示
    display(widgets.VBox([
        widgets.GridBox(sum(grid, []), layout=widgets.Layout(grid_template_columns="repeat(4, 50px)")),
        status,
        reset_btn
    ]))

ちょっと長いプログラムですが、確認しましょう。

6行目

    clear_output(wait=True)

毎回、表示内容を全て消します。

これがないと、今まで表示されていた内容が残ってしまうためです。

7行目〜

    grid = []
    for i in range(4):
        row = []
        for j in range(4):
            num = tiles[i][j]
            label = str(num) if num != 0 else ""
            btn = widgets.Button(description=label, layout=widgets.Layout(width='50px', height='50px'))
            def on_click(b, i=i, j=j):
                if can_move(i, j):
                    move_tile(i, j)
                    update_display()
            btn.on_click(on_click)
            row.append(btn)
        grid.append(row)

ここでは、以下の内容を、4×4の16回行うことで、16個のボタンを配置しています。

・現在のtilesの内容を、各ボタンに設定する。

・クリックしたら交換を行う関数を作成する。

・その関数内で、交換をしたのち画面の再表示を行う。

・ボタンの設定が完了したら、表示する。

22行目~

    # ステータス表示
    if is_goal():
        status = widgets.HTML(value="You Win!")
    else:
        status = widgets.HTML(value="")

クリアしているかどうかを判断し、クリアしていたら、「You Win!」と表示する。

28行目~

        # リセットボタン
    reset_btn = widgets.Button(description="Reset", layout=widgets.Layout(width='100px'))
    def on_reset(b):
        shuffle()
        update_display()
    reset_btn.on_click(on_reset)

リセットボタンを押した時、最初の状態に戻れるようにシャッフルしなおします。

2.実行

いよいよ実行です。

以下のプログラムを打ちましょう。

実行

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

# 初期シャッフル&表示
shuffle()
update_display()

以下は、実行時に表示される内容と、クリア時に表示される内容です。

実行時の状態例

クリア時の状態

動かない場合はページ以下のコードをすべてコピーして実行しましょう。

プログラム1:盤面モデルの作成

盤面モデルとして、「15パズル」の4×4のマスを、二次元配列で表現します。

初期値として、クリアの状態が入っていますが、後でシャッフルされます。

tiles = [
    [1,2,3,4],
    [5,6,7,8],
    [9,10,11,12],
    [13,14,15,0]
]

プログラム2:空きマスの位置を確認する関数

呼び出すことで、空きマスの位置を戻り値として返す関数です。

def find_blank():
        for i in range(4):
            for j in range(4):
                if tiles[i][j] == 0:
                    return i, j

プログラム3:移動できるか確認する関数

引数で指定した位置が、移動できるか(空きマスが隣接しているか)を確認する関数です。

def can_move(i, j):
    bi, bj = find_blank()
    # 同じ行で横に1マス差 or 同じ列で縦に1マス差
    return (
        (i == bi and (j == bj + 1 or j  == bj - 1)) or
        (j == bj and (i == bi + 1 or i  == bi - 1))
    )

プログラム4:マスのスライドをする関数

指定したマスと空きマスの内容を交換します。

def move_tile(i, j):
    bi, bj = find_blank()
    temp = tiles[i][j]
    tiles[i][j] = tiles[bi][bj]
    tiles[bi][bj] = temp

プログラム5:クリアの状態を判定する関数

現在、クリアの状態かを確認する関数です。

クリアの状態なら、"true"を戻り値として返します。

# ゴール状態か確認
def is_goal():
    last_tiles = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,0]]
    return tiles == last_tiles

プログラム6:盤面モデルをシャッフルする関数

盤面モデルをシャッフルします。

空きマスへランダムに移動をおこないます。

import random
# 盤面モデルをシャッフル
def shuffle(num = 500):
    for _ in range(num):
        bi, bj = find_blank()
        options = []
        #空きマスの上が盤面モデルの中
        if bi - 1 >= 0:
            options.append([bi-1, bj])
        #空きマスの下が盤面モデルの中
        if bi + 1 < 4:
            options.append([bi+1, bj])
        #空きマスの左が盤面モデルの中
        if bj - 1 >= 0:
            options.append([bi, bj-1])
        #空きマスの右が盤面モデルの中
        if bj + 1 < 4:
            options.append([bi, bj+1])

        ni, nj = random.choice(options)
        move_tile(ni, nj)

プログラム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]
            label = str(num) if num != 0 else ""
            btn = widgets.Button(description=label, layout=widgets.Layout(width='50px', height='50px'))
            def on_click(b, i=i, j=j):
                if can_move(i, j):
                    move_tile(i, j)
                    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):
        shuffle()
        update_display()
    reset_btn.on_click(on_reset)

    #「」、数字ボタンの表示
    display(widgets.VBox([
        widgets.GridBox(sum(grid, []), layout=widgets.Layout(grid_template_columns="repeat(4, 50px)")),
        status,
        reset_btn
    ]))

プログラム8:実行

盤面モデルをシャッフルし、実行します。

# 初期シャッフル&表示
shuffle()
update_display()
    

Well done!

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

次のステップに進む

← 前のステップにもどる