Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

viti

Frischling

  • »viti« ist der Autor dieses Themas
  • Private Nachricht senden

1

23.12.2014, 14:13

c++: 'Game of Life' - mein erster Versuch

Ich lese grad das Buch 'c++ für Spieleprogrammierer' von Heiko Kalista. Bin noch ziemlicher Anfänger (lerne seit ca. 4 Wochen c++ von Null auf), hab die Konsole noch nicht verlassen und bin grad dabei, mich mit der STL zu beschäftigen.

Auf der Suche nach zusätzlichem Übungsmaterial bin ich auf das 'game of life' vom Mathematiker John H. Conway gestossen. Ich dachte, das sei von der Schwierigkeit her grad etwa richtig, bin am Ende aber sicher acht-zwölf Stunden drangesessen (ich denke, es funktioniert jetzt so weit korrekt). In diesem kleinen Programm steckt ziemlich alles, was ich vom technischen her bisher kann.

Mir erscheint der Code aber doch sehr, sehr umständlich. Als Anfänger frag ich mich auch, ob ich nicht gegen alle Prinzipien des guten Programmierens verstossen habe. Wenn also jemand Zeit und Lust hat, den ziemlich langen Code durchzulesen, bin ich dankbar für jede Anregung, die mir zeigt:
a) wie ich auf kürzerem Weg zum gleichen Ergebnis kommen könnte (ohne zusätzliche neue Befehle aus der STL, also ohne Vektoren, Container etc.)
b) ob ich an irgendeiner Stelle schlechten Code drin hab, also zb Code, der viel zu viel Speicher frisst oder viel zu viel Leistung von der cpu saugt.
c) ob die 'Dokumentation' mit //Kommentaren unvollständig/hinreichend/zu ausführlich ist.

Wie gesagt, der Quellcode ist recht lang und enthält auch nochmals einige Erläuterungen in den Kommentaren:

Quellcode

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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
/*

Game of Life - nach dem Mathematiker John H. Conway
Quellcode von viti, 22.-23.12.2014

Drei Dinge machen es schwer:
(1) Die Grösse des Feldes soll vom Benutzer festgelegt werden -> dynamisches Array
(2) Das Feld stellt eine Kugel dar, dh eine Zelle am linken Rand hat
    Nachbarn am rechten Rand. Gleiches gilt für oberen und unteren Rand.
(3) Es muss ein 2D-Array in ein 1D-Array umgewandelt werden
    (container ua Methoden kenn ich noch nicht.)

    Meine Formel für 2D-Array in 1D-Array:
    Position [a][b] im Array[x*y] ==> Array[x*b+a]

Ideen für bessere Perfomance:
(a) Nachbarzählung abbrechen, sobald vier Nachbarn gefunden wurden
(b) Nicht jedes mal das ganze Array kopieren (Funktion 'CopyArray'),
    statt dessen nur die Koordinaten lebender Zellen speichern
    ==> GetNeighbours müsste ziemlich umgeschrieben werden

Andere Ausbaumöglichkeiten:
- Generation 0 aus einer Datei auslesen!

*/

#include <iostream>
#include <time.h>               //für den Zufallsgenerator
//#include "log.hpp"            //==>Logfile zur Fehlersuche deaktiviert
using namespace std;


/* 
Klasse cFeld - Programm wäre auch ohne Klasse, dh. nur mit Funktionen möglich. Wäre das sinnvoller
und warum?
*/

class cFeld
{
private:
    int xPos, yPos,             //Grösse des Feldes
        n;                      //Generation
    bool *pCells; bool *pOldCells;

    //cLog *pLog;

public:
    cFeld(int x, int y, int b);     //Feld erstmals erstellen und zufällig füllen
    ~cFeld();
    void Paint();               //Feld malen
    void NewStatus();           //Zellen im Feld neu berechnen
    void CopyArray();
    int GetNeighbours(int a, int b);
};

cFeld::cFeld(int x, int y, int b)   //Konstruktor
{
    //pLog=new cLog;
    //pLog->Init();

    n=0;
    xPos=x; yPos=y;
    pCells=new bool[xPos*yPos];     //2-Dimensionales Array auf 1-D-Array abbilden
    pOldCells=new bool[xPos*yPos];

    int random=0;                   //Zufallsgenerator starten
    time_t t;                   
    time(&t);
    srand((unsigned int)t);

    for (int i=0; i<xPos*yPos; i++) //Feld erstmals mit Zellen füllen
        if ((rand()%b)==1)
            pCells[i]=true;
    //pLog->Write("Konstruktor aufgerufen");
}

