Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions NETWORK_SYNC_FIX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# 🔧 תיקון סינכרון רשת - Kung Fu Chess

## 🎯 הבעיה שנפתרה

הבעיה המקורית הייתה שהשרת לא ניהל מצב משחק מרכזי, מה שגרם לחוסר סינכרון בין הלקוחות:
- ✅ כל לקוח ניהל את המשחק שלו בנפרד
- ✅ השרת רק העביר הודעות בין לקוחות
- ✅ לא היה מצב משחק אמיתי מרכזי
- ✅ מהלכים לא הוחלו בצורה עקבית

## 🛠️ הפתרון שיושם

### 1. מנוע משחק מרכזי בשרת
```python
class ServerGameEngine:
"""Server-side game engine that manages the authoritative game state."""
```
- השרת עכשיו מנהל את מצב המשחק האמיתי
- כל מהלך מעובד בשרת ומוחל על המצב המרכזי
- השרת שולח עדכונים לכל הלקוחות

### 2. סינכרון תקופתי
```python
async def _periodic_sync(self):
"""Periodically sync game state to all clients."""
```
- השרת שולח עדכוני מצב כל 100ms
- כל הלקוחות מקבלים את אותו מצב משחק
- סינכרון אמיתי בזמן אמת

### 3. עיבוד מהלכים מרכזי
```python
def process_move(self, player_color: str, from_pos: tuple, to_pos: tuple, piece_id: str = None):
```
- כל מהלך מעובד בשרת
- ולידציה מרכזית
- עדכון מצב אמיתי
- שידור לכל הלקוחות

## 🚀 איך להפעיל

### שלב 1: הפעל את השרת החדש
```bash
cd server
python websocket_server.py
```

### שלב 2: בדוק עם הטסט
```bash
python test_server_sync.py
```

### שלב 3: הפעל שני לקוחות
```bash
# Terminal 1
cd client
python main.py

# Terminal 2
cd client
python main.py
```

## 🔍 מה השתנה

### בשרת (`server/websocket_server.py`):
1. ✅ **ServerGameEngine** - מנוע משחק מרכזי
2. ✅ **process_move()** - עיבוד מהלכים מרכזי
3. ✅ **periodic_sync()** - סינכרון תקופתי
4. ✅ **authoritative_game_state** - מצב משחק מוסמך

### בלקוח (`client/network_game_manager.py`):
1. ✅ **_apply_authoritative_game_state()** - קבלת מצב מהשרת
2. ✅ **_on_periodic_sync_received()** - טיפול בסינכרון תקופתי
3. ✅ **_on_game_state_update_received()** - טיפול בעדכוני מהלכים
4. ✅ הפחתת שליחת מצב מקומי - השרת מנהל הכל

### בלקוח WebSocket (`client/websocket_client.py`):
1. ✅ handlers חדשים למסרים מהשרת
2. ✅ תמיכה ב-`authoritative_game_state`
3. ✅ תמיכה ב-`periodic_sync`
4. ✅ תמיכה ב-`game_state_update`

## 🎮 תוצאה

עכשיו כשאת מפעילה שני לקוחות:

1. **השרת מנהל את המשחק** - יש מצב משחק אמיתי אחד
2. **מהלכים מסונכרנים** - כל מהלך מעובד בשרת ונשלח לכולם
3. **עדכונים בזמן אמת** - כל 100ms כל הלקוחות מקבלים עדכון
4. **לכידות מסונכרנת** - כשכלי נלכד, כל הלקוחות רואים את זה
5. **מצבי כלים מסונכרנים** - cooldown, תנועה, וכו'

## 🧪 בדיקה

הרץ את `test_server_sync.py` כדי לוודא שהכל עובד:
```bash
python test_server_sync.py
```

הטסט יצור שני לקוחות, יחבר אותם לחדר, וינסה מהלכים כדי לוודא סינכרון.

## 🎯 סיכום

