Click here to Skip to main content
15,880,956 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
I'm creating a mine Sweepers game on c#, and I'm trying to implement a flood fill algorithm. But when I click on an empty button (one with no bombs around it ), the other buttons around don't reveal their values. At first, I thought it was my flood fill algorithm, but it seems to be working just fine after running some tests, but the buttons refuse to display their corresponding values. The text I ran was for the buttons that are meant to be clicked to be printed out on the terminal, so I know that the <pre> FloodFill method(int x, int y)</pre> is working just fine.

Clicking on a button works just fine, so the individual buttons themselves don't seem to be the issue, but after clicking on an empty button, the surrounding one's are meant to display their values.

here's my complete code
C#
 <pre>using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Windows.Threading;


namespace MineSweepers
{
    public partial class Form1 : Form
    {

        DispatcherTimer disTimer = new DispatcherTimer();

        int h, m, s;
        private int col = 10;
        private int rows = 10;
        private int bombNum;
        HashSet<Button> flood = new HashSet<Button>();
        private Cell[,] grid;


        public Form1(int column, int row, int bombNum,int width,int length)

        {

            col = column;
            rows = row;
            this.bombNum = bombNum;
            grid = new Cell[rows, col];
            InitializeComponent();
            FlowLayoutPanel1.Size = new System.Drawing.Size(width, length);
        }
        public Form1()

        {

            col = 13;
            rows = 13;
            bombNum = 16;
            grid = new Cell[rows, col];
            InitializeComponent();
        }
        public void bombPos()
        {
            Random rand = new Random();


            for (int count = 1; count <= bombNum; count++)
            {

                int x = rand.Next(rows);
                int y = rand.Next(col);

                if (grid[x, y].GetIsBomb())
                {
                    count--;
                }
                else
                {
                    grid[x, y].SetIsBomb(true);

                }


            }


        }
        public void createGrid()
        {
            for (int i = 0; i < rows; i++)
            {
                for (int j = 0; j < col; j++)
                {

                    grid[i, j] = new Cell();


                }
            }

        }
        public void displayGrid()
        {
            FlowLayoutPanel1.Controls.Clear();
            createGrid();
            bombPos();

            for (int i = 0; i < rows; i++)
            {

                for (int j = 0; j < col; j++)
                {
                    int count = bombAround(i, j);
                    grid[i, j].setBombCount(count);
                    Button btn = new Button();
                    btn = (grid[i, j].individualButton(i, j));

                    btn.Click += Button_Click;

                    //   Console.WriteLine(count);
                    FlowLayoutPanel1.Controls.Add(btn);

                }
            }

        }



        private void btnStart_Click(object sender, EventArgs e)
        {
            displayGrid();
            TimeLasped();
            disTimer.Start();
        }
        public int bombAround(int x, int y)
        {

            int total = 0;
            for (int i = -1; i < 2; i++)
            {
                for (int j = -1; j < 2; j++)
                {

                    int row = i + x;
                    int column = j + y;
                    if (row > -1 && row < rows && column > -1 && column < col)
                    {
                        if (grid[row, column].GetIsBomb())
                        {
                            total++;
                        }
                    }



                }

            }
            //Console.WriteLine(total);
            return total;
        }
        public void FloodFill(int x, int y)
        {

            Cell neighbour = new Cell();
            Button btn = new Button();
            for (int i = -1; i < 2; i++)
            {
                for (int j = -1; j < 2; j++)
                {
                    int row = i + x;
                    int column = j + y;
                    if (row > -1 && row < rows && column > -1 && column < col)
                    {

                        neighbour = grid[row, column];
                        if (!neighbour.GetIsBomb() && !neighbour.isRevealed)
                        {
                        

                            btn = neighbour.individualButton(row, column);
                            btn.Click += Button_Click;
                           
                            Console.WriteLine("Button " + row.ToString() + " " + column.ToString()+" Text is " + neighbour.individualButton(row, column).Text );
                            btn.PerformClick();

                        }
                    }


                }

            }

           
        
        }

        public void show(int x,int y,Button button)
        {
            if (grid[x, y].GetIsBomb())
            {

            button.Text = "-";


            }
            else
            {

               button.Text = grid[x, y].GetBombCount().ToString();

            }
            if (grid[x, y].GetBombCount() == 0)
            {

               button.Text = "xx";

           
                
            }
        }


        void Button_Click(object sender, EventArgs e)
        {
            Button button = (Button)sender;
            int x = Convert.ToInt32(button.Name.Substring(0, 1));
            int y = Convert.ToInt32(button.Name.Substring(1, 1));
            Console.WriteLine(x.ToString() + " " + y.ToString());

            grid[x, y].isRevealed = true;

            show(x, y,button);
            if (grid[x, y].GetBombCount() == 0)

            {
                FloodFill(x, y);

            }
        }

        
        public void TimeLasped()
        {

            disTimer.Tick += new EventHandler(disTimer_Click);
            disTimer.Interval = new TimeSpan(0, 0, 1);



        }

        private void disTimer_Click(object sender, EventArgs e)
        {
            s += 1;
            if (s == 60)
            {
                s = 0;
                m += 1;
            }
            if (m == 60)
            {
                m = 0;
                h += 1;
            }
            textBoxTime.Text = String.Format("{0}:{1}:{2}", h.ToString(), m.ToString(), s.ToString());

        }

    }
    public class Cell
    {


        private int bombCount;
        private bool isBomb;
        public bool isRevealed = false;
        public int size = 40;
        public int rows;
        public int col;

        public bool GetIsBomb()
        {

            return isBomb;
        }

