#include "pch.h" // функция minmax #include // функции srand и rand #include // функция time (используется в srand) #include // функция setw #include // объекты cin, cout #include // структура pair #include class Game { public: // ширина поля static const unsigned int FieldWidth = 10; // состояние клетки на поле enum CellState { Empty = 0, Occupied = 1 }; // валидность хода enum MoveValidity { Invalid = 0, Miss = 1, Hit = 2, Kill = 3 }; // была ли клетка проверена enum MoveState { Unchecked = 0, Checked = 1 }; // второе имя для пары координат using Coordinates = std::pair; // второе имя для массива с информацией о кораблях using Field = CellState[FieldWidth][FieldWidth]; // второе имя для массива с информацией о ходах using MoveField = MoveState[FieldWidth][FieldWidth]; // конструктор класса Game Game(); // запускает игру и возвращает код ошибки 0 int start(); // печатает поле игрока void printPlayerField() const; // печатает поле бота void printBotField() const; protected: // получает координаты корабла выбранной длины от игрока, возвращает true // и сохраняет координаты на поле игрока, если введённые координаты // представляют собой верное расположение корабля на поле игрока bool getPlayerCoords(unsigned int); // генерирует случайные верные координаты корабла выбранной длины, возвращает // true и сохраняет координаты на поле бота bool getBotCoords(unsigned int); // получает координаты (необязательно валидные) хода от игрока Coordinates getPlayerMove() const; // генерирует случайные валидные координаты хода бота Coordinates getBotMove() const; // проверяет, представляют ли координаты верное расположение корабля указанной // длины на указанном поле, дополнительно можно включить вывод ошибок static bool checkCoords(const Field &, unsigned int, Coordinates, Coordinates, bool = true); // проверяет ход, представленный указанными координатами на указанном поле, // дополнительно можно включить вывод ошибок static MoveValidity checkMove(const Field &, const MoveField &, Coordinates, bool = true); // на поле установить прямоугольник с углами в указанных координатах в // указанное состояние; при переходе от состояния Empty к состоянию Occupied // увеличить счётчик static void writeField(Field &, unsigned int &, CellState, Coordinates, Coordinates); // записать ход в массив ходов static void writeMove(MoveField &, MoveState, Coordinates); // печатает текущее состояние поля static void print(const Field &); private: // количество клеток, занятых кораблями игрока unsigned int playerCount; // количество клеток, занятых кораблами бота unsigned int botCount; // поле игрока Field playerField; // поле бота Field botField; // ходы игрока MoveField playerMoves; // ходы бота MoveField botMoves; }; Game::Game() { // функция make_pair создаёт объект структуры pair, содержащей пару из двух // значений writeField(playerField, playerCount, Empty, std::make_pair(0, 0), std::make_pair(FieldWidth - 1, FieldWidth - 1)); writeField(botField, botCount, Empty, std::make_pair(0, 0), std::make_pair(FieldWidth - 1, FieldWidth - 1)); for (unsigned int i = 0; i < FieldWidth; ++i) { for (unsigned int j = 0; j < FieldWidth; ++j) { writeMove(playerMoves, Unchecked, std::make_pair(i, j)); writeMove(botMoves, Unchecked, std::make_pair(i, j)); } } playerCount = 0; botCount = 0; } int Game::start() { std::cout << "The game begins!\n"; for (unsigned int shipLength = 1; shipLength <= 4; ++shipLength) { std::cout << "Enter coordinates for ships of length " << shipLength << "\n"; unsigned int shipCount = 4 - shipLength + 1; for (unsigned int shipNumber = 1; shipNumber <= shipCount; ++shipNumber) { std::cout << "Ship " << shipNumber << " of " << shipCount << "\n"; // !(выражение) значит выражение != true while (!getPlayerCoords(shipLength)) { std::cout << "You specified invalid coordinates, please try again\n"; } getBotCoords(shipLength); } } printPlayerField(); //printBotField(); while (playerCount > 0 && botCount > 0) { bool playerTurn = true; while (playerTurn && botCount > 0) { playerTurn = false; Coordinates playerMove; MoveValidity moveValidity; do { playerMove = getPlayerMove(); moveValidity = checkMove(botField, playerMoves, playerMove); } while (moveValidity == Invalid); writeMove(playerMoves, Checked, playerMove); if (moveValidity == Hit || moveValidity == Kill) { --botCount; playerTurn = true; if (moveValidity == Hit) { std::cout << "You hit bot's ship\n"; } else { std::cout << "You killed bot's ship\n"; } } else { std::cout << "You missed\n"; } std::cout << "Bot still have " << botCount << " units\n\n"; } if (botCount == 0) { break; } bool botTurn = true; while (botTurn && playerCount > 0) { botTurn = false; Coordinates botMove = getBotMove(); MoveValidity moveValidity = checkMove(playerField, botMoves, botMove); writeMove(botMoves, Checked, botMove); std::cout << "Bot fired at (" << botMove.first + 1 << "; " << botMove.second + 1 << ")\n"; if (moveValidity == Hit || moveValidity == Kill) { --playerCount; botTurn = true; if (moveValidity == Hit) { std::cout << "Bot hit your ship\n"; } else { std::cout << "Bot killed your ship\n"; } } else { std::cout << "Bot missed\n"; } std::cout << "You still have " << playerCount << " units\n\n"; } } std::cout << "Game finished\n\n"; printBotField(); std::cout << "\n"; if (botCount == 0) { std::cout << "You win\n"; } else { std::cout << "You lose\n"; } return 0; } void Game::printPlayerField() const { std::cout << "Your field\n"; print(playerField); } void Game::printBotField() const { std::cout << "Bot's field\n"; print(botField); } bool Game::getPlayerCoords(unsigned int shipLength) { if (shipLength == 1) { std::cout << "Enter coordinates of a ship: "; Coordinates coords; std::cin >> coords.first >> coords.second; --coords.first; --coords.second; if (checkCoords(playerField, shipLength, coords, coords)) { writeField(playerField, playerCount, Occupied, coords, coords); return true; } else { return false; } } else { std::cout << "Enter coordinates of the start of the ship: "; Coordinates coords1; std::cin >> coords1.first >> coords1.second; --coords1.first; --coords1.second; std::cout << "Enter coordinates of the end of the ship: "; Coordinates coords2; std::cin >> coords2.first >> coords2.second; --coords2.first; --coords2.second; if (checkCoords(playerField, shipLength, coords1, coords2)) { writeField(playerField, playerCount, Occupied, coords1, coords2); return true; } else { return false; } } } bool Game::getBotCoords(unsigned int shipLength) { bool foundCorrectCoords = false; while (!foundCorrectCoords) { bool horizontal = std::rand() % 2; Coordinates coords1(unsigned(std::rand()) % FieldWidth, unsigned(std::rand()) % FieldWidth); Coordinates coords2(coords1); if (horizontal) { coords2.second += shipLength - 1; } else { coords2.first += shipLength - 1; } if (checkCoords(botField, shipLength, coords1, coords2, false)) { writeField(botField, botCount, Occupied, coords1, coords2); foundCorrectCoords = true; } } return foundCorrectCoords; } Game::Coordinates Game::getPlayerMove() const { std::cout << "Enter coordinates of your move: "; Coordinates coords; std::cin >> coords.first >> coords.second; --coords.first; --coords.second; return coords; } Game::Coordinates Game::getBotMove() const { Coordinates coords; do { coords = std::make_pair(unsigned(std::rand()) % FieldWidth, unsigned(std::rand()) % FieldWidth); } while (checkMove(playerField, botMoves, coords, false) == Invalid); return coords; } bool Game::checkCoords(const Field &field, unsigned int shipLength, Coordinates coords1, Coordinates coords2, bool printErrors) { // функция minmax возващает пару значений, где первый элемент - меньший, а // второй - больший Coordinates checkI = std::minmax(coords1.first, coords2.first); Coordinates checkJ = std::minmax(coords1.second, coords2.second); if (checkI.second >= FieldWidth) { if (printErrors) { std::cout << "Range error\n"; } return false; } else if (checkJ.second >= FieldWidth) { if (printErrors) { std::cout << "Range error\n"; } return false; } if (checkI.first == checkI.second) { if (checkJ.second - checkJ.first != shipLength - 1) { if (printErrors) { std::cout << "Invalid length of horizontal ship\n"; } return false; } } else if (checkJ.first == checkJ.second) { if (checkI.second - checkI.first != shipLength - 1) { if (printErrors) { std::cout << "Invalid length of vertical ship\n"; } return false; } } else { if (printErrors) { std::cout << "Ship is not on one line\n"; } return false; } if (checkI.first > 0) { --checkI.first; } if (checkJ.first > 0) { --checkJ.first; } if (checkI.second < FieldWidth - 1) { ++checkI.second; } if (checkJ.second < FieldWidth - 1) { ++checkJ.second; } for (unsigned int i = checkI.first; i <= checkI.second; ++i) { for (unsigned int j = checkJ.first; j <= checkJ.second; ++j) { if (field[i][j] != Empty) { if (printErrors) { std::cout << "Cell at (" << i + 1 << "; " << j + 1 << ") is not empty\n"; } return false; } } } return true; } Game::MoveValidity Game::checkMove(const Field &field, const MoveField &moves, Coordinates coords, bool printErrors) { if (coords.first >= FieldWidth) { if (printErrors) { std::cout << "Move is out of field\n"; } return Invalid; } if (coords.second >= FieldWidth) { if (printErrors) { std::cout << "Move is out of field\n"; } return Invalid; } if (moves[coords.first][coords.second] == Checked) { if (printErrors) { std::cout << "This cell has already been checked\n"; } return Invalid; } if (field[coords.first][coords.second] == Empty) { return Miss; } unsigned int i = coords.first; unsigned int j = coords.second; while (true) { if (i > 0 && field[i - 1][j] != Empty) { --i; } else if (j > 0 && field[i][j - 1] != Empty) { --j; } else { break; } } unsigned int hit = 0; unsigned int occupied = 0; while (true) { hit += moves[i][j] == Checked || (i == coords.first && j == coords.second); occupied += 1; if (i < FieldWidth - 1 && field[i + 1][j] != Empty) { ++i; } else if (j < FieldWidth - 1 && field[i][j + 1] != Empty) { ++j; } else { break; } } if (hit == occupied) { return Kill; } else { return Hit; } } void Game::writeField(Field &field, unsigned int &counter, CellState state, Coordinates coords1, Coordinates coords2) { Coordinates setI = std::minmax(coords1.first, coords2.first); Coordinates setJ = std::minmax(coords1.second, coords2.second); for (unsigned int i = setI.first; i <= setI.second; ++i) { for (unsigned int j = setJ.first; j <= setJ.second; ++j) { if (field[i][j] == Empty && state == Occupied) { ++counter; } field[i][j] = state; } } } void Game::writeMove(MoveField &moves, MoveState state, Coordinates coords) { moves[coords.first][coords.second] = state; } void Game::print(const Field &field) { for (unsigned int i = 0; i <= FieldWidth; ++i) { if (i == 0) { for (unsigned int j = 0; j <= FieldWidth; ++j) { std::cout << std::setw(2) << j << " "; } } else { for (unsigned int j = 0; j <= FieldWidth; ++j) { if (j == 0) { std::cout << std::setw(2) << i << " "; } else { switch (field[i - 1][j - 1]) { case Empty: std::cout << " "; break; case Occupied: std::cout << "## "; break; } } } } std::cout << "\n"; } } int main() { // инициализирует генератор псевдослучайных чисел текущим временем std::srand(unsigned(time(nullptr))); Game game; return game.start(); }