**הבעיה נפתרה!** 🎉

עכשיו יש לך משחק רשת אמיתי עם:
- ✅ מצב משחק מרכזי בשרת
- ✅ סינכרון מלא בין לקוחות
- ✅ מהלכים מעובדים מרכזית
- ✅ עדכונים בזמן אמת
- ✅ לכידות מסונכרנות
- ✅ משחק Kung Fu Chess אמיתי ברשת!

כל מה שצריך לעשות זה להפעיל את השרת ושני לקוחות, ולהתחיל לשחק! 🥋♟️
102 changes: 91 additions & 11 deletions client/network_game_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,14 @@ def _setup_websocket_handlers(self):
self.websocket_client.on_player_left = self._on_player_left
self.websocket_client.on_game_state_received = self._on_game_state_received

# Setup periodic game state sync
# Add handlers for new server messages
self.websocket_client.on_authoritative_game_state = self._on_authoritative_game_state_received
self.websocket_client.on_periodic_sync = self._on_periodic_sync_received
self.websocket_client.on_game_state_update = self._on_game_state_update_received

# Setup periodic game state sync (reduced frequency since server now manages state)
self._sync_timer = 0
self._sync_interval = 200 # Sync every 200ms for better consistency
self._sync_interval = 1000 # Reduced to every 1 second since server manages state
self.websocket_client.on_error = self._on_error

def start_network_game(self, mode: str = "create", room_id: str = None):
Expand Down Expand Up @@ -102,26 +107,34 @@ def handle_event(self, event_type: str, data: dict):
return

if event_type == MOVE_DONE:
# Send move to other players
# Send move to server for authoritative processing
command = data.get('command')
if command and hasattr(command, 'params') and len(command.params) >= 2:
from_pos = self._convert_position_to_notation(command.params[0])
to_pos = self._convert_position_to_notation(command.params[1])
piece = command.piece_id[:2] if command.piece_id else ''
from_pos = command.params[0] # Keep as coordinates
to_pos = command.params[1] # Keep as coordinates
piece_id = command.piece_id if hasattr(command, 'piece_id') else ''

# Send move to server for authoritative processing
self.websocket_client.make_move(from_pos, to_pos, piece_id)
logger.info(f"Sent move to server: {from_pos} to {to_pos} (piece: {piece_id})")

# Don't process the move locally - wait for server response
# The server will send back the authoritative game state

self.websocket_client.make_move(from_pos, to_pos, piece)
logger.info(f"Sent move to server: {from_pos} to {to_pos} (piece: {piece})")
else:
logger.warning("Invalid move data received from game")

