6
\$\begingroup\$

I made this mini life simulation thing. I got the idea from the Eloquent JavaScript textbook, and just kinda ran with it. I've been programming for a little less than year and this is probably the largest thing I've made to date, so I'm looking for a little feedback.

If there's anything you see that can be improved, please let me know.

main.cpp

#include <string>
#include <vector>
#include <algorithm>
#include <ctime>
#include <cstdlib>
#include <map>
#include <ncurses.h>

#include "terrariums.h"

/*==============================================================================
    CONSTANT GAME VARIABLES
==============================================================================*/

const bool ALLOW_DIAGONAL_DIRECTIONS = true;


const char EMPTY_SYM = ' ';
const char ROCK_SYM = '@';
const char DUMBBUG_SYM = 'o';
const char M_SMARTBUG_SYM = 'X';
const char F_SMARTBUG_SYM = 'x';
const char DUMBBUGEGG_SYM = 'e';
const char SMARTBUGEGG_SYM = 'a';
const char SMALLPLANT_SYM = '\'';

/*
   y|
    |
    |     o
    |_______
          x
*/

struct Vec2 {
    int x;
    int y;

    Vec2()
    {
        x = 0;
        y = 0;
    }
    Vec2(int _x, int _y)
    {
        x = _x;
        y = _y;
    }
};
Vec2 operator + (Vec2 u, Vec2 v)
{
    Vec2 result;
    result.x = u.x + v.x;
    result.y = u.y + v.y;

    return result;
}
void operator += (Vec2 &u, Vec2 v)
{
    u.x += v.x;
    u.y += v.y;
}
Vec2 operator * (Vec2 u, int s)
{
    Vec2 result;
    result.x = u.x * s;
    result.y = u.y * s;

    return result;
}
Vec2 operator *= (Vec2 &u, int s)
{
    u.x *= s;
    u.y *= s;
}

//directions map is global
enum Direction { n=0, ne=4, e=1, se=5, s=2, sw=6, w=3, nw=7 };
std::map<Direction, Vec2> directions;

enum Action { nothing, walk, walktofood, changedirection, eat, 
              layegg, grow, hatch, die };

/*
o               .        ___---___                    .
       .              .--\        --.     .     .         .
                    ./.;_.\     __/~ \.
                   /;  / `-'  __\    . \
 .        .       / ,--'     / .   .;   \        |
                 | .|       /       __   |      -O-       .
                |__/    __ |  . ;   \ | . |      |
                |      /  \\_    . ;| \___|
   .    o       |      \  .~\\___,--'     |           .
                 |     | . ; ~~~~\_    __|
    |             \    \   .  .  ; \  /_/   .
   -O-        .    \   /         . |  ~/                  .
    |    .          ~\ \   .      /  /~          o
  .                   ~--___ ; ___--~
                 .          ---         .              
*/

int findRowLength(std::string s) {

    int length = 1;
    for (int i = 0; s[i] != '\n'; i++)
    {
        length++;
    }
    return length;
}
int findColLength(std::string s, int rowLength) {

    int length = s.length() / rowLength;
    return length;
}

struct Terrarium
{
    std::string grid;
    int rowLength;
    int colLength;

    Terrarium(std::string s)
    {
        grid = s;
        rowLength = findRowLength(s);
        colLength = findColLength(s, rowLength);
    }

    // Grid char manipulation
    inline void changeCharAt(Vec2 location, char c)
    {
        grid[location.y * rowLength + location.x] = c;  
    }
    inline char charAt(Vec2 location)
    {
        return grid[location.y * rowLength + location.x];
    }

