Skip to content
Navigation Menu
Provide feedback
Saved searches
Use saved searches to filter your results more quickly
Sign up
Appearance settings
Repository files navigation
Saper
Игра, аналогичная встроеной в Windows.
Написана на C# во время прохождения курса ООП в ХНУРЭ.
Демонстрирует применения паттерна Model-View. Цель паттерна — вынесение логики приложения из обработчиков событий WinForms.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 |
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace WindowsFormsApplication2 { public partial class Form1 : Form { # region Переменные private const int W = 40; private const int H = 40; private int[,] Fild = new int[Settings1.Default.MR + 2, Settings1.Default.MC + 2]; private int nMin; private int nFlag; private int status; # endregion # region Методы // Конструктор public Form1() { InitializeComponent(); newGame(); } // Новая игра private void newGame() { for (int row = 0; row <= Settings1.Default.MR + 1; row++) { Fild[row, 0] = -3; Fild[row, Settings1.Default.MC + 1] = -3; } for (int col = 0; col <= Settings1.Default.MC + 1; col++) { Fild[0, col] = -3; Fild[Settings1.Default.MR + 1, col] = -3; } this.ClientSize = new Size(W * Settings1.Default.MC + 1, H * Settings1.Default.MR + menuStrip1.Height + 1); int trow, tcol; int n = 0; int k; for (trow = 1; trow <= Settings1.Default.MR; trow++) for (tcol = 1; tcol <= Settings1.Default.MC; tcol++) Fild[trow, tcol] = 0; Random rnd = new Random(); do { trow = rnd.Next(Settings1.Default.MR) + 1; tcol = rnd.Next(Settings1.Default.MC) + 1; if (Fild[trow, tcol] != 9) { Fild[trow, tcol] = 9; n++; } } while (n != Settings1.Default.NM); for (trow = 1; trow <= Settings1.Default.MR; trow++) { for (tcol = 1; tcol <= Settings1.Default.MC; tcol++) { if (Fild[trow, tcol] != 9) { k = 0; if (Fild[trow - 1, tcol - 1] == 9) k++; if (Fild[trow - 1, tcol] == 9) k++; if (Fild[trow - 1, tcol + 1] == 9) k++; if (Fild[trow, tcol - 1] == 9) k++; if (Fild[trow, tcol + 1] == 9) k++; if (Fild[trow + 1, tcol - 1] == 9) k++; if (Fild[trow + 1, tcol] == 9) k++; if (Fild[trow + 1, tcol + 1] == 9) k++; Fild[trow, tcol] = k; } } } status = 0; nMin = 0; nFlag = 0; } // Отрисовка и расчет клеток поля private void showPole(Graphics g, int status) { for (int row = 1; row <= Settings1.Default.MR; row++) for (int col = 1; col <= Settings1.Default.MC; col++) this.kletka(g, row, col, status); } // Клетка поля private void kletka(Graphics g, int row, int col, int status) { int x; int y; x = (col - 1) * W + 1; y = (row - 1) * H + 1; if (Fild[row, col] < 100) { g.FillRectangle(Brushes.GreenYellow, x - 1, y - 1, W, H); } if (Fild[row, col] >= 100) { if (Fild[row, col] != 109) g.FillRectangle(Brushes.Khaki, x - 1, y - 1, W, H); else g.FillRectangle(Brushes.Red, x - 1, y - 1, W, H); if ((Fild[row, col] >= 101) && (Fild[row, col] <= 108)) g.DrawString((Fild[row, col] - 100).ToString(), new Font("Tahoma", 16, System.Drawing.FontStyle.Regular), Brushes.Indigo, x + 10, y + 7); } if (Fild[row, col] >= 200) { this.flag(g, x, y); } g.DrawRectangle(Pens.Black, x - 1, y - 1, W, H); if ((status == 2) && ((Fild[row, col] % 10) == 9)) { this.mina(g, x, y); } } // Открыть private void open(int row, int col) { int x = (col - 1) * W + 1; int y = (row - 1) * H + 1; if (Fild[row, col] == 0) { Fild[row, col] = 100; this.kletka(g, row, col, status); this.open(row, col - 1); this.open(row - 1, col); this.open(row, col + 1); this.open(row + 1, col); this.open(row - 1, col - 1); this.open(row - 1, col + 1); this.open(row + 1, col - 1); this.open(row + 1, col + 1); } else if ((Fild[row, col] < 100) && (Fild[row, col] != -3)) { Fild[row, col] += 100; this.kletka(g, row, col, status); } } // Так проще? ) private void mina(Graphics g, int x, int y) { g.DrawImage(Pic.m, new Point(x, y)); } // Так проще? ) private void flag(Graphics g, int x, int y) { g.DrawImage(Pic.f, new Point(x, y)); } # endregion # region Обработчики // private void panel1_MouseClick(object sender, MouseEventArgs e) { if (status == 2) return; if (status == 0) status = 1; int row = (int)(e.Y / H) + 1; int col = (int)(e.X / W) + 1; int x = (col - 1) * W + 1; int y = (row - 1) * H + 1; if (e.Button == MouseButtons.Left) { if (Fild[row, col] == 9) { Fild[row, col] += 100; status = 2; this.panel1.Invalidate(); const string message = "Вы взорвались:)!!! \n \n \n Играть еще??? "; const string caption = "проигрешь"; var result = MessageBox.Show(message, caption, MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (result == DialogResult.Yes) { newGame(); showPole(g, status); } else { Close(); } } else if (Fild[row, col] < 9) { this.open(row, col); } } if (e.Button == MouseButtons.Right) { if (Fild[row, col] <= 9) { nFlag += 1; if (Fild[row, col] == 9) { nMin += 1; } Fild[row, col] += 200; if ((nMin == Settings1.Default.NM) && (nFlag == Settings1.Default.NM)) { this.Invalidate(); const string message = "Хотите сыграть ещё??"; const string caption = "Победа!!!:)"; var result = MessageBox.Show(message, caption, MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (result == DialogResult.Yes) { newGame(); showPole(g, status); } else { Close(); } } else { this.kletka(g, row, col, status); } } else if (Fild[row, col] >= 200) { nFlag -= 1; Fild[row, col] -= 200; this.kletka(g, row, col, status); } } } // private void новаяИграToolStripMenuItem_Click(object sender, EventArgs e) { newGame(); showPole(g, status); } // private void оПрограммеToolStripMenuItem_Click(object sender, EventArgs e) { Form2 aboutBox = new Form2(); aboutBox.ShowDialog(); } // private void профессToolStripMenuItem_Click(object sender, EventArgs e) { Settings1.Default.MR = 16; Settings1.Default.MC = 30; Settings1.Default.NM = 99; Settings1.Default.Save(); this.ClientSize = new System.Drawing.Size(1202, 666); newGame(); showPole(g, status); } // private void любительToolStripMenuItem_Click(object sender, EventArgs e) { Settings1.Default.MR = 16; Settings1.Default.MC = 16; Settings1.Default.NM = 40; Settings1.Default.Save(); this.ClientSize = new System.Drawing.Size(642, 665); newGame(); showPole(g, status); } // private void новToolStripMenuItem_Click(object sender, EventArgs e) { Settings1.Default.MR = 9; Settings1.Default.MC = 9; Settings1.Default.NM = 10; Settings1.Default.Save(); this.ClientSize = new System.Drawing.Size(362, 385); newGame(); showPole(g, status); } // Отрисовка protected override void OnPaint(PaintEventArgs e) { showPole(e.Graphics, status); } # endregion } } |
Курсовой проект по .NET написанный на чистом C# с использованием WinForms
Задача проекта разработать приложение аналог легендарной игры Сапер.
«Сапер» – несложная игра, которая, тем не менее, развивает внимание, память и логику. Основная ее цель — найти и обозначить флажками мины. Нужно быть очень осторожным: как только вы открываете клетку, под которой спрятан взрывоопасный сюрприз, игра окончена. Сапер ошибается только один раз.
Архив содержит полные исходники всего приложения, установщик по пути Mine Sweeper/Debug/Mine Sweeper.msi, иконку, картинку с фоном, курсовой док-файл на 35 страниц, содержащий описание основной логики приложения, список использованной литературы и прочее.
Писался проект на Visual Studio 2017 Community
В Сапере реализовано:
- Новая игра
- Таймер
- Сброс незавершенной игры путём нажатия кнопки Новой игры
- Результат игры выигрыш/проигрыш выносится игроку в MessageBox с показанием затраченного на игру времени
- Стандартный выбор уровней сложности «Легко», «Средне», «Трудно» вшиты в игру
- По желанию игрок может самостоятельно задать размер игрового поля и количество мин на нём, а также изменить цвет закрытых клеток, наложены ограничения на «слишком» маленький или «слишком» большой размер игрового поля, максимально возможный размер поля 20×30=600 клеток, соответственно с количеством мин в 599 единиц(Ну думаю и этого любому пробователю хватит с головой:)), присутствуют другие ограничения на: «мин не может быть больше чем размер поля», «данные о полях не могут быть пустыми» и прочии.
Управление реализовывается мышью.
Левой кнопкой мыши открываются клетки. Правой — ставятся и убираются флаги.
Игрок выигрывает в том случае если все клетки с минами будут закрыты флажками(Кол-во флагов = Кол-ву мин)
Единственное отличие логики от оригинального Сапера в том, что в этом мы можем проиграть с первого хода. Так что игроку в этом плане придётся положиться только на свою удачу:) Всё остальное как оно есть.
namespace WindowsFormsApplication47 { public partial class Form1 : Form { private const int MR = 10, // кол-во клеток по вертикали MC = 10, // кол-во клеток по горизонтали NM = 10, // кол-во мин W = 40, // ширина клетки H = 40; // высота клетки // игровое (минное) поле private int[,] Pole = new int[MR + 2, MC + 2]; // значение элемента массива: // 0..8 - количество мин в соседних клетках // 9 - в клетке мина // 100..109 - клетка открыта // 200..209 - в клетку поставлен флаг private int nMin; // кол-во найденных мин private int nFlag; // кол-во поставленных флагов // статус игры private int status; // 0 - начало игры, // 1 - игра, // 2 – результат // графическая поверхность формы private System.Drawing.Graphics g; public Form1() { InitializeComponent(); // В неотображаемые эл-ты массива, соответствующие // клеткам границы игрового поля запишем число -3. // Это значение используется процедурой open() // для завершения рекурсивного процесса открытия // соседних пустых клеток for (int row = 0; row <= MR + 1; row++) { Pole[row, 0] = -3; Pole[row, MC + 1] = -3; } for (int col = 0; col <= MC + 1; col++) { Pole[0, col] = -3; Pole[MR + 1, col] = -3; } // устанавливаем размер формы в соответствии // с размером игрового поля this.ClientSize = new Size(W * MC + 1, H * MR + menuStrip1.Height + 1); newGame(); // новая игра // графическая поверхность g = panel1.CreateGraphics(); } // новая игра private void newGame() { int row, col; // индексы клетки int n = 0; // количество поставленных мин int k; // кол-во мин в соседних клетках // очистить поле for (row = 1; row <= MR; row++) for (col = 1; col <= MC; col++) Pole[row, col] = 0; // инициализация генератора случайных чисел Random rnd = new Random(); // расставим мины do { row = rnd.Next(MR) + 1; col = rnd.Next(MC) + 1; if (Pole[row, col] != 9) { Pole[row, col] = 9; n++; } } while (n != NM); // для каждой клетки вычислим кол-во // мин в соседних клетках for (row = 1; row <= MR; row++) for (col = 1; col <= MC; col++) if (Pole[row, col] != 9) { k = 0; if (Pole[row - 1, col - 1] == 9) k++; if (Pole[row - 1, col] == 9) k++; if (Pole[row - 1, col + 1] == 9) k++; if (Pole[row, col - 1] == 9) k++; if (Pole[row, col + 1] == 9) k++; if (Pole[row + 1, col - 1] == 9) k++; if (Pole[row + 1, col] == 9) k++; if (Pole[row + 1, col + 1] == 9) k++; Pole[row, col] = k; } status = 0; // начало игры nMin = 0; // нет обнаруженных мин nFlag = 0; // нет поставленных флагов } // рисует поле private void showPole(Graphics g, int status) { for (int row = 1; row <= MR; row++) for (int col = 1; col <= MC; col++) this.kletka(g, row, col, status); } // рисует клетку private void kletka(Graphics g, int row, int col, int status) { int x, y;// координаты левого верхнего угла клетки x = (col - 1) * W + 1; y = (row - 1) * H + 1; // не открытые клетки - серые if (Pole[row, col] < 100) g.FillRectangle(SystemBrushes.ControlLight, x - 1, y - 1, W, H); // открытые или помеченные клетки if (Pole[row, col] >= 100) { // открываем клетку, открытые - белые if (Pole[row, col] != 109) g.FillRectangle(Brushes.White, x - 1, y - 1, W, H); else // на этой мине подорвались! g.FillRectangle(Brushes.Red, x - 1, y - 1, W, H); // если в соседних клетках есть мины, // указываем их количество if ((Pole[row, col] >= 101) && (Pole[row, col] <= 108)) g.DrawString((Pole[row, col] - 100).ToString(), new Font("Tahoma", 10, System.Drawing.FontStyle.Regular), Brushes.Blue, x + 3, y + 2); } // в клетке поставлен флаг if (Pole[row, col] >= 200) this.flag(g, x, y); // рисуем границу клетки g.DrawRectangle(Pens.Black, x - 1, y - 1, W, H); // если игра завершена (status = 2), // показываем мины if ((status == 2) && ((Pole[row, col] % 10) == 9)) this.mina(g, x, y); } // открывает текущую и все соседние с ней клетки, // в которых нет мин private void open(int row, int col) { // координаты области вывода int x = (col - 1) * W + 1, y = (row - 1) * H + 1; if (Pole[row, col] == 0) { Pole[row, col] = 100; // отобразить содержимое клетки this.kletka(g, row, col, status); // открыть примыкающие клетки // слева, справа, сверху, снизу this.open(row, col - 1); this.open(row - 1, col); this.open(row, col + 1); this.open(row + 1, col); //примыкающие диагонально this.open(row - 1, col - 1); this.open(row - 1, col + 1); this.open(row + 1, col - 1); this.open(row + 1, col + 1); } else if ((Pole[row, col] < 100) && (Pole[row, col] != -3)) { Pole[row, col] += 100; // отобразить содержимое клетки this.kletka(g, row, col, status); } } // рисует мину private void mina(Graphics g, int x, int y) { // корпус g.FillRectangle(Brushes.Green, x + 16, y + 26, 8, 4); g.FillRectangle(Brushes.Green, x + 8, y + 30, 24, 4); g.DrawPie(Pens.Black, x + 6, y + 28, 28, 16, 0, -180); g.FillPie(Brushes.Green, x + 6, y + 28, 28, 16, 0, -180); // полоса на корпусе g.DrawLine(Pens.Black, x + 12, y + 32, x + 28, y + 32); // вертикальный "ус" g.DrawLine(Pens.Black, x + 20, y + 22, x + 20, y + 26); // боковые "усы" g.DrawLine(Pens.Black, x + 8, y + 30, x + 6, y + 28); g.DrawLine(Pens.Black, x + 32, y + 30, x + 34, y + 28); } // рисует флаг private void flag(Graphics g, int x, int y) { Point[] p = new Point[3]; Point[] m = new Point[5]; // флажок p[0].X = x + 4; p[0].Y = y + 4; p[1].X = x + 30; p[1].Y = y + 12; p[2].X = x + 4; p[2].Y = y + 20; g.FillPolygon(Brushes.Red, p); // древко g.DrawLine(Pens.Black, x + 4, y + 4, x + 4, y + 35); // буква M на флажке m[0].X = x + 8; m[0].Y = y + 14; m[1].X = x + 8; m[1].Y = y + 8; m[2].X = x + 10; m[2].Y = y + 10; m[3].X = x + 12; m[3].Y = y + 8; m[4].X = x + 12; m[4].Y = y + 14; g.DrawLines(Pens.White, m); } // щелчок кнопкой в клетке игрового поля private void panel1_MouseClick(object sender, MouseEventArgs e) { // игра завершена if (status == 2) return; // первый щелчок if (status == 0) status = 1; // преобразуем координаты мыши в индексы // клетки поля, в которой был сделан щелчок; // (e.X, e.Y) - координаты точки формы, // в которой была нажата кнопка мыши; int row = (int)(e.Y / H) + 1, col = (int)(e.X / W) + 1; // координаты области вывода int x = (col - 1) * W + 1, y = (row - 1) * H + 1; // щелчок левой кнопки мыши if (e.Button == MouseButtons.Left) { // открыта клетка, в которой есть мина if (Pole[row, col] == 9) { Pole[row, col] += 100; // игра закончена status = 2; // перерисовать форму this.panel1.Invalidate(); } else if (Pole[row, col] < 9) this.open(row, col); } // щелчок правой кнопки мыши if (e.Button == MouseButtons.Right) { // в клетке не было флага, ставим его if (Pole[row, col] <= 9) { nFlag += 1; if (Pole[row, col] == 9) nMin += 1; Pole[row, col] += 200; if ((nMin == NM) && (nFlag == NM)) { // игра закончена status = 2; // перерисовываем все игровое поле this.Invalidate(); } else // перерисовываем только клетку this.kletka(g, row, col, status); } else // в клетке был поставлен флаг, // повторный щелчок правой кнопки мыши // убирает его и закрывает клетку if (Pole[row, col] >= 200) { nFlag -= 1; Pole[row, col] -= 200; // перерисовываем клетку this.kletka(g, row, col, status); } } } // команда Новая игра private void новаяИграToolStripMenuItem_Click(object sender, EventArgs e) { newGame(); showPole(g, status); } // обработка события Paint панели private void panel1_Paint(object sender, PaintEventArgs e) { showPole(g, status); } // выбор в меню Справка команды О программе private void оПрограммеToolStripMenuItem_Click(object sender, EventArgs e) { Form2 aboutBox = new Form2(); aboutBox.ShowDialog(); } } }
I have always wondered how hard it would be to write a game in C#. I picked Minesweeper for my first try. Made an OO design and then went programming.
See the result in the downloadable zip file. The whole program is less then 400 lines of code!
Screenshot
I have reduced the complexity a bit by eliminating the flag-icon and all menu options.
Design
The application is build using 3 objects: a Form, a Game which holds an array of Square objects.
UML Class Diagram (Visio)
MinewsweeperForm class
This class creates a Game object when the Start button is clicked. It also updates the Timer and Mines display. It contains a Panel control in which the Game object generates the Squares.
Game class
This class contains a multidimensional array of Square objects which is initialized in the constructor. The mines are randomly placed in the squares. It has a Timer which is used to calculate the elapsed gametime. The events DismantledMinesChanged and Tick are used to notify the MinesweeperForm that is has to update the GUI.
Square class
This class contains a normal WinForm Button object which Click and MouseDown events are captured. These events trigger the real game logic. The number of surrounding mines is calculated (cascading). Mines are dismantled (marked) or explode which is reported to the Game object using events.
Conclusion
Writing the Minesweeper game was a lot of fun. It demonstrates how object orientation really makes it easy. Examine it by stepping trough the code using the VS.NET debugger. I hope you enjoyed this article and sample.
Any suggestions and feedback for improving this article is most welcome. Send your suggestions and feedback to Fons.Sonnemans@reflectionit.nl
Download
All postings/content on this blog are provided «AS IS» with no warranties, and confer
no rights. All entries in this blog are my opinion and don’t necessarily reflect
the opinion of my employer or sponsors. The content on this site is licensed under
a Creative Commons Attribution By license.
About Fons Sonnemans
Algemeen directeur, trainer, speaker, coach, developer