void cFeld::Paint()             //Malt das Feld
{
    cout << "Generation " << n << ":" << endl << endl;
    for (int i=0; i<yPos; i++)
    {
        cout << "|";
        for (int j=0; j<xPos; j++)
        {
            if (pCells[xPos*i+j]==true) //'width * row + col' wegen 2D->1D
                cout << "*";
            else
                cout << " ";
        }
        cout << "|" << endl;
    }
}

cFeld::~cFeld()                 //Destruktor
{
    delete[] pCells;
    delete[] pOldCells;
    pCells=NULL; pOldCells=NULL;
    //pLog->Write("Destruktor aufgerufen!");
}

void cFeld::NewStatus()
{
    n++;
    //pLog->Write("Neue Generation");
    int neighbours=0;

    for (int i=0; i<yPos; i++)
    {
        for (int j=0; j<xPos; j++)
        {
            //pLog->Write("BEGINNE MIT ZELLE", j, i);
            neighbours=GetNeighbours(j, i);
            if (pOldCells[xPos*i+j]==true)
            {
                //pLog->Write("Lebende Zelle identifiziert", j, i);

                if (neighbours>3||neighbours<2)
                {
                    //pLog->Write("Zelle gestorben", j, i);
                    pCells[xPos*i+j]=false;
                }
            }
            else
            {
                //pLog->Write("Tote Zelle identifiziert", j, i);
                if (neighbours>2)
                {
                    //pLog->Write("Zelle geboren", j, i);
                    pCells[xPos*i+j]=true;
                }
            }
        }
    }
}

int cFeld::GetNeighbours(int a, int b)
{
    int count=0;
    int aMinus, aPlus, bMinus, bPlus;

    if (a==0)               //Alles wegen dem offenen Feldrand!!
    {
        aMinus=xPos-1;      //->liegt a am linken Rand, so mach aus (a-1) den rechten Rand
        aPlus=a+1;
    }
    else if (a==xPos-1)
    {
        aPlus=0;            //->liegt a am rechten Rand, so mach auch a+1 den linken Rand
        aMinus=a-1;
    }
    else
    {
        aMinus=a-1;         
        aPlus=a+1;
    }
                            //und das gleiche Spiel für oben/unten...
    if (b==0)
    {
        bMinus=yPos-1;
        bPlus=b+1;
    }
    else if (b==yPos-1)
    {
        bPlus=0;
        bMinus=b-1;
    }
    else
    {
        bMinus=b-1;
        bPlus=b+1;
    }
                            //Jetzt können die Nachbarn gezählt werden!
    //pLog->Write("Prüfe Nachbarzelle", aMinus, bMinus);
    if (pOldCells[xPos*bMinus+aMinus]==true)
    {
        //pLog->Write("Nachbar gefunden auf", aMinus, bMinus);
        count++;
    }
    //pLog->Write("Prüfe Nachbarzelle", a, bMinus);
    if (pOldCells[xPos*bMinus+a]==true)
    {
        //pLog->Write("Nachbar gefunden auf", a, bMinus);
        count++;
    }
    //pLog->Write("Prüfe Nachbarzelle", aPlus, bMinus);
    if (pOldCells[xPos*bMinus+aPlus]==true)
    {
        //pLog->Write("Nachbar gefunden auf", aPlus, bMinus);
        count++;
    }
    //pLog->Write("Prüfe Nachbarzelle", aMinus, b);
    if (pOldCells[xPos*b+aMinus]==true)
    {
        count++;
        //pLog->Write("Nachbar gefunden auf", aMinus, b);
    }
    //pLog->Write("Prüfe Nachbarzelle", aPlus, b);
    if (pOldCells[xPos*b+aPlus]==true)
    {
        count++;
        //pLog->Write("Nachbar gefunden auf", aPlus, b);
    }
    //pLog->Write("Prüfe Nachbarzelle", aMinus, bPlus);
    if (pOldCells[xPos*bPlus+aMinus]==true)
    {
        //pLog->Write("Nachbar gefunden auf", aMinus, bPlus);
        count++;
    }
    //pLog->Write("Prüfe Nachbarzelle", a, bPlus);
    if (pOldCells[xPos*bPlus+a]==true)
    {
        //pLog->Write("Nachbar gefunden auf", a, bPlus);
        count++;
    }
    //pLog->Write("Prüfe Nachbarzelle", aPlus, bPlus);
    if (pOldCells[xPos*bPlus+aPlus]==true)
    {
        //pLog->Write("Nachbar gefunden auf", aPlus, bPlus);
        count++;
    }

    //pLog->Write("Lebende Nachbarzellen:", count, 0);
    return (count);
}
 
