using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Media; using Avalonia.Threading; using Snake.Core; namespace Snake.Avalonia.Views; public partial class GameView : UserControl { private const int BoardWidth = 20; private const int BoardHeight = 15; private const double CellSize = 20; private const int BaseTickMs = 120; private SnakeGame _game = new(BoardWidth, BoardHeight); private readonly DispatcherTimer _timer; private bool _isRunning; public GameView() { InitializeComponent(); GameCanvas.Width = BoardWidth * CellSize; GameCanvas.Height = BoardHeight * CellSize; _timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(BaseTickMs) }; _timer.Tick += OnTimerTick; StartButton.Click += OnStartClick; RestartButton.Click += OnRestartClick; KeyDown += OnKeyDown; GameCanvas.PointerPressed += (_, _) => Focus(); UpdateHud(); RenderBoard(); } private void OnStartClick(object? sender, RoutedEventArgs e) { if (_isRunning && !_game.IsGameOver) return; if (_game.IsGameOver) ResetGame(); StartGame(); } private void OnRestartClick(object? sender, RoutedEventArgs e) { ResetGame(); StartGame(); } private void ResetGame() { _game = new SnakeGame(BoardWidth, BoardHeight); _isRunning = false; _timer.Stop(); UpdateHud(); RenderBoard(); } private void StartGame() { _isRunning = true; _timer.Interval = TimeSpan.FromMilliseconds(SnakeGame.GetTickIntervalMs(_game.Score, BaseTickMs)); _timer.Start(); UpdateHud(); Focus(); } private void OnTimerTick(object? sender, EventArgs e) { if (!_isRunning || _game.IsGameOver) return; _game.Tick(); if (_game.IsGameOver) { _isRunning = false; _timer.Stop(); } else { var interval = SnakeGame.GetTickIntervalMs(_game.Score, BaseTickMs); if (_timer.Interval != TimeSpan.FromMilliseconds(interval)) _timer.Interval = TimeSpan.FromMilliseconds(interval); } UpdateHud(); RenderBoard(); } private void OnKeyDown(object? sender, KeyEventArgs e) { if (!_isRunning && !_game.IsGameOver && e.Key is Key.Up or Key.Down or Key.Left or Key.Right) { StartGame(); } switch (e.Key) { case Key.Up: _game.SetDirection(Direction.Up); break; case Key.Down: _game.SetDirection(Direction.Down); break; case Key.Left: _game.SetDirection(Direction.Left); break; case Key.Right: _game.SetDirection(Direction.Right); break; } } private void UpdateHud() { ScoreText.Text = $"Score: {_game.Score}"; LevelText.Text = $"Level: {_game.Level}"; StatusText.Text = _game.IsGameOver ? "Game Over!" : _isRunning ? "Playing" : "Press Start or an arrow key"; } private void RenderBoard() { GameCanvas.Children.Clear(); var snakeCells = _game.Snake.Segments.ToHashSet(); var head = _game.Snake.Head; for (var y = 0; y < BoardHeight; y++) { for (var x = 0; x < BoardWidth; x++) { var position = new Position(x, y); if (!snakeCells.Contains(position)) continue; var rect = new Rectangle { Width = CellSize - 1, Height = CellSize - 1, Fill = position == head ? Brushes.LimeGreen : Brushes.Green }; Canvas.SetLeft(rect, x * CellSize); Canvas.SetTop(rect, y * CellSize); GameCanvas.Children.Add(rect); } } var food = _game.Food.Position; var foodRect = new Rectangle { Width = CellSize - 1, Height = CellSize - 1, Fill = Brushes.Red }; Canvas.SetLeft(foodRect, food.X * CellSize); Canvas.SetTop(foodRect, food.Y * CellSize); GameCanvas.Children.Add(foodRect); } }