Теперь наша программа почти готова к работе.

Мы предлагаем вашему вниманию третью статью нашей серии с подробным изложением этапов разработки программы для игры в "Крестики-нолики" (англ. название - tic-tac-toe), выполняемой средствами Microsoft Visual C++. В первой из них мы показали, как с помощью AppWizard создать базовый исходный текст этой программы. Во второй - подготовили функцию OnDraw класса view, предназначенную для рисования квадратного игрового поля с клетками (и их число по вертикали и горизонтали - 5х5), и с помощью "Мастера" ClassWizard составили программу для обработки происходящих с мышью событий, которая при щелчках игрока левой клавишей на любой клетке выводит на экран сообщение. Наконец, настало время заняться классом document CTicDoc, где будут храниться сведения о введенных игроком крестиках и ноликах.

Запустите Visual C++ и откройте проект, содержащий файлы исходных текстов для этой программы. Убедитесь, что перед вами окно Project Workspaces, и щелкните вкладку ClassView, чтобы отобразить список используемых в нашей программе классов.

Хранение данных о крестиках и ноликах

Обычно в программе, создаваемой средствами Visual C++, данные хранятся в объекте document, а их графическое представление обеспечивается классом view. "Мастер" AppWizard подготовил для нас пустые классы document и view, производные от классов CDocument и CView библиотеки MFC. Мы уже дополнили класс view средствами, позволяющими отображать на экране несложную картинку.

Теперь приступили к задаче, связанной с хранением данных о крестиках и ноликах, что возлагается на класс document. Добавим в него новую принадлежащую переменную m_grid, представляющую собой массив 5х5, состоящий из 1-байт элементов. Каждый элемент этого массива будет соответствовать одной клетке игрового поля. Для начала всем элементами будут присвоены нулевые значения, чтобы все клетки оказались пустыми. При вводе X или O надлежащему элементу будет присваиваться соответственно значение 1 или 2. Чтобы исключить возможность прямого обращения из объекта view к массиву m_grid, мы добавим две принадлежащие public-функции - AddX и AddO, - которые будут вносить в этот массив сведения о крестиках и ноликах.

Можно было бы добавить массив m_grid в класс CTicDoc путем внесения изменений в заголовочный файл TicDoc.h, но более простой и наглядный способ - выполнить это средствами Visual C++. В списке классов, представленном в окне Project Workspaces, щелкните правой клавишей мыши на элементе CTicDoc и выберите из контекстного меню команду Add Variable (Добавить переменную). Заполните поля в диалоговом окне Add Member Variable (Добавить компонентную переменную). Не забудьте установить соответствующий переключатель в положение Protected, чтобы m_grid была объявлена как protected-компонента (защищенная). Согласно правилам языка C++, защищенные переменные доступны только в рамках класса, внутри которого дается их определение. Поэтому принадлежащие функции будем задавать как public, для того чтобы с их помощью можно было получить доступ из других классов к данным о клетках игрового поля, хранящимся в классе document. Ограничение доступа к массиву m_grid рамками класса document и возможность обращений к нему из других классов только через принадлежащие public-функции позволяет нам обеспечивать целостность записываемых данных, поскольку решение об изменении значений массива принимается только внутри класса document. Теперь щелкните кнопку OK, и массив m_grid будет внесен в класс document.

Чтобы проверить, действительно ли массив m_grid внесен в этот класс, просмотрите список принадлежащих функций и переменных класса CTicDoc, щелкнув на вкладке ClassView. Найдите в нем пиктограмму, помеченную как m_grid. Если дважды щелкнуть на ней, то вы увидите строку файла CTicDoc.h, содержащую определение массива m_grid.

Теперь нам предстоит обнулить все элементы массива m_grid. В обычных программах на Cи++ инициализация переменных выполняется с помощью конструктора класса. Однако в программах Visual C++ на основе спецификации SDI (Single-Document Interface - интерфейс программ, обеспечивающих единовременную обработку только одного документа) объекты document и view создаются единожды - в начале работы программы, затем используются всякий раз, когда открываются и закрываются документы. Если начальные значения для массива m_grid устанавливаются с помощью конструктора класса document, эта операция исполняется лишь один раз. Нам же нужно, чтобы инициализация значений m_grid происходила при начале каждой новой игры, поэтому мы добавили соответствующий фрагмент программы в функцию OnNewDocument этого класса. Это виртуальная функция класса CDocument, которая вызывается при создании каждого нового документа. Мастер AppWizard уже переопределил ее в классе CTicDoc, поэтому единственное, что требуется от нас, - вставить фрагмент для задания начальных значений элементов массива. Расскажем, как это делается.

Сначала, щелкнув на вкладке ClassView, найдите в списке принадлежащих функций класса CTicDoc элемент OnNewDocument и дважды щелкните на нем, чтобы на экране появился исходный текст CTicDoc::OnNewDocument. На лист. 1 показан окончательный вариант этой функции - нам нужно добавить строки, начинающиеся с оператора for.

Вставка крестиков и ноликов

Теперь, когда мы добавили массив m_grid и присвоили начальные значения его элементам, приступим к подготовке функций, которые будут использоваться в классе view для внесения в этот массив сведений о крестиках и ноликах. Сначала с помощью "мастера" ClassWizard создадим прототипы этих функций, а затем добавим предложения для изменения значений отдельных элементов массива.

В списке, появившемся после щелчка на закладке ClassView, щелкните правой клавишей мыши на элементе CTicDoc и выберите в контекстном меню команду Add Function (Добавить функцию).

