Морской бой и С++

в 10:01, , рубрики: c++, Морской бой, Песочница, Программирование, С++, метки: ,

Я помню, была неделя-две тематики морского боя, я к сожалению не успел опубликовать свою версию этой замечательной игры на С++.
Моя версия Морского боя под катом!

Начнем

Для начала хочу сказать, что эта статья не предназначена для новичков, я выкладыю ее просто для того, чтобы поделится своими решениями и трудностями при написании Морского боя.

Игру я делал в консоли, так что о графике не может быть и речи. А так же она написана полностью на процедурном С++, по причине того, что я сам все еще изучаю базу языка. На тот момент я не был знаком с ООП / Структурами / Битовыми полями.

Проблема №1:

Самой первой проблемой было то, что я совершенно не представлял, как адекватно выводить два поля в консоли. Написание Морского боя (Дальше М.б.) происходило на Mac OS, поэтом о библиотеке conio.h и функциях для работы с графикой консоли не могло быть и речи.
Но это и проблемой назвать нельзя, наверника скажет большинство из вас. И, наверное, я с вами соглашусь. Код вывода получился очень простым:

void paintfield(int choose)
{ 
    /* Если выбрана установка кораблей, выводим поле игрока и количество его кораблей */
    if (choose == 1)
    {
    std::cout << "n  0 1 2 3 4 5 6 7 8 9n";
    for (int count = 0; count < 10; count++) // Выводим 10 столбцов
    { 
        std::cout << count << " "; // Координаты сбоку поля
        for (int count2 = 0; count2 < 10; count2++)
        {
            
            std::cout << Playerfield[count][count2] << " "; // Выводим по 10 символов в ряду
            
        }
        std::cout << std::endl; // Переходим на следующую строку
    }
        std::cout << std::endl;
        
        std::cout << "Ваши корабли: " << ships[0] << ", " << ships[1] << ", " << ships[2] << ", " << ships[3] << "; n"; // Оставшиеся у игрока корабли
    }
    
    else if(choose == 2) // Если выбрана игра, рисуем два поля
    {
        std::cout << "n  0 1 2 3 4 5 6 7 8 9t   0 1 2 3 4 5 6 7 8 9n";
        for (int count = 0; count < 10; count++)
        {
            std::cout << count << " ";
            for (int count2 = 0; count2 < 10; count2++)
            {
                std::cout << Playerfield[count][count2] << " "; // Поле игрока
            }
            
            std::cout << "   "; // Растояние между полями 4 пробела
            std::cout << count << " "; // Координаты сбоку поля
            for (int count2 = 0; count2 < 10; count2++)
            {
                std::cout << Enemyfield[count][count2] << " ";  // Поле компьютера
            }
            
            std::cout << std::endl;
        }
          std::cout << std::endl;
    }

Поля заполняются функцией fillarr при вызове в главном файле, в main функции. Код заполнения:

void fillarr()
{
    for (int count = 0; count < 10; count++)
    {
        for (int count2 = 0; count2 < 10; count2++)
        {
            Playerfield[count][count2] = '.';
            Enemyfield[count][count2] = '.';
            
        }
    }
}
Проблема №2:

Проверка на границы кораблей. Это была достаточно сложная часть в моем М.б. для меня.
Решил, но работает плохо. Сейчас если и буду переделывать игру, то такой способ использовать не буду ни в коем случае:

bool playercheckplase(int y, int x, int side, int size)
{
    for (int i = 0; i <= size+1; i++)
    {
        
    switch (side)
        {
            
        case Right:
                if (Playerfield[y][x-1] == 'O' || Playerfield[y-1][x-1] == 'O'
                    || Playerfield[y+1][x-1] == 'O' || Playerfield[y-1][x+i] == 'O'
                    || Playerfield[y+1][x+i] == 'O' || Playerfield[y][x+i] == 'O')
                {
                    return 0;
                }
            break;
            
        case Left:
                if (Playerfield[y][x+1] == 'O' || Playerfield[y-1][x+1] == 'O'
                    || Playerfield[y+1][x+1] == 'O' || Playerfield[y-1][x-i] == 'O'
                    || Playerfield[y+1][x-i] == 'O' || Playerfield[y][x-i] == 'O')
                {
                    return 0;
                }
            break;
                
            case Up:
                if (Playerfield[y+1][x] == 'O' || Playerfield[y+1][x+1] == 'O'
                    || Playerfield[y+1][x-1] == 'O' || Playerfield[y-i][x-1] == 'O'
                    || Playerfield[y-i][x+1] == 'O' || Playerfield[y-i][x] == 'O')
                {
                    return 0;
                }
                break;
                
            case Down:
                if (Playerfield[y-1][x] == 'O' || Playerfield[y-1][x+1] == 'O'
                    || Playerfield[y-1][x-1] == 'O' || Playerfield[y+i][x-1] == 'O'
                    || Playerfield[y+i][x+1] == 'O' || Playerfield[y+i][x] == 'O')
                {
                    return 0;
                }
                break;
                
            default:
                if (Playerfield[y+1][x] == 'O' || Playerfield[y+1][x+1] == 'O'
                    || Playerfield[y+1][x-1] == 'O' || Playerfield[y-1][x-1] == 'O'
                    || Playerfield[y-1][x+1] == 'O' || Playerfield[y-1][x] == 'O'
                    || Playerfield[y][x-1] == 'O' || Playerfield[y][x+1] == 'O')
            {
                return 0;
            }
                break;
    
        }
    }
    return 1;
}

Я думаю, самое непонятное здесь, почему я сравниваю все с символом «О».
Из-за того, что установка кораблей происходит у меня таким образом:

void playership()
{
    int x, y, side, size;
    side = NONE;
    
    std::cout << "Enter X coord: ";
    std::cin >> x;
    std::cout << "Enter Y coord: ";
    std::cin >> y;
    std::cout << "Enter size: ";
    std::cin >> size;
    
    if (size > 1)
    {
    std::cout << "Enter side (0 - right, 1 - up, 2 - down, 3 - left): ";
    std::cin >> side;
        std::cout << "n";
    }
    installship(y, x, side, size);
    paintfield(1);
}

Это получение координат от пользователя, непосредственно установка:

void installship(int y, int x, int side, int size)
{
        if (x < 10 && y < 10 && size < 5 && size > 0 && playercheckplase(y, x, side, size) != 0 && checkborder(y, x, side, size))
        {
            

                switch(side)
                {
                    case Right:
                        
                        if(ships[size-1])
                        {
                            
                            for(int i = 0; i < size; i++)
                        {
                        Playerfield[y][x+i] = 'O';
                        }
                            
                        }
                        break;
                        
                    case Up:
                        
                        if(ships[size-1])
                        {
                            
                            for(int i = 0; i < size; i++)
                        {
                        Playerfield[y-i][x] = 'O';
                        }
                            
                        }
                        break;
                        
                    case Down:
                        
                        if(ships[size-1])
                        {
                            
                            for(int i = 0; i < size; i++)
                        {
                        Playerfield[y+i][x] = 'O';
                        }
                            
                        }
                        break;
                        
                    case Left:
                        
                        if(ships[size-1])
                        {
                            
                        for(int i = 0; i < size; i++)
                        {
                        Playerfield[y][x-i] = 'O';
                        }
                            
                        }
                        break;
                        
                        
                    default:
                        
                        if(ships[size-1])
                        {
                            
                        for(int i = 0; i < size; i++)
                        {
                        Playerfield[y][x] = 'O';
                        }
                            
                        }
                        break;
                        }
            ships[size-1]--;
        }
    }

Проблема №3:

Границы поля. Больше всего мне понравилось писать именно прокерку границ поля. Я долго делал разнообразные проверки границ, но большинство из них либо не работали, либо работали с ошибками. Остановился на этом варианте:

bool checkborder(int y, int x, int side, int size)
{
    size--;
    switch (side)
    {
            
            
        case Right:
            
            if (x+size > 9) // Если координата x (горизонталь) + размер больше самого поля, возвращаем 0. Актуально для случая установки вправо. 
            {
                return 0;
            }
                break;
            
        case Left: 
            
            if (x-size < 0) // Если координата x (горизонталь) + размер больше длинны поля, возвращаем 0. Актуально для случая установки влево. 
            {
                return 0;
            }
            break;
            
        case Up: 
            
            if (y-size < 0) // Если координата y (вертикаль) + размер больше высоты поля возвращаем 0. Актуально для случая установки вверх. 
            {
                return 0;
            }
            break;
            
        case Down:
            
            if (y+size > 9) // Если координата y (вертикаль) + размер больше высоты поля, возвращаем 0. Актуально для случая установки вниз.
            {
                    return 0;
            }
            break;
     
        default: 
            return 1; // Если все нормально, возвращаем 1.
            break;
    }
}

На этом главные проблемы и кончаются. Атака осуществляется самым элементарным образом для игрока:

void playerattack()
{

   int x = 0;
   int y = 0;
    std::cout << "Enter X coord: ";
    std::cin >> x;
    std::cout << "Enter Y coord: ";
    std::cin >> y;
    if (x < 10 && y < 10)
    {
        Enemyfield[y][x] = 'X';
    }

}

И для компьютера:

void enemyattack()
{
    int x, y, z;
    z = 1;
    x = 0;
    y = 0;
    x = rand()%9;
    y = rand()%9;

     Playerfield[y][x] = 'X';
}

А вот здесь и проявляется недописанность программы, в функцию атаки компьютера требуется рекурсия.

На этом все, около 30% игры не доделано, а именно:

  • Завершение игры при чьей либо победе
  • Более сильный противник

Так что, прошу вашей критики. Я знаю, что код не самый лучший, но и я только учусь.

Исходники.

Автор: RussDragon

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js