Разработка программы для реализации игры "Сапер"
Обнаружение всех скрытых на игровом поле мин, расставленных компьютером, путем расчета вероятного местоположения - сущность игры "Сапер". Информация о доменах пустот как необходимые данные для реализации автоматического открытия прилежащих ячеек.
Рубрика | Программирование, компьютеры и кибернетика |
Вид | курсовая работа |
Язык | русский |
Дата добавления | 18.02.2019 |
Размер файла | 322,5 K |
Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже
Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.
Размещено на http://www.allbest.ru
Размещено на http://www.allbest.ru
Введение
Курсовая работа подразумевает собой создание игры. Суть игры заключается в том, чтобы обнаружить все скрытые на игровом поле мины, расставленные компьютером, путем расчета вероятного местоположения мин исходя из доступной информации. Обнаружив мину, пометим её, делая щелчок правой кнопкой мыши по соответствующей ячейке игрового поля. В игре существует возможность перед стартом игры задать размеры поля, тем самым создать индивидуальную, уникальную игру. Минимальный размер поля: 5х5, максимальный размер 64х64. (Данные размеры были выбраны опытным путем для достижения наилучшего визуального отображения). Максимальные/минимальные размеры можно пересмотреть и легко внести изменения в исходный код. После выбора размеров создается поле на котором компьютер случайным образом расставляет мины. Количество мин так же является случайным и зависит от размеров игрового поля. Слева внизу расположен игровой таймер, отображающий текущее затраченное время, который будет запущен, как только будет сделан первый ход (произойдет открытие или будет помечена любая из ячеек как заминированная). Справа внизу расположен счетчик непомеченных мин. Условие завершения игры:
1) Игра выиграна - игрок пометил всё количество мин, при этом все пометки расставлены верно (контроль с помощью специальной функции);
2) Игра проиграна - игрок попал в ячейку содержащую мину.
После окончания игры будет выведено сообщение - результат (выигрыш/проигрыш) и время, затраченное на текущую игру. Так же результат игры будет записан в статистику.
1. Постановка задачи
Разобраться в правилах игры, придумать контрольные примеры, структуры данных для игры.
Правила игры «Сапер»:
Цель игры - обнаружить все заминированные ячейки.
Правила игры «Сапер» очень просты:
Игроку дается поле выбранного размера.
Игрок поочередно открывает закрытые ячейки поля, попутно помечая возможные места расположения мин. Левый клик мышкой - открыть ячейку, правый - пометить как опасную. При попадании в пустую ячейку, дополнительно будут открыты все пустые ячейки, входящие в текущий «домен пустот» (участки пустоты на игровом поле). В процессе открытия ячеек будет доступна информация о соседствующих вокруг текущей ячейки, заминированных ячейках.
Если игрок открывает ячейку, в которой находится мина - игра окончена.
2. Проектирование
Рисунок 1 - Используемые в игре классы
В программе реализованы такие классы (см. рисунок 1):
Model - представление игры в памяти компьютера (массив структур);
View - класс отображения модели (Отрисовка поля, ячеек и других элементов окна);
Statistic - сбор и отображение статистики о играх (Дата игры, параметры поля, время затраченное на завершения игры, результат игры);
Game - класс обеспечивающий процесс игры. (Запуск и остановка процесса игры, сбор статистики (Statistic) о текущей игре, отрисовка Model используя View).
3. Описание модулей
3.1 Класс Model
Реализует представление игры в памяти компьютера.
Поле представлено в виде вектора структур, в качестве таких структур выступает описание ячейки. Структура описания ячейки выглядит следующим образом:
struct Cell
{
// Состояние ячейки {Закрыта, Открыта, Установлен флаг обнаружения мины}
CellState state;
// Присутствует ли тут мина
bool hasMine;
};
В классе реализованы следующие методы:
SetMines() - метод расстановки мин на поле. Исходя из заданных параметров поля, генерируется определенное количество мин. Затем происходит расстановка мин на поле случайным образом, при этом существует проверка, позволяющая установить ровно одну мину в ячейку.
SetCell(x, y, CellState) - метод изменения состояния ячейки по индексу {x, y} на заданное состояние.
GetCell(x, y) - получить содержимое ячейки. Возвращаемое значение метода - ссылка на ячейку игрового поля.
Open(x, y) - открыть ячейку по заданным координатам. В качестве координат выступают: x - индекс столбца, y - индекс строки.
Mark(x, y) - пометить ячейку, с заданными координатами, как ячейку содержащую мину. Стоит заметить, что открытие помеченной ячейки в игровом процессе будет запрещено до тех пор, пока метка не снята.
FlagsCorrect() - метод проверки расстановки флагов. Возвращаемое значение: true - если все метки на поле расставлены верно, false - в противном случае.
Также внутри класса Model есть класс Domain, который генерирует дополнительную информацию о поле - список образованных пустот на карте (рисунки 2, 3).
Рисунок 2 - Домены пустот (2 шт.)
компьютер игровой ячейка домен
Рисунок 3 - Домены пустот (5 шт.)
Информация о доменах пустот необходима для автоматического открытия прилежащих ячеек. При открытии пустой ячейки - автоматически открываются все ячейки, входящие в текущий домен пустот, так же дополнительно открываются прилежащие информационные ячейки (ячейки с количеством заминированных соседей).
3.2 Класс View - отображение модели
Класс View содержит указатель на модель, которую в последствии этот класс и отображает, обращаясь к полям и функциям объекта типа Model.
View содержит следующие методы:
ReSetModel(Model *mod) - метод позволяющий заменить текущий объект модели на новый, переданный в параметрах (Старая модель уничтожается). ReleaseModel() - метод отсоединения модели, без замены на новую. InitGL() - метод установки параметров библиотеки OpenGL.
DrawGLScene() - рисование элементов сцены. В данном случае это отрисовка поля, игрового таймера и количества непомеченных бомб и других элементов игрового интерфейса. Размеры окна подбираются исходя из параметров ширины и высоты игрового поля.
ReSizeGLScene(width, height) - метод перерасчета координат и подстройки системы OpenGL при изменении размеров окна.
drawClosedField(x, y) - метод рисования закрытой ячейки по заданным координатам на игровом поле.
drawClosedField(x, y, minesAround) - метод рисования закрытой ячейки игрового поля и дополнительно ячейка помечается информацией о количестве заминированных соседей.
drawOpenedField(x, y) - рисование открытой ячейки игрового поля по заданным координатам (в качестве координат выступают индексы ячейки).
drawFlag(x, y) - рисование помеченной ячейки по заданным координатам (индексам).
drawMine(x, y) - рисование мины в ячейке с индексами {x, y}.
GetOGLPos(x, y) - преобразовать пару экранных (оконных) координат в пару координат в системе OpenGL.
GetCellIndex(x, y) - расчет индексов ячейки по которой произошел клик. В качестве входной информации - координаты клика мышки, в качестве выходной информации - индексы ячейки игрового поля.
BuildFont() - построение bitmap шрифта. Используется для изображения текстовых надписей в системе OpenGL.
KillFont() - освобождение ресурсов занимаемых bitmap шрифтом.
glPrint(const char *fmt, ...) - Метод вывода текстовой надписи.
CreateGLWindow(WNDPROC, LPCWSTR, int) - метод создания окна windows приложения.
KillGLWindow() - метод корректного разрушения окна приложения.
3.3 Класс Statistic - сбор и хранение данных о играх.
Класс Statistic содержит список структур - записей о прошедших играх. Внешний вид статистики изображен на рисунке 4.
В запись входят: Дата игры, параметры поля и количество мин, время, затраченное на игру и результат игры - выигрыш/проигрыш.
Единица записи представлена следующей структурой:
struct StatRec
{
// Дата и время записи
char recDate[80];
// Параметры поля
int Width, Height, BombCount;
// Затраченное время на игру
long gameTime;
// Тип завершения игры: выигрыш, проигрыш
bool WinGame;
};
Statistic содержит следующие методы:
AddRecord(StatRec) - добавление новой записи о завершенной игре к общему списку записей.
Clear() - уничтожение всех записей статистики.
SaveStat() - сохранение в файл данных о завершенных играх.
LoadStat() - загрузка данных из файла.
Refresh() - обновление статистических данных о играх.
ToString() - сбор статистики в строку.
GetRecords()- получить доступ к списку записей.
Рисунок 4 - Внешний вид окна статистики
3.4 Класс Game - контроль процесса игры
Класс Game обеспечивает игровой процесс: настройка и запуск, а также остановка игры, запуск и остановка игрового таймера. Класс Game содержит в качестве полей указатели на объекты модели, отображения и статистики.
Game содержит следующие методы:
InitGame(int, int) - инициализация игры: создание модели заданного размера, присоединение модели к объекту отображения.
GameStart() - запуск игры: запускается игровой таймер, маркер отвечающий за запуск игры выставляется в true.
EndGame() - остановка игры: останавливается игровой таймер, маркер запуска игры выставляется в false, отсоединяется модель от объекта отображения.
IsStart() - метод проверки запущена игра или нет.
GameProcess(x, y) - проверка ячейки с заданными координатами. Возвращаемое значение - процесс игры: "Игра окончена, победил компьютер", "Игра окончена, победил игрок", "Игра продолжается".
TimerStart() - запуск игрового таймера.
TimerStop() - остановка игрового таймера.
UpdateTimer() - обновить отображение текущего времени игры используя объект отображения класса View.
GetModel() - получить доступ к объекту модели.
GetView() - получить доступ к объекту отображения.
SetView(View*) - задать объект отображения.
GetStat() - доступ к объекту статистики.
Внешний вид игрового меню можно наблюдать на рис.5, рис. 6. Изображение игрового поля рис. 7, рис. 8.
Рисунок 5 - Внешний вид игрового меню
Рисунок 6 - Выбор размеров игрового поля
Рисунок 7 - Построение игрового поля
Рисунок 8 - Игровой процесс
Заключение
В ходе курсовой работы были разобраны принципы, по которым работает компьютерная игра «Сапер» от компании Microsoft. Они были использованы для написания собственной игры. При программировании использовалась библиотека OpenGL, языком создания программы был С++.
Логика игры «Сапер» была в большей части воспроизведена в курсовой работе, за исключением элементов, дополняющие геймплей игры. Воссозданная игра может быть использоваться наравне с оригинальной версией.
Литература
1. Т. Кормен, Ч. Лейзер - Алгоритмы. Построение и анализ.
2. Начальный курс OpenGl. Баяковский Ю.М., Игнатенко А.В.
3. Технология программирования на C++. Win32 API-приложения. Н.А. Литвиненко, Издательство: БХВ-Петербург.
Приложение
=============================Модель========================
#pragma once
#include "Model.h"
// Конструктор
Model::Model() {}
// Конструктор с параметрами
Model::Model(int width = 10, int height = 10)
{
timer = 0;
// Задаем размер поля
Width = width;
Height = height;
// Генерируем некоторое количество бомб
BombCount = (int)sqrt(width * height);
BombCount += rand() % BombCount;
// Флагов пока нет
FlagCount = 0;
// Создаем поле в виде вектора
field = new Cell[width * height];
// Расставляем мины
SetMines();
domainMatrix = new int*[height];
for(int i = 0; i < height; i++)
domainMatrix[i] = new int[width];
for(int y = 0; y < height; y++)
{
for(int x = 0; x < width; x++)
{
int minesAround = 0;
for (int yy = y - 1; yy <= y + 1; ++yy)
{
for (int xx = x - 1; xx <= x + 1; ++xx)
{
if ((xx == x && yy == y) ||
xx < 0 || xx >= width ||
yy < 0 || yy >= height)
continue;
if (GetCell(xx, yy).hasMine)
++minesAround;
}
}
if(GetCell(x, y).state == CellState::CLOSED &&
GetCell(x, y).hasMine == false &&
minesAround == 0)
{
domainMatrix[y][x] = 1;
}
else
{
domainMatrix[y][x] = 0;
}
}
}
for(int y = 0; y < height; y++)
{
for(int x = 0; x < width; x++)
{
if(domainMatrix[y][x] == 1)
{
if(x + 1 < width)
{
// Справа прямо
if(domainMatrix[y][x + 1] != 1 && domainMatrix[y][x + 1] == 0)
domainMatrix[y][x + 1] = 2;
// Справа сверху
if(y + 1 < height)
if(domainMatrix[y + 1][x + 1] != 1 && domainMatrix[y + 1][x + 1] == 0)
domainMatrix[y + 1][x + 1] = 2;
// Справа снизу
if(y - 1 >= 0)
if(domainMatrix[y - 1][x + 1] != 1 && domainMatrix[y - 1][x + 1] == 0)
domainMatrix[y - 1][x + 1] = 2;
}
if(x - 1 >= 0)
{
// Слева прямо
if(domainMatrix[y][x - 1] != 1 && domainMatrix[y][x - 1] == 0)
domainMatrix[y][x - 1] = 2;
// Слева сверху
if(y + 1 < height)
if(domainMatrix[y + 1][x - 1] != 1 && domainMatrix[y + 1][x - 1] == 0)
domainMatrix[y + 1][x - 1] = 2;
// Слева снизу
if(y - 1 >= 0)
if(domainMatrix[y - 1][x - 1] != 1 && domainMatrix[y - 1][x - 1] == 0)
domainMatrix[y - 1][x - 1] = 2;
}
if(y + 1 < height)
if(domainMatrix[y + 1][x] != 1 && domainMatrix[y + 1][x] == 0)
domainMatrix[y + 1][x] = 2;
if(y - 1 >= 0)
if(domainMatrix[y - 1][x] != 1 && domainMatrix[y - 1][x] == 0)
domainMatrix[y - 1][x] = 2;
}
}
}
// Получаем домены
for(int i = 0; i < height; i++)
{
for(int j = 0; j < width; j++)
{
// Нашли стартовую точку домена
if(domainMatrix[i][j] == 1)
{
// Создаем новый домен
Domain d(pair<int, int>(i, j), height, width);
// Получаем все координаты домена
domainMatrix = d.add_neighbors(domainMatrix, height, width);
// Добавляем в список доменов
domains.push_back(d);
}
}
}
}
// Копирования конструктор
Model::Model(const Model &href)
{
timer = href.timer;
field = new Cell[href.Width * href.Height];
Width = href.Width;
Height = href.Height;
BombCount = href.BombCount;
FlagCount = href.FlagCount;
// Копируем параметры ячеек
for (int x = 0; x < Width; x++)
for (int y = 0; y < Height; y++)
{
// Копируем наличие бомбы
field[y * Width + x].hasMine = href.field[y * Width + x].hasMine;
// Копируем состояние ячейки
field[y * Width + x].state = href.field[y * Width + x].state;
}
// Копируем список доменов
domains.assign(href.domains.begin(), href.domains.end());
}
// Деструктор
Model::~Model()
{
delete []field;
for(int i = 0; i < Height; i++)
delete []domainMatrix[i];
delete []domainMatrix;
}
// Расстановка мин
void Model::SetMines()
{
srand((unsigned)time(NULL));
// Заполняем параметры ячеек на поле
for (int y = 0; y < Height; y++)
for (int x = 0; x < Width; x++)
{
// Изначально ячейка закрыта
field[y * Width + x].state = CellState::CLOSED;
// Бомб нет
field[y * Width + x].hasMine = false;
}
// Расставляем мины
int count = 0;
while(count < BombCount)
{
// Координаты мины
int x, y;
do
{
// Генерируем х координату на поле
x = rand() % Width;
// Генерируем y координату
y = rand() % Height;
}
// Повторяем пока по текущим координатам попадаем на занятое поле
while (GetCell(x, y).hasMine);
// Выставляем мину в ячейку
GetCell(x, y).hasMine = true;
count++;
}
}
// Изменение ячейки
void Model::SetCell(int x, int y, CellState state)
{
// Меняем свойство яйечки
field[y * Width + x].state = state;
}
// Получить доступ к ячейке по индексу
Cell &Model::GetCell(int x, int y)
{
return field[y * Width + x];
}
// Открыть ячейку
void Model::Open(int x, int y)
{
// Если ячейка помечена флагом или открыта - выходим
if(GetCell(x, y).state != CellState::CLOSED)
return;
int domainID = -1;
// Проверяем попадает ли точка в какой либо домен свободных клеток
for(size_t i = 0; i < domains.size() && domainID == -1; i++)
{
// Проверяем точки домена
for(size_t j = 0; j < domains.at(i).points.size() && domainID == -1; j++)
// Если найдено вхождение
if(domains.at(i).points.at(j).first == y && domains.at(i).points.at(j).second == x)
// Запоминаем индекс домена
domainID = i;
}
// Если точка входит в какой либо домен
if(domainID != -1)
{
// Открываем все клетки из этого домена
for(size_t j = 0; j < domains.at(domainID).points.size(); j++)
{
// Открываем текущую ячейку
GetCell(domains.at(domainID).points.at(j).second, domains.at(domainID).points.at(j).first).state = CellState::OPEN;
// Открываем справа
if(domains.at(domainID).points.at(j).second + 1 < Width)
{
// Справа прямо
GetCell(domains.at(domainID).points.at(j).second + 1, domains.at(domainID).points.at(j).first).state = CellState::OPEN;
// Справа вверху
if(domains.at(domainID).points.at(j).first + 1 < Height)
GetCell(domains.at(domainID).points.at(j).second + 1, domains.at(domainID).points.at(j).first + 1).state = CellState::OPEN;
// Справа внизу
if(domains.at(domainID).points.at(j).first - 1 >= 0)
GetCell(domains.at(domainID).points.at(j).second + 1, domains.at(domainID).points.at(j).first - 1).state = CellState::OPEN;
}
// Открываем слева
if(domains.at(domainID).points.at(j).second - 1 >= 0)
{
// Справа прямо
GetCell(domains.at(domainID).points.at(j).second - 1, domains.at(domainID).points.at(j).first).state = CellState::OPEN;
// Справа вверху
if(domains.at(domainID).points.at(j).first + 1 < Height)
GetCell(domains.at(domainID).points.at(j).second - 1, domains.at(domainID).points.at(j).first + 1).state = CellState::OPEN;
// Справа внизу
if(domains.at(domainID).points.at(j).first - 1 >= 0)
GetCell(domains.at(domainID).points.at(j).second - 1, domains.at(domainID).points.at(j).first - 1).state = CellState::OPEN;
}
// Сверху
if(domains.at(domainID).points.at(j).first - 1 >= 0)
{
GetCell(domains.at(domainID).points.at(j).second, domains.at(domainID).points.at(j).first - 1).state = CellState::OPEN;
}
// Снизу
if(domains.at(domainID).points.at(j).first + 1 < Height)
GetCell(domains.at(domainID).points.at(j).second, domains.at(domainID).points.at(j).first + 1).state = CellState::OPEN;
}
}
// Открываем текущую
GetCell(x, y).state = CellState::OPEN;
}
// Пометить ячейку
void Model::Mark(int x, int y)
{
switch (GetCell(x, y).state)
{
case CellState::OPEN:
break;
case CellState::CLOSED:
FlagCount++;
GetCell(x, y).state = CellState::FLAG;
break;
case CellState::FLAG:
if(FlagCount > 0)
FlagCount--;
GetCell(x, y).state = CellState::CLOSED;
break;
}
}
// Все ли флаги раставленны корректно
bool Model::FlagsCorrect()
{
// Счетчик верно расставленных флагов
int correctFlags = 0;
// Цикл подсчета количества верно расставленных флагов
for(int y = 0; y < Height; y++)
for(int x = 0; x < Width; x++)
// Если в клетке есть мина и на ней поставлен флажек
if(GetCell(x, y).hasMine && GetCell(x, y).state == CellState::FLAG)
// Увеличиваем счетчик правильных флагов
correctFlags++;
// Количество правильных флагов равно количеству бомб
return correctFlags == BombCount;
}
=============================Отображение====================
#pragma once
#include "View.h"
#include "Model.h"
View::View()
{
/*model = new Model(FIELD_TYPE0, FIELD_TYPE0);
// Строим текущую модель на основе переданной в параметрах
ReSetModel(*model); */
model = NULL;
// Параметры ячейки
CellWidth = 30;
CellHeight = 30;
// Ширина отступа
BorderSize = 50;
// Ширина полосы
lineWidth = 4;
// Задаем параметры экрана:
SceneWidth = FIELD_TYPE0 * CellWidth/* ширина занимаемая клеткам */
//+ (FIELD_TYPE0 + 1) * lineWidth/* ширина отступов между клетками */
+ BorderSize * 2;/* ширина "бордюра" вокруг поля */
SceneHeight = FIELD_TYPE0 * CellHeight/* высота занимаемая клеткам */
//+ (FIELD_TYPE0 + 1) * lineWidth/* ширина отступов между клетками */
+ BorderSize * 2;/* ширина "бордюра" вокруг поля */
// Обнуляем переменные
hRC = NULL;
hDC = NULL;
hWnd = NULL;
// Окно активно по умолчанию
active = true;
}
// Деструктор
View::~View()
{
KillGLWindow();
}
// Задать параметры
void View::ReSetModel(Model *mod)
{
// Создаем новую на основе заданной
model = mod;//new Model(mod);
// Параметры ячейки
CellWidth = 30;
CellHeight = 30;
// Ширина отступа
BorderSize = 50;
// Ширина полосы
lineWidth = 4;
// Задаем параметры экрана:
SceneWidth = model->Width * CellWidth/* ширина занимаемая клеткам */
//+ (model->Width + 1) * lineWidth/* ширина отступов между клетками */
+ BorderSize * 2;/* ширина "бордюра" вокруг поля */
SceneHeight = model->Height * CellHeight/* высота занимаемая клеткам */
//+ (model->Height + 1) * lineWidth/* ширина отступов между клетками */
+ BorderSize * 2;/* ширина "бордюра" вокруг поля */
// Окно активно по умолчанию
active = true;
RECT rect;
GetWindowRect(hWnd, &rect);
int w = rect.right - rect.left;
int h = rect.bottom - rect.top;
if(w != SceneWidth || h != SceneHeight)
{
MoveWindow(hWnd, rect.left, rect.top, SceneWidth, SceneHeight, true);
}
}
// Отсоединяем модель от класса отображения
void View::ReleaseModel()
{
if(model == NULL) return;
delete model;
model = NULL;
}
// Инициализация библиотеки
int View::InitGL( GLvoid )
{
// Построение шрифта
BuildFont();
// Разрешить плавное цветовое сглаживание
glShadeModel(GL_SMOOTH);
// Очистка экрана в черный цвет
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// Разрешить очистку буфера глубины
glClearDepth( 1.0f );
// Разрешить тест глубины
glEnable( GL_DEPTH_TEST );
// Тип теста глубины
glDepthFunc( GL_LEQUAL );
// Улучшение в вычислении перспективы
glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
// Инициализация прошла успешно
return true;
}
// Отрисовка сцены
int View::DrawGLScene(GLvoid)
{
glClearColor(0.0, 0.4, 0.5, 1.0);
// Очистить экран и буфер глубины
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
// Сбросить текущую матрицу
glLoadIdentity();
// Задаем параметры координат сцены
gluOrtho2D(0.0f, SceneWidth, 0.0f, SceneHeight);
// Если модель не задана - ничего не рисуем
if(model == NULL) return true;
// Цвет линии окружающей поле
glColor3f(0.0f, 0.0f, 0.0f);
//glTranslatef(0.0f, 0.0f, 1.0f);
// Начало рисования линий
glBegin(GL_LINES);
glVertex2f(BorderSize, SceneHeight - BorderSize);
glVertex2f(SceneWidth - BorderSize, SceneHeight - BorderSize);
glVertex2f(BorderSize, BorderSize);
glVertex2f(SceneWidth - BorderSize, BorderSize);
glVertex2f(BorderSize, BorderSize);
glVertex2f(BorderSize, SceneHeight - BorderSize);
glVertex2f(SceneWidth - BorderSize, BorderSize);
glVertex2f(SceneWidth - BorderSize, SceneHeight - BorderSize);
// Окончание рисования линий
glEnd();
// Рисование поля
for (int y = 0; y < model->Height; y++)
for (int x = 0; x < model->Width; x++)
{
switch (model->GetCell(x, y).state)
{
case CellState::CLOSED:
drawClosedField(x, y);
break;
// Если ячейка открыта
case CellState::OPEN:
// И ячейка не имеет мины
if (!model->GetCell(x, y).hasMine)
{
// Подсчитываем количество мин сеседствующих с текущей клеткой
int minesAround = 0;
for (int yy = y - 1; yy <= y + 1; ++yy)
for (int xx = x - 1; xx <= x + 1; ++xx)
{
if ((xx == x && yy == y) ||
xx < 0 || xx >= model->Width ||
yy < 0 || yy >= model->Height)
continue;
if (model->GetCell(xx, yy).hasMine)
++minesAround;
}
// Рисуем открытую яч и подпись количества соседних мин
drawOpenedField(x, y, minesAround);
}
// Рисуем мину
else
drawMine(x, y);
break;
case CellState::FLAG:
// Рисуем флаг
drawFlag(x, y);
break;
}
}
// Выводим информацию о игре
glColor3f(1.0f, 1.0f, 1.0f);
glRasterPos2f(BorderSize, BorderSize / 2.0f);
// Время
glPrint("Time:%d", model->timer);
// Количество не помеченных бомб
glRasterPos2f(SceneWidth - BorderSize * 2.0f, BorderSize / 2.0f);
glPrint("Bomb left:%d", model->BombCount - model->FlagCount);
// Прорисовка прошла успешно
return true;
}
// Перерисовка при изменении размеров окна
GLvoid View::ReSizeGLScene( GLsizei width, GLsizei height )
{
// Предотвращение деления на ноль
if(height == 0)
height = 1;
// Сброс текущей области вывода
glViewport(0, 0, width, height);
// Выбор матрицы вида модели
glMatrixMode( GL_MODELVIEW );
// Сброс матрицы вида модели
glLoadIdentity();
}
// Рисуем закрытое поле
void View::drawClosedField(int x, int y)
{
// Установить текущий цвет (R,G,B)
glColor3f(0.8f, 0.8f, 0.8f);
glBegin(GL_QUADS);
glVertex2f(BorderSize + x * CellWidth, SceneHeight - (BorderSize + y * CellHeight));
glVertex2f(BorderSize + (x + 1) * CellWidth, SceneHeight - (BorderSize + y * CellHeight));
glVertex2f(BorderSize + (x + 1) * CellWidth, SceneHeight - (BorderSize + (y + 1) * CellHeight));
glVertex2f(BorderSize + x * CellWidth, SceneHeight - (BorderSize + (y + 1) * CellHeight));
glEnd();
// Установить текущий цвет (R,G,B)
glColor3f(1.0f, 1.0f, 1.0f);
glBegin(GL_LINES);
glVertex2f(BorderSize + x * CellWidth, SceneHeight - (BorderSize + y * CellHeight));
glVertex2f(BorderSize + (x + 1) * CellWidth - 1, SceneHeight - (BorderSize + y * CellHeight));
glVertex2f(BorderSize + x * CellWidth, SceneHeight - (BorderSize + y * CellHeight));
glVertex2f(BorderSize + x * CellWidth, SceneHeight - (BorderSize + (y + 1) * CellHeight - 1));
glEnd();
// Установить текущий цвет (R,G,B)
glColor3f(0.2f, 0.2f, 0.2f);
glBegin(GL_LINES);
glVertex2f(BorderSize + (x + 1) * CellWidth - 1, SceneHeight - (BorderSize + y * CellHeight));
glVertex2f(BorderSize + (x + 1) * CellWidth - 1, SceneHeight - (BorderSize + (y + 1) * CellHeight - 1));
glVertex2f(BorderSize + x * CellWidth, SceneHeight - (BorderSize + (y + 1) * CellHeight - 1));
glVertex2f(BorderSize + (x + 1) * CellWidth - 1, SceneHeight - (BorderSize + (y + 1) * CellHeight - 1));
glEnd();
}
// Рисуем открытое поле
void View::drawOpenedField(int x, int y, int minesAround)
{
drawOpenedField(x, y);
if (minesAround > 0)
{
switch (minesAround)
{
case 1:
glColor3f(0.0f, 1.0f, 0.0f);
break;
case 2:
glColor3f(0.0f, 0.0f, 1.0f);
break;
case 3:
glColor3f(1.0f, 0.0f, 0.0f);
break;
case 4:
glColor3f(0.0f, 0.7f, 0.0f);
break;
case 5:
glColor3f(0.5f, 0.4f, 0.0f);
break;
case 6:
glColor3f(0.0f, 0.8f, 0.5f);
break;
case 7:
glColor3f(0.1f, 0.1f, 0.1f);
break;
case 8:
glColor3f(0.3f, 0.3f, 0.3f);
break;
}
// Выводим надпись количества соседей - мин
glRasterPos2f(BorderSize + x * CellWidth + (30 - 8) / 2 + 1, SceneHeight - (BorderSize + (y + 1) * CellHeight - 15));
glutBitmapCharacter(GLUT_BITMAP_9_BY_15, '0' + minesAround % 10);
}
}
// Открытая ячейка
void View::drawOpenedField(int x, int y)
{
glColor3f(0.6f, 0.6f, 0.6f);
glBegin(GL_QUADS);
glVertex2f(BorderSize + x * CellWidth, SceneHeight - (BorderSize + y * CellHeight));
glVertex2f(BorderSize + (x + 1) * CellWidth, SceneHeight - (BorderSize + y * CellHeight));
glVertex2f(BorderSize + (x + 1) * CellWidth, SceneHeight - (BorderSize + (y + 1) * CellHeight));
glVertex2f(BorderSize + x * CellWidth, SceneHeight - (BorderSize + (y + 1) * CellHeight));
glColor3f(0.2f, 0.2f, 0.2f);
glEnd();
glBegin(GL_LINES);
glVertex2f(BorderSize + (x + 1) * CellWidth - 1, SceneHeight - (BorderSize + y * CellHeight));
glVertex2f(BorderSize + (x + 1) * CellWidth - 1, SceneHeight - (BorderSize + (y + 1) * CellHeight - 1));
glVertex2f(BorderSize + x * CellWidth, SceneHeight - (BorderSize + (y + 1) * CellHeight - 1));
glVertex2f(BorderSize + (x + 1) * CellWidth - 1, SceneHeight - (BorderSize + (y + 1) * CellHeight - 1));
glEnd();
}
// Рисуем флаг
void View::drawFlag(int x, int y)
{
// Установить текущий цвет (R,G,B)
glColor3f(0.8f, 0.8f, 0.8f);
glBegin(GL_QUADS);
glVertex2f(BorderSize + x * CellWidth, SceneHeight - (BorderSize + y * CellHeight));
glVertex2f(BorderSize + (x + 1) * CellWidth, SceneHeight - (BorderSize + y * CellHeight));
glVertex2f(BorderSize + (x + 1) * CellWidth, SceneHeight - (BorderSize + (y + 1) * CellHeight));
glVertex2f(BorderSize + x * CellWidth, SceneHeight - (BorderSize + (y + 1) * CellHeight));
glEnd();
// Установить текущий цвет (R,G,B)
glColor3f(0.0f, 0.0f, 0.0f);
glBegin(GL_LINES);
glVertex2f(BorderSize + x * CellWidth + CellWidth / 2, SceneHeight - (BorderSize + y * CellHeight + 3));
glVertex2f(BorderSize + x * CellWidth + CellWidth / 2, SceneHeight - (BorderSize + (y + 1) * CellHeight - 3));
glEnd();
//Установить текущий цвет (R,G,B)
glColor3f(1.0f, 0.0f, 0.0f);
glBegin(GL_TRIANGLES);
glVertex2f(BorderSize + x * CellWidth + CellWidth / 2, SceneHeight - (BorderSize + y * CellHeight + 3));
glVertex2f(BorderSize + x * CellWidth + CellWidth / 2 - 3, SceneHeight - (BorderSize + y * CellHeight + 3 + 3));
glVertex2f(BorderSize + x * CellWidth + CellWidth / 2, SceneHeight - (BorderSize + y * CellHeight + 3 + 3 + 3));
glEnd();
}
// Рисуем мину
void View::drawMine(int x, int y)
{
drawOpenedField(x, y);
glColor3f(0.0f, 0.0f, 0.0f);
glPointSize(10);
glBegin(GL_POINTS);
glVertex2f(BorderSize + x * CellWidth + CellWidth / 2.0f, SceneHeight - (BorderSize + y * CellHeight + CellHeight / 2.0f));
glEnd();
}
// Получить координаты в система OpenGL
POINT View::GetOGLPos(int x, int y)
{
GLint viewport[4];
GLdouble modelview[16];
GLdouble projection[16];
GLfloat winX, winY, winZ;
GLdouble posX, posY, posZ;
glGetDoublev( GL_MODELVIEW_MATRIX, modelview );
glGetDoublev( GL_PROJECTION_MATRIX, projection );
glGetIntegerv( GL_VIEWPORT, viewport );
winX = (float)x;
winY = (float)viewport[3] - (float)y;
glReadPixels( x, int(winY), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ );
gluUnProject( winX, winY, winZ, modelview, projection, viewport, &posX, &posY, &posZ);
POINT p;
p.x = posX;
p.y = posY;
return p;
}
// Получить индекс ячейки в матрице модели по координатам позиции мышки
POINT View::GetCellIndex(POINT point)
{
// Calc index
POINT p;
p.x = -1;
p.y = -1;
// Проверяем установлена ли модель
if(model == NULL)
return p;
// Если установлена - определем "X" координату
for(int x = 0; x < model->Width && p.x == -1; x++)
{
double x1 = BorderSize + x * CellWidth;
double x2 = BorderSize + (x + 1) * CellWidth;
if(point.x > x1 && point.x < x2)
p.x = x;
}
// Определем "Y" координату
for(int y = 0; y < model->Height && p.y == -1; y++)
{
double y1 = SceneHeight - (BorderSize + y * CellHeight);
double y2 = SceneHeight - (BorderSize + (y + 1) * CellHeight);
if(point.y < y1 && point.y > y2)
p.y = y;
}
// Возвращаем координаты
return p;
}
// Построение битмап шрифта
GLvoid View::BuildFont(GLvoid)
{
HFONTfont;// Идентификатор шрифта
HFONToldfont;
base = glGenLists(96);// Список из 96 символов
font = CreateFont(-12,// Высота
0,// Ширина
0,
0,// Угол ориентации
FW_BOLD,// Жирный или нет
FALSE,// Наклонный
FALSE,// Подчеркивание
FALSE,
ANSI_CHARSET,
OUT_TT_PRECIS,
CLIP_DEFAULT_PRECIS,
ANTIALIASED_QUALITY,// Качество
FF_DONTCARE|DEFAULT_PITCH,
L"Courier New");// Название шрифта
oldfont = (HFONT)SelectObject(hDC, font);
// Построение шрифта из 96 символов
wglUseFontBitmaps(hDC, 32, 96, base);
// Выбираем наш шрифт
SelectObject(hDC, oldfont);
// Удаляем шрифт
DeleteObject(font);
}
// Очистка ресурсов шрифта
GLvoid View::KillFont(GLvoid)
{
glDeleteLists(base, 96);
}
// Функция вывода шрифта
GLvoid View::glPrint(const char *fmt, ...)
{
// Буффер для текста
chartext[256];
// Указатель на список аргументов
va_listap;
// Если нет текста - ничего не делаем
if (fmt == NULL)
return;
// Разбираем список аргументов
va_start(ap, fmt);
vsprintf(text, fmt, ap);
va_end(ap);
// Сохраняем список
glPushAttrib(GL_LIST_BIT);
glListBase(base - 32);
// Рисуем список
glCallLists(strlen(text), GL_UNSIGNED_BYTE, text);
glPopAttrib();
}
// Построение окна
BOOL View::CreateGLWindow(WNDPROC WndProc, LPCWSTR title, int bits)
{
// Хранит результат после поиска
GLuintPixelFormat;
// Структура окна
WNDCLASSwc;
// Расширенный стиль окна
DWORDdwExStyle;
// Обычный стиль окна
DWORDdwStyle;
// Параметры окна
RECT WindowRect;
// Установить левую составляющую в 0
WindowRect.left= (long)0;
// Установить правую составляющую в Width
WindowRect.right= (long)SceneWidth;
// Установить верхнюю составляющую в 0
WindowRect.top= (long)0;
// Установить нижнюю составляющую в Height
WindowRect.bottom= (long)SceneHeight;
// Получаем дескриптор приложения
hInstance= GetModuleHandle(NULL);
// Заполняем поля стуктуры
// Перерисуем при перемещении и создаём скрытый DC
wc.style= CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
// Процедура обработки сообщений
wc.lpfnWndProc= (WNDPROC)WndProc;
// Нет дополнительной информации для окна
wc.cbClsExtra= 0;
// Нет дополнительной информации для окна
wc.cbWndExtra= 0;
// Устанавливаем дескриптор
wc.hInstance= hInstance;
// Загружаем иконку по умолчанию
wc.hIcon= LoadIcon(NULL, IDI_WINLOGO);
// Загружаем указатель мышки
wc.hCursor= LoadCursor(NULL, IDC_ARROW);
// Фон не требуется для GL
wc.hbrBackground= NULL;
// Меню в окне не будет
wc.lpszMenuName= NULL;
// Устанавливаем имя классу
wc.lpszClassName= L"Saper OpenGL Window Class";
// Пытаемся зарегистрировать класс окна
if(!RegisterClass(&wc))
{
// Неудачная попытка зарегистрировать класс
MessageBox( NULL, L"Failed To Register The Window Class.", L"ERROR", MB_OK | MB_ICONEXCLAMATION );
// Выход и возвращение функцией значения false
return false;
}
// Расширенный стиль окна
dwExStyle= WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
// Обычный стиль окна
dwStyle= WS_DLGFRAME;
// Подбирает окну подходящие размеры
AdjustWindowRectEx( &WindowRect, dwStyle, false, dwExStyle );
if(!( hWnd = CreateWindowEx(dwExStyle,// Расширенный стиль для окна
L"Saper OpenGL Window Class",// Имя класса
title,// Заголовок окна
WS_CLIPSIBLINGS |// Требуемый стиль для окна
WS_CLIPCHILDREN |// Требуемый стиль для окна
dwStyle,// Выбираемые стили для окна
0, 0,// Позиция окна
WindowRect.right - WindowRect.left,// Вычисление подходящей ширины
WindowRect.bottom - WindowRect.top,// Вычисление подходящей высоты
NULL,// Нет родительского
NULL,// Нет меню
hInstance,// Дескриптор приложения
NULL)))// Не передаём ничего до WM_CREATE
{
// Восстановить экран
KillGLWindow();
MessageBox( NULL, L"Window Creation Error.", L"ERROR", MB_OK | MB_ICONEXCLAMATION );
// Ошибка создания окна
return false;
}
HMENU hMenu = CreateMenu();
HMENU hPopMenuFile = CreatePopupMenu();
AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hPopMenuFile, L"Игра");
AppendMenu(hPopMenuFile, MF_STRING, 1021, L"Старт");
AppendMenu(hPopMenuFile, MF_STRING, 1022, L"Статистика");
AppendMenu(hPopMenuFile, MF_STRING, 1023, L"Выход");
SetMenu(hWnd, hMenu);
SetMenu(hWnd, hPopMenuFile);
static PIXELFORMATDESCRIPTOR pfd=// pfd сообщает Windows каким будет вывод на экран каждого пикселя
{
sizeof(PIXELFORMATDESCRIPTOR),// Размер дескриптора данного формата пикселей
1,// Номер версии
PFD_DRAW_TO_WINDOW |// Формат для Окна
PFD_SUPPORT_OPENGL |// Формат для OpenGL
PFD_DOUBLEBUFFER,// Формат для двойного буфера
PFD_TYPE_RGBA,// Требуется RGBA формат
bits,// Выбирается бит глубины цвета
0, 0, 0, 0, 0, 0,// Игнорирование цветовых битов
0,// Нет буфера прозрачности
0,// Сдвиговый бит игнорируется
0,// Нет буфера накопления
0, 0, 0, 0,// Биты накопления игнорируются
32,// 32 битный Z-буфер (буфер глубины)
0,// Нет буфера трафарета
0,// Нет вспомогательных буферов
PFD_MAIN_PLANE,// Главный слой рисования
0,// Зарезервировано
0, 0, 0// Маски слоя игнорируются
};
// Попытка получить контекст устройства
if(!(hDC = GetDC(hWnd)))
{
// Восстановить экран
KillGLWindow();
MessageBox(NULL, L"Can't Create A GL Device Context.", L"ERROR", MB_OK | MB_ICONEXCLAMATION);
// Ошибка получения контекста устройства
return false;
}
// Найден ли подходящий формат пикселя?
if(!(PixelFormat = ChoosePixelFormat(hDC, &pfd)))
{
// Восстановить экран
KillGLWindow();
MessageBox(NULL, L"Can't Find A Suitable PixelFormat.", L"ERROR", MB_OK | MB_ICONEXCLAMATION);
// Ошибка, формат для пикселя не найден
return false;
}
// Возможно ли установить Формат Пикселя?
if(!SetPixelFormat(hDC, PixelFormat, &pfd))
{
// Восстановить экран
KillGLWindow();
MessageBox(NULL, L"Can't Set The PixelFormat.", L"ERROR", MB_OK | MB_ICONEXCLAMATION);
// Ошибка задания формата пиксел
return false;
}
// Возможно ли установить Контекст Рендеринга?
if(!(hRC = wglCreateContext(hDC)))
{
// Восстановить экран
KillGLWindow();
MessageBox(NULL, L"Can't Create A GL Rendering Context.", L"ERROR", MB_OK | MB_ICONEXCLAMATION);
// Ошибка установки контекста рендеринга
return false;
}
// Попробовать активировать Контекст Рендеринга
if(!wglMakeCurrent(hDC, hRC))
{
// Восстановить экран
KillGLWindow();
MessageBox(NULL, L"Can't Activate The GL Rendering Context.", L"ERROR", MB_OK | MB_ICONEXCLAMATION);
// Ошибка активации конекства
return false;
}
// Показать окно
ShowWindow(hWnd, SW_SHOW);
// Слегка повысим приоритет
SetForegroundWindow(hWnd);
// Установить фокус клавиатуры на наше окно
SetFocus(hWnd);
// Инициализация библиотеки OpenGL для текущего окна
if(!InitGL())
{
// Восстановить экран
KillGLWindow();
MessageBox(NULL, L"Initialization Failed.", L"ERROR", MB_OK | MB_ICONEXCLAMATION );
return false;
}
// Завершение инициализации окна успешно
return true;
}
// Корректное разрушение окна
GLvoid View::KillGLWindow(GLvoid)
{
// Проверка существования контекста устройства
if(hRC)
{
// Попытка освободить RC и DC
if(!wglMakeCurrent(NULL, NULL))
MessageBox(NULL, L"Release Of DC And RC Failed.", L"SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
// Попытка удалить RC
if(!wglDeleteContext(hRC))
MessageBox(NULL, L"Release Rendering Context Failed.", L"SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
// Установить RC в NULL
hRC = NULL;
}
// Попытка уничтожить DC
if(hDC && !ReleaseDC(hWnd, hDC))
{
MessageBox(NULL, L"Release Device Context Failed.", L"SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
// Установить DC в NULL
hDC = NULL;
}
// Попытка уничтожить окно
if(hWnd && !DestroyWindow(hWnd))
{
MessageBox(NULL, L"Could Not Release hWnd.", L"SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
// Установить hWnd в NULL
hWnd = NULL;
}
// Возможно ли разрегистрировать класс
if(!UnregisterClass(L"Saper OpenGL Window Class", hInstance))
{
MessageBox(NULL, L"Could Not Unregister Class.", L"SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
// Установить hInstance в NULL
hInstance = NULL;
}
}
=============================Статистика=====================
#pragma once
#include "Statistic.h"
#include <windows.h>
// Конструктор
Statistic::Statistic()
{
// Общее количество выигранных игр
wins = 0;
// Общее количество проигрышей
looses = 0;
// Общее время затраченное на игры
TotalTime = 0;
// Время самой долгой победы
slowGame = 0;
// Время самой быстрой победы
quickGame = MAXLONG;
// Путь к файлу статистики
savePath = "Stat.dat";
}
// Деструктор
Statistic::~Statistic()
{
Clear();
}
// Добавить запись
void Statistic::AddRecord(StatRec &rec)
{
// Добавляем запись в список
records.push_back(new StatRec(rec));
// Обновляем данные статистики
Refresh();
}
// Очистить все записи
void Statistic::Clear()
{
for(size_t i = 0; i < records.size(); i++)
delete records.at(i);
}
// Сброс данных в файл
void Statistic::SaveStat()
{
// Открываем файл
ofstream outf(savePath, ios::trunc);
// Сохраняем все записи что есть на данный момент
for(size_t i = 0; i < records.size(); i++)
outf << records.at(i)->recDate << '#'
<< records.at(i)->Width << '#' << records.at(i)->Height << '#' << records.at(i)->BombCount << '#'
<< records.at(i)->gameTime << '#' << ((records.at(i)->WinGame) ? "win" : "loose") << '#' << endl;
// Закрываем файл
outf.close();
}
// Загрузка данных из файла
void Statistic::LoadStat()
{
// Открываем файл
ifstream inf(savePath);
if(!inf)
return;
// Временная строка - буфер
string buff = "";
char k;
StatRec temp;
// Читаем файл
while(!inf.eof())
{
k = inf.get();
// Если запись окончена
if(k == '\n')
{
// Разбираем данные в строке
Parse(&temp, buff);
// Очищаем буфер
buff.clear();
// Добавляем в вектор
records.push_back(new StatRec(temp));
}
buff += k;
}
// Обновляем данные статистики
Refresh();
// Закрываем файл
inf.close();
}
// Разбор строки параметров
void Statistic::Parse(StatRec *p, string buff)
{
string temp = "";
int count = 0;
for(size_t i = 0; i < buff.size(); i++)
{
// Обнаружен коней строки текущего параметра
if(buff.at(i) == '#')
{
switch(count)
{
// Добавляем первый параметр - дата игры
case 0:
strcpy(p->recDate, temp.c_str());
break;
// Ширина поля
case 1:
p->Width = atoi(temp.c_str());
break;
// Высота поля
case 2:
p->Height = atoi(temp.c_str());
break;
// Количество бомб
case 3:
p->BombCount = atoi(temp.c_str());
break;
// Время затраченное на игру
case 4:
p->gameTime = atoi(temp.c_str());
break;
// Исход игры: выиграл/проиграл
case 5:
p->WinGame = (strcmp(temp.c_str(), "win") == 0) ? true : false;
break;
default:
break;
}
// Очистка буферной строки
temp.clear();
// Переходим к следующему параметру
count++;
}
// Добавляем в буферную строку
else if(buff.at(i) != '\n')
temp += buff.at(i);
}
}
// Обновление глобальных данных о игре
void Statistic::Refresh()
{
for(size_t i = 0; i < records.size(); i++)
{
// Общее количество выигранных игр
if(records.at(i)->WinGame)
wins++;
// Общее количество проигрышей
else
looses++;
// Общее время затраченное на игры
TotalTime += records.at(i)->gameTime;
// Время самой долгой победы
if(records.at(i)->gameTime > slowGame && records.at(i)->WinGame)
slowGame = records.at(i)->gameTime;
// Время самой быстрой победы
if(records.at(i)->gameTime < quickGame && records.at(i)->WinGame)
quickGame = records.at(i)->gameTime;
}
}
// Собрать статистику в одну строку
string Statistic::ToString()
{
char buff[32];
string str = "\r\n\r\n";
itoa(wins, buff, 10);
str += "Количество выиграных игр:\t ";
str += buff; str += "\r\n";
itoa(looses, buff, 10);
str += "Количество проигранных игр:\t ";
str += buff; str += "\r\n";
itoa(TotalTime, buff, 10);
str += "Время затраченное на игры:\t ";
str += buff; str += "\r\n";
itoa(slowGame, buff, 10);
str += "Время самой длинной победы:\t ";
str += buff; str += "\r\n";
if(quickGame == MAXLONG)
itoa(0, buff, 10);
else
itoa(quickGame, buff, 10);
str += "Время самой быстрой победы:\t ";
str += buff; str += "\r\n";
return str;
}
// Получить список игровых записей
vector<StatRec*> Statistic::GetRecords()
{
return records;
}
===============================Игра========================
#pragma once
#include "Game.h"
// Идентификатор таймера
#define GAME_TIMER 1
// Конструктор
Game::Game()
{
// Загружаем данные статистики
stat.LoadStat();
model = NULL;
nTimerID = -1;
gameStart = false;
}
// Деструктор
Game::~Game()
{
delete model;
}
// Инициализация игры
void Game::InitGame(int FieldWidth, int FieldHeight)
{
// Создаем представление
model = new Model(FieldWidth, FieldHeight);
// Задаем параметры
view->ReSetModel(model);
}
// Старт игры
void Game::GameStart()
{
if(model == NULL) return;
gameStart = true;
TimerStart();
}
void Game::EndGame()
{
gameStart = false;
TimerStop();
view->ReleaseModel();
model = NULL;
}
// Запущенна ли игра
bool Game::IsStart()
{
return gameStart;
}
// Проверка состояния игры
GameState Game::GameProcess(int x, int y)
{
// Если нарвались на мину - игра окончена победил компьютер
if (model->GetCell(x, y).state == CellState::OPEN && model->GetCell(x, y).hasMine)
{
stat.AddRecord(StatRec(model->Width, model->Height, model->BombCount, model->timer, false));
// Возвращаем статус игры
return GameState::CompWin;
}
// Если количество флагов достигло количества бомб и все флаги раставлены только на бомбах а не на пустых клетках - игра окончена победил игрок
else if(model->FlagCount == model->BombCount && model->FlagsCorrect())
{
stat.AddRecord(StatRec(model->Width, model->Height, model->BombCount, model->timer, true));
return GameState::PlayerWin;
}
// Иначе игра продолжается
return GameState::Continue;
}
// Старт таймера
void Game::TimerStart()
{
// Создаем таймер
nTimerID = SetTimer(view->hWnd, GAME_TIMER, 1000, NULL);
}
// Остановка таймера
void Game::TimerStop()
{
// Остановливаем таймер
KillTimer(view->hWnd, GAME_TIMER);
}
// Обновить таймер
void Game::UpdateTimer()
{
model->timer++;
}
// Доступ к модели
Model * Game::GetModel()
{
return model;
}
// Доступ к объкту отображения
View * Game::GetView()
{
return view;
}
// Задать объект отображения
void Game::SetView(View *v)
{
view = v;
}
// Доступ к объекту статистики
Statistic & Game::GetStat()
{
return stat;
}
===================Исходный код главной процедуры==============
#pragma once
#include "Headers.h"
// Параметры поля
int FieldWidth = 10;
int FieldHeight = 10;
// Максимальные размеры поля
int fieldMaxWidth = 64, fieldMinWidth = 5;
int fieldMaxHeight = 64, fieldMinHeight = 5;
// Создаем глобальный объект игры
Game game;
// Объект отображения
View view;
// Прототип функции WndProc главного окна
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
// Прототип калбек функции дочернего окна
BOOL CALLBACK ChildDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK statDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance,// Дескриптор приложения
HINSTANCE hPrevInstance,// Дескриптор родительского приложения
LPSTR lpCmdLine,// Параметры командной строки
int nCmdShow)// Состояние отображения окна
{
MSG msg;// Структура для хранения сообщения Windows
BOOL done= false;// Логическая переменная для выхода из цикла
// Задаем объект отображения
game.SetView(&view);
// Создаем OpenGL окно
if(!view.CreateGLWindow(WndProc, L"Saper", 32))
return 0;
// Пока не конец
while(!done)
{
// Если есть в очереди сообщение
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// Сообщение о выходе
if(msg.message == WM_QUIT)
done = true;
else
{
// Переводим сообщение
TranslateMessage(&msg);
// Отсылаем сообщение
DispatchMessage(&msg);
}
}
// Если нет сообщений
else
{
// Если программа активна
if(view.active)
{
// Нажата клавиша ESC
if(view.keys[VK_ESCAPE])
{
// Завершение работы
done = true;
}
// Иначе обновляем экран
else
{
SetWindowText(view.hWnd, L"Saper. Группа 23ф Данил<Sansual>");
// Рисуем сцену
view.DrawGLScene();
// Меняем буфер (двойная буферизация)
SwapBuffers(view.hDC);
}
}
}
}
game.GetStat().SaveStat();
// Завершение работы
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd,// Дескриптор окна
UINT uMsg,// Сообщение для этого окна
WPARAM wParam,// Дополнительная информация
LPARAM lParam)// Дополнительная информация
{
// Проверка сообщения для окна
switch (uMsg)
{
case WM_INITDIALOG:
game.GetStat().LoadStat();
return TRUE;
//
break;
// Проверка сообщения активности окна
case WM_ACTIVATE:
{
// Проверить состояние минимизации
if(!HIWORD(wParam))
{
// Программа активна
view.active = true;
}
else
{
// Программа теперь не активна
view.active = false;
}
// Возвращаемся в цикл обработки сообщений
return 0;
}
// Перехватываем системную команду
case WM_SYSCOMMAND:
{
// Останавливаем системный вызов
switch(wParam)
{
// Пытается ли запустится скринсейвер?
case SC_SCREENSAVE:
// Пытается ли монитор перейти в режим сбережения энергии?
case SC_MONITORPOWER:
// Предотвращаем это
return 0;
}
// Выход
break;
}
// Мы получили сообщение о закрытие?
case WM_CLOSE:
{
// Отправить сообщение о выходе
PostQuitMessage(0);
// Вернуться назад
return 0;
}
// Была ли нажата кнопка?
case WM_KEYDOWN:
{
// Если нажата кнопка, мы присваиваем этой ячейке в массиве true
view.keys[wParam] = true;
// Возвращаемся
return 0;
}
// Была ли отпущена клавиша?
case WM_KEYUP:
{
// Если отпущенна кнопка, мы присваиваем этой ячейке в массиве false
view.keys[wParam] = false;
// Возвращаемся
return 0;
}
// Изменены размеры OpenGL окна
case WM_SIZE:
{
// Младшее слово=Width, старшее слово=Height
view.ReSizeGLScene( LOWORD(lParam), HIWORD(lParam) );
// Возвращаемся
return 0;
}
case WM_TIMER:
{
game.UpdateTimer();
return 0;
}
case WM_LBUTTONDOWN:
{
// Если модель не создана - не обрабатываем кнопку мыши
if(!game.GetModel())
return 0;
// Координата клика
POINT p;
// Получаем "x" компоненту
p.x = MAKEPOINTS(lParam).x;
// Получаем "y" компоненту
p.y = MAKEPOINTS(lParam).y;
// Расчитываем координаты в системе OpenGL
POINT pgl = view.GetOGLPos(p.x, p.y);
// Если клик попал в игровое поле
if(pgl.x < view.SceneWidth - view.BorderSize &&
pgl.x > view.BorderSize &&
pgl.y < view.SceneHeight - view.BorderSize &&
pgl.y > view.BorderSize)
{
// Получаем индексы клетки по которой произошел клик
POINT id = view.GetCellIndex(pgl);
if(id.x == -1 || id.y == -1)
return 0;
/// Игра не запущенна
if (!game.IsStart())
{
// Запускаем
game.GameStart();
}
// Открываем клетку
game.GetModel()->Open(id.x, id.y);
// Проверяем статус игры
if(game.GameProcess(id.x, id.y) == GameState::CompWin)
{
MessageBox(NULL, L"Allahu Akbar", L"Obama Win", MB_OK | MB_ICONERROR);
game.EndGame();
return 0;
}
else if(game.GameProcess(id.x, id.y) == GameState::PlayerWin)
{
wchar_t buff[32];
_itow(game.GetModel()->timer, buff, 10);
MessageBox(NULL, L"Counter-Terrorist Win", L"LEFT BUTTON", MB_OK | MB_ICONEXCLAMATION);
MessageBox(NULL, buff, L"Затраченное время", MB_OK | MB_ICONINFORMATION);
game.EndGame();
return 0;
}
}
return 0;
}
case WM_RBUTTONDOWN:
{
// Если модель не создана - не обрабатываем кнопку мыши
if(!game.GetModel())
return 0;
// Координата клика
POINT p;
// Получаем "x" компоненту
p.x = MAKEPOINTS(lParam).x;
// Получаем "y" компоненту
p.y = MAKEPOINTS(lParam).y;
// Расчитываем координаты в системе OpenGL
POINT pgl = view.GetOGLPos(p.x, p.y);
// Если клик попал в игровое поле
if(pgl.x < view.SceneWidth - view.BorderSize &&
pgl.x > view.BorderSize &&
pgl.y < view.SceneHeight - view.BorderSize &&
pgl.y > view.BorderSize)
{
// Получаем индексы клетки по которой произошел клик
POINT id = view.GetCellIndex(pgl);
// Игра не запущенна
if (!game.IsStart())
{
// Запускаем
game.GameStart();
}
// Помечаем клетку
game.GetModel()->Mark(id.x, id.y);
// Проверяем статус игры
if(game.GameProcess(id.x, id.y) == GameState::PlayerWin)
{
wchar_t buff[32];
_itow(game.GetModel()->timer, buff, 10);
MessageBox(NULL, L"Counter-Terrorist Win", L"RIGHT BUTTON :D", MB_OK | MB_ICONEXCLAMATION);
MessageBox(NULL, buff, L"Затраченное время", MB_OK | MB_ICONINFORMATION);
game.EndGame();
return 0;
}
}
return 0;
}
case WM_COMMAND:
{
switch(wParam)
{
case IDC_MENU_1:
// Показываем диалог из ресурсов
DialogBox(NULL, MAKEINTRESOURCE(IDD_CHILDLG), view.hWnd, ChildDlgProc);
// Сбрасываем значения параметров поля
FieldWidth = FIELD_TYPE0;
FieldHeight = FIELD_TYPE0;
break;
case IDC_MENU_2:
// Показываем диалог из ресурсов
DialogBox(NULL, MAKEINTRESOURCE(IDD_DIALOGBAR), view.hWnd, statDlgProc);
break;
case IDC_MENU_3:
view.keys[VK_ESCAPE] = true;
break;
default:
break;
}
return 0;
}
}
// пересылаем все необработанные сообщения DefWindowProc
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
// Калбек процедура диалогового окна с настройками параметров игры
BOOL CALLBACK ChildDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDOK:
// Если была запущенна игра
if(game.IsStart())
// Останавливаем
game.EndGame();
// ...
Подобные документы
Разработка программы "Сапер", удовлетворяющей необходимым требованиям эффективности в интегрированной среде программирования Microsoft Visual C++. Специфика создания Windows-приложений. Применение логической игры для развития интереса к обучению у детей.
курсовая работа [511,1 K], добавлен 01.06.2013Разработка приложения на WinAPI с реализацией логической структуры в игре "Сапер". Реализация графической части приложения. Проверка на корректность поведения интерфейса программы, работы логической части игры, корректности записи и чтения файла.
курсовая работа [1,1 M], добавлен 17.10.2012Основные подходы при создании Windows приложений. Изучение навыков работы с 2D графикой в Windows приложениях. Методы генерации псевдослучайных чисел. Разработка игры "Сапер" с расположением мин на основе нескольких методов генерации случайных чисел.
курсовая работа [63,2 K], добавлен 18.02.2009Разработка программы игры в крестики-нолики. Примеры игровой ситуации на игровом поле. Описание входных и выходных данных, переменных и функций программы. Реализация алгоритма работы программы на языке C++. Текст программы и примеры ее выполнения.
курсовая работа [352,8 K], добавлен 14.04.2011Выбор языка и среды программирования, его обоснование. Методика и этапы реализации заданной игрой программными средствами. Принципы разработки пояснительной записки для описания функциональных возможностей разработанного программного приложения.
курсовая работа [354,8 K], добавлен 02.03.2014Общие сведения и существующие среды реализации компьютерной игры "Лабиринт". Разработка алгоритмов в виде блок-схемы, принципы программной реализации игры. Особенности тестирования разработанного программного продукта. Аспекты эксплуатации продукта.
курсовая работа [1,4 M], добавлен 18.01.2017История создания игры "Тетрис", смысл и правила данной головоломки. Разработка поля игры и фигур тетрамино. Процедуры и функции, используемые для реализации движения фигур, их поворота и складывания в ряды, удаления и подсчета количества целых рядов.
курсовая работа [87,0 K], добавлен 02.02.2013Игровая программа "шашки" для игры между человеком и компьютером. Разработка алгоритмов, историческая линия развития задач. Различные подходы к построению систем. Сокращенный листинг программы и описание алгоритма. Компоненты искусственного интеллекта.
курсовая работа [196,2 K], добавлен 26.03.2009Выбор программных средств для реализации игры "Угадай число". Разработка функционально-структурной схемы для написания текста программы. Изучение сути вариации алгоритма полного перебора с определённой эвристикой. Варианты компьютерной реализации игры.
контрольная работа [337,3 K], добавлен 19.06.2012Приемы программирования в Delphi. Алгоритм поиска альфа-бета отсечения, преимущества. Описание программного средства. Разработка программы, реализующая алгоритм игры "реверси". Руководство пользователя. Листинг программы. Навыки реализации алгоритмов.
курсовая работа [357,1 K], добавлен 28.02.2011Разработка программы логической игры в "крестики-нолики" пять в ряд на поле размера 15х15 клеток с применением графики на языке Pascal с использованием объектно-ориентированного программирования. Структура алгоритма программы и описание ее работы.
курсовая работа [821,5 K], добавлен 13.02.2012Разработка эскизного и технического проекта программы игры "Собери картинку". Назначение и область применения, основные технические характеристики. Разработка рабочего проекта, разработка и спецификация программы игры. Описание и тестирование программы.
курсовая работа [22,6 K], добавлен 10.06.2010Знакомство с интерфейсом пользователя и сценарием использования программы игры в крестики и нолики. Функциональные и нефункциональные требования для персонального компьютера. Исключительные ситуации и реакция программы. Пример кода игры и комментарии.
курсовая работа [236,5 K], добавлен 27.01.2014Исследование спецификации логической игры "Сапёр". Системное и функциональное проектирование приложения. Разработка программных модулей. Обзор классов, необходимых для создания интерфейса данного приложения. Инструменты для реализации логической игры.
курсовая работа [1,2 M], добавлен 13.01.2016Разработка аналога игры "Крестики-нолики", где игроки выбирают размер поля. Правила игры. Интерфейс программы. Главная функция main. Класс XO. Метод вывода поля и хода игроков. Методы поиска крестиков, ноликов. Методы проверки выигрышных ситуаций игроков.
курсовая работа [281,5 K], добавлен 30.01.2018Описание правил игры "Морской бой". Особенности современных компьютеров и искусственного интеллекта. Создание общей блок-схемы программы, ее внешний вид. Необходимые переменные, процедуры и функции. Характеристика объектов, используемых в приложении.
курсовая работа [950,1 K], добавлен 05.11.2012Понятие и эволюция игр, анализ их различных жанров и существующих аналогов. Выбор программных средств для реализации игры, написание сюжета и выбор среды разработки игры. Алгоритмы для придания гибкости обучающей игре. Описание программных модулей.
дипломная работа [2,7 M], добавлен 27.10.2017Проектирование игры "Морской бой" путем составления диаграмм UML, IDEF0, DFD, моделирующих требования к программе. Разработка программы с использованием языка C# и фреймворка.NETFramework 3.5. Тестирование белого ящика и альфа-тестирование продукта.
курсовая работа [3,9 M], добавлен 24.10.2013Разработка и создание игры "Змейка". Использование динамически-активных принципов языка Java. Графические объекты программы. Описание игры, правила, теоретические сведения. Классы приложения. Типы данных. Реализация. Метод. Объект. Блок-схема игры.
курсовая работа [12,4 K], добавлен 18.06.2008Обоснование необходимости разработки программы для игры "Тетрис". Математическая и графическая части алгоритма. Выбор языка и среды программирования. Отладка текста программы, разработка интерфейса пользователя. Тестирование, руководство пользователя.
курсовая работа [1,5 M], добавлен 17.01.2011