ryomo commited on
Commit
389bf98
·
1 Parent(s): bf92151

feat: implement game state management

Browse files
Files changed (2) hide show
  1. app.py +56 -17
  2. src/unpredictable_lord/game_state.py +150 -0
app.py CHANGED
@@ -11,15 +11,17 @@ import gradio as gr
11
  import spaces
12
 
13
  from unpredictable_lord.chat import chat_with_llm_stream
 
 
 
 
 
14
 
15
  logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
16
  logger = logging.getLogger(__name__)
17
 
18
  logger.info(f"ZeroGPU: {spaces.config.Config.zero_gpu}")
19
 
20
- # Global session storage
21
- game_sessions: dict[str, dict] = {}
22
-
23
 
24
  def init_game(personality: str = "cautious") -> dict:
25
  """
@@ -38,19 +40,12 @@ def init_game(personality: str = "cautious") -> dict:
38
  """
39
  session_id = str(uuid.uuid4())
40
 
41
- # Initial game state
42
- game_sessions[session_id] = {
43
- "territory": 50,
44
- "population": 50,
45
- "treasury": 50,
46
- "unrest": 50,
47
- "royal_trust": 50,
48
- "advisor_trust": 50,
49
- "turn": 1,
50
- "lord_personality": personality,
51
- "game_over": False,
52
- "result": None,
53
- }
54
 
