File size: 8,014 Bytes
f083c5c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57cd64c
 
 
f083c5c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f4e6fa5
f083c5c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
import os
import sys
import threading
import time
import numpy as np
import gradio as gr
import pygame

# 1. Headless Config (Must be before pygame.init)
os.environ["SDL_VIDEODRIVER"] = "dummy"
os.environ["SDL_AUDIODRIVER"] = "dummy"

# 2. Path Setup
current_dir = os.path.dirname(os.path.abspath(__file__))
pydino_path = os.path.join(current_dir, "pydino")
sys.path.append(pydino_path)

# 3. Imports from existing codebase
import pickle
from dimensions import Dimensions
from watch_model import ModelWatcher
# Import necessary classes effectively re-using watch_model logic
# We need to load the brain manually

# Global State
GAME_INSTANCE = None
CURRENT_FRAME = None
CURRENT_FRAME = None
CURRENT_BRAIN_DATA = {"inputs": [], "hidden": [], "outputs": []}
BRAIN_WEIGHTS_JSON = "{}" 
LOCK = threading.Lock()
TARGET_TPS = 60

# --- JS Visualizer Code (Global Injection) ---
# This JS will be injected once at page load via demo.load(js=...)
# It defines window.drawBrain which is called by the data stream.

def load_web_ui_asset(filename):
    asset_path = os.path.join(current_dir, "neurodino", "web-ui", filename)
    if not os.path.exists(asset_path):
        print(f"Warning: Asset not found: {asset_path}")
        return ""
    with open(asset_path, "r", encoding="utf-8") as f:
        return f.read()

VISUALIZER_JS_TEMPLATE = load_web_ui_asset("visualizer.js")

def load_brain(brain_path="best_brain.pkl"):
    """Load the best brain."""
    if not os.path.exists(brain_path):
        return None
    try:
        with open(brain_path, "rb") as f:
            data = pickle.load(f)
            if isinstance(data, tuple):
                return data[0] # brain, score
            return data # just brain
    except Exception as e:
        print(f"Error loading brain: {e}")
        return None

def game_thread_func():
    """Background thread calling the game update loop."""
    global GAME_INSTANCE, CURRENT_FRAME, CURRENT_BRAIN_DATA, TARGET_TPS
    
    clock = pygame.time.Clock()
    print("Game thread started.")
    
    while True:
        try:
            if GAME_INSTANCE:
                pygame.event.pump()
                GAME_INSTANCE.update()
                
                if GAME_INSTANCE.crashed:
                    GAME_INSTANCE.restart_game()
                
                view = pygame.surfarray.array3d(GAME_INSTANCE.screen)
                view = view.transpose([1, 0, 2])
                
                if hasattr(GAME_INSTANCE, 'brain'):
                    b = GAME_INSTANCE.brain
                    # Force flatten using numpy to handle both lists and arrays robustly
                    inputs = np.array(b.last_inputs).flatten().tolist()
                    hidden = np.array(b.last_hidden).flatten().tolist()
                    outputs = np.array(b.last_outputs).flatten().tolist()
                    
                    brain_data = {
                        "inputs": inputs,
                        "hidden": hidden,
                        "outputs": outputs
                    }
                else:
                    brain_data = {"inputs": [], "hidden": [], "outputs": []}

                with LOCK:
                    CURRENT_FRAME = view
                    CURRENT_BRAIN_DATA = brain_data
            
            if TARGET_TPS > 0:
                clock.tick(TARGET_TPS) 
            else:
                clock.tick()
            
        except Exception as e:
            print(f"Error in game loop: {e}")
            time.sleep(1)

def set_speed(choice):
    global TARGET_TPS
    if choice == "Yavaş (30 FPS)":
        TARGET_TPS = 30
    elif choice == "Normal (60 FPS)":
        TARGET_TPS = 60
    elif choice == "Hızlı (120 FPS)":
        TARGET_TPS = 120
    elif choice == "Maksimum (Unlimited)":
        TARGET_TPS = 0
    return f"Hız ayarlandı: {choice}"