    // Find Surroundings
    std::map<Direction, char>
    findDirectSurroundings(Vec2 pos)
    {    
        std::map<Direction, char> surroundings;
        for (int i = 0; i < directions.size(); i++)
        {
            surroundings[(Direction)i] = (charAt(pos + directions[(Direction)i]));
        }
        return surroundings;
    }
    std::map<Direction, char>
    findExtendedSurroundings(Vec2 pos, 
                             int range, 
                             std::map<Direction, char> directSurroundings)
    {
        std::map<Direction, char> extendedSurroundings = directSurroundings;
        for (int i = 1; i < range; i++)
        {
            for (int j = 0; j < directions.size(); j++)
            {
                if (extendedSurroundings[(Direction)j] == EMPTY_SYM) {
                    extendedSurroundings[(Direction)j] = charAt(pos + (directions[(Direction)j] * (i+1)));
                }
            }
        }
        return extendedSurroundings;
    }

    // Life manipulation
    template <class T>
    void registerLife(const char SYM, std::vector<T> &lifeVect, T (*f)(Vec2))
    {

        for (int y = 0; y <= colLength; y++) {
            for (int x = 0; x <= rowLength; x++) {
                if (charAt(Vec2(x, y)) == SYM)
                    lifeVect.push_back((*f)(Vec2(x, y)));
            }
        }
    }
    template <class T>
    T newLife(T life)
    {
        changeCharAt(life.currentPos, life.sym);
        return life;
    }
    template <class T>
    inline void killLife(std::vector<T> &lifeVect, int i)
    {
        changeCharAt(lifeVect[i].currentPos, EMPTY_SYM);
        lifeVect.erase(lifeVect.begin() + i);
    }
};

/*==============================================================================
    P R E D I C A T E S
==============================================================================*/

bool predicate_MapValueIsSmallPlant(std::pair<Direction, char> m)
{
    return m.second == SMALLPLANT_SYM;
}

bool predicate_MapValueIsMaleSmartBug(std::pair<Direction, char> m)
{
    return m.second == M_SMARTBUG_SYM;
}

/*==============================================================================
    GENERAL BUG FUNTIONS
==============================================================================*/

bool canSupportBug(Terrarium &t, Vec2 pos)
{
    return (t.charAt(pos) == EMPTY_SYM);
}

template <class T>
void moveBug(Terrarium &t, T &b, Direction d)
{
    if (t.charAt(b.currentPos + directions[d]) != EMPTY_SYM)
        return;

    b.newPos += directions[d];

    t.grid[b.newPos.y * t.rowLength + b.newPos.x] = b.sym;
    t.grid[b.currentPos.y * t.rowLength + b.currentPos.x] = EMPTY_SYM;
    b.currentPos = b.newPos;
}


/*
          ,_    /) (\    _,
           >>  <<,_,>>  <<
          //   _0.-.0_   \\
          \'._/       \_.'/
           '-.\.--.--./.-'
           __/ : :Y: : \ _
   ';,  .-(_| : : | : : |_)-.  ,:'
     \\/.'  |: : :|: : :|  `.\//
      (/    |: : :|: : :|    \)
            |: : :|: : :;
           /\ : : | : : /\
          (_/'.: :.: :.'\_)
           \\  `""`""`  //
            \\         //
             ':.     .:'
*/

struct DumbBug {
    Vec2 currentPos;
    Vec2 newPos;
    int energy;
    char sym;

    std::map<Direction, char> surroundings;

    DumbBug(Vec2 pos)
    {
        currentPos = pos;
        newPos = pos;
        energy = rand() % 10 + 100;
        sym = DUMBBUG_SYM;
    }

    Action act()
    {
        if (energy <= 0)
            return die;

        else if (energy > 250)
            return layegg;

        else if ((std::find_if(surroundings.begin(), surroundings.end(), predicate_MapValueIsSmallPlant) != surroundings.end()) &&
                 energy <= 250)
            return eat;

        else
            return walk;
    }
};
DumbBug regDumbBug(Vec2 pos) { return DumbBug(pos); }

/*
       _---~~(~~-_.
     _{        )   )
   ,   ) -~~- ( ,-' )_
  (  `-,_..`., )-- '_,)
 ( ` _)  (  -~( -_ `,  }
 (_-  _  ~_-~~~~`,  ,' )
   `~ -^(    __;-,((()))
         ~~~~ {_ -_(())
                `\  }
                  { }
*/

