морской бой на с++
Недавно написал морской бой, хоть код и рабочий ,но мне кажется ,что он плохой и в нём куча ошибок. Так как я только недавно стал изучать с++ , то сам эти ошибки не увижу. Если вам не трудно , то можете дать несколько советов по этому коду. Заранее огромное спасибо.
#include <iostream>
#include <Windows.h>
#include <cstdlib>
#include <ctime>
#include <string>
#include <conio.h>
using namespace std;
void field(string(&a2)[10][10], string(&b2)[10][10]) {
cout << endl;
cout << " " << "Морской бой от valecop";
cout << endl << endl << endl;
//создание и заполнение таблиц
for (int w = 0; w < 1; w++) {
cout << " ";
for (int y = 0; y < size(a2); y++) {
cout << y << " ";
//отвечает за столбцы первой таблицы
}
cout << " ";
for (int y = 0; y < size(a2); y++) {
cout << y << " ";
//отвечает за столбцы второй таблицы
}
cout << endl;
for (int i = 0; i < size(a2); i++) {
cout << " ";
cout << i;
for (int j = 0; j < size(a2); j++) {
cout << "|" << a2[i][j];
}
cout << "|" << " ";
cout << i;
for (int f = 0; f < size(b2); f++) {
cout << "|" << b2[i][f];
}
cout << "|" << endl;
}
}
}
void help(short& l, short& s, string(&a2)[10][10], string(&b2)[10][10]) {
cout << endl;
cout << "Выберите местоположение ваших кораблей" << endl << "Корабли выставляются по возрастанию , начиная с однопалубного и заканчивая трёхпалубным" << endl;
cout << "Введите координаты и поставьте знаки " << endl;
cout << "Знак" << '"' << "^" << '"' << " " << "чтобы корабль стоял носом наверх\n";
cout << "Знак" << '"' << "<" << '"' << " " << "чтобы корабль стоял носом вправо\n";
cout << "Знак" << '"' << ">" << '"' << " " << "чтобы корабль стоял носом влево \n";
cout << "Знак" << '"' << "!" << '"' << " " << "чтобы корабль стоял носом вниз \n";
cout << "введите столбик:"; cin >> l;
cout << "введите строку: "; cin >> s;
int tu = 10;
while (tu != 0) {
tu = 0;
if (s > 9 || l > 9) {
tu = 1;
cout << "\t\t\t\t\t\tНеверные координаты";
Sleep(2000);
system("cls");
field(a2, b2);
help(l, s, a2, b2);
}
else if (s < 0 || l < 0) {
tu = 1;
cout << "\t\t\t\t\t\tНеверные координаты";
Sleep(2000);
system("cls");
field(a2, b2);
help(l, s, a2, b2);
}
}
}
int game(string a[10][10], string b[10][10]) {
char t;
short s, l;
string a2[10][10];
string b2[10][10];
// заполнение масивов
for (char i = 0; i < size(a2); i++) {
for (char j = 0; j < size(a2); j++) {
a2[i][j] = "?";
b2[i][j] = "?";
a[i][j] = "0";
b[i][j] = "0";
}
}
field(a2, b2);
help(l, s, a2, b2);
//расстановка корабля
a[s][l] = "1";
a2[s][l] = '^';
system("cls");
field(a2, b2);
help(l, s, a2, b2);
cout << "введите направление:"; cin >> t;
//расстановка корабля
switch (t){
case '^' :
if (s - 1 >= 0) {
a[s][l] = "1";
a2[s][l] = "O";
a[s - 1][l] = "1";
a2[s - 1][l] = "^";
}
break;
case '>':
if (l + 1 <= 9) {
a[s][l] = "1";
a2[s][l] = "O";
a[s][l + 1] = "1";
a2[s][l + 1] = ">";
}
break;
case '<':
if (l - 1 >= 0) {
a[s][l] = "1";
a2[s][l] = "O";
a[s][l - 1] = "1";
a2[s][l - 1] = "<";
}
break;
case '!':
if (s + 1 <= 9) {
a[s][l] = "1";
a2[s][l] = "O";
a[s + 1][l] = "1";
a2[s + 1][l] = "o";
}
}
system("cls");
field(a2, b2);
help(l, s, a2, b2);
cout << "введите направление:"; cin >> t;
switch (t){
case '^':
if (s - 2 >= 0) {
a[s][l] = "1";
a2[s][l] = "O";
a[s - 1][l] = "1";
a2[s - 1][l] = "O";
a[s - 2][l] = "1";
a2[s - 2][l] = "^";
}
break;
case '>':
if (l + 2 <= 9) {
a[s][l] = "1";
a2[s][l] = "O";
a[s][l + 1] = "1";
a2[s][l + 1] = "O";
a[s][l + 2] = "1";
a2[s][l + 2] = ">";
}
break;
case '<':
if (l - 2 >= 0) {
a[s][l] = "1";
a2[s][l] = "O";
a[s][l - 1] = "1";
a2[s][l - 1] = "O";
a[s][l - 2] = "1";
a2[s][l - 2] = "<";
}
break;
case '!':
if (s + 2 <= 9) {
a[s][l] = "1";
a2[s][l] = "O";
a[s + 1][l] = "1";
a2[s + 1][l] = "O";
a[s + 2][l] = "1";
a2[s + 2][l] = "o";
}
}
system("cls");
field(a2, b2);
//бот расставляет корабли
srand(time(NULL));
int TAB_BOT, LINE_BOT;
TAB_BOT = rand() % 10;
LINE_BOT = rand() % 10;
b[TAB_BOT][LINE_BOT] = "1";
Sleep(200);
int u = 0;
l = 0;
while (u != 1) {
TAB_BOT = rand() % 10;
LINE_BOT = rand() % 10;
if (TAB_BOT - 1 <= 9 && LINE_BOT - 1 <= 9) {
if (b[TAB_BOT][LINE_BOT] != "1" && b[TAB_BOT - 1][LINE_BOT] != "1") {
b[TAB_BOT][LINE_BOT] = "1";
b[TAB_BOT - 1][LINE_BOT] = "1";
u = 1;
}
}
}
Sleep(200);
l = 0;
while (l != 1) {
TAB_BOT = rand() % 10;
LINE_BOT = rand() % 10;
if (TAB_BOT + 1 <= 9 && TAB_BOT + 2 <= 9) {
if (b[TAB_BOT][LINE_BOT] != "1" && b[TAB_BOT + 1][LINE_BOT] != "1") {
if (b[TAB_BOT + 2][LINE_BOT] != "1")
b[TAB_BOT][LINE_BOT] = "1";
b[TAB_BOT + 1][LINE_BOT] = "1";
b[TAB_BOT][LINE_BOT] = "1";
b[TAB_BOT + 2][LINE_BOT] = "1";
l++;
}
}
}
//бот закончил
short bot_heat = 0, person_heat = 0;
system("cls");
while (bot_heat < 6 && person_heat < 6) {
field(a2, b2);
cout << endl;
cout << "Ваш ход , вбейте координаты\n";
cout << "Введите номер столбца:"; cin >> l;
cout << "Введите номер строки: "; cin >> s;
if (b[s][l] == "1") {
person_heat++;
b2[s][l] = "*";
cout << endl;
cout << "вы попали";
}
else {
b2[s][l] = "x";
cout << "вы промахнулись";
}
system("cls");
field(a2, b2);
cout << endl;
cout << "ход противника";
Sleep(300);
l = 1;
while (l != 0) {
TAB_BOT = rand() % 10;
LINE_BOT = rand() % 10;
if (a2[TAB_BOT][LINE_BOT] != "x" && a2[TAB_BOT][LINE_BOT] != "*") {
if (a[TAB_BOT][LINE_BOT] == "1") {
bot_heat++;
a2[TAB_BOT][LINE_BOT] = "*";
l = 0;
}
else {
a2[TAB_BOT][LINE_BOT] = "x";
l = 0;
}
}
}
system("cls");
}
if (bot_heat > person_heat) {
cout << "\t\t\t\t\t\tБот победил!";
}
else {
cout << "\t\t\t\t\t\tВы победили!";
}
return 0;
}
int main() {
setlocale(LC_ALL, "rus");
string a[10][10];
string b[10][10];
game(a, b);
}
Ответы (2 шт):
Названия переменных плохие, надо переименовать
Для массивов лучше использовать std::array
Лучше не использовать using namespace std;
Лучше не использовать неявные преобразования типов (в индексах, например)
Вместо endl лучше использовать \n
Вместо односимвольных строк лучше использовать символы
Вместо множества явных пробелов для форматирования лучше использовать манипуляторы
Стоит добавить обработку ошибок, а то непонятно, что вводить игроку
В коде кроме всяких мелочей (типа endl<->\n) есть более глобальные проблемы. Начнем с мелкого, постепенно пофиксим немного.
Первое, что бросается в глаза, это вот это
string a[10][10];
после того, как присмотришься, стает понятно, что это игровое поле и a и b - это свое и противника, но абсолютно не очевидно, где чье. Сделаем первый шаг - заведем отдельный тип для этого и добавим туда пару функций.
class Field
{
std::string m_field[10][10];
public:
const std::string& get(int x, int y) { return m_field[x][y]; }
void set(int x, int y, const std::string& v) { m_field[x][y] = x; }
};
этот класс просто заворачивает массив в себя. Многие на этом моменте скажут, мол, нужно уже на char поменять или использовать вектор векторов. Не нужно спешить, такое делается мелкими шагами.
Теперь в коде заиспользуем этот класс и посмотрим как оно будет.
И сразу видим в функции game вот такое
for (char i = 0; i < size(a2); i++) {
for (char j = 0; j < size(a2); j++) {
a2[i][j] = "?";
b2[i][j] = "?";
a[i][j] = "0";
b[i][j] = "0";
}
}
хм, так это же 4 массива заполняются символами. Более того, просто повезло, что размеры совпали удачно. Но это кандидат на отдельную функцию в Field.
А сделаем вот так
a2.fill("?");
b2.fill("?");
a.fill("0");
b.fill("0");
и соответственно
void fill(const std::string& el) {
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
m_field[i][j] = el;
}
}
}
да, ещё не очевидно, что значат знаки вопроса, но уже понятна суть. Двигаемся дальше по функции game.
Там вызов
field(a2, b2);
help(l, s, a2, b2);
идем в сигнатуры этих функций и меняем типы.
функция help похоже ошибок не содержит из за замены типа, а вот field не повезло. Первое - там куча size(a2). Если вдруг поле станет не квадратным - все пойдет кувырком. Но и кода там намешано куча, попробуем понять, что оно делает.
Для начала нам понадобится две функции в наш класс Field
int get_width() const { return width; }
int get_height() const { return height; }
и все эти size(a2) можно поменять. правда я делал это немного наугад.
теперь настало время поменять все a[i][j] на a.get(i,j) или a.set(i,j,...).
После всех замен выясняется, что да, в массиве m_field могут хранится только символы. И не просто символы, а только определенные символы. Заведем перечисление для этого
enum class CellType
{
None,
Type0, // 0
Type1, // 1
TypeQ, // ?
TypeS, // *
TypeO, // O
Typeo, // o
TypeU, // ^
TypeL, // <
TypeR, // >
TypeX, // x
};
и функцию для вывода. Она нам ещё пригодится.
std::ostream& operator<<(std::ostream& os, const CellType& c)
{
switch (c) {
case CellType::None: os << ' '; break;
case CellType::Type0: os << '0'; break;
case CellType::Type1: os << '1'; break;
case CellType::TypeQ: os << '?'; break;
case CellType::TypeS: os << '*'; break;
case CellType::TypeO: os << 'O'; break;
case CellType::Typeo: os << 'o'; break;
case CellType::TypeU: os << '^'; break;
case CellType::TypeL: os << '<'; break;
case CellType::TypeR: os << '>'; break;
case CellType::TypeX: os << 'x'; break;
}
return os;
}
возникает вопрос - а почему такие странные имена? а я делал полуавтоматическую замену. я не до конца понимал суть каждого символа. Это следующий этап.
Теперь смотрим по коду и пытаемся приписать этим элементам перечисления "разумные имена".
enum class CellType
{
None,
Type0, // 0
Ship, // 1
TypeQ, // ?
Hit, // *
TypeO, // O
BowBottom, // o
BowUp, // ^
BowLeft, // <
BowRight, // >
Miss, // x
};
Не все получилось, но это такое...
Теперь, если посмотреть в код, там есть ещё system("cls") и Sleep, который немного напрягает. Давайте сделаем для этого маленький класс
class Platform {
public:
static void Init() { setlocale(LC_ALL, "rus"); }
static void CleanScreen() { system("cls"); }
static void Sleep(int s) { ::Sleep(s); }
};
На первый взгляд он выглядит странно, но на самом деле он инкапсулировал в себе все платформенно зависимое. И теперь вместе с #include <Windows.h> можно унести в отдельный файл. Потом, позже, чуточку переделав, можно сделать этот код рабочим и под Linux/Mac или ncurce. Мелочь, а приятно.
Попутно выясняется, что функция field скорее всего должна называться show_field. Функция help на самом деле не только показывает подсказу, а и спрашивает координаты удара. get_user_input? ask_user_about_coord?. А по хорошему ее нужно ещё и разделить на несколько меньших.
Да, мы подобрались к самому вкусному. Функция game - она на самом деле огромная. Она должна быть порезана на куски. Более того, комментарии внутри намекают на это.
Смотрим на это
get_user_input(l, s, a2, b2);
cout << "введите направление:"; cin >> t;
ой ой. мы получили координаты, а потом ещё прямо запрашиваем направление и чуть ниже заполняем наши поля. И посмотрев на это по новому, я понял, что это просто запрос одного однопалубного, одного двух палубного и одного трехпалубного. Неочевиденько.
В процессе выделения выясняется, что там есть прям готовая функция
void generate_bot_ships(Field& b)
о, значит b - это все таки от бот, а a - это игрок.
Я также заметил, что очень часто одни и теже переменные используются для разных целей. Не нужно боятся делать разные переменные для разных целей. одной такой есть переменная с именем l, которая и для пользовательского ввода используется, и для флажков.
Пока получилось уже так
#include <iostream>
#include <Windows.h>
#include <cstdlib>
#include <ctime>
#include <string>
#include <conio.h>
using namespace std;
class Platform {
public:
static void Init() { setlocale(LC_ALL, "rus"); }
static void CleanScreen() { system("cls"); }
static void Sleep(int s) { ::Sleep(s); }
};
enum class CellType
{
None,
Type0, // 0
Ship, // 1
TypeQ, // ?
Hit, // *
TypeO, // O
BowBottom, // o
BowUp, // ^
BowLeft, // <
BowRight, // >
Miss, // x
};
std::ostream& operator<<(std::ostream& os, const CellType& c)
{
switch (c) {
case CellType::None: os << ' '; break;
case CellType::Type0: os << '0'; break;
case CellType::Ship: os << '1'; break;
case CellType::TypeQ: os << '?'; break;
case CellType::Hit: os << '*'; break;
case CellType::TypeO: os << 'O'; break;
case CellType::BowBottom: os << 'o'; break;
case CellType::BowUp: os << '^'; break;
case CellType::BowLeft: os << '<'; break;
case CellType::BowRight: os << '>'; break;
case CellType::Miss: os << 'x'; break;
}
return os;
}
class Field
{
static const int width = 10;
static const int height = 10;
CellType m_field[width][height];
public:
const CellType get(int x, int y) { return m_field[x][y]; }
void set(int x, int y, const CellType v) { m_field[x][y] = v; }
void fill(const CellType el) {
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
m_field[i][j] = el;
}
}
}
int get_width() const { return width; }
int get_height() const { return height; }
};
void show_field(Field& a2, Field& b2) {
cout << endl;
cout << " " << "Морской бой от valecop";
cout << endl << endl << endl;
//создание и заполнение таблиц
for (int w = 0; w < 1; w++) {
cout << " ";
for (int y = 0; y < a2.get_width(); y++) {
cout << y << " ";
//отвечает за столбцы первой таблицы
}
cout << " ";
for (int y = 0; y < a2.get_width(); y++) { // 2? b2?
cout << y << " ";
//отвечает за столбцы второй таблицы
}
cout << endl;
for (int i = 0; i < a2.get_width(); i++) {
cout << " ";
cout << i;
for (int j = 0; j < a2.get_width(); j++) {
cout << "|" << a2.get(i,j);
}
cout << "|" << " ";
cout << i;
for (int f = 0; f < b2.get_width(); f++) {
cout << "|" << b2.get(i,f);
}
cout << "|" << endl;
}
}
}
void get_user_input(short& l, short& s, Field& a2, Field& b2) {
cout << endl;
cout << "Выберите местоположение ваших кораблей" << endl << "Корабли выставляются по возрастанию , начиная с однопалубного и заканчивая трёхпалубным" << endl;
cout << "Введите координаты и поставьте знаки " << endl;
cout << "Знак" << '"' << CellType::BowUp << '"' << " " << "чтобы корабль стоял носом наверх\n";
cout << "Знак" << '"' << CellType::BowLeft << '"' << " " << "чтобы корабль стоял носом вправо\n";
cout << "Знак" << '"' << CellType::BowRight << '"' << " " << "чтобы корабль стоял носом влево \n";
cout << "Знак" << '"' << "!" << '"' << " " << "чтобы корабль стоял носом вниз \n";
cout << "введите столбик:"; cin >> l;
cout << "введите строку: "; cin >> s;
int tu = 10;
while (tu != 0) {
tu = 0;
if (s > 9 || l > 9) {
tu = 1;
cout << "\t\t\t\t\t\tНеверные координаты";
Platform::Sleep(2000);
Platform::CleanScreen();
show_field(a2, b2);
get_user_input(l, s, a2, b2);
}
else if (s < 0 || l < 0) {
tu = 1;
cout << "\t\t\t\t\t\tНеверные координаты";
Platform::Sleep(2000);
Platform::CleanScreen();
show_field(a2, b2);
get_user_input(l, s, a2, b2);
}
}
}
void ask_user_about_initial_ships(Field& a2, Field& b2, Field& a)
{
char t;
short s, l;
show_field(a2, b2);
get_user_input(l, s, a2, b2);
//расстановка корабля
a.set(s, l, CellType::Ship);
a2.set(s, l, CellType::BowUp);
Platform::CleanScreen();
show_field(a2, b2);
get_user_input(l, s, a2, b2);
cout << "введите направление:"; cin >> t;
//расстановка корабля
switch (t) {
case '^':
if (s - 1 >= 0) {
a.set(s, l, CellType::Ship);
a2.set(s, l, CellType::TypeO);
a.set(s - 1, l, CellType::Ship);
a2.set(s - 1, l, CellType::BowUp);
}
break;
case '>':
if (l + 1 <= 9) {
a.set(s, l, CellType::Ship);
a2.set(s, l, CellType::TypeO);
a.set(s + 1, l, CellType::Ship);
a2.set(s + 1, l, CellType::BowRight);
}
break;
case '<':
if (l - 1 >= 0) {
a.set(s, l, CellType::Ship);
a2.set(s, l, CellType::TypeO);
a.set(s, l - 1, CellType::Ship);
a2.set(s, l - 1, CellType::BowLeft);
}
break;
case '!':
if (s + 1 <= 9) {
a.set(s, l, CellType::Ship);
a2.set(s, l, CellType::TypeO);
a.set(s + 1, l, CellType::Ship);
a2.set(s + 1, l, CellType::BowBottom);
}
}
Platform::CleanScreen();
show_field(a2, b2);
get_user_input(l, s, a2, b2);
cout << "введите направление:"; cin >> t;
switch (t) {
case '^':
if (s - 2 >= 0) {
a.set(s, l, CellType::Ship);
a2.set(s, l, CellType::TypeO);
a.set(s - 1, l, CellType::Ship);
a2.set(s - 1, l, CellType::TypeO);
a.set(s - 2, l, CellType::Ship);
a2.set(s - 2, l, CellType::BowUp);
}
break;
case '>':
if (l + 2 <= 9) {
a.set(s, l, CellType::Ship);
a2.set(s, l, CellType::TypeO);
a.set(s, l + 1, CellType::Ship);
a2.set(s, l + 1, CellType::TypeO);
a.set(s, l + 2, CellType::Ship);
a2.set(s, l + 2, CellType::BowRight);
}
break;
case '<':
if (l - 2 >= 0) {
a.set(s, l, CellType::Ship);
a2.set(s, l, CellType::TypeO);
a.set(s, l - 1, CellType::Ship);
a2.set(s, l - 1, CellType::TypeO);
a.set(s, l - 2, CellType::Ship);
a2.set(s, l - 2, CellType::BowLeft);
}
break;
case '!':
if (s + 2 <= 9) {
a.set(s, l, CellType::Ship);
a2.set(s, l, CellType::TypeO);
a.set(s + 1, l, CellType::Ship);
a2.set(s + 1, l, CellType::TypeO);
a.set(s + 2, l, CellType::Ship);
a2.set(s + 2, l, CellType::BowBottom);
}
}
}
void generate_bot_ships(Field& b)
{
short l;
//бот расставляет корабли
srand(time(NULL));
int TAB_BOT = rand() % 10;
int LINE_BOT = rand() % 10;
b.set(TAB_BOT, LINE_BOT, CellType::Ship);
Platform::Sleep(200);
int u = 0;
l = 0;
while (u != 1) {
TAB_BOT = rand() % 10;
LINE_BOT = rand() % 10;
if (TAB_BOT - 1 <= 9 && LINE_BOT - 1 <= 9) {
if (b.get(TAB_BOT, LINE_BOT) != CellType::Ship && b.get(TAB_BOT - 1, LINE_BOT) != CellType::Ship) {
b.set(TAB_BOT, LINE_BOT, CellType::Ship);
b.set(TAB_BOT - 1, LINE_BOT, CellType::Ship);
u = 1;
}
}
}
Platform::Sleep(200);
l = 0;
while (l != 1) {
TAB_BOT = rand() % 10;
LINE_BOT = rand() % 10;
if (TAB_BOT + 1 <= 9 && TAB_BOT + 2 <= 9) {
if (b.get(TAB_BOT, LINE_BOT) != CellType::Ship && b.get(TAB_BOT + 1, LINE_BOT) != CellType::Ship) {
if (b.get(TAB_BOT + 2, LINE_BOT) != CellType::Ship)
b.set(TAB_BOT, LINE_BOT, CellType::Ship);
b.set(TAB_BOT + 1, LINE_BOT, CellType::Ship);
b.set(TAB_BOT, LINE_BOT, CellType::Ship);
b.set(TAB_BOT + 2, LINE_BOT, CellType::Ship);
l++;
}
}
}
//бот закончил
}
int game(Field a, Field b) {
//char t;
short s, l;
Field a2{};
Field b2{};
// заполнение масивов
a2.fill(CellType::TypeQ);
b2.fill(CellType::TypeQ);
a.fill(CellType::Type0);
b.fill(CellType::Type0);
ask_user_about_initial_ships(a2, b2, a);
Platform::CleanScreen();
show_field(a2, b2);
generate_bot_ships(b);
short bot_heat = 0, person_heat = 0;
Platform::CleanScreen();
while (bot_heat < 6 && person_heat < 6) {
show_field(a2, b2);
cout << endl;
cout << "Ваш ход , вбейте координаты\n";
cout << "Введите номер столбца:"; cin >> l;
cout << "Введите номер строки: "; cin >> s;
if (b.get(s,l) == CellType::Ship) {
person_heat++;
b2.set(s,l, CellType::Hit);
cout << endl;
cout << "вы попали";
}
else {
b2.set(s,l, CellType::Miss);
cout << "вы промахнулись";
}
Platform::CleanScreen();
show_field(a2, b2);
cout << endl;
cout << "ход противника";
Platform::Sleep(300);
bool need_to_continue = false;
while (need_to_continue) {
int TAB_BOT = rand() % 10;
int LINE_BOT = rand() % 10;
if (a2.get(TAB_BOT,LINE_BOT) != CellType::Miss && a2.get(TAB_BOT,LINE_BOT) != CellType::Hit) {
if (a.get(TAB_BOT,LINE_BOT) == CellType::Ship) {
bot_heat++;
a2.set(TAB_BOT,LINE_BOT, CellType::Hit);
}
else {
a2.set(TAB_BOT,LINE_BOT, CellType::Miss);
}
need_to_continue = true;
}
}
Platform::CleanScreen();
}
if (bot_heat > person_heat) {
cout << "\t\t\t\t\t\tБот победил!";
}
else {
cout << "\t\t\t\t\t\tВы победили!";
}
return 0;
}
int main() {
Platform::Init();
Field a{};
Field b{};
game(a, b);
}
Но тут ещё работать и работать, а у меня есть и своя работа.