Question

I am attempting to make Conway's Game of Life in C# with XAML. The window allows the user to specify the number of rows and columns of my 2D array of cells using a slider. When my Uniform Grid is a perfect square (10x10, 20x20, or even 16x16), the simulations work without a problem. However, when the user attempts to specify a rectangular uniform grid (13x14, 15x26, 24x14), the cells are thrown of by the difference (i.e. in a 33x27 grid, difference = 6, so the cell goes appropriately up, but is thrown off (left/right) by the difference). I have narrowed down that this only happens on the x-axis; the cells are never thrown off on the y-axis. THE QUESTION: Why is my array throwing off my x axis? Is there something wrong with it?

As far as I can tell, everything should work fine. I set up a log to check the dimensions of my 2D arrays and my uniform grid. I'm not sure what is wrong, and I have been staring and debugging for literally DAYS. I'm at my wits end. Please help, I hope there is something that I am simply not catching.

Code Legend: unigridOfCells is a Uniform Grid in XAML. slideWidth/slideHeight are sliders. Also, I am using a converter from my resource which converts my isAlive property to a SolidColorBrush.

    private Cell[,] cells;
    private Cell[,] nextGenCells;
    private int codeColumn, codeRow, difference, secondDiff;

    public MainWindow()
    {
        InitializeComponent();
        unigridOfCells.Height = 500;
        unigridOfCells.Width = 500;
        setCellsOnGrid(10, 10);
    }

    //Sets all the cells on the grid, as well as setting the number of columns and rows to be reset for all arrays in the application
    public void setCellsOnGrid(int column, int row)
    {
        unigridOfCells.Rows = row;
        unigridOfCells.Columns = column;
        codeColumn = column;
        codeRow = row;
        time = new Timer(3000);

        cells = new Cell[codeColumn, codeRow];
        nextGenCells = new Cell[codeColumn, codeRow];
        for (int i = 0; i < codeColumn; i++)
        {
            for (int j = 0; j < codeRow; j++)
            {
                cells[i, j] = new Cell();
                Rectangle block = new Rectangle();
                block.Height = 10;
                block.Width = 10;
                block.DataContext = cells[i, j];
                block.MouseLeftButtonDown += cells[i, j].ParentClicked;
                //block.MouseLeftButtonDown += blockSpace;

                Binding b = new Binding();
                b.Source = cells[i, j];
                b.Path = new PropertyPath("isAlive");
                b.Converter = (BoolColorConverter)Application.Current.FindResource("cellLifeSwitch");
                block.SetBinding(Rectangle.FillProperty, b);
                unigridOfCells.Children.Add(block);
            }
        }

    }

    public void blockSpace(object sender, MouseButtonEventArgs e)
    {
        int spot = 0;
        int pick = 0;
        for (int i = 0; i < codeColumn; i++)
        {
            for (int j = 0; j < codeRow; j++)
            {
                spot = unigridOfCells.Children.IndexOf((Rectangle)sender);

            }
        }
        MessageBox.Show("" + spot + " : " + pick);
    }

    //Updates the cells. This is where the rules are applied and the isAlive property is changed (if it is).
    public void updateCells()
    {
        for (int n = 0; n < codeColumn; n++)
        {
            for (int m = 0; m < codeRow; m++)
            {
                nextGenCells[n, m] = new Cell();
                bool living = cells[n, m].isAlive;
                int count = GetLivingNeighbors(n, m);
                bool result = false;
                if (living && count < 2)
                {
                    result = false;
                }
                if (living && (count == 2 || count == 3))
                {
                    result = true;
                }
                if (living && count > 3)
                {
                    result = false;
                }
                if (!living && count == 3)
                {
                    result = true;
                }

                nextGenCells[n, m].isAlive = result;
            }
        }
        setNextGenCells();
    }

    //Resets all the cells in a time step
    public void setNextGenCells()
    {
        for (int f = 0; f < codeColumn; f++)
        {
            for (int k = 0; k < codeRow; k++)
            {
                cells[f, k].isAlive = nextGenCells[f, k].isAlive;
            }
        }
    }

    //Checks adjacent cells to the cell in the position that was passed in
    public int GetLivingNeighbors(int x, int y)
    {
        int count = 0;

        // Check cell on the right.
        if (x != codeColumn - 1)
            if (cells[x + 1, y].isAlive)
                count++;

        // Check cell on the bottom right.
        if (x != codeColumn - 1 && y != codeRow - 1)
            if (cells[x + 1, y + 1].isAlive)
                count++;

        // Check cell on the bottom.
        if (y != codeRow - 1)
            if (cells[x, y + 1].isAlive)
                count++;

        // Check cell on the bottom left.
        if (x != 0 && y != codeRow - 1)
            if (cells[x - 1, y + 1].isAlive)
                count++;

        // Check cell on the left.
        if (x != 0)
            if (cells[x - 1, y].isAlive)
                count++;

        // Check cell on the top left.
        if (x != 0 && y != 0)
            if (cells[x - 1, y - 1].isAlive)
                count++;

        // Check cell on the top.
        if (y != 0)
            if (cells[x, y - 1].isAlive)
                count++;

        // Check cell on the top right.
        if (x != codeColumn - 1 && y != 0)
            if (cells[x + 1, y - 1].isAlive)
                count++;
        return count;
    }

    //Fires when the next generation button is clicked. Simply makes the board go through the algorithm
    private void nextGenerationClick(object sender, RoutedEventArgs e)
    {
        updateCells();
    }

    //Fired when the "Reset Grid" button is pressed, resets EVERYTHING with the new values from the sliders
    private void resetGrid(object sender, RoutedEventArgs e)
    {

        MessageBox.Show("First Slide (width) value: " + slideWidth.Value + "\nSecond Slide (length) value: " + slideHeight.Value +  "\nDifference: " + (codeColumn - codeRow) + "\nColumns: " + unigridOfCells.Columns + " \nRows: " + unigridOfCells.Rows + "\nChildren count: " + unigridOfCells.Children.Count + " \nLengths: "
            + "\n\tOf 1D of cells: " + cells.GetLength(0) + "\n\tOf 1D of nextGenCells: " + nextGenCells.GetLength(0) + "\n\tUniform Grid Columns: " + unigridOfCells.Columns + " \nWidths: " 
            + "\n\tOf 2D of cells: " + cells.GetLength(1) + "\n\tOf 2D of nextGenCells: " + nextGenCells.GetLength(1) + "\n\tUniform Grid Rows: " + unigridOfCells.Rows);
        unigridOfCells.Children.Clear();
        setCellsOnGrid((int)slideWidth.Value, (int)slideHeight.Value);
    }
