PyCatan-AI / .github /instructions /AI_REFACTOR_PLAN.md
EZTIME2025
1
e36cfa2

🔄 AI System Refactoring Plan

תוכנית ריפקטור מערכת ה-AI

תאריך: January 2026
סטטוס: 📋 Planning
גרסה: 1.0


📋 תוכן עניינים

  1. סקירת המצב הנוכחי
  2. הבעיות שצריך לפתור
  3. הארכיטקטורה החדשה
  4. פירוט הרכיבים
  5. תוכנית מימוש
  6. מיגרציה והגירה
  7. בדיקות

🔴 סקירת המצב הנוכחי

מבנה קיים

┌─────────────────────────────────────────────────────────────────────┐
│                        המצב הנוכחי (בלאגן)                          │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  GameManager                                                        │
│       │                                                             │
│       ▼ saves state to file                                         │
│  current_state.json                                                 │
│       │                                                             │
│       ▼ watches file changes                                        │
│  play_with_prompts.py (background thread)                           │
│       │                                                             │
│       ▼ calls                                                       │
│  generate_prompts_from_state.py                                     │
│       │                                                             │
│       ├──► generate_what_happened_message() (guesses from state!)   │
│       │                                                             │
│       ▼ saves                                                       │
│  prompt_N.json files                                                │
│       │                                                             │
│       ▼ watches files                                               │
│  test_ai_live.py                                                    │
│       │                                                             │
│       ▼ sends to                                                    │
│  LLM (Gemini)                                                       │
│       │                                                             │
│       ▼ ???                                                         │
│  How do responses get back to game? ← 🚨 BROKEN                     │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

קבצים קיימים

קובץ מיקום תפקיד
prompt_manager.py pycatan/ai/ יצירת פרומפטים
llm_client.py pycatan/ai/ תקשורת עם Gemini
state_filter.py pycatan/ai/ סינון state לשחקן
prompt_templates.py pycatan/ai/ תבניות ו-schemas
config.py pycatan/ai/ קונפיגורציה
generate_prompts_from_state.py examples/ai_testing/ יצירת פרומפטים מקובץ
test_ai_live.py examples/ai_testing/ שליחה ל-LLM
play_with_prompts.py examples/ai_testing/ הרצת משחק עם AI
web_viewer.py examples/ai_testing/ צפייה בפרומפטים
user.py pycatan/players/ ממשק User מופשט
human_user.py pycatan/players/ מימוש לשחקן אנושי

🚨 הבעיות שצריך לפתור

בעיה 1: הפרדת אחריות חסרה

❌ GameManager לא צריך לדעת על AI/פרומפטים
❌ יצירת פרומפטים מפוזרת במספר מקומות
❌ אין מקום מרכזי לניהול סוכני AI

בעיה 2: "מה קרה" מבוסס על ניחושים

❌ generate_what_happened_message() מנסה לשחזר מה קרה מתוך state
❌ לא מדויק - חסר מידע על מה באמת התרחש
❌ GameManager יודע בדיוק מה קרה אבל המידע לא מועבר

בעיה 3: אין שליטה על מתי לשלוח פרומפטים

❌ פרומפטים נשלחים על כל שינוי קובץ
❌ אין בדיקה אם כבר ממתינים לתשובה (pending)
❌ צ'אט לא מעורר שליחת פרומפטים

בעיה 4: תשובות לא חוזרות למשחק

❌ אין מסלול ברור לתשובות לחזור ל-GameManager
❌ הכל עובד דרך קבצים - לא real-time

בעיה 5: זיכרון מפוזר

❌ note_to_self נשמר בקבצים, לא במקום מרכזי
❌ אירועים (events) לא נשמרים לכל סוכן
❌ סיכומי צ'אט לא קיימים

🟢 הארכיטקטורה החדשה

תרשים מבנה

