Тем, кто шарит в С++

Господа профессионалы С++ и любители разминок для мозгов. Позвольте представить вам проблему поинтереснее вывода кириллицы на экран и самозакрывающейся командной строки. Подозреваю и надеюсь, вам она покажется не сложнее! :-)

Составил программу, не могу найти причину её не стабильной работы.
Описание программы: Хищник (‘V’) бегает по полю, поедая рандомно появляющуюся добычу (‘P’), выбирая ближайшую (‘A’). Координаты хищника и добычи записаны попарно в вектор – чётный элемент – х-координата, нечётный – у-координата. Программа выбирает ближайшую цель, и записывает в её координаты в векторе максимальные значения (длина и ширина поля * 2) для исключения их из следующего поиска.
При уничтожении хищником добычи, рандомно появляются от ноля до двух новых. Их координаты записываются в вектор в первую очередь в максимальные значения, если их нет – то пуш.бэкаются в конец вектора.
Максимально возможное количество добычи – 9 штук, плюс сам хищник. Итого, 10 пар координат – 20 элементов в векторе максимум.

Но время от времени столь же рандомно запись максимальных значений в вектор не происходит и новые координаты начинают писаться сверх лимита.
Косяк может вылезти в первый же момент игры, а может не проявлять себя минуты 2-3.
Косяк прячется в районе функции compare_coor() в первом цикле. Если первый цикл совсем убрать, всё работает идеально. Если же в его условии вместо переменной count, хранящей размер вектора прописать функцию vector.size() получаем Stack overflow при первом же проявлении косяка.
Второе подозрительное место — функция replace(), задача которой записать максимальные значения в координаты выбранной цели. Потому что, когда программа начинает пуш.бэкать лишние элементы в вектор, максимальных значений в векторе нет, независимо от его размера.
Внешне, косяк проявляет себя в появлении лишней добыче на экране, невидимой для хищника и накоплении элементов в векторе.
Циклической рекурсии в программе нет.
Может, у кого появится желание разобраться в моей писанине и подскажет в чём дело!?

Фото: На экране 11 единиц добычи, вместо 9. Целью выбрана не ближайшая. Координаты ближайшей в выборе не участвуют, хотя по идее в векторе они есть!

// main.cpp

// main.cpp

#include <iostream>
#include <ctime>

#include "map.h"
#include "game.h"

int main()
{
    srand(static_cast<unsigned int> (time(NULL)));

    map_making(); // подготовка карты

    game start; // создаём объект класса game

    start.set_coord(); // устанавливаем первоначальное расположение хищника и добычи

    while (true)
    {
        start.aim_select(); // получение контейнера с координатами и выбор цели
        start.screen(); // вывод на экран
        start.move_vermin(); // движение хищника к цели
    }
}

// map.h

// map.h

#ifndef MAP_H
#define MAP_H

#define length 22 // длина карты (длина поля +2 на стены по краям)
#define width 17 // ширина карты (ширина поля +2 на стены по краям)

extern char map[width][length]; // карта игры

void map_making(); // построение карты

#endif

// map.cpp

// map.cpp

#include "map.h"

char map[width][length]; // карта игры

void map_making() // построение карты
{
    char wall = '#';

    for (int i = 0; i<width; i = i + width - 1) // создаём горизонтальные стены
    {
        for (int j = 0; j<length; j++)
            map[i][j] = wall;
    }
    for (int i = 1; i<width - 1; i++) // создаём вертикальные стены
    {
        for (int j = 0; j<length; j = j + length - 1)
            map[i][j] = wall;
    }
    for (int i = 1; i<width - 1; i++) // инициализация внутренних клеток игровой карты пробелами
    {
        for (int j = 1; j<length - 1; j++)
            map[i][j] = ' ';
    }
}

// game.h

// game.h

#ifndef GAME_H
#define GAME_H

#include <vector>

using std::vector;

class game
{
public:

