Разработка игры «Сапер» в программе Microsoft Excel с использованием языка VBA
Игра будет похожа на свой аналог из Windows.Состояние поля (мины, флаги, открытые клетки, количество мин в соседних клетках) будем хранить в одном массиве fld. Вывод массива на экран будет осуществлять отдельная функция. То есть у нас получается отдельно логика игры и интерфейс. Еще нужно организовать взаимодействие с пользователем.
Поскольку работать будем не с формой, а с рабочим листом Excel, то выбор функций невелик. Кликом правой кнопки мыши будем ставить флаг, а двойным кликом левой кнопки будем открывать ячейку. Получается слегка непривычно после стандартной игры.
На рабочем листе выделяем диапазон 10 на 10 ячеек. Устанавливаем нужную ширину и высоту ячеек, границы. Присвоим имя этому диапазону, допустим fld. Вместо флага будем ставить букву F и закрашивать ячейку в зеленый цвет. Мину будет обозначать буква М и красный цвет ячейки. Ячейка с миной, на которой игрок подрывается, будет закрашена в более яркий цвет, чем другие мины. Общий вид игрового поля:
Переходим в редактор Visual Basic (Alt+F11) и создаем новый модуль. Назовем его saper.
Вот такие переменные нам нужны:
Public cntRow As Byte – количество строк поля
Public cntColumn As Byte – количество столбцов поля
Public cntMina As Byte – количество мин на поле
Public cntFoundMina As Byte – количество найденных мин
Public cntFlag As Byte – количество поставленных флагов
Public gameStatus As Byte – статус игры (0 – начало, 1 – игра, 2 – игра закончена).
Dim fld(1 To 12, 1 To 12) As Integer – массив, который содержит все клетки поля, включая границы.
Возможные значения элементов массива fld:
- 0..8 – количество мин во всех соседних клетках;
- 9 – в данной клетке мина;
- 100..109 – клетка открыта;
- 200..209 – в клетку поставлен флаг.
Нам нужны функции: новая игра, вывод поля на экран, установка флага, открытие незанятых минами ячеек (рекурсивная), открытие одной ячейки. И еще обработка нажатий клавиш мыши.
Функция Новая игра (NewGame)
Сначала инициализируем общие переменные. Затем присваиваем всем элементам массива значение -1. Затем элементам поля внутри границ (10 на 10) присваиваем значения 0. То есть мин пока на поле нет. Затем случайным образом (с помощью функции rnd) устанавливаем мины в ячейки. Продолжаем, пока количество установленных мин не будет равно нужному количеству.
После установки мин перебираем все элементы массива. Если в текущей ячейке нет мины, то считаем окружающие мины и записываем их число. Найденных мин и установленных флагов в начале игры нет, обнуляем эти значения. В завершение выводим массив на экран.
Public Sub NewGame()
Dim row, col As Byte
Dim n As Byte 'количество мин
Randomize
cntRow = 10
cntColumn = 10
cntMina = 10
gameStatus = 0
For row = 1 To cntRow + 2
For col = 1 To cntColumn + 2
fld(row, col) = -1
Next col
Next row
For row = 2 To cntRow + 1
For col = 2 To cntColumn + 1
fld(row, col) = 0
Next col
Next row
n = 0
Do
row = Int(cntRow * Rnd + 2)
col = Int(cntColumn * Rnd + 2)
If fld(row, col) <> 9 Then
fld(row, col) = 9
n = n + 1
End If
Loop While n <> cntMina
For row = 2 To cntRow + 1
For col = 2 To cntColumn + 1
If fld(row, col) <> 9 Then
n = 0
If fld(row - 1, col - 1) = 9 Then n = n + 1
If fld(row - 1, col) = 9 Then n = n + 1
If fld(row - 1, col + 1) = 9 Then n = n + 1
If fld(row, col - 1) = 9 Then n = n + 1
If fld(row, col + 1) = 9 Then n = n + 1
If fld(row + 1, col - 1) = 9 Then n = n + 1
If fld(row + 1, col) = 9 Then n = n + 1
If fld(row + 1, col + 1) = 9 Then n = n + 1
fld(row, col) = n
End If
Next col
Next row
cntFoundMina = 0
cntFlag = 0
showFld
End Sub
Функция вывода на экран.
В начале игры (статус игры – 0) выводим все ячейки, как закрытые, то есть значения равны «», цвет шрифта – черный, цвет ячейки – серый. Если статус игры 1 или 2, то выводим массив в зависимости от содержимого. Смотрим комментарии в тексте функции.
Public Sub showFld()
Dim row, col As Byte
If gameStatus = 0 Then
For row = 1 To cntRow
For col = 1 To cntColumn
Range("fld").Cells(row, col) = ""
Range("fld").Cells(row, col).Interior.Color = RGB(230, 230, 230)
Range("fld").Cells(row, col).Font.Color = RGB(0, 0, 0)
Next col
Next row
Exit Sub
End If
For row = 1 To cntRow
For col = 1 To cntColumn
‘ячейка закрыта
If fld(row + 1, col + 1) < 100 Then
Range("fld").Cells(row, col) = ""
Range("fld").Cells(row, col).Interior.Color = RGB(230, 230, 230)
‘в ячейке ненайденная мина и игра окончена
If (gameStatus = 2) And (fld(row + 1, col + 1) = 9) Then
Range("fld").Cells(row, col) = "M"
Range("fld").Cells(row, col).Interior.Color = RGB(250, 150, 150)
End If
End If
‘мин нет, ячейка открыта. Окрашиваем в светлый цвет
If fld(row + 1, col + 1) = 100 Then
Range("fld").Cells(row, col) = ""
Range("fld").Cells(row, col).Interior.Color = RGB(250, 250, 250)
End If
‘в соседних ячейках есть мины. В зависимости от их количества меняется цвет шрифта. Цвет ячейки светлый серый
If (fld(row + 1, col + 1) >= 101) And (fld(row + 1, col + 1) <= 108) Then
Range("fld").Cells(row, col) = fld(row + 1, col + 1) - 100
Range("fld").Cells(row, col).Interior.Color = RGB(250, 250, 250)
Select Case fld(row + 1, col + 1)
Case 101
Range("fld").Cells(row, col).Font.Color = RGB(10, 10, 250)
Case 102
Range("fld").Cells(row, col).Font.Color = RGB(10, 250, 10)
Case Is > 102
Range("fld").Cells(row, col).Font.Color = RGB(250, 10, 10)
End Select
End If
‘установлен флаг
If fld(row + 1, col + 1) >= 200 Then
Range("fld").Cells(row, col) = "F"
Range("fld").Cells(row, col).Interior.Color = RGB(10, 200, 10)
End If
‘игра окончена, показываем мины, найденные и нет
If (gameStatus = 2) And (fld(row + 1, col + 1) Mod 10 = 9) Then
Range("fld").Cells(row, col) = "M"
Range("fld").Cells(row, col).Interior.Color = RGB(250, 150, 150)
End If
‘на этой мине игрок подорвался. Делаем более красный цвет, чем обычная мина
If fld(row + 1, col + 1) = 109 Then
Range("fld").Cells(row, col) = "M"
Range("fld").Cells(row, col).Interior.Color = RGB(250, 100, 100)
End If
Next col
Next row
End Sub
Следующая процедура рекурсивно открывает текущую клетку и соседние пустые клетки
Public Sub openCell(row As Integer, col As Integer)
If fld(row, col) = 0 Then
fld(row, col) = 100
openCell row, col - 1
openCell row - 1, col
openCell row, col + 1
openCell row + 1, col
openCell row - 1, col - 1
openCell row - 1, col + 1
openCell row + 1, col - 1
openCell row + 1, col + 1
Else
‘открываем клетку, если она не граничная, то есть не равна -1
If (fld(row, col) < 100) And (fld(row, col) <> -1) Then
fld(row, col) = fld(row, col) + 100
End If
End If
End Sub
Открываем одну клетку.
Если игра закончена, то сразу выходим из процедуры. Если жто начало игры, то устанавливаем статус, равный 1, то есть игра началась. Если в клетке мина, то подрываемся и заканчиваем игру. Если мины нет, то открываем соседние пустые клетки. Затем выводим поле на экран.
Public Sub open1cell(row As Integer, col As Integer)
If gameStatus = 2 Then Exit Sub
If gameStatus = 0 Then gameStatus = 1
If fld(row, col) = 9 Then
fld(row, col) = fld(row, col) + 100
gameStatus = 2
showFld
MsgBox "Вы подорвались на мине! Игра закончена."
ElseIf fld(row, col) < 9 Then
openCell row, col
showFld
End If
End Sub
Установка флага
Если игра закончена, то сразу выходим из процедуры. Если жто начало игры, то устанавливаем статус, равный 1, то есть игра началась. Флаг устанавливаем только в открытые ячейки. Если в ячейке уже стоит флаг, то снимем его. При установке увеличиваем количество флагов, при снятии уменьшаем. Если в ячейке с устанавливаемым флагом на самом деле содержится мина, то увеличиваем счетчик найденных мин. И наоборот, счетчик уменьшаем, если флаг снят с ячейки с миной. И в конце сравниваем количество флагов, количество мин и количество найденных мин. Если они совпадают, то игрок выиграл и игра закончена.
Public Sub SetFlag(row As Integer, col As Integer)
If gameStatus = 2 Then Exit Sub
If gameStatus = 0 Then gameStatus = 1
‘клетка закрыта, ставим флаг
If fld(row, col) <= 9 Then
cntFlag = cntFlag + 1
‘в клетке мина
If fld(row, col) = 9 Then cntFoundMina = cntFoundMina + 1
fld(row, col) = fld(row, col) + 200
‘в клетку уже стоит флаг, снимем его
ElseIf fld(row, col) >= 200 Then
cntFlag = cntFlag - 1
fld(row, col) = fld(row, col) – 200
‘в клетке мина
If fld(row, col) = 9 Then cntFoundMina = cntFoundMina - 1
End If
‘все флаги установлены на всех минных ячейках
If (cntMina = cntFoundMina) And (cntFlag = cntMina) Then
gameStatus = 2
MsgBox "Все мины найдены! Игра закончена."
End If
showFld
End Sub
При открытии книги запустим новую игру:
Private Sub Workbook_Open()
NewGame
End Sub
Обработка двойного клика левой кнопки (открываем клетку)
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Dim row As Integer
Dim col As Integer
row = ActiveCell.row
col = ActiveCell.column
‘нажатие вне поля игры, выходим
If (row < 5) And (row > 14) Then Exit Sub
If (col < 3) And (col > 12) Then Exit Sub
‘открываем клетку, передавая в нее координаты, пересчитанные в наш диапазон из координат на листе Excel
open1cell row - 3, col – 1
‘отказ от стандартной обработки нажатия
Cancel = True
End Sub
Обработка клика правой кнопкой мыши
Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, Cancel As Boolean)
Dim row As Integer
Dim col As Integer
row = ActiveCell.row
col = ActiveCell.column
‘нажатие вне поля игры, выходим
If (row < 5) And (row > 14) Then Exit Sub
If (col < 3) And (col > 12) Then Exit Sub
‘вызываем процедуру установки флага в текущую ячейку, передавая ее координаты, пересчитанные в диапазон поля из диапазона листа
SetFlag row - 3, col – 1
‘отказ от стандартной обработки нажатия
Cancel = True
End Sub
Добавляем на лист кнопку (вкладка Разработчик), пишем текст «Новая игра», присваиваем ей макрос – процедуру NewGame из модуля saper.
Процесс игры
Процесс игры
Попали на мину:
Можно еще доделать различные размеры поля, запись рекордов и так далее.