┌─────────────────────────────────────────────────────────────────────────┐
│                        ארכיטקטורה חדשה                                 │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌──────────────────┐                    ┌──────────────────────┐       │
│  │                  │                    │                      │       │
│  │   GameManager    │◄──── Actions ──────│     AIManager        │       │
│  │                  │                    │                      │       │
│  │  • Game Loop     │                    │  • Creates Prompts   │       │
│  │  • Rules         │──── Events ───────►│  • Manages Agents    │       │
│  │  • State         │   (notify_all)     │  • Handles Chat      │       │
│  │  • Turn Flow     │                    │  • Tracks Pending    │       │
│  │                  │                    │  • Sends to LLM      │       │
│  └────────┬─────────┘                    └──────────┬───────────┘       │
│           │                                         │                   │
│           │ get_input()                             │                   │
│           ▼                                         ▼                   │
│  ┌──────────────────┐                    ┌──────────────────────┐       │
│  │                  │                    │                      │       │
│  │   HumanUser      │                    │   AIUser (Wrapper)   │       │
│  │   (CLI)          │                    │                      │       │
│  │                  │                    │   • Delegates to     │       │
│  │                  │                    │     AIManager        │       │
│  └──────────────────┘                    │                      │       │
│                                          └──────────┬───────────┘       │
│                                                     │                   │
│                                                     ▼                   │
│                                          ┌──────────────────────┐       │
│                                          │                      │       │
│                                          │     AILogger         │       │
│                                          │                      │       │
│                                          │  • MD logs           │       │
│                                          │  • JSON files        │       │
│                                          │  • Web Viewer        │       │
│                                          │                      │       │
│                                          └──────────────────────┘       │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

עקרונות מנחים

עיקרון פירוט
הפרדת אחריות GameManager לא יודע על AI, AIManager לא יודע על חוקי המשחק
מקום אחד לפרומפטים כל יצירת הפרומפטים דרך AIManager
שליטה מרכזית should_send_prompt() מחליט מתי לשלוח
"מה קרה" מדויק Events מגיעים ישירות מ-GameManager
לוגים תואמים AILogger שומר על אותו פורמט קבצים

📦 פירוט הרכיבים

1. AgentState - מצב סוכן יחיד

@dataclass
class AgentState:
    """
    מצב של סוכן AI יחיד.
    
    כל הזיכרון והמצב של סוכן ספציפי מנוהל כאן.
    """
    
    # === זיהוי ===
    player_name: str                    # שם השחקן ("dudu", "shon")
    player_id: int                      # מספר שחקן (0, 1, 2...)
    player_color: str                   # צבע ("Red", "Blue")
    
    # === סטטוס בקשה ===
    pending_request: bool = False       # האם ממתין לתשובה מ-LLM?
    last_request_time: Optional[float] = None
    
    # === זיכרון פרטי ===
    memory: Optional[str] = None        # note_to_self מתשובה אחרונה
    
    # === סיכומי צ'אט (לעתיד) ===
    chat_summaries: List[str] = field(default_factory=list)
    # דוגמה: ["Turn 5: Dana agreed to trade wood for brick"]
    
    # === אירועים שקרו מאז הפרומפט האחרון ===
    recent_events: List[Dict[str, Any]] = field(default_factory=list)
    # דוגמה: [{"type": "dice_roll", "message": "Rolled 6", "timestamp": 123}]
    
    # === מעקב שינויים ===
    last_state_hash: Optional[str] = None
    last_prompt_time: Optional[float] = None
    
    # === סטטיסטיקות ===
    total_requests: int = 0
    total_tokens_used: int = 0

מיקום: pycatan/ai/agent_state.py


2. AIManager - המנהל המרכזי