    void set_coord(); // присвоение значений переменным и получение координат хищника
    void aim_select(); // выбор цели
    void screen(); // вывод на экран
    void move_vermin(); // движение хищника к цели
    void set_respawn_prey_coord(); // получение координат добычи при респауне

    struct object
    {
        int x; // координата объекта по горизонтали
        int y; // координата объекта по вертикали
        char view; // внешний вид объекта
    };

private:

    void set_prey_coord(); // получение координат добычи

    vector<int> store; // хранилище координат
    int amount; // количество добычи
    int del_x; // хранилище адреса пары координат выбранной цели, для очистки значений
    int del_y;
};

#endif

// game.cpp

// game.cpp

#include <iostream>
#include <Windows.h>

#include "game.h"
#include "map.h"
#include "coordinates.h"

using std::cout;
using std::endl;
using std::vector;

location coordinate(length, width); // создаём объект класса location

game::object vermin; // хищник
game::object prey; // добыча
game::object aim; // цель-добыча

//-----------------------------------------------------------

void game::set_coord() // присваиваем значения переменным
{
    coordinate.set_location(); // установка координат хищника
    vermin.x = coordinate.get_x_coor(); // получение координаты хищника по горизонтали
    vermin.y = coordinate.get_y_coor(); // получение координаты хищника по вертикали
    vermin.view = 'V'; // внешний вид хищника

    map[vermin.y][vermin.x] = vermin.view; // задаём положение хищника

    aim.view = 'A'; // внешний вид цели-добычи
    prey.view = 'P'; // внешний вид добычи
    amount = 5; // количество добычи в начале

    for (int i = 0; i<amount; i++) // получение координат добычи
    {
        set_prey_coord();
    }
}

void game::set_prey_coord() // получение координат добычи
{
    coordinate.set_location(); // установка координат добычи
    prey.x = coordinate.get_x_coor(); // получение координаты добычи по горизонтали
    prey.y = coordinate.get_y_coor(); // получение координаты добычи по вертикали

    map[prey.y][prey.x] = prey.view; // задаём положение добычи
}
//----------------------------------------------------------

void game::aim_select() // выбор цели
{
    store = coordinate.get_store(); // получение контейнера с координатами для выбора цели

    int store_size = store.size(); // размер контейнера

    double d_ctrl = sqrt(pow(length, 2.0) + pow(width, 2.0)); // кратчайшее расстояние (по умолчанию, максимально возможное)
    double dist; // расстояние до цели

    for (int i = 2; i<store_size; i = i + 2) // сравниваем попарно координаты хищника с парами координат добычи
    {
        dist = sqrt(pow(vermin.x - store[i], 2.0) + pow(vermin.y - store[i + 1], 2.0)); // находим расстояние до добычи

        if (dist < d_ctrl) // если измеренное расстояние меньше кратчайшего
        {
            d_ctrl = dist; // сохраняем это расстояние, как кратчайшее
            aim.x = store[i]; // назначаем эту пару координат целью
            aim.y = store[i + 1];
            del_x = i; // сохраняем адрес этой пары координат для очистки значений
            del_y = i + 1;
        }
    }
    map[aim.y][aim.x] = aim.view; // помещаем цель на карту
    coordinate.replace(del_x, del_y); // отправляем её координаты на уничтожение
}
//-------------------------------------------------------------

void game::screen() // вывод на экран
{
    system("cls"); // Очистка экрана от предыдущего ввода

    for (int i = 0; i<width; i++) // выводим карту на экран
    {
        for (int j = 0; j<length; j++)
            cout << map[i][j];

        cout << endl; // переход к следующему ряду карты (следующая строка массива)
    }
}
//-  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -

