266 lines
7.7 KiB
C#
266 lines
7.7 KiB
C#
using System;
|
|
using System.Drawing;
|
|
using System.Windows.Forms;
|
|
|
|
class MineFieldModel
|
|
{
|
|
public int RemainingMinesCount{
|
|
get{
|
|
var count = 0;
|
|
ForEachCell((i,j)=>{
|
|
if (Mines[i,j] && !Marked[i,j])
|
|
count++;
|
|
});
|
|
return count;
|
|
}
|
|
}
|
|
|
|
public bool[,] Mines{get; private set;}
|
|
public bool[,] Opened{get;private set;}
|
|
public bool[,] Marked{get; private set;}
|
|
public int[,] Values{get;private set; }
|
|
public int Width{ get{return Mines.GetLength(1);} }
|
|
public int Height{ get{return Mines.GetLength(0);} }
|
|
|
|
public MineFieldModel(bool[,] mines)
|
|
{
|
|
this.Mines = mines;
|
|
this.Opened = new bool[Height, Width]; // filled with 'false' by default
|
|
this.Marked = new bool[Height, Width];
|
|
this.Values = CalculateValues();
|
|
}
|
|
|
|
private int[,] CalculateValues()
|
|
{
|
|
int[,] values = new int[Height, Width];
|
|
ForEachCell((i,j) =>{
|
|
var value = 0;
|
|
ForEachNeighbor(i,j, (i1,j1)=>{
|
|
if (Mines[i1,j1])
|
|
value++;
|
|
});
|
|
values[i,j] = value;
|
|
});
|
|
return values;
|
|
}
|
|
|
|
// Helper method for iterating over cells
|
|
public void ForEachCell(Action<int,int> action)
|
|
{
|
|
for (var i = 0; i < Height; i++)
|
|
for (var j = 0; j < Width; j++)
|
|
action(i,j);
|
|
}
|
|
|
|
// Helper method for iterating over cells' neighbors
|
|
public void ForEachNeighbor(int i, int j, Action<int,int> action)
|
|
{
|
|
for (var i1 = i-1; i1 <= i+1; i1++)
|
|
for (var j1 = j-1; j1 <= j+1; j1++)
|
|
if (InBounds(j1, i1) && !(i1==i && j1 ==j))
|
|
action(i1, j1);
|
|
}
|
|
|
|
private bool InBounds(int x, int y)
|
|
{
|
|
return y >= 0 && y < Height && x >=0 && x < Width;
|
|
}
|
|
|
|
public event Action Exploded = delegate{};
|
|
public event Action Win = delegate{};
|
|
public event Action Updated = delegate{};
|
|
|
|
public void OpenCell(int i, int j){
|
|
if(!Opened[i,j]){
|
|
if (Mines[i,j])
|
|
Exploded();
|
|
else{
|
|
OpenCellsStartingFrom(i,j);
|
|
Updated();
|
|
CheckForVictory();
|
|
}
|
|
}
|
|
}
|
|
|
|
void OpenCellsStartingFrom(int i, int j)
|
|
{
|
|
Opened[i,j] = true;
|
|
ForEachNeighbor(i,j, (i1,j1)=>{
|
|
if (!Mines[i1,j1] && !Opened[i1,j1] && !Marked[i1,j1])
|
|
OpenCellsStartingFrom(i1, j1);
|
|
});
|
|
}
|
|
|
|
void CheckForVictory(){
|
|
int notMarked = 0;
|
|
int wrongMarked = 0;
|
|
ForEachCell((i,j)=>{
|
|
if (Mines[i,j] && !Marked[i,j])
|
|
notMarked++;
|
|
if (!Mines[i,j] && Marked[i,j])
|
|
wrongMarked++;
|
|
});
|
|
if (notMarked == 0 && wrongMarked == 0)
|
|
Win();
|
|
}
|
|
|
|
public void Mark(int i, int j){
|
|
if (!Opened[i,j])
|
|
Marked[i,j] = true;
|
|
Updated();
|
|
CheckForVictory();
|
|
}
|
|
}
|
|
|
|
class MineFieldView: UserControl{
|
|
public const int CellSize = 40;
|
|
|
|
MineFieldModel _model;
|
|
public MineFieldModel Model{
|
|
get{ return _model; }
|
|
set
|
|
{
|
|
_model = value;
|
|
this.Size = new Size(_model.Width * CellSize+1, _model.Height * CellSize+2);
|
|
}
|
|
}
|
|
|
|
public MineFieldView(){
|
|
//Enable double-buffering to eliminate flicker
|
|
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer,true);
|
|
this.Font = new Font(FontFamily.GenericSansSerif, 14, FontStyle.Bold);
|
|
|
|
this.MouseUp += (o,e)=>{
|
|
Point cellCoords = GetCell(e.Location);
|
|
if (Model != null)
|
|
{
|
|
if (e.Button == MouseButtons.Left)
|
|
Model.OpenCell(cellCoords.Y, cellCoords.X);
|
|
else if (e.Button == MouseButtons.Right)
|
|
Model.Mark(cellCoords.Y, cellCoords.X);
|
|
}
|
|
};
|
|
}
|
|
|
|
Point GetCell(Point coords)
|
|
{
|
|
var rgn = ClientRectangle;
|
|
var x = (coords.X - rgn.X)/CellSize;
|
|
var y = (coords.Y - rgn.Y)/CellSize;
|
|
return new Point(x,y);
|
|
}
|
|
|
|
static readonly Brush MarkBrush = new SolidBrush(Color.Blue);
|
|
static readonly Brush ValueBrush = new SolidBrush(Color.Black);
|
|
static readonly Brush UnexploredBrush = new SolidBrush(SystemColors.Control);
|
|
static readonly Brush OpenBrush = new SolidBrush(SystemColors.ControlDark);
|
|
|
|
|
|
protected override void OnPaint(PaintEventArgs e)
|
|
{
|
|
base.OnPaint(e);
|
|
var g = e.Graphics;
|
|
if (Model != null)
|
|
{
|
|
Model.ForEachCell((i,j)=>
|
|
{
|
|
var bounds = new Rectangle(j * CellSize, i * CellSize, CellSize, CellSize);
|
|
if (Model.Opened[i,j])
|
|
{
|
|
g.FillRectangle(OpenBrush, bounds);
|
|
if (Model.Values[i,j] > 0)
|
|
{
|
|
DrawStringInCenter(g, Model.Values[i,j].ToString(), ValueBrush, bounds);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g.FillRectangle(UnexploredBrush, bounds);
|
|
if (Model.Marked[i,j])
|
|
{
|
|
DrawStringInCenter(g, "?", MarkBrush, bounds);
|
|
}
|
|
var outlineOffset = 1;
|
|
var outline = new Rectangle(bounds.X+outlineOffset, bounds.Y+outlineOffset, bounds.Width-2*outlineOffset, bounds.Height-2*outlineOffset);
|
|
g.DrawRectangle(Pens.Gray, outline);
|
|
}
|
|
g.DrawRectangle(Pens.Black, bounds);
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
static readonly StringFormat FormatCenter = new StringFormat
|
|
{
|
|
LineAlignment = StringAlignment.Center,
|
|
Alignment=StringAlignment.Center
|
|
};
|
|
|
|
void DrawStringInCenter(Graphics g, string s, Brush brush, Rectangle bounds)
|
|
{
|
|
PointF center = new PointF(bounds.X + bounds.Width/2, bounds.Y + bounds.Height/2);
|
|
g.DrawString(s, this.Font, brush, center, FormatCenter);
|
|
}
|
|
|
|
}
|
|
|
|
class MineSweepForm: Form
|
|
{
|
|
|
|
MineFieldModel CreateField(int width, int height)
|
|
{
|
|
var field = new bool[height, width];
|
|
int mineCount = (int)(0.2 * height * width);
|
|
var rnd = new Random();
|
|
while(mineCount > 0)
|
|
{
|
|
var x = rnd.Next(width);
|
|
var y = rnd.Next(height);
|
|
if (!field[y,x])
|
|
{
|
|
field[y,x] = true;
|
|
mineCount--;
|
|
}
|
|
}
|
|
return new MineFieldModel(field);
|
|
}
|
|
|
|
public MineSweepForm()
|
|
{
|
|
var model = CreateField(6, 4);
|
|
var counter = new Label{ };
|
|
counter.Text = model.RemainingMinesCount.ToString();
|
|
var view = new MineFieldView
|
|
{
|
|
Model = model, BorderStyle = BorderStyle.FixedSingle,
|
|
};
|
|
var stackPanel = new FlowLayoutPanel
|
|
{
|
|
Dock = DockStyle.Fill,
|
|
FlowDirection = FlowDirection.TopDown,
|
|
Controls = {counter, view}
|
|
};
|
|
this.Controls.Add(stackPanel);
|
|
model.Updated += delegate{
|
|
view.Invalidate();
|
|
counter.Text = model.RemainingMinesCount.ToString();
|
|
};
|
|
model.Exploded += delegate {
|
|
MessageBox.Show("FAIL!");
|
|
Close();
|
|
};
|
|
model.Win += delegate {
|
|
MessageBox.Show("WIN!");
|
|
view.Enabled = false;
|
|
};
|
|
|
|
}
|
|
}
|
|
|
|
class Program
|
|
{
|
|
static void Main()
|
|
{
|
|
Application.Run(new MineSweepForm());
|
|
}
|
|
}
|