Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- .DS_Store +0 -0
- .gitattributes +15 -0
- app.py +219 -0
- llm.py +37 -0
- main.py +31 -0
- nodes.py +37 -0
- pics/.DS_Store +0 -0
- pics/0.png +3 -0
- pics/1.png +3 -0
- pics/2.png +3 -0
- pics/3.png +3 -0
- pics/4.png +3 -0
- pics/5.png +3 -0
- pics/6.png +3 -0
- pics/7.png +3 -0
- pics/8.png +3 -0
- pics/E1.png +3 -0
- pics/E2.png +3 -0
- pics/E3.png +3 -0
- pics/E4.png +3 -0
- pics/E5.png +3 -0
- pics/origin.png +3 -0
- player.py +58 -0
- requirements.txt +267 -0
- settings.py +155 -0
- utils.py +98 -0
.DS_Store
ADDED
|
Binary file (6.15 kB). View file
|
|
|
.gitattributes
CHANGED
|
@@ -33,3 +33,18 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
pics/0.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
pics/1.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
pics/2.png filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
pics/3.png filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
pics/4.png filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
pics/5.png filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
pics/6.png filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
pics/7.png filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
pics/8.png filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
pics/E1.png filter=lfs diff=lfs merge=lfs -text
|
| 46 |
+
pics/E2.png filter=lfs diff=lfs merge=lfs -text
|
| 47 |
+
pics/E3.png filter=lfs diff=lfs merge=lfs -text
|
| 48 |
+
pics/E4.png filter=lfs diff=lfs merge=lfs -text
|
| 49 |
+
pics/E5.png filter=lfs diff=lfs merge=lfs -text
|
| 50 |
+
pics/origin.png filter=lfs diff=lfs merge=lfs -text
|
app.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
import gradio as gr
|
| 4 |
+
import numpy as np
|
| 5 |
+
from PIL import Image
|
| 6 |
+
import torch
|
| 7 |
+
from transformers import pipeline
|
| 8 |
+
|
| 9 |
+
from huggingface_hub import InferenceClient
|
| 10 |
+
from moviepy.video.io.ImageSequenceClip import ImageSequenceClip
|
| 11 |
+
|
| 12 |
+
from settings import EVENTS
|
| 13 |
+
from player import Player
|
| 14 |
+
from utils import PROMPT, INPUT_PROMPT, extract_after_final_answer
|
| 15 |
+
|
| 16 |
+
# Initialize the HF Inference Client
|
| 17 |
+
# It will automatically pick up the HF_TOKEN environment variable if it's set
|
| 18 |
+
client = InferenceClient()
|
| 19 |
+
|
| 20 |
+
# Define the models we want to use from the Hugging Face Hub
|
| 21 |
+
LLM_MODEL = "Qwen/Qwen2.5-7B-Instruct"
|
| 22 |
+
|
| 23 |
+
target_size=(1024, 1024)
|
| 24 |
+
|
| 25 |
+
def compile_game_video(player, output_path="game_summary.mp4", fps=0.5):
|
| 26 |
+
"""Compiles accumulated images into a video. fps=0.5 means 2 seconds per image."""
|
| 27 |
+
image_paths = player.get_images()
|
| 28 |
+
if not image_paths:
|
| 29 |
+
return None
|
| 30 |
+
|
| 31 |
+
image_arrays = []
|
| 32 |
+
try:
|
| 33 |
+
for idx, img_path in enumerate(image_paths):
|
| 34 |
+
if isinstance(img_path, str) and os.path.exists(img_path):
|
| 35 |
+
with Image.open(img_path) as img:
|
| 36 |
+
# 1. Force convert to RGB (strips alpha channel if PNG)
|
| 37 |
+
img = img.convert("RGB")
|
| 38 |
+
|
| 39 |
+
# 2. Resize to prevent MoviePy size mismatch crash
|
| 40 |
+
if img.size != target_size:
|
| 41 |
+
img = img.resize(target_size, Image.Resampling.LANCZOS if hasattr(Image, 'Resampling') else Image.LANCZOS)
|
| 42 |
+
|
| 43 |
+
# 3. Convert PIL Image directly into a NumPy array and store in RAM
|
| 44 |
+
img_array = np.array(img)
|
| 45 |
+
image_arrays.append(img_array)
|
| 46 |
+
else:
|
| 47 |
+
print(f"⚠️ Warning: Skipping missing path: {img_path}")
|
| 48 |
+
|
| 49 |
+
if not image_arrays:
|
| 50 |
+
print("❌ Error: No valid image arrays collected.")
|
| 51 |
+
return None
|
| 52 |
+
|
| 53 |
+
print(f"🎬 Compiling {len(image_arrays)} arrays directly from RAM...")
|
| 54 |
+
# MoviePy natively accepts a list of NumPy arrays!
|
| 55 |
+
clip = ImageSequenceClip(image_arrays, fps=fps)
|
| 56 |
+
clip.write_videofile(output_path, codec="libx264", logger=None)
|
| 57 |
+
return output_path
|
| 58 |
+
except Exception as e:
|
| 59 |
+
print(f"Video compilation failed: {e}")
|
| 60 |
+
return None
|
| 61 |
+
|
| 62 |
+
def start_game():
|
| 63 |
+
"""Initializes or resets the game state when the app loads or restarts."""
|
| 64 |
+
initial_loc = 0
|
| 65 |
+
initial_player = Player('player') # Assuming your Player class initializes fresh attributes
|
| 66 |
+
initial_event = EVENTS[initial_loc]
|
| 67 |
+
|
| 68 |
+
initial_player.append_file(initial_event.filelist)
|
| 69 |
+
|
| 70 |
+
# Format the very first event presentation
|
| 71 |
+
initial_story = f"### {initial_event.get_title()}\n\n{initial_event.get_description()}"
|
| 72 |
+
status = initial_player.get_attribute()
|
| 73 |
+
|
| 74 |
+
# Return initial UI values + initial state values
|
| 75 |
+
return (
|
| 76 |
+
initial_story, # updates game_screen_ui
|
| 77 |
+
status['hp'], # updates status_ui
|
| 78 |
+
status['mp'],
|
| 79 |
+
status['coin'],
|
| 80 |
+
initial_event.get_images(),
|
| 81 |
+
gr.update(visible=True), # ensures user input layout is visible
|
| 82 |
+
gr.update(visible=False, value=None),
|
| 83 |
+
initial_loc, # sets state_loc
|
| 84 |
+
initial_player, # sets state_player
|
| 85 |
+
initial_event # sets state_cur
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
def play_turn(human_response, loc, player, cur, custom_token, request: gr.Request):
|
| 89 |
+
"""Executes a single turn of the game loop based on user input."""
|
| 90 |
+
if not human_response.strip():
|
| 91 |
+
return "Please type an action to proceed!", str(player.get_attribute()), gr.update(), gr.update(), loc, player, cur
|
| 92 |
+
|
| 93 |
+
chosen_token = custom_token.strip() if custom_token and custom_token.strip() else (request.token if request else None)
|
| 94 |
+
|
| 95 |
+
if not chosen_token:
|
| 96 |
+
yield "❌ **Authentication Required:** Please enter a token above or use OAuth.", "N/A", gr.update(), gr.update(), loc, player, cur
|
| 97 |
+
return
|
| 98 |
+
|
| 99 |
+
# 1. Process AI Model Response
|
| 100 |
+
messages = [
|
| 101 |
+
{
|
| 102 |
+
"role": "user",
|
| 103 |
+
"content": PROMPT + INPUT_PROMPT.format(human_response)
|
| 104 |
+
}
|
| 105 |
+
]
|
| 106 |
+
try:
|
| 107 |
+
response = client.chat_completion(
|
| 108 |
+
model=LLM_MODEL,
|
| 109 |
+
messages=messages,
|
| 110 |
+
max_tokens=500,
|
| 111 |
+
temperature=0.7
|
| 112 |
+
)
|
| 113 |
+
except Exception as e:
|
| 114 |
+
yield f"Error calling API: {str(e)}", str(player.get_attribute()), gr.update(), loc, player, cur
|
| 115 |
+
return
|
| 116 |
+
|
| 117 |
+
processed_response = extract_after_final_answer(response.choices[0].message.content)
|
| 118 |
+
|
| 119 |
+
# 2. Update Game State / Location
|
| 120 |
+
loc = cur._next(processed_response, player)
|
| 121 |
+
attribute = player.get_attribute()
|
| 122 |
+
|
| 123 |
+
# 3. Check for Game Over conditions
|
| 124 |
+
if attribute['hp'] <= 0 or attribute['mp'] <= 0:
|
| 125 |
+
loc = -1
|
| 126 |
+
|
| 127 |
+
cur = EVENTS[loc]
|
| 128 |
+
if loc == -1 or loc >= 10:
|
| 129 |
+
# Final summary / Game Over screens
|
| 130 |
+
end_title = cur.get_title()
|
| 131 |
+
end_desc = cur.get_description()
|
| 132 |
+
next_image = cur.get_images()
|
| 133 |
+
player.append_file(cur.filelist)
|
| 134 |
+
|
| 135 |
+
final_story = f"### 🏁 {end_title}\n\n{end_desc}\n\n**The Journey Ends Here.**"
|
| 136 |
+
yield final_story, attribute['hp'], attribute['mp'], attribute['coin'], next_image, gr.update(visible=False), gr.update(), loc, player, cur
|
| 137 |
+
# Compile video now that game loop is terminating
|
| 138 |
+
video_file = compile_game_video(player)
|
| 139 |
+
|
| 140 |
+
if video_file:
|
| 141 |
+
final_story += "\n\n🎞️ **Your adventure video is ready below!**"
|
| 142 |
+
yield final_story, attribute['hp'], attribute['mp'], attribute['coin'], next_image, gr.update(visible=False), gr.update(visible=True, value=video_file), loc, player, cur
|
| 143 |
+
return
|
| 144 |
+
|
| 145 |
+
# 4. Advance to the next event normally
|
| 146 |
+
cur = EVENTS[loc]
|
| 147 |
+
next_story = f"### {cur.get_title()}\n\n{cur.get_description()}"
|
| 148 |
+
next_image = cur.get_images()
|
| 149 |
+
|
| 150 |
+
player.append_file(cur.filelist)
|
| 151 |
+
|
| 152 |
+
# Clear out the text box for the next turn while returning updated states
|
| 153 |
+
yield next_story, attribute['hp'], attribute['mp'], attribute['coin'], next_image, gr.update(value=""), gr.update(), loc, player, cur
|
| 154 |
+
|
| 155 |
+
# --------------------------------------------------------------------------
|
| 156 |
+
# LAYOUT DESIGN WITH INTEGRATED GALLERY BUILDER
|
| 157 |
+
# --------------------------------------------------------------------------
|
| 158 |
+
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 159 |
+
gr.Markdown("# ⚔️ RPG Sandbox")
|
| 160 |
+
gr.Markdown("## You can find your adventure recap below.")
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
with gr.Accordion("🔑 Authentication Options", open=False):
|
| 164 |
+
with gr.Row():
|
| 165 |
+
gr.LoginButton()
|
| 166 |
+
user_token_input = gr.Textbox(label="Or paste your HF Token (hf_...)", type="password")
|
| 167 |
+
|
| 168 |
+
state_loc = gr.State()
|
| 169 |
+
state_player = gr.State()
|
| 170 |
+
state_cur = gr.State()
|
| 171 |
+
|
| 172 |
+
with gr.Row():
|
| 173 |
+
with gr.Column(scale=2):
|
| 174 |
+
game_screen_ui = gr.Markdown(value="### Setup authentication, then click below to wake up.")
|
| 175 |
+
|
| 176 |
+
gr.Markdown("### 📜 Status Panel")
|
| 177 |
+
hp_ui = gr.Textbox(label="HP", interactive=False, value="0")
|
| 178 |
+
mp_ui = gr.Textbox(label="Mana Points (MP)", interactive=False, value="0")
|
| 179 |
+
coin_ui = gr.Textbox(label="Mana Points (MP)", interactive=False, value="0")
|
| 180 |
+
|
| 181 |
+
with gr.Row(visible=False) as input_panel:
|
| 182 |
+
user_input = gr.Textbox(label="Your Turn Action", placeholder="Type action and hit Enter...", scale=4)
|
| 183 |
+
submit_btn = gr.Button("Execute Action", variant="primary", scale=1)
|
| 184 |
+
|
| 185 |
+
start_btn = gr.Button("🚀 Start / Reset Game", variant="secondary")
|
| 186 |
+
|
| 187 |
+
with gr.Column(scale=3):
|
| 188 |
+
gr.Markdown("### 🖼️ Story Scene")
|
| 189 |
+
|
| 190 |
+
# UPGRADED: Gr.Image swapped to gr.Gallery to swallow flexible size limits
|
| 191 |
+
game_gallery_ui = gr.Gallery(
|
| 192 |
+
label="Story Plot",
|
| 193 |
+
columns=[2], # Display up to 2 side-by-side tiles automatically
|
| 194 |
+
rows=[2],
|
| 195 |
+
object_fit="contain",
|
| 196 |
+
height=800,
|
| 197 |
+
interactive=False
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
# Added: Video output component, hidden by default
|
| 201 |
+
video_summary_ui = gr.Video(label="🎬 Adventure Recap", visible=False, autoplay=True)
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
start_btn.click(
|
| 205 |
+
fn=start_game,
|
| 206 |
+
outputs=[game_screen_ui, hp_ui, mp_ui, coin_ui, game_gallery_ui, input_panel, video_summary_ui, state_loc, state_player, state_cur]
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
turn_config = {
|
| 210 |
+
"fn": play_turn,
|
| 211 |
+
"inputs": [user_input, state_loc, state_player, state_cur, user_token_input],
|
| 212 |
+
"outputs": [game_screen_ui, hp_ui, mp_ui, coin_ui, game_gallery_ui, user_input, video_summary_ui, state_loc, state_player, state_cur]
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
submit_btn.click(**turn_config)
|
| 216 |
+
user_input.submit(**turn_config)
|
| 217 |
+
|
| 218 |
+
if __name__ == "__main__":
|
| 219 |
+
demo.queue().launch()
|
llm.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint
|
| 2 |
+
|
| 3 |
+
HUGGINGFACE = {
|
| 4 |
+
'type': 'huggingface',
|
| 5 |
+
'param':{
|
| 6 |
+
'repo_id': "Qwen/Qwen2.5-7B-Instruct",
|
| 7 |
+
'task': 'text-generation',
|
| 8 |
+
'max_new_tokens': 512,
|
| 9 |
+
'do_sample': False,
|
| 10 |
+
'repetition_penalty': 1.03,
|
| 11 |
+
'provider': 'auto',
|
| 12 |
+
},
|
| 13 |
+
'model': 'huggingface:Qwen2.5-7B-Instruct',
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
HUGGINGFACE_LITE = {
|
| 17 |
+
'type': 'huggingface-lite',
|
| 18 |
+
'param':{
|
| 19 |
+
'repo_id': "Qwen/Qwen2.5-1.5B-Instruct",
|
| 20 |
+
'task': 'text-generation',
|
| 21 |
+
'max_new_tokens': 512,
|
| 22 |
+
'do_sample': False,
|
| 23 |
+
'repetition_penalty': 1.03,
|
| 24 |
+
'provider': 'auto',
|
| 25 |
+
},
|
| 26 |
+
'model': 'huggingface:Qwen2.5-1.5B-Instruct',
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
valid_LLM = [HUGGINGFACE, HUGGINGFACE_LITE]
|
| 30 |
+
|
| 31 |
+
def setup_model(config: dict, callbacks=None):
|
| 32 |
+
if config['type'].startswith('huggingface'):
|
| 33 |
+
llm = HuggingFaceEndpoint(**config['param'])
|
| 34 |
+
model = ChatHuggingFace(llm=llm, callbacks=callbacks)
|
| 35 |
+
else:
|
| 36 |
+
model = None
|
| 37 |
+
return model
|
main.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dotenv import load_dotenv
|
| 2 |
+
load_dotenv('./.env')
|
| 3 |
+
|
| 4 |
+
from settings import EVENTS
|
| 5 |
+
from player import Player
|
| 6 |
+
from llm import *
|
| 7 |
+
from utils import PROMPT, INPUT_PROMPT, extract_after_final_answer
|
| 8 |
+
|
| 9 |
+
loc = 0
|
| 10 |
+
cur = EVENTS[loc]
|
| 11 |
+
player = Player('player-1')
|
| 12 |
+
model = setup_model(HUGGINGFACE)
|
| 13 |
+
|
| 14 |
+
while loc != -1 and loc < 10:
|
| 15 |
+
print(f'Title: {cur.get_title()}')
|
| 16 |
+
print(f'{cur.get_description()}')
|
| 17 |
+
|
| 18 |
+
response = input("Human Response: ")
|
| 19 |
+
response = model.invoke(PROMPT + INPUT_PROMPT.format(response))
|
| 20 |
+
response = extract_after_final_answer(response.content)
|
| 21 |
+
loc = cur._next(response, player)
|
| 22 |
+
attribute = player.get_attribute()
|
| 23 |
+
print(response, loc)
|
| 24 |
+
if attribute['hp'] <= 0 or attribute['mp'] <= 0:
|
| 25 |
+
loc = -1
|
| 26 |
+
cur = EVENTS[loc]
|
| 27 |
+
|
| 28 |
+
print('*'*20)
|
| 29 |
+
print(f'Title: {cur.get_title()}')
|
| 30 |
+
print(f'{cur.get_description()}')
|
| 31 |
+
print(player.get_attribute())
|
nodes.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from player import Player
|
| 2 |
+
from utils import *
|
| 3 |
+
|
| 4 |
+
class Node:
|
| 5 |
+
def __init__(self, title: str, description: str, filelist: list[str], effect: dict = None, route = None, default: int = -1):
|
| 6 |
+
self.title = title
|
| 7 |
+
self.description = description
|
| 8 |
+
self.filelist = filelist
|
| 9 |
+
self.effect = effect
|
| 10 |
+
self.route = route
|
| 11 |
+
self.default = default
|
| 12 |
+
|
| 13 |
+
def _next(self, result: str, player: Player) -> int:
|
| 14 |
+
result = result.upper()
|
| 15 |
+
|
| 16 |
+
paired = self.effect.get(result, None) if self.effect is not None else None
|
| 17 |
+
if paired is not None:
|
| 18 |
+
for (change, attr, amount) in paired:
|
| 19 |
+
change(player, attr, amount)
|
| 20 |
+
|
| 21 |
+
if self.route is None:
|
| 22 |
+
return -1
|
| 23 |
+
if isinstance(self.route, dict):
|
| 24 |
+
next_node = self.route.get(result, self.default)
|
| 25 |
+
else:
|
| 26 |
+
print('Here')
|
| 27 |
+
next_node = self.route(player)
|
| 28 |
+
return next_node - 1 if next_node >= 0 else -1
|
| 29 |
+
|
| 30 |
+
def get_title(self) -> str:
|
| 31 |
+
return self.title
|
| 32 |
+
|
| 33 |
+
def get_description(self) -> str:
|
| 34 |
+
return self.description
|
| 35 |
+
|
| 36 |
+
def get_images(self) -> list[str]:
|
| 37 |
+
return self.filelist
|
pics/.DS_Store
ADDED
|
Binary file (6.15 kB). View file
|
|
|
pics/0.png
ADDED
|
Git LFS Details
|
pics/1.png
ADDED
|
Git LFS Details
|
pics/2.png
ADDED
|
Git LFS Details
|
pics/3.png
ADDED
|
Git LFS Details
|
pics/4.png
ADDED
|
Git LFS Details
|
pics/5.png
ADDED
|
Git LFS Details
|
pics/6.png
ADDED
|
Git LFS Details
|
pics/7.png
ADDED
|
Git LFS Details
|
pics/8.png
ADDED
|
Git LFS Details
|
pics/E1.png
ADDED
|
Git LFS Details
|
pics/E2.png
ADDED
|
Git LFS Details
|
pics/E3.png
ADDED
|
Git LFS Details
|
pics/E4.png
ADDED
|
Git LFS Details
|
pics/E5.png
ADDED
|
Git LFS Details
|
pics/origin.png
ADDED
|
Git LFS Details
|
player.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
|
| 3 |
+
class Player:
|
| 4 |
+
def __init__(self, name: str):
|
| 5 |
+
self.name = name
|
| 6 |
+
print(f"Player's Name is {name} and initialize all his attributes")
|
| 7 |
+
self.hp = 75
|
| 8 |
+
self.mp = 50
|
| 9 |
+
self.coin = 20
|
| 10 |
+
self.items = []
|
| 11 |
+
self.frames = []
|
| 12 |
+
|
| 13 |
+
def reduce_hp(self, amount: int):
|
| 14 |
+
print(f'Reduce {amount} HP')
|
| 15 |
+
self.hp = max(0, self.hp-amount)
|
| 16 |
+
|
| 17 |
+
def add_hp(self, amount: int):
|
| 18 |
+
print(f'Add {amount} HP')
|
| 19 |
+
self.hp += amount
|
| 20 |
+
|
| 21 |
+
def reduce_mp(self, amount: int):
|
| 22 |
+
print(f'Reduce {amount} MP')
|
| 23 |
+
self.mp = max(0, self.mp-amount)
|
| 24 |
+
|
| 25 |
+
def add_mp(self, amount: int):
|
| 26 |
+
print(f'Add {amount} MP')
|
| 27 |
+
self.mp += amount
|
| 28 |
+
|
| 29 |
+
def reduce_coin(self, amount: int):
|
| 30 |
+
print(f'Reduce {amount} Coin')
|
| 31 |
+
self.coin = max(0, self.coin-amount)
|
| 32 |
+
|
| 33 |
+
def add_coin(self, amount: int):
|
| 34 |
+
print(f'Add {amount} Coin')
|
| 35 |
+
self.coin += amount
|
| 36 |
+
|
| 37 |
+
def add_item(self, item):
|
| 38 |
+
print(f'Add {item} to the item bag')
|
| 39 |
+
self.items.append(item)
|
| 40 |
+
|
| 41 |
+
def use_item(self, item):
|
| 42 |
+
if item not in self.items:
|
| 43 |
+
print(f'Could not use {item}')
|
| 44 |
+
else:
|
| 45 |
+
self.items.remove(item)
|
| 46 |
+
print(f'Used up {item} and there are {len(item)} items left')
|
| 47 |
+
|
| 48 |
+
def get_attribute(self) -> dict:
|
| 49 |
+
return {'hp': self.hp, 'mp': self.mp, 'coin': self.coin}
|
| 50 |
+
|
| 51 |
+
def set_name(self, name: str):
|
| 52 |
+
self.name = name
|
| 53 |
+
|
| 54 |
+
def append_file(self, filelist: list[str]):
|
| 55 |
+
self.frames += filelist
|
| 56 |
+
|
| 57 |
+
def get_images(self):
|
| 58 |
+
return self.frames
|
requirements.txt
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
acres==0.5.0
|
| 2 |
+
aiofiles==24.1.0
|
| 3 |
+
aiohappyeyeballs==2.6.1
|
| 4 |
+
aiohttp==3.12.15
|
| 5 |
+
aiosignal==1.4.0
|
| 6 |
+
annotated-doc==0.0.4
|
| 7 |
+
annotated-types==0.7.0
|
| 8 |
+
anyio==4.10.0
|
| 9 |
+
appnope==0.1.4
|
| 10 |
+
argon2-cffi==25.1.0
|
| 11 |
+
argon2-cffi-bindings==25.1.0
|
| 12 |
+
arrow==1.3.0
|
| 13 |
+
asttokens==3.0.0
|
| 14 |
+
async-lru==2.0.5
|
| 15 |
+
attrs==25.3.0
|
| 16 |
+
Authlib==1.7.2
|
| 17 |
+
babel==2.17.0
|
| 18 |
+
backoff==2.2.1
|
| 19 |
+
beautifulsoup4==4.13.5
|
| 20 |
+
bleach==6.2.0
|
| 21 |
+
brotli==1.2.0
|
| 22 |
+
cachetools==5.5.2
|
| 23 |
+
certifi==2025.8.3
|
| 24 |
+
cffi==2.0.0
|
| 25 |
+
charset-normalizer==3.4.3
|
| 26 |
+
ci-info==0.3.0
|
| 27 |
+
click==8.4.1
|
| 28 |
+
clickhouse-connect==0.15.1
|
| 29 |
+
comm==0.2.3
|
| 30 |
+
configobj==5.0.9
|
| 31 |
+
configparser==7.2.0
|
| 32 |
+
contourpy==1.3.3
|
| 33 |
+
cryptography==45.0.7
|
| 34 |
+
cycler==0.12.1
|
| 35 |
+
dataclasses-json==0.6.7
|
| 36 |
+
ddgs==9.14.1
|
| 37 |
+
debugpy==1.8.16
|
| 38 |
+
decorator==5.2.1
|
| 39 |
+
defusedxml==0.7.1
|
| 40 |
+
distro==1.9.0
|
| 41 |
+
duckdb==1.5.2
|
| 42 |
+
duckduckgo_search==8.1.1
|
| 43 |
+
emoji==2.14.1
|
| 44 |
+
environs==9.5.0
|
| 45 |
+
etelemetry==0.3.1
|
| 46 |
+
executing==2.2.0
|
| 47 |
+
faiss-cpu==1.8.0
|
| 48 |
+
fastapi==0.136.1
|
| 49 |
+
fastjsonschema==2.21.2
|
| 50 |
+
ffmpy==1.0.0
|
| 51 |
+
filelock==3.25.2
|
| 52 |
+
filetype==1.2.0
|
| 53 |
+
fitz==0.0.1.dev2
|
| 54 |
+
fonttools==4.62.1
|
| 55 |
+
fqdn==1.5.1
|
| 56 |
+
frozenlist==1.7.0
|
| 57 |
+
fsspec==2026.2.0
|
| 58 |
+
google-ai-generativelanguage==0.6.18
|
| 59 |
+
google-api-core==2.25.1
|
| 60 |
+
google-auth==2.49.2
|
| 61 |
+
google-genai==1.73.1
|
| 62 |
+
googleapis-common-protos==1.70.0
|
| 63 |
+
gradio==6.6.0
|
| 64 |
+
gradio_client==2.1.0
|
| 65 |
+
greenlet==3.2.4
|
| 66 |
+
groovy==0.1.2
|
| 67 |
+
grpcio==1.69.0
|
| 68 |
+
grpcio-status==1.69.0
|
| 69 |
+
h11==0.16.0
|
| 70 |
+
hf-gradio==0.4.1
|
| 71 |
+
hf-xet==1.5.1
|
| 72 |
+
hnswlib==0.8.0
|
| 73 |
+
html5lib==1.1
|
| 74 |
+
httpcore==1.0.9
|
| 75 |
+
httplib2==0.30.0
|
| 76 |
+
httptools==0.7.1
|
| 77 |
+
httpx==0.28.1
|
| 78 |
+
httpx-sse==0.4.1
|
| 79 |
+
huggingface_hub==0.36.2
|
| 80 |
+
idna==3.10
|
| 81 |
+
ImageIO==2.37.3
|
| 82 |
+
imageio-ffmpeg==0.6.0
|
| 83 |
+
inquirerpy==0.3.4
|
| 84 |
+
ipykernel==6.30.1
|
| 85 |
+
ipython==9.5.0
|
| 86 |
+
ipython_pygments_lexers==1.1.1
|
| 87 |
+
ipywidgets==8.1.7
|
| 88 |
+
isoduration==20.11.0
|
| 89 |
+
itsdangerous==2.2.0
|
| 90 |
+
jedi==0.19.2
|
| 91 |
+
Jinja2==3.1.6
|
| 92 |
+
joblib==1.5.2
|
| 93 |
+
joserfc==1.7.1
|
| 94 |
+
json5==0.12.1
|
| 95 |
+
jsonpatch==1.33
|
| 96 |
+
jsonpointer==3.0.0
|
| 97 |
+
jsonschema==4.25.1
|
| 98 |
+
jsonschema-specifications==2025.4.1
|
| 99 |
+
jupyter-events==0.12.0
|
| 100 |
+
jupyter-lsp==2.3.0
|
| 101 |
+
jupyter_server==2.17.0
|
| 102 |
+
jupyter_server_terminals==0.5.3
|
| 103 |
+
jupyterlab_pygments==0.3.0
|
| 104 |
+
jupyterlab_server==2.27.3
|
| 105 |
+
jupyterlab_widgets==3.0.15
|
| 106 |
+
kiwisolver==1.5.0
|
| 107 |
+
langchain==0.3.27
|
| 108 |
+
langchain-community==0.3.29
|
| 109 |
+
langchain-core==0.3.75
|
| 110 |
+
langchain-google-genai==2.1.10
|
| 111 |
+
langchain-huggingface==0.3.1
|
| 112 |
+
langchain-milvus==0.1.7
|
| 113 |
+
langchain-text-splitters==0.3.9
|
| 114 |
+
langdetect==1.0.9
|
| 115 |
+
langgraph==0.6.7
|
| 116 |
+
langgraph-checkpoint==2.1.1
|
| 117 |
+
langgraph-prebuilt==0.6.4
|
| 118 |
+
langgraph-sdk==0.2.6
|
| 119 |
+
langsmith==0.4.19
|
| 120 |
+
lark==1.2.2
|
| 121 |
+
looseversion==1.3.0
|
| 122 |
+
lxml==6.0.1
|
| 123 |
+
lz4==4.4.5
|
| 124 |
+
Markdown==3.8.2
|
| 125 |
+
markdown-it-py==4.0.0
|
| 126 |
+
MarkupSafe==3.0.2
|
| 127 |
+
marshmallow==3.26.1
|
| 128 |
+
matplotlib==3.10.9
|
| 129 |
+
matplotlib-inline==0.1.7
|
| 130 |
+
mdurl==0.1.2
|
| 131 |
+
milvus-lite==2.4.12
|
| 132 |
+
mistune==3.1.4
|
| 133 |
+
moviepy==2.2.1
|
| 134 |
+
mpmath==1.3.0
|
| 135 |
+
multidict==6.6.4
|
| 136 |
+
mypy_extensions==1.1.0
|
| 137 |
+
nbclient==0.10.2
|
| 138 |
+
nbconvert==7.16.6
|
| 139 |
+
nbformat==5.10.4
|
| 140 |
+
nest-asyncio==1.6.0
|
| 141 |
+
networkx==3.6.1
|
| 142 |
+
nibabel==5.3.2
|
| 143 |
+
nipype==1.10.0
|
| 144 |
+
nltk==3.9.1
|
| 145 |
+
notebook_shim==0.2.4
|
| 146 |
+
numpy==1.26.4
|
| 147 |
+
olefile==0.47
|
| 148 |
+
orjson==3.11.3
|
| 149 |
+
ormsgpack==1.10.0
|
| 150 |
+
packaging==25.0
|
| 151 |
+
pandas==2.3.2
|
| 152 |
+
pandocfilters==1.5.1
|
| 153 |
+
parso==0.8.5
|
| 154 |
+
pathlib==1.0.1
|
| 155 |
+
pexpect==4.9.0
|
| 156 |
+
pfzy==0.3.4
|
| 157 |
+
pillow==11.3.0
|
| 158 |
+
platformdirs==4.4.0
|
| 159 |
+
playwright-stealth==2.0.3
|
| 160 |
+
posthog==7.14.0
|
| 161 |
+
primp==1.2.3
|
| 162 |
+
proglog==0.1.12
|
| 163 |
+
prometheus_client==0.22.1
|
| 164 |
+
prompt_toolkit==3.0.52
|
| 165 |
+
propcache==0.3.2
|
| 166 |
+
proto-plus==1.26.1
|
| 167 |
+
protobuf==5.29.5
|
| 168 |
+
prov==2.1.1
|
| 169 |
+
psutil==7.0.0
|
| 170 |
+
ptyprocess==0.7.0
|
| 171 |
+
pure_eval==0.2.3
|
| 172 |
+
puremagic==1.30
|
| 173 |
+
pyasn1==0.6.1
|
| 174 |
+
pyasn1_modules==0.4.2
|
| 175 |
+
pycparser==2.22
|
| 176 |
+
pydantic==2.13.4
|
| 177 |
+
pydantic-settings==2.10.1
|
| 178 |
+
pydantic_core==2.46.4
|
| 179 |
+
pydot==4.0.1
|
| 180 |
+
pydub==0.25.1
|
| 181 |
+
pyee==13.0.1
|
| 182 |
+
Pygments==2.19.2
|
| 183 |
+
pymilvus==2.4.9
|
| 184 |
+
PyMuPDF==1.26.4
|
| 185 |
+
pyparsing==3.2.3
|
| 186 |
+
pypdf==6.0.0
|
| 187 |
+
python-dateutil==2.9.0.post0
|
| 188 |
+
python-dotenv==1.1.1
|
| 189 |
+
python-iso639==2025.2.18
|
| 190 |
+
python-json-logger==3.3.0
|
| 191 |
+
python-magic==0.4.27
|
| 192 |
+
python-multipart==0.0.32
|
| 193 |
+
python-oxmsg==0.0.2
|
| 194 |
+
pytz==2025.2
|
| 195 |
+
pyxnat==1.6.3
|
| 196 |
+
PyYAML==6.0.2
|
| 197 |
+
pyzmq==27.0.2
|
| 198 |
+
RapidFuzz==3.14.0
|
| 199 |
+
rdflib==7.1.4
|
| 200 |
+
referencing==0.36.2
|
| 201 |
+
regex==2025.9.1
|
| 202 |
+
requests==2.32.5
|
| 203 |
+
requests-toolbelt==1.0.0
|
| 204 |
+
rfc3339-validator==0.1.4
|
| 205 |
+
rfc3986-validator==0.1.1
|
| 206 |
+
rfc3987-syntax==1.1.0
|
| 207 |
+
rich==15.0.0
|
| 208 |
+
rpds-py==0.27.1
|
| 209 |
+
rsa==4.9.1
|
| 210 |
+
safehttpx==0.1.7
|
| 211 |
+
safetensors==0.6.2
|
| 212 |
+
scikit-learn==1.7.1
|
| 213 |
+
scipy==1.16.1
|
| 214 |
+
seaborn==0.13.2
|
| 215 |
+
semantic-version==2.10.0
|
| 216 |
+
Send2Trash==1.8.3
|
| 217 |
+
sentence-transformers==5.1.0
|
| 218 |
+
setuptools==82.0.1
|
| 219 |
+
shellingham==1.5.4
|
| 220 |
+
simplejson==3.20.1
|
| 221 |
+
six==1.17.0
|
| 222 |
+
sniffio==1.3.1
|
| 223 |
+
soupsieve==2.8
|
| 224 |
+
SQLAlchemy==2.0.43
|
| 225 |
+
stack-data==0.6.3
|
| 226 |
+
starlette==0.52.1
|
| 227 |
+
sympy==1.14.0
|
| 228 |
+
tenacity==9.1.2
|
| 229 |
+
terminado==0.18.1
|
| 230 |
+
threadpoolctl==3.6.0
|
| 231 |
+
tinycss2==1.4.0
|
| 232 |
+
tokenizers==0.21.4
|
| 233 |
+
tomlkit==0.13.3
|
| 234 |
+
torch==2.2.2
|
| 235 |
+
torchvision==0.17.2
|
| 236 |
+
tornado==6.5.2
|
| 237 |
+
tqdm==4.67.1
|
| 238 |
+
traitlets==5.14.3
|
| 239 |
+
traits==7.0.2
|
| 240 |
+
transformers==4.49.0
|
| 241 |
+
typer==0.25.1
|
| 242 |
+
types-python-dateutil==2.9.0.20250822
|
| 243 |
+
types-requests==2.32.4.20250809
|
| 244 |
+
typing-inspect==0.9.0
|
| 245 |
+
typing-inspection==0.4.2
|
| 246 |
+
typing_extensions==4.15.0
|
| 247 |
+
tzdata==2025.2
|
| 248 |
+
ujson==5.11.0
|
| 249 |
+
unstructured==0.18.14
|
| 250 |
+
unstructured-client==0.42.3
|
| 251 |
+
uri-template==1.3.0
|
| 252 |
+
urllib3==2.5.0
|
| 253 |
+
uvicorn==0.46.0
|
| 254 |
+
uvloop==0.22.1
|
| 255 |
+
watchfiles==1.1.1
|
| 256 |
+
wcwidth==0.2.13
|
| 257 |
+
webcolors==24.11.1
|
| 258 |
+
webencodings==0.5.1
|
| 259 |
+
websocket-client==1.8.0
|
| 260 |
+
websockets==16.0
|
| 261 |
+
wheel==0.47.0
|
| 262 |
+
widgetsnbextension==4.0.14
|
| 263 |
+
wikipedia==1.4.0
|
| 264 |
+
wrapt==1.17.3
|
| 265 |
+
xxhash==3.5.0
|
| 266 |
+
yarl==1.20.1
|
| 267 |
+
zstandard==0.24.0
|
settings.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
|
| 3 |
+
from nodes import Node
|
| 4 |
+
from utils import *
|
| 5 |
+
|
| 6 |
+
EVENTS = [
|
| 7 |
+
Node(
|
| 8 |
+
"Dungeon Entrance",
|
| 9 |
+
"A massive gate blocks the dungeon. A stone guardian awakens and says: 'Only those with strength may enter. What will you do to pass through it? \n\nAttack with sword, or magic, or even BRIDE it?",
|
| 10 |
+
['pics/0.png'],
|
| 11 |
+
{
|
| 12 |
+
PHYSICAL_ATTACK: [(minus_attribute, 'hp', 20)],
|
| 13 |
+
MAGICAL_ATTACK: [(minus_attribute, 'mp', 20)],
|
| 14 |
+
SPEND_MONEY: [(minus_attribute, 'coin', 30)]
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
PHYSICAL_ATTACK: 2,
|
| 18 |
+
MAGICAL_ATTACK: 3,
|
| 19 |
+
SPEND_MONEY: 4,
|
| 20 |
+
LEAVE: -1
|
| 21 |
+
},
|
| 22 |
+
default = random.randint(2, 4)
|
| 23 |
+
),
|
| 24 |
+
Node(
|
| 25 |
+
"Injured Warrior",
|
| 26 |
+
"Inside the dungeon, you find a dying warrior. He carries a rare potion. What will you do? \n\nHelp Him, or Attack Him?",
|
| 27 |
+
['pics/1.png'],
|
| 28 |
+
{
|
| 29 |
+
PHYSICAL_REFILL: [(add_attribute, 'hp', 5)],
|
| 30 |
+
PHYSICAL_ATTACK: [(minus_attribute, 'hp', 5)],
|
| 31 |
+
MAGICAL_ATTACK: [(minus_attribute, 'mp', 5)],
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
PHYSICAL_REFILL: 5,
|
| 35 |
+
PHYSICAL_ATTACK: 5,
|
| 36 |
+
MAGICAL_ATTACK: 5,
|
| 37 |
+
LEAVE: -1
|
| 38 |
+
},
|
| 39 |
+
default = 5
|
| 40 |
+
),
|
| 41 |
+
Node(
|
| 42 |
+
"Magic Altar",
|
| 43 |
+
"A mysterious altar offers power. What will you do? \n\nAttack this altar or Refill your stamina here?",
|
| 44 |
+
['pics/2.png'],
|
| 45 |
+
{
|
| 46 |
+
MAGICAL_REFILL: [(add_attribute, 'mp', 10), (minus_attribute, "hp", 5)],
|
| 47 |
+
PHYSICAL_ATTACK: [(minus_attribute, 'hp', 15)],
|
| 48 |
+
PHYSICAL_REFILL: [(add_attribute, 'hp', 10)]
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
MAGICAL_REFILL: 5,
|
| 52 |
+
PHYSICAL_ATTACK: 5,
|
| 53 |
+
},
|
| 54 |
+
default = 5
|
| 55 |
+
),
|
| 56 |
+
Node(
|
| 57 |
+
"Merchant Room",
|
| 58 |
+
"A merchant appears and says: 'I trade only with survivors.' What will you do? \n\nSpend money, or Rob him?",
|
| 59 |
+
['pics/3.png'],
|
| 60 |
+
{
|
| 61 |
+
PHYSICAL_ATTACK: [(minus_attribute, "hp", 15), (add_attribute, 'coun', 10)],
|
| 62 |
+
MAGICAL_ATTACK: [(minus_attribute, 'mp', 10), (add_attribute, 'coin', 10)],
|
| 63 |
+
SPEND_MONEY: [(minus_attribute, 'coin', 25), (add_attribute, 'mp', 5), (add_attribute, 'hp', 5)],
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
PHYSICAL_ATTACK: 5,
|
| 67 |
+
MAGICAL_ATTACK: 5,
|
| 68 |
+
MAGICAL_REFILL: 5,
|
| 69 |
+
SPEND_MONEY: 5,
|
| 70 |
+
EARN_MONEY: 5,
|
| 71 |
+
},
|
| 72 |
+
default = 5
|
| 73 |
+
),
|
| 74 |
+
Node(
|
| 75 |
+
"Underground Lake",
|
| 76 |
+
"A giant monster blocks the way. What will you do? \n\nAttack or Run?",
|
| 77 |
+
['pics/4.png'],
|
| 78 |
+
{
|
| 79 |
+
PHYSICAL_ATTACK: [(minus_attribute, "hp", 30)],
|
| 80 |
+
MAGICAL_ATTACK: [(minus_attribute, 'mp', 20)],
|
| 81 |
+
LEAVE: [(minus_attribute, 'hp', 10)],
|
| 82 |
+
},
|
| 83 |
+
{
|
| 84 |
+
PHYSICAL_ATTACK: 6,
|
| 85 |
+
MAGICAL_ATTACK: 7,
|
| 86 |
+
LEAVE: 8,
|
| 87 |
+
},
|
| 88 |
+
default = random.randint(6, 8)
|
| 89 |
+
),
|
| 90 |
+
Node(
|
| 91 |
+
"Ancient Library",
|
| 92 |
+
"You find dragon history. What will you do? \n\nDestroy, or Rest, or Buy Weapon?",
|
| 93 |
+
['pics/5.png', 'pics/origin.png'],
|
| 94 |
+
{
|
| 95 |
+
PHYSICAL_REFILL: [(add_attribute, "hp", 10)],
|
| 96 |
+
MAGICAL_REFILL: [(add_attribute, 'mp', 10)],
|
| 97 |
+
EARN_MONEY: [(add_attribute, 'coin', 20)],
|
| 98 |
+
MAGICAL_ATTACK: [(minus_attribute, 'mp', 15)]
|
| 99 |
+
},
|
| 100 |
+
{
|
| 101 |
+
PHYSICAL_REFILL: 9,
|
| 102 |
+
MAGICAL_REFILL: 9,
|
| 103 |
+
EARN_MONEY: 9,
|
| 104 |
+
},
|
| 105 |
+
default = 9
|
| 106 |
+
),
|
| 107 |
+
Node(
|
| 108 |
+
"Dragon Prison",
|
| 109 |
+
"A baby dragon is trapped. It is telling you the ORIGIN of DRAGON. What will you do? \n\nAttack, or Run, or Rescue?",
|
| 110 |
+
['pics/6.png', 'pics/origin.png'],
|
| 111 |
+
{
|
| 112 |
+
PHYSICAL_ATTACK: [(minus_attribute, "hp", 30)],
|
| 113 |
+
MAGICAL_ATTACK: [(minus_attribute, 'mp', 20)],
|
| 114 |
+
LEAVE: [(minus_attribute, 'hp', 10)],
|
| 115 |
+
},
|
| 116 |
+
{
|
| 117 |
+
PHYSICAL_REFILL: 9,
|
| 118 |
+
MAGICAL_REFILL: 9,
|
| 119 |
+
EARN_MONEY: 9,
|
| 120 |
+
},
|
| 121 |
+
default = 9
|
| 122 |
+
),
|
| 123 |
+
Node(
|
| 124 |
+
"Dark Corridor",
|
| 125 |
+
"A shadow creature attacks. Its attack is fierce and fast. What will you do? \n\nFight back, or Run away?",
|
| 126 |
+
['pics/7.png'],
|
| 127 |
+
{
|
| 128 |
+
PHYSICAL_ATTACK: [(minus_attribute, "hp", 45), (add_attribute, 'coin', 30)],
|
| 129 |
+
MAGICAL_ATTACK: [(minus_attribute, "mp", 35), (add_attribute, 'coin', 30)],
|
| 130 |
+
LEAVE: [(minus_attribute, 'hp', 20), (minus_attribute, 'mp', 20), (minus_attribute, 'coin', 20)]
|
| 131 |
+
},
|
| 132 |
+
{
|
| 133 |
+
PHYSICAL_ATTACK: 9,
|
| 134 |
+
MAGICAL_ATTACK: 9,
|
| 135 |
+
LEAVE: 9
|
| 136 |
+
},
|
| 137 |
+
default = 9
|
| 138 |
+
),
|
| 139 |
+
Node(
|
| 140 |
+
"Dragon Chamber",
|
| 141 |
+
"The ancient dragon wakes. Its eyes glow in the darkness. 'Another adventurer seeking my treasure?' What will you do?\n\nAttack and Rescue, or Run away?",
|
| 142 |
+
['pics/8.png'],
|
| 143 |
+
{
|
| 144 |
+
PHYSICAL_ATTACK: [(minus_attribute, "hp", 30), (add_attribute, 'coin', 50)],
|
| 145 |
+
MAGICAL_ATTACK: [(minus_attribute, "mp", 25), (add_attribute, 'coin', 50)],
|
| 146 |
+
LEAVE: [(minus_attribute, 'hp', 15), (minus_attribute, 'mp', 15), (minus_attribute, 'coin', 15)]
|
| 147 |
+
},
|
| 148 |
+
route=check_ending
|
| 149 |
+
),
|
| 150 |
+
Node("Perfect Victory", "You defeat the dragon without losing yourself. The dungeon recognizes you as the true successor.", ['pics/E1.png']),
|
| 151 |
+
Node("Exhausted Victory", "You defeat the dragon, but the battle drains every last bit of your power.", ['pics/E2.png']),
|
| 152 |
+
Node("You Survive", "You barely survive, but you lost everything", ['pics/E3.png']),
|
| 153 |
+
Node("Tyrant Ending", "Player seizes the dragon's power. Becomes the next monster.", ['pics/E4.png']),
|
| 154 |
+
Node("Game Over", "You have lost everything and this game", ['pics/E5.png'])
|
| 155 |
+
]
|
utils.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re, json
|
| 2 |
+
from player import Player
|
| 3 |
+
|
| 4 |
+
PHYSICAL_ATTACK = 'PHYSICAL_ATTACK'
|
| 5 |
+
MAGICAL_ATTACK = 'MAGICAL_ATTACK'
|
| 6 |
+
SPEND_MONEY = "SPEND_MONEY"
|
| 7 |
+
LEAVE = "LEAVE"
|
| 8 |
+
|
| 9 |
+
PHYSICAL_REFILL = "PHYSICAL_REFILL"
|
| 10 |
+
MAGICAL_REFILL = 'MAGICAL_REFILL'
|
| 11 |
+
EARN_MONEY = "EARN_MONEY"
|
| 12 |
+
|
| 13 |
+
PROMPT = """
|
| 14 |
+
You have to identify the category that the primary action described in the response belongs to.
|
| 15 |
+
|
| 16 |
+
Here are the strict categories:
|
| 17 |
+
- PHYSICAL_ATTACK: Any action that causes or threatens physical damage, like stabbing, punching, firing guns, or weapon coercion.
|
| 18 |
+
- MAGICAL_ATTACK: Any action that causes damage with spells or magic, like summoning a monster, striking a target with a flood or thunder.
|
| 19 |
+
- SPEND_MONEY: Using money/coins to solve a problem, like bribing an NPC or buying an item.
|
| 20 |
+
- PHYSICAL_REFILL: Any action that refills HP, offers medical aid, rests, or performs supportive/altruistic deeds (e.g., helping an injured NPC, patching wounds, resting) without spending coins or items.
|
| 21 |
+
- MAGICAL_REFILL: Any action that refills MP, meditates, or channels spiritual energy without spending coins or items.
|
| 22 |
+
- EARN_MONEY: Any action focused on earning money (like looting gold, selling items, or doing a job), provided it does not involve a PHYSICAL_ATTACK or MAGICAL_ATTACK.
|
| 23 |
+
- LEAVE: Running away, retreating, doing absolutely nothing, or ignoring the situation entirely.
|
| 24 |
+
|
| 25 |
+
The input format will be [RESPONSE]: "HUMAN RESPONSE"
|
| 26 |
+
You must output your answer strictly in the following JSON format:
|
| 27 |
+
{"FINAL ANSWER": "YOUR_ANSWER"}
|
| 28 |
+
|
| 29 |
+
Remember: Identify ONLY the single most dominant primary action. If the action is purely a non-violent, helpful, or restorative interaction, classify it as PHYSICAL_REFILL. If the response is completely empty or completely irrelevant to the game, output LEAVE.
|
| 30 |
+
|
| 31 |
+
---
|
| 32 |
+
EXAMPLES:
|
| 33 |
+
|
| 34 |
+
Input: [RESPONSE]: "I will bandage his wounds and help him stand up."
|
| 35 |
+
Output: {"FINAL ANSWER": "PHYSICAL_REFILL"}
|
| 36 |
+
|
| 37 |
+
Input: [RESPONSE]: "I see an injured traveler on the road. I will help him."
|
| 38 |
+
Output: {"FINAL ANSWER": "PHYSICAL_REFILL"}
|
| 39 |
+
|
| 40 |
+
Input: [RESPONSE]: "I draw my sword to threaten the shopkeeper, then summon lightning to strike him."
|
| 41 |
+
Output: {"FINAL ANSWER": "MAGICAL_ATTACK"}
|
| 42 |
+
|
| 43 |
+
Input: [RESPONSE]: "I turn around and walk away back into the forest."
|
| 44 |
+
Output: {"FINAL ANSWER": "LEAVE"}
|
| 45 |
+
"""
|
| 46 |
+
|
| 47 |
+
INPUT_PROMPT = '\n[RESPONSE]: {}'
|
| 48 |
+
|
| 49 |
+
def add_attribute(player: Player, attribute: str, amount: int):
|
| 50 |
+
attribute = attribute.lower()
|
| 51 |
+
if attribute == 'hp':
|
| 52 |
+
player.add_hp(amount)
|
| 53 |
+
elif attribute == 'mp':
|
| 54 |
+
player.add_mp(amount)
|
| 55 |
+
elif attribute == 'coin':
|
| 56 |
+
player.add_coin(amount)
|
| 57 |
+
|
| 58 |
+
def minus_attribute(player: Player, attribute: str, amount: int):
|
| 59 |
+
attribute = attribute.lower()
|
| 60 |
+
if attribute == 'hp':
|
| 61 |
+
player.reduce_hp(amount)
|
| 62 |
+
elif attribute == 'mp':
|
| 63 |
+
player.reduce_mp(amount)
|
| 64 |
+
elif attribute == 'coin':
|
| 65 |
+
player.reduce_coin(amount)
|
| 66 |
+
|
| 67 |
+
def check_ending(player: Player):
|
| 68 |
+
print('Ending', player.hp, player.mp)
|
| 69 |
+
if player.hp >= 50 and player.mp >= 20:
|
| 70 |
+
return 10
|
| 71 |
+
elif player.hp > 0 and player.mp > 0:
|
| 72 |
+
if player.coin <= 10:
|
| 73 |
+
return 12
|
| 74 |
+
else:
|
| 75 |
+
return 11
|
| 76 |
+
elif player.mp < 0:
|
| 77 |
+
return 13
|
| 78 |
+
else:
|
| 79 |
+
return 13
|
| 80 |
+
|
| 81 |
+
def extract_after_final_answer(text: str) -> str:
|
| 82 |
+
# re.findall finds every instance of { ... }
|
| 83 |
+
# [^}]* ensures we don't accidentally skip over nested structures
|
| 84 |
+
matches = re.findall(r'(\{.*?\})', text, re.DOTALL)
|
| 85 |
+
if not matches:
|
| 86 |
+
return None
|
| 87 |
+
|
| 88 |
+
# We take the last match [-1]
|
| 89 |
+
last_json_str = matches[-1]
|
| 90 |
+
|
| 91 |
+
try:
|
| 92 |
+
# Replace single quotes with double quotes for valid JSON
|
| 93 |
+
valid_json_str = last_json_str.replace("'", '"')
|
| 94 |
+
data = json.loads(valid_json_str)
|
| 95 |
+
return data.get("FINAL ANSWER", "LEAVE")
|
| 96 |
+
except json.JSONDecodeError:
|
| 97 |
+
print(f'Could not fetch the keywords: "FINAL ANSWER" with the given response: {text}')
|
| 98 |
+
return "LEAVE"
|