# Periodically sync full game state (less frequently)
# Since server now manages authoritative state, we don't need to send full state
# The server will send us updates via periodic sync
# Only send state occasionally for debugging/backup purposes
current_time = self.game.game_time_ms()
if not hasattr(self, '_last_sync_time'):
self._last_sync_time = 0
if (current_time - self._last_sync_time > 1000 and # Only sync every 1 second
if (current_time - self._last_sync_time > 5000 and # Only sync every 5 seconds for backup
hasattr(self, 'room_id') and self.room_id): # And only if in a room
self._last_sync_time = current_time
self._send_full_game_state()
# Don't send full state anymore - server is authoritative
# self._send_full_game_state()

def _send_full_game_state(self):
"""Send complete game state to synchronize everything."""
Expand Down Expand Up @@ -184,6 +197,73 @@ def _send_full_game_state(self):

def _on_game_state_received(self, state_data: dict):
"""Apply received game state to synchronize everything."""
# This is now handled by the new authoritative handlers
# Keep for backward compatibility but redirect to new handler
self._apply_authoritative_game_state(state_data)

def _on_authoritative_game_state_received(self, message_data: dict):
"""Handle authoritative game state from server."""
game_state = message_data.get('game_state', {})
self._apply_authoritative_game_state(game_state)

def _on_periodic_sync_received(self, message_data: dict):
"""Handle periodic sync from server."""
game_state = message_data.get('game_state', {})
self._apply_authoritative_game_state(game_state)

def _on_game_state_update_received(self, message_data: dict):
"""Handle game state update after a move."""
game_state = message_data.get('game_state', {})
move_data = message_data.get('move', {})

logger.info(f"Received move update: {move_data}")
self._apply_authoritative_game_state(game_state)

def _apply_authoritative_game_state(self, state_data: dict):
"""Apply the authoritative game state from server."""
try:
# Check if data is wrapped in 'state' key (from server)
if 'pieces' not in state_data and 'state' in state_data:
state_data = state_data['state']

pieces_state = state_data.get('pieces', {})

# Apply piece positions and states from server
for piece_id, piece_data in pieces_state.items():
if piece_id in self.game.pieces:
piece = self.game.pieces[piece_id]
server_pos = piece_data.get('position', piece.current_state.physics.current_board_cell)
server_state = piece_data.get('state', 'idle')
is_moving = piece_data.get('is_moving', False)
target_pos = piece_data.get('target_position', server_pos)

# Only update if position actually changed
current_pos = piece.current_state.physics.current_board_cell
if current_pos != server_pos:
piece.current_state.physics.current_board_cell = server_pos
piece.current_state.physics.target_board_cell = target_pos

# Handle captured pieces (moved off board)
if server_pos == (-1, -1):
logger.info(f"Piece {piece_id} was captured")
# Hide captured piece
piece.current_state.physics.current_board_cell = (-1, -1)

logger.debug(f"Updated piece {piece_id} position: {current_pos} -> {server_pos}")

# Update game statistics if provided
game_stats = state_data.get('game_stats', {})
if game_stats and hasattr(self.game, 'score_manager'):
scores = game_stats.get('scores', {})
if scores:
# Update local score manager with server data
pass # Score manager updates handled by server

except Exception as e:
logger.error(f"Failed to apply authoritative game state: {e}")

def _on_old_game_state_received(self, state_data: dict):
"""OLD: Apply received game state to synchronize everything."""
try:
# Check if data is wrapped in 'state' key (from server)
if 'pieces' not in state_data and 'state' in state_data:
Expand Down
21 changes: 21 additions & 0 deletions client/websocket_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ def __init__(self, host: str = "localhost", port: int = 8765):
self.on_error: Optional[Callable[[str]]] = None
self.on_game_state_received: Optional[Callable[[Dict]]] = None

# New authoritative server callbacks
self.on_authoritative_game_state: Optional[Callable[[Dict]]] = None
self.on_periodic_sync: Optional[Callable[[Dict]]] = None
self.on_game_state_update: Optional[Callable[[Dict]]] = None

# Background thread for websocket communication
self.websocket_thread: Optional[threading.Thread] = None
self.should_stop = False
Expand Down Expand Up @@ -218,6 +223,22 @@ def _handle_message(self, data: Dict[str, Any]):
logger.info(f"Game state received with {len(state_data.get('pieces', []))} pieces")
if self.on_game_state_received:
self.on_game_state_received(state_data)

elif message_type == 'authoritative_game_state':
logger.debug("Authoritative game state received from server")
if hasattr(self, 'on_authoritative_game_state') and self.on_authoritative_game_state:
self.on_authoritative_game_state(data)

elif message_type == 'periodic_sync':
logger.debug("Periodic sync received from server")
if hasattr(self, 'on_periodic_sync') and self.on_periodic_sync:
self.on_periodic_sync(data)

elif message_type == 'game_state_update':
move_data = data.get('move', {})
logger.info(f"Game state update received for move: {move_data.get('from')} to {move_data.get('to')}")
if hasattr(self, 'on_game_state_update') and self.on_game_state_update:
self.on_game_state_update(data)

elif message_type == 'pong':
logger.debug("Received pong from server")
Expand Down
Loading