センター試験「情報関係基礎」2020年の問3 宝探しをプログラミングする:つづき
まず,宿題の答えから。
def up():
dx = 0
dy = -1
を定義して,ボタンを作り,この関数を呼ぶようにした。しかし動かないのは何故か。
dx , dy がグローバル変数だからだ。関数の中では参照はできるが値は変更できない。といって,dx , dy をボタンで変更する方法は他にない。したがって,
def up():
global dx
global dy
dx = 0
dy = -1
とするというのが正解。他の関数もすべて同様。変数の値を変更している関数では,それらをすべて global で宣言しなければならない。たとえば,図2の,移動ボタンが押されたときの手続きでは
# 移動ボタンが押されたときの手続き (図2)
def move():
global robox
global roboy
global nokori
global zyotai
global message
message = ""
if (robox+dx > 0 and robox+dx <= YOKO and
roboy+dy > 0 and roboy+dy <= TATE):
robox = robox + dx
roboy = roboy + dy
nokori = nokori - 1
if takarax == robox and takaray == roboy:
message = "宝を見つけた! 宝探し成功!"
# 罠かどうかの判定をして,画面を再描画
hantei()
banmen()
とする。いやー,面倒。(なお,zyotai はあとで追加するので書いておいた)
ところで,図5のプログラムは,前半と後半に分けた。この部分だ。
12行目以降を,detection() として定義し,ボタンに割り当てる。
Cindyscriptで作ったときは,ボタンに設定できるスクリプトのなかで,ボタン番号を書いている。 Button = 1 のように。したがって,問題通りに,
// 行動 (図5)
action():=(
if(Button==1,
move());
,
repeat(WANASUU,i,
・・・・
と書けた。「罠探知ボタンならば」はいらない。移動ボタンでなければ罠探知ボタンだからだ。
しかし,前回書いたものだと,
btn5 = tk.Button(text="移 動",command = move)
btn6 = tk.Button(text="罠探知", command = detection)
としてボタンを作っている。command オプションによって,それぞれの関数が呼べるが,引数を渡すことができない。したがって,呼び出す関数をCindyscriptのように action() として同じものを呼び出すと,どちらのボタンを押したかがわからないのだ。そこで,関数も2つに分けた。そのために,問題の図5とは違う形になった。(このことはあとで解決する)
次に,図6のプログラム。
一見すると,01,02 行目は初期設定で,07行目からはwhile を使って,これまでに作った関数を呼び出せばよさそうだ。しかし,この通りにはできない。
と書いた。
というのは,Tkinter の仕様のためだ。Tkinter では,最後に root.mainloop() を書くことでプログラムが実行され,イベント待ちになる。いわば,無限ループに入るのだ。while でループしているのと同じ。描画ウィンドウを閉じればループも終わる。したがって,while 文にする必要はない。
問題には続きがある。
これができれば,問題にすべて答えたことになる。
授業では,図5を action() としているので
図7のコードは,図6の(29-37)行目「罠のマス判定手続の直後に挿入」することになっているが,その前に挿入してもよい。
したがって,行動指定ボタンが押されたときの手続きである action() の末尾に入れる。[タ] の修正もした上で,action() の末尾に書き込みなさい。
としている。
問題は,大学入試センターなどで入手できるので,挑戦してみたらどうだろう。図5の形が変わっているので,アルゴリズムをしっかり考えないと適切なところに入れることができないから,少し難しくなっている。
さて,ここまでできても,実は,ゲームとして完成したとは言えない。早くできた生徒のために,次の課題を与えている。
(1) 罠探知で,罠がなかったときメッセージがない。
そこで,「罠はなかった」というメッセージを出すようにする。
(2) 罠に落ちて「ダメージを受けた」というメッセージは出るが,実際にはダメージがない。試行回数を1回減らすというダメージを与える。
また,Cindyscriptでは,クリックした方向指定ボタンを「押された」状態にできるので,今どちらへ進もうとしているかがわかる。しかし,Pythonのボタンではそれができないので(仕様としてはあるようだがその通りに動かない),ボタンを押したらどちらへ進もうとしているかを表示したい。
さらに,ゲームが終わったとき,ソースに戻って再実行しないとやりなおしができない。やはり,「もう一度やる」といったボタンを用意して,何度でもできるようにしたいものだ。これは少し難しい。
授業ではそこまで用意したものを作っておいて,問題の図2〜図5の中身を書かせて完成するようにしたい。
なぜ,そこまでやるのか。
書いたプログラムが実際に動くからプログラミングは楽しいのだ。
紙の上で問題を解くだけで,どこが面白いというのだろう。
やはり,プログラミングは動いてこそ面白い。動かなかったもの(バグ)を直して,動くようになった時が楽しい。授業での「動いた!」という生徒の声がそれを物語っている。
何度でも書く。だから,そのための教材を作るために,教員は生徒の何倍のものスキルを要するのだ。
では,問題の図6までをやって,動くようになっているプログラム全体を掲示する。コピペすれば動くと思うが,場合によってはインデントの修正が必要かもしれない。それと,ロボット,宝,罠の画像が必要。フリーの素材を使って,ひとマス 50×50ピクセルに入るサイズにしておく。ファイル名は,robot.png , takara.png, wana.png だ。これを同じディレクトリに置く。
このあと,図7,および空欄シ〜タに該当するものをいれて完成してもらいたい。さらに,これのどこを変えて完成させ,どう教材化するか。それは2,3日後に示そう。
import numpy as np
import tkinter as tk
root = tk.Tk()
root.title("宝探しゲーム")
root.geometry("700x350")
canvas = tk.Canvas(root, width=402, height=252, bg="white")
# 初期位置設定 マスの番号は1からカウント
YOKO = 8
TATE = 5
Shokikaisuu = 9
WANASUU = 7
Wanahyoji = np.zeros(WANASUU)
Wanax = [2, 3, 3, 4, 5, 5, 6]
Wanay = [3, 2, 4, 2, 4, 5, 5]
direcstr = ["","上","下","左","右"]
wana = tk.PhotoImage(file="wana.png")
robot = tk.PhotoImage(file="robot.png")
takara = tk.PhotoImage(file="takara.png")
zyotai = 0
miss = 0
nokori = Shokikaisuu
robox = 6
roboy = 2
takarax = 2
takaray = 5
message = ""
direc = 0
hosuu = 0
dx = 0
dy = 0
def up():
global dx
global dy
dx = 0
dy = -1
def down():
global dx
global dy
dx = 0
dy = 1
def left():
global dx
global dy
dx = -1
dy = 0
def right():
global dx
global dy
dx = 1
dy = 0
# 移動ボタンが押されたときの手続き (図2)
def move():
global robox
global roboy
global nokori
global zyotai
global message
message = ""
if (robox+dx > 0 and robox+dx <= YOKO and
roboy+dy > 0 and roboy+dy <= TATE):
robox = robox + dx
roboy = roboy + dy
nokori = nokori - 1
if takarax == robox and takaray == roboy:
message = "宝を見つけた! 宝探し成功!"
# 罠かどうかの判定をして,画面を再描画
hantei()
banmen()
# 最小歩数の計算 (図3)
def calc():
global hosuu
sax = takarax - robox
say = takaray - roboy
if sax < 0:
sax = sax * (-1)
if say < 0:
say = say * (-1)
hosuu = sax + say
#calc()
# 罠のマス判定 (図4)
def hantei():
global miss
global nokori
global message
global zyotai
for i in range(WANASUU):
if Wanax[i] == robox and Wanay[i] == roboy:
message = "罠にかかった! ダメージを受けた!"
miss = miss + 1
if miss == 3:
message = "3回目だ! ついに壊れた・・宝探し失敗"
# 罠探知 (図5後半)
def detection():
global message
global nokori
global zyotai
for i in range(WANASUU):
if Wanax[i] == robox+dx and Wanay[i] == roboy+dy:
message = "罠を発見した"
Wanahyoji[i] = 1
nokori=nokori-1;
#盤面を再描画
banmen()
# 盤面の再描画
def banmen():
calc()
for i in range(WANASUU):
if Wanahyoji[i] == 1:
canvas.create_image(
Wanax[i]*50, Wanay[i]*50,
image=wana,
anchor=tk.SE
)
canvas.moveto("robo", robox*50-38, roboy*50-45)
info1.config(text="宝までの最小歩数 "+str(hosuu))
info2.config(text="残り操作回数 "+str(nokori))
info3.config(text="罠にかかった回数 "+str(miss))
info4.config(text=message)
# 関数の定義はここまで
# 盤面の描画
unitw = 50
for x in range(YOKO):
for y in range(TATE):
px = x * unitw + 3
py = y * unitw + 3
canvas.create_rectangle(
px, py, px + unitw, py + unitw
)
# TclError: image "pyimage4" doesn't exist
# というタイプのエラーが出たら,1 == 0 にして実行する。
# ウィンドウが複数(エラーの時のもの)出るのでそれらを消し
# エラー箇所を直したら 1 == 1にして再実行する。
if 1 == 1:
canvas.create_image(
robox*50-38, roboy*50-45,
image=robot,
anchor=tk.NW,
tag = 'robo'
)
canvas.create_image(
takarax*50, takaray*50,
image=takara,
anchor=tk.SE
)
btn1 = tk.Button(text="上", command = up)
btn2 = tk.Button(text="下", command = down)
btn3 = tk.Button(text="左", command = left)
btn4 = tk.Button(text="右", command = right)
btn5 = tk.Button(text="移 動",command = move)
btn6 = tk.Button(text="罠探知", command = detection)
info1 = tk.Label(text="宝までの最小歩数 "+str(hosuu))
info2 = tk.Label(text="残り操作回数 "+str(nokori))
info3 = tk.Label(text="罠にかかった回数 "+str(miss))
info4 = tk.Label(text=message)
canvas.place(x=50, y=50)
btn1.place(x=500, y=75)
btn2.place(x=500, y=125)
btn3.place(x=470, y=100)
btn4.place(x=525, y=100)
btn5.place(x=600, y=80)
btn6.place(x=600, y=120)
info1.place(x=50, y=315)
info2.place(x=200, y=315)
info3.place(x=350, y=315)
info4.place(x=470, y=225)
root.mainloop()