2D arrays and conway's game of life

Pages: 12
Write your question here.

[Hi all, I am brand new to programming but I am having trouble displaying a 2d array and inserting a character in a random location, which is the basic function of conway's game of life. I have used the rand() function before in a smaller, simpler program. and the "life" that I want to insert is a "U" made of ones in a box of zeros. like the example below. I just don't know how to make it so that it appears randomly, I tried making it a print where I just cout the "life" however I would like to make it a 2d array since when I tried to print it would not work. I tried watching a few youtube videos about it however I was not able to translate them properly to this code. Any small push in the right direction or hint would help, thank you in advance and I'm looking forward to learning more from you all and this website. I posted the functions I have so far above, it's not much but it's what I would like it to look like in the long run.

tldr, I want to make a "U" made of 1's appear randomly in a box of zeros.

000000000
010000010
010000010
010000010
010000010
010000010
011111110
000000000
]
Put the code you need help with here.
[/#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;

int main()
{
int MAX_COL = 60;
int MAX_ROW = 30;
int currentArray[30][60];
int tempArray[30][60];
int x = 0;
int y = 0;
int i = 0;
int j = 0;
// x/i is rows & y/j is columns //
}

int displayMenu()
{
cout << "[P]lay - Press 'P' to play." << endl;
cout << "[Q]uit - Press 'Q' to exit."
}

int setZeroArray ()
{
for (x=0;x<29, x++)
{
x= "0";
}
for (y=0, y<59,y++)
{

}
}

int setInitialPatternArray()
{
}

int copyArray()
{
}

int displayArray ()
{
}
]
Something like this, perhaps:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <iostream>
#include <cstdlib>
#include <ctime>

const std::size_t NROWS = 30 ;
const std::size_t NCOLS = 60 ;

// array_type is just another name for the type of the NROWSxNCOLS array of char
using array_type = char[NROWS][NCOLS] ;

void print( const array_type& array ) // note: array is passed by reference
{
    // http://www.stroustrup.com/C++11FAQ.html#for
    // http://www.stroustrup.com/C++11FAQ.html#auto
    for( const auto& row : array ) // for each row in the array
    {
        for( char c : row ) std::cout << c ; // print each character i the row
        std::cout << '\n' ;
    }
    std::cout << '\n' ;
}

void fill( array_type& array, char fill_char )
{
    for( auto& row : array ) // for each row in the array
        for( char& c : row ) // for each character in the row
            c = fill_char ;
}

// set a random U pattern at a (reasonably) random location
void set_pattern( array_type& array, char pattern_char )
{
    const std::size_t MIN_SZ = 5 ;

    // random width and height of the 'U' (adjust as required)
    const std::size_t height = std::rand() % (NROWS/2) + MIN_SZ ;
    const std::size_t width = std::rand() % (NCOLS/2) + MIN_SZ ;

    // choose a random location for the top left corner of the U
    std::size_t x = std::rand() % (NROWS/2-MIN_SZ) ;
    std::size_t y = std::rand() % (NCOLS/2-MIN_SZ) ;

    // place the two vertical lines
    for( std::size_t i = 0 ; i < (height-1) ; ++i )
        array[x+i][y] = array[x+i][y+width] = pattern_char ;

    // and the bottom line of the U
    for( std::size_t j = 0 ; j <= width ; ++j )
        array[x+height-1][y+j] =  pattern_char ;
}

void init( array_type& array )
{
    fill( array, '0' ) ; // fill with all '0'
    set_pattern( array, '1' ) ; // set the pattern using '1'
}

int main()
{
    std::srand( std::time(nullptr) ) ; // initialise the legacy rng

    array_type array ;
    init(array) ;
    print(array) ;
}

http://coliru.stacked-crooked.com/a/0fb1bd3705817338
Hi, thanks for replying.
wow that's really cool, I want to "U" to be the same size each time though, I think I can work it from here, I don't want to be spoon fed too much. Thanks again for the help.

edit: sorry I'm already stuck again, I'm trying to add a if then loop to either get the game to reset or terminate,

I declared

char pq;

and in the main function I have added

