0

(Edit: change suggested in the comments section now allows the desired behavior)

I'm developing an agent-based simulation in C++, and so far I'm able to run 1 simulation and I transcribe the results manually. It consists of an Environment class with a 2-d array with agents (hunters and preys)

To automate the data collection, I'm trying to implement some functions so that I can later transcribe relevant results of each simulation to a CSV file.

The following code in Visual Studio generates the Environment, populates it, counts the agents and display an ASCI map of the hunters and preys with a legend.

#include <iostream>
#include <cstdlib>  // for rand
#include <ctime>    // for time
#include <vector>
#include <Windows.h> //for color in output

using namespace std;

const int WORLDSIZEX = 100;
const int WORLDSIZEY = 30;

enum AgentType { PREY, HUNTER };

class Environment;

class Agent {
public:
    Agent(Environment* aWorld, int xcoord, int ycoord);
    virtual ~Agent() { }
    virtual AgentType getType() const = 0;
    virtual char representation() const = 0;

protected:
    int x;
    int y;
    Environment* world;
private:
};

struct Position {
    int x;
    int y;
};

class Environment {
public:
    Environment(unsigned int seed, int _id, int _initialPopulationPreys, int _initialPopulationHunmters);
    ~Environment();
    Agent* getAt(int x, int y) const; 
    void setAt(int x, int y, Agent* org);
    void display();
    Position randomPosition() const;
    Position randomPositionHunter() const;
    int numberPreys();
    int numberHunters();

private:
    Agent* grid[WORLDSIZEX][WORLDSIZEY];

    void createOrganisms(AgentType orgType, int count);
    int id;

    int timeStep;
    int initialPopulationPreys;
    int initialPopulationHunters;

};

class Hunter : public Agent {
public:
    Hunter(Environment* aWorld, int xcoord, int ycoord);
    AgentType getType() const;
    char representation() const;

private:
    bool altruistic;
};

class Prey : public Agent {
public:
    Prey(Environment* aWorld, int xcoord, int ycoord);
    AgentType getType() const;
    char representation() const;

private:
    int huntingDifficulty;
};

Prey::Prey(Environment* aWorld, int xcoord, int ycoord) : Agent(aWorld, xcoord, ycoord) { huntingDifficulty = int(rand() % 3); }
AgentType Prey::getType() const { return PREY; }
char Prey::representation() const { return 'o'; }

Hunter::Hunter(Environment* aWorld, int xcoord, int ycoord) : Agent(aWorld, xcoord, ycoord) { }
AgentType Hunter::getType() const { return HUNTER; }
char Hunter::representation()const { return 'X'; }

Agent::Agent(Environment* aWorld, int xcoord, int ycoord) {
    world = aWorld;
    x = xcoord;
    y = ycoord;
    world->setAt(x, y, this);
}

Environment::Environment(unsigned int seed, int _id, int _initialPopulationPreys, int _initialPopulationHunters) {
    srand(seed);
    id = _id;

    initialPopulationPreys = _initialPopulationPreys;
    initialPopulationHunters = _initialPopulationHunters;

    for (int i = 0; i < WORLDSIZEX; i++) {
        for (int j = 0; j < WORLDSIZEY; j++) {
            grid[i][j] = NULL;
        }
    }
    timeStep = 0;
    createOrganisms(PREY, initialPopulationPreys);
    createOrganisms(HUNTER, initialPopulationHunters);
}

Environment::~Environment() {
    for (int i = 0; i < WORLDSIZEX; i++) {
        for (int j = 0; j < WORLDSIZEY; j++) {
            if (grid[i][j] != NULL) {
                delete grid[i][j];
            }
        }
    }
}

Agent* Environment::getAt(int x, int y) const {
    if ((x >= 0) && (x < WORLDSIZEX) && (y >= 0) && (y < WORLDSIZEY)) {
        return grid[x][y];
    }
    else {
        return NULL;
    }
}

void Environment::setAt(int x, int y, Agent* org) {
    if ((x >= 0) && (x < WORLDSIZEX) && (y >= 0) && (y < WORLDSIZEY)) {
        grid[x][y] = org;
    }
}

// Displays the world in ASCII.
void Environment::display() {
    int numPreys = 0;
    int numHunters = 0;

    HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
    // Remember how things were when we started
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    GetConsoleScreenBufferInfo(hstdout, &csbi);

    cout << endl << endl;
    for (int j = 0; j < WORLDSIZEY; j++) {
        for (int i = 0; i < WORLDSIZEX; i++) {
            if (grid[i][j] == NULL) {
                cout << "_";
            }
            else {
                if (grid[i][j]->getType() == PREY) {
                    SetConsoleTextAttribute(hstdout, 10);
                    numPreys++;
                }
                else if (grid[i][j]->getType() == HUNTER) {
                    SetConsoleTextAttribute(hstdout, 12);
                    numHunters++;
                }
                cout << grid[i][j]->representation();
                SetConsoleTextAttribute(hstdout, csbi.wAttributes);
            }
        }
        cout << endl;
    }
    cout << "Preys 'o': " << numPreys << " Hunters 'X': " << numHunters << endl;
    cout << "Timestep:" << timeStep << " World ID:" << id << endl;
}

Position Environment::randomPosition() const {    // returns a random number in the range 0 to WORLDSIZEX - 1 (or WORLDSIZEY - 1)
    Position p;
    p.x = rand() % WORLDSIZEX;
    p.y = rand() % WORLDSIZEY;
    return p;
}

