勝ったときと負けたとせの画面を表示する 戦闘画面でく勝った〉ボタンをクリックしたらもとのマップに戻り、く負けた〉ボタン をクリックしたら失敗メッセージを表示するようにしましよう。 勝った場合、戦闘画面を非表示にするだけでなく、マップデータ上の敵を消して通過可 能にする必要もあります。ところが FightManage 「クラス内の関数から外部のデータ に直接アクセスすることはできません。そこで、先ほど fight ー sta 「 t 関数の引数でマップ データと主人公の座標を渡しておいたのです。マップデータはリストなのでミュータブ ル型です ( P. 165 参照 ) 。つまり、引数として渡した場合、呼び出し元が持っているデー タを書き替えることが可能になります。 chap7fight . py FightManager クラス chap7. py map_data «•••••x 外部にある 接な 直き タス デク ア chap7fight . py FightManager クラス def fight_start( ・・ def fight_win( ・・ chap7. py fight_start(map data, b 「 ave X, brave_y) map data 引数として渡して 記憶しておく ミュータブル型の アクセス 性質を利用して 実際にやってみましよう。 fight ー sta 「 t 関数の引数に渡されたマップデータと主人公の 現在地を、オプジェクトの属性として記憶しておきます。そして勝ったときに呼び出さ れる fight ー win 関数の中でマップデータを書き替えます。 chap7fight . py # 戦闘開始 def fight_start(self, map_data, x, y): self . dialog . place(x=l@, y=l@) self . map_data map_data self . brave X self . brave_y @ 1 8 @ 19 @21 @ 2 2 @ 2 3 212 Chapter
戦關画面き作ってみよっ 7 - 2 これで消えるようになりました。次はく負けた〉ボタンをクリックしたときに、バッド 工ンドのメッセージを表示します。本当は負けたら最初からゲームをやり直せるように したいのですが、今回はプログラムを終了してもらうことにします。 fight_win 関数のあとに fight-lose 関数を追加します。 6 章で作った ending 関数を参 考にしてください。 chap7fight . py self . dialog . place_forget() @ 2 8 # 敗北 @2 9 def fight_lose(self) : tkinter. Canvas(self. dialog, width=820, ( a n V a S @ 3 1 height=434) canvas . place(x=O, y=O) @3 2 canvas . create_rectangIe(), 0 , 620 , 434 , fill="red") @3 3 canvas . create text(300, 200 , @ 3 4 font=("MS ゴシック” , 15 ) , f 1 ll = ” w h i t e ” @ 3 5 text = ”””勇者は負けてしまった。 @3 6 @3 7 最初からやり直してくれたまえ。 ボタン作成のところでく負けた〉ボタンをクリックしたときに fight ー lose 関数を呼び 出すようにします。 Cha p 「 7 chap7fight . py # ボタン作成 tkinter . Button(se1f . dialog, text=" 勝った ") w i n b u t t 0 n winbutton . pIace(x=18@, y=34@) self . fight_win winbutton["command"] tkinter . Button(seIf . dialog, text=" 負けた ") 10 5 e b u t t 0 n losebutton . place(x=32@, y = 34 の losebutton["command ・・ ] = self . fight_lose @11 @ 1 2 @ 1 3 @14 @ 1 5 @ 16 @17 勇者は負けてしまった。 最初からやり直してくれたまえ。 0 く負けた〉ボタンをクリックするとバッドエンドのメッセージが表示される . 215
chap7. py import tkinter import chap7fight @@4 # マップの描画 同じフォルダー内にファイルがあればちゃんと入力支援も働きます。 chap7fight. py chap7. py PY 1 2 3 4 5 6 7 8 9 import tkinter from PIL import lmage, ImageTk import ( h ョ可 ( } chap6 物マップの描い ( h 叩 7 def draw ( } ( h 叩 7- ( 1a55 for y i い chap7fight for x in range(), MAX_WIDTH) : p = map-data[y][xl chap7fight ; Oimpo 杙文のあとで先頭数文字を入力すれば候補が出る は 2 つのファイルを切り替えながら進めるので、書くファイルを間違えないよう注意し 。ここから chap7fight. py に戦闘画面を管理する FightManage 「クラスを作成します FightManager クラスと戦闘画面を作成する モジュールと呼ぶのです。 作られていると予想できますね。この別ファイルにクラスなどの定義をまとめたものを impo 「 t 文で読み込めるということは、これまで使ってきた tkinte 「なども同じように ・ ch 叩 7. py 工クスプローラー ファイ IIÅF) 編 *(E) 選 ( 5 ) 刈 ( h 叩 7fig - python てください。 3 2 from PIL import lmage, ImageTk import tkinter chap7fight. py X chap7. py 表示移動向へルプ ( H ) - Ⅵ su Studio 204 し、コンストラクタを書いていきます。 こちらでも tkinte 「を使用するのでインポートしておき、 FightMange 「クラスを定義 0 複数のファイルを開いておけば、画面上部のタブで切り替えられる
7 - 2 戦闘画面き作ってみよっ # 勝利 def fight_win(self): self . map_data[self . brave_yl [self . brave_x] self . dialog. place_forget() ボタン作成のところでく勝った〉ボタンをクリックしたときに fight ー win 関数を呼び出 すようにします。 @2 4 @2 5 @2 6 @2 7 0 chap7fight . py # ボタン作成 tkinter . Button(seIf . dialog, text=" 勝った ") w i n b u t t 0 n winbutton . p1ace(x=18@, y=34@) winbutton["command"] self . fight_win tkinter . Button(self . dialog, text=" 負けた ") losebutton @ 11 @ 1 2 @ 1 3 @ 14 @ 1 5 ′タンジョン & バイソン ′タンジョン & バイソン 0 00000 00 000 0 00000 ロロ 0000000 Cha p 「 7 0 く勝った〉ボタンをクリックするとマップ画面に戻る できましたね。ところが移動してみるとモンスターがまだ残っています。通過できる のですが、モンスターのいる部分が地面に変わっていません。 0 通過できたがモンスターは消え ていない ? 213
ボタン 攻撃 攻撃 攻撃 攻撃 関数 ボタン無効化 自分が攻撃したときの計算 ボタン有効化 結果表示 敵が攻撃したときの計算 結果表示 自分のター 敵のター ン ン 今回のゲームでは〈攻撃〉ボタンとく力をためる〉ボタンの 2 つがあり、攻撃力の計算方 法は異なりますが、そこから先の処理は同じなので 1 つの関数にまとめられるはずです。 大まかな設計が決まったところで、プログラムを書いていきましよう。一瞬で戦闘が 進んではあまりゲームらしくないので、 5 章でも使用した引 eep 関数を利用できるよう、 time モジュールをインポートしておきます。 chap7fight . py import time import random impo 「 t tkinter を渡せるようにし、 FightManage 「クラスの属性として記録します。 主人公の HP などの情報も必要です。引数を 1 つ増やして B 「 ave クラスのオプジェクト 戦闘を開始する fight ー sta 「 t 関数はマップデータと現在地の座標を渡していましたが、 主人公の情報を FightManager クラスに渡す chap7fight . py @2 2 @2 3 @2 4 @2 5 228 # 戦闘開始 def fight_start(self, mapdata, x, y, self . dialog . place(x=l@, y = 10 ) self . map_data mapdata brave) :
動先をチェックする check ー move 関数を書き替えればよさそうですね。マップデータ が 5 以上のときに、あとで追加する fight ー sta 「 t 関数を呼び出すようにします。この関数 にはマップデータと座標を渡します。 chap7. py @ 1 7 # 移動先のチェック @ 18 def check_move(), y): @ 19 @21 @ 2 2 @2 3 @2 4 @2 5 @2 6 @2 7 @2 8 @ 2 9 @31 @3 2 @3 3 @3 4 @3 5 @3 6 @3 7 @ 3 8 global brave x, brave_y, flag_key if X>=@ and x く MAX WIDTH and Y>=@ and y く MAX_HEIGHT: map—data[y][x] P r e t u 「 n e 1 i f p flag_key True map—data[y][x] c a n v a 5 . d e 1 e t e ( " a 11 " ) el i f p > = 5 : r e t u 「 n e 1 5 e : e n d i n g ( ) if flag_key eli f p draw_map() ( a n V a S . C 00 「 d 5 ( ” b 「 a V e brave x * 62 + 31 , brave y * 62 + 31 ) brave_y b 「 a V e X fightmanager . fight_start(map_data, x, y) chap7fight. py に切り替えて fight-start 関数を追加しましよう。 p [ ace 関数を使って dialog を再表示します。 chap7fight . py @@ 1 210 Chapter import tkinter class FightManager: # コンストラクタ def ( 5 e 1 f ) : self . dialog . place(x=l@, y=l@) self . dialog tkinter . Frame(width=82@, height=434) C a n V a 5 height=434) tkinter.Canvas(seIf . dialog, width=82@,
chap7-class . py @@ 5 # クラスの定義 ( 1 a 5 5 C h a r aTe 5 t : d e f f i g h t ( s e 1 f ) : p r i n t ( " 戦うそ ! ” ) # クラスの使用 charal CharaTest() charal . fight() PS ( : \Users\ohtsu\Documents\python 〉 python chap7-c1ass. py 戦うそ ! PS ( : \Users\ohtsu\Documents\python> 0 実行結果 fight 関数には実際には使っていない self という引数を指定していました。この self はオプジェクト自身を表す特殊な引数です。使い方は次のページ説明しますが、 se [ f を指定しなかった場合、関数を呼び出したときに「関数名 ( ) takes O positional a 「 guments but 1 was given ( O 個の引数を取る仕様なのに 1 つ渡された ) 」という工 ラーが発生します。結構つまずきやすいところなので、注意してください。 PS C: \Users\ohtsu\Documents\python> python chap7-c1ass. py Traceback (most recent ( a11 last) : FiIe "chap7-c1ass . py", line 7 , in く module> charal . fight() TypeError: fight( ) takes 9 positional arguments but 1 was given PS ( : \Users\ohtsu\Documents\python> 0 self を書かなかった場合に表示されるエラー いらないのに 書なせやいけない なんて不思議ね ? 198 Chapter 0
7 - 4 本番の戦闘画面き作成しよっ 勝ち負けをチェックして次のターンに進む 主人公が攻撃して勝った場合、つまりモンスターの HP が O になった場合、戦闘画面を 消してマップ画面に戻ります。このチェックをするのは主人公のターンとモンスターの ターンの間です。 次の戦闘のためにボタンの無効化を解除しておき、 fight-win 関数を呼び出して retu 「 n 文で脱出します。 chap7fight . py labeltext 1 a b e 1 t e x t + \ ハ n モンスターの残り体力は” + str(self . monster . hp) s e 1 f . 1 a be 1 [ " t ex t " ] 1 a b e 1 t e x t self . dialog . update() i f 5 e 1 f. mon 5 t e 「 . hp く 1 : time . sIeep(2) # 2 秒待ち self . fbutton["state"l self . rbutton["state"l self . fight_win() 「 e t u 「 n # モンスターのターン モンスターが攻撃して勝った場合、つまり主人公の HP が O になった場合、バッドエン ディング画面を表示します。主人公の HP がまだ残っている場合は、ボタンを有効化し て関数を終了します。主人公の HP をチェックするのはモンスターのターンのあとです。 @8 5 @8 6 @8 7 @8 8 089 @91 @9 2 @9 3 @9 4 @9 5 ” no 「 maI ” "normal" ( hap 「 7 chap7fight . py 1 a b e 1 t e x t 1 a be 1 t e x t + \ " \ n 勇者の残り体力は " + str(self . brave . hp) 5 e 1 f . 1 a be 1 [ ” t e x t ” ] 1 a b e 1 t e x t self . dialog . update() i f 5e1 f . brave . hp く 1 : time . s1eep(2) # 2 秒待ち self. fight_lose() # ボタンを有効化して次のターンへ self . fbutton["state"l “ no 「 mal ” self . rbutton["state"l ” no 「 maI ” モンスターと戦って結果を確認してみましよう。ちゃんと倒せましたか ? 119 12@ 1 2 4 12 6 12 7 12 8 12 9 else: 23 /
PS ( : \Users\ohtsu\Documents\python> python chap7. py Traceback (most recent ( a11 last) : line 115 , in く module> Fi1e "chap7. py" print(brave. get_atk( ) ) Fi1e " ( :\Users\0htsu\Documents\python\chap7fight. py" self . rsv r ・ Brave' object has no attribute ・ rsv ・ AttributeError : PS ( : \Users\ohtsu\Documents\python> 0 工ラーが出てしまった line 55 , in get_atk 「 Att 「 ibuteE 「「 0 「 : 'B 「 ave' object has no attribute ' 「 sv' ( 属性工ラー : B 「 ave オプジェ を呼び出したときに「属性がない」というエラーが出てしまったのです。 ん。 B 「 ave ワラスのコンストラクタでは「 sv 属性を追加していないので、 get-atk 関数 実は子クラスに同じ名前の関数を書いた場合、親クラスの同名関数は呼び出されませ のコンストラクタで追加したものが反映されていないということですね。 クトは「 sv 属性を持っていない ) 」というエラーが出てしまいました。 Cha 「 acte 「クラス chap7. py print(brave. get_atk()) brave chap7fight . Brave() chap7fight . py ( 1 a s s C h a 「 a ( t e 「 : def i n i t 5 e 1 f . 「 5 v 呼び出され (self) : 1 class Brave(Character): init_(self): def self . name = " 勇者ハル” この場合の基本的な解決策は、親クラスにアクセスできる supe 「関数を使って子ク ラスから親クラスの同名関数を呼び出すというものです。コンストラクタの場合は 「 supe 「 (). —init—(self) 」と書きます。 chap7. py brave chap7fight . Brave() print(b 「 ave . get_atk()) 224 chap7fight . py ( 1 a 5 5 C h a 「 a ( t e 「 : 自分で 呼び出す def i n i t 5 e 1 f . 「 5 v (self) : 1 ( 1a55 Brave(Character): init_(self): def init (self) super() . self . name = " 勇者ハル "
本番の戦闘画面き作成しようを 7 - 4 阯戦闘の処理を作 3 う click_fight 関数と click- 「 ese 「 ve 関数の中身を書いていきます。戦闘処理のメインを 書く d 〇一 turn 関数も追加しておきましよう。 chap7fight . pys # 攻撃ホタン def click_fight(self): ” disabled" self . fbutton["state"l "disabled" self . rbutton["state"] self . do turn(self . brave . get_atk()) # 力をためるボタン def click_reserve(self): self . fbutton["state"l self . rbutton["state"l self . brave . reserve() 5 e 1 f . d 0 t u 「 n ( - 1 ) # 戦闘処理 def do turn(self, brave atk) : paSS click ー fight 関数では 2 つのボタンを無効化したら、 B 「 aveO ラスの get-atk 関数を呼 び出して主人公の攻撃力を求めます。 do ー tu 「 n 関数を呼び出すときは攻撃力を渡します。 click ー「 ese 「 ve 関数ではボタンの無効化後に B 「 ave クラスの「 ese 「 ve 関数を呼び出し て力をためます。 d 〇一 tu 「 n 関数を呼び出すときは、攻撃をスキップすることを伝えるた めに - 1 を渡します。 @ 5 1 @ 5 2 @ 5 3 @5 4 @ 5 5 @5 6 @ 5 7 @ 5 8 @ 5 9 @61 @ 6 2 @6 3 064 ” disabled" ” disabled" Chap 「 7 同しオフジェクトの 関数を呼ひ出すとせは 「 self. 」き忘れるなよ d 〇一 tu 「 n 関数では主人公のターンの処理から書いていきます。ラベルに表示するメッ セージはローカル変数 [ abe [ text に記憶し、戦闘が進むにつれて少しずつ追加されるよ うにします。 233