2

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);
    }
RBarryYoung
  • 55,398
  • 14
  • 96
  • 137
Carlos
  • 105
  • 1
  • 9
  • Hi Carlos. Coincidentally, I recently [asked a question](http://stackoverflow.com/questions/16642461/brushes-white-slows-graphics-demo-down) about a life demo in C#/XAML/WPF. The full code is posted there is you're curious about another approach. – dharmatech May 25 '13 at 18:21
  • 1
    A couple of screen shots would help a lot here. – RBarryYoung May 25 '13 at 18:26
  • Try swapping the row/column values for `uniGridOfCells` around: `unigridOfCells.Rows = column;` and `unigridOfCells.Columns = row;`. This is a classic case of getting x/y logic confused (a screenshot would be nice as RBarryYoung said) - whether this is the whole problem or not you are current adding the first column first, which fill up the first row (and doesn't quite fit) in the Uniform Grid (it fills along the top, then onto the next row, etc. etc.) – VisualMelon May 25 '13 at 19:34
  • Thanks VisualMelon, you are indeed correct, my array was going top to bottom, left to right, and it didn't line up with my Uniform Grid (left to right, top to bottom). And apologies, but I couldn't add a picture because I just joined the site and don't have the 'reputation' necessary to post pictures. Thanks again. – Carlos May 25 '13 at 20:18

1 Answers1

2

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?

Luke Woodward
  • 63,336
  • 16
  • 89
  • 104
  • Thank you so, so, so much. This fixed it. I didn't realize I was setting my cells top to bottom. I stared at this for too long thinking my logic in get living neighbors was wrong, literally debugging it step by step. Again, many thanks, I am very grateful. Also, I couldn't post a picture because it was my first post. But thank you very much. – Carlos May 25 '13 at 20:05
  • Also, the blockspace method was a debug method I was going to use to see the position of the cell I clicked relative to the 2d array, but I didn't finish it, after realizing Array.IndexOf doesn't support 2D arrays. – Carlos May 25 '13 at 20:17