CCCPaste

Sea fight

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