Назад | Начало урока | Вперед
Содержание

Глава 5

Стреляем по кораблям!

Мы научили компьютер расставлять корабли на чужом поле
в случайном порядке. Теперь уберем код, который отображал
корабли, расставленные на чужом поле. По условию игры это не надо.

Далее надо научиться стрелять по кораблям.
Что делать - это морской бой.

Для этого надо дописать функцию OnLButtonDown()
на поле противника. И изменить функцию OnPaint()

В чем выражается выстрел? Надо проанализировать что
находится в данной клетке, и соответственно отобразить
это на поле. Если попали в корабль - изобразить взрыв.
Если мимо - поставить крестик. Это визуально. Но в свойствах
матрицы надо изменить данные клетки - корабль ранен,
или не попал (по клетке стреляли, но не попали).

Для начала изменим функцию OnLButtonDown().
Добавим в нее ветвь - "режим наше поле"

Вот полная функция:


if(m_Mode == FOREIGN_FIELD && m_Operation == FIRE) {
point.x = point.x/20;
point.y = point.y/20;

//по этой клетке уже стреляли. Выход.
if(m_Event[point.y][point.x] == SHIPKILLED ||

m_Event[point.y][point.x] == SHIPWOUND ||
m_Event[point.y][point.x] == CANNOT ||
m_Event[point.y][point.x] == NEARKILLEDSHIP) return;
//по клетке еще не стреляли. Инкремент числа ходов
m_Steps++;

if(m_Event[point.y][point.x] == SHIP) {

m_GoodSteps++; //инкремент результативный ход
IsKilled = SHIPKILLED;
m_Event[point.y][point.x] = SHIPWOUND;
for(i=1; i<4; i++)
if(m_Event[point.y][point.x+i] != SHIPWOUND || point.x+i==10) break;
if(m_Event[point.y][point.x+i] == SHIP && point.x+i<10)
IsKilled = SHIPWOUND;
for(i=1; i<4; i++)
if(m_Event[point.y][point.x-i] != SHIPWOUND || point.x-i==-1) break;
if(m_Event[point.y][point.x-i] == SHIP && point.x-i>=0)
IsKilled = SHIPWOUND;
for(i=1; i<4; i++)
if(m_Event[point.y+i][point.x] != SHIPWOUND || point.y+i==10) break;
if(m_Event[point.y+i][point.x] == SHIP && point.y+i<10)
IsKilled = SHIPWOUND;
for(i=1; i<4; i++)
if(m_Event[point.y-i][point.x] != SHIPWOUND || point.y-i==-1) break;
if(m_Event[point.y-i][point.x] == SHIP && point.y-i>=0)
IsKilled = SHIPWOUND;
if(IsKilled == SHIPKILLED) {
pos = point;
sumx = razx = sumy = razy = 0;
for(i=1; i<4; i++) {
if(m_Event[pos.y][pos.x+i] == SHIPWOUND && pos.x+i<10) {
m_Event[pos.y][pos.x+i] = SHIPKILLED;
sumx++;
}
if(m_Event[pos.y][pos.x-i] == SHIPWOUND && pos.x-i>=0) {
m_Event[pos.y][pos.x-i] = SHIPKILLED;
razx++;
}
if(m_Event[pos.y+i][pos.x] == SHIPWOUND && pos.y+i<10) {
m_Event[pos.y+i][pos.x] = SHIPKILLED;
sumy++;
}
if(m_Event[pos.y-i][pos.x] == SHIPWOUND && pos.y-i>=0) {
m_Event[pos.y-i][pos.x] = SHIPKILLED;
razy++;
}
}
m_Event[pos.y][pos.x] = SHIPKILLED;
if(pos.x-razx-1>=0) x = pos.x-razx-1;
else x = pos.x-razx;
if(pos.x+sumx+1<10) cx = pos.x+sumx+1;
else cx = pos.x+sumx;
if(pos.y-razy-1>=0) y = pos.y-razy-1;
else y = pos.y-razy;
if(pos.y+sumy+1<10) cy = pos.y+sumy+1;
else cy = pos.y+sumy;
for(i=y; i<=cy; i++)
for(j=x; j<=cx; j++)
if(m_Event[i][j] != SHIPKILLED)
m_Event[i][j] = NEARKILLEDSHIP;
KilledShips++;
InvalidateRect(CRect(x*20, y*20, (cx+1)*20, (cy+1)*20));
::SendMessage(m_Parent->m_hWnd, WM_STATIC_TO_DLG, WM_KILL, 0);
if(KilledShips == 10) {
::SendMessage(m_Parent->m_hWnd, WM_STATIC_TO_DLG, WM_WIN_YOU, 0);
m_Operation = NONE;
}
}
else {
InvalidateRect(CRect(point.x*20, point.y*20, (point.x+1)*20, (point.y+1)*20));
}
}
else {
m_Event[point.y][point.x] = CANNOT;
m_Operation = NONE;
InvalidateRect(CRect(point.x*20, point.y*20, (point.x+1)*20, (point.y+1)*20));
::SendMessage(m_Parent->m_hWnd, WM_STATIC_TO_DLG, WM_COMP_MOVE, 0);
}
}

