@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:
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 ¤t_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