Search code examples
c#arraysmultidimensional-arrayconsole-application.net-5

Value of array is always the same as the value from another array


I'm trying to make a snake game that causes as few foreground updates in the console as possible, so I have two grids in one class. The first is the current grid and the second is used to check whether something has changed in the current grid. The second grid should therefore always have the value of the current grid from the last game tick, but the problem is, if I change the current grid, the second grid also changes, even though the two variables have nothing to do with each other.

public class Canvas
{
    public Size Bounds { get; set; }
    public Position Location { get; set; }
    public Table Grid { get; set; } // That is grid one
    public Position? Apple { get; set; }

    private Table oldState; // And that is the second one

    public Canvas(Position location, Size bounds)
    {
        Bounds = bounds;
        Location = location;
        Grid = new(location, bounds);
        oldState = new(location, bounds);
    }

    public Task DrawField(bool borders = false, bool ignoreIf = false)
    {
        if (borders) DrawBorders();
        if (Grid.Cells == oldState.Cells && !ignoreIf) return Task.CompletedTask;
        for (int y = 0; y < Bounds.Height - 1; y += 2)
        {
            for (int x = 0; x < Bounds.Width; x++)
            {
                // Here is the problem: Both grids are the same
                if (Grid.Cells[x, y].Type != oldState.Cells[x, y].Type || Grid.Cells[x, y + 1].Type != oldState.Cells[x, y + 1].Type || ignoreIf)
                {
                    Console.BackgroundColor = Grid.Cells[x, y].GetColor() ?? (IsEven(x) ? ConsoleColor.Green : ConsoleColor.DarkGreen);
                    Console.ForegroundColor = Grid.Cells[x, y + 1].GetColor() ?? (IsEven(x) ? ConsoleColor.DarkGreen : ConsoleColor.Green);
                    Console.SetCursorPosition(Location.Left + x, Location.Top + y / 2);
                    Console.Write("▄");
                }
            }
        }
        oldState.SetCells(Grid.Cells);
        return Task.CompletedTask;
    }

    public Task ClearField()
    {
        Grid.ClearField();
        oldState.ClearField();
        return Task.CompletedTask;
    }

    public Task SetNewApple()
    {
        List<Position> availableFields = new();
        for (int y = 0; y < Bounds.Height; y++)
        {
            for (int x = 0; x < Bounds.Width; x++)
            {
                if (Grid.Cells[x, y].Type is Cell.CellTypes.Empty) availableFields.Add(new(x, y));
            }
        }

        if (availableFields.Count == 0) return Task.CompletedTask; // GAME WIN

        Random r = new(DateTime.Now.Millisecond);
        if (Apple != null) Grid.Cells[Apple.Value.Left, Apple.Value.Top] = new(Cell.CellTypes.Empty);
        Apple = availableFields[r.Next(0, availableFields.Count)];

        Cell[,] newField = Grid.Cells;
        newField[Apple.Value.Left, Apple.Value.Top] = new(Cell.CellTypes.Apple);

        Grid.SetCells(newField); // This is where the apple is added for the snake game and that ONLY for grid 1, but this is where the second grid gets the apple added as well.

        return Task.CompletedTask;
    }
}

public class Table
{
    public Cell[,] Cells { get; private set; }
    public Position Location { get; set; }
    public Size Bounds { get { return new(Cells.GetLength(0), Cells.GetLength(1)); } }

    public Table(Position pos, Size size)
    {
        Cells = new Cell[size.Width, size.Height];
        Location = pos;
        ClearField();
    }

    public Task SetCells(Cell[,] cells)
    {
        Cells = cells;
        return Task.CompletedTask;
    }

    public Task ClearField()
    {
        for (int y = 0; y < Cells.GetLength(1); y++)
        {
            for (int x = 0; x < Cells.GetLength(0); x++)
            {
                Cells[x, y] = new(Cell.CellTypes.Empty);
            }
        }
        return Task.CompletedTask;
    }
}

public class Cell
{
    public CellTypes Type { get; set; }

    public Cell()
    {
        Type = CellTypes.Empty;
    }
    public Cell(CellTypes type) => Type = type;

    public ConsoleColor? GetColor() => (Type is CellTypes.Snake) ? ConsoleColor.White : (Type is CellTypes.Apple) ? ConsoleColor.Red : null;

    public enum CellTypes { Empty, Snake, Apple }
}

The Position struct has two integers: Top and Left.

If you need more information about the code and my problem, feel free to ask, thank you.


Solution

  • Cell[,] newField = Grid.Cells;
    

    A struct is passed by value, and copied on assignment. But array of anything (struct or class) is not a struct, and is passed by reference. You don't get a copy of the array even if Cell were a struct, which it isn't. After the line above newField and Grid.Cells reference the same array of cells.

    To copy an array you actually have to copy all elements of the array into another array of the same dimensions. Manually. In your case I think you can get away with the "shallow copy". Cell is a class, but think here it's fine to have two arrays referencing the same cells. One thing though, Why not just use CellType instead of a Cell? ConsoleColor could just be an extension method in a separate class, instead of a property, and Cell class would become unnecessary.