新しいセルに、以下のプログラムを完成させて実行してください。
このプログラムは、引数で受け取ったリスト「lb」を時間(lb[添字]["time"])の昇順で整列します。
時間が等しい場合は、移動回数(lb[添字]["moves"])の小さい方を先にします。
ここでは、今までに学んだ3つの整列法、それぞれで表現したものが、下の三つのプログラムです。
情報活用編プラス
第3章「コンピュータとプログラミング」
IP02-14
ゲームを作ってみよう②
学習の目的
・ランキングの表示
最後に、ゲームクリア後にランキングの表示を行いましょう。
今回は、架空の得点をいくつか用意しておき、クリア後に自分のデータと比較してランキング表示を行います。
ランキングの対象になるのは、クリアタイムです。
ランキングがかかってくると、燃えてくるよね!!
絶対一位目指すぞ!!
それではいきましょう。
現状のプログラム7を書いておきますね。
# プログラム1
def init():
global tiles,moves,start_time
tiles = [
[1,2,3,4],
[5,6,7,8],
[9,10,11,12],
[13,14,15,0]
]
moves = 0
start_time = None
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):
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()
btn.on_click(on_click)
row.append(btn)
grid.append(row)
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 # 追加
# リセットボタン
reset_btn = widgets.Button(description="Reset", layout=widgets.Layout(width='100px'))
def on_reset(b):
global moves # movesを関数内で使用するための宣言を追加
shuffle()
moves = 0 # movesを0で初期化する処理を追加
update_display()
reset_btn.on_click(on_reset)
moves_html = widgets.HTML(value=f"移動回数: {moves}")
# VBox に time_html を条件付きで挿入
children = [
widgets.GridBox(sum(grid, []), layout=widgets.Layout(grid_template_columns="repeat(4, 50px)")),
status,
moves_html,
reset_btn
]
if time_html is not None: # ← クリア時だけ追加
children.insert(2, time_html) # status のすぐ下あたりに差し込む
display(widgets.VBox(children))
新しいセルに、以下のプログラムを入れて実行してください。
# ランキング(time=秒, moves=手数)
leaderboard = [
{"name": "JUN", "moves": 160, "time": 230},
{"name": "RIN", "moves": 110, "time": 200},
{"name": "AAA", "moves": 120, "time": 125},
{"name": "MAO", "moves": 140, "time": 250},
{"name": "KAZ", "moves": 150, "time": 170},
]
TOP_K = 10
PLAYER_NAME = "YOU" # 任意の表示名
続いて、プログラム1に、以下の修正を入れて実行してください。
# プログラム1
def init():
global tiles,moves,start_time,ranked #rankedの追加
tiles = [
[1,2,3,4],
[5,6,7,8],
[9,10,11,12],
[13,14,15,0]
]
moves = 0
start_time = None
ranked = False # クリア記録を「今回分」を登録済みかどうか
まずは、架空のメンバー5人分の得点を保存しました。
これは、どんなデータなんですか?
辞書データと、リストの組み合わせです。
辞書データとは、リストのようにインデックスを数値ではなく、キーの値、例えば文字列で表したものです。
キーの値?
今回だと、"name"や、"time"がそれにあたります。
例えば、以下のプログラムを打つと、データの内容が確認できます。
print(leaderboard[0]["name"]) # JUNと表示
print(leaderboard[2]["time"]) # 125と表示
なるほど、"name"に名前、"moves"に移動回数、"time"にクリア時間が記録されているんだね。
以前に学んだ整列法を使って、ランキング表を整列しましょう。
新しいセルに、以下のプログラムを完成させて実行してください。
このプログラムは、引数で受け取ったリスト「lb」を時間(lb[添字]["time"])の昇順で整列します。
時間が等しい場合は、移動回数(lb[添字]["moves"])の小さい方を先にします。
ここでは、今までに学んだ3つの整列法、それぞれで表現したものが、下の三つのプログラムです。
def sort_leaderboard(lb):
n = len(lb)
for i in range(n - 1):
min_idx = i
for j in range(i + 1, n):
a = lb[j]
b = lb[min_idx]
if (a["time"] < b["time"]) or (a["time"] == b["time"] and a["moves"] < b["moves"]):
min_idx = j
temp = lb[i]
lb[i] = lb[min_idx]
lb[min_idx] = temp
このプログラムは、どの整列法(交換法・選択法・挿入法のどれか)でしょうか?
これは、繰り返しの中で最も小さい値を選んで、最後に交換していますね。
def sort_leaderboard(lb):
n = len(lb)
for i in range(n - 1):
swapped = False
for j in range(n - 1 - i):
if (lb[j + 1]["time"] < lb[j]["time"]) or (lb[j + 1]["time"] == lb[j]["time"] and lb[j + 1]["moves"] < lb[j]["moves"]):
temp = lb[j]
lb[j] = lb[j + 1]
lb[j + 1] = temp
swapped = True
if swapped == False:
break
このプログラムは、どの整列法(交換法・選択法・挿入法のどれか)でしょうか?
隣同士の比較をしているようです。
def sort_leaderboard(lb):
n = len(lb)
for i in range(1, n):
key = lb[i]
j = i - 1
while j >= 0 and (
(key["time"] < lb[j]["time"]) or (key["time"] == lb[j]["time"] and key["moves"] < lb[j]["moves"])
):
lb[j + 1] = lb[j]
j = j - 1
lb[j + 1] = key
このプログラムは、どの整列法(交換法・選択法・挿入法のどれか)でしょうか?
データを一つずつ後ろへずらしているようです。
3つの整列法のうち1つを選んで、新しいセルに入力し、実行してください。
どれを選んでも結果は変わらないので、好きなものを選んでください。
新しいセルに、以下のプログラムを入れて実行してください。
def build_leaderboard_html(lb):
rows = []
for idx, rec in enumerate(lb, start=1):
rows.append(
f"<tr>"
f"<td>{idx}</td>"
f"<td>{rec['name']}</td>"
f"<td style='text-align:right;'>{format_mmss(rec['time'])}</td>"
f"<td style='text-align:right;'>{rec['moves']}</td>"
f"</tr>"
)
return (
"<table style='border-collapse:collapse; font-family:sans-serif;'>"
"<tr><th style='text-align:left;'>RANK</th>"
"<th style='text-align:left;'>NAME</th>"
"<th style='text-align:right;'>TIME</th>"
"<th style='text-align:right;'>MOVES</th></tr>"
+ "".join(rows) + "</table>"
)
ここでは、ランキングを表示するためのHTML文書を作成しています。
引数で先ほど作成したleaderboardを渡すと、ランキング表の表示を行います。
HTMLについての説明は、ここでは省きますね。
プログラム7に、以下の修正を加えます。
※ここで記載されている部分は、プログラムの一部になるので注意してください。
元のプログラムの行番号です。コピーしたプログラムを貼り付ける位置に注意してください。
if is_goal():
status = widgets.HTML(value="You Win!")
elapsed = int(time.time() - start_time) if start_time else 0
time_html = widgets.HTML(value=f"時間: {format_mmss(elapsed)}")
# 追加ここから
global ranked
if not ranked:
leaderboard.append({"name": PLAYER_NAME, "moves": moves, "time": elapsed})
sort_leaderboard(leaderboard)
del leaderboard[TOP_K:] # 上位のみ保持
ranked = True
# 追加ここまで
else:
status = widgets.HTML(value="")
time_html = None # 追加
ここで、クリアした時に自分の記録を追加し、整列をしています。
プログラム7に、以下の変更を加えます。
※ここで記載されている部分は、プログラムの一部になるので注意してください。
ここは、大きく変更を加えています。元のプログラムの54行目〜57行目を全て、以下のプログラムに置き換えてください。
if time_html is not None: # ← クリア時だけ追加
children.insert(2, time_html)
# 追加ここから
rank_title = widgets.HTML(value="ランキング")
rank_box = widgets.HTML(value=build_leaderboard_html(leaderboard))
children.extend([rank_title, rank_box])
# 追加ここまで
display(widgets.VBox(children))
これで、クリア時にランキングが表示されるはずです。
ただ、リセット時の処理も追加しましょう。
以下の図は実行例です。
プログラム8を実行し、ゲームをプレイましょう。
これで、クリア時にランキングが表示されるはずです。
以下の図は実行例です。
うまくできましたか?
お疲れ様でした。
※この再現版では提出は行いません(送信先は未接続です)