Position Environment::randomPositionHunter() const { // returns a random number in the central fifth of the grid
    Position p;
    int subGridSizeX = WORLDSIZEX / 5;
    int subGridSizeY = WORLDSIZEY / 5;

    p.x = subGridSizeX * 1 + (rand() % (3 * subGridSizeX));
    p.y = subGridSizeY * 2 + (rand() % subGridSizeY);
    return p;
}

int Environment::numberPreys() {
    int numPreys = 0;
    for (int j = 0; j < WORLDSIZEY; j++) {
        for (int i = 0; i < WORLDSIZEX; i++) {
            if (grid[i][j] && grid[i][j]->getType() == PREY) {
                numPreys++;
            }
        }
    }
    return numPreys;
}

int Environment::numberHunters() {
    int numHunters = 0;
    for (int j = 0; j < WORLDSIZEY; j++) {
        for (int i = 0; i < WORLDSIZEX; i++) {
            if (grid[i][j] && grid[i][j]->getType() == HUNTER) {
                numHunters++;
            }
        }
    }
    return numHunters;
}


void Environment::createOrganisms(AgentType orgType, int count) {
    int orgCount = 0;
    while (orgCount < count) {

        Position p = randomPosition();
        Position q = randomPositionHunter();

        if (orgType == PREY) {
            if (grid[p.x][p.y] == NULL) {           // Only put Organism in empty spot
                orgCount++;
                new Prey(this, p.x, p.y);       // Create a Prey and put it into the world
            }
        }
        else if (orgType == HUNTER) {
            if (grid[q.x][q.y] == NULL) {           // Only put Organism in empty spot
                orgCount++;
                new Hunter(this, q.x, q.y);     // Create a Hunter and put it into the world
            }
        }
    }
}


int main() {
    int initialPreys = 60;
    int initialHunters = 15;
    int id = 0;

    //Creating the environment
    int seed = time(0);
    Environment myWorld(seed, id, initialPreys, initialHunters);

    cout << "This is the setup of the environment for all the simulations" << endl;
    myWorld.display();

    char ch;

    return 0;
}

I would like to replace the Environment::display() function with:

void Environment::display() {
    int numPreys = numberPreys();
    int numHunters = numberHunters();

        HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
        // Remember how things were when we started
        CONSOLE_SCREEN_BUFFER_INFO csbi;
        GetConsoleScreenBufferInfo(hstdout, &csbi);

        cout << endl << endl;
        for (int j = 0; j < WORLDSIZEY; j++) {
            for (int i = 0; i < WORLDSIZEX; i++) {
                if (grid[i][j] == NULL) {
                    cout << "_";
                }
                else {
                    if (grid[i][j]->getType() == PREY) {
                        SetConsoleTextAttribute(hstdout, 10);
                    }
                    else if (grid[i][j]->getType() == HUNTER) {
                        SetConsoleTextAttribute(hstdout, 12);
                    }
                    cout << grid[i][j]->representation();
                    SetConsoleTextAttribute(hstdout, csbi.wAttributes);
                }
            }
            cout << endl;
        }
        cout << "Preys 'o': " << numPreys << " Hunters 'X': " << numHunters << endl;
        cout << "Timestep:" << timeStep << " World ID:" << id << endl;
    }

But then, the function doesn't display anything and the console window closes after a while.

My question: How can I call the counting functions inside of the display function?

EloyRD
  • 304
  • 3
  • 12
  • numberHunters is wrong. It returns numberPreys. – stark Jun 20 '18 at 20:55
  • @stark I'm correcting it in my main code also. Thanks – EloyRD Jun 20 '18 at 20:58
  • I shall add that the issue with the display() function not working is still there, – EloyRD Jun 20 '18 at 21:00
  • `if (grid[i][j]->getType() == PREY)` is also wrong. It should be `if (grid[i][j] && grid[i][j]->getType() == PREY)` . Same goes for the hunter count. – WhozCraig Jun 20 '18 at 21:07
  • @WhozCraig It already checked for NULL. I don't see a problem with this code. What happens the calls are moved below the display loop? – stark Jun 20 '18 at 21:30
  • ok let me put this another way. Unless **every** position in your grid is occupied with either a hunter, a prey, or some other derivation of agent, the code I stated in my prior comment *is broken*. you can choose to believe that or not; but it doesn't make it any less true either way. You invoke *undefined behavior* as soon as first null element is encountered. If `createOrganisms` gets a `count` that is not *precisely* worldsizey * worldsizex, that broken behavior is *guaranteed* on the first execution of either prey or hunter count calculation. – WhozCraig Jun 20 '18 at 21:36
  • @WhozCraig You are right. I was invoking the function correctly, but the function was wrong. I was filling the main code with lots of counters like this (without verifying for null), I'm double checking so that I am not making the same mistake. – EloyRD Jun 21 '18 at 03:54
  • Whit the sugested change implemmented it works like I intended – EloyRD Jun 21 '18 at 03:54
  • @EloyRD I expected as such. I rewrote the entire app to use smart pointers for better RAII, as well as some loop restructuring. It works well, so I think you're set. Best of luck. – WhozCraig Jun 21 '18 at 03:55
  • @WhozCraig Thanks for the solution. I'll check tutorials on those topics (smart pointers and loop structures) to see what I can improve. I'm learning a lot of C++ with this implementation, but my book of agent simulation with C is outdated. – EloyRD Jun 21 '18 at 04:04

0 Answers0