0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【アプリ開発】pythonで簡単RPGの作成

Last updated at Posted at 2025-06-22

はじめに

昔から某RPGが大好きで、いつか勇者になれたらと思った過去もありました。
今回は学習しているpythonを通して、RPGの作成をしながら学んでいきたいと思います。
勉強の復習にもつながるように、内容はある程度詳細として表記をしてみました。

要件定義

サーバーサイド:python
フロントエンド:tkinter

ゲーム構成
はじまりのまち
↓ドロップアイテムの選択
中間町
↓ドロップアイテムの選択
魔王を倒す

選択したドロップアイテムにより、主人公に補正がかかるので、最終戦での戦いやすさが変化する。

作成過程

gui_game.py
import tkinter as tk
from tkinter import messagebox
import time
import random
from PIL import Image, ImageTk # Pillowライブラリをインポート

from main import create_player, items_data, enemies, locations, calculate_damage

class RPGGameGUI:
    def __init__(self, master):
        self.master = master
        master.title("勇者みならいの冒険")
        master.geometry("800x600")
        master.resizable(False, False)

        self.player = None
        self.current_location_key = None
        self.current_enemy = None
        self.current_enemy_key = None

        # キャラクター画像のパスを定義
        self.enemy_images = {
            "ゴブリン": "img/ゴブリン.png",
            "ゴブリンキング": "img/ゴブリン.png", # ゴブリンキングの画像がないため、一旦ゴブリンを代用
            "オーク": "img/オーク.png",
            "魔導士": "img/魔法使い.png", # ファイル名が「魔法使い.png」のため修正
            "デビル": "img/デビル.png",
            "魔王": "img/maou.png"
        }
        self.loaded_images = {} # 画像オブジェクトを保持するための辞書

        self.create_widgets()
        self.create_battle_widgets()
        
        self.start_game_setup()

    def create_widgets(self):
        self.master.configure(bg="#2c3e50")

        # 背景画像表示用のキャンバス
        self.background_canvas = tk.Canvas(self.master, bg="#2c3e50", highlightthickness=0)
        self.background_canvas.pack(fill="both", expand=True)

        # 全てのUIフレームをキャンバスのウィンドウとして配置
        self.message_frame = tk.Frame(self.background_canvas, bd=2, relief="solid", bg="#34495e", padx=10, pady=10)
        self.message_frame_window = self.background_canvas.create_window(400, 50, window=self.message_frame, width=760, anchor="n") # 中央上
        self.message_label = tk.Label(self.message_frame, text="ゲームを開始します...", wraplength=720, 
                                      justify="left", font=("Meiryo UI", 14), fg="#ecf0f1", bg="#34495e")
        self.message_label.pack(pady=5, fill="both", expand=True)

        self.status_frame = tk.Frame(self.background_canvas, bd=2, relief="solid", bg="#34495e", padx=10, pady=5)
        self.status_frame_window = self.background_canvas.create_window(400, 150, window=self.status_frame, width=760, anchor="n") # 少し下
        self.status_label = tk.Label(self.status_frame, 
                                     text="プレイヤーHP: 100/100 攻撃力: 10 防御力: 5", 
                                     font=("Meiryo UI", 12, "bold"), fg="#ecf0f1", bg="#34495e")
        self.status_label.pack(pady=3)

        self.choices_frame = tk.Frame(self.background_canvas, bd=2, relief="solid", bg="#34495e", padx=10, pady=10)
        self.choices_frame_window = self.background_canvas.create_window(400, 250, window=self.choices_frame, width=760, height=300, anchor="n") # さらに下、高さ指定
        
        self.choice_buttons = []
        self.item_drop_buttons = []

    def create_battle_widgets(self):
        # 敵のステータス表示はキャンバスの下部に配置
        self.enemy_status_label = tk.Label(self.background_canvas, text="", 
                                           font=("Meiryo UI", 12, "bold"), fg="#e74c3c", bg="#2c3e50")
        self.enemy_status_label_window = self.background_canvas.create_window(400, 200, window=self.enemy_status_label, anchor="n") # 敵のステータス位置調整

        self.battle_commands_frame = tk.Frame(self.background_canvas, bd=2, relief="solid", bg="#34495e", padx=10, pady=10)
        self.battle_commands_frame_window = self.background_canvas.create_window(400, 500, window=self.battle_commands_frame, width=760, anchor="s") # コマンドを下部に

        button_font = ("Meiryo UI", 12, "bold")
        button_bg = "#2980b9"
        button_fg = "#ecf0f1"

        self.attack_button = tk.Button(self.battle_commands_frame, text="⚔️ たたかう", 
                                       command=self.player_attack, font=button_font, bg=button_bg, fg=button_fg,
                                       activebackground="#3498db", activeforeground="#ecf0f1")
        self.item_button = tk.Button(self.battle_commands_frame, text="🩹 どうぐ", 
                                     command=self.show_item_menu, font=button_font, bg=button_bg, fg=button_fg,
                                     activebackground="#3498db", activeforeground="#ecf0f1")
        self.escape_button = tk.Button(self.battle_commands_frame, text="🏃 にげる", 
                                      command=self.try_escape, font=button_font, bg=button_bg, fg=button_fg,
                                      activebackground="#3498db", activeforeground="#ecf0f1")

        # 初期状態では非表示
        self.background_canvas.itemconfigure(self.enemy_status_label_window, state="hidden")
        self.background_canvas.itemconfigure(self.battle_commands_frame_window, state="hidden")


    def update_display(self):
        """現在のゲーム状態に基づいて表示を更新する (マップ画面用)"""
        # 戦闘用ウィジェットを非表示に
        self.background_canvas.itemconfigure(self.enemy_status_label_window, state="hidden")
        self.background_canvas.itemconfigure(self.battle_commands_frame_window, state="hidden")
        self.attack_button.pack_forget()
        self.item_button.pack_forget()
        self.escape_button.pack_forget()
        self._clear_item_drop_buttons()
        self._clear_enemy_image() # 戦闘終了後、敵画像をクリア

        # 通常のUIフレームを表示
        self.background_canvas.itemconfigure(self.message_frame_window, state="normal")
        self.background_canvas.itemconfigure(self.status_frame_window, state="normal")
        self.background_canvas.itemconfigure(self.choices_frame_window, state="normal")


        if not self.player:
            return

        current_loc = locations[self.current_location_key]
        self.message_label.config(text=f"--- 現在地: {current_loc['name']} ---\n{current_loc['description']}")
        self.status_label.config(text=f"👨‍🦰 勇者{self.player['name']} HP: {self.player['hp']}/{self.player['max_hp']} 攻撃力: {self.player['attack']} 防御力: {self.player['defense']}")
        
        # 回復スポットの場合の処理
        if current_loc.get("healing_spot"):
            if self.player["hp"] < self.player["max_hp"]:
                self.player["hp"] = self.player["max_hp"]
                self.message_label.config(text=f"--- 現在地: {current_loc['name']} ---\n{current_loc['description']}\n\n✨ 泉の力でHPが満タンになった!")
                self.update_battle_display() # HP表示を即座に更新
            else:
                self.message_label.config(text=f"--- 現在地: {current_loc['name']} ---\n{current_loc['description']}\n\nすでにHPは満タンだ。")


        for button in self.choice_buttons:
            button.destroy()
        self.choice_buttons.clear()

        # 動的に選択肢を生成
        current_choices = dict(current_loc["choices"]) # 元のchoicesをコピーして変更を加える

        # 回復の泉への選択肢を動的に追加
        if "東の森_戦闘" in self.player["cleared_enemies"] and \
           "西の洞窟_戦闘" in self.player["cleared_enemies"] and \
           self.current_location_key == "始まりの町":
            current_choices["回復の泉へ向かう"] = "回復の泉"


        if current_choices:
            for i, (choice_text, next_location_key) in enumerate(current_choices.items()):
                button = tk.Button(self.choices_frame, text=choice_text, 
                                   command=lambda key=next_location_key: self.move_to_location(key),
                                   font=("Meiryo UI", 12), bg="#2980b9", fg="#ecf0f1",
                                   activebackground="#3498db", activeforeground="#ecf0f1")
                button.pack(pady=5, fill="x", padx=10)
                self.choice_buttons.append(button)
            
            status_button = tk.Button(self.choices_frame, text="📊 ステータスを見る", command=self.show_status,
                                       font=("Meiryo UI", 12), bg="#7f8c8d", fg="#ecf0f1",
                                       activebackground="#95a5a6", activeforeground="#ecf0f1")
            status_button.pack(pady=5, fill="x", padx=10)
            self.choice_buttons.append(status_button)
            
            exit_button = tk.Button(self.choices_frame, text="🚪 ゲーム終了", command=self.master.quit,
                                    font=("Meiryo UI", 12), bg="#c0392b", fg="#ecf0f1",
                                    activebackground="#e74c3c", activeforeground="#ecf0f1")
            exit_button.pack(pady=5, fill="x", padx=10)
            self.choice_buttons.append(exit_button)
        else:
            if current_loc["enemy"]:
                battle_start_button = tk.Button(self.choices_frame, text=f"⚔️ {current_loc['enemy']}と戦う!", 
                                                command=lambda: self.start_battle(current_loc['enemy']),
                                                font=("Meiryo UI", 14, "bold"), bg="#e67e22", fg="#ecf0f1",
                                                activebackground="#f39c12", activeforeground="#ecf0f1")
                battle_start_button.pack(pady=20, fill="x", padx=10)
                self.choice_buttons.append(battle_start_button)
            else:
                pass # Game clear / game over message if applicable

    def start_game_setup(self):
        self.message_label.config(text="勇者の名前を入力してください:")
        
        self.name_entry = tk.Entry(self.choices_frame, font=("Meiryo UI", 12), bg="#ecf0f1", fg="#2c3e50", insertbackground="#2c3e50")
        self.name_entry.pack(pady=10)
        
        name_submit_button = tk.Button(self.choices_frame, text="名前を決定", command=self.process_name_input,
                                       font=("Meiryo UI", 12, "bold"), bg="#27ae60", fg="#ecf0f1",
                                       activebackground="#2ecc71", activeforeground="#ecf0f1")
        name_submit_button.pack(pady=5)
        self.choice_buttons.append(self.name_entry)
        self.choice_buttons.append(name_submit_button)


    def process_name_input(self):
        player_name = self.name_entry.get().strip()
        if not player_name:
            messagebox.showwarning("入力エラー", "名前を入力してください!", parent=self.master)
            return

        self.player = create_player(player_name)
        
        for widget in self.choice_buttons:
            widget.destroy()
        self.choice_buttons.clear()

        self.message_label.config(text="冒険を始めるにあたり、どちらかのオーブを選んでください。\n✨ 金のオーブ (攻撃力アップ)\n🛡️ 銀のオーブ (防御力アップ)")
        
        orb_button_1 = tk.Button(self.choices_frame, text="✨ 金のオーブ (攻撃力アップ)", command=lambda: self.choose_orb("1"),
                                 font=("Meiryo UI", 12), bg="#f1c40f", fg="#2c3e50",
                                 activebackground="#f39c12", activeforeground="#2c3e50")
        orb_button_1.pack(pady=5, fill="x", padx=10)
        self.choice_buttons.append(orb_button_1)

        orb_button_2 = tk.Button(self.choices_frame, text="🛡️ 銀のオーブ (防御力アップ)", command=lambda: self.choose_orb("2"),
                                 font=("Meiryo UI", 12), bg="#bdc3c7", fg="#2c3e50",
                                 activebackground="#95a5a6", activeforeground="#2c3e50")
        orb_button_2.pack(pady=5, fill="x", padx=10)
        self.choice_buttons.append(orb_button_2)

    def choose_orb(self, choice):
        if choice == "1":
            self.player["items"].append(items_data["金のオーブ"])
            # ここは修正: main.pyのitems_dataに合わせてアクセスする
            self.player["attack"] += items_data["金のオーブ"]["attack_boost"]
            self.message_label.config(text="金のオーブを手に入れた!攻撃力がアップした!")
        elif choice == "2":
            self.player["items"].append(items_data["銀のオーブ"])
            # ここは修正: main.pyのitems_dataに合わせてアクセスする
            self.player["defense"] += items_data["銀のオーブ"]["defense_boost"]
            self.message_label.config(text="銀のオーブを手に入れた!防御力がアップした!")
        else:
            self.message_label.config(text="無効な選択です。オーブは選べませんでした。")

        for button in self.choice_buttons:
            button.destroy()
        self.choice_buttons.clear()

        self.current_location_key = "始まりの町"
        self.master.after(1000, self.update_display)

    def move_to_location(self, next_location_key):
        self.current_location_key = next_location_key
        self.update_display()

    def start_battle(self, enemy_key):
        self.current_enemy_key = enemy_key
        self.current_enemy = dict(enemies[enemy_key])
        
        # 通常のUIフレームを非表示に
        self.background_canvas.itemconfigure(self.message_frame_window, state="hidden")
        self.background_canvas.itemconfigure(self.status_frame_window, state="hidden")
        self.background_canvas.itemconfigure(self.choices_frame_window, state="hidden")

        # 戦闘用ウィジェットを表示
        self.background_canvas.itemconfigure(self.enemy_status_label_window, state="normal")
        self.background_canvas.itemconfigure(self.battle_commands_frame_window, state="normal")
        self.message_frame_window_battle = self.background_canvas.create_window(400, 50, window=self.message_frame, width=760, anchor="n") # メッセージフレームは戦闘中も表示
        self.status_frame_window_battle = self.background_canvas.create_window(400, 150, window=self.status_frame, width=760, anchor="n") # ステータスフレームも戦闘中表示

        self.attack_button.pack(side="left", expand=True, fill="x", padx=5, pady=5)
        self.item_button.pack(side="left", expand=True, fill="x", padx=5, pady=5)
        self.escape_button.pack(side="left", expand=True, fill="x", padx=5, pady=5)

        # 敵の画像を背景に表示
        self._load_and_display_enemy_image(enemy_key)

        self.update_battle_display()
        self.display_battle_message(f"--- ⚔️ {self.current_enemy['name']}が現れた! ⚔️ ---")
        
        self.master.after(1000, self.player_turn)

    def _load_and_display_enemy_image(self, enemy_key):
        image_path = self.enemy_images.get(enemy_key)
        if image_path:
            try:
                # 画像を読み込み、サイズ調整
                original_image = Image.open(image_path)
                
                # 画像が大きすぎる場合、適切なサイズにリサイズ(例: 300x300)
                # アスペクト比を維持しつつリサイズ
                width, height = original_image.size
                max_size = 300
                if width > max_size or height > max_size:
                    if width > height:
                        new_width = max_size
                        new_height = int(max_size * height / width)
                    else:
                        new_height = max_size
                        new_width = int(max_size * width / height)
                    resized_image = original_image.resize((new_width, new_height), Image.Resampling.LANCZOS)
                else:
                    resized_image = original_image

                tk_image = ImageTk.PhotoImage(resized_image)
                self.loaded_images[enemy_key] = tk_image # 参照を保持

                # 古い画像を削除
                if hasattr(self, 'current_enemy_image_id'):
                    self.background_canvas.delete(self.current_enemy_image_id)
                
                # キャンバスの中央に配置
                self.current_enemy_image_id = self.background_canvas.create_image(400, 300, image=tk_image, anchor="center") # 中央に表示
                self.background_canvas.tag_lower(self.current_enemy_image_id) # 他のウィジェットの下に配置

            except FileNotFoundError:
                print(f"Error: Image file not found for {enemy_key} at {image_path}")
            except Exception as e:
                print(f"Error loading image for {enemy_key}: {e}")
        else:
            self._clear_enemy_image() # 画像がない場合はクリア

    def _clear_enemy_image(self):
        if hasattr(self, 'current_enemy_image_id'):
            self.background_canvas.delete(self.current_enemy_image_id)
            del self.current_enemy_image_id
        self.loaded_images.clear() # 保持していた画像参照もクリア

    def update_battle_display(self):
        self.status_label.config(text=f"👨‍🦰 勇者{self.player['name']} HP: {self.player['hp']}/{self.player['max_hp']} 攻撃力: {self.player['attack']} 防御力: {self.player['defense']}")
        
        enemy_icon = "👹" # デフォルトアイコン
        if self.current_enemy_key == "ゴブリン": enemy_icon = "🧌"
        elif self.current_enemy_key == "ゴブリンキング": enemy_icon = "👑"
        elif self.current_enemy_key == "オーク": enemy_icon = "🐷"
        elif self.current_enemy_key == "魔導士": enemy_icon = "🧙"
        elif self.current_enemy_key == "デビル": enemy_icon = "👿"
        elif self.current_enemy_key == "魔王": enemy_icon = "😈"

        self.enemy_status_label.config(text=f"{enemy_icon} 敵: {self.current_enemy['name']} HP: {self.current_enemy['hp']}/{enemies[self.current_enemy_key]['hp']}")

    def display_battle_message(self, message):
        current_message = self.message_label.cget("text")
        lines = (current_message + "\n" + message).split('\n')
        display_lines = lines[-5:]
        self.message_label.config(text="\n".join(display_lines))
        self.master.update_idletasks()

    def player_turn(self):
        self.update_battle_display()
        self.attack_button.config(state=tk.NORMAL)
        self.item_button.config(state=tk.NORMAL)
        self.escape_button.config(state=tk.NORMAL)
        self.display_battle_message("あなたのターンです。コマンドを選択してください:")

    def player_attack(self):
        self.attack_button.config(state=tk.DISABLED)
        self.item_button.config(state=tk.DISABLED)
        self.escape_button.config(state=tk.DISABLED)

        self.display_battle_message(f"👨‍🦰 {self.player['name']}の攻撃!")
        
        player_attack_power = self.player['attack']
        damage = calculate_damage(player_attack_power, self.current_enemy['defense'])
        self.current_enemy['hp'] -= damage
        
        self.master.after(1000, lambda: self._display_player_damage(damage))

    def _display_player_damage(self, damage):
        self.display_battle_message(f"{self.current_enemy['name']}{damage}のダメージを与えた!")
        self.update_battle_display()
        if self.current_enemy['hp'] <= 0:
            self.check_battle_end()
        else:
            self.master.after(1000, self.enemy_attack)

    def enemy_attack(self):
        if self.current_enemy['hp'] <= 0:
            self.check_battle_end()
            return

        self.display_battle_message(f"\n--- ⚡️ {self.current_enemy['name']}の攻撃! ⚡️ ---")
        
        enemy_attack_message = ""
        if "attack_patterns" in self.current_enemy and self.current_enemy["attack_patterns"]:
            enemy_attack_message = random.choice(self.current_enemy['attack_patterns'])
            self.display_battle_message(f"{self.current_enemy['name']}: {enemy_attack_message}")
        else:
            self.display_battle_message(f"{self.current_enemy['name']}は攻撃してきた!")
        
        self.master.after(1500, self._perform_enemy_damage)

    def _perform_enemy_damage(self):
        damage = calculate_damage(self.current_enemy['attack'], self.player['defense'])
        self.player['hp'] -= damage
        self.display_battle_message(f"👨‍🦰 {self.player['name']}{damage}のダメージを受けた!")
        self.update_battle_display()

        if self.player['hp'] <= 0:
            self.check_battle_end()
        else:
            self.master.after(1000, self.player_turn)

    def check_battle_end(self):
        if self.player['hp'] <= 0:
            self.display_battle_message(f"👨‍🦰 {self.player['name']}は力尽きた...")
            messagebox.showerror("ゲームオーバー", "力尽きてしまった...", parent=self.master)
            self.master.quit()
            return
        
        if self.current_enemy['hp'] <= 0:
            self.display_battle_message(f"🎉 {self.current_enemy['name']}を倒した! 🎉")
            
            current_loc_data = locations.get(self.current_location_key)
            if current_loc_data and "clear_condition" in current_loc_data:
                condition_key = current_loc_data["clear_condition"][0]
                if condition_key not in self.player["cleared_enemies"]:
                    self.player["cleared_enemies"].append(condition_key)
                    print(f"DEBUG: cleared_enemies: {self.player['cleared_enemies']}")


            self.master.after(1000, self._handle_item_drop)
            return
        
    def _handle_item_drop(self):
        # main.py の enemies データ構造に合わせて 'drop_items' に変更
        dropped_item_keys = enemies[self.current_enemy_key].get("drop_items", [])

        if not dropped_item_keys:
            self.display_battle_message("何もドロップしなかった...")
            self.master.after(1000, lambda: self.end_battle("win"))
            return

        self.display_battle_message("ドロップアイテムがあります!どれを選びますか?")
        
        self.attack_button.pack_forget()
        self.item_button.pack_forget()
        self.escape_button.pack_forget()

        for item_key in dropped_item_keys:
            item = items_data[item_key]
            icon = "🎁"
            if item.get("type") == "consumable":
                icon = "🩹"
            elif item.get("type") == "weapon":
                icon = "⚔️"
            elif item.get("type") == "armor":
                icon = "🛡️"
            elif item.get("type") == "permanent_boost":
                icon = ""
            elif item.get("type") == "special":
                icon = "🏆"

            item_description = item.get("description", "")
            
            if item.get("type") == "weapon" and "attack_boost" in item:
                item_description = f"攻撃力+{item['attack_boost']}"
            elif item.get("type") == "armor" and "defense_boost" in item:
                item_description = f"防御力+{item['defense_boost']}"
            elif item.get("type") == "permanent_boost":
                boost_info = []
                if "attack_boost" in item:
                    boost_info.append(f"攻撃力+{item['attack_boost']}")
                if "defense_boost" in item:
                    boost_info.append(f"防御力+{item['defense_boost']}")
                item_description = ", ".join(boost_info)

            item_button = tk.Button(self.battle_commands_frame,
                                    text=f"{icon} {item['name']} ({item_description})",
                                    command=lambda it=item: self._select_dropped_item(it),
                                    font=("Meiryo UI", 12), bg="#e7b416", fg="#2c3e50",
                                    activebackground="#f0d56b", activeforeground="#2c3e50")
            item_button.pack(pady=5, fill="x", expand=True)
            self.item_drop_buttons.append(item_button)
        
        self.battle_commands_frame.pack(pady=10, padx=20, fill="x")

    def _select_dropped_item(self, selected_item):
        self.player['items'].append(selected_item)
        self.display_battle_message(f"{selected_item['name']}を手に入れた! ✨")
        
        status_change_message = ""

        item_type = selected_item.get('type')
        if item_type == 'permanent_boost':
            if 'attack_boost' in selected_item:
                self.player['attack'] += selected_item['attack_boost']
                status_change_message += f"攻撃力が{selected_item['attack_boost']}アップした!\n"
            if 'defense_boost' in selected_item:
                self.player['defense'] += selected_item['defense_boost']
                status_change_message += f"防御力が{selected_item['defense_boost']}アップした!\n"
        elif item_type == 'weapon':
            if self.player['equipped_weapon']:
                self.player['attack'] -= self.player['equipped_weapon'].get('attack_boost', 0)
                status_change_message += f"{self.player['equipped_weapon']['name']}の装備を解除した。\n"
            self.player['equipped_weapon'] = selected_item
            self.player['attack'] += selected_item.get('attack_boost', 0)
            status_change_message += f"{selected_item['name']}を装備した!攻撃力が{selected_item.get('attack_boost', 0)}アップした!\n"
        elif item_type == 'armor':
            if self.player['equipped_armor']:
                self.player['defense'] -= self.player['equipped_armor'].get('defense_boost', 0)
                status_change_message += f"{self.player['equipped_armor']['name']}の装備を解除した。\n"
            self.player['equipped_armor'] = selected_item
            self.player['defense'] += selected_item.get('defense_boost', 0)
            status_change_message += f"{selected_item['name']}を装備した!防御力が{selected_item.get('defense_boost', 0)}アップした!\n"
        
        if status_change_message:
            self.display_battle_message(status_change_message.strip())

        messagebox.showinfo("アイテム獲得", f"{selected_item['name']}を手に入れた!\n{status_change_message.strip()}", parent=self.master)
        
        self._clear_item_drop_buttons()

        self.master.after(500, lambda: self.end_battle("win"))

    def _clear_item_drop_buttons(self):
        for button in self.item_drop_buttons:
            button.destroy()
        self.item_drop_buttons.clear()


    def end_battle(self, result):
        # キャンバスに配置されたウィジェットを非表示に
        self.background_canvas.itemconfigure(self.enemy_status_label_window, state="hidden")
        self.background_canvas.itemconfigure(self.battle_commands_frame_window, state="hidden")
        
        # パックされたボタンも非表示に
        self.attack_button.pack_forget()
        self.item_button.pack_forget()
        self.escape_button.pack_forget()
        self._clear_item_drop_buttons()
        self._clear_enemy_image() # 戦闘終了時に敵画像をクリア

        # 通常のUIフレームを表示に戻す
        self.background_canvas.itemconfigure(self.message_frame_window, state="normal")
        self.background_canvas.itemconfigure(self.status_frame_window, state="normal")
        self.background_canvas.itemconfigure(self.choices_frame_window, state="normal")


        if result == "win":
            if self.current_enemy_key == "ゴブリン":
                self.current_location_key = "北の砦"
            elif self.current_enemy_key == "ゴブリンキング":
                self.current_location_key = "古びた塔"
            elif self.current_enemy_key == "オーク":
                self.current_location_key = "奈落の淵"
            elif self.current_enemy_key == "魔導士":
                self.current_location_key = "奈落の淵"
            elif self.current_enemy_key == "デビル":
                self.current_location_key = "魔王城"
            elif self.current_enemy_key == "魔王":
                messagebox.showinfo("ゲームクリア!", "魔王を倒し、世界に平和が訪れた!\n勇者の伝説が今、始まった!", parent=self.master)
                self.master.quit()
                return
            
            self.update_display()
            self.display_battle_message("探索を続けよう!")
            

        elif result == "lose":
            pass
        elif result == "escaped":
            self.current_location_key = "始まりの町"
            self.update_display()
            self.display_battle_message("戦いを避け、始まりの町へ逃げ帰った。")

    def show_item_menu(self):
        self.attack_button.config(state=tk.DISABLED)
        self.item_button.config(state=tk.DISABLED)
        self.escape_button.config(state=tk.DISABLED)

        self.display_battle_message("どのどうぐを使いますか?")
        
        recoverable_items = [item for item in self.player['items'] if item.get('type') == 'consumable' and 'hp_recovery' in item]
        
        if not recoverable_items:
            self.display_battle_message("回復アイテムを持っていません!")
            self.master.after(1000, self.player_turn)
            return

        self.attack_button.pack_forget()
        self.item_button.pack_forget()
        self.escape_button.pack_forget()

        self.item_choice_buttons = []
        for i, item in enumerate(recoverable_items):
            item_button = tk.Button(self.battle_commands_frame, 
                                    text=f"🩹 {item['name']} (HP+{item['hp_recovery']})",
                                    command=lambda it=item: self._use_item(it),
                                    font=("Meiryo UI", 11), bg="#8e44ad", fg="#ecf0f1",
                                    activebackground="#9b59b6", activeforeground="#ecf0f1")
            item_button.pack(pady=3, fill="x", expand=True)
            self.item_choice_buttons.append(item_button)

        cancel_button = tk.Button(self.battle_commands_frame, text="キャンセル", 
                                  command=self._cancel_item_menu,
                                  font=("Meiryo UI", 11), bg="#34495e", fg="#ecf0f1",
                                  activebackground="#7f8c8d", activeforeground="#ecf0f1")
        cancel_button.pack(pady=3, fill="x", expand=True)
        self.item_choice_buttons.append(cancel_button)

    def _use_item(self, item):
        for btn in self.item_choice_buttons:
            btn.destroy()
        self.item_choice_buttons.clear()
        
        self.attack_button.pack(side="left", expand=True, fill="x", padx=5, pady=5)
        self.item_button.pack(side="left", expand=True, fill="x", padx=5, pady=5)
        self.escape_button.pack(side="left", expand=True, fill="x", padx=5, pady=5)

        if item['type'] == 'consumable' and 'hp_recovery' in item:
            self.player['hp'] = min(self.player['max_hp'], self.player['hp'] + item['hp_recovery'])
            self.player['items'].remove(item)
            self.display_battle_message(f"🩹 {item['name']}を使った!HPが{item['hp_recovery']}回復した!")
            self.update_battle_display()
        else:
            self.display_battle_message("そのアイテムは使えません。")

        self.master.after(1000, self.enemy_attack)

    def _cancel_item_menu(self):
        for btn in self.item_choice_buttons:
            btn.destroy()
        self.item_choice_buttons.clear()
        
        self.attack_button.pack(side="left", expand=True, fill="x", padx=5, pady=5)
        self.item_button.pack(side="left", expand=True, fill="x", padx=5, pady=5)
        self.escape_button.pack(side="left", expand=True, fill="x", padx=5, pady=5)

        self.display_battle_message("アイテムの使用をキャンセルしました。")
        self.master.after(500, self.player_turn)

    def try_escape(self):
        self.attack_button.config(state=tk.DISABLED)
        self.item_button.config(state=tk.DISABLED)
        self.escape_button.config(state=tk.DISABLED)

        self.display_battle_message("🏃 逃走を試みる...")
        self.master.update_idletasks()
        
        if random.random() < 0.5:
            self.master.after(1000, lambda: self.display_battle_message("💨 うまく逃げ切れた!"))
            self.master.after(1500, lambda: messagebox.showinfo("逃走成功", "うまく逃げ切れた!", parent=self.master))
            self.master.after(2000, lambda: self.end_battle("escaped"))
        else:
            self.master.after(1000, lambda: self.display_battle_message("❌ 逃げられなかった!"))
            self.master.after(1500, self.enemy_attack)

    def show_status(self):
        status_text = (
            f"名前: {self.player['name']}\n"
            f"HP: {self.player['hp']}/{self.player['max_hp']}\n"
            f"攻撃力: {self.player['attack']}\n"
            f"防御力: {self.player['defense']}\n"
        )
        equipped_weapon_name = self.player["equipped_weapon"]["name"] if self.player["equipped_weapon"] else "なし"
        equipped_armor_name = self.player["equipped_armor"]["name"] if self.player["equipped_armor"] else "なし"
        status_text += f"装備: 武器 - {equipped_weapon_name}, 防具 - {equipped_armor_name}\n"
        
        items_list = [item['name'] for item in self.player['items']]
        status_text += f"所持アイテム: {', '.join(items_list) if items_list else 'なし'}"

        messagebox.showinfo("📊 プレイヤー情報", status_text, parent=self.master)

