2
\$\begingroup\$

Write a program that creates a linked list of bunny objects. Each bunny object must have Sex: Male, Female (random at creation 50/50) color: white, brown, black, spotted age : 0-10 (years old) Name : randomly chosen at creation from a list of bunny names. radioactive_mutant_vampire_bunny: true/false (decided at time of bunny creation 2% chance of true)

At program initialization 5 bunnies must be created and given random colors. Each turn afterwards the bunnies age 1 year. So long as there is at least one male age 2 or older, for each female bunny in the list age 2 or older; a new bunny is created each turn. (i.e. if there was 1 adult male and 3 adult female bunnies, three new bunnies would be born each turn) New bunnies born should be the same color as their mother. If a bunny becomes older than 10 years old, it dies. If a radioactive mutant vampire bunny is born then each turn it will change exactly one non radioactive bunny into a radioactive vampire bunny. (if there are two radioactive mutant vampire bunnies two bunnies will be changed each turn and so on...) Radioactive vampire bunnies are excluded from regular breeding and do not count as adult bunnies. Radioactive vampire bunnies do not die until they reach age 50. The program should print a list of all the bunnies in the colony each turn along w/ all the bunnies details, sorted by age. The program should also output each turns events such as "Bunny Thumper was born! Bunny Fufu was born! Radioactive Mutant Vampire Bunny Darth Maul was born! Bunny Julius Caesar died! The program should write all screen output to a file. When all the bunnies have died the program terminates. If the bunny population exceeds 1000 a food shortage must occur killing exactly half of the bunnies (randomly chosen)

★ Modify the program to run in real time, with each turn lasting 2 seconds, and a one second pause between each announement.

★★ Allow the user to hit the 'k' key to initiate a mass rabit cull! which causes half of all the rabits to be killed (randomly chosen).

★★★★ Modify the program to place the rabits in an 80x80 grid. Have the rabits move one space each turn randomly. Mark juvenile males with m, adult males w/ M, juvenile females w/ f, adult femails w/ F radioactive mutant vampire bunnies with X

