rui3000 commited on
Commit
bb5919c
Β·
verified Β·
1 Parent(s): f3f2cc7

Delete RockPaperScissor

Browse files
This view is limited to 50 files because it contains too many changes. Β  See raw diff
Files changed (50) hide show
  1. RockPaperScissor/.DS_Store +0 -0
  2. RockPaperScissor/__init__.py +0 -2
  3. RockPaperScissor/__pycache__/__init__.cpython-313.pyc +0 -0
  4. RockPaperScissor/config/.gitkeep +0 -1
  5. RockPaperScissor/config/database.py +0 -31
  6. RockPaperScissor/game_cache/__init__.py +0 -8
  7. RockPaperScissor/game_cache/__pycache__/__init__.cpython-313.pyc +0 -0
  8. RockPaperScissor/game_cache/__pycache__/memory_cache.cpython-313.pyc +0 -0
  9. RockPaperScissor/game_cache/memory_cache.py +0 -84
  10. RockPaperScissor/models/__init__.py +0 -17
  11. RockPaperScissor/models/__pycache__/__init__.cpython-313.pyc +0 -0
  12. RockPaperScissor/models/__pycache__/adaptive_markov_ai.cpython-313.pyc +0 -0
  13. RockPaperScissor/models/__pycache__/base_ai.cpython-313.pyc +0 -0
  14. RockPaperScissor/models/__pycache__/random_ai.cpython-313.pyc +0 -0
  15. RockPaperScissor/models/adaptive_markov_ai.py +0 -151
  16. RockPaperScissor/models/base_ai.py +0 -17
  17. RockPaperScissor/models/random_ai.py +0 -11
  18. RockPaperScissor/repositories/.gitkeep +0 -1
  19. RockPaperScissor/repositories/__init__.py +0 -12
  20. RockPaperScissor/repositories/__pycache__/__init__.cpython-313.pyc +0 -0
  21. RockPaperScissor/repositories/__pycache__/combined_storage.cpython-313.pyc +0 -0
  22. RockPaperScissor/repositories/__pycache__/s3_storage.cpython-313.pyc +0 -0
  23. RockPaperScissor/repositories/__pycache__/sql_storage.cpython-313.pyc +0 -0
  24. RockPaperScissor/repositories/__pycache__/storage.cpython-313.pyc +0 -0
  25. RockPaperScissor/repositories/combined_storage.py +0 -71
  26. RockPaperScissor/repositories/s3_storage.py +0 -121
  27. RockPaperScissor/repositories/sql_storage.py +0 -307
  28. RockPaperScissor/repositories/storage.py +0 -23
  29. RockPaperScissor/routes/__init__.py +0 -9
  30. RockPaperScissor/routes/__pycache__/__init__.cpython-310.pyc +0 -0
  31. RockPaperScissor/routes/__pycache__/__init__.cpython-313.pyc +0 -0
  32. RockPaperScissor/routes/__pycache__/game.cpython-310.pyc +0 -0
  33. RockPaperScissor/routes/__pycache__/game.cpython-313.pyc +0 -0
  34. RockPaperScissor/routes/__pycache__/game_o.cpython-310.pyc +0 -0
  35. RockPaperScissor/routes/__pycache__/history.cpython-313.pyc +0 -0
  36. RockPaperScissor/routes/__pycache__/stats.cpython-310.pyc +0 -0
  37. RockPaperScissor/routes/__pycache__/stats.cpython-313.pyc +0 -0
  38. RockPaperScissor/routes/game.py +0 -77
  39. RockPaperScissor/schemas/__pycache__/game.cpython-313.pyc +0 -0
  40. RockPaperScissor/schemas/game.py +0 -23
  41. RockPaperScissor/services/LLM_service.py +0 -105
  42. RockPaperScissor/services/__init__.py +0 -8
  43. RockPaperScissor/services/__pycache__/LLM_service.cpython-313.pyc +0 -0
  44. RockPaperScissor/services/__pycache__/__init__.cpython-313.pyc +0 -0
  45. RockPaperScissor/services/__pycache__/game_service.cpython-313.pyc +0 -0
  46. RockPaperScissor/services/__pycache__/game_service_class.cpython-313.pyc +0 -0
  47. RockPaperScissor/services/__pycache__/service_instance.cpython-313.pyc +0 -0
  48. RockPaperScissor/services/game_service_class.py +0 -174
  49. RockPaperScissor/services/service_instance.py +0 -6
  50. RockPaperScissor/utils/__init__.py +0 -6
RockPaperScissor/.DS_Store DELETED
Binary file (6.15 kB)
 
RockPaperScissor/__init__.py DELETED
@@ -1,2 +0,0 @@
1
-
2
-
 
 
 
RockPaperScissor/__pycache__/__init__.cpython-313.pyc DELETED
Binary file (172 Bytes)
 