# メイン処理
if __name__ == "__main__":
    root = tk.Tk()
    game_gui = RPGGameGUI(root)
    root.mainloop()

Tkinter pythonのライブラリの一つで、install不要のもの。 学習コストが少なく、簡易的なアプリのフロントであればおすすめ。 できること ウィンドウの生成 ボタン、メニュー、テキストボックス、ラベル、チェックボックス、ラジオボタンなどのウィジェットの配置や制御
main.py
import random
import time

# --- ゲームデータ定義 ---

# プレイヤーの初期データ
def create_player(name):
    return {
        "name": f"勇者{name}",
        "hp": 100,
        "max_hp": 100,
        "attack": 10,
        "defense": 5,
        "items": [],
        "equipped_weapon": None,
        "equipped_armor": None,
        "cleared_enemies": [], # 新しく追加: 倒した敵のキーを記録するリスト
    }

# アイテムデータ (変更なし)
items_data = {
    "金のオーブ": {"name": "金のオーブ", "type": "orb", "attack_boost": 10, "description": "攻撃力が上がる不思議なオーブ。"},
    "銀のオーブ": {"name": "銀のオーブ", "type": "orb", "defense_boost": 10, "description": "防御力が上がる不思議なオーブ。"},
    "薬草": {"name": "薬草", "type": "consumable", "hp_recovery": 30, "description": "HPを30回復する。"},
    "こんぼう": {"name": "こんぼう", "type": "weapon", "attack_boost": 5, "description": "ごく一般的なこんぼう。"},
    "革の盾": {"name": "革の盾", "type": "armor", "defense_boost": 5, "description": "初心者向けの革の盾。"},
    "勇者の証": {"name": "勇者の証", "type": "special", "description": "魔王を倒した勇者の証。"},
    "おまもり": {"name": "おまもり", "type": "permanent_boost", "attack_boost": 10, "defense_boost": 30, "description": "持っているだけで守りが強くなるおまもり。"},
    "戦いの護符": {"name": "戦いの護符", "type": "permanent_boost", "attack_boost": 50, "description": "持っているだけで攻撃力が高まる護符。"},
    "秘薬": {"name": "秘薬", "type": "consumable", "hp_recovery": 80, "description": "HPを大きく回復する秘薬。"},
    "勇者の剣": {"name": "勇者の剣", "type": "weapon", "attack_boost": 60, "description": "伝説の勇者が使ったとされる剣。"},
    "鉄の鎧": {"name": "鉄の鎧", "type": "armor", "defense_boost": 50, "description": "頑丈な鉄でできた鎧。"},
    "はねのブーツ": {"name": "はねのブーツ", "type": "armor", "defense_boost": 20, "description": "軽い素材でできたブーツ。"}
}