def start_game_server():
    global GAME_INSTANCE, BRAIN_WEIGHTS_JSON

    import os
    os.environ["SDL_VIDEODRIVER"] = "dummy"
    pygame.init()
    brain = load_brain()
    if not brain: return False

    # Extract static weights
    import json
    weights = {
        "ih": brain.weights_ih.tolist() if hasattr(brain.weights_ih, 'tolist') else brain.weights_ih,
        "ho": brain.weights_ho.tolist() if hasattr(brain.weights_ho, 'tolist') else brain.weights_ho,
        "bh": np.array(brain.bias_h).flatten().tolist(),
        "bo": np.array(brain.bias_o).flatten().tolist()
    }
    BRAIN_WEIGHTS_JSON = json.dumps(weights)

    pygame.display.set_mode((600, 150))
    dims = Dimensions(width=600, height=150)
    game_surface = pygame.Surface((dims.width, dims.height))
    
    GAME_INSTANCE = ModelWatcher(game_surface, dims, brain, silent=False)
    GAME_INSTANCE.start()
    
    t = threading.Thread(target=game_thread_func, daemon=True)
    t.start()
    return True

def data_producer():
    """Yields (image, json_data) tuple."""
    while True:
        with LOCK:
                yield (CURRENT_FRAME, CURRENT_BRAIN_DATA)
        # Reduce sleep to almost zero (1ms) to maximize frame rate
        # Gradio will yield as fast as network permits
        
        # Adaptive Streaming: If Unlimited (0), throttle stream to 20 FPS to save CPU
        if TARGET_TPS == 0:
            time.sleep(0.05) # 20 FPS cap for visuals
        else:
            time.sleep(0.1) # Sync with Game Speed (e.g. 60 FPS) for smoothness

# --- Gradio UI ---
description = """
# 🦖 NeuroDino Canlı Yayın
Bu demo, **Genetik Algoritma** ile eğitilmiş bir Yapay Zeka'nın (Neural Network) canlı oynayışını gösterir.
"""

# HTML for the Canvas (No Script here)
CANVAS_HTML = load_web_ui_asset("canvas.html")

# CSS
CSS = load_web_ui_asset("style.css")

# HTML for Custom Controls
CUSTOM_CONTROLS_HTML = load_web_ui_asset("custom_controls.html")

# JS to force Light Mode
FORCE_LIGHT_JS = """
function refresh() {
    const url = new URL(window.location);
    if (url.searchParams.get('__theme') !== 'light') {
        url.searchParams.set('__theme', 'light');
        window.location.href = url.href;
    }
}
"""

with gr.Blocks(css=CSS, theme=gr.themes.Default(), js="document.body.classList.remove('dark');") as demo:
    
    with gr.Row(elem_id="main_row"):
        with gr.Column(scale=1.5, elem_id="left_game_col"):
            # Added elem_id="game_display" and removed internal height cap
            image_out = gr.Image(label="Oyun Görünümü", streaming=True, elem_id="game_display", show_label=False)
            
            # Custom Controls (Pixel Art Style)
            gr.HTML(CUSTOM_CONTROLS_HTML)
            
            with gr.Row():
                 speed_radio = gr.Radio(
                    choices=["Yavaş (30 FPS)", "Normal (60 FPS)", "Hızlı (120 FPS)", "Maksimum (Unlimited)"],
                    value="Normal (60 FPS)",
                    label="Oyun Hızı",
                    interactive=True
                )
        
        with gr.Column(scale=2):
            # Just the canvas container
            gr.HTML(CANVAS_HTML)
            
            # Hidden JSON sink
            brain_data_sink = gr.JSON(visible=False)

    # Initialize Server & Get JS
    if start_game_server():
        # Inject Javascript Global Code
        js_code = VISUALIZER_JS_TEMPLATE.replace("__WEIGHTS_PLACEHOLDER__", BRAIN_WEIGHTS_JSON)
        demo.load(None, None, None, js=js_code)
    

    
    
    # Stream Loop
    demo.load(data_producer, inputs=None, outputs=[image_out, brain_data_sink])
    
    # Trigger JS draw on data update
    brain_data_sink.change(
        fn=None,
        inputs=[brain_data_sink],
        js="(data) => { if(window.drawBrain) window.drawBrain(data); }"
    )   
    
    # Speed control connection
    status_msg = gr.Markdown(visible=False)
    speed_radio.change(fn=set_speed, inputs=speed_radio, outputs=status_msg)

if __name__ == "__main__":
    # Server already started in block definition to get weights, just launch
    demo.queue().launch(server_name="0.0.0.0", server_port=7860, share=True)