Modify the program so that radioactive mutant vampire bunnies only convert bunnies that end a turn on an adjacent square. Modify the program so that new babies are born in an empty random adjacent square next to the mother bunny. (if no empty square exits then the baby bunny isn't born)

I recently started learning C++ myself online and I have attempted to do the bunny beginner exercise here. I did not do the "run in real time" and "save each turn to a file" modification yet, but I did do everything else, including the grid.

Basically I am looking for any feedback, if at all. Some feedback I have seen so far on similar threads are:

  • Do not use "using namespace std"
  • Do not use rand(), instead use C++ 11 random ( it was too late for me to change)

Honestly any comment that would help, such as errors in way of doing things and ways to make things simpler would be much appreciated. It is a lot of lines in total, therefore I am thankful if you read through it.

Here are the codes:

main.cpp

#include <iostream>
#include <string>
#include <time.h>
#include "Bunny.h"
#include "listfunc.h"
#include <windows.h>
using namespace std;


int main() {
srand(time(NULL));
listfunc bunnylist;

for(int x=0; x<5; x++) {        // initialize 5 bunnies.
bunnylist.create();
}
bunnylist.echo();               // shows first list of bunnies
bunnylist.display();

char c;
cout<< "Enter 'P' to start the bunny simulation\n";
cin>> c;                        // allows user to "start"



while(c=='P') {
    system("cls");
    bunnylist.age();                    //first increase all bunnies age
    bunnylist.old();                    //second kill those that are too old
    bunnylist.radioinfect();            //third cause the radioactivity to spread
    bunnylist.baby();                   //fourth create bunny reproduction
    bunnylist.display();                //show the grid.
    bunnylist.event();                  //display what happened that turn

    if (bunnylist.whatnum()>1000) {     // if number of bunnies is more than 1000, kill half of the bunnies.
        bunnylist.cull();
    }

    cout<< "Enter 'P' again to continue, 'K' to cull half of all bunnies, 'E' to show list of bunnies, enter anything else to exit\n";
    cin>> c;

    while(c=='K' || c=='E') {
        if (c=='K')
            bunnylist.cull();
        if (c=='E')
            bunnylist.echo();

        cout<< "Enter 'P' again to continue, 'K' to cull half of all bunnies, 'E' to show list of bunnies, enter anything else to exit\n";
        cin>> c;
    }

}

}

Bunny.h

#ifndef BUNNY_H
#define BUNNY_H

#include <iostream>
#include <string>
#include <iomanip>
using namespace std;

class Bunny{
public:
    Bunny(int x, int y, int a);

    friend class listfunc;         // lets listfunc access Bunny

    string whatname();             // creating a list of bunny names

    bool ifradio();                // for deciding if bunny is radioactive or not

    string whatcolor();            // for outputting color in text

    void echo();                   // reading out the bunny's parameters

private:
    bool male;                     // is it male
    int color;                     // color represented by int
    int age;
    string name;
    bool radio;                    // is it radioactive
    Bunny *next;
    int X, Y;                      // bunny's coordinates on grid.

};

#endif // BUNNY_H

Bunny.cpp

#include "Bunny.h"

Bunny::Bunny(int x, int y, int a)
:male(rand()% 2), color(a), age(0), name(whatname()), radio(ifradio()), next(NULL), X(x), Y(y) {}

string Bunny::whatname() {             // creating a list of bunny names
string names[14] = {"Thumper", "Oreo", "Bunn", "Coco", "Cinnabun", "Snowball", "Bella",
                    "Midnight", "Angel", "Shadow", "Hops", "Bugs", "Floppy", "Whiskers"};
return names[rand()%14];
}

bool Bunny::ifradio() {
int a = rand() % 50;                         // 1/50 chance
return ((a==0) ? true : false);
}

string Bunny::whatcolor() {                  // for outputting 1 of 4 colors.
    int a = color;
    switch(a) {
        case 1:
            return "white";
            break;
        case 2:
            return "brown";
            break;
        case 3:
            return "black";
            break;
        case 4:
            return "spotted";
            break;
    }
}

void Bunny::echo() {
cout <<setw(7)<<left<< ( (male) ? "Male" : "Female" ) <<setw(8)<< whatcolor() << age << " " <<setw(9)<< name << ((radio) ? "Radioactive" : "Normal") << "\n";
}

listfunc.h

#ifndef LISTFUNC_H
#define LISTFUNC_H

#include <iostream>
#include <string>
#include "Bunny.h"

class listfunc {
public:
    listfunc()
    : head(NULL), tail(NULL), num(0), radionum(0) {
        for (int x=0; x<Xmax; x++) {
            for (int y=0; y<Ymax; y++) {
                grid[x][y] = blank;            //initialise the grid
            }
        }
    }

    void create( int x= rand()%Xmax, int y= rand()%Ymax,int a = rand()%4+1 );        //create new bunny, parameter is for color.

    void echo ();                             //output bunny stats

    void baby();                              //bunny reproduction function

    void age();                               //increase all age by 1, aka one turn

    void old();                               //kill all bunnies that are too old

    void kill(Bunny *a);                      //the function used to kill any particular bunny

    void cull();                              //function to kill half of all bunnies

    void radioinfect();                       //function to infect one normal bunny for every radioactive bunny there is

    void display() const;                     //outputs the grid

    void movebunny(Bunny *a);                 //move the specific bunny a random position.

    bool checkif(Bunny *a, int b, char c) const;    //a function to check if the adjacent position is a particular char.

    void event();                             //a function to echo out the events of previous turn.

    int whatnum() {
        return num;                           // function to read num
    }



private:
    Bunny *head, *tail;
    int num, radionum; // bunny and radioactive counter
    const char blank = '.', jmale = 'm', jfemale = 'f', amale='M', afemale= 'F', radiob = 'X';   // for managing the grid legend.
    static const int Xmax = 40, Ymax = 100; // set the size of the grid.
    char grid[Xmax][Ymax];

    int eventradio;                  // keeps track of how many radioactive bunnies infected per turn
    string eventborn, eventdead;     // keeps track of the bunnies dead or born per turn.

};


#endif // LISTFUNC_H

listfunc.cpp

#include "listfunc.h"

void listfunc::create(int x, int y, int a) {     // create new Bunny, if no input is given, randomly assign 1-4 for color.
    while(grid[x][y] != '.') {                   // check if spot is taken, check really only required for first 5 bunnies.
        x = rand()%Xmax; y = rand()%Ymax;           // randomly choose new spot
    }
    Bunny *temp = new Bunny(x, y, a);
    temp->next= NULL;
    if (head==NULL) {
        head=temp;
        tail=temp;
    }
    else {
        tail->next=temp;
        tail = temp;
    }
    if(temp->radio) {
    eventborn.append("Radioactive bunny "); eventborn.append(temp->name); eventborn.append(" was born!\n");        // write to the events log.
    radionum++;
    grid[x][y] = radiob;
    }
    else {
    (temp->male) ? grid[x][y] = jmale : grid[x][y] = jfemale;
    eventborn.append("Bunny "); eventborn.append(temp->name); eventborn.append(" was born!\n");
    }
    num++;
}

void listfunc::echo () {               // for outputting the bunny stats.
    cout<< "Current bunny count is " << num << " with " << radionum << " radioactive bunnies!\n\n" <<"SEX    COLOR  AGE NAME    RADIOACTIVE?\n";
    Bunny *temp = head;
    while(temp!=NULL) {
        temp->echo();
        temp = temp->next;
    }
    cout<< "\n";
}

void listfunc::baby() {                 // for bunny reproduction.
    Bunny *temp = head;                 // sets temp back to head
    int x=0;                            // counter for male/female
    while(temp!=NULL && x==0) {         // search for at least 1 male aged >1.
        if(temp->male == true && temp->age>1 && temp->radio==false) {
            x++;
        }
        else {
            temp=temp->next;
        }
    }
    if(x>0) {                           //if at least 1 male
        temp=head;                      //sets temp back to head
        while (temp!= NULL) {
            if(temp->male == false && temp->age>1  && temp->radio==false) { // when temp points to a female Bunny aged >1
                if (checkif(temp, 1, blank) || checkif(temp, 2, blank) || checkif(temp, 3, blank) || checkif(temp, 4, blank)) {  //if at least one adjacent square is blank.
                    int w = 0;
                    while (w<1) {                                                 // if no bunnies is created.
                        int z = (rand()%4+1);                                     // random direction.
                        if(checkif(temp, z, blank)) {                             // if random direction is empty, create bunny in that direction. else repeat loop with different random number.
                            switch(z) {
                                case 1:
                                    create(temp->X-1, temp->Y, temp->color);
                                    break;
                                case 2:
                                    create(temp->X+1, temp->Y, temp->color);
                                    break;
                                case 3:
                                    create(temp->X, temp->Y-1, temp->color);
                                    break;
                                case 4:
                                    create(temp->X, temp->Y+1, temp->color);
                                    break;
                        }
                        w++;
                    }
                    }
                }
            }
        temp=temp->next;
        }
    }
}

void listfunc::age() {                               //increases all age by 1.
    Bunny *temp=head;
    while (temp != NULL) {
        temp->age++;
        if (temp->radio==false) {                    // if a non-radioactive turns 2, they will be represented by capital letter on the grid.
            if (temp->male && temp->age == 2) {
                grid[temp->X][temp->Y] = amale;
            }
            else if (temp->male == false && temp->age == 2) {
                grid[temp->X][temp->Y] = afemale;
            }
        }
        listfunc::movebunny(temp);                   // bunny moves randomly every turn.
        temp=temp->next;
    }
}

void listfunc::old() {                 //kill those that are too old
    Bunny *temp=head;
    Bunny *tokill;
    while (temp != NULL) {
        if(temp->radio) {              // radioactive bunny dies at age 50
            if(temp->age == 50) {
                tokill=temp;           //point tokill to the object to be killed.
                temp=temp->next;       //move temp to next object.
                kill(tokill);
            }
            else {
                temp=temp->next;       // if no one is to be killed, move temp to next object either way.
            }
        }
        else {
            if (temp->age == 10) {     // normal bunny dies at age 10
                tokill=temp;           // same procedure as above
                temp=temp->next;
                kill(tokill);
            }
            else {
                temp=temp->next;
            }
        }
    }
}

void listfunc::kill(Bunny *a) {      // for deleting a certain bunny
    Bunny *temp=head;
    if(a==head) {                    // deal with the head first.
        head=head->next;             //change head to next in line
    }
    else if (a==tail) {              //if its the last bunny
        while(temp->next !=tail) {
            temp=temp->next;
        }
        tail=temp;
        temp->next= NULL;
    }
    else{                            // if somewhere in between
        while(temp->next != a) {
            temp=temp->next;
        }
        temp->next= a->next;         // link object before a to object after a
    }
    if(a->radio) {
        eventdead.append("Radioactive bunny "); eventdead.append(a->name); eventdead.append(" has died.\n");      // write to the events log.
        radionum--;
    }
    else {
        eventdead.append("Bunny "); eventdead.append(a->name); eventdead.append(" has died.\n");
    }
    grid[a->X][a->Y] = blank;
    delete a;                        // delete a (aka kill)
    num--;                           // reduce bunny count
}

void listfunc::cull() {                 // function to kill half of all bunnies randomly.
    Bunny *temp=head, *tokill;
    int killnum = num/2, x=0;
    while(x < killnum) {                // trying to make a loop that doesn't break until 1/2 rabbits are killed.
        while(temp != NULL) {
            if (rand()%2) {             // 50/50 chance to kill that bunny
                tokill=temp;
                temp=temp->next;
                kill(tokill);
                x++;                    //increase kill counter if a bunny is culled
                if (x==killnum) {
                    break;
                }
            }
            else
                temp=temp->next;
        }
        temp=head;                     // go to start of list again if not enough bunnies are killed

    }

}


void listfunc::radioinfect() {                    //function to for one radio bunny to infect other bunnies
    Bunny *temp=head;
    int x=0;                                      // infected counter
    while(temp != NULL) {                         // search through all the bunnies to find radioactive bunnies.
        if (temp->radio) {
            if ((checkif(temp, 1, blank) == false && checkif(temp, 1, radiob) == false && temp->X != 0) || (checkif(temp, 2, blank) == false && checkif(temp, 2, radiob) == false && temp->X != Xmax-1) || // check if adjacent has a
                (checkif(temp, 3, blank) == false && checkif(temp, 3, radiob) == false && temp->Y != 0) || (checkif(temp, 4, blank) == false && checkif(temp, 4, radiob) == false && temp->Y != Ymax-1) ) { // non-radioactive bunny.
                    bool looping = true;
                    while(looping) {                                                                   // if no bunny has been infected yet, looping won't continue.
                        int z = (rand()%4+1);                                                          // chose one of 4 directions.
                        if ( (checkif(temp, z, blank) == false) && (checkif(temp,z,radiob) == false)) {     // checking if that direction is NOT a radioactive bunny or a blank space
                            switch(z) {
                                case 1:
                                    if (temp->X != 0) {                                                // required because checkif function will also return false if checking a position out of the grid.
                                        grid[temp->X-1][temp->Y] = radiob;                             // all conditions satisfied, set that grid to a radioactive bunny first.
                                        looping = false;
                                    }
                                    break;
                                case 2:
                                    if (temp->X != Xmax-1) {
                                        grid[temp->X+1][temp->Y] = radiob;
                                        looping = false;
                                    }
                                    break;
                                case 3:
                                    if (temp->Y != 0) {
                                        grid[temp->X][temp->Y-1] = radiob;
                                        looping = false;
                                    }
                                    break;
                                case 4:
                                    if (temp->Y != Ymax-1) {
                                        grid[temp->X][temp->Y+1] = radiob;
                                        looping = false;
                                    }
                                    break;
                            }
                            if (looping==false) {
                                x++; radionum++;
                            }

                        }
                    }
            }
        }
        temp = temp->next;                           // go next bunny is current bunny is not radioactive.
    }

    temp = head;
    while(temp!=NULL) {
        if (grid[temp->X][temp->Y] == radiob)        // set all bunnies that is labeled as radioactive on the grid to radioactive.
            temp->radio=true;
        temp=temp->next;
    }
    eventradio = x;
}

void listfunc::display() const {                    // to display the rabbits in a grid
    for (int x=0; x<Xmax; x++) {
        for (int y=0; y<Ymax; y++) {
            cout<< grid[x][y];
        }
        cout << "\n";
    }

}

void listfunc::movebunny(Bunny *a) {
    int z = rand()%5+1;
    char old = grid[a->X][a->Y];
    if (checkif(a,z,blank)) {       //first check if direction given by z is clear or not.
    switch(z) {
        case 1:                      // move up
                grid[a->X][a->Y]= blank;                      // swapping places with one position above on the grid.
                grid[a->X-1][a->Y]= old;
                a->X--;                                     // change bunny internal X coordinate value.
        break;
        case 2:                      // move down
                grid[a->X][a->Y]= blank;                      // swapping places with one position below on the grid.
                grid[a->X+1][a->Y]= old;
                a->X++;                                     // change bunny internal X coordinate value.
        break;
        case 3:                      // move left
                grid[a->X][a->Y]= blank;
                grid[a->X][a->Y-1]= old;
                a->Y--;
        break;
        case 4:                      // move right
                grid[a->X][a->Y]= blank;
                grid[a->X][a->Y+1]= old;
                a->Y++;
        break;
        case 5:                      // stay still
        break;
    }
    }
    else this->movebunny(a);                        // if conditions don't meet, retry movement.
}

bool listfunc::checkif(Bunny *a, int b, char c) const {
switch (b) {
        case 1:                      // check up
            if( grid[a->X-1][a->Y] == c  && a->X != 0 ) {    // checking if one position upwards is char specified, and current X is not at highest point.
            return true;
            }
            else return false;
        break;
        case 2:                      // check down
            if( grid[a->X+1][a->Y] == c  && a->X != Xmax-1 ) {
            return true;
            }
            else return false;
        break;
        case 3:                      // check left
            if( grid[a->X][a->Y-1] ==  c && a->Y != 0  ) {
            return true;
            }
            else return false;
        break;
        case 4:                      // check right
            if( grid[a->X][a->Y+1] == c  && a->Y != Ymax-1 ) {
            return true;
            }
            else return false;
        break;
        case 5:                      // movebunny() has 5 as a possible choice.
            return true;
        break;
}
}

void listfunc::event() {
cout<< "Events List\n\n";
cout<< eventdead << "\n";
cout<< eventborn << "\n";
cout<< eventradio << " bunnies were infected by radioactive bunnies!\n\n";
eventdead.clear();
eventborn.clear();
}

Also, the exercise requires a linked list. However is std::vector a much better option?

\$\endgroup\$
  • 1
    \$\begingroup\$ If you need a linked list you can use std::list or std::forward_list. \$\endgroup\$ – Null Jan 17 at 17:17
  • 1
    \$\begingroup\$ It might be nice if the indentation in main() was fixed. \$\endgroup\$ – pacmaninbw Jan 17 at 17:47
  • \$\begingroup\$ A suggestion for further learning: When you have it perfect, rewrite it without regard to the given internal details, just as it should have been to provide the requested output. Yes, that should mean using std::vector (and std::string_view) among others. \$\endgroup\$ – Deduplicator Jan 18 at 20:20
5
\$\begingroup\$

This question is tagged C++ but I would say that the code looks more like C with classes.
STL offers you a lot of functionality you could use.


Use STL instead of providing your own implementation

Trust me, folks who wrote the standard library have a lot of experience, it was tested really hard and it's (almost always) more efficient than what you'd write yourself.

  • Use std::array instead of char grid[Xmax][Ymax] if Xmas/Ymas is known at the compile time, otherwise use std::vector
  • Do not create your own implementation of a linked list. Use std::list instead.
  • Smart pointers are your friend. Managing memory is highly error-prone. Take a look at std::unique_ptr or std::shared_ptr instead of using Bunny *temp = new Bunny(x, y, a);

Performance improvements

Although recent compilers are really efficient in removing unnecessary creation/copying, still they are not perfect yet.

string names[14] = {"Thumper", "Oreo", "Bunn", "Coco", "Cinnabun", "Snowball", "Bella",
                    "Midnight", "Angel", "Shadow", "Hops", "Bugs", "Floppy", "Whiskers"};

Names array above is created each time the function is executed. This has some performance penalties if compiler optimization does not kick in. Consider marking it as static to create a single array that lasts till you die.


Clarity improvements

Formatting

This one is really important to me personally. If you ever read loads of code after somebody else, this gets quite serious real quick. It helps you keep on the track with the code, helps you better understand what it does and increases your effectiveness.

I am used to camelCase formatting, but whichever you choose, stay consistent. Take for example the following code

if (<some really long code here with lots of ballast everywhere>){
if (<some more>){
auto k = can();
    while(k){
  doFoo();
  k = can();} }}

is really hard to read. But if you rewrite it as follows:

if (<first> && <second>)
{
    auto keepRunning = canContine();
    while (keepRunning)
    {
         doFoo();
         keepRunning = canContinue();
    }
}

It is more readable and apparent what you are trying to accomplish. This is just an example so it might not make sense but you get the point.

Naming

I know that naming variables a, b, c... are faster to write but you'll get lost pretty quickly if you try to read it later. Using keepRunning in the above example instead of k made it more clear what you're trying to do and what is the purpose of that variable.


Yes, I know. It isn't much but should you get started at last. There are many more which I did not include but are the ones I consider quite important. Wish you best luck :)

\$\endgroup\$
  • \$\begingroup\$ Thank you, I didn't know about std::array, std::list, as well as the purpose of static. Will definitely use it in future exercises. \$\endgroup\$ – Name Is HK Jan 18 at 9:30
  • \$\begingroup\$ Using std::array is a bit more verbose. I would generally refrain, unless I have to return by value, copy, assign, and the like. \$\endgroup\$ – Deduplicator Jan 18 at 20:23
  • \$\begingroup\$ @Deduplicator It depends. If the size of the array is known at the compile-time, there are certain advantages while using std::array because it's often more efficient, especially for small sizes, because in practice it's mostly a lightweight wrapper around a C-style array. However, it's more secure, since the implicit conversion to pointer is disabled, and it provides much of the STL-related functionality of std::vector and of the other containers, so you can use it easily with STL algorithms & co. \$\endgroup\$ – Quest Jan 19 at 12:50
  • \$\begingroup\$ std::begin(), std::end(), and std::size() work with native arrays. Pointer-decay being disabled I don't see as an issue either way. \$\endgroup\$ – Deduplicator Jan 19 at 14:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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