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:
@@ -6,7 +6,10 @@
|
|||||||
"Bash(python:*)",
|
"Bash(python:*)",
|
||||||
"Bash(pip install:*)",
|
"Bash(pip install:*)",
|
||||||
"Bash(git log:*)",
|
"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": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ class Renderer:
|
|||||||
color = COLOR_SNAKES[snake.color_index % len(COLOR_SNAKES)]
|
color = COLOR_SNAKES[snake.color_index % len(COLOR_SNAKES)]
|
||||||
|
|
||||||
# Prepare length text
|
# 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)"
|
status = "" if snake.alive else " (DEAD)"
|
||||||
text = f"{prefix}Length {len(snake.body)}{status}"
|
text = f"{prefix}Length {len(snake.body)}{status}"
|
||||||
|
|
||||||
|
|||||||
@@ -21,12 +21,13 @@ class GameLogic:
|
|||||||
"""Initialize game logic."""
|
"""Initialize game logic."""
|
||||||
self.state = GameState()
|
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.
|
"""Create a new snake for a player.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
player_id: Unique identifier for the player
|
player_id: Unique identifier for the player
|
||||||
color_index: Index in COLOR_SNAKES array for this snake's color
|
color_index: Index in COLOR_SNAKES array for this snake's color
|
||||||
|
player_name: Human-readable name for the player
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
New Snake instance
|
New Snake instance
|
||||||
@@ -41,7 +42,7 @@ class GameLogic:
|
|||||||
for i in range(INITIAL_SNAKE_LENGTH)
|
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
|
return snake
|
||||||
|
|
||||||
def spawn_food(self) -> Food:
|
def spawn_food(self) -> Food:
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ class GameServer:
|
|||||||
# Add snake to game if game is running
|
# Add snake to game if game is running
|
||||||
if self.game_logic.state.game_running:
|
if self.game_logic.state.game_running:
|
||||||
color_index = self.player_colors[player_id]
|
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)
|
self.game_logic.state.snakes.append(snake)
|
||||||
|
|
||||||
print(f"Player {player_name} ({player_id}) joined via {client_type.value}. Total players: {len(self.clients)}")
|
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 = []
|
self.game_logic.state.snakes = []
|
||||||
for player_id in self.clients.keys():
|
for player_id in self.clients.keys():
|
||||||
color_index = self.player_colors.get(player_id, 0)
|
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)
|
self.game_logic.state.snakes.append(snake)
|
||||||
|
|
||||||
# Spawn initial food
|
# Spawn initial food
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class Snake:
|
|||||||
score: int = 0
|
score: int = 0
|
||||||
stuck: bool = False # True when snake is blocked and shrinking
|
stuck: bool = False # True when snake is blocked and shrinking
|
||||||
color_index: int = 0 # Index in COLOR_SNAKES array for persistent color
|
color_index: int = 0 # Index in COLOR_SNAKES array for persistent color
|
||||||
|
player_name: str = "" # Human-readable player name
|
||||||
|
|
||||||
def get_head(self) -> Position:
|
def get_head(self) -> Position:
|
||||||
"""Get the head position of the snake."""
|
"""Get the head position of the snake."""
|
||||||
@@ -49,6 +50,7 @@ class Snake:
|
|||||||
"score": self.score,
|
"score": self.score,
|
||||||
"stuck": self.stuck,
|
"stuck": self.stuck,
|
||||||
"color_index": self.color_index,
|
"color_index": self.color_index,
|
||||||
|
"player_name": self.player_name,
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -61,6 +63,7 @@ class Snake:
|
|||||||
snake.score = data["score"]
|
snake.score = data["score"]
|
||||||
snake.stuck = data.get("stuck", False) # Default to False for backward compatibility
|
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.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
|
return snake
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -322,8 +322,8 @@ class GameClient {
|
|||||||
const nameSpan = document.createElement('span');
|
const nameSpan = document.createElement('span');
|
||||||
nameSpan.className = 'player-name';
|
nameSpan.className = 'player-name';
|
||||||
nameSpan.textContent = snake.player_id === this.playerId ?
|
nameSpan.textContent = snake.player_id === this.playerId ?
|
||||||
`You (${snake.player_id.substring(0, 8)})` :
|
`You (${snake.player_name})` :
|
||||||
snake.player_id.substring(0, 8);
|
snake.player_name;
|
||||||
|
|
||||||
const scoreSpan = document.createElement('span');
|
const scoreSpan = document.createElement('span');
|
||||||
scoreSpan.className = 'player-score';
|
scoreSpan.className = 'player-score';
|
||||||
|
|||||||
Reference in New Issue
Block a user