class AIManager:
    """
    מנהל מרכזי לכל סוכני ה-AI.
    
    אחריות:
    - ניהול מצב כל הסוכנים
    - יצירת ושליחת פרומפטים
    - קבלת תשובות והמרה לפעולות
    - ניהול צ'אט והיסטוריה
    - החלטה מתי לשלוח פרומפטים
    """
    
    def __init__(
        self,
        game_manager: GameManager,
        config: AIConfig = None,
        session_dir: Path = None
    ):
        """
        Args:
            game_manager: הפניה ל-GameManager לקריאת state
            config: קונפיגורציה (ברירת מחדל אם None)
            session_dir: תיקיית session ללוגים
        """
        self.game_manager = game_manager
        self.config = config or AIConfig()
        
        # רכיבים פנימיים
        self.prompt_manager = PromptManager(self.config)
        self.llm_client = self._create_llm_client()
        self.logger = AILogger(session_dir)
        
        # מצב
        self.agents: Dict[str, AgentState] = {}
        self.chat_history: List[Dict] = []
        self.max_chat_history: int = 20
    
    # === ניהול סוכנים ===
    
    def register_agent(self, player_name: str, player_id: int, player_color: str = ""):
        """רושם סוכן AI חדש"""
        
    def unregister_agent(self, player_name: str):
        """מסיר סוכן (אם שחקן עזב)"""
    
    # === נקודת הכניסה הראשית ===
    
    def process_agent_turn(self, player_name: str) -> Action:
        """
        מעבד תור של סוכן AI.
        
        זו הפונקציה שנקראת מ-AIUser.get_input()
        
        Returns:
            Action: הפעולה שהסוכן בחר לבצע
        """
    
    # === קבלת אירועים ===
    
    def on_game_event(self, event_type: str, message: str, affected_players: List[int] = None):
        """
        נקרא כשמתרחש אירוע במשחק.
        
        נקרא מ-AIUser.notify_game_event() עבור כל סוכן.
        """
    
    def on_chat_message(self, from_player: str, message: str):
        """
        נקרא כשסוכן שולח הודעת צ'אט.
        
        מוסיף להיסטוריה ושולח פרומפטים לסוכנים אחרים.
        """
    
    # === לוגיקת שליחה ===
    
    def should_send_prompt(self, player_name: str) -> bool:
        """
        מחליט אם לשלוח פרומפט לסוכן.
        
        כללים:
        1. אין בקשה תלויה (pending_request == False)
        2. וגם אחד מאלה:
           - מצב המשחק השתנה (state_hash שונה)
           - הגיעה הודעת צ'אט חדשה
        """
    
    # === פונקציות פנימיות ===
    
    def _create_prompt(self, agent: AgentState, is_active_turn: bool) -> Dict:
        """יוצר פרומפט מלא לסוכן"""
    
    def _build_what_happened(self, agent: AgentState) -> str:
        """בונה תיאור 'מה קרה' מהאירועים האחרונים"""
    
    def _get_optimized_state(self) -> Dict:
        """מחזיר state ממוטב לשליחה ל-LLM"""
    
    def _hash_state(self) -> str:
        """יוצר hash של ה-state לזיהוי שינויים"""
    
    def _convert_to_action(self, parsed_response: Dict, player_id: int) -> Action:
        """ממיר תשובת LLM לאובייקט Action"""
    
    def _broadcast_chat(self, from_player: str, message: str):
        """משדר הודעת צ'אט לכל הסוכנים"""

מיקום: pycatan/ai/ai_manager.py


3. AIUser - Wrapper לממשק User

class AIUser(User):
    """
    Wrapper שמחבר בין GameManager ל-AIManager.
    
    GameManager רואה את זה כ-User רגיל ומתקשר איתו
    דרך get_input() ו-notify_game_event().
    """
    
    def __init__(self, name: str, user_id: int, ai_manager: AIManager, color: str = ""):
        """
        Args:
            name: שם השחקן
            user_id: מספר שחקן (0-based)
            ai_manager: הפניה ל-AIManager
            color: צבע השחקן
        """
        super().__init__(name, user_id)
        self.ai_manager = ai_manager
        self.color = color
        
        # רושם את עצמו ב-AIManager
        ai_manager.register_agent(name, user_id, color)
    
    def get_input(
        self, 
        game_state: GameState, 
        prompt_message: str, 
        allowed_actions: Optional[List[str]] = None
    ) -> Action:
        """
        GameManager קורא לזה כשצריך פעולה.
        
        מעביר את הבקשה ל-AIManager לטיפול.
        """
        return self.ai_manager.process_agent_turn(self.name)
    
    def notify_game_event(
        self, 
        event_type: str, 
        message: str, 
        affected_players: Optional[List[int]] = None
    ):
        """
        GameManager קורא לזה כשמתרחש אירוע.
        
        מעביר ל-AIManager לשמירה.
        """
        self.ai_manager.on_game_event(event_type, message, affected_players)
    
    def notify_action(self, action: Action, success: bool, message: str = ""):
        """
        GameManager קורא לזה אחרי ביצוע פעולה.
        """
        # אפשר להוסיף לוגיקה אם צריך
        pass

מיקום: pycatan/ai/ai_user.py


