проблема с отображением игрового поля

решил написать игру 2048 и получаю следующее при запуске:

[тут как бы должно быть игровое поле1

сделал игру в виде нескольких файлов с использованием Avalonia файл MainWindow.axaml.cs

using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace Game
{
    public partial class MainWindow : Window
    {
        private const int Size = 4;
        private int[,] _field;
        private int _score;
        private Random _random;

        private TextBlock? _scoreTextBlock;
        public ObservableCollection<GameTile> GameTiles { get; set; }

        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
            _random = new Random();
            _field = new int[Size, Size];
            _scoreTextBlock = this.FindControl<TextBlock>("ScoreTextBlock");
            GameTiles = new ObservableCollection<GameTile>();
            StartGame();
        }

        private void InitializeComponent()
        {
            AvaloniaXamlLoader.Load(this);
#pragma warning disable CS8622 // Допустимость значений NULL для ссылочных типов в типе параметра не соответствует целевому объекту делегирования (возможно, из-за атрибутов допустимости значений NULL).
            this.KeyDown += OnKeyDown;
#pragma warning restore CS8622 // Допустимость значений NULL для ссылочных типов в типе параметра не соответствует целевому объекту делегирования (возможно, из-за атрибутов допустимости значений NULL).
        }

        private void StartGame()
        {
            _score = 0;
            ClearField();
            GameTiles.Clear();

            for (int i = 0; i < Size; i++)
            {
                for (int j = 0; j < Size; j++)
                {
                    GameTiles.Add(new GameTile { Value = _field[i, j] });
                }
            }

            AddNewNumber();
            AddNewNumber();
            UpdateUI();
        }

        private void ClearField()
        {
            for (int i = 0; i < Size; i++)
                for (int j = 0; j < Size; j++)
                    _field[i, j] = 0;
        }

        private void AddNewNumber()
        {
            List<(int x, int y)> emptyCells = new List<(int x, int y)>();
            for (int i = 0; i < Size; i++)
            {
                for (int j = 0; j < Size; j++)
                {
                    if (_field[i, j] == 0)
                    {
                        emptyCells.Add((i, j));
                    }
                }
            }

            if (emptyCells.Count > 0)
            {
                var (x, y) = emptyCells[_random.Next(emptyCells.Count)];
                _field[x, y] = _random.NextDouble() < 0.9 ? 2 : 4;
            }
        }

        private bool CanMove()
        {
            for (int i = 0; i < Size; i++)
            {
                for (int j = 0; j < Size; j++)
                {
                    if (_field[i, j] == 0) return true;

                    if (i < Size - 1 && _field[i, j] == _field[i + 1, j]) return true;
                    if (j < Size - 1 && _field[i, j] == _field[i, j + 1]) return true;
                }
            }

            return false;
        }

        private void UpdateUI()
        {
            for (int i = 0; i < Size; i++)
            {
                for (int j = 0; j < Size; j++)
                {
                    GameTiles[i * Size + j].Value = _field[i, j];
                }
            }
#pragma warning disable CS8602 // Разыменование вероятной пустой ссылки.
            _scoreTextBlock.Text = $"Score: {_score}";
#pragma warning restore CS8602 // Разыменование вероятной пустой ссылки.
        }

        private void OnKeyDown(object sender, KeyEventArgs e)
        {
            switch (e.Key)
            {
                case Key.Left:
                    MoveLeft();
                    break;
                case Key.Right:
                    MoveRight();
                    break;
                case Key.Up:
                    MoveUp();
                    break;
                case Key.Down:
                    MoveDown();
                    break;
            }

            if (CanMove())
            {
                AddNewNumber();
                UpdateUI();
            }
            else
            {
                // Game Over Logic
            }
        }

        private void MoveLeft()
        {
            bool needToAddNewNumber = false;
            for (int i = 0; i < Size; i++)
            {
                for (int j = 1; j < Size; j++)
                {
                    if (_field[i, j] == 0) continue;
                    int k = j;
                    while (k > 0 && _field[i, k - 1] == 0)
                    {
                        _field[i, k - 1] = _field[i, k];
                        _field[i, k] = 0;
                        k--;
                        needToAddNewNumber = true;
                    }
                    if (k > 0 && _field[i, k - 1] == _field[i, k])
                    {
                        _field[i, k - 1] *= 2;
                        _field[i, k] = 0;
                        _score += _field[i, k - 1];
                        needToAddNewNumber = true;
                    }
                }
            }

            if (needToAddNewNumber) AddNewNumber();
        }

        private void MoveRight()
        {
            bool needToAddNewNumber = false;
            for (int i = 0; i < Size; i++)
            {
                for (int j = Size - 2; j >= 0; j--)
                {
                    if (_field[i, j] == 0) continue;
                    int k = j;
                    while (k < Size - 1 && _field[i, k + 1] == 0)
                    {
                        _field[i, k + 1] = _field[i, k];
                        _field[i, k] = 0;
                        k++;
                        needToAddNewNumber = true;
                    }
                    if (k < Size - 1 && _field[i, k + 1] == _field[i, k])
                    {
                        _field[i, k + 1] *= 2;
                        _field[i, k] = 0;
                        _score += _field[i, k + 1];
                        needToAddNewNumber = true;
                    }
                }
            }

            if (needToAddNewNumber) AddNewNumber();
        }

        private void MoveUp()
        {
            bool needToAddNewNumber = false;
            for (int j = 0; j < Size; j++)
            {
                for (int i = 1; i < Size; i++)
                {
                    if (_field[i, j] == 0) continue;
                    int k = i;
                    while (k > 0 && _field[k - 1, j] == 0)
                    {
                        _field[k - 1, j] = _field[k, j];
                        _field[k, j] = 0;
                        k--;
                        needToAddNewNumber = true;
                    }
                    if (k > 0 && _field[k - 1, j] == _field[k, j])
                    {
                        _field[k - 1, j] *= 2;
                        _field[k, j] = 0;
                        _score += _field[k - 1, j];
                        needToAddNewNumber = true;
                    }
                }
            }

            if (needToAddNewNumber) AddNewNumber();
        }

        private void MoveDown()
        {
            bool needToAddNewNumber = false;
            for (int j = 0; j < Size; j++)
            {
                for (int i = Size - 2; i >= 0; i--)
                {
                    if (_field[i, j] == 0) continue;
                    int k = i;
                    while (k < Size - 1 && _field[k + 1, j] == 0)
                    {
                        _field[k + 1, j] = _field[k, j];
                        _field[k, j] = 0;
                        k++;
                        needToAddNewNumber = true;
                    }
                    if (k < Size - 1 && _field[k + 1, j] == _field[k, j])
                    {
                        _field[k + 1, j] *= 2;
                        _field[k, j] = 0;
                        _score += _field[k + 1, j];
                        needToAddNewNumber = true;
                    }
                }
            }

            if (needToAddNewNumber) AddNewNumber();
        }
    }
}