void cFeld::CopyArray()
{
    for (int i=0; i<yPos; i++)
    {
        for (int j=0; j<xPos; j++)
        {
            pOldCells[xPos*i+j]=pCells[xPos*i+j];
        }
    }
}

int main()
{
    int x, y, b;
    cout << endl;
    cout << "---------------------" << endl;
    cout << "G A M E  O F  L I F E" << endl;
    cout << "---------------------" << endl << endl;
    do
    {
        cout << "How big should the map be (max. 70*70)?" << endl;
        cout << "Length (x): "; cin >> x;
        cout << "Height (y): "; cin >> y;
        cout << "If 2 means that 1/2 of the initial map is alive..." << endl;
        cout << "...select a fraction of your choice (2-20): ";
        cin >> b;
        cout << endl << endl << endl;
    }while (x<1||y<1||x>70||y>70||b<2||b>20);

    cFeld *pFeld;                       //Feld auf dem Heap erstellen
    pFeld=new cFeld(x,y,b);
    cin.ignore();

    cout << "Random initialization of the map:" << endl << endl;
    pFeld->Paint();
    cout << endl << "Press Enter to continue..." << endl;
    cin.ignore();

    do
    {
        cout << "Calculating...." << endl << endl;
        pFeld->CopyArray();
        pFeld->NewStatus();
        pFeld->Paint();
        cout << endl << "Press Enter to continue..." << endl << endl;
        cin.ignore();
    }while (x>0);       //Momentan eine Endlosschleife


    if (pFeld!=0)       //wegen Endlosschlaufe grad obsolet
    {
        delete pFeld;   
        pFeld=NULL;
    }
    return 0;
}

2

23.12.2014, 15:47

Das sieht doch insgesamt eigentlich ganz gut aus. Wenn du die STL benutzen möchtest kannst du die manuellen allokationen in der klasse durch einen container wie std::vector benutzen. Auch die Klasse brauchst du nicht auf dem Heap zu instanzieren sondern kannst das auf dem Stack machen (du kannst das gerne machen, aber dann lass das if und nullsetzung am ende weg, das ist sinnlos). Außerdem kannst du CopyArray in NewStatus packen, dann muss man es außerhalb nicht aufrufen.

3

23.12.2014, 16:45

  • Wie schon geschrieben: Klasse auf den Stack
  • Nullpointer: Du mischt 0 und NULL. Entscheide dich für eines, am besten wäre allerdings nullptr
  • CopyArray ist überflüssig. Du liest aus dem einen Array und schreibst in das andere. Statt kopieren kannst du die beiden einfach austauschen.
  • if-Kaskade (if (pOldCells[xPos*b+aMinus]==true) etc): das kann man eleganter mit einer Schleife lösen.
  • Namensgebung: Du mischt fröhlich ungarische Notation (pFeld) und Camelcase (xPos, aPlus). Das ist unschön und vor allem verwirrend (bPlus ist kein bool, sondern eine Koordinate).
  • Bool-Vergleiche: if (a == true) ist äquivalent zu if (a).
  • return ist keine Funktion.
"Theory is when you know something, but it doesn’t work. Practice is when something works, but you don’t know why. Programmers combine theory and practice: Nothing works and they don’t know why." - Anon

4

25.12.2014, 01:54

Bool-Vergleiche: if (a == true) ist äquivalent zu if (a).

Nein, nur fast:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
    #include <iostream>
    using namespace std;
     
    int main() {
    // your code goes here
    if(2==true)
        cout << "Ja 1";
    if(2)
        cout << "Ja 2";
    return 0;
    }

In diesem programm wird nur "Ja 2" ausgegeben.

Nicht jeder Wert der wahr ist, ist equivalent zu true. Deswegen ist die zweite Form auch zu bevorzugen, da die erste sehr überraschend sein kann.
Lieber dumm fragen, als dumm bleiben!

FSA

Community-Fossil

  • Private Nachricht senden

5

25.12.2014, 14:10

Da stand doch Bool-Vergleiche. 2 ist in keiner Welt ein boolscher Wert in der EDV.

Zitat

Der RCCSWU (RandomCamelCaseSomtimesWithUndersquare) Stil bricht auch mal mit den veraltet strukturierten Denkmustern und erlaubt dem Entwickler seine Kreativität zu entfalten.

