Как и с классом Polosa, я рекомендую завести отдельный модуль (не модуль класса, а просто «Module»), связанный с технологией использования ООП для элементов управления пользовательской формы. Назовите его, например, OOP_UF (от UserForm).
Применительно к классу Polosa мы говорили о том, что объекты обычно объединяются в массивы или в коллекции. Нумерация полос нам была неважна, поэтому мы использовали коллекцию. А вот для клеток нумерация важна, поэтому на этот раз мы используем вариант массива.
Public arr_kletki(9) As Kletka
По синтаксису и использованию класса эта строчка аналогична строке
Public Poloski As Object для класаа Polosa.
Подчеркнем мысль, которую вскользь упомянули выше: массив arr_kletki и представляет клетки на экране, и является структурой для хранения данных. Поэтому объявление массива фактически заменят строчку основного модуля программы:
Public arr_pole(9) As String
Действительно, массив arr_pole нам теперь не нужен. Соответственно, эту строчку можно удалить или пометить, как комментарий, указав, что таким было старое решение.
Следующий шаг – заполнение массива. Поскольку массив клеток будет заполняться в значительной степени кнопками, эта часть программного текста должна быть написана в программном модуле формы. Соответственно, нам надо сделать следующее:
§ создать новую форму (Меню – Insert – Class Module)
§ присвоить ей разумное имя, например F_Desk (от «доска» как в шашках или шахматах); в дальнейших примерах я буду считать, что форма называется именно так;
§ установить ширину формы в 150 (30*3 для доски и по 30 по краям), а высоту побольше, с запасом для двух управляющих кнопок.
§ получить программный модуль формы ((Меню – View – Code))
После этого можно вернуться к текущей задаче: заполнению массива клеток.
При создании коллекции мы сначала создавали объекты во временной переменной, а потом добавляли их к коллекции. Логика работы с массивом другая: массив сразу рождается состоящим из элементов (в нашем случае из 9), поэтому работать мы сразу будем с элементами массива. По этой же причине мы откажемся от вложенной процедуры типа AddKletka, а будем сразу выполнять все действия в процедуре инициализации клеток.
Приведем текст процедуры с комментариями.
Sub Kletki_InI()
Dim i As Integer Переменная-счетчик для цикла
For i = 1 To 9 Начало цикла, перебираем 9 клеток
Set arr_kletki(i) = New Kletka
Привязываем элемент массива к новому объекту класса kletka. Строка аналогична строке Set pls = New Polosa из работы с полосами, только (как говорилось выше) вместо объектной переменной (pls) используется элемент массива (arr_kletki(i)).
Set arr_kletki(i).Bttn =
F_Desk.Controls.Add("Forms.CommandButton.1", "bttn" & i)
Это одна «длинная» строка. Она создает новую пустую кнопку и связывает ее с полем Bttn ткущей клетки. Разберем эту строку чуть позже.
arr_kletki(i).create i
Запускаем метод класса kletka, который устанавливает начальные свойства нового объекта, в том числе экранные свойства новой кнопки. Строка аналогична строке pls.create a1, d из работы с полосами, только опять вместо объектной переменной (pls) используется элемент массива (arr_kletki(i)) ну и параметры связаны с особенностями метода для разных объектов..
Next i
End Sub
А теперь более подробно разберем ключевую и новую для нас строку, связывающую ООП с элементами управления:
Set arr_kletki(i).Bttn =
F_Desk.Controls.Add("Forms.CommandButton.1", "bttn" & i)
У пользовательской формы (F_Desk) есть коллекция Controls, которая содержит все элементы управления формы (получается F_Desk.Controls). К этой коллекции можно добавлять элементы с помощью метода Add; что эквивалентно созданию нового элемента управления. Собственно, программно элементы управления создаются именно так (получается F_Desk.Controls.Add). У метода Add два параметра: «программный идентификатор» (ProgID) создаваемого элемента и имя элемента. Программный идентификатор кнопки - "Forms.CommandButton.1". Так написано в справочнике и так работает. ProgID других элементов управления строятся аналогично. Имя элемента произвольно, но не должно совпадать у разных кнопок, поэтому в данном примере оно складывается из постоянной части “Bttn” и числового индекса, равного счетчику. Получается Bttn1, Bttn2 и т.д. Впрочем, в предлагаемом решении имена кнопок не обрабатываются и не используются.
Метод Add может использоваться как функция, т.е. возвращать ссылку на элемент управления, созданный «на лету». Именно так – справа от знака равенства – он используется в данной строке. Слева от знака равенства записано поле Bttn объекта Kletka, поле, созданное для ссылки на кнопку, как экранное представление клетки. Объект Kletka представлен элементом массива клеток.
Продолжим работу в модуле формы. Опишем старт проекта. В текущей версии мы отделим друг от друга запуск проекта и начало новой игры: к запуску проекта будет относится инициализация полосок и создание клеток, а к началу игры – очистка поля, расстановка «цветов» и запуск хода компьютера, если он играет крестиками. Начнем с запуска проекта.
Запуск проекта, использующего пользовательскую форму, должен быть описан в процедуре «инициализация» этой формы. Напомним способ автоматического получения этой процедуры:
§ из раскрывающегося списка объектов (над модулем, слева) выбрать UserForm
§ из раскрывающегося списка событий (над модулем, справа) выбрать Initialize
§ появившуюся «по дороге» процедуру UserForm_Click удалить.
В текст этой процедуры надо включить вызов двух процедур, отнесенных нами к типу «инициализация.
Private Sub UserForm_Initialize()
Poloski_Ini
Kletki_InI
End Sub
На этой стадии уже можно проверить работу проекта: запустить форму на выполнение (щелчок по заголовку формы и кнопка “Play”). Вы должны увидеть форму и на ней – 9 пустых клеток.
Завершим работу с формой.
§ Создайте две кнопки: «Играть крестиками» и «Играть ноликами».
§ Присвойте им разумные имена, например: CB_x и CB_o.
§ Получите заготовки событийных процедур для этих кнопок (двойной щелчок по кнопке на форме
Напишем их текст «сверху-вниз»:
Private Sub CB_x_Click() | |
Start | Очистка доски. Процедура пока не написана |
pl_clr = 1 | Расстановка «цветов» |
pc_clr = 2 | |
End Sub |
Текст процедуры CB_o_Click будет аналогичен, только в конце будет стоять вызов хода компьютера:
PC_hod
Эта процедура у нас есть, потребует ли она редактирования – пока непонятно.
Очистка доски будет состоять в вызове метода Start для всех 9 клеток:
Sub Start()
Dim i As Integer
For i = 1 To 9
arr_kletki(i).Start
Next i
End Sub
Теоретически можно запустить форму и проверить работу кнопок. Правда, щелчок по клетке «Играть крестиками» будет просто «ничего не делать», а щелчок по клетке «Играть ноликами» может вызвать ошибку из-за непроверенной процедуры PC_hod.
Вернемся в работе в основных модулях. В связи с переходом на новую структуру данных и новый интерфейс нам надо изменить два решения. Первое – это оболочка над данными kl_znak, позволяющая получить знак из клетки с указанным номером. Ее старый текст:
Function kl_znak(nom As Integer) As String
kl_znak = arr_pole(nom)
End Function
Теперь она будет выглядеть так:
Function kl_znak(nom As Integer) As String
kl_znak = arr_kletki(nom).kl_znak
End Function
Ключевая строчка этой функции обращается к элементу массива как объекту класса kletka и вызывает метод этого объекта kl_znak, описанный нами в модуле класса kletka. Обратите внимание, что мы имеем право называть функцию основного модуля и функцию-метод класса одним и тем же именем kl_znak – принцип полиморфизма в действии.
По организации работы я бы рекомендовал перенести эту процедуру из основного модуля в OOP_UF и там отредактировать ее.
Второе решение – это процедура, которая записывает новый знак в структуру данных и выводит его на экран. Приведем ее старое и новое решение в сравнении.
Sub hod_kletka(nom As Integer, clr As Integer) | |
Dim m_hod As Range | не нужна, т.к. другой интерфейс |
Dim zn As String | Dim zn As String |
zn = Mid("xo", clr, 1) | zn = Mid("xo", clr, 1) |
arr_pole(nom) = zn | arr_kletki(nom).new_zn zn |
Set m_hod = line_to_mesto(nom) | |
m_hod = zn | |
m_hod.Select | |
End Sub |
Заголовок процедуры (система параметров) остается без изменений. Это очень правильно, так как не придется ничего менять в строчках, вызывающих эту процедуру. Ключевая строка
arr_kletki(nom).new_zn zn
обращается к элементу массива как объекту класса kletka и вызывает метод этого объекта new_zn. Эта строка заменяет целых 4 строки старого решения, поскольку метод new_zn и записывает знак в данные (первая старая строка), и выводит его на интерфейс (три старые строки). По организации работы я также рекомендую перенести текст этой процедуры из основного модуля в модуль OOP_UF и «исправить» его.
Завершая работу с «нижним» уровнем программы, можно отметить, что функция is_pusto останется без изменений, поскольку обращается к данным не напрямую, а с использованием оболочки kl_znak, Это хороший аргумент за использование процедур-оболочек над данными.
Теперь посмотрим, что происходит на верхнем уровне программы – уровне действий пользователя. Во-первых отметим, что процедура Start основного модуля, которая запускалась с кнопки, «переехала» в инициализацию формы. Соответственно, в основном модуле ее нужно удалить (или «закрыть» комментариями).
Во-вторых обсудим изменившийся интерфейс действий игроков. Ход игрока теперь будет запускаться не щелчком по кнопке «Ход игрока», а щелчком по одной из 9 кнопок игрового поля. Следовательно, кнопка «ход игрока» больше не нужна. Возникает идея отказаться и от кнопки «Ход компьютера» и сделать так, чтобы компьютер автоматически делал ход после каждого хода игрока (либо при запуске «игры ноликами», что мы уже реализовали выше). Будем иметь в виду такой интерфейс.
Ход компьютера (процедура PC_hod). Анализ ее текста говорит о следующем:
§ Из текста может быть исключен фрагмент проверки начала игры (установки «цветов»), поскольку теперь мы делаем это явно кнопками «Играть крестиками» и «Играть ноликами».
§ Все вопросы, связанные с интерфейсом, решаются во вложенной процедуре hod_kletka, а текст этой процедуры мы уже изменили. Соответственно, больше изменений в процедуре PC_hod не будет.
Ход игрока (процедура Pl_hod.
Ход игрока теперь запускается щелчком по одной из 9 клеток игрового поля, поэтому он должен быть перенесен из основного модуля в событийную процедуру кнопок, представляющих эти клетки на экране. Напомним, что использование класса Kletka мы затеяли для того, чтобы не писать 9 событийных процедур, а создать одну универсальную, и сейчас мы подходим к самому интересному – оформлению такой процедуры. Итак:
§ Универсальная событийная процедура должна быть записана не в модуле формы, а в модуле класса Kletka;
§ Имя этой процедуры должно быть записано по обычным правилам событийной процедуры: <Объект>_<Событие>.
§ В качестве объекта надо использовать имя объектной переменной (поля класса Kletka), которая ссылается на кнопку, т.е. Bttn. Получается:
Sub Bttn_Click
Сделайте следующее
§ В модуле класса напишите вручную заголовок этой процедуры.
§ Перенесите в эту процедуру текст «старой» процедуры Pl_hod процедуры из основного модуля.
§ В основном модуле процедуру Pl_hod нужно удалить (или «закрыть» комментариями)
Пройдемся по старому тексту процедуры Pl_hod нужно
Проверку начала игры (в отличие от хода компьютера) можно сохранить: вдруг пользователь «по ошибке» щелкнет не «Играть крестиками», а сразу по игровому полю – в этом случае допустимо начать игру
Строчка
nom = mesto_to_line(ActiveCell)
не нужна, причем не только в таком варианте, но и в принципе. Когда мы щелкаем по кнопке, соответствующий объект класса Kletka ставится активным, и активизируются все его переменные с их значениями, в том числе переменная nom
Проверка щелчка за пределами игрового поля не нужна: теперь у игрока просто нет возможности это сделать. Так более совершенный интерфейс избавляет от необходимости писать защиту от неверных действий пользователя.
Проверка щелчка по занятой клетке должна остаться: от этой возможности мы не защищены.
Строка
hod_kletka nom, pl_clr
остается. При этом pl_clr – это по-прежнему глобальная переменная, а вот nom – теперь переменная класса. Если вы вдруг назвали эту переменную по-другому, используйте ваше имя. Напомним, что вопросы интерфейса в процедуре уже решены.
Последнее, что нужно добавить в соответствии с нашей новой договоренностью, это вызов ответного хода компьютера – процедуру PC_hod.
Можно проверять работу проекта.