Расстановка кораблей на поле (Консольный морской бой) на 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 шт):

Автор решения: aepot

Посмотрел ваш код, посмотрел код @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 ~ ~
→ Ссылка