# 敵データ (変更なし)
enemies = {
    "ゴブリン": {
        "name": "ゴブリン", "hp": 50, "attack": 15, "defense": 3,
        "attack_patterns": ["「キーキー!」と叫びながら攻撃!", "素早い動きで切りかかってきた!"],
        "drop_items": ["薬草", "こんぼう"]
    },
    "ゴブリンキング": {
        "name": "ゴブリンキング", "hp": 100, "attack": 25, "defense": 8,
        "attack_patterns": ["「グガアアア!」と雄叫びを上げて殴りかかってきた!", "巨大な棍棒を振り下ろす!"],
        "drop_items": ["鉄の鎧", "秘薬"]
    },
    "オーク": {
        "name": "オーク", "hp": 80, "attack": 20, "defense": 6,
        "attack_patterns": ["鈍重な一撃!", "「ブヒー!」と突進してきた!"],
        "drop_items": ["革の盾", "薬草"]
    },
    "魔導士": {
        "name": "魔導士", "hp": 70, "attack": 22, "defense": 4,
        "attack_patterns": ["炎の魔法を唱えた!", "氷の魔法があなたを凍らせる!"],
        "drop_items": ["はねのブーツ", "秘薬"]
    },
    "デビル": {
        "name": "デビル", "hp": 120, "attack": 30, "defense": 10,
        "attack_patterns": ["闇の力で襲いかかってきた!", "邪悪な波動を放つ!"],
        "drop_items": ["おまもり", "戦いの護符"]
    },
    "魔王": {
        "name": "魔王", "hp": 200, "attack": 40, "defense": 15,
        "attack_patterns": ["絶望の淵に突き落とす一撃!", "世界の終わりを告げる闇の波動!"],
        "drop_items": ["勇者の証", "勇者の剣"]
    },
}