Введите в диалоговом окне Add Member Function (Ввод принадлежащей функции) в поле Function Type (Тип функции) слово "void", а в поле Function Declaration (Объявление функции) строку "AddX(int i, int j)". Убедитесь, что соответствующий переключатель в панели Access (Доступ) установлен в положение Public. Щелкните OK.

Добавьте в CTicDoc::AddX следующие строки:

if ((i >= 0) && (i <= 4) && (j >= 0) && (j <= 4)) 
	m_grid[i][j]=1;

Обратите внимание: передаваемые в функцию номера строки и столбца проверяются на принадлежность заданному диапазону. Крайней необходимости в такой проверке нет, поскольку нам доподлинно известно, что все передаваемые из класса view значения не выходят за оговоренные границы. Однако в соответствие с правилами хорошего стиля программирования необходимо предусмотреть возможность взаимодействия класса document с какими-то другими классами view, помимо нашего, которые, как может оказаться, не очень хорошо продуманы.

Повторите эту процедуру, чтобы добавить в класс document также функцию AddO; только теперь m_grid[i][j] будут присваиваться значения 2, а не 1. Окончательный текст функций CTicDoc::AddX и CTicDoc::AddO показан на лист. 2.

Функция CTicDoc::GetSquare

Теперь в класс document входит массив для записи данных об X и O, а также принадлежащие public-функции для внесения в него значений соответствующих значкам X и O. Однако прежде чем выбранная клетка будет заполнена, необходимо, чтобы в классе view выполнялась проверка, пуста ли данная клетка. Далее мы рассказываем, как добавить в класс document принадлежащую public-функцию с именем GetSquare, которая считывает содержащиеся в клетке с заданными координатами данные.

Вновь щелкните в ClassView правой клавишей мыши на элементе CTicDoc и выберите в контекстном меню команду Add Function.

Введите в диалоговом окне Add Member Function в поле Function Type слово "BYTE", а в поле Function Declaration - строку "GetSquare(int i, int j)". Щелкните OK, и эта функция будет создана.

Добавьте в тело функции CTicDoc::GetSquare следующие строки:

if ((i >= 0) && (i <= 4) && (j >= 0) && (j <= 4)) 
	return m_grid[i][j]; 
return (BYTE) -1;

Окончательный вариант функции показан на лист. 3.

Теперь, если в функцию GetSquare передаются разрешенные номера строки и столбца, она передает 0, если указанная клетка пуста; 1, если в ней находится X; или 2, если в ней O. Если ее return-значение -1, значит, был указан недопустимый номер строки или столбца.

Чья очередь?

Подготовка класса document практически завершена. Однако пока не был затронут еще один вопрос: каким образом в класс view передается информация о том, что проставить в пустой клетке - X или O? В нашей программе необходимо где-то вести запись очередности хода игроков. В конце концов, когда текущий документ будет записываться на диск, эту информацию также следует сохранять вместе с данными массива m_grid. В противном случае при повторной загрузке начатой ранее игры будет неизвестно, чей следующий ход.

Для отслеживания очередности создадим принадлежащую переменную m_bXsTurn; когда ход X, ее значение устанавливается равным TRUE, когда ход O - FALSE. Поскольку m_bXsTurn, как и m_grid, относится к данным нашей программы, отнесем ее к классу document. Ее начальное значение TRUE будет задаваться в CTicDoc::OnNewDocument; если в document добавляется X, ей будет присваиваться значение FALSE, а если O - TRUE. Теперь об этом более подробно.

В списке ClassView щелкните правой клавишей на CTicDoc, выберите команду Add Variable и добавьте в класс document принадлежащую protected-переменную m_bXsTurn с типом BOOL (Boolean). Выберите функцию OnNewDocument класса document и установите переменной m_bXsTurn начальное значение TRUE.

Добавьте в CTicDoc::AddX предложение, с помощью которого переменной m_bXsTurn будет присваиваться значение FALSE, если в массив m_grid вносится X. Аналогичным образом добавьте предложение и в CTicDoc::AddO, чтобы m_bXsTurn присваивалось значение TRUE при внесении значка O.

В списке ClassView щелкните правой клавишей но CTicDoc, выберите команду Add Function и создайте принадлежащую public-функцию с именем IsItXsTurn, которая будет передавать в качестве результата значение m_bXsTurn.

На лист. 4 показан новый исходный текст функций OnNewDocument, AddX, AddO и IsItXsTurn. Теперь в классе document хранится информация о том, чья очередь, а значение m_bXsTurn при внесении в массив m_grid крестика или нолика автоматически меняется. Кроме этого объект view получает сведения о том, чей ход, путем обращений к функции IsItXsTurn класса document. Полученное от нее нулевое значение подразумевает очередь за X; а 0 - следующий ход O.

Подведем итоги

Вот теперь мы реализовали все функции класса CTicDoc. Мы предусмотрели принадлежащие public-функции для добавления в клетки игрового поля значков X и O, для считывания информации, содержащейся в конкретной клетке и, выяснения, чей следующий ход. Все данные класса document хранятся в его переменных и доступны лишь через соответствующие принадлежащие функции.

В части 4 мы займемся задачей взаимодействия классов document и view, т. е. чтобы при щелчке на пустой клетке проставлялся значок X или O, а в массиве m_grid класса document производилась соответствующая запись. Мы внесем изменения в функцию OnDraw класса view, чтобы добиться точного отображения состояния игрового поля с внесенными X и O. На этом разработка программы будет практически завершена, если не считать некоторых окончательных штрихов.


Страница 1 -> Страница 2 -> Страница 3 -> Страница 4 -> Страница 5 ->