Add player names to snake display

Show human-readable player names instead of player IDs in both desktop
and web clients. Player names are now stored in the Snake model and
synchronized across all clients.

Changes:
- Added player_name field to Snake model
- Updated create_snake() to accept player_name parameter
- Desktop client shows "YOU:" or "PlayerName:"
- Web client shows "You (Name)" or "Name"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Vladyslav Doloman
2025-10-04 17:21:53 +03:00
parent b28d78575f
commit 97d6df1896
6 changed files with 16 additions and 8 deletions

View File

@@ -6,7 +6,10 @@
"Bash(python:*)",
"Bash(pip install:*)",
"Bash(git log:*)",
"Bash(git add:*)"
"Bash(git add:*)",
"Bash(git commit -m \"$(cat <<''EOF''\nImplement stuck snake mechanics, persistent colors, and length display\n\nMajor gameplay changes:\n- Snakes no longer die from collisions\n- When blocked, snakes get \"stuck\" - head stays in place, tail shrinks by 1 per tick\n- Snakes auto-unstick when obstacle clears (other snakes move/shrink away)\n- Minimum snake length is 1 (head-only)\n- Game runs continuously without rounds or game-over state\n\nColor system:\n- Each player gets a persistent color for their entire connection\n- Colors assigned on join, rotate through available colors\n- Color follows player even after disconnect/reconnect\n- Works for both desktop and web clients\n\nDisplay improvements:\n- Show snake length instead of score\n- Length accurately reflects current snake size\n- Updates in real-time as snakes grow/shrink\n\nServer fixes:\n- Fixed HTTP server initialization issues\n- Changed default host to 0.0.0.0 for network multiplayer\n- Improved file serving with proper routing\n\nTesting:\n- Updated all collision tests for stuck mechanics\n- Added tests for stuck/unstick behavior\n- Added tests for color persistence\n- All 12 tests passing\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude <noreply@anthropic.com>\nEOF\n)\")",
"Bash(git push:*)",
"Bash(git commit:*)"
],
"deny": [],
"ask": []

View File

@@ -109,7 +109,7 @@ class Renderer:
color = COLOR_SNAKES[snake.color_index % len(COLOR_SNAKES)]
# Prepare length text
prefix = "YOU: " if snake.player_id == player_id else f"Player: "
prefix = "YOU: " if snake.player_id == player_id else f"{snake.player_name}: "
status = "" if snake.alive else " (DEAD)"
text = f"{prefix}Length {len(snake.body)}{status}"

View File

@@ -21,12 +21,13 @@ class GameLogic:
"""Initialize game logic."""
self.state = GameState()
def create_snake(self, player_id: str, color_index: int = 0) -> Snake:
def create_snake(self, player_id: str, color_index: int = 0, player_name: str = "") -> Snake:
"""Create a new snake for a player.
Args:
player_id: Unique identifier for the player
color_index: Index in COLOR_SNAKES array for this snake's color
player_name: Human-readable name for the player
Returns:
New Snake instance
@@ -41,7 +42,7 @@ class GameLogic:
for i in range(INITIAL_SNAKE_LENGTH)
]
snake = Snake(player_id=player_id, body=body, direction=RIGHT, color_index=color_index)
snake = Snake(player_id=player_id, body=body, direction=RIGHT, color_index=color_index, player_name=player_name)
return snake
def spawn_food(self) -> Food:

View File

@@ -169,7 +169,7 @@ class GameServer:
# Add snake to game if game is running
if self.game_logic.state.game_running:
color_index = self.player_colors[player_id]
snake = self.game_logic.create_snake(player_id, color_index)
snake = self.game_logic.create_snake(player_id, color_index, player_name)
self.game_logic.state.snakes.append(snake)
print(f"Player {player_name} ({player_id}) joined via {client_type.value}. Total players: {len(self.clients)}")
@@ -193,7 +193,8 @@ class GameServer:
self.game_logic.state.snakes = []
for player_id in self.clients.keys():
color_index = self.player_colors.get(player_id, 0)
snake = self.game_logic.create_snake(player_id, color_index)
player_name = self.player_names.get(player_id, f"Player_{player_id[:8]}")
snake = self.game_logic.create_snake(player_id, color_index, player_name)
self.game_logic.state.snakes.append(snake)
# Spawn initial food

View File

@@ -34,6 +34,7 @@ class Snake:
score: int = 0
stuck: bool = False # True when snake is blocked and shrinking
color_index: int = 0 # Index in COLOR_SNAKES array for persistent color
player_name: str = "" # Human-readable player name
def get_head(self) -> Position:
"""Get the head position of the snake."""
@@ -49,6 +50,7 @@ class Snake:
"score": self.score,
"stuck": self.stuck,
"color_index": self.color_index,
"player_name": self.player_name,
}
@classmethod
@@ -61,6 +63,7 @@ class Snake:
snake.score = data["score"]
snake.stuck = data.get("stuck", False) # Default to False for backward compatibility
snake.color_index = data.get("color_index", 0) # Default to 0 for backward compatibility
snake.player_name = data.get("player_name", "") # Default to empty string for backward compatibility
return snake

View File

@@ -322,8 +322,8 @@ class GameClient {
const nameSpan = document.createElement('span');
nameSpan.className = 'player-name';
nameSpan.textContent = snake.player_id === this.playerId ?
`You (${snake.player_id.substring(0, 8)})` :
snake.player_id.substring(0, 8);
`You (${snake.player_name})` :
snake.player_name;
const scoreSpan = document.createElement('span');
scoreSpan.className = 'player-score';