Postanowiłem napisać grę w życie w C. Jako, że nie znam jeszcze technik programowania grafiki (poza odrobiną allegro) moja wersja implementacji będzie wyświetlana w okienku konsolowym, a żywe i martwe komórki będą reprezentowane odpowiednio za pomocą 1 i 0. Chciałbym się zapytać jak można dość efektywnie rozwiązać problem "animacji" ewolucji. Tzn. mam pierwszy stan, poddaje go obróbce i chcę żeby się wyświetlał. Myślałem żeby zastosować prostą wersję podwójnego buforowania, ale może ktoś z was zna jakiś lepszy sposób.
Szczerze mówiąc, ciągłe przerysowywanie 10-15-20 linii w konsoli nie będzie zbyt optymalne. Zmieniaj tylko te znaki, które musisz.
Pisałem parę miesięcy temu grę w życie w konsoli z użyciem <newconio> ( http://edu.i-lo.tarnow.pl/inf/prg/008_projekty/data/newconio.h , http://edu.i-lo.tarnow.pl/inf/prg/008_projekty/data/newconio.cpp ).
Wygląda to w ten sposób: http://scr.hu/screenshooter/1084042/efrdrxs
Ramki rysuję za pomocą funkcji z tej biblioteki, a potem planszę 80x30 przerysowuję całą z każdą zmianą stanu jadąc po prostu dwiema pętlami i stawiając znaki poprzez putchxy() - wygląda płynnie, bez mrugania czy czegoś w podobnego, stąd nie miałem potrzeby troszczyć się o optymalizowanie tego.
Proste podwójne buforowanie (konsola) z pomocą WinAPI (WriteConsoleOutputCharacter):
#include <iostream>
#include <ctime>
#include <windows.h>
using namespace std;
//znaki 'char' reprezentujace stany komorek
#define SUBAKTYWNA ((char) 32) //czarny
#define AKTYWNA ((char) 176) //szary
#define NIEAKTYWNA ((char) 177) //bialy
#define MARTWA NIEAKTYWNA
#define ZYWA SUBAKTYWNA
char bufory[2][25][80]; //dwa bufory zawierajace siatke komorek - tablice char'ow reprezentujacych stany komorek, jeden bufor aktywny drugi pasywny (wymiennie)
int A = 0; //indeks aktywnego buforu, w tym buforze zapisywane sa nowe stany komorek
int P = 1; //indeks pasywnego buforu, z tego buforu odczytywane sa stany komorek, jest on rowniez wyswietlany na ekranie
int szerokosc, wysokosc; //wymiary siatki, max 80x25
int automat; //numer automatu: 1 - zycie, 2 - marsz, 3 - inwazja
int interwal; //czas w ms pomiedzy kolejnymi krokami
double gestosc; //prawdopodobienstwo z jakim pojawiaja sie zywe komorki podczas losowania
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); //uchwyt konsoli uzywany przy wyswietlaniu siatki
//wyswietla komorki i zamienia bufory
void FlipPrint()
{
//aby wyeliminowac brzydki efekt "przewijania"
//do wyswietlania uzyta jest funkcja WriteConsoleOutputCharacterA,
DWORD cWritten;
COORD coord= { 0, 0 };
WriteConsoleOutputCharacterA(hStdOut, bufory[A][0], 80 * 25 - 1, coord, &cWritten);
//zamiana indeksow buforow
int tmp = A; A = P; P = tmp;
}
//wypelnia losowo siatke z zadana gestoscia
void WypelnijLosowo()
{
srand(time(NULL));
if(automat == 3) //inwazja - 3 stany komorek
for(int i = 0; i < wysokosc; ++i)
for(int j = 0; j < szerokosc; ++j)
if((rand() / (double) RAND_MAX) < gestosc)
bufory[A][i][j] = ((rand() % 2) ? AKTYWNA : SUBAKTYWNA);
else
bufory[A][i][j] = NIEAKTYWNA;
else //zycie lub marsz - 2 stany komorek
for(int i = 0; i < wysokosc; ++i)
for(int j = 0; j < szerokosc; ++j)
if((rand() / (double) RAND_MAX) < gestosc)
bufory[A][i][j] = ZYWA;
else
bufory[A][i][j] = MARTWA;
}
//zlicza ilosc wystapien danego typu w siatce komorek wokol komorki (i, j)
int Zlicz(int i, int j, char typ)
{
int rezultat = 0; //zkiczana ilosc powtorzen
int gora = i - 1, dol = i + 1, lewy = j - 1, prawy = j + 1; //krawedzie poszukiwan
//petle zliczaja caly kwadrat (wraz ze srodkiem i,j), zatem startujemy od -1 jesli srodek ma odpowiedni typ
if(bufory[P][i][j] == typ) rezultat = -1;
//przy krawedziach siatki nalezy zmniejszyc obszar poszukiwan
if(i == 0) ++gora; else
if(i == (wysokosc - 1)) --dol;
if(j == 0) ++lewy; else
if(j == (szerokosc - 1)) --prawy;
for(i = gora; i <= dol; ++i)
for(j = lewy; j <= prawy; ++j)
if(bufory[P][i][j] == typ) ++rezultat;
return rezultat;
}
//krok symulacji Zycie
void Zycie()
{
int zywe; //ilosc zywych komorek sasiednich
for(int i = 0; i < wysokosc; ++i)
for(int j = 0; j < szerokosc; ++j)
{
zywe = Zlicz(i, j, ZYWA);
if((zywe == 3) || ((bufory[P][i][j] == ZYWA) && (zywe == 2)))
bufory[A][i][j] = ZYWA;
else
bufory[A][i][j] = MARTWA;
}
}
//krok symulacji Marsz
void Marsz()
{
int aktywne; //ilosc aktywnych komorek sasiednich
for(int i = 0; i < wysokosc; ++i)
for(int j = 0; j < szerokosc; ++j)
{
aktywne = Zlicz(i, j, SUBAKTYWNA);
if((aktywne == 3) || (aktywne == 2) || ((bufory[P][i][j] == SUBAKTYWNA) && (aktywne == 1)))
bufory[A][i][j] = SUBAKTYWNA;
else
bufory[A][i][j] = NIEAKTYWNA;
}
}
//krok symulacji Inwazja
void Inwazja()
{
int aktywne; //ilosc aktywnych komorek sasiednich
int subaktywne; //ilosc subaktywnych komorek sasiednich
for(int i = 0; i < wysokosc; ++i)
for(int j = 0; j < szerokosc; ++j)
{
aktywne = Zlicz(i, j, AKTYWNA);
subaktywne = Zlicz(i, j, SUBAKTYWNA);
if((bufory[P][i][j] == AKTYWNA) && ((aktywne >= 3) || (subaktywne >= 3)))
bufory[A][i][j] = SUBAKTYWNA;
else if((bufory[P][i][j] == NIEAKTYWNA) && ((aktywne + subaktywne) >= 3))
bufory[A][i][j] = AKTYWNA;
else if((bufory[P][i][j] == AKTYWNA) && (((aktywne + subaktywne) == 1) || ((aktywne + subaktywne) == 2)))
bufory[A][i][j] = AKTYWNA;
else
bufory[A][i][j] = NIEAKTYWNA;
}
}
int main()
{
cout << "Podaj wysokosc siatki (1 - 25): ";
cin >> wysokosc; if((wysokosc > 25) || (wysokosc < 1)) wysokosc = 25;
cout << "Podaj szerokosc siatki (1 - 80): ";
cin >> szerokosc; if((szerokosc > 80) || (szerokosc < 1)) szerokosc = 80;
cout << "Wybierz automat (1 - Zycie, 2 - Marsz, 3 - Invazja): ";
cin >> automat; if(automat < 1) automat = 1; if(automat > 3) automat = 3;
cout << "Podaj gestosc komorek (0.0 - 1.0): ";
cin >> gestosc; if(gestosc < 0.0) gestosc = 0.0; if(gestosc > 1.0) gestosc = 1.0;
cout << "Podaj interwal czasowy w ms ( >0 ): ";
cin >> interwal; if(interwal < 1) interwal = 1;
WypelnijLosowo();
for(;;)
{
FlipPrint();
Sleep(interwal);
switch(automat)
{
case 1: Zycie(); break;
case 2: Marsz(); break;
case 3: Inwazja(); break;
}
}
}