4. AILogger - ניהול לוגים

class AILogger:
    """
    מנהל לוגים עבור מערכת ה-AI.
    
    שומר על תאימות לפורמט הקיים:
    - player_X.md (לוגים קריאים)
    - prompt_N.json (פרומפטים)
    - response_N.json (תשובות)
    
    גם תומך ב-Web Viewer (לעתיד).
    """
    
    def __init__(self, session_dir: Path = None):
        """
        Args:
            session_dir: תיקיית session. אם None, יוצר אוטומטית.
        """
        if session_dir is None:
            session_dir = self._create_session_dir()
        
        self.session_dir = session_dir
        self.session_dir.mkdir(parents=True, exist_ok=True)
        
        self.request_counters: Dict[str, int] = {}
        self.start_time = datetime.now()
        
        # יצירת header ללוגים
        self._init_session()
    
    # === Logging ===
    
    def log_prompt(
        self, 
        player_name: str, 
        prompt: Dict, 
        schema: Dict,
        is_active: bool = True
    ) -> Path:
        """
        שומר פרומפט לקובץ.
        
        יוצר:
        - session/player_name/prompts/prompt_N.json
        - session/player_name/prompts/prompt_N.txt
        
        Returns:
            Path: נתיב לקובץ JSON
        """
    
    def log_response(
        self, 
        player_name: str, 
        response: LLMResponse, 
        parsed: Dict
    ):
        """
        שומר תשובה ומעדכן MD log.
        
        יוצר:
        - session/player_name/responses/response_N.json
        - מעדכן session/player_name.md
        """
    
    def log_chat(self, from_player: str, message: str):
        """שומר הודעת צ'אט ללוג"""
    
    def log_error(self, player_name: str, error: str):
        """שומר שגיאה ללוג"""
    
    # === MD Generation ===
    
    def _init_player_md(self, player_name: str, model: str):
        """יוצר header לקובץ MD של שחקן"""
    
    def _append_request_to_md(
        self, 
        player_name: str, 
        num: int, 
        prompt: Dict
    ):
        """מוסיף request section ל-MD"""
    
    def _append_response_to_md(
        self, 
        player_name: str, 
        num: int, 
        response: LLMResponse, 
        parsed: Dict
    ):
        """מוסיף response section ל-MD"""
    
    # === Utilities ===
    
    def _create_session_dir(self) -> Path:
        """יוצר תיקיית session עם timestamp"""
    
    def get_session_path(self) -> Path:
        """מחזיר נתיב ה-session"""
    
    def save_agent_memories(self, agents: Dict[str, AgentState]):
        """שומר את הזיכרונות של כל הסוכנים לקובץ"""
    
    def save_chat_history(self, chat_history: List[Dict]):
        """שומר היסטוריית צ'אט לקובץ"""

מיקום: pycatan/ai/ai_logger.py


🔄 זרימות עבודה

זרימה 1: תור של סוכן AI

┌────────────────────────────────────────────────────────────────────┐
│                    AGENT TURN FLOW                                 │
├────────────────────────────────────────────────────────────────────┤
│                                                                    │
│  1. GameManager._process_user_action()                            │
│         │                                                          │
│         │ calls                                                    │
│         ▼                                                          │
│  2. AIUser.get_input(game_state, prompt_message, allowed_actions)  │
│         │                                                          │
│         │ delegates to                                             │
│         ▼                                                          │
│  3. AIManager.process_agent_turn(player_name)                     │
│         │                                                          │
│         ├─► Get agent state                                        │
│         │                                                          │
│         ├─► Build "what_happened" from recent_events               │
│         │                                                          │
│         ├─► Create prompt via PromptManager                        │
│         │                                                          │
│         ├─► AILogger.log_prompt() → saves files                   │
│         │                                                          │
│         ├─► Mark agent.pending_request = True                     │
│         │                                                          │
│         ├─► LLMClient.generate(prompt) ──────►  🤖 Gemini         │
│         │                                              │           │
│         │◄─── Response ◄──────────────────────────────┘           │
│         │                                                          │
│         ├─► Mark agent.pending_request = False                    │
│         │                                                          │
│         ├─► Parse response                                        │
│         │                                                          │
│         ├─► AILogger.log_response() → saves files + MD            │
│         │                                                          │
│         ├─► Update agent.memory (note_to_self)                    │
│         │                                                          │
│         ├─► Clear agent.recent_events                             │
│         │                                                          │
│         ├─► Handle chat_message if present                        │
│         │                                                          │
│         ▼                                                          │
│  4. Return Action                                                  │
│         │                                                          │
│         ▼                                                          │
│  5. GameManager.execute_action()                                  │
│         │                                                          │
│         ▼                                                          │
│  6. GameManager._notify_all_users() ──► AIUser.notify_game_event() │
│                                              │                     │
│                                              ▼                     │
│                                         AIManager.on_game_event()  │
│                                              │                     │
│                                              ▼                     │
│                                         Save to agent.recent_events│
│                                                                    │
└────────────────────────────────────────────────────────────────────┘

