Полный текст:
Оглавление
Введение. 4
Глава 1. Разработка эскизного и технического проектов программы.. 5
1.1. Назначение и область применения. 5
1.2. Технические характеристики. 5
1.2.1. Постановка задачи. 5
1.2.2. Описание алгоритма. 7
1.2.3. Организация входных и выходных данных. 9
1.2.4. Выбор состава технических и программных средств. 10
Глава 2. Разработка рабочего проекта. 11
2.1. Разработка программы.. 11
2.2. Написание программы.. 16
2.3. Спецификация программы.. 18
2.4. Текст программы.. 20
2.5. Тестирование программы.. 26
Глава 3. Внедрение. 27
3.1. Условия выполнения программы.. 27
3.2. Выполнение программы.. 27
3.3. Сообщение оператору. 27
Заключение. 29
Список литературы.. 30
Приложение 1
Приложение 2
Введение
Разработка приложения для Windows, представляющего собой компьютерную игру «Морской бой».
Условие задачи:
Разработать программу, моделирующую игру «Морской бой». На поле 10 на 10 позиций стоят невидимые вражеские корабли: 4 корабля по 1 клетке, 3 корабля по 2 клетки, 2 корабля по 3 клетки, 1 корабль в 4 клетки. Необходимо поразить каждую из клеток кораблей. Два игрока вводят позиции кораблей в виде цифр (1, 2, 3, 4) в соответствующие элементы матрицы, тем самым определяя конфигурацию и положение кораблей. Игроки по очереди «наносят удары» по кораблям противника. Если позиция корабля указана верно, то она помечается крестиком на поле. Предусмотреть вариант игры, когда одним из играющих является ЭВМ.
Глава 1. Разработка эскизного и технического проектов программы
Стандарт ГОСТ 19.404-79[1] устанавливает основные требования к содержанию и оформлению программного документа «Пояснительная записка», входящего в состав документов на стадиях разработки эскизного и технического проектов программы.
1.1. Назначение и область применения
Необходимо разработать учебную программу. Область применения: досуг программиста. Поскольку ставится задача разработать приложение для Windows, то использоваться программа может под управлением Windows 9x.
1.2. Технические характеристики
1.2.1. Постановка задачи
Для отображения кораблей на поле будем использовать визуальный компонент TGridString. Расстановка кораблей производится мышью. Игрок, который расставляет корабли, сначала нажимает на клетку, в которой будет начинаться корабль, а потом на клетку, в которой он будет заканчиваться. Корабли расставляются по порядку, т.е. сначала 4-х палубный, затем два 3-х палубных, и т.д. Размер корабля проверяется, и корабль не поставится, если игрок ткнул в клетку, которая дальше или ближе запланированной. Для однопалубных кораблей нужно ткнуть в клетку только один раз.
При запуске игры нужно сделать выбор, игра происходит против человека или против компьютера. Выбор будет храниться в глобальной переменной на протяжении игры для того, чтобы при смене хода определять, нужно ли визуально сменить поля. При игре против компьютера этого делать не следует.
После расстановки кораблей первым (левым) игроком происходит расстановка кораблей другим игроком. Если игра идёт против человека, то левое поле скрывается, и правый игрок расставляет корабли. Если же выбран режим игры против компьютера, то компьютер расставляет корабли в случайном порядке. При расстановке кораблей компьютером должны быть соблюдено правило, что корабли не могут находиться в соседних или смежных клетках. Алгоритм расстановки наугад и проверки, может ли он там стоять – не самый лучший, т.к. если изменятся условия игры и надо будет поставить больше кораблей, может возникнуть ситуация, когда корабль может встать всего в одну точку, может потребоваться продолжительное время, пока компьютер случайным образом попадёт в эту точку. Алгоритм расстановки в игре следует реализовать следующим образом: для каждого корабля происходит перебор координат и проверка, может ли он встать в этой точке. Если может, то координаты той точки сохраняются. После просмотра всех точек поля случайным образом выбирается одна из сохранённых координат. С каждым следующим кораблём количество мест, в которые он может встать, будет уменьшаться.
После расстановки кораблей случайным образом происходит выбор того, кто будет первым стрелять. В случае попадания игрок ходит ещё раз, в случае промаха происходит смена стреляющего. При игре против человека следует сделать двухсекундную паузу, чтобы первый игрок успел отвернуться, после чего показываются поля для другого игрока. В случае игры против компьютера, поля показывать не надо.
После того, как первый игрок промахнулся, ход переходит к другому игроку. Для игры с компьютером добавим невизуальный компонент TTimer, который будет проверять каждые несколько секунд, можно ли стрелять. Если можно – будет произведён выстрел по полю первого игрока.
Для отображения полей нам понадобится хранить реальное расположение кораблей и координаты промахов и попаданий для каждого игрока. После каждого выстрела нужно проверить, есть ли ещё неподбитые корабли. Если все корабли подбиты – выводится сообщение о выигрыше.
1.2.2. Описание алгоритма
При запуске игры программа просит выбрать противника и сохраняет выбор пользователя в глобальной переменной pcopponent. Элементы формы выбора противника скрываются и показывается второе поле.
Первый игрок расставляет свои корабли. Глобальная переменная ведёт подсчёт расставленных кораблей. Когда количество кораблей станет равно 10, настанет очередь второго игрока расставлять корабли. Если была выбрана игра против компьютера, то корабли расставятся случайным образом. В случае игры против человека, поле первого игрока с расставленными кораблями будет скрыто, и второй игрок получит возможность расставить корабли на своё усмотрение. По окончании расстановки кораблей вторым игроком выбирается игрок, который будет ходить первым.
Все последующие нажатия на поле будут трактоваться как выстрел. При промахе программа ждёт пару секунд и ход переходит к другому игроку. В случае попадания, проверяется, остались ли у противника ещё корабли. В случае наличия кораблей текущий игрок стреляет ещё раз. Если кораблей нет, значит следует вывести сообщение о победе.
Обобщенная блок-схема алгоритма игры представлена на рисунке 1.
начало
Право хода = правый
Ввод типа противника, установка значений по умолчанию
Выбор клетки
Право хода = левый
Режим = расстановка
Корабли обоих игроков расставлены
стреляем
да
Ставим корабль
Корабли игрока расставлены
Оба игрока расставили корабли
Режим = стрельба
Выбор первого стреляющего
Попали
У противника есть корабли
Право хода = другой игрок
Сообщение о выигрыше
конец
нет
да
да
да
да
нет
нет
нет
нет
Противник компьютер
Расставить корабли
да
нет
Рис. 1 Блок-схема алгоритма игры
1.2.3. Организация входных и выходных данных
Входными данными являются: тип соперника и координаты расставленных кораблей.
Глобальные переменные, используемые в программе и описанные в Unit1, приведены в таблице 1.
Таблица 1
Переменная
Тип
Описание
leftfield
Двумерный массив целого типа, состоящий из двух 10 строк и 10 столбцов
Хранит реальное расположение кораблей для левого игрока.
rightField
Двумерный массив целого типа, состоящий из двух 10 строк и 10 столбцов
Хранит реальное расположение кораблей для правого игрока.
leftVisible
Двумерный массив целого типа, состоящий из двух 10 строк и 10 столбцов
Хранит открытые правым игроком клетки левого поля
rightVisible
Двумерный массив целого типа, состоящий из двух 10 строк и 10 столбцов
Хранит открытые левым игроком клетки правого поля
ships
Массив целого типа, состоящий из 10 элементов
Хранит конфигурацию кораблей (количество палуб)
currentship
Целый
Текущий расставляемый корабль
lastrow
Целый
Во время расстановки запоминает строку первой палубы
lastcol
Целый
Во время расстановки запоминает столбец первой палубы
firstpoint
Логический
Показывает, была ли поставлена первая палуба корабля
beginduel
Логический
Режим игры: true для начала игры, false для расстановки кораблей
pcopponent
Логический
Противник: true – компьютер, false – человек
leftshoots
Логический
Право хода: true – стреляет левый, false – стреляет правый
1.2.4. Выбор состава технических и программных средств
Делается вывод о необходимости использования интегральной среды разработки программ Borland C++ Builder. Среда Borland C++ Builder позволяет достаточно быстро разрабатывать приложения для Windows. Технические характеристики компьютера: Pentium 166 МГц и выше; объём оперативной памяти 128 Мб; не меньше 115 Мб на жёстком диске. Дополнительных средств (принтер, сканер и т. д.) не требуется.
Глава 2. Разработка рабочего проекта
2.1. Разработка программы
Для разработки приложения «Морской бой» используются средства визуального программирования Borland C++ Builder. Проект программы содержит одно окно: Form1. При запуске игры в его правой части содержится форма выбора противника. При выборе противника форма скрывается и вместо неё появляется поле второго игрока. Форма выбора противника представлена на рисунке 3, окно программы после выбора противника – на рисунке 4.
Рис. 3 Форма выбора противника
1 - компонент Label1: TLabel
Свойства:
Caption = “Противник”:
Событий нет.
2 - компонент RadioButton1: TRadioButton
Свойства:
Caption = “Компьютер”
Checked = True
Событий нет.
3 - компонент RadioButton2: TRadioButton
Свойства:
Caption = “Человек”
Checked = False
Событий нет.
4 - компонент Button1: TButton
Свойства:
Caption = “Начать игру”
События:
OnClick = Button1Click – начать игру
Рис. 4 Окно игры – Form1
Компонент Form1 – окно игры
Свойства:
Caption = “Морской бой”
Событий нет.
5 - компонент Timer1: TTimer
Свойства:
Enabled = False
Interval = 2000
События:
OnTimer = Timer1Timer – при игре против компьютера – выстрел компьютера, при игре против человека – смена ходящего.
6 - компонент StringGrid1: TStringGrid
Свойства:
ColCount = 11
Ctl3D = False
DefaultColWidth = 25
DefaultRowHeight = 25
Enabled = False
RowCount = 11
События:
OnDrawCell = StringGrid1DrawCell – показывает корабли на поле. Если стреляет левый игрок (leftshoots = true) - показывает полностью открытое поле, если правый – те клетки, куда он стрелял;
OnSelectCell = StringGrid1SelectCell – в зависимости от режима игры, расставляет корабли или стреляет.
компонент StringGrid2: TStringGrid
Свойства:
ColCount = 11
Ctl3D = False
DefaultColWidth = 25
DefaultRowHeight = 25
RowCount = 11
Visible = False
События:
OnDrawCell = StringGrid2DrawCell – показывает корабли на поле. Если стреляет правый игрок (leftshoots = false) - показывает полностью открытое поле, если левый – те клетки, куда он стрелял;
OnSelectCell = StringGrid2SelectCell – в зависимости от режима игры, расставляет корабли или стреляет.
7 - компонент Panel1: TPanel
Свойства:
Width = 305
Height = 305
Событий нет.
компонент Panel2: TPanel
Свойства:
Width = 305
Height = 305
Событий нет.
8 - компонент StatusBar1: TStatusBar
Свойства:
Width = 617
Panels = <
item
Width = 307
end
item
Width = 50
end>
Событий нет.
2.2. Написание программы
Программную реализацию разработанных алгоритмов содержат обработчики событий.
После запуска игры следует выбрать противника. При нажатии на кнопку Button1 обрабатывается событие Onclick, которое запоминает тип противника, прячет элементы формы выбора соперника, показывает поле второго игрока и вызывает функцию StartNewGame(), в которой происходит установка значений по умолчанию. Далее программа ожидает клика по левому полю.
При нажатии мышью на клетку левого поля, StringGrid1, обрабатывается событие OnSelectCell. Если корабли ещё не расставлены, вызывается функция ManualPlacing, которая определяет, пытается ли игрок поставить начало корабля, или его конец, и ставит начало или конец корабля. В случае, если корабли уже расставлены, и идёт дуэль, вызывается функция PlayerShoots(), открывающая данную клетку.
Каждый раз при отрисовке клетки поля игрока на форме срабатывает событие OnDrawCell, в котором вызывается функция CellColorer, раскрашивающая поля. В зависимости от того, кто сейчас ходит функция раскрашивает одно поле полностью открытым, а на другом – только те места, куда стрелял текущий игрок. Корабли левого игрока раскрашиваются синим цветом, корабли правого – красным. Попадание в корабль отмечается крестиком (на соответствующем цветном фоне), промах отмечается кружочком на белом фоне.
На форме находится невизуальный компонент Timer1. В начале игры его свойство Enabled установлено в False. Момент включения таймера и его функция зависит от того, против кого мы играем. При игре против компьютера таймер включится после выбора того, кто будет стрелять первым. При игре против человека таймер включится в случае промаха игрока. Функциональность также различается. При игре против компьютера таймер будет проверять, настал ли черёд компьютера стрелять. Если настал – вызывается функция ComputerShoots(), которая стреляет по левому полю. При игре против человека, таймер включится после промаха игрока, подождёт две секунды, чтобы игрок успел отвернуться, после чего сменит поля и выключится.
2.3. Спецификация программы
Исполняемый файл программы «Морской бой» имеет название Seawar.exe. Его работоспособность не зависит от расположения на диске. Наименования файлов входящих в проект и краткая информация об их содержании отображены в таблице 3.
Таблица 3
Наименование
Обозначение
Примечание
seawar.bpr
Файл описания проекта
Содержит текущие установки проекта: настройки компилятора и компоновщика, имена служебных каталогов, условные директивы
seawar.cpp
Файл проекта
Связывает все файлы, из которых состоит приложение, создаёт и запускает приложение
seawar.obj
Объектный файл для seawar.cpp
Откомпилированная версия seawar.cpp
seawar.res
Файл ресурсов
Содержит пиктограммы, графические изображения
seawar.tds
Служебная информация
Содержит отладочную информацию о проекте
Unit1.cpp
Файл программного модуля
Основная функциональность программы
Unit1.dfm
Файл описания формы
Содержит список свойств всех компонентов, включённых в форму Form1
Unit1.h
Заголовочный файл формы
Описание класса TForm1, указание на подключение необходимых файлов
Unit1.obj
Объектный файл для Unit1.cpp
Откомпилированная версия Unit1.cpp
2.4. Текст программы
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
int leftField[10][10], rightField[10][10];
int leftVisible[10][10], rightVisible[10][10];
int ships[] = {4, 3, 3, 2, 2, 2, 1, 1, 1, 1};
int currentship;
int lastrow, lastcol;
bool firstpoint;
bool beginduel;
bool pcopponent; // противник
bool leftshoots = TRUE;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
char c;
int i;
// определимся с противником
if (RadioButton1->Checked)
pcopponent = TRUE;
else
pcopponent = FALSE;
StringGrid1->Enabled = TRUE;
// спрячем элементы
Label1->Visible = FALSE;
RadioButton1->Visible = FALSE;
RadioButton2->Visible = FALSE;
Button1->Visible = FALSE;
StringGrid2->Visible = TRUE;
// нарисуем координаты
for (i = 1; i <= 10; i++) {
StringGrid1->Cells[i][0] = i;
StringGrid2->Cells[i][0] = i;
c = i + 96;
StringGrid1->Cells[0][i] = c;
StringGrid2->Cells[0][i] = c;
}
StartNewGame();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::StartNewGame(void)
{
currentship = 1;
beginduel = FALSE;
firstpoint = FALSE;
Timer1->Tag = 0;
int i, j;
// очистим поля
for (i = 0; i < 10; i++) {
for (j = 0; j < 10; j++) {
leftField[i][j] = 0;
leftVisible[i][j] = 0;
rightField[i][j] = 0;
rightVisible[i][j] = 0;
}
}
StatusBar1->Panels->Items[0]->Text = "Левый игрок располагает корабли";
// дальше обрабатывается в зависимости от нажатий
}
//---------------------------------------------------------------------------
void __fastcall TForm1::StringGrid1SelectCell(TObject *Sender, int ACol,
int ARow, bool &CanSelect)
{
if (beginduel == FALSE) { // значит пока что расставляем корабли
ManualPlacing(Sender, ACol, ARow);
} else { // значит стреляем
if (leftshoots == FALSE) { // на всякий случай проверим, его ли ход
PlayerShoots(Sender, ACol, ARow);
}
}
StringGrid1->Refresh();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::StringGrid2SelectCell(TObject *Sender, int ACol,
int ARow, bool &CanSelect)
{
if (beginduel == FALSE) { // значит пока что расставляем корабли
ManualPlacing(Sender, ACol, ARow);
} else { // значит стреляем
if (leftshoots == TRUE) { // на всякий случай проверим, его ли ход
PlayerShoots(Sender, ACol, ARow);
}
}
StringGrid2->Refresh();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ManualPlacing(TObject *Sender, int ACol, int ARow)
{
TStringGrid* currentGrid = (TStringGrid*)Sender;
int (*playerField)[10];
if (((TStringGrid*)Sender)->Name == "StringGrid1") {
playerField = leftField;
} else {
playerField = rightField;
}
int shipsize = ships[currentship - 1];
int direction = -1; // всего четыре направления
int currentrow, currentcol;
int i;
// продолжаем расставлять корабли только в том случае, если есть ещё
if (currentship <= 10) {
// расставляем кораблики
if (firstpoint == FALSE || shipsize == 1) { //значит это будет первая точка
playerField[ARow - 1][ACol - 1] = currentship;
lastrow = ARow;
lastcol = ACol;
if (shipsize == 1)
currentship++;
else
firstpoint = TRUE;
currentGrid->Refresh();
} else { // тогда ставим хвост корабля
// определим конкретное направление
if (ARow - lastrow == shipsize-1 && lastcol == ACol) // вверх
direction = 0;
else if (lastrow - ARow == shipsize-1 && lastcol == ACol) // вниз
direction = 1;
else if (lastcol - ACol == shipsize-1 && lastrow == ARow) // вправо
direction = 2;
else if (ACol - lastcol == shipsize-1 && lastrow == ARow) // влево
direction = 3;
// если направление удовлетворяет
if (direction != -1) {
currentrow = ARow - 1;
currentcol = ACol - 1;
shipsize = 0;
// заполним всё пространство между концом и началом
while (shipsize != ships[currentship - 1] - 1) {
playerField[currentrow][currentcol] = currentship;
if (direction == 0) // вверх
currentrow--;
else if (direction == 1) // вниз
currentrow++;
else if (direction == 2) // вправо
currentcol++;
else // влево
currentcol--;
shipsize++;
}
currentship++;
firstpoint = FALSE;
currentGrid->Refresh();
}
}
if (currentship > 10 && leftshoots == TRUE)
NextPlayerSetsCoordinates();
if (currentship > 10 && leftshoots == FALSE)
WhoIsFirst();
} // if (currentship <= 10)
}
//---------------------------------------------------------------------------
void __fastcall TForm1::NextPlayerSetsCoordinates(void)
{
/* правый игрок располагает корабли */
currentship = 1; // начали отсчёт кораблей заново
leftshoots = FALSE; // ходит правый
// спрячем левое поле если человек
if (pcopponent == FALSE)
StringGrid1->Refresh();
StatusBar1->Panels->Items[0]->Text = "";
StatusBar1->Panels->Items[1]->Text = "Правый игрок располагает корабли";
if (pcopponent)
RandomCoordinates();
// иначе расставляет человек
}
//---------------------------------------------------------------------------
void __fastcall TForm1::RandomCoordinates(void)
{
int i, j, s;
int shipsize; // нужен при переборе
int direction; // направление, 0 - горизонталь, 1 - вертикаль
int goodpositions[100];
int goodposcounter = 0;
int currentline, currentrow;
bool good;
currentship = 1;
// десять кораблей
for (s = 0; s < 10; s++) {
shipsize = ships[s];
goodposcounter = 0;
// выбираем направление
randomize();
direction = random(2); // 0 или 1
// собираем места, куда можем поставить
// для этого обходим каждую клетку поля
for (i = 0; i < 10; i++) {
for (j = 0; j < 10; j++) {
// и проверяем, помещается ли там корабль или нет
currentline = i;
currentrow = j;
good = TRUE;
shipsize = ships[s];
// обходим каждую палубу корабля
while (shipsize > 0) {
if (currentrow > 9 || currentline > 9) {
good = FALSE;
}
// придётся проверить все 8 сторон
if (currentline != 0) { // верхний средний
if (rightField[currentline - 1][currentrow] != 0) {
good = FALSE;
}
}
if (currentline != 0 && currentrow != 9) { // верхний правый
if (rightField[currentline - 1][currentrow + 1] != 0) {
good = FALSE;
}
}
if (currentrow != 9) { // средний правый
if (rightField[currentline][currentrow + 1] != 0) {
good = FALSE;
}
}
if (currentline != 9 && currentrow != 9) { // нижний правый
if (rightField[currentline + 1][currentrow + 1] != 0) {
good = FALSE;
}
}
if (currentline != 9) { // нижний средний
if (rightField[currentline + 1][currentrow] != 0) {
good = FALSE;
}
}
if (currentline != 9 && currentrow != 0) { // нижний левый
if (rightField[currentline + 1][currentrow - 1] != 0) {
good = FALSE;
}
}
if (currentrow != 0) { // средний левый
if (rightField[currentline][currentrow - 1] != 0) {
good = FALSE;
}
}
if (currentline != 0 && currentrow != 0) { // верхний левый
if (rightField[currentline - 1][currentrow - 1] != 0) {
good = FALSE;
}
}
if (good == FALSE)
break;
if (direction == 0)
currentrow++;
else
currentline++;
shipsize--;
} // while (sizecounter > 0)
if (good == TRUE) {
goodpositions[goodposcounter] = i*10 + j;
goodposcounter++;
}
} // for j
} // for i
// выбираем из них одно
randomize();
int place = random(goodposcounter); // выбрали место старта
// теперь поставим корабль
shipsize = ships[s];
currentline = (goodpositions[place] - goodpositions[place]%10)/10;
currentrow = goodpositions[place]%10;
while (shipsize > 0) {
rightField[currentline][currentrow] = currentship;
if (direction == 0)
currentrow++;
else
currentline++;
shipsize--;
}
currentship++;
}
// компьютер расставил корабли, решаем, кто ходит первый
WhoIsFirst();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::WhoIsFirst(void)
{
/* выбор, кто ходит первым */
int randchoice = random(2);
if (randchoice == 0) {
leftshoots = TRUE;
StatusBar1->Panels->Items[1]->Text = "";
StatusBar1->Panels->Items[0]->Text = "Левый игрок ходит";
}
else {
leftshoots = FALSE;
StatusBar1->Panels->Items[1]->Text = "Правый игрок ходит";
}
beginduel = TRUE; // игра началась!
if (pcopponent)
Timer1->Enabled = TRUE;
// иначе ждём нажатий
}
//---------------------------------------------------------------------------
void __fastcall TForm1::PlayerShoots(TObject *Sender, int ACol, int ARow)
{
int i, j;
bool killed = TRUE;
bool win = TRUE;
int shipnumber;
int (*opponentField)[10], (*opponentVisible)[10];
TStatusPanel* playerStatusPanel;
if (((TStringGrid*)Sender)->Name == "StringGrid1") {
opponentField = leftField;
opponentVisible = leftVisible;
playerStatusPanel = StatusBar1->Panels->Items[1];
} else {
opponentField = rightField;
opponentVisible = rightVisible;
playerStatusPanel = StatusBar1->Panels->Items[0];
}
shipnumber = opponentField[ARow - 1][ACol - 1];
if (shipnumber != 0) { // если в этом месте корабль
opponentVisible[ARow - 1][ACol - 1] = 1;
// проверяем, попал или убил
for (i = 0; i < 10; i++)
for (j = 0; j < 10; j++)
if (opponentField[i][j] == shipnumber && opponentVisible[i][j] == 0)
killed = FALSE;
if (killed) {
playerStatusPanel->Text = "Убил!";
// проверяем, есть ли ещё корабли
for (i = 0; i < 10; i++)
for (j = 0; j < 10; j++)
if (opponentField[i][j] != 0 && opponentVisible[i][j] == 0)
win = FALSE;
if (beginduel && win) {
Application->MessageBoxA("Победа!", "", MB_OK);
StringGrid1->Enabled = false;
StringGrid2->Enabled = false;
}
} else
playerStatusPanel->Text = "Попал!";
} else {
opponentVisible[ARow - 1][ACol - 1] = -1;
playerStatusPanel->Text = "Мимо!";
if (pcopponent == TRUE) {
leftshoots = !leftshoots; // смена игрока
ColorPanels();
} else {
Timer1->Enabled = TRUE;
}
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
if (pcopponent == TRUE) {
if (leftshoots == FALSE)
ComputerShoots(); // пли!
} else {
if (Timer1->Tag < 2)
Timer1->Tag++;
else {
Timer1->Tag = 0;
Timer1->Enabled = FALSE;
leftshoots = !leftshoots; // смена игрока
ColorPanels();
}
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ComputerShoots(void)
{
int i = random(10);
int j = random(10);
if (leftField[i][j] == 0)
leftVisible[i][j] = -1;
else
leftVisible[i][j] = 1;
PlayerShoots(StringGrid1, j+1, i+1);
StringGrid1->Refresh();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ColorPanels(void)
{
if (leftshoots == TRUE) {
Panel1->Color = clNavy;
Panel2->Color = clBtnFace;
} else{
Panel1->Color = clBtnFace;
Panel2->Color = clNavy;
}
StringGrid1->Refresh();
StringGrid2->Refresh();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::CellColorer(TObject *Sender, int ACol, int ARow, TRect &Rect)
{
TColor playerColor, opponentColor;
TStringGrid* currentGrid = (TStringGrid*)Sender;
int (*playerField)[10], (*playerVisible)[10], (*opponentVisible)[10], (*opponentField)[10];
if (leftshoots == TRUE) {
playerField = leftField;
playerVisible = leftVisible;
opponentVisible = rightVisible;
playerColor = clSkyBlue;
opponentColor = clRed;
} else {
playerField = rightField;
playerVisible = rightVisible;
opponentVisible = leftVisible;
playerColor = clRed;
opponentColor = clSkyBlue;
opponentField = leftField;
}
// показываем реальное (своё)
if ((leftshoots && currentGrid->Name == "StringGrid1") ||
(leftshoots == FALSE && currentGrid->Name == "StringGrid2")) {
// ^ показываем реальное расположение кораблей для правого поля только если играет человек
// игрок расставляет корабли или смотрит в игре на свои
if (playerField[ARow - 1][ACol - 1] != 0 && playerVisible[ARow - 1][ACol - 1] == 0) {
if ((pcopponent == FALSE || leftshoots == TRUE || currentGrid->Name != "StringGrid2")) {
// если в этом поле корабль, и в него не стреляли
currentGrid->Canvas->Brush->Color = playerColor;
currentGrid->Canvas->FillRect(Rect);
}
} else if (playerField[ARow - 1][ACol - 1] != 0 && playerVisible[ARow - 1][ACol - 1] == 1) {
// если в этом поле корабль, и в него попали
currentGrid->Canvas->Brush->Color = playerColor;
currentGrid->Canvas->FillRect(Rect);
currentGrid->Canvas->TextOut(Rect.Left + 9, Rect.Top + 5, "x");
} else if (playerField[ARow - 1][ACol - 1] == 0 && playerVisible[ARow - 1][ACol - 1] == -1) {
// если в этом поле пусто, и в него стреляли и промазали
currentGrid->Canvas->TextOut(Rect.Left + 9, Rect.Top + 5, "o");
}
}
// показываем видимое (чужое)
if ((leftshoots && currentGrid->Name == "StringGrid2") ||
(leftshoots == FALSE && currentGrid->Name == "StringGrid1")) {
if (opponentVisible[ARow - 1][ACol - 1] == 1) {
currentGrid->Canvas->Brush->Color = opponentColor;
currentGrid->Canvas->FillRect(Rect);
currentGrid->Canvas->TextOut(Rect.Left + 9, Rect.Top + 5, "x");
} else if (opponentVisible[ARow - 1][ACol - 1] == -1) {
currentGrid->Canvas->TextOut(Rect.Left + 9, Rect.Top + 5, "o");
} else if (pcopponent == TRUE && leftshoots == FALSE && currentGrid->Name == "StringGrid1" && opponentField[ARow - 1][ACol - 1] != 0) {
// для игры с компьютером
currentGrid->Canvas->Brush->Color = opponentColor;
currentGrid->Canvas->FillRect(Rect);
}
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::StringGrid1DrawCell(TObject *Sender, int ACol,
int ARow, TRect &Rect, TGridDrawState State)
{
if (ARow > 0 && ACol > 0) {
CellColorer(Sender, ACol, ARow, Rect);
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::StringGrid2DrawCell(TObject *Sender, int ACol,
int ARow, TRect &Rect, TGridDrawState State)
{
if (ARow > 0 && ACol > 0) {
CellColorer(Sender, ACol, ARow, Rect);
}
}
//---------------------------------------------------------------------------
2.5. Тестирование программы
После запуска приложения появляется главное окно программы в центре экрана. Вид программы представлен в приложении 1. Выбираем компьютер в качестве противника и нажимаем кнопку «Начать игру». Появляется поле второго игрока. Расставляем корабли на левом поле. Сначала ставится 4-х палубный корабль: нажимаем на клетку, в которой будет начало корабля, потом на клетку, в которой будет конец корабля, так чтобы корабль занимал 4 клетки. Подобным образом ставим два 3-х палубных и три 2-х палубных корабля. Однопалубные корабли ставятся одним нажатием.
Играем с компьютером, нажимая на клетки его поля, когда вокруг поля появляется фиолетовая рамка. Вид окна программы на последнем этапе игры представлен в приложении 1, четвертый рисунок.
Глава 3. Внедрение
В разделе описываются (руководство ГОСТ 19.505-79[2]):
- Условие выполнения программы;
- Выполнение программы;
- Сообщение оператору;
3.1. Условия выполнения программы
Данное приложение, разработанное в среде Borland C++ Builder, способно работать на персональных компьютерах с минимальными требованиями Pentium 100, оперативной памятью 32 Мб, операционной средой Windows.
3.2. Выполнение программы
Запустить приложение можно также как и любое другое. Окно приложения загрузится и появится в центре экрана. Для начала игры следует нажать кнопку «Начать игру». Для выхода из программы необходимо нажать Alt+F4 или щелкнуть левой кнопкой мыши по крестику в правом верхнем углу экрана.
3.3. Сообщение оператору
Постоянно на протяжении всей игры в строку состояния выводятся подсказки пользователю. Так, например, в первую панель строки (под левым полем) выводятся сообщения о промахе, попадании или потоплении корабля левым игроком: «Мимо!», «Попал!», «Убил!». Во вторую панель выводятся такие же сообщения для правого игрока.
Заключение
В заключении проведённой разработки игры в рамках курсовой работы можно сделать нижеследующие выводы.
При разработке небольших логических компьютерных игр для операционной системы Windows следует использовать среды разработки, такие как Borland C++ Builder или Delphi, так как они содержат визуальные компоненты, упрощающие вывод информации и позволяют сосредоточиться на логике работы программы, и не задумываться над визуальной отрисовкой.
При проектировании игры для нескольких человек, следует определять, какие действия будут выполняться для всех игроков, и при программировании выносить такие действия в отдельные функции, что позволит сократить количество кода и сделать его более понятным. Следует помнить, что при выборе клетки компонента TStringGrid передаётся указатель на StringGrid, который содержит эту клетку. Поэтому в данной курсовой работе нажатия на поле игрока передают этот указатель в общие для обоих игроков функции установки корабля или стрельбы по кораблю, в которых уже и происходит действие для поля конкретного игрока.
Несмотря на то, что разработка игры является увлекательным занятием, во время написания приложения следует, по возможности, максимально автоматизировать ввод данных. Во время разработки, данное приложение содержало функцию, которая автоматически расставляло корабли и для левого игрока.
Список литературы
1. Калверт Ч., Рейсдорф К. Borland C++ Builder 5. Энциклопедия программиста. «ДиаСофт», 2001.
2. В. Ермолаев, Т. Сорока C++ Builder: Книга рецептов. КУДИЦ-Образ, 2006.
3. А. Я. Архангельский Язык С++ в С++Builder. Бином-Пресс, 2008.
4. А. Я. Архангельский Компоненты C++Builder. Справочное и методическое пособие. Бином-Пресс, 2008.
Приложение 1
Виды программы, принимаемые во время ее тестирования.
Приложение 2
К семестровой курсовой работе прилагаются исходные файлы.
Наименование
Обозначение
Размер
seawar.bpr
Файл описания проекта
4,16 Кб
seawar.cpp
Файл проекта
925 байт
seawar.exe
Исполняемый файл программы
649 Кб
seawar.obj
Объектный файл для seawar.cpp
17,2 Кб
seawar.res
Файл ресурсов
876 байт
seawar.tds
Служебная информация
3,12 Мб
Unit1.cpp
Файл программного модуля
16,8 Кб
Unit1.dfm
Файл описания формы
2,50 Кб
Unit1.h
Заголовочный файл формы
2,09 Кб
Unit1.obj
Объектный файл для Unit1.cpp
122 Кб
[1] ГОСТ 19.404-79 Установление общих требований к содержанию документации процесса разработки, МНИЦ, ГНИИ ИТТ, ВНИИ, стандарт принят взамен ГОСТ 19.401-79 до июля 2004г.
[2] ГОСТ 19.505-79 Установление общих требований к содержанию документации процесса разработки, МНИЦ, ГНИИ ИТТ, ВНИИ, стандарт принят взамен ГОСТ 19.501-79 до июля 2004г.