viti

Frischling

  • »viti« ist der Autor dieses Themas
  • Private Nachricht senden

6

26.12.2014, 13:19

Danke für die bisherigen Antworten. Eine konsequente Namensgebung muss ich mir noch angewöhnen, aber immerhin scheints kein Spaghetti-Code zu sein 8) Was auf den Heap kommen soll und was auf den Stack ist mir auch nicht immer klar. Die if-Kaskade könnte man wohl mit einem 'switch' lösen.

Ich denk ich komm auf das game of life zurück, wenn ich ein wenig 2D-Grafik beherrsche, scheint mir eine gute Anfängerübung zu sein.

7

26.12.2014, 14:55

Da stand doch Bool-Vergleiche. 2 ist in keiner Welt ein boolscher Wert in der EDV.

Joah, es wurde aber auch nicht explizit gesagt, dass a den Typ bool hat. Ich wollte es aber hauptsächlich deshalb erwähnt haben, weil ich damals mal einen fiesen Fehler hatte, als ich den Rückgabewert einer Funktion auf Gleichheit mit true getestet habe, statt ihn einfach so in die Abfrage zu schreiben und mich tagelang gefragt habe, warum diese dusselige Abfrage nie erfüllt wird.
Lieber dumm fragen, als dumm bleiben!

8

12.01.2015, 02:52

Gegendarstellung ;)

C-/C++-Quelltext

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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
#include <cstddef>
#include <cctype>
#include <stdexcept>
#include <algorithm>
#include <chrono>
#include <random>
#include <array>
#include <vector>
#include <string>
#include <sstream>
#include <iostream>

#define _WIN32_WINNT 0x0500
#define WIN32_LEAN_AND_MEAN 
#define NOMINMAX
#include <windows.h>
#include <conio.h>

class console_window
{
    HWND      window_handle;
    HANDLE    output_handle;
    unsigned  max_width;
    unsigned  max_height;
    unsigned  width;
    unsigned  height;
    HANDLE    original_buffer;
    HANDLE    front_buffer;
    HANDLE    back_buffer;

    public:
        console_window( console_window const & )      = delete;
        console_window & operator=( console_window )  = delete;

        console_window()
        : window_handle    { GetConsoleWindow() },
          output_handle    {},
          max_width        {},
          max_height       {},
          width            {},
          height           {},
          original_buffer  {},
          front_buffer     {},
          back_buffer      {}
        {
            if( !window_handle ) {
            
                if( !AllocConsole() )
                    throw std::runtime_error{ "An attempt to allocate a console for this process failed!" };

                window_handle = GetConsoleWindow();
            }

            output_handle = GetStdHandle( STD_OUTPUT_HANDLE );

            if( !output_handle || output_handle == INVALID_HANDLE_VALUE )
                throw std::runtime_error{ "Couldn't get standard output handle!" };

            COORD max_size( GetLargestConsoleWindowSize( output_handle ) );
            max_width  = max_size.X;
            max_height = max_size.Y;

            CONSOLE_SCREEN_BUFFER_INFOEX csbiex{ sizeof csbiex };
            GetConsoleScreenBufferInfoEx( output_handle, &csbiex );
            width  = csbiex.srWindow.Right  - csbiex.srWindow.Left + 1;
            height = csbiex.srWindow.Bottom - csbiex.srWindow.Top  + 1;

            resize( width, height );
        }

        console_window( unsigned width, unsigned height )
        : console_window{}
        {
            resize( width, height );
        }

        ~console_window()
        {
            if( original_buffer )
                toggle_double_buffering();
        }

        unsigned get_max_width()  const { return max_width;  }
        unsigned get_max_height() const { return max_height; }
        
        unsigned get_width()  const { return  width; }
        unsigned get_height() const { return height; }

        void set_title( std::string const & title ) { SetConsoleTitleA( title.c_str() ); }
        
        void move_window( unsigned x, unsigned y )
        {
            SetWindowPos( window_handle, nullptr, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER );
        }

        void resize( unsigned new_width, unsigned new_height )
        {
            if( !new_width || !new_height || new_width > max_width || new_height > max_height )
                throw std::runtime_error{ "New dimensions for console window exceed system limits!" };
            
            CONSOLE_SCREEN_BUFFER_INFOEX csbiex{ sizeof csbiex };
            GetConsoleScreenBufferInfoEx( output_handle, &csbiex );

            csbiex.srWindow = { 0, 0, 1, 1 };
            SetConsoleWindowInfo( output_handle, TRUE, &csbiex.srWindow );

            csbiex.dwSize = { new_width, new_height };
            SetConsoleScreenBufferInfoEx( output_handle, &csbiex );

            csbiex.srWindow = { 0, 0, new_width - 1, new_height - 1 };
            SetConsoleWindowInfo( output_handle, TRUE, &csbiex.srWindow );
        
            width  = new_width;
            height = new_height;
        }

