ПОНЯТНО О Visual Basic NET (том 2)

Использование массивов при программировании игр


Идеология. Есть ли польза от массивов при программировании игр? Вопрос праздный. Массивы необходимы и для шахмат, и для шашек, и для морского боя, и для крестиков-ноликов, и для многих других игр, в особенности для тех, где игра проходит на прямоугольном поле, расчерченном на квадраты. Возьмем для примера игру против компьютера в крестики-нолики на поле размером 3 на 3. Компьютеру приходится здесь рисовать на экране большие клетки, а в них – нолики (кружочки) после ваших ходов и крестики (пересекающиеся косые линии) после своих. Но этого умения недостаточно. Компьютеру ведь еще надо соображать, куда ставить крестики. А для этого нужно как минимум знать, где уже стоят крестики и нолики. А откуда он это знает? Если знание об этом хранится только на экране, то это очень неудобно, так как анализировать информацию о пикселях экрана трудно. Гораздо разумнее заранее организовать массив  Dim  a (3, 3) As Integer  и записывать туда в нужные места нолики после ходов человека и, скажем, единички после ходов компьютера. Сразу же после записи в элемент массива нуля или единицы программа должна рисовать в соответствующем месте экрана кружок или крестик. Мыслить компьютер мог бы при помощи примерно таких операторов –

If   a(1,1)=0   And   a(1,2)=0   Then   a(1,3)=1

Это очевидный защитный ход компьютера – на два кружочка в ряду он ставит в тот же ряд крестик.

Итак, сделаем вывод, что массив в памяти компьютера и поле для игры на экране в любой момент времени соответствуют друг другу, но компьютеру удобнее глядеть не на экран, а в память.

Проиллюстрируем идею использования массивов в играх подробнее, на специально придуманном примитивном примере (типа морского боя, но гораздо проще).



Задание на создание игры: Играют друг против друга два человека на квадратном поле размером 2 на 2:

Компьютер в игре участвует только как судья, а не как игрок.

Правила: Сначала первый игрок тайком от второго сообщает компьютеру, в каких двух клетках находятся его одноклеточные корабли (например, в правой верхней и правой нижней). Затем второй сообщает компьютеру, в какие клетки он производит два выстрела (тоже, конечно, наугад – например, в левую верхнюю и правую нижнюю). Если подбиты оба корабля – он выиграл, если подбит один – ничья, если ни одного – выиграл первый игрок.


Сначала запрограммируем эту игру без графики, а потом с графикой.
Поле боя должно быть показано на экране только один раз – после двух выстрелов, причем, если без графики, то в виде распечатки из 4 букв:
м к
о х
Здесь я использовал такие обозначения:
о                       - корабля здесь нет и сюда не стреляли
к                       - неподбитый корабль
х                       - подбитый корабль
м                      - мимо (стреляли и промахнулись)
Других вариантов быть не может. Вы видите, что приведенная распечатка отражает результат расстановки кораблей и выстрелов, описанных мной в качестве примера.
Вот программа без графики:
Dim a(2, 2) As String                                          'Поле боя
Dim i, j, Подбито As Integer
'Главная процедура:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        a(1, 1) = "о" : a(1, 2) = "о"                          'Поначалу на поле боя кораблей нет
        a(2, 1) = "о" : a(2, 2) = "о"
        Устанавливаем_корабль(1)
        Устанавливаем_корабль(2)
        Подбито = 0                                               'Пока не стреляли
        Выстрел(1)
        Выстрел(2)
        Показываем_поле_боя()
        Debug.WriteLine(Подбито)                        'Показываем исход битвы -  количество подбитых кораблей
End Sub
Sub Устанавливаем_корабль(ByVal Номер_корабля As Integer)
        i = InputBox("Первый игрок, назовите номер строки для корабля " & Номер_корабля)
        j = InputBox("Первый игрок, назовите номер столбца для  корабля " & Номер_корабля)
        a(i, j) = "к"
End Sub
Sub Выстрел(ByVal Номер_выстрела As Integer)
        i = InputBox("Второй игрок, назовите номер строки для выстрела " & Номер_выстрела)
        j = InputBox("Второй игрок, назовите номер столбца для выстрела " & Номер_выстрела)


        If a(i, j) = "к" Then                    'Если попал, то
            a(i, j) = "х"                            'ставим крестик
            Подбито = Подбито + 1     'и увеличиваем счетчик подбитых кораблей
        ElseIf a(i, j) = "о" Then             'иначе если промахнулся, то
            a(i, j) = "м"                           'ставим м
        End If
End Sub
Sub Показываем_поле_боя()
        For i = 1 To 2
            For j = 1 To 2
                Debug.Write(a(i, j))
            Next j
            Debug.WriteLine("")
        Next i