const int SMARTBUG_SIGHT_DISTANCE = 8;
const int SMARTBUG_DIRECTION_CHANCE = 20; //   1/x chance of 
const int SMARTBUG_MAX_ENERGY = 450;      // changing direction

struct SmartBug {
    Vec2 currentPos;
    Vec2 newPos;
    Direction direction;

    int lifespan;
    int energy;
    char sym;

    std::map<Direction, char> directSurroundings;
    std::map<Direction, char> extendedSurroundings;

    SmartBug(Vec2 pos)
    {
      currentPos = pos;
      newPos = pos;
      energy = rand() % 50 + 300;
      lifespan = rand() % 300 + 1500;

      rand() % 2 ? sym = M_SMARTBUG_SYM : sym = F_SMARTBUG_SYM;

      direction = (Direction)(rand() % directions.size());
    }

    Action act(Terrarium &t)
    {
        if (energy <= 0 || lifespan <= 0)
            return die;

        else if ((std::find_if(directSurroundings.begin(), directSurroundings.end(), predicate_MapValueIsSmallPlant) != directSurroundings.end()) &&
                  energy <= SMARTBUG_MAX_ENERGY)
            return eat;

        else if (sym == F_SMARTBUG_SYM &&
                 (std::find_if(directSurroundings.begin(), directSurroundings.end(), predicate_MapValueIsMaleSmartBug) != directSurroundings.end()) &&
                 energy >= 400)
            return layegg;

        else
        {
            if (std::find_if(extendedSurroundings.begin(), extendedSurroundings.end(), predicate_MapValueIsSmallPlant) != extendedSurroundings.end())
                return walktofood;
            else
            {
                if (t.charAt(currentPos + directions[direction]) != EMPTY_SYM || rand() % SMARTBUG_DIRECTION_CHANCE == 0)
                    return changedirection;
                else
                    return walk;
            }
        }
    }
};
SmartBug regSmartBug(Vec2 pos) { return SmartBug(pos); }

/*
                   .-~-.
                 .'     '.
                /         \
        .-~-.  :           ;
      .'     '.|           |
     /         \           :
    :           ; .-~""~-,/
    |           /`        `'.
    :          |             \
     \         |             /
      `.     .' \          .'
        `~~~`    '-.____.-'
*/

struct DumbBugEgg
{
    Vec2 currentPos;
    int hatchTime;
    char sym;

    DumbBugEgg(Vec2 pos)
    {
        currentPos = pos;
        hatchTime = rand() % 150 + 150;
        sym = DUMBBUGEGG_SYM;
    }
    Action act()
    {
        if (hatchTime <= 0)
            return hatch;
        else
            return nothing;
    }

};
DumbBugEgg regDumbBugEgg(Vec2 pos) { return DumbBugEgg(pos); }

struct SmartBugEgg
{
    Vec2 currentPos;
    int hatchTime;
    char sym;

    SmartBugEgg(Vec2 pos)
    {
        currentPos = pos;
        hatchTime = rand() % 100 + 100;
        sym = SMARTBUGEGG_SYM;
    }

    Action act()
    {
        if (hatchTime <= 0)
            return hatch;
        else
            return nothing;
    }
};
SmartBugEgg regSmartBugEgg(Vec2 pos) { return SmartBugEgg(pos); }

/*
           ___..._
      _,--'       "`-.
    ,'.  .            \
  ,/:. .     .       .'
  |;..  .      _..--'
  `--:...-,-'""\
          |:.  `.
          l;.   l
          `|:.   |
           |:.   `.,
          .l;.    j, ,
       `. \`;:.   //,/
        .\\)`;,|\'/(
         ` `;;'' `(,
*/

const int SMALLPLANT_GROW_VALUE = 50;

struct SmallPlant {
    Vec2 currentPos;
    int energy;
    char sym;

    std::map<Direction, char> surroundings;
    int surroundingSmallPlants;

    SmallPlant(Vec2 pos) 
    {
      currentPos = pos;
      energy = 10;
      sym = SMALLPLANT_SYM;
    }

