xingd.net

.net related techonology

导航

Minesweeper: GDI+ 初步实现

Posted on 2008-03-06 18:14  xingd  阅读(3962)  评论(1编辑  收藏  举报
逻辑代码部分借鉴木野狐博客中的代码,参考http://www.cnblogs.com/RChen/archive/2005/07/07/188107.html,之后我会加入自己的设计调整。

一些必要的枚举:

public enum GameStatus
{
    NotStarted,
    Playing,
    Win,
    Lose
}


public enum MineCellStatus : short
{
    Covered,
    MarkDoubt,
    MarkMine,
    Uncovered,
    Exploded
}


public struct MineCell
{
    
public MineCellStatus Status;
    
public bool IsMine;        
    
public byte NearbyMines;
}

将MineCell声明为struct或class代表了两种不同的设计策略,简单起见,先使用struct。

逻辑代码如下:

namespace xingd.Minesweeper
{
    
public class MineBoard
    
{
        
private MineCell[,] cells = new MineCell[00];

        
private int totalMines;
        
private int makedMines;
        
private GameStatus status;

        
public int Columns get return cells.GetLength(1); } }

        
public int Rows get return cells.GetLength(0); } }

        
public int TotalMines get return totalMines; } }
        
public int MarkedMines get return makedMines; } }

        
public GameStatus Status get return status; } }

        
public MineCell this[int row, int col] get return cells[row, col]; } }

        
public void Init(int rows, int columns, int mines)
        
{
            
if (rows <= 0 || columns <= 0 || mines >= columns * rows)
            
{
                
throw new ArgumentException();
            }


            cells 
= new MineCell[rows, columns];
            totalMines 
= mines;
            makedMines 
= 0;
            status 
= GameStatus.NotStarted;

            PlaceRandomMines();
            UpdateNearbyMinesCount();
        }


        
public void Uncover(int row, int col)
        
{
            
if (!IsValidCell(row, col))
                
return;

            
if (status == GameStatus.NotStarted)
            
{
                status 
= GameStatus.Playing;
            }


            MineCell cell 
= cells[row, col];

            
if (cell.Status != MineCellStatus.Covered)
                
return;
            
            
if (cell.IsMine)
            
{
                cells[row, col].Status 
= MineCellStatus.Exploded;
                
this.status = GameStatus.Lose;
                
return;
            }

            
else
            
{
                cells[row, col].Status 
= MineCellStatus.Uncovered;

            }


            
// 如果周围雷数是 0, 那么顺便把周围的 8 个位置都翻开
            if (!cell.IsMine && cell.NearbyMines == 0)
            
{
                
for (int i = 0; i < moves.GetLength(0); i++)
                    Uncover(row 
+ moves[i, 0], col + moves[i, 1]);
            }

        }


        
/**/
        
/// <summary>
        
/// 设定或取消标志, 相当于鼠标右键单击的行为
        
/// </summary>
        
/// <param name="row"></param>
        
/// <param name="col"></param>

        public void ChangeMark(int row, int col)
        
{
            
if (!IsValidCell(row, col))
                
return;
            
            
if (status == GameStatus.NotStarted)
            
{
                status 
= GameStatus.Playing;
            }

            
            MineCell cell 
= cells[row, col];

            
// 轮换几种标志
            switch (cell.Status)
            
{
                
case MineCellStatus.Covered:
                    cell.Status 
= MineCellStatus.MarkMine;
                    makedMines
++;
                    
break;
                
case MineCellStatus.MarkMine:
                    cell.Status 
= MineCellStatus.MarkDoubt;
                    
break;
                
case MineCellStatus.MarkDoubt:
                    makedMines
--;
                    cell.Status 
= MineCellStatus.Covered;
                    
break;
                
default:
                    
break;
            }


            cells[row, col] 
= cell;
        }


        
/// <summary>
        
/// 鼠标左右键同时按下行为
        