End Sub
Пояснения: Вы видите, что в качестве массива, представляющего поле для игры, я выбрал строковый массив
Dim a(2, 2) As String                                          'Поле боя
Теперь прочтите главную процедуру. Убедитесь, что она правильно отражает основной порядок действий в процессе игры. Затем разберитесь в процедурах Устанавливаем_корабль и Выстрел. Они с параметрами. Наконец, разберитесь в процедуре Показываем_поле_боя.
Программа не объявляет итогов боя, а всего лишь печатает количество подбитых кораблей. Определение и объявление победителя оставляю вам.
Обратите внимание, что игра мгновенно переделывается из игры на поле 2 на 2 в игру на поле, скажем, 30 на 30, простой заменой числа 2 в тексте программы на число 30. Этой возможностью мы наслаждаемся только благодаря использованию массива! В этом случае, конечно, придется в цикле записать во все клеточки поля букву «о». А если мы хотим при этом иметь больше двух кораблей и двух выстрелов, нам придется в главной процедуре обратиться в цикле к процедуре Устанавливаем_корабль и в цикле к процедуре Выстрел, что очень просто.
Если бы мы пренебрегли секретностью, то могли бы показывать поле боя после каждого хода игроков. Для этого достаточно в конец процедур Устанавливаем_корабль и Выстрел включить строку
        Показываем_поле_боя()
 
Программа с графикой. Придумаем для простоты такую графику. Поле состоит из 4 цветных квадратов. Вот возможные цвета:


Голубой квадрат                       - корабля здесь нет и сюда не стреляли
Серый квадрат                          - неподбитый корабль
Красный квадрат                      - подбитый корабль
Зеленый квадрат                      - мимо (стреляли и промахнулись)
Замечательно, что от добавлении графики программа абсолютно не изменится за исключением единственной процедуры Показываем_поле_боя. Причем и в ней-то вся структура цикла останется неизменной. По большому счету выкинем только строку
            Debug.WriteLine("")
как нужную только для текстового вывода, а строку, печатающую очередную букву из четырех:
                Debug.Write(a(i, j))
заменим фрагментом, рисующим очередной квадрат из четырех. Вот новая процедура Показываем_поле_боя:
Sub Показываем_поле_боя()
        Dim Размер As Integer = 100                            'Размер квадрата
        Dim Гр As Graphics = Me.CreateGraphics
        Dim Кисть_для_воды As New SolidBrush(Color.LightBlue)
        Dim Кисть_для_корабля As New SolidBrush(Color.Gray)
        Dim Кисть_для_попадания As New SolidBrush(Color.Red)
        Dim Кисть_для_промаха As New SolidBrush(Color.Green)
        Dim Кисть As SolidBrush                             'Текущая кисть для квадрата
        Гр.Clear(Color.White)                                          'Стираем поле, нарисованное после предыдущего хода
        For i = 1 To 2
            For j = 1 To 2
                Select Case a(i, j)                                       'Выбираем кисть для очередного квадрата
                    Case "о" : Кисть = Кисть_для_воды
                    Case "к" : Кисть = Кисть_для_корабля
                    Case "х" : Кисть = Кисть_для_попадания
                    Case "м" : Кисть = Кисть_для_промаха
                End Select                                                 'Рисуем очередной квадрат:
                Гр.FillRectangle(Кисть, Размер * j, Размер * i, Размер, Размер)


            Next j
        Next i
End Sub
Пояснения: Предположим, наша процедура работает после каждого хода игроков. Мы могли бы написать ее так, чтобы после каждого очередного хода (поставили корабль или выстрелили) компьютер перерисовывал только тот квадрат, о котором шла речь. Остальные ведь остались неизменными – чего их перерисовывать? В этом случае компьютеру пришлось бы «меньше трудиться». Но я пошел по более простому и универсальному пути – после каждого хода все поле со всеми квадратами стирается и рисуется заново согласно содержимому массива a.
Кстати, строка
        Гр.Clear(Color.White)                                          'Стираем поле, нарисованное после предыдущего хода
в нашем случае излишня. Ведь мы все равно заново перерисовываем все квадраты поля, поэтому предварительно стирать их не имеет смысла.
Крестики-нолики 3х3 – советы. В принципе вы уже готовы к программированию игры против компьютера в обычные крестики-нолики 3х3. Всю техническую сторону дела мы прошли. Остается логика, то есть объяснение компьютеру, куда ставить крестики. И вот с логикой-то у нас будет проблема. Вы скажете: Какая проблема? – ведь клеточек всего 9 штук! Один из возможных операторов уже написан:
If   a(1,1)=0   And   a(1,2)=0   Then   a(1,3)=1
Напишу еще пару десятков подобных операторов – и дело с концом! – А пару сотен не хотите?! Вы только попробуйте перебрать все возможные варианты расстановки крестиков, ноликов и пустых клеток! Их вообще несколько тысяч. В этом случае, чтобы сократить программу, нужно применять в качестве индексов переменные величины и ломать голову над тем, какие писать процедуры, ветвления и циклы.
Поэтому любителям игр рекомендую для тренировки запрограммировать крестики-нолики не против компьютера, а как игру человека с человеком, где компьютер – лишь судья. Если получится, вот тогда можно замахнуться и на большее. Но и здесь идите постепенно. Рекомендую написать большую процедуру для правильной простановки крестика в произвольном одномерном
массиве из трех клеток. У этой процедуры будет три параметра – по числу клеток. Затем заметьте, что в реальной игре 3х3 вас интересует только 8 рядов: 3 по горизонтали, 3 по вертикали и 2 по диагонали. Значит у вас будет 8 обращений к этой процедуре. Дальше думайте сами. Все это совсем не просто.

Содержание раздела