void game::move_vermin() // движение хищника к цели
{
    bool correct = false;

    do
    {
        int dx = vermin.x - aim.x; // находим разности координат хищника идобычи
        int dy = vermin.y - aim.y;

        map[vermin.y][vermin.x] = ' '; // закрашиваем клетку где стоял хищник пробелом

        if (abs(dx) > abs(dy)) // корректируем координаты хищника ориентируясь на координаты добычи
        {
            if (vermin.x > aim.x)
                vermin.x--;
            else if (vermin.x < aim.x)
                vermin.x++;
        }
        else
        {
            if (vermin.y > aim.y)
                vermin.y--;
            else if (vermin.y < aim.y)
                vermin.y++;
        }
        map[vermin.y][vermin.x] = vermin.view; // задаём новое положение хищника

        Sleep(300); // делаем паузу перед следующим шагом хищника

        if (vermin.x == aim.x && vermin.y == aim.y) // добыча поймана и съедена
        {
            correct = true;
            coordinate.respawn(); // респаун добычи
            screen();
        }
        else
            screen();
    } while (correct == false);
}

void game::set_respawn_prey_coord() // получение координат добычи при респауне
{
    prey.x = coordinate.get_x_coor(); // получение координаты добычи по горизонтали
    prey.y = coordinate.get_y_coor(); // получение координаты добычи по вертикали

    map[prey.y][prey.x] = prey.view; // задаём положение добычи
}

// coordinates.h

// coordinates.h

#ifndef COORDINATES_H
#define COORDINATES_H

using std::vector;

class location
{
public:

    location(const int, const int); // конструтор - присвоение значений переменным
    void set_location(); // установка рандомных координат (не в стене и не у стен)
    int get_x_coor(); // получение х координаты
    int get_y_coor(); // получение у координаты
    vector<int> get_store(); // получение контейнера с координатами для выбора цели
    void respawn(); // респаун добычи
    void replace(int, int); // присвоение максимальных значений координатам выбранной цели, для исключения из следующего поиска

private:

    vector<int> store; // хранилище координат
    int x_coor; // х координата
    int y_coor; // у координата
    int length_field; // длина карты, минус по 2 клетки от краёв
    int width_field; // ширина карты, минус по 2 клетки от краёв
    int max_length; // максимальные значения для исключения из поиска цели
    int max_width;
    int amount_respawn; // количество возродившейся добычи
    int range_amount; // регулятор численности добычи
};

#endif

// coordinates.cpp

// coordinates.cpp

#include <vector>

#include "coordinates.h"
#include "game.h"

location::location(const int length, const int width) // установка размеров игрового поля
{
    length_field = length - 4; // минус стены и у стен
    width_field = width - 4;
    range_amount = 5; // число добычи
    max_length = length * 2; // максимальные значения для исключения из поиска цели
    max_width = width * 2;

    store.reserve(20);
}

void location::set_location() // случайным образом задаём координаты (но не у стен!)
{
    x_coor = 2 + rand() % (length_field);
    y_coor = 2 + rand() % (width_field);
                                                     // КОСЯК ГДЕ-ТО ТУТ!!!
    unsigned count = store.size();  // Если убрать count из условия первого for, и вставить store.size(), то получим Stack overflow (у второго цикла это не наблюдается)
                                    // А если удалить первый цикл совсем, то всё работает, как часы!

    for (unsigned j = 0; j < count /*store.size()*/; j = j + 2) // если совпали перезапуск подбора координат
    {
        if (x_coor == store[j] && y_coor == store[j + 1])
        {
            set_location();
        }
    }
    for (unsigned i = 0; i < count; i = i + 2) // пишем новые координаты в элементы вектора с максимальными значениями
    {
        if (store[i] == max_length && store[i + 1] == max_width)
        {
            store[i] = x_coor;
            store[i + 1] = y_coor;
            return;
        }
    }
    store.push_back(x_coor); // если макисмальных значений нет - то новые в конец вектора
    store.push_back(y_coor);
}

int location::get_x_coor() // получение х координаты
{
    return x_coor;
}

int location::get_y_coor() // получение у координаты
{
    return y_coor;
}

vector<int> location::get_store() // получение контейнера с координатами для выбора цели
{
    return store;
}

