Dynamicly create a playboard for Minesweeper game

2019-01-15 11:10发布

问题:

with the objective to learn C#, XAML and especially MVVM I started to program the Minesweeper game. First version I made was without the MVVM part, where I created and added the buttons with C# code instead of doing it in XAML via the MVVM way. Now I try to apply the MVVM pattern into the game.

I made an own User Control which contains a button representing a minefield part. This control also has a ViewModel and a Model Class to store some state data and handle some commands. In a test project I create 4 of those own User Controls and try to put them in a grid where the buttons forms a square of 2 by 2 buttons. In the code behind, the buttons are put in an ObservableCollection object. I assume that in this object the buttons are listed and indexed like:

Button1
Button2
Button3
Button4

but in the presentation grid I want the buttons to be shown like

Button1 | Button2
--------+--------
Button3 | Button4

The question is: how do I do that dynamicly? Cause in my test project I test with 4 buttons, but In the project I want to use this, the amount of buttons can be different depending on the difficulty of the game the player has chosen.

And a second question is how I can figure out what the neigbhours are of a button. So if the grid is 4 by 5 containing 20 buttons. And for example I pick button 8 which has as neighbour buttons number 2, 3, 4, 7, 9, 12, 13 and 14. How can I reach those neighbour buttons when the buttons are listed?

I hope my questions are clear enough.

Thank you in advance!

回答1:

You can display your collection using an ItemsControl that has it's ItemsPanel set to a Grid or UniformGrid. I have some ItemsControl examples here that may help you, as I don't find MDSN's examples very helpful.

A UniformGrid would be easiest if you can bind your Rows and Columns properties to a property in your ViewModel, however that requires all your cells be the same size, and I can't remember if the properties Rows and Columns are DependencyProperties that participate in the binding system or not.

<ItemsControl ItemsSource="{Binding MyCollection}">
    <!-- ItemsPanelTemplate -->
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <UniformGrid Columns="{Binding ColumnCount}" 
                         Rows="{Binding RowCount}" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

If this doesn't work for you, you can use a Grid as the ItemsPanel and set the Grid.Row and Grid.Column bindings in the ItemContainerStyle.

This will require you have properties on each of your cell objects in the ObservableCollection to say what Row/Column that cell is in, however I suspect you'll need these anyways to determine things like adjacent cells in your click command.

In addition, there's no built-in way to bind the number of Rows/Columns in your Grid, so I tend to use some custom attached properties that will dynamically build the Grid's RowDefinitions and ColumnDefinitions based on a bound value.

So your end result if you're using a Grid would probably look something like this:

<ItemsControl ItemsSource="{Binding Cells}">
    <!-- ItemsPanelTemplate -->
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <!-- This is assuming you use the attached properties linked above -->
            <Grid local:GridHelpers.RowCount="{Binding Height}"
                  local:GridHelpers.ColumnCount="{Binding Width}" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <!-- ItemContainerStyle -->
    <ItemsControl.ItemContainerStyle>
        <Style>
            <Setter Property="Grid.Column" 
                    Value="{Binding ColumnIndex}" />
            <Setter Property="Grid.Row" 
                    Value="{Binding RowIndex}" />
        </Style>
    </ItemsControl.ItemContainerStyle>
</ItemsControl>

With a Model/ViewModel looking something like this

public class GameViewModel
{
    // These should be full properties w/ property change notification
    // but leaving that out for simplicity right now
    public int Height;
    public int Width;
    public ObservableCollection<CellModel> Cells;
}

public class CellModel
{
    // Same thing, full properties w/ property change notification
    public int ColumnIndex;
    public int RowIndex;
    public bool IsVisible;
    public bool IsMine;
    public int NumberAdjacentMines;
}


回答2:

Recently I solved the Minesweeper game logic, for a sort of software development contest. I think that the main issue of his game is finding the adjacent mines and the adjacent empty cells around an item. In both cases we have to consider that at most each cell (item) could be surrounded by eight items (cells). If we consider a 4x4 grid (matrix) a cell C defined by a X,Y coordinates it will be surrounded by the following cells:

C1 (X-1,Y-1)
C2 (X,Y-1)
C3 (X+1,Y-1)
C4 (X-1,Y)
C5 (X+1,Y)
C6 (X-1,Y+1)
C7 (X,Y+1)
C8 (X+1,Y+1)

In other words we can find the adjacent cells of an item by translating a point on the x and y axis. I developed a class set by splitting the presentation of the game (front-end) by the game logic (back-end).
By using this strategy actually you can use it with different .Net environment: we don't are "locked" around UI elements or components.
You can download my project here:
https://github.com/alchimya/csharp-minesweeper-sdk



回答3:

Off the top of my head:

List<Button> myButtons = new List<Button>();
Button myFirstButton = new Button(/*make first button*/);
myButtons.Add(myFirstButton);

void addButtonX ()
{//add new button along x axis
    Button tempButt = new Button();        
    Button lastButt = myButtons[myButtons.count - 1];
    tempButt.posX = lastButt.posX + lastButt.sizeX;
    tempButt.posY = lastButt.posY;
    //other code for buttons
    myButtons.Add(tempButt);
}
void addButtonY ()
{//add new button along y axis
    Button tempButt = new Button();
    Button lastButt = myButtons[myButtons.count - 1];
    tempButt.posX = lastButt.posX;
    tempButt.posY = lastButt.posY + lastButt.sizeY;
    //other code for buttons
    myButtons.Add(tempButt);
}

To keep track of the buttons around, you will need to know how many buttons wide the grid is, for instance, if the grid is 20 buttons wide, the buttons will be like this:

[B-20-1][B-20][B-20+1]
[B-1]   [B]   [B+1]
[B+20-1][B+20][B+20+1]

Where B = button clicked.

Of course, you'll have to check that no values go off the grid (B-20-1 is not less than 0, B+20+1 is not greater than the number of buttons for example.), but that's quite easily done.



标签: c# xaml mvvm