        void set_font_size( unsigned size )
        {
            if( size < 2 )
                throw std::runtime_error{ "Can't set a console font size smaller than 2!" };

            CONSOLE_FONT_INFOEX cfiex{ sizeof cfiex };
            GetCurrentConsoleFontEx( output_handle, FALSE, &cfiex );
            
            cfiex.dwFontSize.Y = size;
            SetCurrentConsoleFontEx( output_handle, FALSE, &cfiex );

            COORD max_size( GetLargestConsoleWindowSize( output_handle ) );
            max_width  = max_size.X;
            max_height = max_size.Y;
        }

        void gotoxy( unsigned x, unsigned y )
        {
            if( !SetConsoleCursorPosition( output_handle, COORD{ x, y } ) )
                throw std::runtime_error{ "gotoxy() failed!" };
        }

        void write_buffer( char const * buffer, std::size_t length, std::size_t x, std::size_t y )
        {
            DWORD written;
            WriteConsoleOutputCharacterA( front_buffer ? back_buffer : front_buffer,
                                          buffer, static_cast< DWORD >( length ),
                                          COORD{ static_cast< SHORT >( x ),
                                                 static_cast< SHORT >( y ) },
                                          &written );
        }

        bool  kbhit   ()  const { return _kbhit() != 0; }
        int   getch   ()  const { return _getch(); }
        int   ungetch ( int key  ) const { return _ungetch( key ); }
        void  sleep   ( DWORD ms ) const { Sleep( ms ); }

        void toggle_double_buffering()
        {
            if( !front_buffer ) {

                front_buffer = original_buffer = CreateFileA( "CONOUT$", GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr );
                back_buffer = CreateConsoleScreenBuffer( GENERIC_READ | GENERIC_WRITE, 0, nullptr, CONSOLE_TEXTMODE_BUFFER, nullptr );
    
            } else {
    
                SetConsoleActiveScreenBuffer( original_buffer );
                CloseHandle( original_buffer == front_buffer ? back_buffer : front_buffer );
                original_buffer = front_buffer = back_buffer = nullptr;
            }
        }

        HANDLE get_front_buffer() const { return front_buffer; }
        HANDLE get_back_buffer()  const { return  back_buffer; }

        void flip()
        {
            std::swap( front_buffer, back_buffer );
            SetConsoleActiveScreenBuffer( front_buffer );
        }
};

class game_of_life
{
    public:
        class ruleset
        {
            std::array< std::size_t, 9 > birth;
            std::array< std::size_t, 9 > survival;

            public:
                ruleset()
                : birth     ({ 0, 0, 0, 1, 0, 0, 0, 0, 0 }),  // {    3 }
                  survival  ({ 0, 0, 1, 1, 0, 0, 0, 0, 0 })   // { 2, 3 }
                {}

                ruleset( std::vector< unsigned > &     birth_neighbours,
                         std::vector< unsigned > &  survival_neighbours )
                : birth     {},
                  survival  {}
                {
                    for( auto i : birth_neighbours )
                        birth.at( 1 << i ) = 1;

                    for( auto i : survival_neighbours )
                        survival.at( 1 << i ) = 1;
                }

                bool dead_or_alive( bool cell, std::size_t neighbours_alive ) const
                {
                    return cell && survival[ neighbours_alive ] || birth[ neighbours_alive ];
                }
        };

    private:
        typedef std::vector< char > state;

        static char const *  caption;
        static char const *  caption_separator;
        static char const *  stats_title;
        static char const *  stats_separator;
        static char const *  stats_generation_title;
        static char const *  stats_population_title;

        std::size_t const    width;
        std::size_t const    height;
        std::size_t          generation;
        std::size_t          population;
        state                current_state;
        state                next_state;
        ruleset              rules;

        std::size_t get_moore_neighbours( std::size_t i ) const
        {
            return current_state[ i - width - 1 ]
                 + current_state[ i - width     ]
                 + current_state[ i - width + 1 ]
                 + current_state[ i - 1 ]
                 + current_state[ i + 1 ]
                 + current_state[ i + width - 1 ]
                 + current_state[ i + width     ]
                 + current_state[ i + width + 1 ];
        }