RockPaperScissor/config/.gitkeep DELETED
@@ -1 +0,0 @@
1
- # Placeholder to keep the config directory tracked by git
 
 
RockPaperScissor/config/database.py DELETED
@@ -1,31 +0,0 @@
1
- # Minimal placeholder for database configuration
2
- # RockPaperScissor/config/database.py
3
- import os
4
- from pathlib import Path
5
-
6
- # Base directory for data storage
7
- BASE_DATA_DIR = Path("data")
8
- BASE_DATA_DIR.mkdir(parents=True, exist_ok=True)
9
-
10
- # SQLite configuration
11
- SQLITE_CONFIG = {
12
- "db_path": str(BASE_DATA_DIR / "game_history.db"),
13
- "timeout": 5.0,
14
- "check_same_thread": False,
15
- }
16
-
17
- # Storage configuration
18
- STORAGE_CONFIG = {
19
- "primary": "combined",
20
- "cache_size": 1000,
21
- "auto_cleanup": True,
22
- "cleanup_interval": 3600 # 1 hour in seconds
23
- }
24
-
25
- # S3 configuration
26
- S3_CONFIG = {
27
- "bucket_name": os.getenv("AWS_S3_BUCKET_NAME", "your-bucket-name"),
28
- "region_name": os.getenv("AWS_REGION", "us-east-1"),
29
- "access_key_id": os.getenv("AWS_ACCESS_KEY_ID"),
30
- "secret_access_key": os.getenv("AWS_SECRET_ACCESS_KEY")
31
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
RockPaperScissor/game_cache/__init__.py DELETED
@@ -1,8 +0,0 @@
1
- # RockPaperScissor/game_cache/__init__.py
2
- from .memory_cache import GameSessionCache # This will be our DummyGameSessionCache
3
- # from .llm_cache import LLMCache # Add LLMCache later if/when LLMService is integrated
4
-
5
- __all__ = [
6
- 'GameSessionCache',
7
- # 'LLMCache',
8
- ]
 
 
 
 
 
 
 
 
 
RockPaperScissor/game_cache/__pycache__/__init__.cpython-313.pyc DELETED
Binary file (271 Bytes)
 
RockPaperScissor/game_cache/__pycache__/memory_cache.cpython-313.pyc DELETED
Binary file (4.17 kB)
 
RockPaperScissor/game_cache/memory_cache.py DELETED
@@ -1,84 +0,0 @@
1
- # RockPaperScissor/game_cache/memory_cache.py
2
- from typing import Dict, Any, Optional
3
- from dataclasses import dataclass
4
- from datetime import datetime
5
-
6
- @dataclass
7
- class GameData:
8
- session_id: str
9
- player_move: str
10
- ai_move: str
11
- result: str
12
- ai_type: str
13
- ai_state: Dict[str, Any]
14
- created_at: datetime = datetime.now()
15
-
16
- class GameSessionCache:
17
- def __init__(self):
18
- self._sessions: Dict[str, Dict[str, Any]] = {}
19
- self._max_cache_size = 1000 # Maximum number of sessions to cache
20
-
21
- def update_session(self, session_id: str, game_data: Dict[str, Any]) -> None:
22
- """Update session data in cache"""
23
- if session_id not in self._sessions:
24
- self._sessions[session_id] = {
25
- 'total_rounds': 0,
26
- 'player_wins': 0,
27
- 'ai_wins': 0,
28
- 'draws': 0,
29
- 'rounds': [],
30
- 'last_updated': datetime.now()
31
- }
32
-
33
- session = self._sessions[session_id]
34
-
35
- # Update session stats
36
- session['total_rounds'] += 1
37
- if game_data['result'] == 'player_win':
38
- session['player_wins'] += 1
39
- elif game_data['result'] == 'ai_win':
40
- session['ai_wins'] += 1
41
- else:
42
- session['draws'] += 1
43
-
44
- # Add round data
45
- session['rounds'].append({
46
- 'round_number': session['total_rounds'],
47
- 'player_move': game_data['player_move'],
48
- 'ai_move': game_data['ai_move'],
49
- 'result': game_data['result'],
50
- 'ai_type': game_data.get('ai_type', 'random'),
51
- 'ai_state': game_data.get('ai_state', {}),
52
- 'created_at': datetime.now()
53
- })
54
-
55
- # Update last updated timestamp
56
- session['last_updated'] = datetime.now()
57
-
58
- # Enforce cache size limit
59
- if len(self._sessions) > self._max_cache_size:
60
- self._remove_oldest_session()
61
-
62
- def get_session(self, session_id: str) -> Optional[Dict[str, Any]]:
63
- """Get session data from cache"""
64
- return self._sessions.get(session_id)
65
-
66
- def _remove_oldest_session(self) -> None:
67
- """Remove the oldest session from cache"""
68
- if not self._sessions:
69
- return
70
-
71
- oldest_session = min(
72
- self._sessions.items(),
73
- key=lambda x: x[1]['last_updated']
74
- )
75
- del self._sessions[oldest_session[0]]
76
-
77
- def clear(self) -> None:
78
- """Clear all cached data"""
79
- self._sessions.clear()
80
-
81
- def remove_session(self, session_id: str) -> None:
82
- """Remove a specific session from cache"""
83
- if session_id in self._sessions:
84
- del self._sessions[session_id]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
RockPaperScissor/models/__init__.py DELETED
@@ -1,17 +0,0 @@
1
- # RockPaperScissor/models/__init__.py
2
- from .random_ai import RandomAI
3
- from .adaptive_markov_ai import AdaptiveMarkovAI
4
- # Import other AIs like PatternAI, MarkovAI later
5
-
6
- # This dictionary will be used by GameService to get AI instances
7
- AI_MODELS = {
8
- "random": RandomAI(),
9
- "adaptive_markov": AdaptiveMarkovAI(),
10
- # "pattern": PatternAI(), # Add later
11
- # "markov": MarkovAI(), # Add later
12
- }
13
-
14
- def get_ai(model_name: str):
15
- """Retrieves an AI instance based on the model name."""
16
- # Fallback to RandomAI if the requested model isn't found or if empty
17
- return AI_MODELS.get(model_name.lower(), RandomAI())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
RockPaperScissor/models/__pycache__/__init__.cpython-313.pyc DELETED
Binary file (690 Bytes)
 
RockPaperScissor/models/__pycache__/adaptive_markov_ai.cpython-313.pyc DELETED
Binary file (6.27 kB)
 
RockPaperScissor/models/__pycache__/base_ai.cpython-313.pyc DELETED
Binary file (1.07 kB)
 
RockPaperScissor/models/__pycache__/random_ai.cpython-313.pyc DELETED
Binary file (988 Bytes)
 
RockPaperScissor/models/adaptive_markov_ai.py DELETED
@@ -1,151 +0,0 @@
1
- from typing import Dict, Any, Optional, Tuple
2
- import math
3
- import numpy as np
4
-
5
- from .base_ai import BaseAI
6
-
7
- class AdaptiveMarkovAI(BaseAI):
8
- """
9
- Adaptive RPS AI that uses entropy-based weighting between Markov and Frequency models.
10
- """
11
- def __init__(self, smoothing_factor=1.0, temperature=1.0):
12
- super().__init__()
13
- self.smoothing = smoothing_factor
14
- self.temperature = temperature
15
-
16
- def make_move(self, model_state: Optional[Dict[str, Any]] = None) -> Tuple[str, Dict[str, Any]]:
17
- """
18
- Generate AI's next move based on model state.
19
-
20
- Args:
21
- model_state: Dictionary containing:
22
- - markov_counts: Transition count matrix
23
- - frequency_counts: Overall frequency counts
24
- - player_last_move: The player's last move from previous round
25
- - smoothing: Smoothing factor for probability calculations
26
- - temperature: Temperature parameter for entropy weighting
27
- - last_lambdas: Last calculated model weights
28
-
29
- Returns:
30
- Tuple containing:
31
- - str: AI's chosen move (rock, paper, scissors)
32
- - Dict: Updated model state (ready for next round after player moves)
33
- """
34
- # Initialize model state if None
35
- if model_state is None:
36
- model_state = {
37
- "player_last_move": None,
38
- "ai_last_move": None,
39
- "last_result": None,
40
- "markov_counts": np.ones((3, 3)) * self.smoothing,
41
- "frequency_counts": np.ones(3) * self.smoothing,
42
- "player_second_last_move": None,
43
- "smoothing": self.smoothing,
44
- "temperature": self.temperature,
45
- "last_lambdas": {"markov": 0.5, "freq": 0.5}
46
- }
47
-
48
- # Extract values from model state
49
- markov_counts = model_state.get("markov_counts", np.ones((3, 3)) * self.smoothing)
50
- frequency_counts = model_state.get("frequency_counts", np.ones(3) * self.smoothing)
51
- player_last_move = model_state.get("player_last_move")
52
- player_second_last_move = model_state.get("player_second_last_move")
53
- smoothing = model_state.get("smoothing", self.smoothing)
54
- temperature = model_state.get("temperature", self.temperature)
55
- last_lambdas = model_state.get("last_lambdas", {"markov": 0.5, "freq": 0.5})
56
-
57
- # Define move mappings
58
- move_to_idx = {"rock": 0, "paper": 1, "scissors": 2}
59
- idx_to_move = {0: "rock", 1: "paper", 2: "scissors"}
60
- counters = {"rock": "paper", "paper": "scissors", "scissors": "rock"}
61
-
62
- # Helper functions
63
- def calculate_entropy(probs):
64
- """Calculate Shannon entropy of a probability distribution"""
65
- entropy = 0
66
- for p in probs:
67
- if p > 0:
68
- entropy -= p * math.log2(p)
69
- return entropy
70
-
71
- def get_markov_probabilities(move):
72
- """Get transition probabilities from the Markov model"""
73
- if move not in move_to_idx:
74
- # Default to uniform if unknown move
75
- return [1/3, 1/3, 1/3]
76
-
77
- move_idx = move_to_idx[move]
78
- row_sum = np.sum(markov_counts[move_idx])
79
- return markov_counts[move_idx] / row_sum
80
-
81
- def get_frequency_probabilities():
82
- """Get overall move probabilities from the frequency model"""
83
- total = np.sum(frequency_counts)
84
- return frequency_counts / total
85
-
86
- def calculate_lambdas(markov_probs, freq_probs):
87
- """Calculate adaptive weights using entropy-based formula"""
88
- # Calculate entropies
89
- markov_entropy = calculate_entropy(markov_probs)
90
- freq_entropy = calculate_entropy(freq_probs)
91
-
92
- # Apply temperature and calculate weights
93
- denom = math.exp(-temperature * markov_entropy) + math.exp(-temperature * freq_entropy)
94
- lambda_markov = math.exp(-temperature * markov_entropy) / denom
95
- lambda_freq = math.exp(-temperature * freq_entropy) / denom
96
-
97
- # Return weights and entropy values for monitoring
98
- return lambda_markov, lambda_freq, {
99
- "markov": lambda_markov,
100
- "freq": lambda_freq,
101
- "markov_entropy": markov_entropy,
102
- "freq_entropy": freq_entropy
103
- }
104
-
105
- # Update the models with historical data if available
106
- if player_last_move and player_last_move in move_to_idx:
107
- # Update frequency counts
108
- last_idx = move_to_idx[player_last_move]
109
- frequency_counts[last_idx] += 1
110
-
111
- # Update Markov model if we have two consecutive moves
112
- if player_second_last_move and player_second_last_move in move_to_idx:
113
- second_last_idx = move_to_idx[player_second_last_move]
114
- markov_counts[second_last_idx][last_idx] += 1
115
-
116
- # For prediction, use player_last_move for Markov model
117
- if player_last_move is None:
118
- # No history yet, use random move (not counter)
119
- ai_move = np.random.choice(self.possible_moves)
120
- else:
121
- # Get probabilities from each model
122
- markov_probs = get_markov_probabilities(player_last_move)
123
- freq_probs = get_frequency_probabilities()
124
-
125
- # Calculate adaptive lambda weights
126
- lambda_markov, lambda_freq, new_lambdas = calculate_lambdas(markov_probs, freq_probs)
127
-
128
- # Combine predictions with lambda weights
129
- combined_probs = lambda_markov * np.array(markov_probs) + lambda_freq * np.array(freq_probs)
130
-
131
- # Predict most likely move
132
- predicted_idx = np.argmax(combined_probs)
133
- predicted_move = idx_to_move[predicted_idx]
134
- ai_move = counters[predicted_move]
135
-
136
- # Update lambdas in state
137
- last_lambdas = new_lambdas
138
-
139
- # Prepare updated state
140
- updated_state = {
141
- "markov_counts": markov_counts,
142
- "frequency_counts": frequency_counts,
143
- "player_last_move": None, # Keep until service layer updates it
144
- "player_second_last_move": player_last_move, # Keep until service layer updates it
145
- "smoothing": smoothing,
146
- "temperature": temperature,
147
- "last_lambdas": last_lambdas
148
- }
149
-
150
- # Return AI move and updated state
151
- return ai_move, updated_state
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
RockPaperScissor/models/base_ai.py DELETED
@@ -1,17 +0,0 @@
1
- # RockPaperScissor/models/base_ai.py
2
- from typing import Dict, Any, Tuple
3
-
4
- class BaseAI:
5
- possible_moves = ["rock", "paper", "scissors"]
6
- def make_move(self, model_state: Dict[str, Any] = None) -> Tuple[str, Dict[str, Any]]:
7
- """Make a move in the game.
8
-
9
- Args:
10
- model_state: Optional state data for the AI model
11
-
12
- Returns:
13
- Tuple of (move, updated_state) where:
14
- - move is one of: "rock", "paper", "scissors"
15
- - updated_state is the new state of the AI model
16
- """
17
- raise NotImplementedError
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
RockPaperScissor/models/random_ai.py DELETED
@@ -1,11 +0,0 @@
1
- # RockPaperScissor/models/random_ai.py
2
- import random
3
- from typing import Dict, Any, Tuple, Optional
4
- from .base_ai import BaseAI # Assuming base_ai.py is in the same directory
5
-
6
- class RandomAI(BaseAI):
7
- def make_move(self, model_state: Optional[Dict[str, Any]] = None) -> Tuple[str, Dict[str, Any]]:
8
- """Makes a random move."""
9
- # model_state is ignored by RandomAI
10
- # Returns the chosen move and an empty dictionary for the (unchanged) state
11
- return random.choice(self.possible_moves), {}
 
 
 
 
 
 
 
 
 
 
 
 
RockPaperScissor/repositories/.gitkeep DELETED
@@ -1 +0,0 @@
1
- # Placeholder to keep the repositories directory tracked by git
 
 
RockPaperScissor/repositories/__init__.py DELETED
@@ -1,12 +0,0 @@
1
- # RockPaperScissor/repositories/__init__.py
2
- from .storage import Storage, StorageError
3
- from .sql_storage import SQLStorage # This will be our DummySQLStorage
4
- from .combined_storage import CombinedStorage # This will use DummySQLStorage
5
- from .s3_storage import S3Storage # Add later if needed
6
-
7
- __all__ = [
8
- 'Storage', 'StorageError',
9
- 'SQLStorage',
10
- 'CombinedStorage', # Uncommented for S3 testing
11
- 'S3Storage',
12
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
RockPaperScissor/repositories/__pycache__/__init__.cpython-313.pyc DELETED
Binary file (450 Bytes)
 
RockPaperScissor/repositories/__pycache__/combined_storage.cpython-313.pyc DELETED
Binary file (4.43 kB)
 
RockPaperScissor/repositories/__pycache__/s3_storage.cpython-313.pyc DELETED
Binary file (6.57 kB)
 
RockPaperScissor/repositories/__pycache__/sql_storage.cpython-313.pyc DELETED
Binary file (13.8 kB)
 
RockPaperScissor/repositories/__pycache__/storage.cpython-313.pyc DELETED
Binary file (1.28 kB)
 
RockPaperScissor/repositories/combined_storage.py DELETED
@@ -1,71 +0,0 @@
1
- # RockPaperScissor/repositories/combined_storage.py
2
- from .storage import Storage, StorageError
3
- from .sql_storage import SQLStorage
4
- from .s3_storage import S3Storage
5
- from typing import Dict, Any, Optional
6
- import os
7
-
8
- class CombinedStorage(Storage):
9
- def __init__(self, db_path: str = "data/game_history.db"):
10
- self.sql_storage = SQLStorage(db_path)
11
- self.s3_storage = S3Storage()
12
- self._memory_cache = {} # Simple in-memory cache
13
-
14
- async def initialize(self):
15
- """Initialize storage components"""
16
- await self.sql_storage.initialize()
17
- await self.s3_storage.initialize()
18
-
19
- async def save_game_round(self, game_data: Dict[str, Any]) -> bool:
20
- """Save game round to both storage systems and cache"""
21
- # Save to SQL storage
22
- sql_success = await self.sql_storage.save_game_round(game_data)
23
-
24
- # Save to S3 storage
25
- s3_success = await self.s3_storage.save_game_round(game_data)
26
-
27
- if sql_success or s3_success:
28
- # Update memory cache
29
- session_id = game_data.get('game_id')
30
- if session_id:
31
- self._memory_cache[session_id] = game_data
32
-
33
- return sql_success or s3_success
34
-
35
- async def save_full_session(self, session_id: str, session_data: Dict[str, Any]) -> bool:
36
- """Save the complete game session to both storage systems"""
37
- # Save to SQL storage
38
- sql_success = await self.sql_storage.save_full_session(session_id, session_data)
39
-
40
- # Save to S3 storage
41
- s3_success = await self.s3_storage.save_full_session(session_id, session_data)
42
-
43
- return sql_success or s3_success
44
-
45
- async def get_game_history(self, session_id: str) -> Optional[Dict[str, Any]]:
46
- """Get game history from cache or storage"""
47
- # Try cache first
48
- if session_id in self._memory_cache:
49
- return self._memory_cache[session_id]
50
-
51
- # Try SQL storage
52
- history = await self.sql_storage.get_game_history(session_id)
53
- if history:
54
- self._memory_cache[session_id] = history
55
- return history
56
-
57
- # Try S3 storage
58
- history = await self.s3_storage.get_game_history(session_id)
59
- if history:
60
- self._memory_cache[session_id] = history
61
- return history
62
-
63
- async def get_ai_state(self, session_id: str, ai_type: str) -> Optional[Dict[str, Any]]:
64
- """Get AI state from storage"""
65
- return await self.sql_storage.get_ai_state(session_id, ai_type)
66
-
67
- async def close(self) -> None:
68
- """Close storage connections"""
69
- await self.sql_storage.close()
70
- await self.s3_storage.close()
71
- self._memory_cache.clear()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
RockPaperScissor/repositories/s3_storage.py DELETED
@@ -1,121 +0,0 @@
1
- import boto3
2
- import json
3
- from datetime import datetime
4
- from typing import Dict, Any, Optional
5
- from .storage import Storage
6
- import os
7
-
8
- class S3Storage(Storage):
9
- def __init__(self, bucket_name: str = None):
10
- self.bucket_name = bucket_name or os.getenv('AWS_S3_BUCKET_NAME')
11
- if not self.bucket_name:
12
- raise ValueError("S3 bucket name must be provided either through constructor or AWS_S3_BUCKET_NAME environment variable")
13
-
14
- # Configure S3 client with LocalStack endpoint if available
15
- endpoint_url = os.getenv('AWS_ENDPOINT_URL')
16
- self.s3_client = boto3.client(
17
- 's3',
18
- endpoint_url=endpoint_url,
19
- region_name=os.getenv('AWS_REGION', 'us-east-1')
20
- )
21
-
22
- async def initialize(self):
23
- """Initialize S3 storage - verify bucket exists"""
24
- try:
25
- self.s3_client.head_bucket(Bucket=self.bucket_name)
26
- except Exception as e:
27
- # If using LocalStack, create the bucket if it doesn't exist
28
- if os.getenv('AWS_ENDPOINT_URL'):
29
- try:
30
- self.s3_client.create_bucket(Bucket=self.bucket_name)
31
- except Exception as create_error:
32
- raise Exception(f"Failed to create S3 bucket {self.bucket_name}: {str(create_error)}")
33
- else:
34
- raise Exception(f"Failed to access S3 bucket {self.bucket_name}: {str(e)}")
35
-
36
- async def save_game_round(self, game_data: Dict[str, Any]) -> bool:
37
- """Save a game round to S3"""
38
- try:
39
- session_id = game_data.get('game_id')
40
- if not session_id:
41
- return False
42
-
43
- # Create the S3 key for this session
44
- s3_key = f"game_sessions/{session_id}/rounds/{datetime.now().isoformat()}.json"
45
-
46
- # Convert any non-serializable objects to strings
47
- serializable_data = self._make_serializable(game_data)
48
-
49
- # Upload to S3
50
- self.s3_client.put_object(
51
- Bucket=self.bucket_name,
52
- Key=s3_key,
53
- Body=json.dumps(serializable_data),
54
- ContentType='application/json'
55
- )
56
-
57
- return True
58
-
59
- except Exception as e:
60
- print(f"Error saving game round to S3: {e}")
61
- return False
62
-
63
- async def save_full_session(self, session_id: str, session_data: Dict[str, Any]) -> bool:
64
- """Save the complete game session to S3"""
65
- try:
66
- # Create the S3 key for this session
67
- s3_key = f"game_sessions/{session_id}/session_summary.json"
68
-
69
- # Add metadata
70
- session_data['saved_at'] = datetime.now().isoformat()
71
-
72
- # Convert any non-serializable objects to strings
73
- serializable_data = self._make_serializable(session_data)
74
-
75
- # Upload to S3
76
- self.s3_client.put_object(
77
- Bucket=self.bucket_name,
78
- Key=s3_key,
79
- Body=json.dumps(serializable_data),
80
- ContentType='application/json'
81
- )
82
-
83
- return True
84
-
85
- except Exception as e:
86
- print(f"Error saving full session to S3: {e}")
87
- return False
88
-
89
- async def get_game_history(self, session_id: str) -> Optional[Dict[str, Any]]:
90
- """Retrieve game history for a session from S3"""
91
- try:
92
- # Get the session summary
93
- s3_key = f"game_sessions/{session_id}/session_summary.json"
94
-
95
- try:
96
- response = self.s3_client.get_object(
97
- Bucket=self.bucket_name,
98
- Key=s3_key
99
- )
100
- return json.loads(response['Body'].read().decode('utf-8'))
101
- except self.s3_client.exceptions.NoSuchKey:
102
- return None
103
-
104
- except Exception as e:
105
- print(f"Error retrieving game history from S3: {e}")
106
- return None
107
-
108
- def _make_serializable(self, data: Any) -> Any:
109
- """Convert data to be JSON serializable"""
110
- if isinstance(data, dict):
111
- return {k: self._make_serializable(v) for k, v in data.items()}
112
- elif isinstance(data, list):
113
- return [self._make_serializable(item) for item in data]
114
- elif isinstance(data, (str, int, float, bool, type(None))):
115
- return data
116
- else:
117
- return str(data)
118
-
119
- async def close(self) -> None:
120
- """Close S3 client connection"""
121
- self.s3_client = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
RockPaperScissor/repositories/sql_storage.py DELETED
@@ -1,307 +0,0 @@
1
- # RockPaperScissor/repositories/sql_storage.py
2
- from .storage import Storage
3
- from typing import Dict, Any, Optional
4
- import sqlite3
5
- import json
6
- import asyncio
7
- from pathlib import Path
8
- from datetime import datetime
9
- import numpy as np
10
-
11
- class SQLStorage(Storage):
12
- def __init__(self, db_path: str = "data/game_history.db"):
13
- self.db_path = db_path
14
- self.conn = None
15
- self._ensure_db_directory()
16
-
17
- def _ensure_db_directory(self):
18
- """Ensure the database directory exists"""
19
- Path(self.db_path).parent.mkdir(parents=True, exist_ok=True)
20
-
21
- async def initialize(self):
22
- """Initialize the database connection and create tables if they don't exist"""
23
- self.conn = sqlite3.connect(self.db_path)
24
- cursor = self.conn.cursor()
25
-
26
- # Create game_sessions table with completion status
27
- cursor.execute('''
28
- CREATE TABLE IF NOT EXISTS game_sessions (
29
- session_id TEXT PRIMARY KEY,
30
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
31
- completed_at TIMESTAMP,
32
- total_rounds INTEGER DEFAULT 0,
33
- player_wins INTEGER DEFAULT 0,
34
- ai_wins INTEGER DEFAULT 0,
35
- draws INTEGER DEFAULT 0,
36
- is_completed BOOLEAN DEFAULT FALSE
37
- )
38
- ''')
39
-
40
- # Create game_rounds table
41
- cursor.execute('''
42
- CREATE TABLE IF NOT EXISTS game_rounds (
43
- round_id INTEGER PRIMARY KEY AUTOINCREMENT,
44
- session_id TEXT,
45
- round_number INTEGER,
46
- player_move TEXT,
47
- ai_move TEXT,
48
- result TEXT,
49
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
50
- FOREIGN KEY (session_id) REFERENCES game_sessions(session_id)
51
- )
52
- ''')
53
-
54
- # Create ai_states table
55
- cursor.execute('''
56
- CREATE TABLE IF NOT EXISTS ai_states (
57
- session_id TEXT,
58
- ai_type TEXT,
59
- state_data TEXT,
60
- last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
61
- PRIMARY KEY (session_id, ai_type),
62
- FOREIGN KEY (session_id) REFERENCES game_sessions(session_id)
63
- )
64
- ''')
65
-
66
- self.conn.commit()
67
-
68
- async def save_game_round(self, game_data: Dict[str, Any]) -> bool:
69
- """Save a game round to the database"""
70
- try:
71
- cursor = self.conn.cursor()
72
- session_id = game_data.get('game_id')
73
-
74
- # Update or insert game session
75
- cursor.execute('''
76
- INSERT INTO game_sessions (session_id, total_rounds, player_wins, ai_wins, draws)
77
- VALUES (?, 1, ?, ?, ?)
78
- ON CONFLICT(session_id) DO UPDATE SET
79
- total_rounds = total_rounds + 1,
80
- player_wins = player_wins + ?,
81
- ai_wins = ai_wins + ?,
82
- draws = draws + ?
83
- ''', (
84
- session_id,
85
- 1 if game_data['result'] == 'player_win' else 0,
86
- 1 if game_data['result'] == 'ai_win' else 0,
87
- 1 if game_data['result'] == 'draw' else 0,
88
- 1 if game_data['result'] == 'player_win' else 0,
89
- 1 if game_data['result'] == 'ai_win' else 0,
90
- 1 if game_data['result'] == 'draw' else 0
91
- ))
92
-
93
- # Get current round number
94
- cursor.execute('''
95
- SELECT COUNT(*) FROM game_rounds WHERE session_id = ?
96
- ''', (session_id,))
97
- round_number = cursor.fetchone()[0] + 1
98
-
99
- # Insert game round
100
- cursor.execute('''
101
- INSERT INTO game_rounds (session_id, round_number, player_move, ai_move, result)
102
- VALUES (?, ?, ?, ?, ?)
103
- ''', (
104
- session_id,
105
- round_number,
106
- game_data['player_move'],
107
- game_data['ai_move'],
108
- game_data['result']
109
- ))
110
-
111
- # Save AI state if provided
112
- if 'ai_state' in game_data:
113
- def convert_ndarray(obj):
114
- if isinstance(obj, np.ndarray):
115
- return obj.tolist()
116
- if isinstance(obj, dict):
117
- return {k: convert_ndarray(v) for k, v in obj.items()}
118
- if isinstance(obj, list):
119
- return [convert_ndarray(x) for x in obj]
120
- return obj
121
- ai_state_serializable = convert_ndarray(game_data['ai_state'])
122
- cursor.execute('''
123
- INSERT INTO ai_states (session_id, ai_type, state_data)
124
- VALUES (?, ?, ?)
125
- ON CONFLICT(session_id, ai_type) DO UPDATE SET
126
- state_data = ?,
127
- last_updated = CURRENT_TIMESTAMP
128
- ''', (
129
- session_id,
130
- game_data.get('ai_type', 'adaptive_markov'),
131
- json.dumps(ai_state_serializable),
132
- json.dumps(ai_state_serializable)
133
- ))
134
-
135
- self.conn.commit()
136
- return True
137
-
138
- except Exception as e:
139
- print(f"Error saving game round: {e}")
140
- self.conn.rollback()
141
- return False
142
-
143
- async def complete_session(self, session_id: str) -> bool:
144
- """Mark a game session as completed"""
145
- try:
146
- cursor = self.conn.cursor()
147
- cursor.execute('''
148
- UPDATE game_sessions
149
- SET is_completed = TRUE,
150
- completed_at = CURRENT_TIMESTAMP
151
- WHERE session_id = ?
152
- ''', (session_id,))
153
- self.conn.commit()
154
- return True
155
- except Exception as e:
156
- print(f"Error completing session: {e}")
157
- self.conn.rollback()
158
- return False
159
-
160
- async def get_game_history(self, session_id: str) -> Optional[Dict[str, Any]]:
161
- """Retrieve game history for a session"""
162
- try:
163
- cursor = self.conn.cursor()
164
-
165
- # Get session stats
166
- cursor.execute('''
167
- SELECT total_rounds, player_wins, ai_wins, draws, is_completed, completed_at
168
- FROM game_sessions
169
- WHERE session_id = ?
170
- ''', (session_id,))
171
- session_data = cursor.fetchone()
172
-
173
- if not session_data:
174
- return None
175
-
176
- # Get all rounds
177
- cursor.execute('''
178
- SELECT round_number, player_move, ai_move, result, created_at
179
- FROM game_rounds
180
- WHERE session_id = ?
181
- ORDER BY round_number
182
- ''', (session_id,))
183
- rounds = cursor.fetchall()
184
-
185
- return {
186
- 'session_id': session_id,
187
- 'total_rounds': session_data[0],
188
- 'player_wins': session_data[1],
189
- 'ai_wins': session_data[2],
190
- 'draws': session_data[3],
191
- 'is_completed': session_data[4],
192
- 'completed_at': session_data[5],
193
- 'rounds': [
194
- {
195
- 'round_number': r[0],
196
- 'player_move': r[1],
197
- 'ai_move': r[2],
198
- 'result': r[3],
199
- 'created_at': r[4]
200
- }
201
- for r in rounds
202
- ]
203
- }
204
-
205
- except Exception as e:
206
- print(f"Error retrieving game history: {e}")
207
- return None
208
-
209
- async def get_ai_state(self, session_id: str, ai_type: str) -> Optional[Dict[str, Any]]:
210
- """Retrieve AI state for a session"""
211
- try:
212
- cursor = self.conn.cursor()
213
- cursor.execute('''
214
- SELECT state_data
215
- FROM ai_states
216
- WHERE session_id = ? AND ai_type = ?
217
- ''', (session_id, ai_type))
218
-
219
- result = cursor.fetchone()
220
- if result:
221
- return json.loads(result[0])
222
- return None
223
-
224
- except Exception as e:
225
- print(f"Error retrieving AI state: {e}")
226
- return None
227
-
228
- async def close(self) -> None:
229
- """Close the database connection"""
230
- if self.conn:
231
- self.conn.close()
232
- self.conn = None
233
-
234
- async def save_full_session(self, session_id: str, session_data: Dict[str, Any]) -> bool:
235
- """Save the full session and all rounds to the database in one go."""
236
- print(f"[DEBUG] save_full_session called for session_id: {session_id}")
237
- print(f"[DEBUG] session_data: {session_data}")
238
- try:
239
- cursor = self.conn.cursor()
240
- # Insert or update the session summary
241
- cursor.execute('''
242
- INSERT INTO game_sessions (session_id, total_rounds, player_wins, ai_wins, draws, is_completed, completed_at)
243
- VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
244
- ON CONFLICT(session_id) DO UPDATE SET
245
- total_rounds = excluded.total_rounds,
246
- player_wins = excluded.player_wins,
247
- ai_wins = excluded.ai_wins,
248
- draws = excluded.draws,
249
- is_completed = excluded.is_completed,
250
- completed_at = excluded.completed_at
251
- ''', (
252
- session_id,
253
- session_data['total_rounds'],
254
- session_data['player_wins'],
255
- session_data['ai_wins'],
256
- session_data['draws'],
257
- True
258
- ))
259
- # Delete existing rounds for this session
260
- cursor.execute('DELETE FROM game_rounds WHERE session_id = ?', (session_id,))
261
- # Insert all rounds
262
- for round_data in session_data['rounds']:
263
- print(f"[DEBUG] Inserting round: {round_data}")
264
- created_at = round_data.get('created_at', datetime.now().isoformat())
265
- cursor.execute('''
266
- INSERT INTO game_rounds (session_id, round_number, player_move, ai_move, result, created_at)
267
- VALUES (?, ?, ?, ?, ?, ?)
268
- ''', (
269
- session_id,
270
- round_data['round_number'],
271
- round_data['player_move'],
272
- round_data['ai_move'],
273
- round_data['result'],
274
- created_at
275
- ))
276
- # Save AI state (if available)
277
- if session_data['rounds']:
278
- last_round = session_data['rounds'][-1]
279
- ai_type = last_round.get('ai_type', 'random')
280
- ai_state = last_round.get('ai_state', {})
281
- def convert_ndarray(obj):
282
- if isinstance(obj, np.ndarray):
283
- return obj.tolist()
284
- if isinstance(obj, dict):
285
- return {k: convert_ndarray(v) for k, v in obj.items()}
286
- if isinstance(obj, list):
287
- return [convert_ndarray(x) for x in obj]
288
- return obj
289
- ai_state_serializable = convert_ndarray(ai_state)
290
- cursor.execute('''
291
- INSERT INTO ai_states (session_id, ai_type, state_data)
292
- VALUES (?, ?, ?)
293
- ON CONFLICT(session_id, ai_type) DO UPDATE SET
294
- state_data = ?,
295
- last_updated = CURRENT_TIMESTAMP
296
- ''', (
297
- session_id,
298
- ai_type,
299
- json.dumps(ai_state_serializable),
300
- json.dumps(ai_state_serializable)
301
- ))
302
- self.conn.commit()
303
- return True
304
- except Exception as e:
305
- print(f"Error saving full session: {e}")
306
- self.conn.rollback()
307
- return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
RockPaperScissor/repositories/storage.py DELETED
@@ -1,23 +0,0 @@
1
- # RockPaperScissor/repositories/storage.py
2
- from abc import ABC, abstractmethod
3
- from typing import Dict, Any, Optional
4
-
5
- class StorageError(Exception):
6
- pass
7
-
8
- class Storage(ABC):
9
- async def initialize(self): # Add initialize method
10
- pass
11
-
12
- @abstractmethod
13
- async def save_game_round(self, game_data: Dict[str, Any]) -> bool:
14
- pass
15
-
16
- # We don't need these for the minimal version with GameService not using user states yet
17
- # def get_user_state(self, user_id: str) -> Optional[Dict[str, Any]]:
18
- # return None
19
- # def save_user_state(self, user_id: str, model_name: str, model_state: Dict[str, Any]) -> None:
20
- # pass
21
-
22
- async def close(self) -> None:
23
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
RockPaperScissor/routes/__init__.py DELETED
@@ -1,9 +0,0 @@
1
- """
2
- Routes package initialization for RockPaperScissor game.
3
- Contains API routes definitions.
4
- """
5
- from .game import game_router
6
-
7
- __all__ = [
8
- 'game_router'
9
- ]
 
 
 
 
 
 
 
 
 
 
RockPaperScissor/routes/__pycache__/__init__.cpython-310.pyc DELETED
Binary file (403 Bytes)
 
RockPaperScissor/routes/__pycache__/__init__.cpython-313.pyc DELETED
Binary file (367 Bytes)
 
RockPaperScissor/routes/__pycache__/game.cpython-310.pyc DELETED
Binary file (2.98 kB)
 
RockPaperScissor/routes/__pycache__/game.cpython-313.pyc DELETED
Binary file (2.65 kB)
 
RockPaperScissor/routes/__pycache__/game_o.cpython-310.pyc DELETED
Binary file (1.8 kB)
 
RockPaperScissor/routes/__pycache__/history.cpython-313.pyc DELETED
Binary file (633 Bytes)
 
RockPaperScissor/routes/__pycache__/stats.cpython-310.pyc DELETED
Binary file (2.45 kB)
 
RockPaperScissor/routes/__pycache__/stats.cpython-313.pyc DELETED
Binary file (3.23 kB)
 
RockPaperScissor/routes/game.py DELETED
@@ -1,77 +0,0 @@
1
- # backend/routes/game.py
2
- from fastapi import APIRouter, HTTPException, Request
3
- from RockPaperScissor.schemas.game import GameRequest, GameResponse #, GameSummary, LLMRequest
4
- from RockPaperScissor.utils.logging import setup_logging
5
- # from RockPaperScissor.repositories import CombinedStorage # Persistent storage (commented for Hugging Face)
6
- from RockPaperScissor.services.service_instance import game_service
7
-
8
- # Set up logger
9
- logger = setup_logging()
10
-
11
- game_router = APIRouter()
12
-
13
- @game_router.post("/play")
14
- async def play_round(request: Request, game_request: GameRequest):
15
- """
16
- Play a round with the player's move (in-memory only for Hugging Face)
17
- """
18
- result = await game_service.play_round(game_request.session_id, game_request.player_move, game_request.ai_type)
19
- if "error" in result:
20
- raise HTTPException(status_code=400, detail=f"GameService error: {result['error']}")
21
- return result
22
-
23
- # @game_router.post("/analyze")
24
- # async def analyze_game_state(llm_request: LLMRequest):
25
- # """
26
- # Get LLM analysis of the current game state.
27
- # """
28
- # try:
29
- # # Log the request
30
- # logger.info(f"Analyze request: {llm_request.model_dump()}")
31
- #
32
- # # Get LLM analysis directly from LLM service
33
- # analysis = llm_service.analyze_game_state(llm_request)
34
- # return {"analysis": analysis}
35
- #
36
- # except Exception as e:
37
- # logger.error(f"Error analyzing game state: {str(e)}")
38
- # raise HTTPException(
39
- # status_code=500,
40
- # detail="An error occurred while analyzing the game state"
41
- # )
42
-
43
- @game_router.post("/end")
44
- async def end_game(request: Request):
45
- """
46
- End the current game session and save the final state.
47
- """
48
- try:
49
- data = await request.json()
50
- session_id = data.get('session_id')
51
- if not session_id:
52
- raise HTTPException(
53
- status_code=400,
54
- detail="No session ID provided"
55
- )
56
- # Save session to database before clearing cache
57
- db_success = await game_service.save_session_to_db(session_id)
58
- # Clear in-memory data
59
- await game_service.clear_session(session_id)
60
- if db_success:
61
- return {
62
- "status": "success",
63
- "message": "Game session ended and saved to database successfully",
64
- "game_history": None
65
- }
66
- else:
67
- return {
68
- "status": "error",
69
- "message": "Game session ended, but failed to save to database",
70
- "game_history": None
71
- }
72
- except Exception as e:
73
- logger.error(f"Error ending game session: {str(e)}")
74
- raise HTTPException(
75
- status_code=500,
76
- detail="An error occurred while ending the game session"
77
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
RockPaperScissor/schemas/__pycache__/game.cpython-313.pyc DELETED
Binary file (1.65 kB)
 
RockPaperScissor/schemas/game.py DELETED
@@ -1,23 +0,0 @@
1
- # RockPaperScissor/schemas/game.py
2
- from pydantic import BaseModel, Field
3
- # from typing import Optional, Dict, Any, Literal # OLD - Pydantic might not re-export Literal
4
- from typing import Optional, Dict, Any, Literal # NEW - Import directly from typing
5
-
6
- import uuid
7
- from datetime import datetime
8
-
9
- class GameRequest(BaseModel):
10
- user_id: Optional[str] = Field("test_user")
11
- session_id: Optional[str] = Field("test_session")
12
- game_id: str = Field(default_factory=lambda: "gr_game_" + str(uuid.uuid4()))
13
- user_move: Optional[Literal["rock", "paper", "scissors"]] = None # Literal is used here
14
- ai_type: Optional[str] = None
15
-
16
- class GameResponse(BaseModel):
17
- game_id: str
18
- user_id: str
19
- session_id: str
20
- user_move: str
21
- ai_move: str
22
- result: Literal["player_win", "ai_win", "draw"] # Literal is used here
23
- session_stats: Dict[str, Any]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
RockPaperScissor/services/LLM_service.py DELETED
@@ -1,105 +0,0 @@
1
- import os
2
- from typing import Dict, Any, Optional
3
- from transformers import AutoModelForCausalLM, AutoTokenizer
4
- import torch
5
- from huggingface_hub import spaces
6
-
7
- class LLMService:
8
- def __init__(self, game_service, hf_model_name: str, hf_api_token: str = None):
9
- """
10
- Initialize the LLM service.
11
-
12
- Args:
13
- game_service: The game service instance to access game stats
14
- hf_model_name: The Hugging Face model name (e.g., "Qwen/Qwen3-0.6B")
15
- hf_api_token: Hugging Face API token (optional if using public models)
16
- """
17
- self.game_service = game_service
18
- self.hf_model_name = hf_model_name
19
- self.hf_api_token = hf_api_token or os.getenv("HF_API_TOKEN")
20
- # Load model and tokenizer locally
21
- self.tokenizer = AutoTokenizer.from_pretrained(self.hf_model_name)
22
- self.model = AutoModelForCausalLM.from_pretrained(
23
- self.hf_model_name,
24
- torch_dtype=torch.float32,
25
- device_map={"": "cpu"}
26
- )
27
-
28
- @spaces.GPU
29
- async def generate_response(self, prompt: str) -> str:
30
- """
31
- Generate a response from the LLM model using local inference.
32
- Args:
33
- prompt: The input prompt for the model
34
- Returns:
35
- str: The model's response
36
- """
37
- # Move model to GPU if not already
38
- if next(self.model.parameters()).device.type != "cuda":
39
- self.model = self.model.to("cuda")
40
- messages = [
41
- {"role": "user", "content": prompt}
42
- ]
43
- text = self.tokenizer.apply_chat_template(
44
- messages,
45
- tokenize=False,
46
- add_generation_prompt=True,
47
- enable_thinking=True
48
- )
49
- model_inputs = self.tokenizer([text], return_tensors="pt").to("cuda")
50
- with torch.no_grad():
51
- generated_ids = self.model.generate(
52
- **model_inputs,
53
- max_new_tokens=256,
54
- temperature=0.6,
55
- top_p=0.95,
56
- top_k=20,
57
- do_sample=True
58
- )
59
- output_ids = generated_ids[0][len(model_inputs.input_ids[0]):].tolist()
60
- content = self.tokenizer.decode(output_ids, skip_special_tokens=True).strip("\n")
61
- return content
62
-
63
- def format_game_stats_prompt(self, game_stats: Dict[str, Any]) -> str:
64
- """
65
- Format game statistics into a prompt for the LLM.
66
-
67
- Args:
68
- game_stats: Dictionary containing game statistics
69
-
70
- Returns:
71
- str: Formatted prompt for the LLM
72
- """
73
- prompt = f"""Based on the following Rock Paper Scissors game statistics, provide a brief, helpful suggestion to improve the player's strategy:
74
-
75
- Game Statistics:
76
- - Total Games Played: {game_stats.get('total_games', 0)}
77
- - Wins: {game_stats.get('wins', 0)}
78
- - Losses: {game_stats.get('losses', 0)}
79
- - Draws: {game_stats.get('draws', 0)}
80
- - Win Rate: {game_stats.get('win_rate', 0)}%
81
- - Most Used Move: {game_stats.get('most_used_move', 'N/A')}
82
- - Opponent's Most Used Move: {game_stats.get('opponent_most_used_move', 'N/A')}
83
-
84
- Please provide a brief, actionable suggestion to help improve the player's performance."""
85
-
86
- return prompt
87
-
88
- async def get_game_suggestion(self) -> str:
89
- """
90
- Get a suggestion based on current game statistics.
91
-
92
- Returns:
93
- str: The LLM's suggestion
94
- """
95
- # Get current game stats from game service
96
- game_stats = self.game_service.get_game_stats()
97
-
98
- # Format the prompt
99
- prompt = self.format_game_stats_prompt(game_stats)
100
-
101
- # Generate response
102
- return await self.generate_response(prompt)
103
-
104
- async def close(self):
105
- pass # No async close needed for local inference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
RockPaperScissor/services/__init__.py DELETED
@@ -1,8 +0,0 @@
1
- # RockPaperScissor/services/__init__.py
2
- from .game_service_class import GameService
3
- # from .llm_service import LLMService # Add LLMService later
4
-
5
- __all__ = [
6
- 'GameService',
7
- # 'LLMService',
8
- ]
 
 
 
 
 
 
 
 
 
RockPaperScissor/services/__pycache__/LLM_service.cpython-313.pyc DELETED
Binary file (4.8 kB)
 
RockPaperScissor/services/__pycache__/__init__.cpython-313.pyc DELETED
Binary file (270 Bytes)
 
RockPaperScissor/services/__pycache__/game_service.cpython-313.pyc DELETED
Binary file (3.53 kB)
 
RockPaperScissor/services/__pycache__/game_service_class.cpython-313.pyc DELETED
Binary file (9.09 kB)
 
RockPaperScissor/services/__pycache__/service_instance.cpython-313.pyc DELETED
Binary file (418 Bytes)
 
RockPaperScissor/services/game_service_class.py DELETED
@@ -1,174 +0,0 @@
1
- from typing import Dict, Any
2
- from enum import Enum
3
- from RockPaperScissor.models.adaptive_markov_ai import AdaptiveMarkovAI
4
- from RockPaperScissor.models.base_ai import BaseAI
5
- from RockPaperScissor.models.random_ai import RandomAI
6
- from RockPaperScissor.game_cache.memory_cache import GameSessionCache
7
- from RockPaperScissor.repositories.sql_storage import SQLStorage
8
-
9
- class Move(Enum):
10
- ROCK = "rock"
11
- PAPER = "paper"
12
- SCISSORS = "scissors"
13
-
14
- class GameResult(Enum):
15
- PLAYER_WIN = "player_win"
16
- AI_WIN = "ai_win"
17
- DRAW = "draw"
18
-
19
- class GameService:
20
- def __init__(self, storage=None, db_path: str = "data/game_history.db"):
21
- self.cache = GameSessionCache()
22
- self.model_states: Dict[str, Dict[str, Any]] = {} # Track model state per session+ai
23
- self.ai_models = {
24
- "random": RandomAI(),
25
- "adaptive_markov": AdaptiveMarkovAI()
26
- }
27
- # Use provided storage or fallback to SQLStorage
28
- self.storage = storage or SQLStorage(db_path)
29
-
30
- async def initialize(self):
31
- await self.storage.initialize()
32
-
33
- async def play_round(self, session_id: str, player_move: str, ai_type: str = "random") -> Dict[str, Any]:
34
- try:
35
- # Get or initialize model state
36
- state_key = f"{session_id}__{ai_type}"
37
- model_state = self.model_states.get(state_key, None)
38
-
39
- # Update model state for adaptive_markov AI
40
- if ai_type == "adaptive_markov":
41
- if model_state is None:
42
- model_state = None
43
- else:
44
- model_state["player_second_last_move"] = model_state.get("player_last_move", None)
45
- model_state["player_last_move"] = player_move
46
-
47
- # Get AI move
48
- ai = self.ai_models.get(ai_type, self.ai_models["random"])
49
- ai_move, updated_state = ai.make_move(model_state)
50
- self.model_states[state_key] = updated_state
51
-
52
- # Determine result
53
- result = self._determine_winner(player_move, ai_move)
54
-
55
- # Create game data for storage
56
- game_data = {
57
- 'game_id': session_id,
58
- 'player_move': player_move,
59
- 'ai_move': ai_move,
60
- 'result': result,
61
- 'ai_type': ai_type,
62
- 'ai_state': updated_state
63
- }
64
-
65
- # Save to storage
66
- await self.storage.save_game_round(game_data)
67
-
68
- # Update cache
69
- self.cache.update_session(session_id, game_data)
70
-
71
- # Get stats from cache
72
- session_data = self.cache.get_session(session_id)
73
- if not session_data:
74
- return {
75
- "player_move": player_move,
76
- "ai_move": ai_move,
77
- "result": result,
78
- "stats": self._get_empty_stats()
79
- }
80
-
81
- return {
82
- "player_move": player_move,
83
- "ai_move": ai_move,
84
- "result": result,
85
- "stats": self._get_formatted_stats(session_data)
86
- }
87
- except Exception as e:
88
- import traceback
89
- print(f"[GameService] Error in play_round: {e}\n{traceback.format_exc()}")
90
- return {"error": str(e)}
91
-
92
- async def save_session_to_db(self, session_id: str) -> bool:
93
- """Save the completed session and all rounds to storage"""
94
- session_data = self.cache.get_session(session_id)
95
- print(f"[DEBUG] save_session_to_db called for session_id: {session_id}")
96
- print(f"[DEBUG] session_data: {session_data}")
97
- if not session_data:
98
- print(f"[GameService] No session data found for session_id: {session_id}")
99
- return False
100
- try:
101
- success = await self.storage.save_full_session(session_id, session_data)
102
- if success:
103
- print(f"[GameService] Session {session_id} saved to storage.")
104
- else:
105
- print(f"[GameService] Failed to save session {session_id} to storage.")
106
- return success
107
- except Exception as e:
108
- import traceback
109
- print(f"[GameService] Error saving session to storage: {e}\n{traceback.format_exc()}")
110
- return False
111
-
112
- def _determine_winner(self, player_move: str, ai_move: str) -> str:
113
- if player_move == ai_move:
114
- return GameResult.DRAW.value
115
- winning_moves = {
116
- Move.ROCK.value: Move.SCISSORS.value,
117
- Move.PAPER.value: Move.ROCK.value,
118
- Move.SCISSORS.value: Move.PAPER.value
119
- }
120
- return GameResult.PLAYER_WIN.value if winning_moves[player_move] == ai_move else GameResult.AI_WIN.value
121
-
122
- def _get_empty_stats(self) -> Dict[str, Any]:
123
- return {
124
- "player_wins": 0,
125
- "ai_wins": 0,
126
- "draws": 0,
127
- "total_rounds": 0,
128
- "player_win_rate": "0.0%",
129
- "ai_win_rate": "0.0%",
130
- "rock_percent": "0%",
131
- "paper_percent": "0%",
132
- "scissors_percent": "0%"
133
- }
134
-
135
- def _get_formatted_stats(self, session_data: Dict[str, Any]) -> Dict[str, Any]:
136
- total = session_data['total_rounds']
137
- player_wins = session_data['player_wins']
138
- ai_wins = session_data['ai_wins']
139
- draws = session_data['draws']
140
-
141
- # Calculate win rates
142
- player_win_rate = (player_wins / total * 100) if total > 0 else 0
143
- ai_win_rate = (ai_wins / total * 100) if total > 0 else 0
144
-
145
- # Calculate move distribution
146
- rounds = session_data['rounds']
147
- rock_count = sum(1 for r in rounds if r['player_move'] == Move.ROCK.value)
148
- paper_count = sum(1 for r in rounds if r['player_move'] == Move.PAPER.value)
149
- scissors_count = sum(1 for r in rounds if r['player_move'] == Move.SCISSORS.value)
150
-
151
- total_moves = rock_count + paper_count + scissors_count
152
- rock_percent = (rock_count / total_moves * 100) if total_moves > 0 else 0
153
- paper_percent = (paper_count / total_moves * 100) if total_moves > 0 else 0
154
- scissors_percent = (scissors_count / total_moves * 100) if total_moves > 0 else 0
155
-
156
- return {
157
- "player_wins": player_wins,
158
- "ai_wins": ai_wins,
159
- "draws": draws,
160
- "total_rounds": total,
161
- "player_win_rate": f"{player_win_rate:.1f}%",
162
- "ai_win_rate": f"{ai_win_rate:.1f}%",
163
- "rock_percent": f"{rock_percent:.0f}%",
164
- "paper_percent": f"{paper_percent:.0f}%",
165
- "scissors_percent": f"{scissors_percent:.0f}%"
166
- }
167
-
168
- async def clear_session(self, session_id: str):
169
- # Remove only the specific session from cache
170
- self.cache.remove_session(session_id)
171
- # Clear model states
172
- keys_to_remove = [k for k in self.model_states if k.startswith(session_id)]
173
- for k in keys_to_remove:
174
- del self.model_states[k]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
RockPaperScissor/services/service_instance.py DELETED
@@ -1,6 +0,0 @@
1
- from RockPaperScissor.services.game_service_class import GameService
2
- from RockPaperScissor.repositories import CombinedStorage
3
-
4
- # Initialize storage with both SQLite and S3
5
- storage = CombinedStorage()
6
- game_service = GameService(storage=storage)
 
 
 
 
 
 
 
RockPaperScissor/utils/__init__.py DELETED
@@ -1,6 +0,0 @@
1
- # RockPaperScissor/utils/__init__.py
2
- from .logging import setup_logging
3
-
4
- __all__ = [
5
- 'setup_logging',
6
- ]