55
  logger.info(
56
  f"New game session created: {session_id} with personality: {personality}"
@@ -59,7 +54,36 @@ def init_game(personality: str = "cautious") -> dict:
59
  return {
60
  "session_id": session_id,
61
  "message": f"New game started! You are now the advisor to a {personality} lord.",
62
- "game_state": game_sessions[session_id],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  }
64
 
65
 
@@ -147,6 +171,7 @@ Add the following to your MCP settings configuration:
147
  | Tool | Description |
148
  |------|-------------|
149
  | `init_game` | Initialize a new game session. Returns a session_id for subsequent operations. |
 
150
 
151
  ### Usage Flow
152
 
@@ -169,6 +194,20 @@ Add the following to your MCP settings configuration:
169
 
170
  init_btn.click(fn=init_game, inputs=personality_input, outputs=init_output)
171
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
  if __name__ == "__main__":
174
  demo.launch(mcp_server=True)
 
11
  import spaces
12
 
13
  from unpredictable_lord.chat import chat_with_llm_stream
14
+ from unpredictable_lord.game_state import (
15
+ PERSONALITY_DESCRIPTIONS,
16
+ create_session,
17
+ get_session,
18
+ )
19
 
20
  logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
21
  logger = logging.getLogger(__name__)
22
 
23
  logger.info(f"ZeroGPU: {spaces.config.Config.zero_gpu}")
24
 
 
 
 
25
 
26
  def init_game(personality: str = "cautious") -> dict:
27
  """
 
40
  """
41
  session_id = str(uuid.uuid4())
42
 
43
+ # Validate personality
44
+ if personality not in PERSONALITY_DESCRIPTIONS:
45
+ personality = "cautious"
46
+
47
+ # Create game session using game_state module
48
+ state = create_session(session_id, personality)
 
 
 
 
 
 
 
49
 
50
  logger.info(
51
  f"New game session created: {session_id} with personality: {personality}"
 
54
  return {
55
  "session_id": session_id,
56
  "message": f"New game started! You are now the advisor to a {personality} lord.",
57
+ "personality_description": PERSONALITY_DESCRIPTIONS[personality],
58
+ "game_state": state.to_dict(),
59
+ }
60
+
61
+
62
+ def get_game_state(session_id: str) -> dict:
63
+ """
64
+ Get the current game state for a session.
65
+
66
+ This MCP tool retrieves the current state of a game session,
67
+ including all parameters, turn number, and game status.
68
+
69
+ Args:
70
+ session_id: The session ID returned from init_game.
71
+
72
+ Returns:
73
+ dict: Current game state or error message if session not found.
74
+ """
75
+ state = get_session(session_id)
76
+
77
+ if state is None:
78
+ return {
79
+ "error": "Session not found",
80
+ "message": f"No game session found with ID: {session_id}. Please call init_game first.",
81
+ }
82
+
83
+ return {
84
+ "session_id": session_id,
85
+ "game_state": state.to_dict(),
86
+ "status_summary": state.get_status_summary(),
87
  }
88
 
89
 
 
171
  | Tool | Description |
172
  |------|-------------|
173
  | `init_game` | Initialize a new game session. Returns a session_id for subsequent operations. |
174
+ | `get_game_state` | Get the current game state for a session. |
175
 
176
  ### Usage Flow
177
 
 
194
 
195
  init_btn.click(fn=init_game, inputs=personality_input, outputs=init_output)
196
 
197
+ gr.Markdown("### Test: Get Game State")
198
+ with gr.Row():
199
+ session_id_input = gr.Textbox(
200
+ label="Session ID",
201
+ placeholder="Enter session_id from init_game",
202
+ )
203
+ get_state_btn = gr.Button("Get State")
204
+
205
+ state_output = gr.JSON(label="Current Game State")
206
+
207
+ get_state_btn.click(
208
+ fn=get_game_state, inputs=session_id_input, outputs=state_output
209
+ )
210
+
211
 
212
  if __name__ == "__main__":
213
  demo.launch(mcp_server=True)
src/unpredictable_lord/game_state.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Game state management for Unpredictable Lord.
3
+
4
+ This module defines the game state data structure and provides
5
+ functions for managing game sessions.
6
+ """
7
+
8
+ from dataclasses import asdict, dataclass, field
9
+ from typing import Literal
10
+
11
+ LordPersonality = Literal["cautious", "idealist", "populist"]
12
+
13
+ PERSONALITY_DESCRIPTIONS = {
14
+ "cautious": "A risk-averse lord who prioritizes stability and financial security above all else.",
15
+ "idealist": "An emotional idealist who pursues noble goals but reacts harshly to opposition.",
16
+ "populist": "A popularity-focused lord who prioritizes public approval over long-term planning.",
17
+ }
18
+
19
+
20
+ @dataclass
21
+ class GameState:
22
+ """
23
+ Represents the current state of a game session.
24
+
25
+ All numeric parameters range from 0 to 100.
26
+ Initial values are set to 50 (balanced state).
27
+ """
28
+
29
+ # Core parameters (0-100, higher is better)
30
+ territory: int = 50 # Territory size - affects income and population cap
31
+ population: int = 50 # Population - base for tax revenue
32
+ treasury: int = 50 # Finances - required for policy execution
33
+ satisfaction: int = 50 # Public satisfaction - low values risk rebellion
34
+ royal_trust: int = 50 # Trust from the kingdom - affects diplomacy
35
+ advisor_trust: int = 50 # Lord's trust in advisor - affects advice acceptance
36
+
37
+ # Game progress
38
+ turn: int = 1
39
+ lord_personality: LordPersonality = "cautious"
40
+
41
+ # Game status
42
+ game_over: bool = False
43
+ result: str | None = None # "victory_territory", "victory_trust", etc.
44
+
45
+ # Event tracking
46
+ current_event: str | None = None
47
+ event_history: list[str] = field(default_factory=list)
48
+
49
+ def to_dict(self) -> dict:
50
+ """Convert game state to dictionary for JSON serialization."""
51
+ return asdict(self)
52
+
53
+ def get_status_summary(self) -> str:
54
+ """Generate a human-readable status summary."""
55
+ status_lines = [
56
+ f"Turn: {self.turn}",
57
+ f"Lord Personality: {self.lord_personality}",
58
+ "",
59
+ "=== Parameters ===",
60
+ f"Territory: {self.territory}/100",
61
+ f"Population: {self.population}/100",
62
+ f"Treasury: {self.treasury}/100",
63
+ f"Public Satisfaction: {self.satisfaction}/100",
64
+ f"Royal Trust: {self.royal_trust}/100",
65
+ f"Advisor Trust: {self.advisor_trust}/100",
66
+ ]
67
+
68
+ if self.current_event:
69
+ status_lines.extend(["", f"Current Event: {self.current_event}"])
70
+
71
+ if self.game_over:
72
+ status_lines.extend(["", f"GAME OVER: {self.result}"])
73
+
74
+ return "\n".join(status_lines)
75
+
76
+ def check_game_over(self) -> tuple[bool, str | None]:
77
+ """
78
+ Check if the game has ended.
79
+
80
+ Returns:
81
+ tuple: (is_game_over, result_type)
82
+ """
83
+ # Defeat conditions
84
+ if self.satisfaction <= 0:
85
+ return True, "defeat_rebellion"
86
+ if self.royal_trust <= 0:
87
+ return True, "defeat_exile"
88
+ if self.treasury <= 0 and self.satisfaction <= 20:
89
+ return True, "defeat_bankruptcy"
90
+
91
+ # Victory conditions
92
+ if self.territory >= 100:
93
+ return True, "victory_territory"
94
+ if self.advisor_trust >= 100 and self.turn >= 10:
95
+ return True, "victory_trust"
96
+ if self.treasury >= 100 and self.royal_trust >= 80:
97
+ return True, "victory_wealth"
98
+
99
+ return False, None
100
+
101
+
102
+ # Global session storage
103
+ game_sessions: dict[str, GameState] = {}
104
+
105
+
106
+ def create_session(
107
+ session_id: str, personality: LordPersonality = "cautious"
108
+ ) -> GameState:
109
+ """
110
+ Create a new game session with the given ID.
111
+
112
+ Args:
113
+ session_id: Unique session identifier.
114
+ personality: The lord's personality type.
115
+
116
+ Returns:
117
+ The newly created GameState.
118
+ """
119
+ state = GameState(lord_personality=personality)
120
+ game_sessions[session_id] = state
121
+ return state
122
+
123
+
124
+ def get_session(session_id: str) -> GameState | None:
125
+ """
126
+ Retrieve a game session by ID.
127
+
128
+ Args:
129
+ session_id: The session ID to look up.
130
+
131
+ Returns:
132
+ The GameState if found, None otherwise.
133
+ """
134
+ return game_sessions.get(session_id)
135
+
136
+
137
+ def delete_session(session_id: str) -> bool:
138
+ """
139
+ Delete a game session.
140
+
141
+ Args:
142
+ session_id: The session ID to delete.
143
+
144
+ Returns:
145
+ True if deleted, False if not found.
146
+ """
147
+ if session_id in game_sessions:
148
+ del game_sessions[session_id]
149
+ return True
150
+ return False