Разработка игры "Крестики-нолики"
МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ
РОССИЙСКОЙ ФЕДЕРАЦИИ
ФЕДЕРАЛЬНОЕ АГЕНСТВО ПО ОБРАЗОВАНИЮ
КУРГАНСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ
Кафедра автоматизации производственных процессов
Курсовая работа
Расчетно-пояснительная записка.
Дисциплина: Программирование и основы алгоритмизации
«Разработка игры «Крестики-нолики»
Студент: Иванов П. С.
Группа: Т-2144
Руководитель: Скобелев И. В.
Комиссия__________________
Оценка____________________
Дата защиты_______________
Курган, 2006
Введение
Стремительное развитие компьютерной техники в последние годы, появление мощнейших графических ускорителей и центральных процессоров способствовало не менее бурному развитию индустрии компьютерных игр. Выдающиеся разработки этой отрасли – это сложнейшие программы, как правило, с очень высокими требованиями к аппаратной части компьютера. Однако для возможности отдохнуть в перерыве от выполнения какой-либо работы оператору компьютера не всегда требуется новейшая компьютерная игра, а зачастую использовать её не позволяет маломощное оборудование офисного компьютера. Именно этой цели – отдыху от монотонной работы служит разработанная в рамках данного курсового проекта программа.
1. Техническое задание
Игровое поле представлено 25 клетками, как показано на рис.1.
Рис. 1. Игровое поле после запуска игры
Игра рассчитана на 2-х игроков, которые по очереди наводят указатель мыши на соответствующую клетку игрового поля и щелчком левой кнопки мыши ставят в ней крестик или нолик. Первый ход совершает пользователь, играющий крестиками. Победа присуждается игроку, который выстроит в линию последовательность четырех значков Х или О - по горизонтали, вертикали или диагонали, о чем выводится соответствующее сообщение. Также имеется возможность сохранять и загружать начатые игры (файлы сохранений имеют свой значок и расширение *.xvo). Начало новой игры - команда меню File->New (или значок , сохранение игры - File -> SaveAs… или File -> Save (или значок, открытие сохраненной игры - File -> Open…(или значок ), распечатка начатой игры File -> Print … (или значок ).
2. Блок-схема алгоритма
3. Описание работы программного продукта
В данном пункте следует привести описание работы программы с приведением необходимого кода, рассказать об основных и вспомогательных функциях, о назначении массивов и методов, структуре алгоритма программы.
Структура программы строится на пяти классах, каждый из которых создан на основе своего базового класса, взятого из MFC, это классы:
CXvsOv1App относится к работе самой программы;
CXvsOv1Doc занимается обработкой рабочих документов;
CXvsOv1View осуществляет отображение документов в рабочем окне;
CMainFrame обслуживает работу базового окна программы;
CAboutDlg обеспечивает работу диалогового окна About (О программе).
В программе данные хранятся в объекте document, а за их отображение отвечает объект view. Фактическим же местом вывода отображаемых данных является окно просмотра. Для SDI-программ данное окно перекрывает видимую клиентскую часть базового окна, которое появляется после компановки подготовленной в AppWizard программы. То, что выглядит как клиентская часть окна - светлая область, обрамленная сверху панелью инструментов, а снизу строкой состояния, - фактически является окном просмотра.
Используемые в программе данные хранится в виде массива полей длиной 1 байт, каждый из которых описывает текущее состояние отдельной клетки игрового поля; этот массив представляет собой элемент данных (data member) класса document. В любой момент класс view может запросить у класса document сведения по каждой клетке и отобразить их на экране. Кроме того, он добавляет в класс document сведения о крестиках и ноликах, когда пользователь щелкает по пустой клетке.
Клетки, образующие игровое поле, задаются матрицей CRect-элементов размерностью 5х5.
Блок инициализации переменной m_rect выглядит так:
CXvsOv1View::CXvsOv1View()
{
for (int i=0; i<5; i++) {
for (int j=0; j<5; j++) {
int x = (i * 70) + 10;
int y = - (j * 70) - 10;
m_rect[i][j].SetRect (x, y, x + 60, y - 60);
m_rect[i][j].NormalizeRect();
}
}}
Для отображения игры на экране используется система координат с единицей измерения, равной 0,01 дюйм. Точка (0,0) - начало координат - находится в верхнем левом углу окна; ось X направлена вправо, ось Y - вверх. Именно по этой причине Y-координаты клеток, назначаемые конструктором, имеют отрицательные значения, а не положительные. Если бы были указаны положительные значения Y, клетки оказались бы за пределами видимой части окна.
Прорисовка игрового поля осуществляется в методе OnDraw. Когда Windows-программа производит вывод на экран, принтер или любое другое устройство вывода, она это делает с использованием так называемого контекста устройства (device context - DC) - некоторой структуры данных, содержащей важные сведения о характеристиках конкретного выводного устройства и о параметрах, применяемых для вывода в данной программе. Поскольку спецификация Graphics Device Interface (интерфейс графических устройств - GDI) системы Windows позволяет получить аппаратно-независимую модель вывода, одна и та же функция будет работать с любым выводным устройством, для которого имеется соответствующий Windows-драйвер.
Вызов функции CDC::SetMapMode присваивает схеме соответствия для контекста устройства значение MM_LOENGLISH, предписывающее, что единицей измерения служит 0,01 дюйм (имеется в виду логический дюйм). Размер логического дюйма определяется некоторым принятым количеством пикселов, необходимым дл отображения реального дюйма на конкретном выводном устройстве. Например, при выводе на принтер один логический дюйм равняется одному физическому (реальному). При выводе на экран размер логического дюйма, как правило, колеблется от 1 до 1,5 физических.
Вложенный цикл for сначала перерисовывает квадраты, а затем, если от функций CXvsOv1Doc::GetSquare получено ненулевое значение, обращается к функции DrawX или DrawO (о функциях класса Doc и функциях DrawX или DrawO будет сказано позднее). Итак, метод OnDraw выглядит следующим образом (функция GetSquare, речь о которой пойдет ниже, осуществляет здесь повторную прорисовку окна после сворачивания):
void CXvsOv1View::OnDraw(CDC* pDC)
{
CXvsOv1Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
//
// Задать режим отображения MM_LOENGLISH,
// в котором за единицу измерения толщины принимается 0,01 дюйма:
//
pDC->SetMapMode (MM_LOENGLISH);
//
// Прорисовать игровое поле:
//
for (int i=0; i<5; i++){
for (int j=0; j<5; j++){
//цикл сначала перерисовывает квадраты, а затем, если от функций
//CXvsOv1Doc::GetSquare получено ненулевое значение, обращается к функции DrawX или DrawO
pDC->Rectangle (m_rect [i][j]);
BYTE bVal = pDoc->GetSquare (i,j);
if (bVal == 1) // Проставить Х
DrawX (pDC, &m_rect[i][j]);
else if (bVal == 2) // Проставить О
Draw0 (pDC, &m_rect[i][j]);
}
}
}
Далее следует рассказать о блоке, отвечающем за рисование крестиков и ноликов после щелчков мыши на клетках поля.
Kогда игрок щелкает левой клавишей и указатель мыши находится в пределах клиентской области окна, оно получает сообщение WM_LBUTTONDOWN. Для отслеживания подобных событий используется принадлежащая классу view функция OnLButtonDown. В начале модуля XvsOv1View находится карта сообщений - особая таблица, в которой устанавливаются соответствия между сообщениями и их обработчиками); с ее помощью будет обеспечиваться вызов функции OnLButtonDown при приеме View-модулем сообщения WM_LBUTTONDOWN. В составе функции OnLButtonDown также присутствует блок преобразования координат поля в размерность MM_LOENGLISH:
CClientDC dc (this);
dc.SetMapMode (MM_LOENGLISH);
dc.DPtoLP (&point);
Итак, функция OnLButtonDown при щелчке на клетке игрового поля обращается к функции GetSquare класса document, чтобы выяснить, оставлен ли в ней значок Х или О. Если GetSquare передает в качестве результата 0, значит клетка пуста, и OnLButtonDown вызывает функцию IsItXsTurn, чтобы получить информацию о том, какой значок нужно вставить - Х или О. Далее с помощью функций AddX или AddO класса document в клетку заносится крестик или нолик. Затем выполняется функция DrawX или DrawO класса view. В конце выполняется функция VinControl(). (Обо всех этих функциях будет сказано ниже). Функция OnLButtonDown выглядит следующим образом:
void CXvsOv1View::OnLButtonDown(UINT nFlags, CPoint point)
{
//
// Получаем указатель на класс document:
CXvsOv1Doc* pDoc = GetDocument ();
//
//Берется контекст устройства для клиентской области окна, в качестве схемы
// соответствия для контекста задается MM_LOENGLISH
CClientDC dc (this);
dc.SetMapMode (MM_LOENGLISH); //Конвертация CPoint-компонентов в MM_LOENGLISH
dc.DPtoLP (&point);
//
// Проверяем попадание указателя мыши
// на клетку игрового поля.
// Если да, рисуем Х или О.
//
BOOL bQuit = FALSE;
for (int i=0; i<5 && !bQuit; i++) {
for (int j=0; j<5 && !bQuit; j++) {
if (m_rect[i][j].PtInRect (point)) {
//Если после преобразования функция PtInRect передает ненулевое RETURN-значение,
//следовательно курсор находится внутри клетки. В этом случае переменные i и j
//содержит индексы, указывающие положение данной клетки.
//Если координаты, переданные аргументом CPoint, лежат за пределами всех
//имеющихся клеток, то вложенный цикл for заканчтвается
if (pDoc->GetSquare (i, j) == 0) {
if (pDoc->IsItXsTurn ()) {
pDoc->AddX (i, j);
DrawX (&dc, &m_rect[i][j]);
} else {
pDoc->AddO (i, j);
Draw0 (&dc, &m_rect[i][j]);
}
}
bQuit = TRUE;
}
}
}
CXvsOv1View::VinControl();
CView::OnLButtonDown(nFlags, point);
}
Для прорисовки крестиков и ноликов используется две функции класса view - DrawX и DrawO. В процессе рисования они используют функции вывода класса CDC. Сначала DrawX создает инструмент для рисования - красный карандаш (pen) для линий толщиной 10 условных единиц (для типа отображения MM_LOENGLISH одна условная единица эквивалентна 0,1 логического дюйма):
CPen pen (PS_SOLID, 10, RGB(255, 0, 0));
Затем с помощью следующих предложений рисуются две пересекающиеся линии:
pDC->MoveTo (rect.left, rect.top);
pDC->LineTo (rect.right, rect.bottom);
pDC->MoveTo (rect.left, rect.bottom);
pDC->LineTo (rect.right, rect.top);
Аналогичным образом функция DrawO создает свой инструмент для рисования - карандаш синего цвета для линий толщиной 10 условных единиц:
CPen pen (PS_SOLID, 10, RGB(0, 0, 255));
и рисует O с помощью MFC-функции CDC::Ellipse
pDC->Ellipse (rect);
В обоих случаях до начала каких-либо операций рисования нужный карандаш выбирается в контекст устройства с помощью функции CDC::SelectObject и возвращается обратно по завершении работ с ним:
CPen* pOldPen = pDC->SelectObject (&pen);
.
.
.
pDC->SelectObject (pOldPen);
Чтобы внутренняя область значка О не закрашивалась, в view-функции DrawO перед обращением к CDC::Ellipse выбирается в контекст NULL-кисть - не заполняющая область рисования:
pDC->SelectStockObject (NULL_BRUSH);
Функции DrawX и DrawO выглядят следующим образом:
void CXvsOv1View::DrawX(CDC *pDC, CRect *pRect)
{// Скопировать параметры переданного прямоугольника и
// уменьшить его размеры.
//
CRect rect;
rect.CopyRect (pRect);
rect.DeflateRect (10, 10);
//
// Создать красный карандаш и нарисовать им Х.
//
CPen pen (PS_SOLID, 10, RGB (255, 0, 0));
//нужный карандаш выбирается в контекст устройства с помощью функции CDC::SelectObject
CPen* pOldPen = pDC->SelectObject (&pen);
pDC->MoveTo (rect.left, rect.top); //
pDC->LineTo (rect.right, rect.bottom); //
pDC->MoveTo (rect.left, rect.bottom); //рисуются две пересекающиеся линии
pDC->LineTo (rect.right, rect.top); //
//карандаш возвращается обратно по завершении работ с ним
pDC->SelectObject (pOldPen);
}
void CXvsOv1View::Draw0(CDC *pDC, CRect *pRect)
{// Скопировать параметры переданного прямоугольника и
// уменьшить его размеры.
//
CRect rect;
rect.CopyRect (pRect);
rect.DeflateRect (10, 10);
//
// Создать синий карандаш и нарисовать им 0.
//
CPen pen (PS_SOLID, 10, RGB (0, 0, 255));
CPen* pOldPen = pDC->SelectObject (&pen);
pDC->SelectStockObject (NULL_BRUSH);
pDC->Ellipse (rect);//рисуется эллипс
//карандаш возвращается обратно по завершении работ с ним
pDC->SelectObject (pOldPen);
}
Функция VinControl()осуществляет проверку, выстроены ли в ряд 4 крестика или нолика и выводит соответствующие сообщения при выполнении этих условий. Функция содержит 56 циклов, при выполнении каждого из которого выводится соответствующее сообщение: "Крестики выстроили ряд!" или "Нолики выстроили ряд!". Функция VinControl()выглядит следующим образом:
void CXvsOv1View::VinControl()
{ CXvsOv1Doc* pDoc = GetDocument (); // Получаем указатель на класс document:
//Проверка победы крестиков по горизонталям
if ((pDoc->m_grid[0][0] == 1) && (pDoc->m_grid[1][0] == 1) && (pDoc->m_grid[2][0] == 1) && (pDoc->m_grid[3][0] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[1][0] == 1) && (pDoc->m_grid[2][0] == 1) && (pDoc->m_grid[3][0] == 1) && (pDoc->m_grid[4][0] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[0][1] == 1) && (pDoc->m_grid[1][1] == 1) && (pDoc->m_grid[2][1] == 1) && (pDoc->m_grid[3][1] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[1][1] == 1) && (pDoc->m_grid[2][1] == 1) && (pDoc->m_grid[3][1] == 1) && (pDoc->m_grid[4][1] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[0][2] == 1) && (pDoc->m_grid[1][2] == 1) && (pDoc->m_grid[2][2] == 1) && (pDoc->m_grid[3][2] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[1][2] == 1) && (pDoc->m_grid[2][2] == 1) && (pDoc->m_grid[3][2] == 1) && (pDoc->m_grid[4][2] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[0][3] == 1) && (pDoc->m_grid[1][3] == 1) && (pDoc->m_grid[2][3] == 1) && (pDoc->m_grid[3][3] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[1][3] == 1) && (pDoc->m_grid[2][3] == 1) && (pDoc->m_grid[3][3] == 1) && (pDoc->m_grid[4][3] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[0][4] == 1) && (pDoc->m_grid[1][4] == 1) && (pDoc->m_grid[2][4] == 1) && (pDoc->m_grid[3][4] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[1][4] == 1) && (pDoc->m_grid[2][4] == 1) && (pDoc->m_grid[3][4] == 1) && (pDoc->m_grid[4][4] == 1))
AfxMessageBox("Крестики выстроили ряд!");
//Проверка победы крестиков по вертикалям
if ((pDoc->m_grid[0][4] == 1) && (pDoc->m_grid[0][3] == 1) && (pDoc->m_grid[0][2] == 1) && (pDoc->m_grid[0][1] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[0][3] == 1) && (pDoc->m_grid[0][2] == 1) && (pDoc->m_grid[0][1] == 1) && (pDoc->m_grid[0][0] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[1][4] == 1) && (pDoc->m_grid[1][3] == 1) && (pDoc->m_grid[1][2] == 1) && (pDoc->m_grid[1][1] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[1][3] == 1) && (pDoc->m_grid[1][2] == 1) && (pDoc->m_grid[1][1] == 1) && (pDoc->m_grid[1][0] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[2][4] == 1) && (pDoc->m_grid[2][3] == 1) && (pDoc->m_grid[2][2] == 1) && (pDoc->m_grid[2][1] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[2][3] == 1) && (pDoc->m_grid[2][2] == 1) && (pDoc->m_grid[2][1] == 1) && (pDoc->m_grid[2][0] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[3][4] == 1) && (pDoc->m_grid[3][3] == 1) && (pDoc->m_grid[3][2] == 1) && (pDoc->m_grid[3][1] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[3][3] == 1) && (pDoc->m_grid[3][2] == 1) && (pDoc->m_grid[3][1] == 1) && (pDoc->m_grid[3][0] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[4][4] == 1) && (pDoc->m_grid[4][3] == 1) && (pDoc->m_grid[4][2] == 1) && (pDoc->m_grid[4][1] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[4][3] == 1) && (pDoc->m_grid[4][2] == 1) && (pDoc->m_grid[4][1] == 1) && (pDoc->m_grid[4][0] == 1))
AfxMessageBox("Крестики выстроили ряд!");
//Проверка победы крестиков по диагоналям снизу слева - вверх вправо
if ((pDoc->m_grid[0][3] == 1) && (pDoc->m_grid[1][2] == 1) && (pDoc->m_grid[2][1] == 1) && (pDoc->m_grid[3][0] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[0][4] == 1) && (pDoc->m_grid[1][3] == 1) && (pDoc->m_grid[2][2] == 1) && (pDoc->m_grid[3][1] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[1][3] == 1) && (pDoc->m_grid[2][2] == 1) && (pDoc->m_grid[3][1] == 1) && (pDoc->m_grid[4][0] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[1][4] == 1) && (pDoc->m_grid[2][3] == 1) && (pDoc->m_grid[3][2] == 1) && (pDoc->m_grid[4][1] == 1))
AfxMessageBox("Крестики выстроили ряд!");
//Проверка победы крестиков по диагоналям снизу справа - вверх влево
if ((pDoc->m_grid[1][0] == 1) && (pDoc->m_grid[2][1] == 1) && (pDoc->m_grid[3][2] == 1) && (pDoc->m_grid[4][3] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[0][0] == 1) && (pDoc->m_grid[1][1] == 1) && (pDoc->m_grid[2][2] == 1) && (pDoc->m_grid[3][3] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[1][1] == 1) && (pDoc->m_grid[2][2] == 1) && (pDoc->m_grid[3][3] == 1) && (pDoc->m_grid[4][4] == 1))
AfxMessageBox("Крестики выстроили ряд!");
if ((pDoc->m_grid[0][1] == 1) && (pDoc->m_grid[1][2] == 1) && (pDoc->m_grid[2][3] == 1) && (pDoc->m_grid[3][4] == 1))
AfxMessageBox("Крестики выстроили ряд!");
//---------------------------------------------------------------------------------
//Проверка победы ноликов по горизонталям
if ((pDoc->m_grid[0][0] == 2) && (pDoc->m_grid[1][0] == 2) && (pDoc->m_grid[2][0] == 2) && (pDoc->m_grid[3][0] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[1][0] == 2) && (pDoc->m_grid[2][0] == 2) && (pDoc->m_grid[3][0] == 2) && (pDoc->m_grid[4][0] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[0][1] == 2) && (pDoc->m_grid[1][1] == 2) && (pDoc->m_grid[2][1] == 2) && (pDoc->m_grid[3][1] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[1][1] == 2) && (pDoc->m_grid[2][1] == 2) && (pDoc->m_grid[3][1] == 2) && (pDoc->m_grid[4][1] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[0][2] == 2) && (pDoc->m_grid[1][2] == 2) && (pDoc->m_grid[2][2] == 2) && (pDoc->m_grid[3][2] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[1][2] == 2) && (pDoc->m_grid[2][2] == 2) && (pDoc->m_grid[3][2] == 2) && (pDoc->m_grid[4][2] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[0][3] == 2) && (pDoc->m_grid[1][3] == 2) && (pDoc->m_grid[2][3] == 2) && (pDoc->m_grid[3][3] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[1][3] == 2) && (pDoc->m_grid[2][3] == 2) && (pDoc->m_grid[3][3] == 2) && (pDoc->m_grid[4][3] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[0][4] == 2) && (pDoc->m_grid[1][4] == 2) && (pDoc->m_grid[2][4] == 2) && (pDoc->m_grid[3][4] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[1][4] == 2) && (pDoc->m_grid[2][4] == 2) && (pDoc->m_grid[3][4] == 2) && (pDoc->m_grid[4][4] == 2))
AfxMessageBox("Нолики выстроили ряд!");
//Проверка победы крестиков по вертикалям
if ((pDoc->m_grid[0][4] == 2) && (pDoc->m_grid[0][3] == 2) && (pDoc->m_grid[0][2] == 2) && (pDoc->m_grid[0][1] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[0][3] == 2) && (pDoc->m_grid[0][2] == 2) && (pDoc->m_grid[0][1] == 2) && (pDoc->m_grid[0][0] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[1][4] == 2) && (pDoc->m_grid[1][3] == 2) && (pDoc->m_grid[1][2] == 2) && (pDoc->m_grid[1][1] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[1][3] == 2) && (pDoc->m_grid[1][2] == 2) && (pDoc->m_grid[1][1] == 2) && (pDoc->m_grid[1][0] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[2][4] == 2) && (pDoc->m_grid[2][3] == 2) && (pDoc->m_grid[2][2] == 2) && (pDoc->m_grid[2][1] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[2][3] == 2) && (pDoc->m_grid[2][2] == 2) && (pDoc->m_grid[2][1] == 2) && (pDoc->m_grid[2][0] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[3][4] == 2) && (pDoc->m_grid[3][3] == 2) && (pDoc->m_grid[3][2] == 2) && (pDoc->m_grid[3][1] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[3][3] == 2) && (pDoc->m_grid[3][2] == 2) && (pDoc->m_grid[3][1] == 2) && (pDoc->m_grid[3][0] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[4][4] == 2) && (pDoc->m_grid[4][3] == 2) && (pDoc->m_grid[4][2] == 2) && (pDoc->m_grid[4][1] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[4][3] == 2) && (pDoc->m_grid[4][2] == 2) && (pDoc->m_grid[4][1] == 2) && (pDoc->m_grid[4][0] == 2))
AfxMessageBox("Нолики выстроили ряд!");
//Проверка победы крестиков по диагоналям снизу слева - вверх вправо
if ((pDoc->m_grid[0][3] == 2) && (pDoc->m_grid[1][2] == 2) && (pDoc->m_grid[2][1] == 2) && (pDoc->m_grid[3][0] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[0][4] == 2) && (pDoc->m_grid[1][3] == 2) && (pDoc->m_grid[2][2] == 2) && (pDoc->m_grid[3][1] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[1][3] == 2) && (pDoc->m_grid[2][2] == 2) && (pDoc->m_grid[3][1] == 2) && (pDoc->m_grid[4][0] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[1][4] == 2) && (pDoc->m_grid[2][3] == 2) && (pDoc->m_grid[3][2] == 2) && (pDoc->m_grid[4][1] == 2))
AfxMessageBox("Нолики выстроили ряд!");
//Проверка победы крестиков по диагоналям снизу справа - вверх влево
if ((pDoc->m_grid[1][0] == 2) && (pDoc->m_grid[2][1] == 2) && (pDoc->m_grid[3][2] == 2) && (pDoc->m_grid[4][3] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[0][0] == 2) && (pDoc->m_grid[1][1] == 2) && (pDoc->m_grid[2][2] == 2) && (pDoc->m_grid[3][3] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[1][1] == 2) && (pDoc->m_grid[2][2] == 2) && (pDoc->m_grid[3][3] == 2) && (pDoc->m_grid[4][4] == 2))
AfxMessageBox("Нолики выстроили ряд!");
if ((pDoc->m_grid[0][1] == 2) && (pDoc->m_grid[1][2] == 2) && (pDoc->m_grid[2][3] == 2) && (pDoc->m_grid[3][4] == 2))
AfxMessageBox("Нолики выстроили ряд!");
}
Теперь нужно рассказать о задаче, связанной с хранением данных о крестиках и ноликах, что возлагается на класс document. В нем существует принадлежащая переменная m_grid, представляющая собой массив 5х5, состоящий из 1-байт элементов. Каждый элемент этого массива соответствует одной клетке игрового поля. Для начала всем элементам присваиваются нулевые значения, чтобы все клетки оказались пустыми. При вводе X или O надлежащему элементу присваивается соответственно значение 1 или 2. Чтобы исключить возможность прямого обращения из класса view к массиву m_grid, добавлены две принадлежащие public-функции - AddX и AddO, которые вносят в этот массив сведения о крестиках и ноликах. Инициализация массива m_grid и обнуление его элементов происходит в функции OnNewDocument, то есть при создании каждого нового документа (при каждом начале новой игры). Итак вид функции OnNewDocument (о назначении переменной m_bXsTurn будет сказано позднее):
BOOL CXvsOv1Doc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
for (int i=0; i<5; i++)
for (int j=0; j<5; j++)
m_grid[i][j]=0;
// TODO: add reinitialization code here
// (SDI documents will reuse this document)
m_bXsTurn=TRUE;
return TRUE;
}
Для внесения сведений о крестиках и ноликах в массив m_grid используются принадлежащие ему две public-функции AddX и AddO. Функция AddX присваивает соответствующему элементу массива 1, а функция AddO – 2.
Однако прежде чем выбранная клетка будет заполнена, необходимо, чтобы в классе view выполнялась проверка, пуста ли данная клетка. Для этого в классе document существует принадлежащая public-функция с именем GetSquare, которая считывает содержащиеся в клетке с заданными координатами данные. Если в функцию GetSquare передаются разрешенные номера строки и столбца, она передает 0, если указанная клетка пуста; 1, если в ней находится X; или 2, если в ней O. Если ее return-значение -1, значит, был указан недопустимый номер строки или столбца.
Для отслеживания очередности существует принадлежащая переменная m_bXsTurn; когда ход X, ее значение устанавливается равным TRUE, когда ход O - FALSE. Поскольку m_bXsTurn, как и m_grid, относится к данным программы, то она относится к классу document. Ее начальное значение TRUE будет задаваться в CXvsOv1Doc::OnNewDocument; если в document добавляется X, ей присваиваевается значение FALSE, а если O – TRUE.
Итак функции OnNewDocument, AddX, AddO и IsItXsTurn .выглядят следующим образом (о назначении выставления флажка изменений SetModifiedFlag() сказано ниже) :
void CXvsOv1Doc::AddX(int i, int j)
//Это - принадлежащая массиву m_grid public-функция, которая будет вносить в него
//сведения о крестиках и ноликах.
{if ((i >= 0) && (i <= 4) && (j >= 0) && (j <= 4))
m_grid[i][j]=1; //Добавление X в массив
SetModifiedFlag (); // Выставить флажок изменений
// в классе document
m_bXsTurn=FALSE;
// переменной m_bXsTurn будет присваиваться значение FALSE, если в массив
// m_grid вносится X.
//Далее следует O
}
void CXvsOv1Doc::AddO(int i, int j)
//Это - принадлежащая массиву m_grid public-функция, которая будет вносить в него
//сведения о крестиках и ноликах.
{
if ((i >= 0) && (i <= 4) && (j >= 0) && (j <= 4))
m_grid[i][j]=2; //Добавление O в массив
SetModifiedFlag (); // Выставить флажок изменений
// в классе document
m_bXsTurn=TRUE;
//переменной m_bXsTurn будет присваиваться значение TRUE, если в массив
//m_grid вносится O.
//Далее следует X
}
BYTE CXvsOv1Doc::GetSquare(int i, int j)
{if ((i >= 0) && (i <= 4) && (j >= 0) && (j <= 4))
return m_grid[i][j];
return (BYTE) -1; //если в функцию GetSquare передаются разрешенные номера строки и столбца,
//она передает 0, если указанная клетка пуста; 1, если в ней находится X; или 2, если в ней O.
//Если return-значение ф-ции GetSquare равно -1, значит,
//был указан недопустимый номер строки или столбца.
return m_bXsTurn;
}
BYTE CXvsOv1Doc::IsItXsTurn() //путем обращений к функции IsItXsTurn класса document,
//объект класса View может получить сведения о том, чей ход.
//Полученное от нее ненулевое значение подразумевает очередь за X; а 0 - следующий ход O.
{
return m_bXsTurn;
}
Таким образом, последнее на что стоит обратить внимание в рамках данного пункта является задача о возможности сохранения и загрузки начатых игр. Для ее решения необходимо внести изменения в функцию Serialize(), принадлежащую уже созданному классу document, и добавить к ней операторы пересылки рабочих данных в объект CArchive или из него. Далее показано как будет выглядеть функция Serialize() класса document после внесения изменений, предназначенных для передачи переменных CXvsOv1Doc::m_grid и C XvsOv1Doc::m_bXsTurn в архив и обратно. Значения, содержащиеся в массиве m_grid, пересылаются последовательно один за другим при выполнении вложенного цикла for; затем сразу же обрабатывается переменная m_bXsTurn. Не имеет принципиального значения, какие данные переправляются в первую очередь, надо только соблюдать порядок. Если в архив отправляется сначала m_bXsTurn, а затем m_grid, тогда чтение из архива следует производить в аналогичном порядке.
void CXvsOv1Doc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
for (int i=0; i<5; i++)
for (int j=0; j<5; j++)
ar << m_grid[i][j];
ar << m_bXsTurn;
}
else
{
for (int i=0; i<5; i++)
for (int j=0; j<5; j++)
ar >> m_grid[i][j];
ar >> m_bXsTurn;
}
}
Чтобы исключить вероятность случайных потерь рабочих данных, корректно составленные программы должны предусматривать средства выдачи пользователю сообщений о необходимости сохранения еще не записанных данных, если программа получает команду завершить работу. В программах архитектуры document/view за решение этой небольшой задачи отвечают средства MFC, но только при том условии, если имеется информация о наличии в рабочем документе еще не сохраненных данных.
Для того чтобы оповестить MFC-механизм о подобном факте, следует обращаться к функции SetModifiedFlag() класса document, всякий раз когда в данные вносятся изменения. Она выставляет внутри класса document некий флажок, который проверяется средствами MFC перед завершением работы программы или при закрытии текущего документа. Если этот флажок выставлен, MFC выводит сообщение пользователю о присутствии еще не сохраненных изменений и предложение сохранить документ до начала дальнейшей обработки. После записи изменений флажок автоматически возвращается в исходное состояние, а класс document помечается как "чистый" (не имеющий изменений) до следующего обращения к SetModifiedFlag(). Вызов функции SetModifiedFlag() используется из функций AddX и AddO класса document.
Инструкция по инсталляции
Данная программа написана с использованием статической библиотеки MFC, поэтому она будет работать и на компьютерах без установленного Microsoft Visual C++. Инсталляции программа не требует, запуск игры производится файлом XvsOv1.exe расположенном в каталоге Debug, который находится в корневой папке программы.
Минимальные системные требования:
-Windows XP Service Pack 1;
-ЦПУ с частотой не ниже 1 ГГц;
-Видеоадаптер с 64 Mб памяти;
-32 Мб оперативной памяти;
-16 Мб свободного дискового пространства;
-мышь.
Инструкция программисту
Данная программа может быть подвергнута доработке с целью упрощения исполняемых кодов. Усовершенствования должны коснуться в первую очередь функции VinControl() класса View. Здесь выполняется 56 циклов for. После выполнения одного из них хорошо было бы прервать проверки и вывести диалог с предложением начать новую игру. Кроме того можно попытаться уменьшить число циклов. Также можно ввести диалог по настройке цветов крестиков, ноликов и клеток игрового поля.
При тестировании ошибочных ситуаций не выявлено.
6. Инструкция оператору
Правила игры подробно описаны в пункте 1., а также при выполнении команды меню Help -> About XvsOv1…(или по значку на панели инструментов) В случае возникновения нештатных ситуаций необходимо вызвать Диспетчер задач Windows двойным нажатием комбинации клавиш Alt+Ctrl+Delete и на вкладке приложения выбрать *-XvsOv1 и нажать кнопку «Снять задачу».
7. Инструкция по проверке и тестированию
Для проверки правильности работы алгоритмов программы необходимо запустить XvxOv1.exe и выстроить по горизонтали, вертикали или диагонали ряд из 4-х крестиков или ноликов. В случае корректной работы программа будет выводить сообщения соответственно "Крестики выстроили ряд!" или "Нолики выстроили ряд!".
Затем надо выполнить команды меню File -> SaveAs… чтобы сохранить начатую игру, после File->New, чтобы начать новую игру, и затем File -> Open…, чтобы произвести загрузку сохраненной игры.
8. Вывод
Итак, несмотря на внешнюю простоту, программа представляет собой сложную систему взаимодействующих друг с другом функций. Потребовалось немало сил и времени на ее написание. Кроме того, потребовалось привлечение большого объема дополнительной информации. И все же может быть усовершенствован сам код программы, а также добавлены новые функции.
Литература
Холзнер, С. Visual C++6. Учебный курс. – СПб:. Питер, 2006. – 570 с:. ил.
Давыдов, В.Г. Программирование и основы алгоритмизации: Учеб. пособие/В.Г. Давыдов. –М.: Высш. шк., 2003. -447 с.: ил.
Интернет-ресурс http://www.firststeps.ru/
1