# 場所データ
locations = {
    "始まりの町": {
        "name": "始まりの町",
        "description": "ここから冒険が始まる。人々の活気に満ちている。",
        "choices": {
            "東の森へ向かう": "東の森",
            "西の洞窟へ向かう": "西の洞窟",
            "北の砦へ向かう": "北の砦",
            "古びた塔へ向かう": "古びた塔",
            "奈落の淵へ向かう": "奈落の淵",
        },
        "enemy": None
    },
    "東の森": {
        "name": "東の森",
        "description": "薄暗い森の奥から、何かの気配がする...",
        "choices": {
            "奥へ進む": "東の森_戦闘",
            "町へ戻る": "始まりの町"
        },
        "enemy": None
    },
    "東の森_戦闘": {
        "name": "東の森の奥",
        "description": "ゴブリンが待ち構えている!",
        "choices": {},
        "enemy": "ゴブリン",
        "clear_condition": ["東の森_戦闘"] # この戦闘をクリアしたことを示すキー
    },
    "北の砦": {
        "name": "北の砦",
        "description": "かつては堅牢な砦だったが、今はオークが徘徊しているようだ。",
        "choices": {
            "奥へ進む": "北の砦_戦闘",
            "町へ戻る": "始まりの町"
        },
        "enemy": None
    },
    "北の砦_戦闘": {
        "name": "北の砦の奥",
        "description": "オークが立ちふさがる!",
        "choices": {},
        "enemy": "オーク",
        "clear_condition": ["北の砦_戦闘"]
    },
    "西の洞窟": {
        "name": "西の洞窟",
        "description": "冷たい風が吹き荒れる洞窟。奥には何かいるようだ。",
        "choices": {
            "奥へ進む": "西の洞窟_戦闘",
            "町へ戻る": "始まりの町"
        },
        "enemy": None
    },
    "西の洞窟_戦闘": {
        "name": "西の洞窟の奥",
        "description": "ゴブリンキングが襲いかかる!",
        "choices": {},
        "enemy": "ゴブリンキング",
        "clear_condition": ["西の洞窟_戦闘"]
    },
    "古びた塔": {
        "name": "古びた塔",
        "description": "今にも崩れそうな古い塔。魔導士の気配がする。",
        "choices": {
            "塔の奥へ": "古びた塔_戦闘",
            "町へ戻る": "始まりの町"
        },
        "enemy": None
    },
    "古びた塔_戦闘": {
        "name": "古びた塔の頂",
        "description": "魔導士が詠唱を始めた!",
        "choices": {},
        "enemy": "魔導士",
        "clear_condition": ["古びた塔_戦闘"]
    },
    "奈落の淵": {
        "name": "奈落の淵",
        "description": "暗く深い奈落の底。ここから魔王城へと続く道がある。",
        "choices": {
            "魔王城へ": "奈落の淵_戦闘",
            "町へ戻る": "始まりの町"
        },
        "enemy": None
    },
    "奈落の淵_戦闘": {
        "name": "奈落の淵の奥",
        "description": "デビルが門番のように立ちはだかる!",
        "choices": {},
        "enemy": "デビル",
        "clear_condition": ["奈落の淵_戦闘"]
    },
    "魔王城": {
        "name": "魔王城",
        "description": "ついに魔王城に辿り着いた。最深部には魔王が待ち受けている。",
        "choices": {
            "最深部へ": "魔王城_戦闘",
            "奈落の淵へ戻る": "奈落の淵"
        },
        "enemy": None
    },
    "魔王城_戦闘": {
        "name": "魔王の間",
        "description": "魔王があなたを待ち構えていた!",
        "choices": {},
        "enemy": "魔王",
        "clear_condition": ["魔王城_戦闘"]
    },
    # 回復の泉を追加
    "回復の泉": {
        "name": "回復の泉",
        "description": "神秘的な光を放つ泉がある。全身の傷が癒されるのを感じる。",
        "choices": {
            "町へ戻る": "始まりの町"
        },
        "enemy": None,
        "healing_spot": True # 回復スポットであることを示すフラグ
    }
}


