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