    public:
        game_of_life( std::size_t field_width, std::size_t field_height, ruleset const & rules = ruleset{} )
        : width             { field_width  + 2 },
          height         { field_height + 2 },
          generation     {},
          population     {},
          current_state  { width * height, 0 },
          next_state     { width * height, 0 },
          rules          { rules }
        {}

        std::size_t get_width()  const { return  width - 2; }
        std::size_t get_height() const { return height - 2; }

        state::const_pointer operator[]( std::size_t y ) const
        {
            if( y > height - 2 )
                throw std::runtime_error{ "Line number out of range!" };

            return &current_state[ ( y + 1 ) * width + 2 ];
        }
                
        std::size_t get_generation() const { return generation; }
        std::size_t get_population() const { return population; }

        std::string generate_stats_line() const
        {
            std::stringstream ss{ caption };
            ss.seekp( 0, std::ios::end );
            
            ss << caption_separator << stats_title
               << get_width() << " x " << get_height() << stats_separator
               << stats_population_title << population << stats_separator
               << stats_generation_title << generation;
            
            return ss.str();
            
        }

        void reset()
        {
            generation = population = 0;
            std::fill( std::begin( current_state ), std::end( current_state ), 0 );
            std::fill( std::begin(    next_state ), std::end(    next_state ), 0 );
        }        

        template< typename Engine >
        void randomize( double probability_of_life, Engine & engine )
        {
            std::bernoulli_distribution distribution( probability_of_life );
            
            std::transform( std::begin( current_state ) + width, std::end( current_state ) - width,
                std::begin( current_state ) + width, [&]( state::value_type ){ return distribution( engine ); } );
            
            for( std::size_t i{ width }; i < current_state.size() - width; ++i ) {
                
                current_state[ i ] = 0;
                current_state[ i += width - 1 ] = 0;
            }
        }

        void iterate()
        {
            population = 0;

            for( std::size_t y{ 1 }; y < height - 1; ++y ) {
                
                for( std::size_t x{ 1 }; x < width - 1; ++x ) {

                    std::size_t i{ y * width + x };
                    
                    if( next_state[ i ] = rules.dead_or_alive( current_state[ i ] != 0, get_moore_neighbours( i ) ) )
                        ++population;
                }
            }

            std::swap( current_state, next_state );
            ++generation;
        }
};

char const * game_of_life::caption                { "Game of Life" };
char const * game_of_life::caption_separator      { " - "          };
char const * game_of_life::stats_title            { "Stats: "      };
char const * game_of_life::stats_separator        { " | "          };
char const * game_of_life::stats_generation_title { "Generation: " };
char const * game_of_life::stats_population_title { "Population: " };

int main()
{
    try {
        console_window console{};
        
        console.set_font_size( 5 );
        console.resize( console.get_max_width() - 4, console.get_max_height() - 4 );
        console.move_window( 0, 0 );
        console.toggle_double_buffering();

        game_of_life life{ console.get_width(), console.get_height() };

        std::mt19937 random_engine{ static_cast< unsigned >(
            std::chrono::high_resolution_clock::now().time_since_epoch().count() ) };

        double const life_chances{ 0.08 };
        life.randomize( life_chances, random_engine );

        bool run{ true };
        DWORD delay{};

        do {
            if( console.kbhit() ) {
            
                switch( std::tolower( console.getch() ) ) {
                
                    case 'r':
                        life.reset();
                        life.randomize( life_chances, random_engine );
                        break;

                    case ' ':
                        if( console.ungetch( console.getch() ) == ' ' )
                            console.getch();
                        break;

                    case 0x1b:  // esc
                        run = false;
                        break;

                    case '+':
                        if( delay )
                            delay -= 10;
                        break;

                    case '-':
                        delay += 10;
                        break;
                }

            } else {

                life.iterate();

                for( std::size_t y{}; y < life.get_height(); ++y )
                    console.write_buffer( life[ y ], life.get_width(), 0, y );                    
                
                console.flip();
                console.set_title( life.generate_stats_line() );
                console.sleep( delay );
            }
                        
        } while( run );

    } catch( std::runtime_error & exception ) {
    
        std::cerr << exception.what() << "\n\n";
        return EXIT_FAILURE;
    
    } catch( ... ) {
    
        std::cerr << "An unknown error occured!\n\n";
        return EXIT_FAILURE;
    }
}

dürft ihr auch gerne auseinandernehmen ;)

Werbeanzeige