        public void SetIsBomb(bool state)

        {
            isBomb = state;
        }
        public int GetBombCount()
        {
            return bombCount;
        }

        public void setBombCount(int count)
        {
            bombCount = count;
        }
        public Button individualButton(int x, int y)
        {

            Button btnCurrent = new Button();

            btnCurrent.Name = x.ToString() + y.ToString();
            btnCurrent.Width = size;
            btnCurrent.Height = size;
            rows = x;
            col = y;



            if (bombCount == 0)
            {
                btnCurrent.Text = "[]";
            }


            return btnCurrent;


        }

    }


}


What I have tried:

Since the problem is the fact that the buttons don't display any text when an empty button is clicked. I have tried different things like having the flood fill return a button but can't seem to figure out the problem.
Posted
Updated 3-Jul-22 5:46am
v2
Comments
[no name] 9-Feb-22 12:37pm    
Just hard code it; you won't use "more code". Left = x-1,y; LeftTop = x-1,y-1; Top = x,y-1; etc. 8 lines.
Casper Josiah 9-Feb-22 12:59pm    
would that make a difference, the floodFill is working and I still have to accept parameters for x and y
Luc Pattyn 12-Feb-22 23:13pm    
Hi Casper,

I created a MineSweeper program and a little article describing it.
Available here
Enjoy!

1 solution

Hi,

Programming MineSweeper is an interesting exercise. A lot of your code makes perfect sense, but I do have several comments.

1.
You have a Control that holds cells, and want to use each cell as a button. However your cells aren't buttons, they don't even "have" a button, they are loosely associated with a button: in individualButton() you create a Button that you don't store in the Cell, you do store it in the grid. But then in floodfill() you create (a lot of) buttons that aren't stored at all, they only are used to call their PerformClick() method which calls Button_Click() which calls show(). By that time, each grid location has at least two buttons, one of them being shown, others wandering in the background to confuse you. To make matters worse, you are likely to have an avalanche of button clicking, flood filling, more button clicking, etc.

My suggestion here is twofold:
(a) make Cell inherit from Button (so a cell is a Button)
(b) don't create extra buttons that only serve your calculations and not the user interface, i.e. give the Cell class a public Reveal() method that gets called when a Cell is clicked but also when it needs to be revealed due to the floodfill.

2.
Several minor comments:
(a) you have a FlowLayoutPanel, IMO a TableLayoutPanel could be more appropriate. Beware, you'd need the MouseDown event, not the (Mouse)Click event, in order to get both right and left mouse clicks then.
(b) your bombPos() may not be exactly what you intended as it may place fewer than bombCount bombs when the random function happens to clash.
(c) you are a Java programmer, aren't you. There are numeral places where a property would be appropriate, rather than a getter and a setter. And you can declare an object without giving it a dummy value, e.g. in floodfill
Cell neighbour = new Cell();
can be reduced to
Cell neighbour;
(which will be undefined rather than null, howver the compiler will keep a watch on you giving it its value before referencing it).
(d) your floodfill isn't finished: as it is it performs a single sweep over the entire board, and you really need the click-floodfill-click chain (with its avalanche risk) to get it flood to the top and/or to the left.
An easier approach would be to put your floodfill code inside a while loop running until nothing more is revealed.
(e) your code displaying elapsed time is a bit peculiar. The DateTime and TimeSpan types could be a great help here, example:
private void timer1_Tick(object sender, EventArgs e) {
    TimeSpan elapsed=DateTime.Now - startTime;
    labTime.Text = string.Format("Elapsed time: {0:N1} seconds", elapsed.TotalSeconds);
}

(f) Storing the button's grid coordinates in its name is currently limiting your board size to 10*10, and it is completely unnecessary if you follow my #1 comment.
 
Share this answer
 
Comments
Graeme_Grant 9-Feb-22 21:19pm    
Nice answer ... 5'ed! :)
Casper Josiah 9-Feb-22 22:08pm    
Hi, thanks for the answer. It's been really insightful, and there's a lot to take away from it, so I have some questions.
I'm new to c#, and as you said, I'm more familiar with java. When you said I shouldn't create extra buttons for calculations do you mean something like this
  Button btn = new Button();
. Also, about the flood fill, I understand the reason for the while loop, and it makes sense, but do you think there's anymore I should do. I'm asking because I have several button/event_handling going on there, and I have right now. I don't get it as it is quite complex, and there are a lot of things going on that are hard to keep track of and so I'm not that confident in the method. And about the
bombPos Method 
thanks for that. I've noticed quite a few times that the bomb count is lower than expected, usually by 1 or 2, and had just assumed it was done to my numerous editing and undoing.
Luc Pattyn 9-Feb-22 22:42pm    
The RHS of Button btn = new Button(); inside displayGrid() is ineffective, as btn gets another value in the next line by calling individualButton().
Overall displayGrid() is acceptable, not perfect.
Floodfill() calling individualButton() is not OK, as that creates a new button for the cell, a button that is not relevant to the user, and later probably gets confused with the original.
One should provide a Control when the user interface needs one, not when calculations seem to be interested in one. As I said, introducing a Cell.Reveal() method would remove the urge of creating extra buttons. And making Cell a descendant of Button would simplify things.
Casper Josiah 10-Feb-22 0:50am    
Do you have any suggestions on how I should set the bombPos. All my attempts keep changing the number of bombs or not making them completely random
Luc Pattyn 10-Feb-22 0:55am    
// that is elementary:
wanted=numBombs;
while(wanted>0) {
....calculate random position
....if position isntbomb {
........setBomb
........wanted--;
....}
}

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900