    void drainEnergy(int a, int b, int c, int d, int e)
    {
        if (surroundingSmallPlants <= a)
            energy += 3;
        else if (surroundingSmallPlants <= b)
            energy += 2;
        else if (surroundingSmallPlants <= c)
            energy += 1;
        else if (surroundingSmallPlants <= d)
            energy += -1;
        else if (surroundingSmallPlants <= e)
            energy += -3;
    }

    Action act(Terrarium t)
    {
        if (energy <= 0 || t.charAt(currentPos) == EMPTY_SYM)
            return die;
        else if (energy >= SMALLPLANT_GROW_VALUE)
            return grow;
        else 
            return nothing;
    }
};
SmallPlant regSmallPlant(Vec2 pos) { return SmallPlant(pos); }

bool canSupportSmallPlant(Terrarium &t, Vec2 pos)
{
    if (t.charAt(pos) != EMPTY_SYM)
        return false;

    std::map<Direction, char> surroundings = t.findDirectSurroundings(pos);

    int surroundingSmallPlants = 0;
    for (int i = 0; i < surroundings.size(); i++)
    {
        if (surroundings[(Direction)i] == SMALLPLANT_SYM)
            surroundingSmallPlants++;
    }

    if (surroundingSmallPlants < 2)
        return true;
    else
        return false;
}

/*==============================================================================
================================================================================
    M A I N   S T A R T S   H E R E
================================================================================
==============================================================================*/