Анализ функции

Что делает эта функция при щелчке на чужом поле?

Она должна определить состояние переменной IsKilled.
Это значение может быть либо SHIPKILLED, либо SHIPWOUND.

Сначала переданное значение координат мыши переводится в
координаты, способные работать с матрицей

point.x = point.x/20;
point.y = point.y/20;

Затем три основных проверки:
1. по этой точке уже стреляли (выход из функции)
2. это корабль (следующий ход снова наш)
3. мимо (поставить крестик - выход из функции)

////////////////////////////
////// Первая проверка. По этой точке уже стреляли, выход:
///////////

if(m_Event[point.y][point.x] == SHIPKILLED ||

m_Event[point.y][point.x] == SHIPWOUND ||
m_Event[point.y][point.x] == CANNOT ||
m_Event[point.y][point.x] == NEARKILLEDSHIP) return;

////////////////
//////// Вторая проверка. Попали в корабль
////////////

m_Steps++;//Инкремент числа ходов

if(m_Event[point.y][point.x] == SHIP) {

m_GoodSteps++;//инкремент результативный ход
IsKilled = SHIPKILLED;//корабль убит

Присвоили переменной IsKilled значение SHIPKILLED (убит).
Например для однопалубного корабля единственное попадание
делает его убитым. Поэтому такое присвоение оправдано.

Далее проверим действительно ли корабль убит или только ранен?
Если окажется, что корабль ранен, присвоим переменной IsKilled
новое значение - РАНЕН

Присвоим точке, в которую попали значение - РАНЕН
m_Event[point.y][point.x] = SHIPWOUND;
...

Если убедились и определили, что корабль убит.
(Значение переменной IsKilled по прежнему SHIPKILLED)
if(IsKilled == SHIPKILLED) {

Перепишем все значения клеток убитого корабля и вокруг убитого корабля
...
}
else//Корабль был ранен.(IsKilled == SHIPWOUND)Перерисуем
{
InvalidateRect(CRect(point.x*20, point.y*20, (point.x+1)*20, (point.y+1)*20));
}

///////////////////
///////// Третья проверка. Если клетка по которой только что был сделан выстрел пуста.
///////////////////
Естественно, что если было попадание, то эта ветка не выполняется.

То есть это не проверка, а единственный возможный оставшийся случай.
То есть в ней ничего нет и по ней не стреляли. Ее значение до выстрела - EMPTY.
После выстрела надо изменить ее значение на CANNOT и выйти из функции.
Кроме того, присвоим переменной m_Operation значение NONE, и пошлем сообщение
WM_COMP_MOVE (Ход компа)

Выход из функции происходит естественный образом без return.
else {

m_Event[point.y][point.x] = CANNOT;
m_Operation = NONE;
InvalidateRect(CRect(point.x*20, point.y*20, (point.x+1)*20, (point.y+1)*20));
::SendMessage(m_Parent->m_hWnd, WM_STATIC_TO_DLG, WM_COMP_MOVE, 0);
}

//////////////////////////////

Это общий взгляд на дело. Теперь посмотрим более углубленно на случай,
когда попали в корабль.

////////////////
//////// Вторая проверка. Попали в корабль
////////////

if(m_Event[point.y][point.x] == SHIP) {

m_GoodSteps++;//инкремент результативный ход
IsKilled = SHIPKILLED;//корабль убит

Присвоили переменной IsKilled значение SHIPKILLED (убит).
Например для однопалубного корабля единственное попадание
делает его убитым. Поэтому такое присвоение оправдано.

Присвоим точке, в которую попали значение - РАНЕН
m_Event[point.y][point.x] = SHIPWOUND;

Далее проверим действительно ли корабль убит или только ранен?
Если окажется, что корабль ранен, присвоим переменной IsKilled
новое значение - РАНЕН

for(i=1; i<4; i++)

if(m_Event[point.y][point.x+i] != SHIPWOUND || point.x+i==10) break;
if(m_Event[point.y][point.x+i] == SHIP && point.x+i<10)
IsKilled = SHIPWOUND;
for(i=1; i<4; i++)
if(m_Event[point.y][point.x-i] != SHIPWOUND || point.x-i==-1) break;
if(m_Event[point.y][point.x-i] == SHIP && point.x-i>=0)
IsKilled = SHIPWOUND;
for(i=1; i<4; i++)
if(m_Event[point.y+i][point.x] != SHIPWOUND || point.y+i==10) break;
if(m_Event[point.y+i][point.x] == SHIP && point.y+i<10)
IsKilled = SHIPWOUND;
for(i=1; i<4; i++)
if(m_Event[point.y-i][point.x] != SHIPWOUND || point.y-i==-1) break;
if(m_Event[point.y-i][point.x] == SHIP && point.y-i>=0)
IsKilled = SHIPWOUND;

Суть этого хорошего кода в том, что он определяет ранен корабль или убит.
Если убедились и определили, что корабль убит, то будет выполняться ветка
if(IsKilled == SHIPKILLED), если корабль ранен то ветка else.
(Значение переменной IsKilled по прежнему SHIPKILLED)

if(IsKilled == SHIPKILLED)
{

Перепишем все значения клеток убитого корабля и вокруг убитого корабля
...
}
else//Корабль был ранен.(IsKilled == SHIPWOUND)Перерисуем {
InvalidateRect(CRect(point.x*20, point.y*20, (point.x+1)*20, (point.y+1)*20));
}

Ветка else очень проста - перерисуем.

А точке, в которую попали теперь присвоено значение - РАНЕН
( m_Event[point.y][point.x] = SHIPWOUND;)

//////////////////

Теперь посмотрим более углубленно случай, когда корабль был убит.
Помним, что в этом случае переменной IsKilled присвоено значение SHIPKILLED,
а точке по которой стрельнули присвоено значение (РАНЕН) SHIPWOUND.

Но наша задача присвоить всем точкам убитого корабля значение SHIPKILLED.

if(IsKilled == SHIPKILLED) {

pos = point;
sumx = razx = sumy = razy = 0;
for(i=1; i<4; i++) {
if(m_Event[pos.y][pos.x+i] == SHIPWOUND && pos.x+i<10) {
m_Event[pos.y][pos.x+i] = SHIPKILLED;
sumx++;
}
if(m_Event[pos.y][pos.x-i] == SHIPWOUND && pos.x-i>=0) {
m_Event[pos.y][pos.x-i] = SHIPKILLED;
razx++;
}
if(m_Event[pos.y+i][pos.x] == SHIPWOUND && pos.y+i<10) {
m_Event[pos.y+i][pos.x] = SHIPKILLED;
sumy++;
}
if(m_Event[pos.y-i][pos.x] == SHIPWOUND && pos.y-i>=0) {
m_Event[pos.y-i][pos.x] = SHIPKILLED;
razy++;
}
}

В этом коде мы присвоили всем точкам убитого корабля
значение SHIPKILLED.(Кроме точки по которой стреляли)

Кроме того мы инкременировали некоторое количество раз локальные
переменные sumx,razx,sumy,razy.

Теперь точке, по которой стреляли так же присвоим значение (УБИТ) SHIPKILLED.
m_Event[pos.y][pos.x] = SHIPKILLED;

Теперь мы имеем все возможности для того, чтобы присвоить всем смежным
с убитым кораблем клеткам значение NEARKILLEDSHIP. Это и делает нижеприведенный код:

if(pos.x-razx-1>=0) x = pos.x-razx-1;
else x = pos.x-razx;
if(pos.x+sumx+1<10) cx = pos.x+sumx+1;
else cx = pos.x+sumx;
if(pos.y-razy-1>=0) y = pos.y-razy-1;
else y = pos.y-razy;
if(pos.y+sumy+1<10) cy = pos.y+sumy+1;
else cy = pos.y+sumy;
for(i=y; i<=cy; i++)

for(j=x; j<=cx; j++)
if(m_Event[i][j] != SHIPKILLED)
m_Event[i][j] = NEARKILLEDSHIP;

Увеличим счет убитым кораблям.
KilledShips++;

Перерисуем прямоугольник, в котором внесли изменения:
InvalidateRect(CRect(x*20, y*20, (cx+1)*20, (cy+1)*20));

Пошлем сообщение WM_KILL(Убит):
::SendMessage(m_Parent->m_hWnd, WM_STATIC_TO_DLG, WM_KILL, 0);

Если уже убито 10 кораблей, то пошлем сообщение WM_WIN_YOU (Вы победитель)
и присвоим переменной m_Operation значение NONE:

if(KilledShips == 10) {

::SendMessage(m_Parent->m_hWnd, WM_STATIC_TO_DLG, WM_WIN_YOU, 0);
m_Operation = NONE;
}

//-------------------------------

Конец. Рассмотрены все варианты.
Попал (ранил или убил), промазал, по этой точке уже стреляли.
Соответственно с этим изменили значения некоторых клеток.
Эти значения будут учтены при перерисовке поля в функции OnPaint().

Так же были посланы соответствующее сообщения:

::SendMessage(m_Parent->m_hWnd, WM_STATIC_TO_DLG, WM_KILL, 0);
::SendMessage(m_Parent->m_hWnd, WM_STATIC_TO_DLG, WM_WIN_YOU, 0);
::SendMessage(m_Parent->m_hWnd, WM_STATIC_TO_DLG, WM_COMP_MOVE, 0);

Это сообщения: Корабль убит, Вы победитель и Ход компа.

Сохраним эту программу.

Естественно пришлось сделать некоторые изменения в классе Field:

Добавим константы:
#define NONE 10001
#define FIRE 10002
//#define WAIT 10003

Что можно сказать об этих констанах?
Обе они предназначены для переменной m_Operation типа int данного класса
Эта переменная определяет тип операции:
PLACEMENT - ставить корабли
FIRE - огонь,
NONE - операции закончились

Константа FIRE присваивается переменной m_Operation во многих функциях.
(именно эти функции надо нам внимательно посмотреть и вероятно добавить)
В данной функции при помощи ее определяется значение переменной m_Operation.
Если значение равно FIRE то:

if(m_Mode == FOREIGN_FIELD && m_Operation == FIRE) {

...

Константа NONE присваивается переменной m_Operation когда уже убито все 10
кораблей, и пора подводить результаты. Посылется сообщение WM_WIN_YOU.
"Вы победитель"

if(KilledShips == 10) {

::SendMessage(m_Parent->m_hWnd, WM_STATIC_TO_DLG, WM_WIN_YOU, 0);
m_Operation = NONE;
}

Так же константа NONE присваивается переменной m_Operation когда выстрел
был произведен мимо, и посылается сообщение WM_COMP_MOVE "Ход компа".

else {

m_Event[point.y][point.x] = CANNOT;
m_Operation = NONE;
InvalidateRect(CRect(point.x*20, point.y*20, (point.x+1)*20, (point.y+1)*20));
::SendMessage(m_Parent->m_hWnd, WM_STATIC_TO_DLG, WM_COMP_MOVE, 0);
}

Еще внимательно посмотрим в какмх функциях переменной m_Operation присваивается
значение FIRE.
//---------------------------------

Добавим приватные переменные класса:
int m_GoodSteps, m_Steps;

Открытую переменную класса:
int KilledShips;

Добавим в функцию переменные:
int i, j, IsKilled, sumx, razx, sumy, razy, x, y, cx, cy;

В заголовок StdAfx.h :
//#define WM_WIN_COMP 13334
#define WM_COMP_MOVE 13335
#define WM_WIN_YOU 13336
#define WM_KILL 13337

//#define WM_SET_MOVE 13347

//--------------------
Что можно сказать о введенных переменных?


Назад | Начало урока | Вверх | Вперед
Содержание

Hosted by uCoz