/// </summary>
        
/// <param name="row"></param>
        
/// <param name="col"></param>

        public void UncoverNearBy(int row, int col)
        
{
            MineCell cell 
= cells[row, col];

            
if (cell.Status == MineCellStatus.Uncovered && cell.NearbyMines > 0)
            
{
                
byte minesFound = 0;

                
for (int i = 0; i < moves.GetLength(0); i++)
                
{
                    
int r = row + moves[i, 0];
                    
int c = col + moves[i, 1];

                    
if (IsValidCell(r, c) && cells[r, c].Status == MineCellStatus.MarkMine)
                        minesFound
++;
                }


                
if (minesFound == cell.NearbyMines)
                
{
                    
for (int i = 0; i < moves.GetLength(0); i++)
                    
{
                        
int r = row + moves[i, 0];
                        
int c = col + moves[i, 1];

                        Uncover(r, c);
                    }

                }

            }

        }

        
        
/// <summary>
        
/// 检验游戏结果
        
/// </summary>

        public void CheckGameResult()
        
{
            
if (makedMines == totalMines)
            
{
                
for (int i = 0; i < Rows; i++)
                
{
                    
for (int j = 0; j < Columns; j++)
                    
{
                        
if (cells[i, j].Status == MineCellStatus.Covered)
                        
{
                            
return;
                        }

                    }

                }


                status 
= GameStatus.Win;
            }

        }


        
private void PlaceRandomMines()
        
{
            
int count = 0;
            Random r 
= new Random((int)DateTime.Now.Ticks);

            
while (count < totalMines)
            
{
                
int row = r.Next(0, Rows);
                
int col = r.Next(0, Columns);

                
if (!cells[row, col].IsMine)
                
{
                    cells[row, col].IsMine 
= true;
                    count
++;
                }

            }

        }


        
private bool IsValidCell(int row, int col)
        
{
            
return (row >= 0 && col >= 0 && row < Rows && col < Columns);
        }


        
/**/
        
/// <summary>
        
/// 从一个格子出发向四周的可能的位移
        
/// </summary>

        private int[,] moves = new int[82-1-1 }-10 }-11 }0-1 }01 }1-1 }10 }11 } };

        
private byte GetNearbyMines(int row, int col)
        
{
            
byte minesFound = 0;

            
for (int i = 0; i < moves.GetLength(0); i++)
            
{
                
int r = row + moves[i, 0];
                
int c = col + moves[i, 1];

                
if (IsValidCell(r, c) && cells[r, c].IsMine)
                    minesFound
++;
            }

            
return minesFound;
        }


        
private void UpdateNearbyMinesCount()
        
{
            
for (int i = 0; i < Rows; i++)
            
{
                
for (int j = 0; j < Columns; j++)
                
{
                    cells[i, j].NearbyMines 
= GetNearbyMines(i, j);
                }

            }

        }

    }

}

窗口代码如下:

namespace xingd.Minesweeper
{
    
public partial class MineForm : Form
    
{
        
private MineBoard board = new MineBoard();

        
private MouseButtons lastClickButton = MouseButtons.None;
        
private long lastClickTicks = 0;
        
        
private const int CellSize = 16;
        
private const long ShortPeriod = 2000000L;
        
private static readonly Rectangle CellRectangle = new Rectangle(00, CellSize, CellSize);

        
public MineForm()
        
{
            InitializeComponent();

            SetStyle(ControlStyles.ResizeRedraw 
| ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true);
        }


        
protected override void OnLoad(EventArgs e)
        
{
            
base.OnLoad(e);

            Init(
9910);
        }


        
public void Init(int rows, int columns, int mines)
        
{
            board.Init(rows, columns, mines);
            ClientSize 
= new Size(columns * CellSize + 1, rows * CellSize + mainMenu.Height + 1);
            Invalidate();
        }


        
protected override void OnPaint(PaintEventArgs e)
        
{
            
base.OnPaint(e);

            Graphics g 
= e.Graphics;
            
            
for (int i = 0; i < board.Rows; i++)
            
{
                
for (int j = 0; j < board.Columns; j++)
                
{
                    g.ResetTransform();
                    g.TranslateTransform(j 
* 16, i * 16 + mainMenu.Height);
                    
                    DrawCell(g, board[i, j]);
                }

            }

        }


        
private void DrawCell(Graphics g, MineCell cell)
        
{
            Rectangle rt 
= CellRectangle;
            
            StringFormat sf 
= new StringFormat();
            sf.Alignment 
= StringAlignment.Center;
            sf.LineAlignment 
= StringAlignment.Center;

            
if (cell.IsMine && board.Status == GameStatus.Lose)
            
{
                g.FillRectangle(Brushes.DarkGray, rt);
                g.DrawString(
"@", Font, cell.Status == MineCellStatus.Exploded ? Brushes.Red : Brushes.DarkBlue, rt, sf);
            }

            
else
            
{
                
if (cell.Status == MineCellStatus.Covered)
                
{
                    g.FillRectangle(Brushes.DarkGray, rt);
                }

                
else if (cell.Status == MineCellStatus.MarkMine)
                
{
                    g.FillRectangle(Brushes.DarkGray, rt);
                    g.DrawString(
"#", Font, Brushes.DarkBlue, rt, sf);
                }

                
else if (cell.Status == MineCellStatus.MarkDoubt)
                
{
                    g.FillRectangle(Brushes.DarkGray, rt);
                    g.DrawString(
"?", Font, Brushes.DarkBlue, rt, sf);
                }

                
else
                
{
                    
if (cell.NearbyMines > 0)
                    
{
                        g.DrawString(cell.NearbyMines.ToString(), Font, Brushes.DarkBlue, rt, sf);
                    }

                }

            }


            g.DrawRectangle(Pens.Black, rt);

            sf.Dispose();
        }


        
protected override void OnMouseClick(MouseEventArgs e)
        
{
            
base.OnMouseClick(e);

            
if (board.Status != GameStatus.Playing && board.Status != GameStatus.NotStarted)
                
return;
            
            
int row, col;
            HitTest(e.Location, 
out row, out col);

            
if (DateTime.Now.Ticks - lastClickTicks < ShortPeriod && ((lastClickButton | e.Button) == (MouseButtons.Left | MouseButtons.Right)))
            
{
                board.UncoverNearBy(row, col);
            }

            
            
if (e.Button == MouseButtons.Left)
            
{
                board.Uncover(row, col);
            }

            
else if (e.Button == MouseButtons.Right)
            
{
                board.ChangeMark(row, col);
            }


            board.CheckGameResult();

            
if (board.Status == GameStatus.Win)
            
{
                MessageBox.Show(
"You Win!");
            }


            Invalidate();
        }


        
protected override void OnMouseDown(MouseEventArgs e)
        
{
            
base.OnMouseDown(e);

            
if (DateTime.Now.Ticks - lastClickTicks < ShortPeriod)
            
{
                lastClickButton 
|= e.Button;
            }

            
else
            
{
                lastClickButton 
= e.Button;
                lastClickTicks 
= DateTime.Now.Ticks;
            }

        }


        
private void HitTest(Point pt, out int row, out int col)
        
{
            col 
= pt.X / CellSize;
            row 
= (pt.Y - mainMenu.Height) / CellSize;
        }


        
private void menuItemExit_Click(object sender, EventArgs e)
        
{
            
this.Close();
        }


        
private void menuItemNew_Click(object sender, EventArgs e)
        
{
            
this.Init(9910);
        }

    }

}

非常简单,不过已经可以玩了,系列稍后的文章会对功能进行改进。

项目文件下载

更正一:在MouseClick中,先Invalidate,再处理取胜判断,用户体验要好一些。