void location::respawn() // респаун добычи
{
    if (range_amount > 7) // если добычи на поле > 7, новых не добавляем
    {
        range_amount--; // минус одна на следующее съедение
        return;
    }
    else if (range_amount < 4) // если добычи на поле < 4, добавляем 2 новые
        amount_respawn = 2;
    else                             // в остальных случаях количество новых определяется рандомно
        amount_respawn = rand() % 3; // количество возродившейся добычи (от 0 до 2)

    range_amount = range_amount + amount_respawn - 1; // счётчик количества добычи на поле (+ новые, -1 убитая)

    for (int i = 0; i<amount_respawn; i++) // запускаем подбор координат и их установку для новой добычи
    {
        set_location();

        game respawn; // создаём объект класса game
        respawn.set_respawn_prey_coord();
    }
}

void location::replace(int del_x, int del_y) // присвоение максимальных значений координатам выбранной цели, для исключения из следующего поиска
{
    store.at(del_x) = max_length; // присваиваем использованным элементам исключающие значения
    store.at(del_y) = max_width;
}

Вариант исправления:

for (unsigned j = 0; j < count /*store.size()*/; j = j + 2) // если совпали перезапуск подбора координат
{
    if (x_coor == store[j] && y_coor == store[j + 1])
    {
        set_location();
        return ; //<-- чтобы после выполнения вызванной set_location не продолжалось выполнение текущей функции.
    }
}

ИМХО, лучше вообще сделать без рекурсии. Например так:

static std::pair<int,int> get_random_coordinate ( int length_field , int width_field )
{
    return std::make_pair<int,int>( 2 + rand() % (length_field), 2 + rand() % (width_field) ) ;
}


void location::set_location() // случайным образом задаём координаты (но не у стен!)
{
    std::pair<int,int> coords = get_random_coordinate ( length_field , width_field ) ;
    x_coor = coords.first ;
    y_coor = coords.second ;
                                                 // КОСЯК ГДЕ-ТО ТУТ!!!
unsigned count = store.size();  // Если убрать count из условия первого for, и вставить store.size(), то получим Stack overflow (у второго цикла это не наблюдается)
                                // А если удалить первый цикл совсем, то всё работает, как часы!
size_t consilienceCount = 0 ;
for (unsigned j = 0; j < count /*store.size()*/; j = j + 2) // если совпали перезапуск подбора координат
{
    if (x_coor == store[j] && y_coor == store[j + 1])
    {
        ++consilienceCount ;
        if ( consilienceCount < (length_field*width_field) )
        {
            coords = get_random_coordinate ( length_field , width_field ) ;
            x_coor = coords.first ;
            y_coor = coords.second ;
        } else
        {
            throw "НЕ УДАЕТСЯ СГЕНЕРИРОВАТЬ КООРДИНАТЫ" ; //Обрабатываем как хотим
        }
    }
}
//...

У меня остался только один вопрос.

Позвольте представить вам проблему поинтереснее вывода кириллицы на экран и самозакрывающейся командной строки

где обещанная проблема, которая интереснее вывода кириллицы?

Спасибо большое! Всё работает.
Вот поди догадайся. Функция никаких других функций не вызывает. Рекурснулась бы разок, другие координаты подобрала и благополучно завершилась бы. Но нет!

Внимание! Это довольно старый топик, посты в него не попадут в новые, и их никто не увидит. Пишите пост, если хотите просто дополнить топик, а чтобы задать новый вопрос — начните новый.

Ответить

Вы можете использовать разметку markdown для оформления комментариев и постов. Используйте функцию предпросмотра для проверки корректности разметки.

Пожалуйста, оформляйте исходный код в соответствии с правилами разметки. Для того, чтобы вставить код в комментарий, скопируйте его в текстовое поле ниже, после чего выделите то, что скопировали и нажмите кнопку «код» в панели инструментов. Иначе ваш код может принять нечитаемый вид.

Либо производите оформление кода вручную, следующим образом:

``` #include <iostream> using namespace std; int main() { // ... } ```

Предпросмотр сообщения

Ваше сообщение пусто.