int main() {

    //random seed
    srand(time(0));

    //declare directions
    directions[n] = Vec2( 0, -1);
    directions[e] = Vec2( 1,  0);
    directions[s] = Vec2( 0,  1);
    directions[w] = Vec2(-1,  0);

    if(ALLOW_DIAGONAL_DIRECTIONS) {

        directions[ne] = Vec2( 1, -1);
        directions[se] = Vec2( 1,  1);
        directions[sw] = Vec2(-1,  1);
        directions[nw] = Vec2(-1, -1);
    }

    Terrarium t(Terra::bigPlan);


    // Register life that starts in Terrarium
    std::vector<DumbBug> dumbBugs;
    t.registerLife(DUMBBUG_SYM, dumbBugs, regDumbBug);

    std::vector<SmartBug> smartBugs;
    t.registerLife(F_SMARTBUG_SYM, smartBugs, regSmartBug);
    t.registerLife(M_SMARTBUG_SYM, smartBugs, regSmartBug);

    std::vector<SmallPlant> smallPlants;
    t.registerLife(SMALLPLANT_SYM, smallPlants, regSmallPlant);

    std::vector<DumbBugEgg> dumbBugEggs;
    t.registerLife(DUMBBUGEGG_SYM, dumbBugEggs, regDumbBugEgg);

    std::vector<SmartBugEgg> smartBugEggs;
    t.registerLife(SMARTBUGEGG_SYM, smartBugEggs, regSmartBugEgg);

    // curses stuff
    initscr();
    raw();
    keypad(stdscr, true);
    noecho();
    timeout(0);
    curs_set(0);

    bool keepWinOpen = true;
    while (keepWinOpen) {

        // 'q' to quit
        int in = getch();
        if (in == 'q') {
            keepWinOpen = false;
        }

/*------------------------------------------------------------------------------
    DumbBug Behavior
------------------------------------------------------------------------------*/

        for (int i = 0; i < dumbBugs.size(); i++)
        {
            DumbBug* b = &dumbBugs[i];

            b->energy--;

            b->surroundings = t.findDirectSurroundings(b->currentPos);

            switch(b->act())
            {
                case die:
                {
                    t.killLife(dumbBugs, i);
                } break;

                case layegg:
                {
                    int r = rand() % directions.size();
                    if (canSupportBug(t, b->currentPos + directions[(Direction)r]))
                        dumbBugEggs.push_back(t.newLife(DumbBugEgg(b->currentPos + directions[(Direction)r])));

                    b->energy = 100;
                } break;

                case eat:
                {
                    for (int j = 0; j < b->surroundings.size(); j++) {
                        if (b->surroundings[(Direction)j] == SMALLPLANT_SYM) {
                            t.changeCharAt(b->currentPos + directions[(Direction)j], EMPTY_SYM);
                            j = b->surroundings.size();
                        }
                    }

                    b->energy += 20;
                } break;

                case walk:
                {
                    int r = rand() % (directions.size() + 6);
                    if (r < directions.size()) {
                        moveBug(t, *b, (Direction)r);
                    }
                } break;
            }
        }

/*------------------------------------------------------------------------------
    DumbBug Egg Behavior
------------------------------------------------------------------------------*/

        for (int i = 0; i < dumbBugEggs.size(); i++)
        {
            DumbBugEgg* e = &dumbBugEggs[i];
            e->hatchTime--;

            switch(e->act())
            {
                case hatch:
                {    
                    dumbBugs.push_back(t.newLife(DumbBug(e->currentPos)));
                    dumbBugEggs.erase(dumbBugEggs.begin() + i);
                } break;
            }
        }

/*------------------------------------------------------------------------------
    SmartBug Behavior
------------------------------------------------------------------------------*/

        for (int i = 0; i < smartBugs.size(); i++)
        {
            SmartBug* b = &smartBugs[i];

            b->energy--;
            b->lifespan--;

            b->directSurroundings = t.findDirectSurroundings(b->currentPos);
            b->extendedSurroundings = t.findExtendedSurroundings(b->currentPos,
                                                                 SMARTBUG_SIGHT_DISTANCE,
                                                                 b->directSurroundings);
            switch(b->act(t))
            {
                case die:
                {
                    t.killLife(smartBugs, i);
                } break;

                case eat:
                {
                    for (int j = 0; j < b->directSurroundings.size(); j++) {
                        if (b->directSurroundings[(Direction)j] == SMALLPLANT_SYM) {
                            t.changeCharAt(b->currentPos + directions[(Direction)j], EMPTY_SYM);
                            j = b->directSurroundings.size();
                        }
                    }
                    b->energy += 20;                    
                } break;

                case layegg:
                {
                    int r = rand() % directions.size();
                    if (canSupportBug(t, b->currentPos + directions[(Direction)r]))
                    {
                        smartBugEggs.push_back(t.newLife(SmartBugEgg(b->currentPos + directions[(Direction)r])));
                        b->energy = 100;
                    }

                } break;

                case walktofood:
                {
                    for (int j = 0; j < b->extendedSurroundings.size(); j++) {
                        if (b->extendedSurroundings[(Direction)j] == SMALLPLANT_SYM) {
                            moveBug(t, *b, (Direction)j);
                            j = b->extendedSurroundings.size();
                        }
                    }
                } break;

                case changedirection:
                {
                    b->direction = (Direction)(rand() % directions.size());
                } break;

                case walk:
                {
                    moveBug(t, *b, b->direction);
                }break;
            }
        }

/*------------------------------------------------------------------------------
    SmartBug Egg Behavior
------------------------------------------------------------------------------*/

        for (int i = 0; i < smartBugEggs.size(); i++)
        {
            SmartBugEgg* e = &smartBugEggs[i];
            e->hatchTime--;

            switch(e->act())
            {
                case hatch:
                {    
                    smartBugs.push_back(t.newLife(SmartBug(e->currentPos)));
                    smartBugEggs.erase(smartBugEggs.begin() + i);
                } break;
            }
        }


/*------------------------------------------------------------------------------
    SmallPlant Behavior
------------------------------------------------------------------------------*/

        for (int i = 0; i < smallPlants.size(); i++)
        {
            SmallPlant* p = &smallPlants[i];

            p->surroundings = t.findDirectSurroundings(p->currentPos);

            p->surroundingSmallPlants = 0;
            for (int j = 0; j < p->surroundings.size(); j++)
            {
                if (p->surroundings[(Direction)j] == SMALLPLANT_SYM)
                    p->surroundingSmallPlants++;
            }

            switch(p->act(t))
            {
                case die:
                {
                    t.killLife(smallPlants, i);
                } break;

                case grow:
                {
                    int r = rand() % directions.size();
                    if (canSupportSmallPlant(t, p->currentPos + directions[(Direction)r]))
                        smallPlants.push_back(t.newLife(SmallPlant(p->currentPos + directions[(Direction)r])));

                    p->energy = 10;
                } break;
            }

            if (ALLOW_DIAGONAL_DIRECTIONS)
            {
                p->drainEnergy(0, 3, 5, 7, 8);
            }
            else
            {
                p->drainEnergy(0, 1, 2, 3, 4);
            }
        }

        // Useful counter information
        static int bugsAlive = 0;
        static int prevBugsAlive = 0;
        static int totalBugs = 0;
        static int peakBugAmount = 0;

        prevBugsAlive = bugsAlive;
        bugsAlive = smartBugs.size();

        if (bugsAlive > prevBugsAlive)
            totalBugs += bugsAlive - prevBugsAlive;

        if (bugsAlive > peakBugAmount)
            peakBugAmount = bugsAlive;

        mvprintw(0, 0, t.grid.c_str());
        mvprintw(t.colLength + 1, 0, "Bugs Alive:   %i    ", bugsAlive);
        mvprintw(t.colLength + 2, 0, "Total Bugs:   %i", totalBugs);
        mvprintw(t.colLength + 1, 20, "Peak Bug Amout:   %i", peakBugAmount);
        if (smartBugs.size() > 0)
            mvprintw(t.colLength + 2, 20, "Bug1 Lifespan:    %i     ", smartBugs[0].lifespan);

        napms(35);
    }
    refresh();
    endwin();
}

