/
Landilf
/
2413_Ryumin_CourseWork
Обзор
Документация
Войти
/
Landilf
/
2413_Ryumin_CourseWork
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
CourseWork/GUI/Views/GameBoardControl.cs
258 строк
10 KB
Landilf
add project
27 янв 2026, 13:57
27 янв 2026, 13:57
753b3e7
Код
Авторство
О чём код?
using Avalonia; using Avalonia.Controls; using Avalonia.Media; using GameCore; using GameCore.Entities; using GameCore.Enums; using GameCore.Resources; using GameCore.Engine; using GUI.ViewModels; using System.Globalization; namespace GUI.Views; /// <summary> /// Отрисовка игрового поля и HUD. /// </summary> public class GameBoardControl : Control { // ReSharper disable all InconsistentNaming #region Поля /// <summary> /// Ширина области просмотра в клетках. /// </summary> private const int VIEWPORT_WIDTH = GameConfig.VIEWPORT_WIDTH; /// <summary> /// Высота области просмотра в клетках. /// </summary> private const int VIEWPORT_HEIGHT = GameConfig.VIEWPORT_HEIGHT; /// <summary> /// Ссылка на вью-модель игры. /// </summary> private GameViewModel? _viewModel; #endregion #region Методы /// <summary> /// Обработчик смены контекста данных. /// </summary> /// <param name="parEventArgs">Аргументы события.</param> protected override void OnDataContextChanged(EventArgs parEventArgs) { base.OnDataContextChanged(parEventArgs); if (DataContext is GameViewModel vm) { _viewModel = vm; _viewModel.RequestRender += InvalidateVisual; } } /// <summary> /// Отрисовывает игровое поле и элементы интерфейса. /// </summary> /// <param name="parContext">Контекст рисования.</param> public override void Render(DrawingContext parContext) { base.Render(parContext); if (_viewModel == null) { return; } var engine = _viewModel.Engine; lock (engine.SyncRoot) { var target = engine.Players.FirstOrDefault(p => p is { IsBot: false, IsAlive: true }) ?? engine.Players.FirstOrDefault(p => !p.IsBot); double playerX = target?.X ?? engine.Width / 2.0; double playerY = target?.Y ?? engine.Height / 2.0; double camLeft = Math.Clamp(playerX - (VIEWPORT_WIDTH / 2.0), -5, engine.Width - VIEWPORT_WIDTH + 5); double camTop = Math.Clamp(playerY - (VIEWPORT_HEIGHT / 2.0), -5, engine.Height - VIEWPORT_HEIGHT + 5); int startX = (int)Math.Floor(camLeft); int startY = (int)Math.Floor(camTop); double subCellX = camLeft - startX; double subCellY = camTop - startY; double cellW = Bounds.Width / VIEWPORT_WIDTH; double cellH = Bounds.Height / VIEWPORT_HEIGHT; using (parContext.PushTransform(Matrix.CreateTranslation(-subCellX * cellW, -subCellY * cellH))) { for (int vy = 0; vy <= VIEWPORT_HEIGHT; vy++) { for (int vx = 0; vx <= VIEWPORT_WIDTH; vx++) { int gx = startX + vx; int gy = startY + vy; var rect = new Rect(vx * cellW, vy * cellH, cellW, cellH); if (gx < 0 || gx >= engine.Width || gy < 0 || gy >= engine.Height) { parContext.DrawRectangle(Brushes.Black, null, rect); continue; } var cell = engine.Map[gx, gy]; // 1. Рисуем захваченную территорию и фон if (cell.OwnerId != null) { var owner = engine.Players.FirstOrDefault(p => p.Id == cell.OwnerId); if (owner != null && Color.TryParse(owner.Color, out var c)) parContext.DrawRectangle(new SolidColorBrush(c, 0.5), null, rect); } else { var color = (gx == 0 || gx == engine.Width - 1 || gy == 0 || gy == engine.Height - 1) ? GameConfig.COLOR_MAP_BORDER : GameConfig.COLOR_MAP_EMPTY; parContext.DrawRectangle(new SolidColorBrush(Color.Parse(color)), null, rect); } // 2. Рисуем хвост if (cell.TailOwnerId != null) { var owner = engine.Players.FirstOrDefault(p => p.Id == cell.TailOwnerId); if (owner != null && Color.TryParse(owner.Color, out var c)) { // Если это та самая клетка, где сейчас находится игрок, рисуем хвост частично if ((int)Math.Round(owner.X) == gx && (int)Math.Round(owner.Y) == gy) { double fracX = owner.X - Math.Floor(owner.X); double fracY = owner.Y - Math.Floor(owner.Y); Rect partialRect = rect; switch (owner.CurrentDirection) { case Direction.Right: partialRect = new Rect(rect.X, rect.Y, rect.Width * fracX, rect.Height); break; case Direction.Left: double w = 1.0 - fracX; partialRect = new Rect(rect.X + rect.Width * (1 - w), rect.Y, rect.Width * w, rect.Height); break; case Direction.Down: partialRect = new Rect(rect.X, rect.Y, rect.Width, rect.Height * fracY); break; case Direction.Up: double h = 1.0 - fracY; partialRect = new Rect(rect.X, rect.Y + rect.Height * (1 - h), rect.Width, rect.Height * h); break; } parContext.DrawRectangle(new SolidColorBrush(c, 1.0), null, partialRect); } else { // В остальных клетках хвост рисуем целиком parContext.DrawRectangle(new SolidColorBrush(c, 1.0), null, rect); } } } } } // 3. Рисуем бонусы foreach (Bonus elBonus in engine.Bonuses) { var rect = new Rect((elBonus.Position.X - startX) * cellW, (elBonus.Position.Y - startY) * cellH, cellW, cellH); var circleBrush = elBonus.Type == BonusType.Speed ? Brushes.Yellow : Brushes.Magenta; parContext.DrawEllipse(circleBrush, new Pen(Brushes.Black, 1), rect.Center, cellW / 3, cellH / 3); string symbol = elBonus.Type == BonusType.Speed ? ">>" : "🛡"; var text = CreateText(symbol, cellH * 0.4, FontWeight.Bold, Brushes.Black); parContext.DrawText(text, new Avalonia.Point(rect.Center.X - text.Width / 2, rect.Center.Y - text.Height / 2)); } // 4. Рисуем игроков foreach (Player elPlayer in engine.Players.Where(x => x.IsAlive)) { var rect = new Rect((elPlayer.X - startX) * cellW, (elPlayer.Y - startY) * cellH, cellW, cellH); if (Color.TryParse(elPlayer.Color, out Color c)) { parContext.DrawRectangle(new SolidColorBrush(c), new Pen(Brushes.Black, 2), rect); if (elPlayer.InvulnerabilityTicks > 0) parContext.DrawEllipse(new SolidColorBrush(Colors.White, 0.4), new Pen(Brushes.White, 1), rect.Center, cellW * 0.7, cellH * 0.7); } } } DrawHud(parContext, engine); } } /// <summary> /// Отрисовывает интерфейс (HUD) поверх поля. /// </summary> /// <param name="parContext">Контекст рисования.</param> /// <param name="parEngine">Движок игры.</param> private void DrawHud(DrawingContext parContext, GameEngine parEngine) { var brushBg = new SolidColorBrush(Color.Parse("#80000000")); var brushText = Brushes.White; var human = parEngine.Players.FirstOrDefault(p => !p.IsBot); if (human != null) { parContext.DrawRectangle(brushBg, null, new Rect(10, 10, 250, 60)); // Игрок parContext.DrawText(CreateText(Strings.HudPlayer, 14, FontWeight.Bold, brushText), new Avalonia.Point(20, 15)); if (Color.TryParse(human.Color, out var c)) parContext.DrawRectangle(new SolidColorBrush(c), null, new Rect(20, 40, 10, 10)); parContext.DrawText(CreateText($"{human.Name} {human.StatusText}", 16, FontWeight.Normal, brushText), new Avalonia.Point(40, 35)); } var topPlayers = parEngine.Players.OrderByDescending(p => p.PercentCaptured).ToList(); parContext.DrawRectangle(brushBg, null, new Rect(Bounds.Width - 260, 10, 250, 30 + topPlayers.Count * 22)); // Лидеры parContext.DrawText(CreateText(Strings.HudScores, 14, FontWeight.Bold, brushText), new Avalonia.Point(Bounds.Width - 150, 15)); for (int i = 0; i < topPlayers.Count; i++) { var p = topPlayers[i]; double y = 40 + i * 22; if (Color.TryParse(p.Color, out var pc)) parContext.DrawRectangle(new SolidColorBrush(pc), null, new Rect(Bounds.Width - 250, y + 5, 10, 10)); parContext.DrawText(CreateText($"{p.Name} {p.StatusText}", 14, FontWeight.Normal, brushText), new Avalonia.Point(Bounds.Width - 235, y)); } if (_viewModel?.IsCountdownActive == true) { parContext.DrawRectangle(new SolidColorBrush(Color.Parse("#40000000")), null, Bounds); var text = CreateText(_viewModel.CountdownValue.ToString(), 120, FontWeight.Bold, Brushes.White); parContext.DrawText(text, new Avalonia.Point((Bounds.Width - text.Width) / 2, (Bounds.Height - text.Height) / 2)); } } /// <summary> /// Создает объект форматированного текста. /// </summary> /// <param name="parText">Текст.</param> /// <param name="parSize">Размер.</param> /// <param name="parWeight">Начертание.</param> /// <param name="parBrush">Кисть.</param> /// <returns>Форматированный текст.</returns> private FormattedText CreateText(string parText, double parSize, FontWeight parWeight, IBrush parBrush) { return new FormattedText(parText, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("Arial", FontStyle.Normal, parWeight), parSize, parBrush); } #endregion }