rui3000 commited on
Commit
a932f1e
·
verified ·
1 Parent(s): cad0de9

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +306 -0
app.py ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # RPS_SIMPLE_DEMO/app.py
2
+ import gradio as gr
3
+ import uuid
4
+ import httpx
5
+ import os
6
+ #from RockPaperScissor.repositories import CombinedStorage
7
+
8
+ # FastAPI imports
9
+ from fastapi import FastAPI, Request, Body
10
+ from fastapi.responses import RedirectResponse
11
+ from RockPaperScissor.routes import game_router
12
+ from RockPaperScissor.services.service_instance import game_service
13
+ from RockPaperScissor.services.LLM_service import LLMService
14
+
15
+ # --- Game Constants and Types ---
16
+ from enum import Enum
17
+
18
+ class Move(Enum):
19
+ ROCK = "rock"
20
+ PAPER = "paper"
21
+ SCISSORS = "scissors"
22
+
23
+ class GameResult(Enum):
24
+ PLAYER_WIN = "player_win"
25
+ AI_WIN = "ai_win"
26
+ DRAW = "draw"
27
+
28
+ # --- Gradio Interface ---
29
+ class RockPaperScissorsUI:
30
+ def __init__(self, game_service):
31
+ self.game_service = game_service
32
+ self.session_id = None # Will be set from State
33
+ self.ai_models = list(self.game_service.ai_models.keys())
34
+ self.ai_descriptions = {
35
+ "random": "Random AI: Makes completely random moves.",
36
+ "adaptive_markov": "Adaptive Markov AI: Uses entropy-weighted Markov and frequency models to predict your next move."
37
+ }
38
+ self.last_move = None
39
+
40
+ async def reset_session(self, session_id: str):
41
+ # Use environment variable for base URL, fallback to localhost
42
+ base_url = os.getenv("HF_SPACE_URL", "http://localhost:7860")
43
+ async with httpx.AsyncClient() as client:
44
+ response = await client.post(
45
+ f"{base_url}/api/game/end",
46
+ json={"session_id": session_id}
47
+ )
48
+ print("[DEBUG] /api/game/end response:", response.text)
49
+ # Optionally, also clear the session in-memory (if needed)
50
+ await self.game_service.clear_session(session_id)
51
+ return {"status": "ok"}
52
+
53
+ def create_interface(self):
54
+ with gr.Blocks(theme=gr.themes.Soft(), title="Rock Paper Scissors 🎮") as demo:
55
+ gr.Markdown("# 🪨📄✂️ Rock Paper Scissors")
56
+
57
+ with gr.Row():
58
+ with gr.Column(scale=1):
59
+ gr.Markdown("### 🎮 Game Setup")
60
+ ai_dropdown = gr.Dropdown(
61
+ choices=self.ai_models,
62
+ value=self.ai_models[0],
63
+ label="Select AI Opponent"
64
+ )
65
+ ai_description = gr.Markdown(self.ai_descriptions[self.ai_models[0]])
66
+
67
+ with gr.Row():
68
+ rock_btn = gr.Button("🪨 Rock", variant="secondary", elem_classes=["move-btn"])
69
+ paper_btn = gr.Button("📄 Paper", variant="secondary", elem_classes=["move-btn"])
70
+ scissors_btn = gr.Button("✂️ Scissors", variant="secondary", elem_classes=["move-btn"])
71
+ # Add End Game button
72
+ end_btn = gr.Button("End Game", variant="stop", elem_id="end-game-btn")
73
+ # Add Help button
74
+ help_btn = gr.Button("💡 Get Help", variant="primary", elem_id="help-btn")
75
+ # Add a dedicated box for the help answer, initially empty
76
+ help_answer_box = gr.Markdown("", visible=True)
77
+
78
+ with gr.Column(scale=2):
79
+ gr.Markdown("### 📊 Game Statistics")
80
+ stats_display = gr.Markdown()
81
+ result_display = gr.Markdown("Make your move!")
82
+ end_result_display = gr.Markdown(visible=False)
83
+ status_display = gr.Markdown(visible=True)
84
+
85
+ ai_dropdown.change(
86
+ fn=self.update_ai_description,
87
+ inputs=[ai_dropdown],
88
+ outputs=[ai_description]
89
+ )
90
+
91
+ # Use a Gradio State to store the session ID
92
+ move_state = gr.State("")
93
+ session_id_state = gr.State("")
94
+
95
+ async def play_rock(ai_type, session_id):
96
+ if not session_id:
97
+ session_id = f"session_{uuid.uuid4()}"
98
+ self.session_id = session_id
99
+ stats, result = await self.play_round(ai_type, "rock")
100
+ return stats, result, session_id
101
+ async def play_paper(ai_type, session_id):
102
+ if not session_id:
103
+ session_id = f"session_{uuid.uuid4()}"
104
+ self.session_id = session_id
105
+ stats, result = await self.play_round(ai_type, "paper")
106
+ return stats, result, session_id
107
+ async def play_scissors(ai_type, session_id):
108
+ if not session_id:
109
+ session_id = f"session_{uuid.uuid4()}"
110
+ self.session_id = session_id
111
+ stats, result = await self.play_round(ai_type, "scissors")
112
+ return stats, result, session_id
113
+
114
+ # Add a hidden HTML block with JS to auto-save on tab close
115
+ gr.HTML("""
116
+ <script>
117
+ // Store session ID in localStorage whenever it changes
118
+ window.setRpsSessionId = function(session_id) {
119
+ if (session_id) {
120
+ localStorage.setItem('rps_session_id', session_id);
121
+ }
122
+ };
123
+ // On tab close, send session end to backend
124
+ window.onbeforeunload = function() {
125
+ let session_id = localStorage.getItem('rps_session_id');
126
+ if (session_id) {
127
+ navigator.sendBeacon("/game/end", JSON.stringify({session_id: session_id}));
128
+ }
129
+ };
130
+ </script>
131
+ """)
132
+
133
+ # Inject JS to click End Game button on tab close
134
+ gr.HTML("""
135
+ <script>
136
+ window.onbeforeunload = function() {
137
+ var btn = document.getElementById('end-game-btn');
138
+ if (btn) {
139
+ btn.click();
140
+ }
141
+ };
142
+ </script>
143
+ """)
144
+
145
+ # After each move, update localStorage with the session ID
146
+ def update_session_id_js(session_id):
147
+ return f"window.setRpsSessionId('{session_id}');"
148
+
149
+ rock_btn.click(
150
+ fn=play_rock,
151
+ inputs=[ai_dropdown, session_id_state],
152
+ outputs=[stats_display, result_display, session_id_state],
153
+ js=update_session_id_js
154
+ )
155
+ paper_btn.click(
156
+ fn=play_paper,
157
+ inputs=[ai_dropdown, session_id_state],
158
+ outputs=[stats_display, result_display, session_id_state],
159
+ js=update_session_id_js
160
+ )
161
+ scissors_btn.click(
162
+ fn=play_scissors,
163
+ inputs=[ai_dropdown, session_id_state],
164
+ outputs=[stats_display, result_display, session_id_state],
165
+ js=update_session_id_js
166
+ )
167
+
168
+ # End Game button logic
169
+ async def end_game(session_id):
170
+ if not session_id:
171
+ return "No session to end. Play a round first!"
172
+ result = await self.reset_session(session_id)
173
+ return f"End Game: {result['status']} - {result.get('message', '')}"
174
+
175
+ end_btn.click(
176
+ fn=end_game,
177
+ inputs=[session_id_state],
178
+ outputs=[end_result_display],
179
+ )
180
+ end_result_display.visible = True
181
+
182
+ # Help button logic
183
+ async def get_help(session_id):
184
+ # Show loading message while waiting
185
+ loading_message = "Generating answer, please wait..."
186
+ try:
187
+ base_url = os.getenv("HF_SPACE_URL", "http://localhost:7860")
188
+ async with httpx.AsyncClient() as client:
189
+ response = await client.post(f"{base_url}/api/help", json={"session_id": session_id})
190
+ if response.status_code == 200:
191
+ data = response.json()
192
+ suggestion = data.get("suggestion", "No suggestion available.")
193
+ else:
194
+ suggestion = "Sorry, I'm having trouble getting help right now."
195
+ except Exception as e:
196
+ suggestion = f"Error getting help: {str(e)}"
197
+ return suggestion
198
+
199
+ # When help button is clicked, first show loading, then update with answer
200
+ def show_loading():
201
+ return "Generating answer, please wait..."
202
+
203
+ help_btn.click(
204
+ fn=show_loading,
205
+ inputs=[],
206
+ outputs=[help_answer_box],
207
+ queue=False
208
+ )
209
+ help_btn.click(
210
+ fn=get_help,
211
+ inputs=[session_id_state],
212
+ outputs=[help_answer_box],
213
+ queue=True
214
+ )
215
+
216
+ return demo
217
+
218
+ def update_ai_description(self, ai_type: str) -> str:
219
+ return self.ai_descriptions[ai_type]
220
+
221
+ async def play_round(self, ai_type: str, move: str):
222
+ if not self.session_id:
223
+ return "Internal Error: Session ID missing in play_round.", "Error"
224
+ result = await self.game_service.play_round(self.session_id, move, ai_type)
225
+ stats = result["stats"]
226
+ stats_text = f"""
227
+ ### Game Statistics
228
+ - Total Rounds: {stats['total_rounds']}
229
+ - Player Wins: {stats['player_wins']} ({stats['player_win_rate']})
230
+ - AI Wins: {stats['ai_wins']} ({stats['ai_win_rate']})
231
+ - Draws: {stats['draws']}
232
+
233
+ ### Player Move Distribution
234
+ - Rock: {stats['player_moves']['rock']}
235
+ - Paper: {stats['player_moves']['paper']}
236
+ - Scissors: {stats['player_moves']['scissors']}
237
+
238
+ ### AI Move Distribution
239
+ - Rock: {stats['ai_moves']['rock']}
240
+ - Paper: {stats['ai_moves']['paper']}
241
+ - Scissors: {stats['ai_moves']['scissors']}
242
+ """
243
+ result_text = f"""
244
+ ### Round Result
245
+ You played: {result['player_move'].upper()}
246
+ AI played: {result['ai_move'].upper()}
247
+ Result: {result['result'].replace('_', ' ').title()}
248
+ """
249
+ return stats_text, result_text
250
+
251
+ async def clear_session(self, session_id: str):
252
+ await self.game_service.clear_session(session_id)
253
+ return {"status": "ok"}
254
+
255
+ # Create FastAPI app
256
+ app = FastAPI()
257
+
258
+ # Initialize LLM Service
259
+ llm_service = LLMService()
260
+
261
+ # Add help endpoint
262
+ @app.post("/api/help")
263
+ async def get_help(request: Request, body: dict = Body(...)):
264
+ try:
265
+ session_id = body.get("session_id")
266
+ stats = None
267
+ if session_id:
268
+ # Try to get stats from the game service
269
+ session_data = game_service.cache.get_session(session_id)
270
+ if session_data:
271
+ # Calculate AI move distribution
272
+ ai_moves = [round['ai_move'] for round in session_data['rounds']]
273
+ total_moves = len(ai_moves)
274
+ if total_moves > 0:
275
+ move_distribution = {
276
+ "rock": int((ai_moves.count("rock") / total_moves) * 100),
277
+ "paper": int((ai_moves.count("paper") / total_moves) * 100),
278
+ "scissors": int((ai_moves.count("scissors") / total_moves) * 100)
279
+ }
280
+ stats = {"move_distribution": move_distribution}
281
+ suggestion = await llm_service.generate_response("help", stats=stats)
282
+ print("Help suggestion:", suggestion) # Debug print
283
+ return {"suggestion": suggestion}
284
+ except Exception as e:
285
+ return {"error": str(e), "suggestion": "Sorry, I'm having trouble generating a suggestion right now."}
286
+
287
+ # Create Gradio interface
288
+ ui = RockPaperScissorsUI(game_service)
289
+ demo = ui.create_interface()
290
+
291
+ # Mount Gradio app
292
+ app.include_router(game_router, prefix="/api/game")
293
+ app = gr.mount_gradio_app(app, demo, path="/")
294
+
295
+ # Initialize the app
296
+ @app.on_event("startup")
297
+ async def startup_event():
298
+ await game_service.initialize()
299
+
300
+ @app.on_event("shutdown")
301
+ async def shutdown_event():
302
+ await llm_service.close()
303
+
304
+ if __name__ == "__main__":
305
+ import uvicorn
306
+ uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=False)