Глава 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] == CANNOT ||
m_Event[point.y][point.x] == NEARKILLEDSHIP) return;
////////////////
m_Steps++;//Инкремент числа ходов
if(m_Event[point.y][point.x] == SHIP)
{
Присвоили переменной IsKilled значение SHIPKILLED (убит).
Далее проверим действительно ли корабль убит или только ранен?
Присвоим точке, в которую попали значение - РАНЕН
Если убедились и определили, что корабль убит.
///////////////////
То есть это не проверка, а единственный возможный оставшийся случай.
Выход из функции происходит естественный образом без return.
//////////////////////////////
Это общий взгляд на дело. Теперь посмотрим более углубленно на случай,
////////////////
if(m_Event[point.y][point.x] == SHIP)
{
Присвоили переменной IsKilled значение SHIPKILLED (убит).
Присвоим точке, в которую попали значение - РАНЕН
Далее проверим действительно ли корабль убит или только ранен?
for(i=1; i<4; i++)
Суть этого хорошего кода в том, что он определяет ранен корабль или убит.
if(IsKilled == SHIPKILLED)
Ветка else очень проста - перерисуем.
А точке, в которую попали теперь присвоено значение - РАНЕН
//////////////////
Теперь посмотрим более углубленно случай, когда корабль был убит.
Но наша задача присвоить всем точкам убитого корабля значение SHIPKILLED.
if(IsKilled == SHIPKILLED)
{ В этом коде мы присвоили всем точкам убитого корабля
Кроме того мы инкременировали некоторое количество раз локальные
Теперь точке, по которой стреляли так же присвоим значение (УБИТ) SHIPKILLED.
Теперь мы имеем все возможности для того, чтобы присвоить всем смежным
if(pos.x-razx-1>=0) x = pos.x-razx-1;
Перерисуем прямоугольник, в котором внесли изменения:
if(KilledShips == 10)
{ //-------------------------------
Конец. Рассмотрены все варианты.
Так же были посланы соответствующее сообщения:
::SendMessage(m_Parent->m_hWnd, WM_STATIC_TO_DLG, WM_KILL, 0);
Это сообщения: Корабль убит, Вы победитель и Ход компа.
Сохраним эту программу.
Естественно пришлось сделать некоторые изменения в классе Field:
Добавим константы:
Что можно сказать об этих констанах?
Константа FIRE присваивается переменной m_Operation во многих функциях.
if(m_Mode == FOREIGN_FIELD && m_Operation == FIRE)
{
Константа NONE присваивается переменной m_Operation когда уже убито все 10
if(KilledShips == 10)
{ Так же константа NONE присваивается переменной m_Operation когда выстрел
else
{ Еще внимательно посмотрим в какмх функциях переменной m_Operation присваивается
Добавим приватные переменные класса:
Открытую переменную класса:
Добавим в функцию переменные:
В заголовок StdAfx.h :
//#define WM_SET_MOVE 13347
//--------------------
Назад |
Начало урока |
Вверх |
Вперед
//////// Вторая проверка. Попали в корабль
////////////
IsKilled = SHIPKILLED;//корабль убит
Например для однопалубного корабля единственное попадание
делает его убитым. Поэтому такое присвоение оправдано.
Если окажется, что корабль ранен, присвоим переменной IsKilled
новое значение - РАНЕН
m_Event[point.y][point.x] = SHIPWOUND;
...
(Значение переменной IsKilled по прежнему SHIPKILLED)
if(IsKilled == SHIPKILLED)
{
...
else//Корабль был ранен.(IsKilled == SHIPWOUND)Перерисуем
{
///////// Третья проверка. Если клетка по которой только что был сделан выстрел пуста.
///////////////////
Естественно, что если было попадание, то эта ветка не выполняется.
То есть в ней ничего нет и по ней не стреляли. Ее значение до выстрела - EMPTY.
После выстрела надо изменить ее значение на CANNOT и выйти из функции.
Кроме того, присвоим переменной m_Operation значение NONE, и пошлем сообщение
WM_COMP_MOVE (Ход компа)
else
{
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;//корабль убит
Например для однопалубного корабля единственное попадание
делает его убитым. Поэтому такое присвоение оправдано.
m_Event[point.y][point.x] = SHIPWOUND;
Если окажется, что корабль ранен, присвоим переменной IsKilled
новое значение - РАНЕН
Если убедились и определили, что корабль убит, то будет выполняться ветка
if(IsKilled == SHIPKILLED), если корабль ранен то ветка else.
(Значение переменной IsKilled по прежнему SHIPKILLED)
{
...
else//Корабль был ранен.(IsKilled == SHIPWOUND)Перерисуем
{
( m_Event[point.y][point.x] = SHIPWOUND;)
Помним, что в этом случае переменной IsKilled присвоено значение SHIPKILLED,
а точке по которой стрельнули присвоено значение (РАНЕН) SHIPWOUND.
sumx = razx = sumy = razy = 0;
for(i=1; i<4; i++)
{
sumx++;
if(m_Event[pos.y][pos.x-i] == SHIPWOUND && pos.x-i>=0)
{
razx++;
if(m_Event[pos.y+i][pos.x] == SHIPWOUND && pos.y+i<10)
{
sumy++;
if(m_Event[pos.y-i][pos.x] == SHIPWOUND && pos.y-i>=0)
{
razy++;
значение SHIPKILLED.(Кроме точки по которой стреляли)
переменные sumx,razx,sumy,razy.
m_Event[pos.y][pos.x] = SHIPKILLED;
с убитым кораблем клеткам значение NEARKILLEDSHIP. Это и делает нижеприведенный код:
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++)
Увеличим счет убитым кораблям.
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:
m_Operation = NONE;
Попал (ранил или убил), промазал, по этой точке уже стреляли.
Соответственно с этим изменили значения некоторых клеток.
Эти значения будут учтены при перерисовке поля в функции OnPaint().
::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);
#define NONE 10001
#define FIRE 10002
//#define WAIT 10003
Обе они предназначены для переменной m_Operation типа int данного класса
Эта переменная определяет тип операции:
PLACEMENT - ставить корабли
FIRE - огонь,
NONE - операции закончились
(именно эти функции надо нам внимательно посмотреть и вероятно добавить)
В данной функции при помощи ее определяется значение переменной m_Operation.
Если значение равно FIRE то:
кораблей, и пора подводить результаты. Посылется сообщение WM_WIN_YOU.
"Вы победитель"
m_Operation = NONE;
был произведен мимо, и посылается сообщение WM_COMP_MOVE "Ход компа".
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);
значение FIRE.
//---------------------------------
int m_GoodSteps, m_Steps;
int KilledShips;
int i, j, IsKilled, sumx, razx, sumy, razy, x, y, cx, cy;
//#define WM_WIN_COMP 13334
#define WM_COMP_MOVE 13335
#define WM_WIN_YOU 13336
#define WM_KILL 13337
Что можно сказать о введенных переменных?
Содержание