Расстановка кораблей на поле (Консольный морской бой) на C#
Хочу написать консольный морской бой, сейчас у меня только выводится поле игрока.
using System;
class Program
{
static int fieldSize = 10;
static char[,] PlayerField = new char[fieldSize, fieldSize];
static char[,] OpponentField = new char[fieldSize, fieldSize];
static Random rnd = new Random();
static void Main(string[] args)
{
InitializateField(PlayerField);
InitializateField(OpponentField);
PrintField(PlayerField, "Поле игрока:");
}
static void InitializateField(char [,] field)
{
for (int i = 0; i < fieldSize; ++i)
{
for (int j = 0; j < fieldSize; ++j)
{
field[i,j] = '~';
}
}
}
static void PrintField(char[,] field, string nameOfField)
{
Console.WriteLine(nameOfField);
Console.Write(" ");
for (int i = 0; i < fieldSize; ++i)
Console.Write($"{i + 1} ");
Console.WriteLine();
for (int i = 0; i < fieldSize; ++i)
{
Console.Write($"{(char)('A' + i)} ");
for (int j = 0; j < fieldSize; ++j)
{
Console.Write($"{field[i,j]} ");
}
Console.WriteLine();
}
}
}
Вот прям вообще не понимаю как можно расставить корабли на поле, всё что могу это самому расставить 10 раз рандомно корабли, а потом вызвать рандом в диапазоне (0, 9) и для каждого значения будет в case
своя ручная расстановка. Так вот как бы мне понять алгоритм расстановки кораблей на поле?
ВОТ ТУТ МЕТОДЫ ДЛЯ РАССТАНОВКИ КОРАБЛЕЙ:
static void PlaceShips(char[,] field)
{
PlaceShip(field, 4);
for (int i = 0; i < 2; i++) PlaceShip(field, 3);
for (int i = 0; i < 3; i++) PlaceShip(field, 2);
for (int i = 0; i < 4; i++) PlaceShip(field, 1);
}
static void PlaceShip(char[,] field, int size)
{
bool placed = false;
while (!placed)
{
// Выбираем случайное направление: 0 - горизонтально, 1 - вертикально
int direction = rnd.Next(2);
// Случайные координаты для начальной точки корабля
int row = rnd.Next(fieldSize);
int col = rnd.Next(fieldSize);
if (CanPlaceShip(field, row, col, size, direction))
{
// Размещаем корабль
for (int i = 0; i < size; i++)
{
if (direction == 0)
field[row, col + i] = 'S'; // горизонтально
else
field[row + i, col] = 'S'; // вертикально
}
placed = true;
}
}
}
// Проверка, можно ли разместить корабль
static bool CanPlaceShip(char[,] field, int row, int col, int size, int direction)
{
// Проверяем, не выходит ли корабль за границы
if (direction == 0 && col + size > fieldSize) return false; // горизонтально
if (direction == 1 && row + size > fieldSize) return false; // вертикально
// Проверяем, что на пути корабля нет других кораблей
for (int i = 0; i < size; i++)
{
int r = row + (direction == 1 ? i : 0);
int c = col + (direction == 0 ? i : 0);
if (field[r, c] == 'S' || !IsSurroundingAreaClear(field, r, c))
{
return false; // Есть пересечения
}
}
return true;
}
// Проверка, что вокруг клетки корабля нет других кораблей
static bool IsSurroundingAreaClear(char[,] field, int row, int col)
{
for (int i = row - 1; i <= row + 1; i++)
{
for (int j = col - 1; j <= col + 1; j++)
{
if (i >= 0 && i < fieldSize && j >= 0 && j < fieldSize)
{
if (field[i, j] == 'S')
return false;
}
}
}
return true;
}
Но я вот думаю что это будет очень сложно разобрать, как мне разобрать эти методы чтобы я понял как она генерируют корабли?
Ответы (1 шт):
Посмотрел ваш код, посмотрел код @EvgeniyZ и написал что-то среднее.
Первое, что бросилось в глаза, это проверка возможности установить корабль. Зачем проверять 9 клеток по координатам, если проверки соседних клеток будут пересекаться? Не проще ли проверить сразу весь прямоугольник за один присест?
Например такой для 4-палубного?
~ ~ ~ ~ ~ ~
~ S S S S ~
~ ~ ~ ~ ~ ~
То есть 3 на 6. Логично же?
Ну вот и метод родился:
static bool CanPlaceShip(char[,] grid, int row, int col, int height, int width)
{
for (int i = row; i < row + height; i++)
{
for (int j = col; j < col + width; j++)
{
if (i >= 0 && i < grid.GetLength(0) && j >= 0 && j < grid.GetLength(1) && grid[i, j] == 'S')
{
return false;
}
}
}
return true;
}
Теперь генератор кораблей
У нас по сути последовательность генерации по размерам кораблей есть [4, 3, 3, 2, 2, 2, 1, 1, 1, 1]
. Такая последовательность генерируется двумя вложенными циклами алгоритмически. Где внешний это перечисление размеров от 4 до 1, а внутренний их количество, которое вычисляется по формуле 4 - размер + 1
.
Далее как было у @EvgeniyZ, пытаемся воткнуть корабль на случайные координаты со случайным направлением до тех пор, пока не воткнётся.
Родился ещё один метод:
static void PlaceShips(char[,] grid)
{
Random rnd = Random.Shared;
for (int shipSize = 4; shipSize > 0; shipSize--)
{
for (int count = 0; count <= 4 - shipSize; count++)
{
while (true)
{
int vertical = rnd.Next(2);
int horizontal = 1 - vertical;
int rowOffset = (shipSize - 1) * vertical;
int colOffset = (shipSize - 1) * horizontal;
int row = rnd.Next(grid.GetLength(0) - rowOffset);
int col = rnd.Next(grid.GetLength(1) - colOffset);
if (CanPlaceShip(grid, row - 1, col - 1, 3 + rowOffset, 3 + colOffset))
{
for (int s = 0; s < shipSize; s++)
{
grid[row + s * vertical, col + s * horizontal] = 'S';
}
break;
}
}
}
}
}
Здесь почти нет условий, одно всего. Всё остальное банальная математика.
vertical
случайное число 0 или 1, оно 1 если корабль вертикальный.horizontal
- 1 если горизонтальный.
С помощью такого подхода к определению направления корабля легко вычислять координаты его границ и расположения клеток математически.
Ну и пару служебных методов создания матрицы и её печати:
static char[,] CreateGrid(int size)
{
char[,] grid = new char[size, size];
for (int i = 0; i < size; i++)
{
for (int j = 0; j < size; j++)
{
grid[i, j] = '~';
}
}
return grid;
}
static void Print(char[,] grid)
{
for (int i = 0; i < grid.GetLength(0); i++)
{
for (int j = 0; j < grid.GetLength(1); j++)
{
Console.Write($"{grid[i, j]} ");
}
Console.WriteLine();
}
}
Поехали:
static void Main(string[] args)
{
char[,] grid = CreateGrid(10);
PlaceShips(grid);
Print(grid);
}
Вывод в консоль:
~ ~ S S S ~ ~ S ~ ~
~ ~ ~ ~ ~ ~ ~ ~ ~ ~
S ~ ~ ~ ~ ~ S S ~ ~
~ ~ ~ ~ S ~ ~ ~ ~ ~
S ~ ~ ~ S ~ S ~ ~ ~
~ ~ ~ ~ S ~ S ~ ~ S
~ S ~ ~ S ~ ~ ~ ~ ~
~ S ~ ~ ~ ~ ~ ~ ~ ~
~ S ~ ~ ~ ~ ~ S ~ ~
~ ~ ~ ~ ~ ~ ~ S ~ ~