# --- ゲームシステム関数 ---

def display_player_status(player):
    """プレイヤーの現在のステータスを表示する関数 (コマンドライン版用)"""
    print("----------------------------------------")
    print(f"名前: {player['name']}")
    print(f"HP: {player['hp']}/{player['max_hp']}")
    print(f"攻撃力: {player['attack']}")
    print(f"防御力: {player['defense']}")
    equipped_weapon_name = player["equipped_weapon"]["name"] if player["equipped_weapon"] else "なし"
    equipped_armor_name = player["equipped_armor"]["name"] if player["equipped_armor"] else "なし"
    print(f"装備: 武器 - {equipped_weapon_name}, 防具 - {equipped_armor_name}")
    print(f"所持アイテム: {[item['name'] for item in player['items']] if player['items'] else 'なし'}")
    print("----------------------------------------")

def calculate_damage(attacker_attack, defender_defense):
    """ダメージを計算する関数"""
    damage = max(1, attacker_attack - defender_defense + random.randint(-2, 2))
    return damage


# この if ブロックの中に、これまでのゲーム開始処理とメインループを移動させる
if __name__ == "__main__":
    # --- ゲーム開始処理 ---

    print("----------------------------------------")
    print("          勇者みならいの冒険          ")
    print("----------------------------------------")

    player_name = input("勇者の名前を入力してください:")
    player = create_player(player_name)

    print("冒険を始めるにあたり、どちらかのオーブを選んでください。")
    print("1: 金のオーブ (攻撃力アップ)")
    print("2: 銀のオーブ (防御力アップ)")

    orb_choice = input("選択してください (1/2): ")

    if orb_choice == "1":
        player["items"].append(items_data["金のオーブ"])
        player["attack"] += items_data["金のオーブ"]["attack_boost"]
        print("金のオーブを手に入れた!攻撃力がアップした!")
    elif orb_choice == "2":
        player["items"].append(items_data["銀のオーブ"])
        player["defense"] += items_data["銀のオーブ"]["defense_boost"]
        print("銀のオーブを手に入れた!防御力がアップした!")
    else:
        print("無効な選択です。オーブは選べませんでした。")

    display_player_status(player)
    current_location_key = "始まりの町" # 常に始まりの町から開始

    print("\n--- 冒険が今、始まる! ---")

    # --- ゲームメインループ ---

    game_over = False
    game_clear = False

    while not game_over and not game_clear:
        current_location = locations[current_location_key]

        print(f"\n--- 現在地: {current_location['name']} ---")
        print(current_location["description"])

        # 回復スポットの場合の処理 (コマンドライン版)
        if current_location.get("healing_spot"):
            if player["hp"] < player["max_hp"]:
                player["hp"] = player["max_hp"]
                print("泉の力でHPが満タンになった!")
            else:
                print("すでにHPは満タンだ。")


        # 場所に敵がいる場合、戦闘開始
        if current_location["enemy"]:
            enemy_key_in_location = current_location["enemy"] # これを保持
            enemy = dict(enemies[enemy_key_in_location]) # 敵データをコピーして使う
            print(f"\n--- {enemy['name']}が現れた! ---")
            
            # コマンドライン版の戦闘ループ
            while player['hp'] > 0 and enemy['hp'] > 0:
                print(f"\n{player['name']} HP: {player['hp']} vs {enemy['name']} HP: {enemy['hp']}")
                print("どうする? (1: たたかう, 2: どうぐ, 3: にげる)")
                
                choice = input(">> ")

                if choice == "1": # たたかう
                    print(f"{player['name']}の攻撃!")
                    time.sleep(1)
                    damage = calculate_damage(player['attack'], enemy['defense'])
                    enemy['hp'] -= damage
                    print(f"{enemy['name']}{damage}のダメージを与えた!")
                    if enemy['hp'] <= 0:
                        break # 敵を倒したらループを抜ける
                    time.sleep(1)

                    print(f"\n{enemy['name']}の攻撃!")
                    time.sleep(1)
                    enemy_attack_pattern = random.choice(enemy['attack_patterns'])
                    print(f"{enemy['name']}: {enemy_attack_pattern}")
                    damage = calculate_damage(enemy['attack'], player['defense'])
                    player['hp'] -= damage
                    print(f"{player['name']}{damage}のダメージを受けた!")
                    time.sleep(1)

                elif choice == "2": # どうぐ
                    if not player["items"]:
                        print("アイテムを持っていません。")
                        continue
                    print("どのアイテムを使いますか?")
                    consumable_items = [item for item in player["items"] if item.get("type") == "consumable"]
                    
                    if not consumable_items:
                        print("回復アイテムを持っていません。")
                        continue

                    for i, item in enumerate(consumable_items):
                         print(f"{i+1}: {item['name']} ({item['description']})")
                    
                    item_choice = input("アイテム番号を入力 (0でキャンセル): ")
                    if item_choice.isdigit() and 1 <= int(item_choice) <= len(consumable_items):
                        chosen_item = consumable_items[int(item_choice)-1]
                        if "hp_recovery" in chosen_item:
                            player["hp"] = min(player["max_hp"], player["hp"] + chosen_item["hp_recovery"])
                            player["items"].remove(chosen_item)
                            print(f"{chosen_item['name']}を使った!HPが{chosen_item['hp_recovery']}回復した!")
                        else:
                            print("そのアイテムは使えません。")
                    else:
                        print("キャンセルしました。")
                    time.sleep(1)
                    
                    # アイテム使用後も敵は攻撃
                    if enemy['hp'] > 0:
                        print(f"\n{enemy['name']}の攻撃!")
                        time.sleep(1)
                        enemy_attack_pattern = random.choice(enemy['attack_patterns'])
                        print(f"{enemy['name']}: {enemy_attack_pattern}")
                        damage = calculate_damage(enemy['attack'], player['defense'])
                        player['hp'] -= damage
                        print(f"{player['name']}{damage}のダメージを受けた!")
                        time.sleep(1)

                elif choice == "3": # にげる
                    print("逃げ出した!")
                    time.sleep(1)
                    if random.random() < 0.5: # 50%の確率で逃走成功
                        print("うまく逃げ切れた!")
                        battle_result = "escaped"
                        break # 戦闘ループを抜ける
                    else:
                        print("逃げられなかった!")
                        time.sleep(1)
                        # 逃げられなかった場合も敵は攻撃
                        if enemy['hp'] > 0:
                            print(f"\n{enemy['name']}の攻撃!")
                            time.sleep(1)
                            enemy_attack_pattern = random.choice(enemy['attack_patterns'])
                            print(f"{enemy['name']}: {enemy_attack_pattern}")
                            damage = calculate_damage(enemy['attack'], player['defense'])
                            player['hp'] -= damage
                            print(f"{player['name']}{damage}のダメージを受けた!")
                            time.sleep(1)

                else:
                    print("無効なコマンドです。")
                    continue

                if player['hp'] <= 0:
                    battle_result = "lose"
                    break # プレイヤーが倒れたらループを抜ける
            
            # コマンドライン版の戦闘結果処理
            if player['hp'] <= 0:
                print(f"{player['name']}は力尽きた...")
                game_over = True
                break
            elif enemy['hp'] <= 0:
                print(f"{enemy['name']}を倒した!")
                
                # 倒した敵の場所キーを記録 (回復の泉開放条件のため)
                if "clear_condition" in current_location and current_location["clear_condition"][0] not in player["cleared_enemies"]:
                    player["cleared_enemies"].append(current_location["clear_condition"][0])


                # ドロップアイテム処理
                if "drop_items" in enemies[enemy_key_in_location] and enemies[enemy_key_in_location]["drop_items"]:
                    print("\nドロップアイテムがあります!どれを選びますか?")
                    for i, item_key in enumerate(enemies[enemy_key_in_location]["drop_items"]):
                        item_info = items_data[item_key]
                        print(f"{i+1}: {item_info['name']} ({item_info['description']})")
                    
                    while True:
                        drop_choice = input("選択してください (1/2): ")
                        if drop_choice.isdigit() and 1 <= int(drop_choice) <= len(enemies[enemy_key_in_location]["drop_items"]):
                            chosen_item_key = enemies[enemy_key_in_location]["drop_items"][int(drop_choice)-1]
                            chosen_item = items_data[chosen_item_key]
                            
                            print(f"{chosen_item['name']}を手に入れた! ✨")
                            player['items'].append(chosen_item)

                            # permanent_boost / weapon / armor タイプのアイテムの効果を適用
                            if chosen_item.get('type') == 'permanent_boost':
                                if 'attack_boost' in chosen_item:
                                    player['attack'] += chosen_item['attack_boost']
                                    print(f"攻撃力が{chosen_item['attack_boost']}アップした!")
                                if 'defense_boost' in chosen_item:
                                    player['defense'] += chosen_item['defense_boost']
                                    print(f"防御力が{chosen_item['defense_boost']}アップした!")
                            elif chosen_item.get('type') == 'weapon':
                                if player['equipped_weapon']:
                                    player['attack'] -= player['equipped_weapon'].get('attack_boost', 0)
                                    print(f"{player['equipped_weapon']['name']}の装備を解除した。")
                                player['equipped_weapon'] = chosen_item
                                player['attack'] += chosen_item.get('attack_boost', 0)
                                print(f"{chosen_item['name']}を装備した!攻撃力が{chosen_item.get('attack_boost', 0)}アップした!")
                            elif chosen_item.get('type') == 'armor':
                                if player['equipped_armor']:
                                    player['defense'] -= player['equipped_armor'].get('defense_boost', 0)
                                    print(f"{player['equipped_armor']['name']}の装備を解除した。")
                                player['equipped_armor'] = chosen_item
                                player['defense'] += chosen_item.get('defense_boost', 0)
                                print(f"{chosen_item['name']}を装備した!防御力が{chosen_item.get('defense_boost', 0)}アップした!")
                            break
                        else:
                            print("無効な選択です。もう一度入力してください。")


                # 勝利後の場所遷移ロジック(コマンドライン版)
                if current_location_key == "東の森_戦闘":
                    current_location_key = "北の砦"
                elif current_location_key == "西の洞窟_戦闘":
                    current_location_key = "古びた塔"
                elif current_location_key == "北の砦_戦闘":
                    current_location_key = "奈落の淵"
                elif current_location_key == "古びた塔_戦闘":
                    current_location_key = "奈落の淵"
                elif current_location_key == "奈落の淵_戦闘":
                    current_location_key = "魔王城"
                
                if current_location_key == "魔王城_戦闘":
                    print("\n----------------------------------------")
                    print("          魔王を倒した!世界に平和が訪れた!")
                    print(f"          勇者{player_name}の伝説が始まった!")
                    print("----------------------------------------")
                    game_clear = True
                    break
            
            if "battle_result" in locals() and battle_result == "escaped":
                 pass
            else:
                 continue

        # 通常の場所移動選択肢の表示
        print("\nどこへ移動しますか? (status: ステータス表示, exit: 終了)")
        available_choices_nums = []
        choice_mapping = {}

        # 通常の選択肢を追加
        for i, (choice_text, next_location_key) in enumerate(current_location["choices"].items()):
            choice_num = str(i + 1)
            print(f"{choice_num}: {choice_text}")
            available_choices_nums.append(choice_num)
            choice_mapping[choice_num] = next_location_key

        # 回復の泉への選択肢を動的に追加
        # 「東の森_戦闘」と「西の洞窟_戦闘」をクリアしているかチェック
        if "東の森_戦闘" in player["cleared_enemies"] and "西の洞窟_戦闘" in player["cleared_enemies"] and current_location_key == "始まりの町":
            # 既に選択肢になければ追加
            if "回復の泉へ向かう" not in current_location["choices"]:
                choice_num = str(len(available_choices_nums) + 1)
                print(f"{choice_num}: 回復の泉へ向かう")
                available_choices_nums.append(choice_num)
                choice_mapping[choice_num] = "回復の泉"


        user_input = input(">> ").strip()

        if user_input.lower() == 'exit':
            print("ゲームを終了します。また遊んでね!")
            break
        elif user_input.lower() == 'status':
            display_player_status(player)
            continue
        
        if user_input in available_choices_nums:
            current_location_key = choice_mapping[user_input]
        else:
            print("無効な選択です。")
            continue

    if game_over:
        print("\n----------------------------------------")
        print("               GAME OVER                ")
        print("----------------------------------------")

基本的に敵やアイテムなどの効果、ステータスについては辞書型で盛り込む形になっています。

課題

現状、正常に機能はするものの、名前が勇者勇者になっていたり、画像の大きさがあっていなかったりとまだGUI的にも改良余地があるので、いくつかに分けて投稿を進めようと思います。

今後改良したいところ:
金のオーブ、銀のオーブ→アイコン一のずれ
表示ログの左寄りを真ん中にする
名前の修正
画像の調整
戦闘コマンドの調整

参考

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?