feat: smooth snake animation + beautiful Avalonia UI overhaul

- Two-timer architecture: game timer + 60fps render timer for smooth interpolation
- Snake body: StreamGeometry path with teal gradient, rounded joins
- Directional head with white eyes and dark pupils
- Food: pulsating glow, highlight, green leaf animation
- Modern dark theme (#0D1117), glassmorphism HUD
- Speed indicator bar, score +N popup
- High score persistence to JSON
- All keyboard shortcuts: Arrows, WASD, Space/P pause, Enter start, R restart, Esc quit
- Window resizable, 640x540 default

New files: AnimationHelper.cs, HighScoreManager.cs, SnakeRenderer.cs
This commit is contained in:
Heller
2026-06-19 10:02:07 +00:00
parent 2e89c6dca3
commit 7d55a08380
9 changed files with 1027 additions and 150 deletions

View File

@@ -1,30 +1,175 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="using:Snake.Avalonia.Views"
x:Class="Snake.Avalonia.Views.GameView"
Background="#0D1117"
Focusable="True">
<Grid RowDefinitions="Auto,*,Auto" Margin="12">
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,8">
<TextBlock x:Name="ScoreText" FontSize="16" />
<TextBlock x:Name="LevelText" FontSize="16" Margin="24,0,0,0" />
<TextBlock x:Name="StatusText" FontSize="16" Margin="24,0,0,0" />
</StackPanel>
<Border Grid.Row="1"
BorderBrush="#666"
BorderThickness="1"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Canvas x:Name="GameCanvas"
Background="#1a1a1a"
Focusable="True" />
<Grid RowDefinitions="Auto,*">
<!-- HUD Panel — glassmorphism top bar -->
<Border Grid.Row="0"
Background="#141C24"
BorderBrush="#1E3A3A"
BorderThickness="0,0,0,1"
Padding="16,10">
<Grid ColumnDefinitions="Auto,*,Auto,Auto,Auto">
<!-- Score -->
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="6">
<TextBlock Text="🍎" FontSize="16" VerticalAlignment="Center" />
<TextBlock x:Name="ScoreText"
FontSize="16"
FontWeight="Bold"
Foreground="#E6EDF3" />
</StackPanel>
<!-- Spacer -->
<TextBlock Grid.Column="1" />
<!-- Level -->
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="6" Margin="0,0,24,0">
<TextBlock Text="⚡" FontSize="14" VerticalAlignment="Center" />
<TextBlock x:Name="LevelText"
FontSize="14"
Foreground="#8B949E"
VerticalAlignment="Center" />
</StackPanel>
<!-- Speed indicator bar -->
<Border Grid.Column="3"
Width="60" Height="16"
Background="#1C2128"
CornerRadius="8"
Margin="0,0,24,0"
VerticalAlignment="Center">
<Border x:Name="SpeedBar"
Background="#0D9488"
CornerRadius="8"
HorizontalAlignment="Left"
Width="15" />
</Border>
<!-- High Score -->
<StackPanel Grid.Column="4" Orientation="Horizontal" Spacing="6">
<TextBlock Text="🏆" FontSize="14" VerticalAlignment="Center" />
<TextBlock x:Name="HighScoreText"
FontSize="14"
Foreground="#FBBF24"
VerticalAlignment="Center" />
</StackPanel>
</Grid>
</Border>
<StackPanel Grid.Row="2"
Orientation="Horizontal"
HorizontalAlignment="Center"
Margin="0,12,0,0">
<Button x:Name="StartButton" Content="Start" Width="100" />
<Button x:Name="RestartButton" Content="Restart" Width="100" Margin="12,0,0,0" />
</StackPanel>
<!-- Game canvas area -->
<Grid Grid.Row="1">
<!-- Snake renderer control -->
<views:SnakeRenderer x:Name="SnakeCanvas"
BoardWidth="20"
BoardHeight="15"
CellSize="28"
Width="560"
Height="420"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<!-- Start screen overlay -->
<Border x:Name="StartOverlay"
Background="#0D1117"
IsVisible="True"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="16">
<TextBlock Text="🐍"
FontSize="48"
HorizontalAlignment="Center" />
<TextBlock Text="SNAKE"
FontSize="36"
FontWeight="Bold"
Foreground="#14B8A6"
HorizontalAlignment="Center" />
<TextBlock Text="Press ENTER or Arrow Keys to Start"
FontSize="16"
Foreground="#8B949E"
HorizontalAlignment="Center"
Margin="0,12,0,0" />
<TextBlock Text="WASD / Arrows = Move · Space = Pause · R = Restart · Esc = Quit"
FontSize="12"
Foreground="#484F58"
HorizontalAlignment="Center" />
</StackPanel>
</Border>
<!-- Pause overlay -->
<Border x:Name="PauseOverlay"
Background="#800D1117"
IsVisible="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="12">
<TextBlock Text="⏸"
FontSize="48"
HorizontalAlignment="Center" />
<TextBlock Text="PAUSED"
FontSize="28"
FontWeight="Bold"
Foreground="#8B949E"
HorizontalAlignment="Center" />
<TextBlock Text="Press Space or P to Resume"
FontSize="14"
Foreground="#484F58"
HorizontalAlignment="Center" />
</StackPanel>
</Border>
<!-- Game Over overlay -->
<Border x:Name="GameOverOverlay"
Background="#B30D1117"
IsVisible="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="12">
<TextBlock Text="💀"
FontSize="48"
HorizontalAlignment="Center" />
<TextBlock Text="GAME OVER"
FontSize="32"
FontWeight="Bold"
Foreground="#EF4444"
HorizontalAlignment="Center" />
<TextBlock x:Name="GameOverScoreText"
FontSize="18"
Foreground="#E6EDF3"
HorizontalAlignment="Center" />
<TextBlock x:Name="GameOverHighScoreText"
FontSize="14"
Foreground="#FBBF24"
HorizontalAlignment="Center" />
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
Spacing="16"
Margin="0,16,0,0">
<Button x:Name="GameOverRestartBtn"
Content="🔄 Restart"
Classes="GameButton"
Width="140" />
<Button x:Name="GameOverQuitBtn"
Content="✕ Quit"
Classes="GameButton GameButtonSecondary"
Width="140" />
</StackPanel>
</StackPanel>
</Border>
<!-- Score popup (will be rendered by SnakeRenderer) -->
</Grid>
</Grid>
</UserControl>