terrariums.h

#ifndef TERRARIUMS_H
#define TERRARIUMS_H

#include <string>

namespace Terra 
{

std::string bigPlan = "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
                      "@''                      @@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
                      "@ ''                      @@     a @@@@@      @@@@@@\n"
                      "@                                @@@         @@@@@@@\n"
                      "@                                             '@@@ @\n"
                      "@                                           @@@ '@@@\n"
                      "@                                            @@   @@\n"
                      "@                                                  @\n"
                      "@                                                  @\n"
                      "@  @@@@                                            @\n"
                      "@   '@@@                           @@              @\n"
                      "@   @@   @@                       @@@              @\n"
                      "@@   @  @@@                         @@     @@@@@   @\n"
                      "@@       '@                        @@@'  @@@@@@@   @\n"
                      "@' @         @@                     @@@@@@@@@@     @\n"
                      "@     @@@                               @@@@       @\n"
                      "@      @                                           @\n"
                      "@                                                  @\n"
                      "@   a                                              @\n"
                      "@                                                  @\n"
                      "@                                                  @\n"
                      "@                     a                            @\n"
                      "@                                                  @\n"
                      "@      @                                           @\n"
                      "@'     @@@                      @@@@@@@            @\n"
                      "@  @@@@@@                      @@@'       @@@@@@   @\n"
                      "@@@@@@@@@@@@@       @@@             '@@@@@@     @  @\n"
                      "@''@@@@@@@@@@@     @@ @@@        @@@               @\n"
                      "@  '     @@@@@@@a@@@@@@@@                          @\n"
                      "@            '@@@@@@                               @\n"
                      "@             @'@@@@                               @\n"
                      "@           @@@ @@                                 @\n"
                      "@            @@  @'               '                @\n"
                      "@   '            @@                                @\n"
                      "@'               @@                                @\n"
                      "@''''                                              @\n"
                      "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n";


}
#endif //TERRARIUMS_H
\$\endgroup\$
4
  • \$\begingroup\$ Way too much code here fire me to critique while mobile, but I love those comments. Code should be fun to read, and this is. \$\endgroup\$
    – phyrfox
    Commented Apr 3, 2015 at 3:50
  • \$\begingroup\$ I dare say that ASCII art comments make it easy to look for a specific entity in the code. \$\endgroup\$
    – Morwenn
    Commented Apr 3, 2015 at 8:43
  • \$\begingroup\$ That was my main motivation. It Works especially well with sublime's mini map. \$\endgroup\$
    – djscrew
    Commented Apr 3, 2015 at 8:49
  • 1
    \$\begingroup\$ Can you use C++11 and/or C++14? This program seems to be written in C++03 and could greatly benefit from C++11 :) \$\endgroup\$
    – Morwenn
    Commented Apr 3, 2015 at 11:56

