Я помню, была неделя-две тематики морского боя, я к сожалению не успел опубликовать свою версию этой замечательной игры на С++.
Моя версия Морского боя под катом!
Начнем
Для начала хочу сказать, что эта статья не предназначена для новичков, я выкладыю ее просто для того, чтобы поделится своими решениями и трудностями при написании Морского боя.
Игру я делал в консоли, так что о графике не может быть и речи. А так же она написана полностью на процедурном С++, по причине того, что я сам все еще изучаю базу языка. На тот момент я не был знаком с ООП / Структурами / Битовыми полями.
Проблема №1:
Самой первой проблемой было то, что я совершенно не представлял, как адекватно выводить два поля в консоли. Написание Морского боя (Дальше М.б.) происходило на Mac OS, поэтом о библиотеке conio.h и функциях для работы с графикой консоли не могло быть и речи.
Но это и проблемой назвать нельзя, наверника скажет большинство из вас. И, наверное, я с вами соглашусь. Код вывода получился очень простым:
void paintfield(int choose)
{
/* Если выбрана установка кораблей, выводим поле игрока и количество его кораблей */
if (choose == 1)
{
std::cout << "n 0 1 2 3 4 5 6 7 8 9n";
for (int count = 0; count < 10; count++) // Выводим 10 столбцов
{
std::cout << count << " "; // Координаты сбоку поля
for (int count2 = 0; count2 < 10; count2++)
{
std::cout << Playerfield[count][count2] << " "; // Выводим по 10 символов в ряду
}
std::cout << std::endl; // Переходим на следующую строку
}
std::cout << std::endl;
std::cout << "Ваши корабли: " << ships[0] << ", " << ships[1] << ", " << ships[2] << ", " << ships[3] << "; n"; // Оставшиеся у игрока корабли
}
else if(choose == 2) // Если выбрана игра, рисуем два поля
{
std::cout << "n 0 1 2 3 4 5 6 7 8 9t 0 1 2 3 4 5 6 7 8 9n";
for (int count = 0; count < 10; count++)
{
std::cout << count << " ";
for (int count2 = 0; count2 < 10; count2++)
{
std::cout << Playerfield[count][count2] << " "; // Поле игрока
}
std::cout << " "; // Растояние между полями 4 пробела
std::cout << count << " "; // Координаты сбоку поля
for (int count2 = 0; count2 < 10; count2++)
{
std::cout << Enemyfield[count][count2] << " "; // Поле компьютера
}
std::cout << std::endl;
}
std::cout << std::endl;
}
Поля заполняются функцией fillarr при вызове в главном файле, в main функции. Код заполнения:
void fillarr()
{
for (int count = 0; count < 10; count++)
{
for (int count2 = 0; count2 < 10; count2++)
{
Playerfield[count][count2] = '.';
Enemyfield[count][count2] = '.';
}
}
}
Проблема №2:
Проверка на границы кораблей. Это была достаточно сложная часть в моем М.б. для меня.
Решил, но работает плохо. Сейчас если и буду переделывать игру, то такой способ использовать не буду ни в коем случае:
bool playercheckplase(int y, int x, int side, int size)
{
for (int i = 0; i <= size+1; i++)
{
switch (side)
{
case Right:
if (Playerfield[y][x-1] == 'O' || Playerfield[y-1][x-1] == 'O'
|| Playerfield[y+1][x-1] == 'O' || Playerfield[y-1][x+i] == 'O'
|| Playerfield[y+1][x+i] == 'O' || Playerfield[y][x+i] == 'O')
{
return 0;
}
break;
case Left:
if (Playerfield[y][x+1] == 'O' || Playerfield[y-1][x+1] == 'O'
|| Playerfield[y+1][x+1] == 'O' || Playerfield[y-1][x-i] == 'O'
|| Playerfield[y+1][x-i] == 'O' || Playerfield[y][x-i] == 'O')
{
return 0;
}
break;
case Up:
if (Playerfield[y+1][x] == 'O' || Playerfield[y+1][x+1] == 'O'
|| Playerfield[y+1][x-1] == 'O' || Playerfield[y-i][x-1] == 'O'
|| Playerfield[y-i][x+1] == 'O' || Playerfield[y-i][x] == 'O')
{
return 0;
}
break;
case Down:
if (Playerfield[y-1][x] == 'O' || Playerfield[y-1][x+1] == 'O'
|| Playerfield[y-1][x-1] == 'O' || Playerfield[y+i][x-1] == 'O'
|| Playerfield[y+i][x+1] == 'O' || Playerfield[y+i][x] == 'O')
{
return 0;
}
break;
default:
if (Playerfield[y+1][x] == 'O' || Playerfield[y+1][x+1] == 'O'
|| Playerfield[y+1][x-1] == 'O' || Playerfield[y-1][x-1] == 'O'
|| Playerfield[y-1][x+1] == 'O' || Playerfield[y-1][x] == 'O'
|| Playerfield[y][x-1] == 'O' || Playerfield[y][x+1] == 'O')
{
return 0;
}
break;
}
}
return 1;
}
Я думаю, самое непонятное здесь, почему я сравниваю все с символом «О».
Из-за того, что установка кораблей происходит у меня таким образом:
void playership()
{
int x, y, side, size;
side = NONE;
std::cout << "Enter X coord: ";
std::cin >> x;
std::cout << "Enter Y coord: ";
std::cin >> y;
std::cout << "Enter size: ";
std::cin >> size;
if (size > 1)
{
std::cout << "Enter side (0 - right, 1 - up, 2 - down, 3 - left): ";
std::cin >> side;
std::cout << "n";
}
installship(y, x, side, size);
paintfield(1);
}
Это получение координат от пользователя, непосредственно установка:
void installship(int y, int x, int side, int size)
{
if (x < 10 && y < 10 && size < 5 && size > 0 && playercheckplase(y, x, side, size) != 0 && checkborder(y, x, side, size))
{
switch(side)
{
case Right:
if(ships[size-1])
{
for(int i = 0; i < size; i++)
{
Playerfield[y][x+i] = 'O';
}
}
break;
case Up:
if(ships[size-1])
{
for(int i = 0; i < size; i++)
{
Playerfield[y-i][x] = 'O';
}
}
break;
case Down:
if(ships[size-1])
{
for(int i = 0; i < size; i++)
{
Playerfield[y+i][x] = 'O';
}
}
break;
case Left:
if(ships[size-1])
{
for(int i = 0; i < size; i++)
{
Playerfield[y][x-i] = 'O';
}
}
break;
default:
if(ships[size-1])
{
for(int i = 0; i < size; i++)
{
Playerfield[y][x] = 'O';
}
}
break;
}
ships[size-1]--;
}
}
Проблема №3:
Границы поля. Больше всего мне понравилось писать именно прокерку границ поля. Я долго делал разнообразные проверки границ, но большинство из них либо не работали, либо работали с ошибками. Остановился на этом варианте:
bool checkborder(int y, int x, int side, int size)
{
size--;
switch (side)
{
case Right:
if (x+size > 9) // Если координата x (горизонталь) + размер больше самого поля, возвращаем 0. Актуально для случая установки вправо.
{
return 0;
}
break;
case Left:
if (x-size < 0) // Если координата x (горизонталь) + размер больше длинны поля, возвращаем 0. Актуально для случая установки влево.
{
return 0;
}
break;
case Up:
if (y-size < 0) // Если координата y (вертикаль) + размер больше высоты поля возвращаем 0. Актуально для случая установки вверх.
{
return 0;
}
break;
case Down:
if (y+size > 9) // Если координата y (вертикаль) + размер больше высоты поля, возвращаем 0. Актуально для случая установки вниз.
{
return 0;
}
break;
default:
return 1; // Если все нормально, возвращаем 1.
break;
}
}
На этом главные проблемы и кончаются. Атака осуществляется самым элементарным образом для игрока:
void playerattack()
{
int x = 0;
int y = 0;
std::cout << "Enter X coord: ";
std::cin >> x;
std::cout << "Enter Y coord: ";
std::cin >> y;
if (x < 10 && y < 10)
{
Enemyfield[y][x] = 'X';
}
}
И для компьютера:
void enemyattack()
{
int x, y, z;
z = 1;
x = 0;
y = 0;
x = rand()%9;
y = rand()%9;
Playerfield[y][x] = 'X';
}
А вот здесь и проявляется недописанность программы, в функцию атаки компьютера требуется рекурсия.
На этом все, около 30% игры не доделано, а именно:
- Завершение игры при чьей либо победе
- Более сильный противник
Так что, прошу вашей критики. Я знаю, что код не самый лучший, но и я только учусь.
Автор: RussDragon