Skip to content

objectionary/game-of-life

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

John Conway's Game of Life 

badge

License: MIT standard-readme compliant workflow badge

This project aims to show that John Conway's Game of Life written in C++ using OOP features (inheritance, encapsulation, polymorphism) works slower than the same realization using EO programming language, the more general aim is to show that using EO language is more beneficial for projects that use many objects in terms of execution time.

Table of Contents

Background

According to previous research measurements showed that C++ realization to count Fibonacci numbers using objects work slowly, therefore, we decided to implement Game of Life. Fibonacci number counting is not sufficient enough because using objects for it's realization is something pulled out of a hat, because we can use a simple loop to count it.

    

Install

At the beginning you need to install BOOST library, and if you don't have g++, clang-format and clang-tidy. Like this, you can install everything:

  sudo apt install g++

  sudo apt-get install libboost-all-dev

  sudo apt-get install -y clang-tidy

  sudo apt install clang-format

In addition to this you need to install EOC from here.

Usage

To build project, just do:

$ make

Then you can choose which version you prefer more, fast_life or slow_life to start the game, if you choose fast_life just run:

$ ./fast_life --help

Otherwise:

$ ./slow_life --help

It will show you all the available options.

All options are the same to both realizations, so fast_life will be used.

For example, you can enter something like this:

$ ./fast_life --batch 20 --size 40x40 --put 3x6 --put 6x8 --put 12x9

This will run an automated game with 20 generations on the grid 40X40 with 3 initial alive cells.

If you want to clean the environment:

$ make clean

If you want to format all .cpp files (clang-format will be called to do so):

$ make fix

If you want to run tests for fast or slow version:

$ make fast_test

OR

$ make slow_test

If you want to see a beautiful game called Gosper glider gun run this:

./fast_life --batch 1000 --sleep 170 --size 40x40 --put 10x26 --put 11x26 --put 11x24 --put 12x14 --put 12x15 --put 12x23 --put 12x22 --put 12x36 --put 12x37 --put 13x13 --put 13x17 --put 13x22 --put 13x23 --put 13x36 --put 13x37 --put 14x2 --put 14x3 --put 14x12 --put 14x18 --put 14x22 --put 14x23 --put 15x2 --put 15x3 --put 15x12 --put 15x16 --put 15x18 --put 15x19 --put 15x24 --put 15x26 --put 16x12 --put 16x18 --put 16x26 --put 17x13 --put 17x17 --put 18x14 --put 18x15

If you want to see the infinite loop run:

./fast_life --batch 40 --sleep 500 --size 10x10 --put 5x4 --put 5x5 --put 5x6

If you want to see how EO version works:

$ eoc --alone dataize life size 3x3 put 2x1 put 2x2 put 2x3

Or you can type whatever arguments you want. For size option you need to type NxM where N is height and M is width of the grid. Then, you can pass some put AxB, that means that you put alive cell in position AxB. So the command will look like this:

$ eoc --alone dataize life size NxM put AxB put CxD put ExF put ... and so on.

If you decide to changed EO file and want to recompile it with new input. You need just skip --alone option, so the command will look like this:

$ eoc dataize life size NxM put AxB put CxD put ExF put ... and so on.

Structure

Structure of Slow version

There are some notable features of Cell and Field objects that should be mentioned: all for statements are replaced with recursion, objects are immutable, if we change something, we make a copy of the object and make changes using the constructor, so to change the current object we create new one with changes.


Object Field() stores the playing field and performs creation of the next generation.

Details. rec_line_print and rec_grid_print print the field. rec_line_print performs creation of the initial field. rec_live preforms creation of the next generation. with performs changing of the Cell in the position (x,y) by returning a new Field object with changed cell. count counts the number of alive cells for the cell with coordinates (x,y). live calls rec_live with special arguments.

class Field {
private:
  vector<vector<Cell>> grid;
  void rec_line_print(int depth);
  void rec_grid_print(int x, int y);

public:
  Field(int n, int m) : Field(make_grid(n, m)) {}
  Field(vector<vector<Cell>> g) : grid(g) {}

  vector<vector<Cell>> field();  // getters

  Field rec_add(Field cur, vector<pair<int, int>> s, int pos);
  Field rec_live(int x, int y, Field cur);

  static vector<vector<Cell>> make_grid(int n, int m);
  Field live();
  Field with(int x, int y, Cell a);
  void print();  // next_gen makers
  int count(int x, int y);
};


Object Cell() stores the state (alive/dead) of the cell. Method live takes an integer to decide whether it will be alive or dead in the next generation and returns Cell object as a result.

 

class Cell {

private:

  bool state;

public:

  Cell(bool st) : state(st) {}

  Cell() : Cell(false) {}  

  bool status() const;  

  Cell live(int cnt) const;  

};


Object Parse() performs parsing using BOOST library and checks arguments entered in command line.

class Parse {
private:
  int n = 100000;
  int m = 100000;
  vector<pair<int, int>> points;
  po::variables_map vm;

public:
  Parse() : Parse(nullptr) {}
  Parse(po::variables_map vmp) : vm(vmp) {} 

  int length() const;
  int width() const;
  vector<pair<int, int>> grid();
  po::variables_map opts(); 

  vector<pair<int, int>> rec_cells(int pos, vector<pair<int, int>> p);
  static bool has(const string &s, char c);
  static bool valid(string const &s);
  pair<int, int> point(const string &s) const;
  static pair<int, int> size(const string &s);
  static pair<int, int> split(const string &s);
  void positive();
  void cells();
  void build();  
};

Structure of Fast version

Structure

The Main object is Game( Grid( Size(), Field()) , *optional* Repeats())


Object Repeats() asks for the number of iterations and stores this value.

class Repeats {

public:

  int rep;

  Repeats();

};


Object Grid() stores the size of the grid and the playing field. In addition to this, it has methods to print the current state and move to the next iteration.

class Grid {

public:

  Size s;

  Field g;

  Grid(Size &st, Field &ff);

  void printGrid();

  void nextGen();

};


Object Size() asks for the size of the playing field and stores this value. Flag here is used to make it possible to ask for the size only once. 

class Size {

public:

  int n;

  int m;

  Size(){};

  Size(int x, int y);

};


Object Field() stores the the 2-D array of Cell(). Field() has methods to ask for initial alive cells and method to count the number of alive neighbor-cells.

class Field {

public:

  vector>> f;

  Field(){};

  Field(Size sz);

  void read_and_set(Size sz);

  int count(int x, int y, int sz);

};


Object Cell() stores the current state of the cell and state after moving to the new generation. Has methods to change and set values to its attributes.

Details: ChangeNewState method helps to remember the next generation state of this cell, but we can't change it at the moment, because if we change it, neighbor cells, which are haven't changed yet, will have no information about the current cell in the current generation, what is important for their state in the next generation. After we know all the new states of the cells, we can easily change old values to the new ones.

As mentioned before, newState is a variable that stores the state of the cell in the next generation during its creation. 

class Cell {

private:

  bool curState = false;

  bool newState = false;

public:

  void changeNewState(bool val);

  void changeCurState();

  void setState(bool val);

  bool getCurState() const;

};



Object Game() performs the game process with the interval of 2 seconds between generations.

class Game {

public:

  Game(Grid gr, Repeats rep, int time);

  Game(Grid gr);

};


Object Parse() performs console arguments parsing and checking.

class Parse {

public:

  Parse(){};

  static pair get_size(string const &s);

  static vector> get_alive(vector const &a, int n, int m);

};


Some objects are not shown because they simply check the correctness of input data and convert strings to ints. 

About

Experimental implementation of Conway's Game of Life in C++ and EO

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published