файл GameTiles.cs

using System.ComponentModel;

namespace Game
{
    public class GameTile : INotifyPropertyChanged
    {
        private int _value;

        public int Value
        {
            get => _value;
            set
            {
                if (_value != value)
                {
                    _value = value;
                    OnPropertyChanged(nameof(Value));
                    OnPropertyChanged(nameof(BackgroundColor));
                }
            }
        }

        public string BackgroundColor => Value switch
        {
            2 => "#eee4da",
            4 => "#ede0c8",
            8 => "#f2b179",
            16 => "#f59563",
            32 => "#f67c5f",
            64 => "#f65e3b",
            128 => "#edcf72",
            256 => "#edcc61",
            512 => "#edc850",
            1024 => "#edc53f",
            2048 => "#edc22e",
            4096 => "#3c3a32",
            8192 => "#3c3a33",
            16384 => "#3c3a34",
            32768 => "#3c3a35",
            65536 => "#3c3a36",
            131072 => "#3c3a37",
            262144 => "#3c3a38",
            524288 => "#3c3a39",
            1048576 => "#3c3a3a",
            2097152 => "#3c3a3b",
            4194304 => "#3c3a3c",
            8388608 => "#3c3a3d",
            16777216 => "#3c3a3e",
            33554432 => "#3c3a3f",
            67108864 => "#3c3a40",
            _ => "#808080", // Фон для значений выше 67108864 или для пустых плиток
        };

        public event PropertyChangedEventHandler? PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName) =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

и файл MainWindow.axaml

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Game"
        x:Class="Game.MainWindow"
        Title="2048 Game">
    <Grid RowDefinitions="Auto, *" ColumnDefinitions="*">
        <TextBlock x:Name="ScoreTextBlock" 
                   Grid.Row="0" 
                   HorizontalAlignment="Center" 
                   Margin="10" 
                   FontSize="20"/>

        <ItemsControl x:Name="GameBoard" 
                      Grid.Row="1" 
                      ItemsSource="{Binding GameTiles}"
                      x:DataType="local:MainWindow">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <UniformGrid Rows="4" Columns="4"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate x:DataType="local:GameTile">
                    <Border Background="{Binding BackgroundColor}" Margin="5">
                        <TextBlock Text="{Binding Value}" 
                                   HorizontalAlignment="Center" 
                                   VerticalAlignment="Center" 
                                   FontSize="24"/>
                    </Border>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>

вопрос мой прост, знает может кто как решить проблему(исправление кода)?


Ответы (1 шт):

Автор решения: Cost

В данном случае проблема заключается в том, что инициализация свойства GameTiles происходит после вызова метода InitializeComponent. Привязка к этому свойству не произошла. Перенесите инициализацию до вызова InitializeComponent и всё заработает:

public MainWindow()
{
    GameTiles = new ObservableCollection<GameTile>();
    InitializeComponent();
    DataContext = this;
    _random = new Random();
    _field = new int[Size, Size];        
    _scoreTextBlock = this.FindControl<TextBlock>("ScoreTextBlock");
    StartGame();
}
→ Ссылка