זרימה 2: הודעת צ'אט

┌────────────────────────────────────────────────────────────────────┐
│                    CHAT MESSAGE FLOW                               │
├────────────────────────────────────────────────────────────────────┤
│                                                                    │
│  1. Agent A response includes chat_message: "Anyone want sheep?"  │
│         │                                                          │
│         ▼                                                          │
│  2. AIManager._broadcast_chat("A", "Anyone want sheep?")          │
│         │                                                          │
│         ├─► Add to chat_history (shared)                          │
│         │                                                          │
│         ├─► AILogger.log_chat()                                   │
│         │                                                          │
│         ▼                                                          │
│  3. For each other agent (B, C, D):                               │
│         │                                                          │
│         ├─► Check should_send_prompt(agent)                       │
│         │       │                                                  │
│         │       ├─► if pending_request: SKIP (don't queue!)       │
│         │       │                                                  │
│         │       └─► if not pending: Send spectator prompt         │
│         │                                                          │
│         └─► Agents see chat in their next prompt                  │
│                                                                    │
└────────────────────────────────────────────────────────────────────┘

זרימה 3: אירוע במשחק (Event)

┌────────────────────────────────────────────────────────────────────┐
│                    GAME EVENT FLOW                                 │
├────────────────────────────────────────────────────────────────────┤
│                                                                    │
│  1. GameManager: Something happens (dice roll, build, trade...)   │
│         │                                                          │
│         ▼                                                          │
│  2. GameManager._notify_all_users("dice_roll", "Rolled 6 (3+3)")  │
│         │                                                          │
│         │ For each user:                                           │
│         ▼                                                          │
│  3. User.notify_game_event("dice_roll", "Rolled 6 (3+3)")         │
│         │                                                          │
│         ├─► HumanUser: prints to console                          │
│         │                                                          │
│         └─► AIUser: delegates to AIManager                        │
│                 │                                                  │
│                 ▼                                                  │
│  4. AIManager.on_game_event("dice_roll", "Rolled 6 (3+3)")        │
│         │                                                          │
│         ▼                                                          │
│  5. For each agent:                                                │
│         │                                                          │
│         └─► agent.recent_events.append({                          │
│                 "type": "dice_roll",                               │
│                 "message": "Rolled 6 (3+3)",                       │
│                 "timestamp": time.time()                           │
│             })                                                     │
│                                                                    │
│  6. When agent's turn comes, recent_events → "what_happened"      │
│                                                                    │
└────────────────────────────────────────────────────────────────────┘

📁 מבנה קבצים חדש

pycatan/
├── ai/
│   ├── __init__.py              # exports
│   │
│   │   # === NEW FILES ===
│   ├── ai_manager.py            # 🆕 AIManager class
│   ├── ai_user.py               # 🆕 AIUser class (wrapper)
│   ├── ai_logger.py             # 🆕 AILogger class
│   ├── agent_state.py           # 🆕 AgentState dataclass
│   │
│   │   # === EXISTING (minimal changes) ===
│   ├── prompt_manager.py        # ✅ Keep as-is
│   ├── llm_client.py            # ✅ Keep as-is
│   ├── state_filter.py          # ✅ Keep as-is
│   ├── prompt_templates.py      # ✅ Keep as-is
│   ├── response_parser.py       # ✅ Keep as-is
│   ├── schemas.py               # ✅ Keep as-is
│   └── config.py                # ✅ Keep as-is
│
├── players/
│   ├── __init__.py
│   ├── user.py                  # ✅ Keep as-is (abstract interface)
│   └── human_user.py            # ✅ Keep as-is
│
├── management/
│   └── game_manager.py          # ✅ Keep as-is (no changes!)
│
examples/
├── ai_testing/
│   │   # === DEPRECATED (will be replaced) ===
│   ├── generate_prompts_from_state.py   # ⚠️ DEPRECATED
│   ├── test_ai_live.py                  # ⚠️ DEPRECATED
│   ├── play_with_prompts.py             # ⚠️ DEPRECATED
│   │
│   │   # === NEW ===
│   ├── play_with_ai.py          # 🆕 New unified entry point
│   │
│   │   # === KEEP ===
│   ├── web_viewer.py            # ✅ Keep (works with new log format)
│   └── my_games/                # ✅ Keep (session storage)

📋 תוכנית מימוש

Phase 1: יצירת רכיבים חדשים (לא שוברים קוד קיים)

Step 1.1: AgentState

📁 Create: pycatan/ai/agent_state.py
⏱️  Estimated: 15 min
📝 Contents:
   - AgentState dataclass
   - Helper methods

Step 1.2: AILogger

📁 Create: pycatan/ai/ai_logger.py
⏱️  Estimated: 45 min
📝 Contents:
   - AILogger class
   - log_prompt() - saves JSON + TXT
   - log_response() - saves JSON + updates MD
   - MD format matching existing player_X.md

Step 1.3: AIManager

📁 Create: pycatan/ai/ai_manager.py
⏱️  Estimated: 1.5 hours
📝 Contents:
   - AIManager class
   - process_agent_turn()
   - on_game_event()
   - on_chat_message()
   - should_send_prompt()
   - _broadcast_chat()

Step 1.4: AIUser

📁 Create: pycatan/ai/ai_user.py
⏱️  Estimated: 30 min
📝 Contents:
   - AIUser class extending User
   - get_input() delegation
   - notify_game_event() delegation

Step 1.5: Update init.py

📁 Modify: pycatan/ai/__init__.py
⏱️  Estimated: 5 min
📝 Add exports for new classes

Phase 2: Entry Point חדש

Step 2.1: Create play_with_ai.py

📁 Create: examples/ai_testing/play_with_ai.py
⏱️  Estimated: 45 min
📝 Contents:
   - Creates GameManager with AIUsers
   - Creates AIManager
   - Runs game loop

Phase 3: בדיקות ווידוא

Step 3.1: Test basic flow

⏱️  Estimated: 30 min
📝 Run play_with_ai.py
   - Verify prompts generated
   - Verify responses logged
   - Verify MD files created
   - Verify actions returned to GameManager

Step 3.2: Test chat flow

⏱️  Estimated: 20 min
📝 Test chat messages
   - Agent sends chat
   - Other agents receive in next prompt
   - Chat history saved

Step 3.3: Test Web Viewer

⏱️  Estimated: 15 min
📝 Verify web_viewer.py works with new log format

Phase 4: Cleanup (אופציונלי)

Step 4.1: Mark deprecated files

📝 Add deprecation notices to:
   - generate_prompts_from_state.py
   - test_ai_live.py
   - play_with_prompts.py

Step 4.2: Update documentation

📝 Update:
   - README
   - AI_ARCHITECTURE.md

⏱️ סיכום זמנים

Phase Task Time
1.1 AgentState 15 min
1.2 AILogger 45 min
1.3 AIManager 1.5 hours
1.4 AIUser 30 min
1.5 init.py 5 min
2.1 play_with_ai.py 45 min
3.x Testing 1 hour
Total ~5 hours

✅ Checklist לפני התחלה

  • מבנה הארכיטקטורה ברור
  • הבנת זרימת העבודה
  • הבנת אחריות כל רכיב
  • מוכן להתחיל Phase 1

🔮 תוספות עתידיות (לא בריפקטור הנוכחי)

  1. סיכום צ'אט אוטומטי - _maybe_summarize_chat()
  2. Web Viewer real-time - WebSocket ל-AILogger
  3. Multi-LLM support - OpenAI, Anthropic
  4. Agent personalities - Different prompts per agent
  5. Replay system - Play back from logs

📚 קבצים קשורים


מוכן להתחיל? 🚀