#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();
}