Spaces:
Configuration error
Configuration error
EZTIME2025 commited on
Commit ·
d89bbf7
1
Parent(s): 2249362
adding win
Browse files- game_viz.log +12 -0
- pycatan/console_visualization.py +23 -1
- pycatan/game_manager.py +5 -1
- pycatan/static/js/main.js +127 -0
- pycatan/visualization.py +18 -0
- pycatan/web_visualization.py +47 -0
game_viz.log
CHANGED
|
@@ -2402,3 +2402,15 @@ Current Player: [1m[92m► c[0m
|
|
| 2402 |
[93m-----[0m
|
| 2403 |
Board Tiles: 19 tiles configured
|
| 2404 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2402 |
[93m-----[0m
|
| 2403 |
Board Tiles: 19 tiles configured
|
| 2404 |
|
| 2405 |
+
|
| 2406 |
+
[1m[93m============================================================[0m
|
| 2407 |
+
[1m[93m✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ [0m
|
| 2408 |
+
|
| 2409 |
+
[1m[92m🎉 GAME OVER - WE HAVE A WINNER! 🎉[0m
|
| 2410 |
+
|
| 2411 |
+
[1m[96m🏆 C (Player 2) 🏆[0m
|
| 2412 |
+
[1m[97mwith 5 victory points![0m
|
| 2413 |
+
|
| 2414 |
+
[1m[93m✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ [0m
|
| 2415 |
+
[1m[93m============================================================[0m
|
| 2416 |
+
|
pycatan/console_visualization.py
CHANGED
|
@@ -700,4 +700,26 @@ class ConsoleVisualization(Visualization):
|
|
| 700 |
return
|
| 701 |
|
| 702 |
# Display structured log string
|
| 703 |
-
self._print(log_entry.to_log_string())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 700 |
return
|
| 701 |
|
| 702 |
# Display structured log string
|
| 703 |
+
self._print(log_entry.to_log_string())
|
| 704 |
+
|
| 705 |
+
def display_winner(self, player_name: str, player_id: int, victory_points: int) -> None:
|
| 706 |
+
"""Display game winner announcement."""
|
| 707 |
+
if not self.enabled:
|
| 708 |
+
return
|
| 709 |
+
|
| 710 |
+
# Create a celebratory banner
|
| 711 |
+
banner_line = "=" * 60
|
| 712 |
+
stars_line = "✨ " * 30
|
| 713 |
+
|
| 714 |
+
self._print()
|
| 715 |
+
self._print(f"{self.colors['bold']}{self.colors['yellow']}{banner_line}{self.colors['reset']}")
|
| 716 |
+
self._print(f"{self.colors['bold']}{self.colors['yellow']}{stars_line}{self.colors['reset']}")
|
| 717 |
+
self._print()
|
| 718 |
+
self._print(f"{self.colors['bold']}{self.colors['green']}🎉 GAME OVER - WE HAVE A WINNER! 🎉{self.colors['reset']}".center(80))
|
| 719 |
+
self._print()
|
| 720 |
+
self._print(f"{self.colors['bold']}{self.colors['cyan']}🏆 {player_name.upper()} (Player {player_id}) 🏆{self.colors['reset']}".center(80))
|
| 721 |
+
self._print(f"{self.colors['bold']}{self.colors['white']}with {victory_points} victory points!{self.colors['reset']}".center(80))
|
| 722 |
+
self._print()
|
| 723 |
+
self._print(f"{self.colors['bold']}{self.colors['yellow']}{stars_line}{self.colors['reset']}")
|
| 724 |
+
self._print(f"{self.colors['bold']}{self.colors['yellow']}{banner_line}{self.colors['reset']}")
|
| 725 |
+
self._print()
|
pycatan/game_manager.py
CHANGED
|
@@ -1716,7 +1716,7 @@ class GameManager:
|
|
| 1716 |
victory_points = player.get_VP(include_dev=True)
|
| 1717 |
|
| 1718 |
# Check if this player has won (10+ victory points)
|
| 1719 |
-
if victory_points >=
|
| 1720 |
self._announce_winner(player_id, victory_points)
|
| 1721 |
return True
|
| 1722 |
|
|
@@ -1738,6 +1738,10 @@ class GameManager:
|
|
| 1738 |
f"🎉 {winner_name} has won the game with {victory_points} victory points! 🎉"
|
| 1739 |
)
|
| 1740 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1741 |
# Log the victory for debugging/statistics
|
| 1742 |
print(f"[GAME END] Player {player_id} ({winner_name}) won with {victory_points} victory points")
|
| 1743 |
|
|
|
|
| 1716 |
victory_points = player.get_VP(include_dev=True)
|
| 1717 |
|
| 1718 |
# Check if this player has won (10+ victory points)
|
| 1719 |
+
if victory_points >= 10:
|
| 1720 |
self._announce_winner(player_id, victory_points)
|
| 1721 |
return True
|
| 1722 |
|
|
|
|
| 1738 |
f"🎉 {winner_name} has won the game with {victory_points} victory points! 🎉"
|
| 1739 |
)
|
| 1740 |
|
| 1741 |
+
# Notify all visualizations
|
| 1742 |
+
if self.visualization_manager:
|
| 1743 |
+
self.visualization_manager.display_winner(winner_name, player_id, victory_points)
|
| 1744 |
+
|
| 1745 |
# Log the victory for debugging/statistics
|
| 1746 |
print(f"[GAME END] Player {player_id} ({winner_name}) won with {victory_points} victory points")
|
| 1747 |
|
pycatan/static/js/main.js
CHANGED
|
@@ -161,6 +161,8 @@ function connectToSSE() {
|
|
| 161 |
logEvent(data.payload, 'info');
|
| 162 |
} else if (data.type === 'error') {
|
| 163 |
logEvent(data.payload, 'error');
|
|
|
|
|
|
|
| 164 |
}
|
| 165 |
};
|
| 166 |
|
|
@@ -394,6 +396,131 @@ function appendToLog(container, element) {
|
|
| 394 |
container.scrollTop = container.scrollHeight;
|
| 395 |
}
|
| 396 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 397 |
// Game initialization
|
| 398 |
document.addEventListener('DOMContentLoaded', async function() {
|
| 399 |
console.log('🎲 Starting Catan board with server connection...');
|
|
|
|
| 161 |
logEvent(data.payload, 'info');
|
| 162 |
} else if (data.type === 'error') {
|
| 163 |
logEvent(data.payload, 'error');
|
| 164 |
+
} else if (data.type === 'game_winner') {
|
| 165 |
+
showWinnerAnnouncement(data.payload);
|
| 166 |
}
|
| 167 |
};
|
| 168 |
|
|
|
|
| 396 |
container.scrollTop = container.scrollHeight;
|
| 397 |
}
|
| 398 |
|
| 399 |
+
// Show winner announcement overlay
|
| 400 |
+
function showWinnerAnnouncement(winnerData) {
|
| 401 |
+
console.log('🎉 GAME WINNER:', winnerData);
|
| 402 |
+
|
| 403 |
+
// Log to action log
|
| 404 |
+
const logDiv = document.getElementById('action-log');
|
| 405 |
+
if (logDiv) {
|
| 406 |
+
const winnerEntry = document.createElement('div');
|
| 407 |
+
winnerEntry.className = 'log-winner';
|
| 408 |
+
winnerEntry.innerHTML = `
|
| 409 |
+
<strong>${winnerData.timestamp}</strong> -
|
| 410 |
+
🎉 <strong>${winnerData.player_name}</strong> won with ${winnerData.victory_points} victory points! 🎉
|
| 411 |
+
`;
|
| 412 |
+
logDiv.insertBefore(winnerEntry, logDiv.firstChild);
|
| 413 |
+
}
|
| 414 |
+
|
| 415 |
+
// Create fullscreen overlay
|
| 416 |
+
const overlay = document.createElement('div');
|
| 417 |
+
overlay.id = 'winner-overlay';
|
| 418 |
+
overlay.style.cssText = `
|
| 419 |
+
position: fixed;
|
| 420 |
+
top: 0;
|
| 421 |
+
left: 0;
|
| 422 |
+
width: 100%;
|
| 423 |
+
height: 100%;
|
| 424 |
+
background: rgba(0, 0, 0, 0.85);
|
| 425 |
+
display: flex;
|
| 426 |
+
justify-content: center;
|
| 427 |
+
align-items: center;
|
| 428 |
+
z-index: 9999;
|
| 429 |
+
animation: fadeIn 0.5s ease-in;
|
| 430 |
+
`;
|
| 431 |
+
|
| 432 |
+
overlay.innerHTML = `
|
| 433 |
+
<div style="
|
| 434 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 435 |
+
padding: 60px;
|
| 436 |
+
border-radius: 20px;
|
| 437 |
+
text-align: center;
|
| 438 |
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
| 439 |
+
animation: bounceIn 0.8s ease-out;
|
| 440 |
+
max-width: 600px;
|
| 441 |
+
">
|
| 442 |
+
<div style="font-size: 80px; margin-bottom: 20px;">🎉</div>
|
| 443 |
+
<h1 style="
|
| 444 |
+
color: white;
|
| 445 |
+
font-size: 48px;
|
| 446 |
+
margin: 20px 0;
|
| 447 |
+
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
| 448 |
+
">GAME OVER!</h1>
|
| 449 |
+
<div style="
|
| 450 |
+
background: rgba(255, 255, 255, 0.2);
|
| 451 |
+
padding: 30px;
|
| 452 |
+
border-radius: 15px;
|
| 453 |
+
margin: 30px 0;
|
| 454 |
+
">
|
| 455 |
+
<div style="font-size: 60px; margin-bottom: 15px;">🏆</div>
|
| 456 |
+
<h2 style="
|
| 457 |
+
color: #ffd700;
|
| 458 |
+
font-size: 42px;
|
| 459 |
+
margin: 10px 0;
|
| 460 |
+
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
| 461 |
+
">${winnerData.player_name.toUpperCase()}</h2>
|
| 462 |
+
<p style="
|
| 463 |
+
color: white;
|
| 464 |
+
font-size: 24px;
|
| 465 |
+
margin-top: 15px;
|
| 466 |
+
">Victory Points: ${winnerData.victory_points}</p>
|
| 467 |
+
</div>
|
| 468 |
+
<button onclick="document.getElementById('winner-overlay').remove()" style="
|
| 469 |
+
background: white;
|
| 470 |
+
color: #667eea;
|
| 471 |
+
border: none;
|
| 472 |
+
padding: 15px 40px;
|
| 473 |
+
font-size: 20px;
|
| 474 |
+
font-weight: bold;
|
| 475 |
+
border-radius: 10px;
|
| 476 |
+
cursor: pointer;
|
| 477 |
+
margin-top: 20px;
|
| 478 |
+
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
|
| 479 |
+
transition: transform 0.2s;
|
| 480 |
+
" onmouseover="this.style.transform='scale(1.05)'"
|
| 481 |
+
onmouseout="this.style.transform='scale(1)'">
|
| 482 |
+
Close
|
| 483 |
+
</button>
|
| 484 |
+
</div>
|
| 485 |
+
`;
|
| 486 |
+
|
| 487 |
+
// Add CSS animations
|
| 488 |
+
if (!document.getElementById('winner-animations')) {
|
| 489 |
+
const style = document.createElement('style');
|
| 490 |
+
style.id = 'winner-animations';
|
| 491 |
+
style.textContent = `
|
| 492 |
+
@keyframes fadeIn {
|
| 493 |
+
from { opacity: 0; }
|
| 494 |
+
to { opacity: 1; }
|
| 495 |
+
}
|
| 496 |
+
@keyframes bounceIn {
|
| 497 |
+
0% { transform: scale(0.3); opacity: 0; }
|
| 498 |
+
50% { transform: scale(1.05); }
|
| 499 |
+
70% { transform: scale(0.9); }
|
| 500 |
+
100% { transform: scale(1); opacity: 1; }
|
| 501 |
+
}
|
| 502 |
+
.log-winner {
|
| 503 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 504 |
+
color: white;
|
| 505 |
+
padding: 15px;
|
| 506 |
+
border-radius: 8px;
|
| 507 |
+
margin: 10px 0;
|
| 508 |
+
font-size: 16px;
|
| 509 |
+
font-weight: bold;
|
| 510 |
+
text-align: center;
|
| 511 |
+
animation: slideIn 0.5s ease-out;
|
| 512 |
+
}
|
| 513 |
+
@keyframes slideIn {
|
| 514 |
+
from { transform: translateX(-100%); opacity: 0; }
|
| 515 |
+
to { transform: translateX(0); opacity: 1; }
|
| 516 |
+
}
|
| 517 |
+
`;
|
| 518 |
+
document.head.appendChild(style);
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
document.body.appendChild(overlay);
|
| 522 |
+
}
|
| 523 |
+
|
| 524 |
// Game initialization
|
| 525 |
document.addEventListener('DOMContentLoaded', async function() {
|
| 526 |
console.log('🎲 Starting Catan board with server connection...');
|
pycatan/visualization.py
CHANGED
|
@@ -114,6 +114,18 @@ class Visualization(ABC):
|
|
| 114 |
"""
|
| 115 |
pass
|
| 116 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
def enable(self) -> None:
|
| 118 |
"""Enable this visualization."""
|
| 119 |
self.enabled = True
|
|
@@ -201,6 +213,12 @@ class VisualizationManager:
|
|
| 201 |
if viz.is_enabled():
|
| 202 |
viz.display_message(message)
|
| 203 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
def log_event(self, log_entry: LogEntry) -> None:
|
| 205 |
"""Log a structured event to all enabled visualizations."""
|
| 206 |
for viz in self.visualizations:
|
|
|
|
| 114 |
"""
|
| 115 |
pass
|
| 116 |
|
| 117 |
+
@abstractmethod
|
| 118 |
+
def display_winner(self, player_name: str, player_id: int, victory_points: int) -> None:
|
| 119 |
+
"""
|
| 120 |
+
Display game winner announcement.
|
| 121 |
+
|
| 122 |
+
Args:
|
| 123 |
+
player_name: Name of the winning player
|
| 124 |
+
player_id: ID of the winning player
|
| 125 |
+
victory_points: Number of victory points the winner achieved
|
| 126 |
+
"""
|
| 127 |
+
pass
|
| 128 |
+
|
| 129 |
def enable(self) -> None:
|
| 130 |
"""Enable this visualization."""
|
| 131 |
self.enabled = True
|
|
|
|
| 213 |
if viz.is_enabled():
|
| 214 |
viz.display_message(message)
|
| 215 |
|
| 216 |
+
def display_winner(self, player_name: str, player_id: int, victory_points: int) -> None:
|
| 217 |
+
"""Update all enabled visualizations with winner announcement."""
|
| 218 |
+
for viz in self.visualizations:
|
| 219 |
+
if viz.is_enabled():
|
| 220 |
+
viz.display_winner(player_name, player_id, victory_points)
|
| 221 |
+
|
| 222 |
def log_event(self, log_entry: LogEntry) -> None:
|
| 223 |
"""Log a structured event to all enabled visualizations."""
|
| 224 |
for viz in self.visualizations:
|
pycatan/web_visualization.py
CHANGED
|
@@ -862,6 +862,53 @@ class WebVisualization(Visualization):
|
|
| 862 |
'payload': message_data
|
| 863 |
})
|
| 864 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 865 |
def _convert_dict_to_web_format(self, game_state: Dict[str, Any]) -> Dict[str, Any]:
|
| 866 |
"""Convert dict game state to web format."""
|
| 867 |
web_format = {
|
|
|
|
| 862 |
'payload': message_data
|
| 863 |
})
|
| 864 |
|
| 865 |
+
def display_winner(self, player_name: str, player_id: int, victory_points: int) -> None:
|
| 866 |
+
"""Display game winner announcement."""
|
| 867 |
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
| 868 |
+
|
| 869 |
+
winner_data = {
|
| 870 |
+
'timestamp': timestamp,
|
| 871 |
+
'player_name': player_name,
|
| 872 |
+
'player_id': player_id,
|
| 873 |
+
'victory_points': victory_points,
|
| 874 |
+
'message': f"🎉 {player_name} has won with {victory_points} victory points! 🎉"
|
| 875 |
+
}
|
| 876 |
+
|
| 877 |
+
# Get turn number safely (handle both dict and GameState object)
|
| 878 |
+
turn_number = 0
|
| 879 |
+
if self.current_game_state:
|
| 880 |
+
if isinstance(self.current_game_state, dict):
|
| 881 |
+
turn_number = self.current_game_state.get('turn_number', 0)
|
| 882 |
+
else:
|
| 883 |
+
turn_number = getattr(self.current_game_state, 'turn_number', 0)
|
| 884 |
+
|
| 885 |
+
# Create structured log entry
|
| 886 |
+
log_entry = create_log_entry(
|
| 887 |
+
event_type=EventType.GAME_END,
|
| 888 |
+
turn=turn_number,
|
| 889 |
+
player_id=player_id,
|
| 890 |
+
player_name=player_name,
|
| 891 |
+
data={
|
| 892 |
+
'victory_points': victory_points,
|
| 893 |
+
'winner': player_name
|
| 894 |
+
},
|
| 895 |
+
status="SUCCESS"
|
| 896 |
+
)
|
| 897 |
+
self.log_entries.append(log_entry)
|
| 898 |
+
|
| 899 |
+
# Add to event history
|
| 900 |
+
self.event_history.append({
|
| 901 |
+
'type': 'game_end',
|
| 902 |
+
'timestamp': timestamp,
|
| 903 |
+
'data': winner_data
|
| 904 |
+
})
|
| 905 |
+
|
| 906 |
+
# Broadcast to web clients
|
| 907 |
+
self._broadcast_to_clients({
|
| 908 |
+
'type': 'game_winner',
|
| 909 |
+
'payload': winner_data
|
| 910 |
+
})
|
| 911 |
+
|
| 912 |
def _convert_dict_to_web_format(self, game_state: Dict[str, Any]) -> Dict[str, Any]:
|
| 913 |
"""Convert dict game state to web format."""
|
| 914 |
web_format = {
|