Spaces:
Configuration error
Configuration error
EZTIME2025 commited on
Commit ·
825efb1
1
Parent(s): a394713
knight finally finished!
Browse files- game_viz.log +2 -0
- pycatan/game_manager.py +59 -6
- pycatan/log_events.py +2 -0
- pycatan/visualization.py +7 -0
- test_knight_steal.py +84 -0
- test_knight_viz.py +86 -0
game_viz.log
CHANGED
|
@@ -1390,6 +1390,8 @@ Current Player: [1m[92m► b[0m
|
|
| 1390 |
[93m-----[0m
|
| 1391 |
Board Tiles: 19 tiles configured
|
| 1392 |
|
|
|
|
|
|
|
| 1393 |
[92m✓[0m b used Knight
|
| 1394 |
|
| 1395 |
[1m[96m==================================================[0m
|
|
|
|
| 1390 |
[93m-----[0m
|
| 1391 |
Board Tiles: 19 tiles configured
|
| 1392 |
|
| 1393 |
+
[92m✓[0m ✨ b used Knight
|
| 1394 |
+
[92m✓[0m 🦹 b stole Sheep from a
|
| 1395 |
[92m✓[0m b used Knight
|
| 1396 |
|
| 1397 |
[1m[96m==================================================[0m
|
pycatan/game_manager.py
CHANGED
|
@@ -15,6 +15,7 @@ from pycatan.user import User, UserList, validate_user_list, UserInputError
|
|
| 15 |
from pycatan.game import Game
|
| 16 |
from pycatan.statuses import Statuses
|
| 17 |
from pycatan.card import DevCard
|
|
|
|
| 18 |
|
| 19 |
|
| 20 |
class GameManager:
|
|
@@ -878,6 +879,9 @@ class GameManager:
|
|
| 878 |
# Remove the card from player's hand
|
| 879 |
self.game.players[player_id].remove_dev_card(DevCard.Knight)
|
| 880 |
|
|
|
|
|
|
|
|
|
|
| 881 |
# Update visualizations
|
| 882 |
player_name = self.users[player_id].name if hasattr(self.users[player_id], 'name') else f"Player {player_id}"
|
| 883 |
|
|
@@ -885,6 +889,23 @@ class GameManager:
|
|
| 885 |
robber_msg = f"⚔️ {player_name} used a Knight card! Robber moved to {tile_display}."
|
| 886 |
print(f"\n {robber_msg}")
|
| 887 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 888 |
# Notify about robber move
|
| 889 |
self._notify_all_users(
|
| 890 |
"knight_used",
|
|
@@ -894,10 +915,43 @@ class GameManager:
|
|
| 894 |
# Notify about card steal if victim exists
|
| 895 |
if victim_id is not None:
|
| 896 |
victim_name = self.users[victim_id].name if hasattr(self.users[victim_id], 'name') else f"Player {victim_id}"
|
| 897 |
-
|
| 898 |
-
|
| 899 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 900 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 901 |
|
| 902 |
# Notify if player got Largest Army
|
| 903 |
if self.game.largest_army == player_id:
|
|
@@ -1725,9 +1779,8 @@ class GameManager:
|
|
| 1725 |
)
|
| 1726 |
|
| 1727 |
# Send to visualizations
|
| 1728 |
-
|
| 1729 |
-
|
| 1730 |
-
viz.log_event(log_entry)
|
| 1731 |
|
| 1732 |
if distribution:
|
| 1733 |
message = f"Rolled {total} ({die1}+{die2}). Resources distributed."
|
|
|
|
| 15 |
from pycatan.game import Game
|
| 16 |
from pycatan.statuses import Statuses
|
| 17 |
from pycatan.card import DevCard
|
| 18 |
+
from pycatan.log_events import EventType, create_log_entry
|
| 19 |
|
| 20 |
|
| 21 |
class GameManager:
|
|
|
|
| 879 |
# Remove the card from player's hand
|
| 880 |
self.game.players[player_id].remove_dev_card(DevCard.Knight)
|
| 881 |
|
| 882 |
+
# Get stolen card info from args (set by game.use_dev_card)
|
| 883 |
+
stolen_card = args.get('stolen_card')
|
| 884 |
+
|
| 885 |
# Update visualizations
|
| 886 |
player_name = self.users[player_id].name if hasattr(self.users[player_id], 'name') else f"Player {player_id}"
|
| 887 |
|
|
|
|
| 889 |
robber_msg = f"⚔️ {player_name} used a Knight card! Robber moved to {tile_display}."
|
| 890 |
print(f"\n {robber_msg}")
|
| 891 |
|
| 892 |
+
# Send Knight card usage log to visualizations
|
| 893 |
+
knight_log = create_log_entry(
|
| 894 |
+
event_type=EventType.USE_DEV_CARD,
|
| 895 |
+
turn=self._current_game_state.turn_number,
|
| 896 |
+
player_id=player_id,
|
| 897 |
+
player_name=player_name,
|
| 898 |
+
data={
|
| 899 |
+
'card': 'Knight',
|
| 900 |
+
'robber_tile': tile_display,
|
| 901 |
+
'message': robber_msg
|
| 902 |
+
},
|
| 903 |
+
status="SUCCESS"
|
| 904 |
+
)
|
| 905 |
+
|
| 906 |
+
if self.visualization_manager:
|
| 907 |
+
self.visualization_manager.log_event(knight_log)
|
| 908 |
+
|
| 909 |
# Notify about robber move
|
| 910 |
self._notify_all_users(
|
| 911 |
"knight_used",
|
|
|
|
| 915 |
# Notify about card steal if victim exists
|
| 916 |
if victim_id is not None:
|
| 917 |
victim_name = self.users[victim_id].name if hasattr(self.users[victim_id], 'name') else f"Player {victim_id}"
|
| 918 |
+
|
| 919 |
+
# stolen_card was already retrieved from args above
|
| 920 |
+
if stolen_card:
|
| 921 |
+
# Map card type to Hebrew/English name
|
| 922 |
+
card_names = {
|
| 923 |
+
'wood': 'עץ (Wood)',
|
| 924 |
+
'brick': 'לבנה (Brick)',
|
| 925 |
+
'sheep': 'כבשה (Sheep)',
|
| 926 |
+
'wheat': 'חיטה (Wheat)',
|
| 927 |
+
'ore': 'עפרה (Ore)'
|
| 928 |
+
}
|
| 929 |
+
card_display = card_names.get(stolen_card.value, stolen_card.value)
|
| 930 |
+
steal_msg = f"🎯 {player_name} stole {card_display} from {victim_name}!"
|
| 931 |
+
else:
|
| 932 |
+
steal_msg = f"🎯 {player_name} stole a card from {victim_name}!"
|
| 933 |
+
|
| 934 |
+
print(f"\n {steal_msg}")
|
| 935 |
+
|
| 936 |
+
# Send steal log to visualizations
|
| 937 |
+
steal_log = create_log_entry(
|
| 938 |
+
event_type=EventType.ROBBER_STEAL,
|
| 939 |
+
turn=self._current_game_state.turn_number,
|
| 940 |
+
player_id=player_id,
|
| 941 |
+
player_name=player_name,
|
| 942 |
+
data={
|
| 943 |
+
'victim_id': victim_id,
|
| 944 |
+
'victim': victim_name,
|
| 945 |
+
'card': stolen_card.name if stolen_card else 'unknown', # Use .name to get 'Wood', 'Brick', etc.
|
| 946 |
+
'message': steal_msg
|
| 947 |
+
},
|
| 948 |
+
status="SUCCESS"
|
| 949 |
)
|
| 950 |
+
|
| 951 |
+
if self.visualization_manager:
|
| 952 |
+
self.visualization_manager.log_event(steal_log)
|
| 953 |
+
|
| 954 |
+
self._notify_all_users("knight_steal", steal_msg)
|
| 955 |
|
| 956 |
# Notify if player got Largest Army
|
| 957 |
if self.game.largest_army == player_id:
|
|
|
|
| 1779 |
)
|
| 1780 |
|
| 1781 |
# Send to visualizations
|
| 1782 |
+
if self.visualization_manager:
|
| 1783 |
+
self.visualization_manager.log_event(log_entry)
|
|
|
|
| 1784 |
|
| 1785 |
if distribution:
|
| 1786 |
message = f"Rolled {total} ({die1}+{die2}). Resources distributed."
|
pycatan/log_events.py
CHANGED
|
@@ -199,6 +199,8 @@ class LogEntry:
|
|
| 199 |
elif self.event_type == EventType.ROBBER_STEAL:
|
| 200 |
victim = self.data.get('victim', '?')
|
| 201 |
card = self.data.get('card', 'a card')
|
|
|
|
|
|
|
| 202 |
return f"🦹 {player_str} stole {card} from {victim}"
|
| 203 |
|
| 204 |
elif self.event_type == EventType.TURN_START:
|
|
|
|
| 199 |
elif self.event_type == EventType.ROBBER_STEAL:
|
| 200 |
victim = self.data.get('victim', '?')
|
| 201 |
card = self.data.get('card', 'a card')
|
| 202 |
+
# Card names come from enum.name which is already capitalized (Wood, Brick, etc.)
|
| 203 |
+
# Just display as-is
|
| 204 |
return f"🦹 {player_str} stole {card} from {victim}"
|
| 205 |
|
| 206 |
elif self.event_type == EventType.TURN_START:
|
pycatan/visualization.py
CHANGED
|
@@ -8,6 +8,7 @@ Different visualization types (console, web, log) inherit from this class.
|
|
| 8 |
from abc import ABC, abstractmethod
|
| 9 |
from typing import Dict, Any, List, Optional
|
| 10 |
from .actions import Action, ActionResult
|
|
|
|
| 11 |
|
| 12 |
|
| 13 |
class Visualization(ABC):
|
|
@@ -200,6 +201,12 @@ class VisualizationManager:
|
|
| 200 |
if viz.is_enabled():
|
| 201 |
viz.display_message(message)
|
| 202 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
def get_visualization_count(self) -> int:
|
| 204 |
"""Get the number of registered visualizations."""
|
| 205 |
return len(self.visualizations)
|
|
|
|
| 8 |
from abc import ABC, abstractmethod
|
| 9 |
from typing import Dict, Any, List, Optional
|
| 10 |
from .actions import Action, ActionResult
|
| 11 |
+
from .log_events import LogEntry
|
| 12 |
|
| 13 |
|
| 14 |
class Visualization(ABC):
|
|
|
|
| 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:
|
| 207 |
+
if viz.is_enabled() and hasattr(viz, 'log_event'):
|
| 208 |
+
viz.log_event(log_entry)
|
| 209 |
+
|
| 210 |
def get_visualization_count(self) -> int:
|
| 211 |
"""Get the number of registered visualizations."""
|
| 212 |
return len(self.visualizations)
|
test_knight_steal.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Test Knight card with stolen card display
|
| 3 |
+
"""
|
| 4 |
+
from pycatan import Game
|
| 5 |
+
from pycatan.card import DevCard, ResCard
|
| 6 |
+
from pycatan.board_definition import board_definition
|
| 7 |
+
from pycatan.actions import GamePhase
|
| 8 |
+
from pycatan.statuses import Statuses
|
| 9 |
+
|
| 10 |
+
def test_knight_with_steal():
|
| 11 |
+
"""Test that Knight card shows which card was stolen."""
|
| 12 |
+
print("\n" + "="*60)
|
| 13 |
+
print("Testing Knight Card with Card Steal")
|
| 14 |
+
print("="*60)
|
| 15 |
+
|
| 16 |
+
# Create game with 3 players
|
| 17 |
+
game = Game(num_of_players=3)
|
| 18 |
+
game.game_phase = GamePhase.NORMAL_PLAY
|
| 19 |
+
|
| 20 |
+
# Give player 0 a Knight card
|
| 21 |
+
game.players[0].add_dev_card(DevCard.Knight)
|
| 22 |
+
|
| 23 |
+
# Give player 1 some resource cards
|
| 24 |
+
game.players[1].add_cards([ResCard.Wheat, ResCard.Wood, ResCard.Brick])
|
| 25 |
+
|
| 26 |
+
# Add a settlement for player 1 at a point adjacent to tile 5
|
| 27 |
+
# This allows player 0 to steal from player 1
|
| 28 |
+
# Tile 5 is at game coords (1,1)
|
| 29 |
+
|
| 30 |
+
# Get a point adjacent to tile (1,1)
|
| 31 |
+
tile = game.board.tiles[1][1]
|
| 32 |
+
if tile.points:
|
| 33 |
+
settlement_point = tile.points[0]
|
| 34 |
+
# Use game.add_settlement instead of player.add_settlement
|
| 35 |
+
game.add_settlement(player=1, point=settlement_point, is_starting=True)
|
| 36 |
+
print(f"\n✓ Game setup complete")
|
| 37 |
+
print(f" - Player 0: {len(game.players[0].dev_cards)} Knight card")
|
| 38 |
+
print(f" - Player 1: {len(game.players[1].cards)} resource cards, 1 settlement")
|
| 39 |
+
print(f" - Player 1's cards: {[card.value for card in game.players[1].cards]}")
|
| 40 |
+
|
| 41 |
+
# Test using Knight card on tile 5 and stealing from player 1
|
| 42 |
+
print(f"\n🧪 Testing Knight card with steal...")
|
| 43 |
+
|
| 44 |
+
args = {
|
| 45 |
+
'robber_pos': [1, 1],
|
| 46 |
+
'victim': 1
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
print(f"📢 Using Knight card to move robber to tile 5 and steal from Player 1")
|
| 50 |
+
|
| 51 |
+
# Record player 1's card count before
|
| 52 |
+
cards_before = len(game.players[1].cards)
|
| 53 |
+
|
| 54 |
+
# Execute
|
| 55 |
+
status = game.use_dev_card(0, DevCard.Knight, args)
|
| 56 |
+
|
| 57 |
+
if status == Statuses.ALL_GOOD:
|
| 58 |
+
cards_after = len(game.players[1].cards)
|
| 59 |
+
stolen = cards_before - cards_after
|
| 60 |
+
|
| 61 |
+
print(f"\n✅ Knight card used successfully!")
|
| 62 |
+
print(f" - Robber moved to tile 5 (coords [1, 1])")
|
| 63 |
+
print(f" - Player 1 had {cards_before} cards, now has {cards_after}")
|
| 64 |
+
print(f" - {stolen} card was stolen")
|
| 65 |
+
print(f" - Player 0 knights: {game.players[0].knight_cards}")
|
| 66 |
+
|
| 67 |
+
# Check if stolen card info is in args
|
| 68 |
+
if 'stolen_card' in args:
|
| 69 |
+
card = args['stolen_card']
|
| 70 |
+
print(f"\n✨ Stolen card: {card.value}")
|
| 71 |
+
print(f" Message should show: '🎯 Player 0 stole {card.value} from Player 1!'")
|
| 72 |
+
else:
|
| 73 |
+
print(f"\n⚠️ No stolen card info found in args")
|
| 74 |
+
else:
|
| 75 |
+
print(f"❌ Failed with status: {status}")
|
| 76 |
+
else:
|
| 77 |
+
print("❌ Could not find points on tile")
|
| 78 |
+
|
| 79 |
+
print("\n" + "="*60)
|
| 80 |
+
print("Test Complete!")
|
| 81 |
+
print("="*60)
|
| 82 |
+
|
| 83 |
+
if __name__ == "__main__":
|
| 84 |
+
test_knight_with_steal()
|
test_knight_viz.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Test Knight card with visualization logs
|
| 3 |
+
"""
|
| 4 |
+
from pycatan import Game
|
| 5 |
+
from pycatan.card import DevCard, ResCard
|
| 6 |
+
from pycatan.board_definition import board_definition
|
| 7 |
+
from pycatan.actions import GamePhase
|
| 8 |
+
from pycatan.statuses import Statuses
|
| 9 |
+
from pycatan.game_manager import GameManager
|
| 10 |
+
from pycatan.human_user import HumanUser
|
| 11 |
+
from pycatan.console_visualization import ConsoleVisualization
|
| 12 |
+
|
| 13 |
+
def test_knight_with_visualizations():
|
| 14 |
+
"""Test that Knight card logs appear in visualizations."""
|
| 15 |
+
print("\n" + "="*60)
|
| 16 |
+
print("Testing Knight Card with Visualization Logs")
|
| 17 |
+
print("="*60)
|
| 18 |
+
|
| 19 |
+
# Create users
|
| 20 |
+
users = [HumanUser("Alice", 0), HumanUser("Bob", 1), HumanUser("Charlie", 2)]
|
| 21 |
+
|
| 22 |
+
# Create console visualization
|
| 23 |
+
console_viz = ConsoleVisualization()
|
| 24 |
+
|
| 25 |
+
# Create game manager
|
| 26 |
+
gm = GameManager(users=users)
|
| 27 |
+
|
| 28 |
+
# Setup visualization manager
|
| 29 |
+
from pycatan.visualization import VisualizationManager
|
| 30 |
+
gm.visualization_manager = VisualizationManager()
|
| 31 |
+
gm.visualization_manager.add_visualization(console_viz)
|
| 32 |
+
|
| 33 |
+
# Setup game manually
|
| 34 |
+
gm.game.game_phase = GamePhase.NORMAL_PLAY
|
| 35 |
+
|
| 36 |
+
# Give player 0 (Alice) a Knight card
|
| 37 |
+
gm.game.players[0].add_dev_card(DevCard.Knight)
|
| 38 |
+
|
| 39 |
+
# Give player 1 (Bob) some resource cards
|
| 40 |
+
gm.game.players[1].add_cards([ResCard.Wheat, ResCard.Wood, ResCard.Brick])
|
| 41 |
+
|
| 42 |
+
# Add a settlement for player 1 at a point adjacent to tile 5
|
| 43 |
+
tile = gm.game.board.tiles[1][1] # Tile 5 is at (1,1)
|
| 44 |
+
if tile.points:
|
| 45 |
+
settlement_point = tile.points[0]
|
| 46 |
+
gm.game.add_settlement(player=1, point=settlement_point, is_starting=True)
|
| 47 |
+
|
| 48 |
+
print(f"\n✓ Game setup complete")
|
| 49 |
+
print(f" - Alice has {len(gm.game.players[0].dev_cards)} Knight card")
|
| 50 |
+
print(f" - Bob has {len(gm.game.players[1].cards)} resource cards")
|
| 51 |
+
|
| 52 |
+
# Create Knight action directly
|
| 53 |
+
from pycatan.actions import Action, ActionType
|
| 54 |
+
|
| 55 |
+
knight_action = Action(
|
| 56 |
+
action_type=ActionType.USE_DEV_CARD,
|
| 57 |
+
player_id=0,
|
| 58 |
+
parameters={
|
| 59 |
+
'card_type': DevCard.Knight,
|
| 60 |
+
'tile_id': 5,
|
| 61 |
+
'victim_name': 'Bob'
|
| 62 |
+
}
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
print(f"\n🧪 Executing Knight card action...")
|
| 66 |
+
print(f" This should send logs to the visualization\n")
|
| 67 |
+
|
| 68 |
+
# Execute through GameManager
|
| 69 |
+
result = gm._use_knight_card(knight_action)
|
| 70 |
+
|
| 71 |
+
if result.success:
|
| 72 |
+
print(f"\n✅ Knight card executed successfully!")
|
| 73 |
+
print(f" Logs should have been displayed above with:")
|
| 74 |
+
print(f" 1. ⚔️ Knight card used message")
|
| 75 |
+
print(f" 2. 🎯 Card stolen message (with card type)")
|
| 76 |
+
else:
|
| 77 |
+
print(f"❌ Failed: {result.error_message}")
|
| 78 |
+
else:
|
| 79 |
+
print("❌ Could not find points on tile")
|
| 80 |
+
|
| 81 |
+
print("\n" + "="*60)
|
| 82 |
+
print("Test Complete!")
|
| 83 |
+
print("="*60)
|
| 84 |
+
|
| 85 |
+
if __name__ == "__main__":
|
| 86 |
+
test_knight_with_visualizations()
|