Was it helpful?

Solution

The problem is that the order in which you create cells differs from the order in which the UniformGrid arranges its children.

In your setCellsOnGrid method, you create cells top-to-bottom, then left-to-right, whereas the UniformGrid arranges its children in the order left-to-right then top-to-bottom.

For a square grid, the cells in the first column of your grid are drawn in the first row of the UniformGrid, and similarly for other columns and rows. You end up with the grid being reflected in the line x = y. However, for a non-square grid, the length of a row does not equal the length of a column and so the grid is completely out of place.

For example, with a 3 × 3 grid, your loop runs in the following order:

1 4 7
2 5 8
3 6 9

However, the controls are added to the UniformGrid in the following order (assuming you haven't set FlowDirection="Right"):

1 2 3
4 5 6
7 8 9

For, for a 3 × 4 grid, your loop runs in the order

1 5 9
2 6 10
3 7 11
4 8 12

but the controls are added to the UniformGrid in the order

1  2  3
4  5  6
7  8  9
10 11 12

This means that cells that adjacent in your cells array might not be drawn as adjacent in the UniformGrid, and vice versa.

Fortunately, the fix is simple: switch the order of the i and j loops in setCellsOnGrid. Make the j loop the outer loop and the i loop the inner loop.

Incidentally, your blockSpace method doesn't appear to use the i and j loop variables - it just calls the same method codeColumn * codeRow times. Is this intentional?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top