2 Answers 2

2
\$\begingroup\$

Here are a few other things you might consider changing:

Use scoped enumerations (enum class, C++11) if you can:

You have a few enums with some fairly common / easy to collide names, like the ones in Direction and Action. Those would be a lot better if scoped. With a C++11 enum class, you could do it:

enum class Direction { 
    n  = 0, 
    ne = 4, 
    e  = 1, 
    se = 5, 
    s  = 2, 
    sw = 6, 
    w  = 3, 
    nw = 7 
};

enum class Action { 
    nothing, 
    walk, 
    walkToFood, 
    changeDirection, 
    eat, 
    layEgg, 
    grow, 
    hatch, 
    die 
};

Then you access them with a scope, e.g.: Action::walk. Also, I would camelCase the constants for the multi-word ones to aid readability.

Then there are your global constants for the map symbols. Usually, the ALL_CAPS notation is reserved to use with preprocessor macros (AKA #defines). Most code these days use camelCase for typed constants as well. The all-uppercase notation of macros stands out more to indicate that the constant might behave in some unusual way / might be implemented using som preprocessor trick. Now back to the map symbols, I would also make those an enum class. The enumerator can be of any integer type, including char:

// Notice that we explicitly require this enum to be of size `char`
enum class Symbols : char { 
    empty       = ' ',
    rock        = '@',
    dumbBug     = 'o',
    mSmartBug   = 'X',
    fSmartBug   = 'x',
    dumbBugEgg  = 'e',
    smartBugEgg = 'a',
    smallPlant  = '\''
};

I personally also like to align the assignment operators to have a "visual handrail".

Use the constructor initialization list to init member data:

When initializing member data of a class or struct in a constructor, it is preferable to use the member initialization list. That will ensure the members are initialized only once, by calling its constructor. Example:

struct Terrarium
{
    Terrarium(const std::string & s)
        : grid(s)
        , rowLength(findRowLength(s))
        , colLength(findColLength(s, rowLength))
    { }
...

If you init the members using operator = in the body of the constructor, that might incur a duplicate initialization. First, the default constructor of the type will be implicitly called, then you will init again with the assignment.

Pass complex objects by reference when adequate:

I see that in many places you are passing complex objects such as std::strings by value. Remember that in C++ the default is always a copy. Take this function for instance:

int findColLength(std::string s, int rowLength) {
    int length = s.length() / rowLength;
    return length;
}

The string s is being copied just to have its length queried inside the function. You should really be taking that parameter by const reference:

int findColLength(const std::string & s, int rowLength) { /* ... */ }
                  ^^^^^             ^

Native/built-in types, such as ints and floats should still be passed by value. References only make sense when you want to avoid coping heavy objects or when you need to have an input-output function parameter.

inline is redundant inside a class/struct body:

When you declare and define a class/struct method directly inside the body, that method is already implicitly inline. Adding the keyword in this case is redundant verbosity. Template functions/classes are always inline, so they don't need the keyword either.

Take a look at the new <random> library:

C++11 added an awesome new pseudo-random number library that replaces std::rand. You might want to check it out and use it if your compiler supports it.

Avoid very long lines:

Excessively long lines are hard to follow and won't fit on most screens. Lets take this for instance:

if (std::find_if(extendedSurroundings.begin(), extendedSurroundings.end(), predicate_MapValueIsSmallPlant) != extendedSurroundings.end())
    return walktofood;

You should introduce some "explaining variables" there to shorten this statement and also clarify it:

auto smallPant = std::find_if(
    extendedSurroundings.begin(), 
    extendedSurroundings.end(), 
    predicate_MapValueIsSmallPlant);

const bool smallPlantNear = (smallPant != extendedSurroundings.end());
if (smallPlantNear) {
    return walktofood;
}

However, there are a few statements similar to this one, using std::find_if in one of the lists. So I would probably consider introducing a helper function that wraps the common operation(s).

\$\endgroup\$
2
  • \$\begingroup\$ Alright, one more question: Using an enum class made it so that every single time I had to compare with one of the constants I had to cast the enum to char. I decided to instead wrap the constants in a namespace, so now they are still organized under Symbols:: but they don't all require (char) in front of them anymore. Are there any reasons not to do this, or reason that an enum class would be better? \$\endgroup\$
    – djscrew
    Commented Apr 4, 2015 at 0:47
  • \$\begingroup\$ @djscrew, yes, that is one aspect of the enum class that sometimes annoys me too. Unfortunately, they have to be casted whenever you want to compare the values with literals. In this case, the C++ standard took type strictness very seriously. I'd say that if you are doing a lot of comparisons, then using an "oldschool" enum inside a namespace will be more elegant. Ultimately, it is up to you to decide if you want strong type strictness with more verbose casting or slacker type checking with less verbosity. \$\endgroup\$
    – glampert
    Commented Apr 4, 2015 at 2:13
2
\$\begingroup\$

Comments

The ASCII art comments are certainly apropos, given the purpose of the program. I find them amusing. The one labelling the axes is useful.

On the other hand, there isn't much of an explanation of how each simulated entity is supposed to behave. The only way to find out is to slog through the code.

Warnings

You should always strive to write code that compiles without warnings. Using clang++ -Wall, I get…

main.cpp:75:1: warning: control reaches end of non-void function [-Wreturn-type]
}
^

The method signature should be Vec2& Vec2::operator*=(int s), and it should return *this. Similarly, your += operator should return a Vec2& to support chaining.


main.cpp:570:20: warning: 5 enumeration values not handled in switch: 'nothing', 'walktofood', 'changedirection'... [-Wswitch]
            switch(b->act())
                   ^
main.cpp:617:20: warning: 8 enumeration values not handled in switch: 'nothing', 'walk', 'walktofood'... [-Wswitch]
            switch(e->act())
                   ^
main.cpp:642:20: warning: enumeration values 'nothing', 'grow', and 'hatch' not handled in switch [-Wswitch]
            switch(b->act(t))
                   ^
main.cpp:702:20: warning: 8 enumeration values not handled in switch: 'nothing', 'walk', 'walktofood'... [-Wswitch]
            switch(e->act())
                   ^
main.cpp:730:20: warning: 7 enumeration values not handled in switch: 'nothing', 'walk', 'walktofood'... [-Wswitch]
            switch(p->act(t))
                   ^

If the remaining cases are deliberately unhandled, then put a default: /* no-op */ at the end of the switch. If the remaining cases are impossible, then put a default: that either throws an exception or makes an assertion.


main.cpp:19:12: warning: unused variable 'ROCK_SYM' [-Wunused-const-variable]
const char ROCK_SYM = '@';
           ^
7 warnings generated.

You could just delete that line or comment it out.

Organization

Your main() is much too long. The event loop shouldn't contain all the logic for plants, dumb bugs, smart bugs, and their eggs. All of those should be subclasses of an Inhabitant virtual base class, whose act(Terrarium &t) method is polymorphic. In other words, each inhabitant object should autonomously do its thing at each time step.

\$\endgroup\$
3
  • \$\begingroup\$ Thanks for the feedback. If you don't mind my asking, what are the disadvantages of a long main function? \$\endgroup\$
    – djscrew
    Commented Apr 3, 2015 at 7:38
  • 2
    \$\begingroup\$ Readability, extensibility, maintainability, … basically all the usual benefits that one would hope to get from a code review. \$\endgroup\$ Commented Apr 3, 2015 at 7:42
  • \$\begingroup\$ Hm. Ok. I'll keep that in mind if I extend this much more. \$\endgroup\$
    – djscrew
    Commented Apr 3, 2015 at 8:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.