Game of Life (naive Implementierung)



  • Naive Implementierung des Game of Life. Mags mir bitte jemand um die Ohren hauen?

    #include <cstddef>
    #include <stdexcept>
    #include <algorithm>
    #include <chrono>
    #include <random>
    #include <array>
    #include <vector>
    #include <string>
    #include <sstream>
    #include <iostream>
    
    #include <tchar.h>
    #include <conio.h>
    
    #define _WIN32_WINNT 0x0500
    #define WIN32_LEAN_AND_MEAN 
    #define NOMINMAX
    #include <windows.h>
    
    class console_t {
    
    	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_t();
    		console_t( unsigned width, unsigned height );
    
    		console_t( console_t const & other ) = delete;
    		console_t & operator=( console_t other ) = delete;
    
    		~console_t();
    
    		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 );
    		void move_window( unsigned x, unsigned y );
    		void resize( unsigned new_width, unsigned new_height );
    		void set_font_size( unsigned size );
    		void gotoxy( unsigned x, unsigned y );
    
    		void toggle_double_buffering();
    		HANDLE get_front_buffer() const;
    		HANDLE get_back_buffer() const;
    		void flip();
    };
    
    console_t::console_t()
    : 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_t::console_t( unsigned width, unsigned height )
    : console_t{}
    {
    	resize( width, height );
    }
    
    console_t::~console_t()
    {
    	if( original_buffer )
    		toggle_double_buffering();
    }
    
    void console_t::set_title( std::string const & title )
    {
    	SetConsoleTitleA( title.c_str() );
    }
    
    void console_t::move_window( unsigned x, unsigned y )
    {
    	SetWindowPos( window_handle, nullptr, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER );
    }
    
    void console_t::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.Left   = 0;
    	csbiex.srWindow.Top    = 0;
    	csbiex.srWindow.Right  = 1;
    	csbiex.srWindow.Bottom = 1;
    	SetConsoleWindowInfo( output_handle, TRUE, &csbiex.srWindow );
    
    	csbiex.dwSize.X = new_width;
    	csbiex.dwSize.Y = new_height;
    	SetConsoleScreenBufferInfoEx( output_handle, &csbiex );
    
    	csbiex.srWindow.Left   = 0;
    	csbiex.srWindow.Top    = 0;
    	csbiex.srWindow.Right  = new_width  - 1;
    	csbiex.srWindow.Bottom = new_height - 1;
    	SetConsoleWindowInfo( output_handle, TRUE, &csbiex.srWindow );
    
    	width  = new_width;
    	height = new_height;
    }
    
    void console_t::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( GetStdHandle( STD_OUTPUT_HANDLE ), FALSE, &cfiex );
    	cfiex.dwFontSize.Y = size;
    	SetCurrentConsoleFontEx( GetStdHandle( STD_OUTPUT_HANDLE ), FALSE, &cfiex );
    
    	COORD max_size( GetLargestConsoleWindowSize( output_handle ) );
    	max_width  = max_size.X;
    	max_height = max_size.Y;
    }
    
    void console_t::gotoxy( unsigned x, unsigned y )
    {
    	if( !SetConsoleCursorPosition( output_handle, COORD{ x, y } ) )
    		throw std::runtime_error{ "gotoxy() failed!" };
    }
    
    void console_t::toggle_double_buffering()
    {
    	if( !front_buffer ) {
    
    		front_buffer = original_buffer = CreateFile( _T( "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 console_t::get_front_buffer() const
    {
    	return front_buffer;
    }
    
    HANDLE console_t::get_back_buffer() const
    {
    	return back_buffer;
    }
    
    void console_t::flip()
    {
    	std::swap( front_buffer, back_buffer );
    	SetConsoleActiveScreenBuffer( front_buffer );
    }
    
    class ruleset_t
    {
    	std::array< std::size_t, 9 >  birth;
    	std::array< std::size_t, 9 >  survival;
    
    	public:
    		ruleset_t();
    		ruleset_t( std::vector< unsigned > & birth_neighbours,
    		           std::vector< unsigned > & survival_neighbours );
    
    		bool dead_or_alive( bool cell, std::size_t neighbours_alive ) const;
    };
    
    ruleset_t::ruleset_t()
    : birth    ( { 0, 0, 0, 1, 0, 0, 0, 0, 0 } ),  // {    3 }
      survival ( { 0, 0, 1, 1, 0, 0, 0, 0, 0 } )   // { 2, 3 }
    {}
    
    ruleset_t::ruleset_t( std::vector< unsigned > & birth_neighbours,
                          std::vector< unsigned > & survival_neighbours )
    : birth    (),
      survival ()
    {
    	for( auto i : birth_neighbours )
    		birth[ 1 << i ] = 1;
    
    	for( auto i : survival_neighbours )
    		survival[ 1 << i ] = 1;
    }
    
    bool ruleset_t::dead_or_alive( bool cell, std::size_t neighbours_alive ) const
    {
    	return cell && survival[ neighbours_alive ] || birth[ neighbours_alive ];
    }
    
    struct point_t
    {
    	std::size_t x;
    	std::size_t y;
    };
    
    class life_t
    {
    	private:
    		typedef std::vector< char > state_t;
    
    		std::size_t  generation;
    		std::size_t  population;
    		std::size_t  width;
    		std::size_t  height;
    		state_t      curr;
    		state_t      next;
    		ruleset_t	 ruleset;
    		std::string  static_title_part;
    
    		std::size_t get_moore_neighbours( std::size_t i ) const;
    
    	public:
    		life_t( std::size_t width, std::size_t height, ruleset_t const & ruleset = ruleset_t() );
    		void reset();
    
    		template< typename Engine >
    		void randomize( double probability_of_life, Engine & engine )
    		{
    			std::bernoulli_distribution distribution( probability_of_life );
    			std::transform( curr.begin(), curr.end(), curr.begin(), [&]( char i ){ return distribution( engine ); } );
    		}
    
    		void iterate();
    		void print( console_t & console ) const;
    };
    
    life_t::life_t( std::size_t width, std::size_t height, ruleset_t const & ruleset )
    : generation (),
      population (),
      width      ( width ),
      height     ( height ),
      curr       ( width * height, 0 ),
      next       ( width * height, 0 ),
      ruleset    ( ruleset )
    {
    	std::stringstream ss{ "Game of Life   [ " };
    	ss.seekp( 0, std::ios::end );
    	ss << width << " x " << height << " | Population: ";
    	static_title_part = ss.str();
    }
    
    std::size_t life_t::get_moore_neighbours( std::size_t i ) const
    {
    	std::size_t neighbours{};
    
    	bool const has_top_neighbour    { i >= width };
    	bool const has_bottom_neighbour { i < curr.size() - width };
    	bool const has_left_neighbour   { i && i % width };
    	bool const has_right_neighbour  { ( i + 1 ) % width != 0 };
    
    	if( has_top_neighbour ) {
    
    		if( curr[ i - width ] )
    			++neighbours;
    
    		if( has_right_neighbour && curr[ i - width + 1 ] )
    			++neighbours;
    
    		if( has_left_neighbour && curr[ i - width - 1 ] )
    			++neighbours;
    	}
    
    	if( has_left_neighbour && curr[ i - 1 ] )
    		++neighbours;
    
    	if( has_right_neighbour && curr[ i + 1 ] )
    		++neighbours;
    
    	if( has_bottom_neighbour ) {
    
    		if( curr[ i + width ] )
    			++neighbours;
    
    		if( has_left_neighbour && curr[ i + width - 1 ] )
    			++neighbours;
    
    		if( has_right_neighbour && curr[ i + width + 1 ] )
    			++neighbours;
    	}
    
    	return neighbours;
    }
    
    void life_t::reset()
    {
    	generation = 0;
    	population = 0;
    
    	std::fill( curr.begin(), curr.end(), 0 );
    	std::fill( next.begin(), next.end(), 0 );
    }
    
    void life_t::iterate()
    {
    	population = 0;
    
    	for( std::size_t i{}; i < width * height; ++i )
    		next[ i ] = ruleset.dead_or_alive( curr[ i ] != 0, get_moore_neighbours( i ) ) && ++population;
    
    	std::swap( curr, next );
    	++generation;
    }
    
    void life_t::print( console_t & console ) const
    {
    	static std::stringstream ss{};
    
    	DWORD written;
    	WriteConsoleOutputCharacterA( console.get_back_buffer(), &curr[ 0 ], static_cast< DWORD >( curr.size() ), { 0, 0 }, &written );
    
    	ss.str( static_title_part );
    	ss.seekp( 0, std::ios::end );
    	ss << population << " | Generation: " << generation << " ]";
    	console.set_title( ss.str() );
    
    	console.flip();
    }
    
    int main()
    {
    	try {
    		console_t 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();
    
    		life_t 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 life_chances{ 0.1 };
    
    		life.randomize( life_chances, random_engine );
    
    		bool run{ true };
    		DWORD delay = 0;
    
    		do {
    			if( _kbhit() ) {
    
    				switch( _getch() ) {
    
    					case 'r':
    						life.reset();
    						life.randomize( life_chances, random_engine );
    						break;
    
    					case 0x20:  // space
    						_ungetch( _getch() );
    						break;
    
    					case 0x1b:  // esc
    						run = false;
    						break;
    
    					case '+':
    						if( delay )
    							delay -= 10;
    						break;
    
    					case '-':
    							delay += 10;
    						break;
    				}
    
    			} else {
    
    				life.iterate();
    				life.print( 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;
    	}
    }
    


  • Viel Kapselung ist da nicht. live_t ruft WriteConsoleOutputCharacterA auf, nimmt aber trotzdem eine allgemeine console_t entgegen. Ausserdem sollten Simulation und Ausgabe völlig getrennt sein, was hier irgendwie nicht der Fall ist.



  • Guter Punkt. Ich hab' mich da bis zum Ende noch mit mehreren Ausgabemöglichkeiten gespielt und es ist was halbgares übrig geblieben. Gehört überarbeitet.



  • Nur um sicherzugehen, kennste Golly nebst http://en.wikipedia.org/wiki/Hashlife ?
    Ist einfach frech, wie er in 20 Sekunden den Status eines Brüters nach 568,369,413,039,756,162,463,293,997,882,080,960,114,807,029,482,683,105,646,244,545,020,753,710,871,466,714,894,912 Generationen berechnet hat.
    😮 😮 😮 😮 😮



  • Der ctor

    ruleset_t::ruleset_t( std::vector< unsigned > & birth_neighbours,
                          std::vector< unsigned > & survival_neighbours )
    : birth    (),
      survival ()
    {
        for( auto i : birth_neighbours )
            birth[ 1 << i ] = 1;
    
        for( auto i : survival_neighbours )
            survival[ 1 << i ] = 1;
    }
    

    ist kaputt. Behaupte ich mal. 1 << i macht hier keinen Sinn. Und nachdem es nicht Performance-kritisch ist wäre mMn. .at() statt [] angebracht.

    get_moore_neighbours kannst du deutlich vereinfachen, auf eine von zwei Arten:

    1. Erweitere das Array auf allen vier Seiten um je eine Zelle die immer 0 bleibt. Das würde ich machen.
      Oder
    2. Bastel dir eine at(x, y) Funktion die 0 für "out of bounds" Zellen zurückliefert und ruf diese in der get_moore_neighbours auf.

    Der && ++population; Trick ist grauenhaft. Ich will lieber nicht genauer darauf eingehen was ich davon halte - will dich nicht beleidigen.

    Wenn du Output und Simulation wirklich trennen willst, dann gehört die print Funktion aus der life_t Klasse raus. Statt dessen wirst du dann irgend eine generische Möglichkeit brauchen in ein life_t reinzugucken bzw. das aktuelle "Frame" zu extrahieren.

    Die Initialisierung von Skalaren mit {...} finde ich ebenso furchtbar, aber das ist ne Stilfrage, darum geht's ja weniger.

    Und - nochmal Stil - ... was soll das _t als Suffix bei den Klassennamen? Verwende statt dessen lieber vernünftige Namen.
    life_t => game_of_life
    ruleset_t => game_of_life::ruleset
    point_t => point
    console_t => console_utility/console_window

    Und dann die Variablennamen... alles sehr einsilbig. Das geht auch besser (klarer, eindeutiger, leichter zu verstehen).



  • @volkard: ja, kenn ich natürlich 😉

    hustbaer schrieb:

    Der ctor [ ruleset() ] ist kaputt. Behaupte ich mal. 1 << i macht hier keinen Sinn. [...]

    Sorry, da steh' ich auf irgendwelchen Leitungen rum ... was ist da kaputt? Bitte sag noch ein bisschen was dazu, damit mir vielleicht ein Licht aufgeht.

    hustbaer schrieb:

    [...] Und nachdem es nicht Performance-kritisch ist wäre mMn. .at() statt [] angebracht.

    Angenommen. 👍

    hustbaer schrieb:

    get_moore_neighbours kannst du deutlich vereinfachen, auf eine von zwei Arten:

    1. Erweitere das Array auf allen vier Seiten um je eine Zelle die immer 0 bleibt. Das würde ich machen.
      Oder [...]

    Erledigt. Sieht besser aus. 👍

    hustbaer schrieb:

    Der && ++population; Trick ist grauenhaft. Ich will lieber nicht genauer darauf eingehen was ich davon halte - will dich nicht beleidigen.

    Keine Angst, dafür könntest mich mit keiner Aussage beleidigen. Ich tipp mal auf Fehlerträchtig und bzw. weil leicht zu übersehen?
    In der jetzigen Version wirst mich halt für die Zuweisung im if haun ...

    hustbaer schrieb:

    Wenn du Output und Simulation wirklich trennen willst, dann gehört die print Funktion aus der life_t Klasse raus. Statt dessen wirst du dann irgend eine generische Möglichkeit brauchen in ein life_t reinzugucken bzw. das aktuelle "Frame" zu extrahieren.

    Erledigt. Aber ob das nicht auch eleganter geht?

    hustbaer schrieb:

    [...] Und dann die Variablennamen... alles sehr einsilbig. Das geht auch besser (klarer, eindeutiger, leichter zu verstehen).

    Hm. Welche sind denn besonders schlimm?

    Aktuelle Version:

    #include <cstddef>
    #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, length, { x, 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_seperator;
    		static char const *  stats_title;
    		static char const *  stats_seperator;
    		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 = game_of_life::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_seperator << stats_title
    			   << get_width() << " x " << get_height() << stats_seperator
    			   << stats_population_title << population << stats_seperator
    			   << 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_seperator      { " - " };
    char const * game_of_life::stats_title            { "Stats: " };
    char const * game_of_life::stats_seperator        { " | " };
    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.1 };
    		life.randomize( life_chances, random_engine );
    
    		bool run{ true };
    		DWORD delay{};
    
    		do {
    			if( console.kbhit() ) {
    
    				switch( 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;
    	}
    }
    

    // edit: Code updated.
    // edit: und auf < Romanlänge gekürzt.
    // edit: dacht' ich zumindest 😕


Anmelden zum Antworten