std::cout << "[P]lay - Press 'P' to play." << '\n';
std::cout << "[Q]uit - Press 'Q' to exit." << '\n';
std::cin >> pq;
if (pq = "p")
{ //and this is where I run the main program}

the problem I'm having is I'm not sure how to get it to work, I only have experience with numerical values for if hen loops and I'm not sure how to go about the syntax for assigning a 'p' or 'q' value for for 'pq' char to either play the game or quit, thanks in advance!
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <cctype>

char play_or_quit()
{
    std::cout << "[P]lay - Press 'P' to play.\n"
              << "[Q]uit - Press 'Q' to exit.\n"
              << "? " ;
    char pq ;
    std::cin >> pq;
    pq = std::tolower(pq) ;
    if( pq == 'p' || pq == 'q' ) return pq ;

    std::cout << "invalid input. try again\n" ;
    return play_or_quit() ; // try again
}

int main()
{
    if( play_or_quit() == 'p' )
    {
        std::cout << "play\n" ;
        // ...
    }
    else
    {
        std::cout << "quit\n" ;
        // ...
    }
}
JLBorges you truly are a life saver, I only now need it to loop each time 'P' is inputted, I was at least able to figure that out by myself, as well as make the size of the "u" constant, thank you so much.
I would like to take this program to the next level and have it so that the consectuive U's interact with one another, much in the same manner conway's game of life operates. so far I have this code
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <string>
#include <cctype>
const std::size_t MAX_ROW = 30 ;
const std::size_t MAX_COL = 60 ;
char pq;
using currentArray = char[MAX_ROW][MAX_COL] ;

void copyArray( const currentArray& array ) // array passed by reference
{

for( const auto& row : array ) // for each row in the array
{
for( char c : row ) std::cout << c ; // print each character i the row
std::cout << '\n' ;
}
std::cout << '\n' ;
}

void setNextGenArray ()
{

}

void setZeroArray( currentArray& array, char fill_char )
{
for( auto& row : array ) // each row in array
for( char& c : row ) // each character in row
c = fill_char ;
}

// set U at random location
void setInitialPatternArray( currentArray& array, char pattern_char )
{
const std::size_t MIN_SZ = 5 ;

// width and height of the 'U'
const std::size_t height = 5 + MIN_SZ ;
const std::size_t width = 7 + MIN_SZ ;

// random location for top left corner of U
std::size_t x = std::rand() % (MAX_ROW/2-MIN_SZ) ;
std::size_t y = std::rand() % (MAX_COL/2-MIN_SZ) ;

// place two vertical lines
for( std::size_t i = 0 ; i < (height-1) ; ++i )
array[x+i][y] = array[x+i][y+width] = pattern_char ;

// bottom line of U
for( std::size_t j = 0 ; j <= width ; ++j )
array[x+height-1][y+j] = pattern_char ;
}

void init( currentArray& array )
{
setZeroArray( array, '0' ) ; // fill with '0'
setInitialPatternArray( array, '1' ) ; // set the pattern using '1'
}
char displayMenu()
{
std::cout << "[P]lay - Press 'P' to play.\n"
<< "[Q]uit - Press 'Q' to exit.\n";
char pq ;
std::cin >> pq;
if( pq == 'p' || pq == 'q' ) return pq ;

std::cout << "invalid input. try again\n" ;
return displayMenu() ;
}

int main()
{
while ( displayMenu() == 'p' )
{
std::srand( std::time(nullptr) ) ;

currentArray array ;
init(array) ;
copyArray(array) ;
}
}

I would like the setNextGenArray function to "play" by the following rules which I wrote in pseudo code
// adjacent and diagonal cells are "neighbors"
Rules for "1" (living cells)
if (2-3 live "1" neighbors = lives to next generation (1 stays 1))
else (death (1 become 0))

Rules for "0" (dead cells)
if (3 live "1" neighbors = cell is bron (0 becomes 1))
else (no life (0 stays 0))

I would like each 'p" input to loop this function ten times and I tried putting in this for loop in the main function but I can't figure out where I can put it exactly for it to work the way I want.
for(int g=0; g < 3; g++)
Thanks in advance!
edit: I found a way for it to loop 3 times using an if loop and a new global variable "ng" for new generation. I'm still having trouble with the generation interaction and the rules for the game of life.
Last edited on
Something along these lines, perhaps:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <cstring>
#include <initializer_list>

constexpr int NROWS = 30 ;
constexpr int NCOLS = 60 ;
using array_type = char[NROWS][NCOLS] ;
enum liveness : char { DEAD = '0', ALIVE = '1', BAD_VALUE = 'B' };

void copy( const array_type& srce, array_type& dest ) // copy array_type from srce to dest
{
    // http://en.cppreference.com/w/cpp/string/byte/memcpy
    if( srce != dest ) std::memcpy( dest, srce, sizeof(array_type) ) ;
}

bool equal( const array_type& a, const array_type& b ) // true if contents of a and b are the same
{
    // http://en.cppreference.com/w/cpp/string/byte/memcpy
    return std::memcmp( a, b, sizeof(array_type) ) == 0 ;
}

// return the value of the character at array[row][col]
// return BAD_VALUE if either row or col is out of range
char get_value( const array_type& array, int row, int col )
{
    if( row < 0 || row >= NROWS || col < 0 || col >= NCOLS ) return BAD_VALUE ;
    else return array[row][col] ;
}

// return true if the cell array[row][col] is alive
bool is_alive( const array_type& array, int row, int col )
{ return get_value( array, row, col ) == ALIVE ; }

// return the count of live neighbours of array[row,col]
int count_live_neighbours( const array_type& array, int row, int col )
{
    int count = 0 ;

    // get the count for all live cells in the 3x3 segment with array[row][col] at the centre
    // http://en.cppreference.com/w/cpp/utility/initializer_list
    for( int delta_row : { -1, 0, +1 } ) for( int delta_col : { -1, 0, +1 } )
        if( is_alive( array, row+delta_row, col+delta_col ) ) ++count ;

    // the count now includes the liveness of array[row][col]; so adjust it
    if( is_alive( array, row, col ) ) --count ;

    return count ;
}

// move array to the next generation
// return true if steady state has not been reached, false otherwise
bool generate( array_type& array )
{
    array_type next_generation ; // note that there is no need to make this a global variable

    // for each cell in the array
    for( int row = 0 ; row < NROWS ; ++row ) for( int col = 0 ; col < NCOLS ; ++col )
    {
        const auto n = count_live_neighbours( array, row, col ) ;

        // Rules for "1" (living cells)
        // if (2-3 live "1" neighbours = lives to next generation (1 stays 1))
        // else (death (1 become 0))
        if( is_alive( array, row, col ) )
             next_generation[row][col] = ( n==2 || n==3 ) ? ALIVE : DEAD ;

        // Rules for "0" (dead cells)
        // if (3 live "1" neighbours = cell is born (0 becomes 1))
        // else (no life (0 stays 0))
        else next_generation[row][col] = n == 3 ? ALIVE : DEAD ;
    }

    if( equal( array, next_generation ) ) return false ; // reached steady state (no change)

    copy( next_generation, array ) ; // update to the next generation
    return true ;
}
I noticed there is no main function, if this meant to go inside the main function of my code? or outside of it and I call these functions in my main function?
It also does not have the functions which were there in the earlier post: fill, set_pattern, print etc.
It is just the part of the program that addresses this one aspect:
"the generation interaction and the rules for the game of life".

To make it a complete program, you would need to add these other functions and also write main().
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <string>
#include <cctype>
#include <cstring>
#include <initializer_list>

const std::size_t MAX_ROW = 30 ;
const std::size_t MAX_COL = 60 ;
char pq;
using currentArray = char[MAX_ROW][MAX_COL] ;
int ng;
constexpr int NROWS = 30 ;
constexpr int NCOLS = 60 ;
using array_type = char[NROWS][NCOLS] ;
enum liveness : char { DEAD = '0', ALIVE = '1', BAD_VALUE = 'B' };

void copyArray( const currentArray& array ) // array passed by reference
{

for( const auto& row : array ) // for each row in the array
{
for( char c : row ) std::cout << c ; // print each character i the row
std::cout << '\n' ;
}
std::cout << '\n' ;
}

void setNextGenArray ()
{

}

void setZeroArray( currentArray& array, char fill_char )
{
for( auto& row : array ) // each row in array
for( char& c : row ) // each character in row
c = fill_char ;
}

// set U at random location
void setInitialPatternArray( currentArray& array, char pattern_char )
{
const std::size_t MIN_SZ = 5 ;

// width and height of the 'U'
const std::size_t height = 5 + MIN_SZ ;
const std::size_t width = 7 + MIN_SZ ;

// random location for top left corner of U
std::size_t x = std::rand() % (MAX_ROW/2-MIN_SZ) ;
std::size_t y = std::rand() % (MAX_COL/2-MIN_SZ) ;

// place two vertical lines
for( std::size_t i = 0 ; i < (height-1) ; ++i )
array[x+i][y] = array[x+i][y+width] = pattern_char ;

// bottom line of U
for( std::size_t j = 0 ; j <= width ; ++j )
array[x+height-1][y+j] = pattern_char ;
}

void init( currentArray& array )
{
setZeroArray( array, '0' ) ; // fill with '0'
setInitialPatternArray( array, '1' ) ; // set the pattern using '1'
}
char displayMenu()
{
std::cout << "[P]lay - Press 'P' to play.\n"
<< "[Q]uit - Press 'Q' to exit.\n";
char pq ;
std::cin >> pq;
if( pq == 'p' || pq == 'q' ) return pq ;

std::cout << "invalid input. try again\n" ;
return displayMenu() ;
}


int main()
{
while (displayMenu() == 'p')
{

if (ng == 3) ng = 0;
while ( ng<3 )
{

std::srand( std::time(nullptr) ) ;
currentArray array ;
init(array) ;
copyArray(array) ;
void copy( const array_type& srce, array_type& dest ) // copy array_type from srce to dest
{
if( srce != dest ) std::memcpy( dest, srce, sizeof(array_type) ) ;
}

bool equal( const array_type& a, const array_type& b ) // true if contents of a and b are the same
{
return std::memcmp( a, b, sizeof(array_type) ) == 0 ;
}

// return the value of the character at array[row][col]
// return BAD_VALUE if either row or col is out of range
char get_value( const array_type& array, int row, int col )
{
if( row < 0 || row >= NROWS || col < 0 || col >= NCOLS ) return BAD_VALUE ;
else return array[row][col] ;
}

// return true if the cell array[row][col] is alive
bool is_alive( const array_type& array, int row, int col )
{ return get_value( array, row, col ) == ALIVE ; }

// return the count of live neighbours of array[row,col]
int count_live_neighbours( const array_type& array, int row, int col )
{
int count = 0 ;

// get the count for all live cells in the 3x3 segment with array[row][col] at the centre
// http://en.cppreference.com/w/cpp/utility/initializer_list
for( int delta_row : { -1, 0, +1 } ) for( int delta_col : { -1, 0, +1 } )
if( is_alive( array, row+delta_row, col+delta_col ) ) ++count ;

// the count now includes the liveness of array[row][col]; so adjust it
if( is_alive( array, row, col ) ) --count ;

return count ;
}

// move array to the next generation
// return true if steady state has not been reached, false otherwise
bool generate( array_type& array )
{
array_type next_generation ; // note that there is no need to make this a global variable

// for each cell in the array
for( int row = 0 ; row < NROWS ; ++row ) for( int col = 0 ; col < NCOLS ; ++col )
{
const auto n = count_live_neighbours( array, row, col ) ;

// Rules for "1" (living cells)
// if (2-3 live "1" neighbours = lives to next generation (1 stays 1))
// else (death (1 become 0))
if( is_alive( array, row, col ) )
next_generation[row][col] = ( n==2 || n==3 ) ? ALIVE : DEAD ;

// Rules for "0" (dead cells)
// if (3 live "1" neighbours = cell is born (0 becomes 1))
// else (no life (0 stays 0))
else next_generation[row][col] = n == 3 ? ALIVE : DEAD ;
}

if( equal( array, next_generation ) ) return false ; // reached steady state (no change)

copy( next_generation, array ) ; // update to the next generation
return true ;
}
ng++;
}

}
I tried integrating it into the mian function and incuded the int variables and added the libraries up top, I pretty much put it right in the main function since I wasn't sure how the new arrays would interact with the old ones but I'm getting an erro code on code:blocks on the copyArray function, It didnt give me that problem before I added the new code, is there some kind of interference with the functions that I'm not seeing?
Caveat: untested

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#include <iostream>
#include <cstring>
#include <initializer_list>
#include <cstdlib>
#include <ctime>

constexpr int NROWS = 30 ;
constexpr int NCOLS = 60 ;
using array_type = char[NROWS][NCOLS] ;
enum liveness : char { DEAD = '0', ALIVE = '1', BAD_VALUE = 'B' };

// declare the functions (used by main)
void print( const array_type& array ) ;

// initialise the array with all DEAD other than a random U pattern of ALIVE
void init( array_type& array ) ;

bool play_or_quit() ; // return true to continue playing

// move array forward by num_generations generations
// return true if steady state has not been reached, false otherwise
// note that cycles are ignored. ie. X => Y => Z => X returns true because Z != X
bool generate( array_type& array, int num_generations ) ;

int main()
{
    std::srand( std::time(nullptr) ) ; // initialise the legacy rng

    array_type array ;
    init(array) ;

    const int num_gens = 100 ;

    // repeatedly, print the array and generate the next num_gen generations
    // till either: the user wants to quit or the generations have reached steady state
    do print(array) ;
    while( play_or_quit() && generate( array, num_gens ) ) ;

    std::cout << "\nat the end:\n" ;
    print(array) ;
}

void print( const array_type& array ) // note: array is passed by reference
{
    // http://www.stroustrup.com/C++11FAQ.html#for
    // http://www.stroustrup.com/C++11FAQ.html#auto
    for( const auto& row : array ) // for each row in the array
    {
        for( char c : row ) std::cout << c ; // print each character i the row
        std::cout << '\n' ;
    }
    std::cout << '\n' ;
}

void fill( array_type& array, char fill_char )
{
    for( auto& row : array ) // for each row in the array
        for( char& c : row ) // for each character in the row
            c = fill_char ;
}

// set a random U pattern at a (reasonably) random location
void set_pattern( array_type& array, char pattern_char )
{
    const std::size_t MIN_SZ = 5 ;

    // random width and height of the 'U' (adjust as required)
    const std::size_t height = std::rand() % (NROWS/2) + MIN_SZ ;
    const std::size_t width = std::rand() % (NCOLS/2) + MIN_SZ ;

    // choose a random location for the top left corner of the U
    std::size_t x = std::rand() % (NROWS/2-MIN_SZ) ;
    std::size_t y = std::rand() % (NCOLS/2-MIN_SZ) ;

    // place the two vertical lines
    for( std::size_t i = 0 ; i < (height-1) ; ++i )
        array[x+i][y] = array[x+i][y+width] = pattern_char ;

    // and the bottom line of the U
    for( std::size_t j = 0 ; j <= width ; ++j )
        array[x+height-1][y+j] =  pattern_char ;
}

void init( array_type& array )
{
    fill( array, DEAD ) ; // fill with all DEAD
    set_pattern( array, ALIVE ) ; // set the pattern using ALIVE
}

bool play_or_quit()
{
    std::cout << "[P]lay - Press 'P' to play.\n"
              << "[Q]uit - Press 'Q' to exit.\n"
              << "? " ;
    char pq ;
    std::cin >> pq;

    if( pq == 'p' || pq == 'P' ) return true ;
    else if( pq == 'q' || pq == 'Q' ) return false ;

    std::cout << "invalid input. try again\n" ;
    return play_or_quit() ; // try again
}

void copy( const array_type& srce, array_type& dest ) // copy array_type from srce to dest
{
    // http://en.cppreference.com/w/cpp/string/byte/memcpy
    if( srce != dest ) std::memcpy( dest, srce, sizeof(array_type) ) ;
}

bool equal( const array_type& a, const array_type& b ) // true if contents of a and b are the same
{
    // http://en.cppreference.com/w/cpp/string/byte/memcpy
    return std::memcmp( a, b, sizeof(array_type) ) == 0 ;
}

// return the value of the character at array[row][col]
// return BAD_VALUE if either row or col is out of range
char get_value( const array_type& array, int row, int col )
{
    if( row < 0 || row >= NROWS || col < 0 || col >= NCOLS ) return BAD_VALUE ;
    else return array[row][col] ;
}

// return true if the cell array[row][col] is alive
bool is_alive( const array_type& array, int row, int col )
{ return get_value( array, row, col ) == ALIVE ; }

// return the count of live neighbours of array[row,col]
int count_live_neighbours( const array_type& array, int row, int col )
{
    int count = 0 ;

    // get the count for all live cells in the 3x3 segment with array[row][col] at the centre
    // http://en.cppreference.com/w/cpp/utility/initializer_list
    for( int delta_row : { -1, 0, +1 } ) for( int delta_col : { -1, 0, +1 } )
        if( is_alive( array, row+delta_row, col+delta_col ) ) ++count ;

    // the count now includes the liveness of array[row][col]; so adjust it
    if( is_alive( array, row, col ) ) --count ;

    return count ;
}

// move array to the next generation
// return true if steady state has not been reached, false otherwise
bool generate( array_type& array )
{
    array_type next_generation ; // note that there is no need to make this a global variable

    // for each cell in the array
    for( int row = 0 ; row < NROWS ; ++row ) for( int col = 0 ; col < NCOLS ; ++col )
    {
        const auto n = count_live_neighbours( array, row, col ) ;

        // Rules for "1" (living cells)
        // if (2-3 live "1" neighbours = lives to next generation (1 stays 1))
        // else (death (1 become 0))
        if( is_alive( array, row, col ) )
             next_generation[row][col] = ( n==2 || n==3 ) ? ALIVE : DEAD ;

        // Rules for "0" (dead cells)
        // if (3 live "1" neighbours = cell is born (0 becomes 1))
        // else (no life (0 stays 0))
        else next_generation[row][col] = n == 3 ? ALIVE : DEAD ;
    }

    if( equal( array, next_generation ) ) return false ; // reached steady state (no change)

    copy( next_generation, array ) ; // update to the next generation
    return true ;
}

bool generate( array_type& array, int num_generations )
{
    for( int n = 0 ; n < num_generations ; ++n )
        if( !generate(array) ) return false ;

    return true ;
}
I ran it on code::blocks, it seems to create the same "w" pattern it seems to be the second generation of the "u" 2d array.
EDITED

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <ctime>
#include <cstdlib>
#include <algorithm>
using namespace std;

using Array = vector<bool>;

//======================================================================

Array readFile( string filename, int &ROWS, int &COLS )
{
   string line;

   ifstream in( "conway.dat" );
   getline( in, line );
   stringstream( line ) >> ROWS >> COLS;
   Array A( ROWS * COLS, false );

   for ( int i = 0; i < ROWS; i++ )
   {
      getline( in, line );
      int len = line.size();
      for ( int j = 0; j < min( COLS, len ); j++ )
      {
         int n = COLS * i + j;
         A[n] = ( line[j] != ' ' );
      }
   }

   in.close();
   return A;
}

//======================================================================

Array randomU( int ROWS, int COLS )
{
   Array A( ROWS * COLS, false );

   bool RANDOM_POSITION = true;

   // Set size and position
   int width, height, left, bottom;
   if ( RANDOM_POSITION )
   {
      width  = 3 + rand() % ( COLS - 4 );
      height = width;
      left   = 1 + rand() % ( COLS - 1 - width  );
      bottom = 1 + rand() % ( ROWS - 1 - height );
   }
   else
   {
      width  = 7;
      height = width;
      left   = 5;
      bottom = 5;
   }

   // Set corresponding positions in array
   int i0 = ROWS - 1 - bottom;
   int j0 = left;
   for ( int i = i0; i >= i0 - ( height - 1 ); i-- )
   {
      int n = COLS * i + j0;
      A[n] = true;                     // left side of U
      A[n+width-1] = true;             // right side of U
   }
   for ( int j = j0; j <= j0 + ( width - 1 ); j++ )
   {
      int n = COLS * i0 + j;           // bottom of U
      A[n] = true;                     
   }
   return A;
}

//======================================================================

int livingNeighbours( const Array &A, int ROWS, int COLS, int ipos, int jpos )
{
   int alive = 0;
   int n;

   int i1 = max( ipos - 1, 0        );
   int i2 = min( ipos + 1, ROWS - 1 );
   int j1 = max( jpos - 1, 0        );
   int j2 = min( jpos + 1, COLS - 1 );

   // Sum over live elements in local array
   for ( int i = i1; i <= i2; i++ )
   {
      for ( int j = j1; j <= j2; j++ )
      {
         n = COLS * i + j;
         alive += A[n];
      }
   }

   // Correct for possibly counting original position
   n = COLS * ipos + jpos;
   alive -= A[n];

   return alive;
}

//======================================================================

void update( Array &A, int ROWS, int COLS )
{
   Array temp = A;

   for ( int i = 0; i < ROWS; i++ )
   {
      for ( int j = 0; j < COLS; j++ )
      {
         int adjacent = livingNeighbours( A, ROWS, COLS, i, j );
         int n = COLS * i + j;
         if (  ( A[n] && (adjacent<2||adjacent>3) )  ||  ( !A[n] && adjacent == 3 )  ) temp[n] = !A[n];
      }
   }

   A = temp;
}

//======================================================================

void print( int t, const Array &A, int ROWS, int COLS )
{
   cout << "\nGeneration: " << t << '\n';

   int n = 0;
   for ( int i = 0; i < ROWS; i++ )
   {
      for ( int j = 0; j < COLS; j++ ) cout << ( A[n++] ? '*' : ' ' );
      cout << '\n';
   }
}

//======================================================================

int main()
{
   int ROWS, COLS;
   srand( time( 0 ) );

   // Initialise by random U
   ROWS = 17, COLS = 17;
   Array A = randomU( ROWS, COLS );

   // Or initialise by file
// Array A = readFile( "conway.dat", ROWS, COLS );

   print( 0, A, ROWS, COLS );


   // Run for some generations

   const int NUM_GENERATIONS = 12;
   for ( int t = 1; t <= NUM_GENERATIONS; t++ )
   {
      update( A, ROWS, COLS );
      print( t, A, ROWS, COLS );
   }
}

//======================================================================  



If you want to try the period-3 "pulsar" instead, then change the lines in main() to read from file:
1
2
3
4
5
6
   // Initialise by random U
//ROWS = 17, COLS = 17;
// Array A = randomU( ROWS, COLS );

   // Or initialise by file
   Array A = readFile( "conway.dat", ROWS, COLS );

and use the following conway.dat file:
17  17
                
     o     o    
     o     o    
     oo   oo    
                
 ooo  oo oo  ooo
   o o o o o o  
     oo   oo    
                
     oo   oo    
   o o o o o o  
 ooo  oo oo  ooo
                
     oo   oo    
     o     o    
     o     o    
                
Last edited on
Hi lastchance, thank you for replying the pattern you made is exactly what I'm looking for. Unfortunately, I kind of mixed up my programs and I have one that doesn't really do much of anything. Do you think you could integrate it into the one I posted before? I really should do a better job with organizing my cpp files but I have a few different revisions that are largely unsuccessful. I don't particularly like been spoon fed but this is by far the largest code I have ever worked. Thank you so much in advance, everyone has been so helpful.
@sero_the_hero

I think that both @JLBorges and I have given demonstration code that can be adapted for your particular problem. They are somewhat different: @JLBorges retaining a 2-d char array that was closer to your original, mine from scratch using a 1-d vector<bool>.

It would be best if you studied the ideas imbedded in those codes - particularly breaking the problem down into much smaller subtasks, and structuring the code with suitable indentation and comments - and transferred any ideas to your own code.

If you continue to struggle then post your own code revisions - INSIDE CODE TAGS - and there are plenty of people on the forum who will help you with individual parts.

Note that, with the random size and position of the initial 'U' you will get different patterns every time you run the code - some are more regular than others. In my code I have adopted the convention that ghost points outside the boundaries are 'dead' - there are alternatives (e.g. invoking periodicity).
Last edited on
You are absolutely correct @lastchance, I suppose frustration got the better of me, I've never dealt with so many funcitons at once. I was able to figure it out, except the repeat three times part but I'm glad I got this far.

If you would be so kind as to throw a dog a bone here, I need to add functions to clear the array, save the array as a file, load a saved file all while maintaining the ability to play, I'll post what I have so far, It's largley just a repieced together code of what JLBorges put up but I understand it more as parts rather than the whole thing. If I somehow got this up and running it would be a christmas miracle.

#include <iostream>
#include <cstring>
#include <initializer_list>
#include <cstdlib>
#include <ctime>

constexpr int NROWS = 30 ;
constexpr int NCOLS = 60 ;
using array_type = char[NROWS][NCOLS] ;
enum liveness : char { DEAD = '0', ALIVE = '1', BAD_VALUE = 'B' };

// declare the functions (used by main)
void print( const array_type& array ) ;

// initialize the array with all DEAD other than a random U pattern of ALIVE
void init( array_type& array ) ;

bool play_or_quit() ; // return true to continue playing

// move array forward by num_generations generations
// return true if steady state has not been reached, false otherwise
bool generate( array_type& array, int num_generations ) ;

void print( const array_type& array ) // note: array is passed by reference
{
for( const auto& row : array ) // for each row in the array
{
for( char c : row ) std::cout << c ; // print each character i the row
std::cout << '\n' ;
}
std::cout << '\n' ;
}

void fill( array_type& array, char fill_char )
{
for( auto& row : array ) // for each row in the array
for( char& c : row ) // for each character in the row
c = fill_char ;
}

// set random U pattern at a (reasonably) random location
void set_pattern( array_type& array, char pattern_char )
{
const std::size_t MIN_SZ = 5 ;

// width and height of the 'U'
const std::size_t height = 5 + MIN_SZ ;
const std::size_t width = 7 + MIN_SZ ;

// choose a random location for the top left corner of the U
std::size_t x = std::rand() % (NROWS/2-MIN_SZ) ;
std::size_t y = std::rand() % (NCOLS/2-MIN_SZ) ;

// place the two vertical lines
for( std::size_t i = 0 ; i < (height-1) ; ++i )
array[x+i][y] = array[x+i][y+width] = pattern_char ;

// and the bottom line of the U
for( std::size_t j = 0 ; j <= width ; ++j )
array[x+height-1][y+j] = pattern_char ;
}

void init( array_type& array )
{
fill( array, DEAD ) ; // fill with all DEAD
set_pattern( array, ALIVE ) ; // set the pattern using ALIVE
}

bool play_or_quit()
{
std::cout << "[P]lay - Press 'P' to play.\n"
<< "[Q]uit - Press 'Q' to exit.\n"
<< "[I] - Press 'I' to load 'U' pattern.\n"
<< "[C]lear - Press 'C' Clear Array.\n"
<< "[S]top - Press 'S' to stop the game.\n"
<< "S[A]ve - Press 'A' to save the file"
<< "[L]oad - Press 'L' to load file"
char pq ;
std::cin >> pq;

if( pq == 'p' || pq == 'P' ) return true ;
else if( pq == 'q' || pq == 'Q' ) return false ;

std::cout << "invalid input. try again\n" ;
return play_or_quit() ; // try again
}

void copy( const array_type& srce, array_type& dest ) // copy array_type from srce to dest
{
if( srce != dest ) std::memcpy( dest, srce, sizeof(array_type) ) ;
}

bool equal( const array_type& a, const array_type& b ) // true if contents of a and b are the same
{

return std::memcmp( a, b, sizeof(array_type) ) == 0 ;
}

// return the value of the character at array[row][col]
// return BAD_VALUE if either row or col is out of range
char get_value( const array_type& array, int row, int col )
{
if( row < 0 || row >= NROWS || col < 0 || col >= NCOLS ) return BAD_VALUE ;
else return array[row][col] ;
}

// return true if the cell array[row][col] is alive
bool is_alive( const array_type& array, int row, int col )
{ return get_value( array, row, col ) == ALIVE ; }

// return the count of live neighbors of array[row,col]
int count_live_neighbours( const array_type& array, int row, int col )
{
int count = 0 ;

// get count for all live cells in the 3x3 segment with array[row][col] at the center
for( int delta_row : { -1, 0, +1 } ) for( int delta_col : { -1, 0, +1 } )
if( is_alive( array, row+delta_row, col+delta_col ) ) ++count ;

// the count now includes the liveness of array[row][col]; so adjust it
if( is_alive( array, row, col ) ) --count ;

return count ;
}

// move array to the next generation
// return true if steady state has not been reached, false otherwise
bool generate( array_type& array )
{
array_type next_generation ; // note that there is no need to make this a global variable

// for each cell in the array
for( int row = 0 ; row < NROWS ; ++row ) for( int col = 0 ; col < NCOLS ; ++col )
{
const auto n = count_live_neighbours( array, row, col ) ;

// Rules for "1" (living cells)
// if (2-3 live "1" neighbors = lives to next generation (1 stays 1))
// else (death (1 become 0))
if( is_alive( array, row, col ) )
next_generation[row][col] = ( n==2 || n==3 ) ? ALIVE : DEAD ;

// Rules for "0" (dead cells)
// if (3 live "1" neighbors = cell is born (0 becomes 1))
// else (no life (0 stays 0))
else next_generation[row][col] = n == 3 ? ALIVE : DEAD ;
}

if( equal( array, next_generation ) ) return false ; // reached steady state (no change)

copy( next_generation, array ) ; // update to the next generation
return true ;
}

bool generate( array_type& array, int num_generations )
{
for( int n = 0 ; n < num_generations ; ++n )
if( !generate(array) ) return false ;

return true ;
}
int main()
{
std::srand( std::time(nullptr) ) ; // initialize the legacy rng

array_type array ;
init(array) ;

const int num_gens = 100 ;

// repeatedly, print the array and generate the next num_gen generations
// till either: the user wants to quit or the generations have reached steady state
do print(array) ;
while( play_or_quit() && generate( array, num_gens ) ) ;
print(array) ;
}

PLEASE PUT YOUR CODE WITHIN CODE TAGS! It is nearly unreadable without.

You have the beginnings of a menu in your play_or_quit() routine:
1
2
3
4
5
6
cout << "[P]lay - Press 'P' to play.\n"
<< "[Q]uit - Press 'Q' to exit.\n"
<< "[I] - Press 'I' to load 'U' pattern.\n"
<< "[C]lear - Press 'C' Clear Array.\n"
<< "[S]ave - Press 'S' to save the file\n"
<< "[L]oad - Press 'L' to load file";

To be honest, I would simply put this in main().

According to whether the user entered P, (Q), I, C, S, or L you can then take the appropriate action - in most cases by calling a single function - using either a sequence of if ... else if, or by using a switch block. @JLBorges has already given you code for the functions responding to P, I, C and (effectively) the action on Q. You simply have the file reads and writes to do.

Pu the menu together, add (and test) one function at a time.

For switch or other control statements see
http://www.cplusplus.com/doc/tutorial/control/

For file input and output see
http://www.cplusplus.com/doc/tutorial/files/

Browsing through the answers to other questions on this forum will also teach you a huge amount.

I added the #include <fstream to the top of library list. and my main function noow looks like this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main()
{
    std::srand( std::time(nullptr) ) ; // initialize the legacy rng

    array_type array ;
    init(array) ;
    fstream textfile;
    ofstream myfile;

    const int num_gens = 100 ;

    // repeatedly, print the array and generate the next num_gen generations
    // till either: the user wants to quit or the generations have reached steady state
    do print(array) ;
    while( play_or_quit() && generate( array, num_gens ) ) ;
    myfileopen ("currentGoLArray.txt");
    myfile << "Write this to file.\n";
    myfile.close();
    return 0;

    print(array) ;
}

I'm trying to just get it to save without involing the switch statements yet just to see if I can get it running, I ran it on codeblocks but it said fstream was not declared, I had thought fstream was a declaration of a type of text file, what am I doing wrong?
For any file operations you will need header (at the top of your file)
#include <fstream>

Within the function that does some READING from file, e.g.
1
2
3
ifstream myfile( "currentGoLArray.txt" );     // open stream myfile, attached to the specified file
// various file-ops
myfile.close();


For the function that does some WRITING to file, use ofstream rather than ifstream. You should definitely read the tutorial
http://www.cplusplus.com/doc/tutorial/files/

You would be better doing these in separate routines, not in main().

I think you would also be better writing your menu first. Then you can add functions to it one by one.


Last edited on
oh I guess I misunderstood, I wrote a save function and plan on calling it in the main function, my save function looks like this:
1
2
3
4
5
6
7
8
int save()
{
    std::ofstream myfile;
    myfile.open ("currentGoLArray.txt");
    myfile << "Write this to file.\n";
    myfile.close();
    return 0;
}


It seems to compile fine, so in the main function should I use a switch statement to call it if "a" is pressed? I'm not sure how to go about doing that, would I have to make a change in the play_or_quit function? the play of quit function looks like this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bool play_or_quit()
{
    std::cout << "[P]lay - Press 'P' to play" << "            " << "[Q]uit - Press 'Q' to exit.\n"
              << "[I] - Press 'I' to load 'U' pattern" << "   " << "[C]lear - Press 'C' Clear Array.\n"
              << "[S]top - Press 'S' to stop the game"<< "  " << "S[A]ve - Press 'A' to save the file.\n"
              << "[L]oad - Press 'L' to load file.\n";
    char pq ;
    std::cin >> pq;

    if( pq == 'p' || pq == 'P' ) return true ;
    else if( pq == 'q' || pq == 'Q' ) return false ;

    std::cout << "invalid input. try again\n" ;
    return play_or_quit() ; // try again
}


If I understand it correctly, it acts as a bool T/F type of operation, so I would have to change that and incorporate a switch operation correct? assuming switch is the correct way to handle those different functions I want to incorporate.
Last edited on
Pages: 12