--- /dev/null
+/////////////////////////////////////////////////////////////////////////////
+// Name: life.cpp
+// Purpose: The game of life, created by J. H. Conway
+// Author: Guillermo Rodriguez Garcia, <guille@iies.es>
+// Modified by:
+// Created: Jan/2000
+// RCS-ID: $Id$
+// Copyright: (c) 2000, Guillermo Rodriguez Garcia
+// Licence: wxWindows licence
+/////////////////////////////////////////////////////////////////////////////
+
+// ==========================================================================
+// declarations
+// ==========================================================================
+
+// minimum and maximum table size, in each dimension
+#define LIFE_MIN 10
+#define LIFE_MAX 200
+
+// some shortcuts
+#define ADD_TOOL(a, b, c, d) \
+ toolBar->AddTool(a, b, wxNullBitmap, FALSE, -1, -1, (wxObject *)0, c, d)
+
+#define GET_FRAME() \
+ ((wxFrame *) wxGetApp().GetTopWindow())
+
+// --------------------------------------------------------------------------
+// headers
+// --------------------------------------------------------------------------
+
+#ifdef __GNUG__
+ #pragma implementation "life.cpp"
+ #pragma interface "life.cpp"
+#endif
+
+// for compilers that support precompilation, includes "wx/wx.h".
+#include "wx/wxprec.h"
+
+#ifdef __BORLANDC__
+ #pragma hdrstop
+#endif
+
+// for all others, include the necessary headers
+#ifndef WX_PRECOMP
+ #include "wx/wx.h"
+#endif
+
+#include "wx/statline.h"
+#include "wx/spinctrl.h"
+
+// --------------------------------------------------------------------------
+// resources
+// --------------------------------------------------------------------------
+
+#if defined(__WXGTK__) || defined(__WXMOTIF__)
+ // the application icon
+ #include "mondrian.xpm"
+
+ // bitmap buttons for the toolbar
+ #include "bitmaps/reset.xpm"
+ #include "bitmaps/play.xpm"
+ #include "bitmaps/stop.xpm"
+#endif
+
+// --------------------------------------------------------------------------
+// private classes
+// --------------------------------------------------------------------------
+
+class Life;
+class LifeCanvas;
+class LifeTimer;
+class LifeFrame;
+class LifeApp;
+
+
+// Life
+class Life
+{
+public:
+ // ctors and dtors
+ Life(int width, int height);
+ ~Life();
+ void Create(int width, int height);
+ void Destroy();
+
+ // public accessors
+ inline int GetWidth() const { return m_width; };
+ inline int GetHeight() const { return m_height; };
+ inline bool IsAlive(int x, int y) const;
+ inline bool HasChanged(int x, int y) const;
+ inline void SetCell(int x, int y, bool alive = TRUE);
+
+ // game operations
+ void Clear();
+ void NextTic();
+
+private:
+ enum CellFlags {
+ CELL_DEAD = 0x0000, // is dead
+ CELL_ALIVE = 0x0001, // is alive
+ CELL_MARK = 0x0002, // will change / has changed
+ };
+ typedef int Cell;
+
+ int GetNeighbors(int x, int y) const;
+ inline void SetCell(int x, int y, Cell status);
+
+ int m_width;
+ int m_height;
+ Cell *m_cells;
+};
+
+// Life canvas
+class LifeCanvas : public wxScrolledWindow
+{
+public:
+ // ctor and dtor
+ LifeCanvas(wxWindow* parent, Life* life);
+ ~LifeCanvas();
+
+ // member functions
+ void Reset();
+ void DrawEverything(bool force = FALSE);
+ void DrawCell(int i, int j);
+ void DrawCell(int i, int j, wxDC &dc);
+ inline int CellToCoord(int i) const { return (i * m_cellsize); };
+ inline int CoordToCell(int x) const { return ((x >= 0)? (x / m_cellsize) : -1); };
+
+ // event handlers
+ void OnPaint(wxPaintEvent& event);
+ void OnMouse(wxMouseEvent& event);
+ void OnSize(wxSizeEvent& event);
+
+private:
+ // any class wishing to process wxWindows events must use this macro
+ DECLARE_EVENT_TABLE()
+
+ enum MouseStatus {
+ MOUSE_NOACTION,
+ MOUSE_DRAWING,
+ MOUSE_ERASING
+ };
+
+ Life *m_life;
+ wxFrame *m_frame;
+ wxBitmap *m_bmp;
+ int m_height;
+ int m_width;
+ int m_cellsize;
+ wxCoord m_xoffset;
+ wxCoord m_yoffset;
+ MouseStatus m_status;
+};
+
+// Life timer
+class LifeTimer : public wxTimer
+{
+public:
+ LifeTimer(LifeFrame *parent);
+ void Notify();
+
+private:
+ LifeFrame *m_parent;
+};
+
+// Life main frame
+class LifeFrame : public wxFrame
+{
+public:
+ // ctor and dtor
+ LifeFrame();
+ ~LifeFrame();
+
+ // member functions
+ void UpdateInfoText();
+
+ // event handlers
+ void OnMenu(wxCommandEvent& event);
+ void OnSlider(wxScrollEvent& event);
+ void OnNewGame();
+ void OnStart();
+ void OnStop();
+ void OnTimer();
+
+private:
+ // any class wishing to process wxWindows events must use this macro
+ DECLARE_EVENT_TABLE()
+
+ Life *m_life;
+ LifeTimer *m_timer;
+ LifeCanvas *m_canvas;
+ wxStaticText *m_text;
+ bool m_running;
+ long m_interval;
+ long m_tics;
+};
+
+// Life new game dialog
+class LifeNewGameDialog : public wxDialog
+{
+public:
+ // ctor
+ LifeNewGameDialog(wxWindow *parent, int *w, int *h);
+
+ // event handlers
+ void OnOK(wxCommandEvent& event);
+ void OnCancel(wxCommandEvent& event);
+
+private:
+ // any class wishing to process wxWindows events must use this macro
+ DECLARE_EVENT_TABLE();
+
+ int *m_w;
+ int *m_h;
+ wxSpinCtrl *m_spinctrlw;
+ wxSpinCtrl *m_spinctrlh;
+};
+
+// Life app
+class LifeApp : public wxApp
+{
+public:
+ virtual bool OnInit();
+};
+
+
+// --------------------------------------------------------------------------
+// constants
+// --------------------------------------------------------------------------
+
+// IDs for the controls and the menu commands
+enum
+{
+ // menu items and toolbar buttons
+ ID_NEWGAME = 101,
+ ID_CLEAR,
+ ID_START,
+ ID_STOP,
+ ID_ABOUT,
+ ID_EXIT,
+
+ // slider
+ ID_SLIDER
+};
+
+// --------------------------------------------------------------------------
+// event tables and other macros for wxWindows
+// --------------------------------------------------------------------------
+
+// Event tables
+
+BEGIN_EVENT_TABLE(LifeFrame, wxFrame)
+ EVT_MENU_RANGE (ID_NEWGAME, ID_ABOUT, LifeFrame::OnMenu)
+ EVT_COMMAND_SCROLL (ID_SLIDER, LifeFrame::OnSlider)
+END_EVENT_TABLE()
+
+BEGIN_EVENT_TABLE(LifeCanvas, wxScrolledWindow)
+ EVT_PAINT ( LifeCanvas::OnPaint)
+ EVT_SIZE ( LifeCanvas::OnSize)
+ EVT_MOUSE_EVENTS ( LifeCanvas::OnMouse)
+END_EVENT_TABLE()
+
+BEGIN_EVENT_TABLE(LifeNewGameDialog, wxDialog)
+ EVT_BUTTON (wxID_OK, LifeNewGameDialog::OnOK)
+ EVT_BUTTON (wxID_CANCEL, LifeNewGameDialog::OnCancel)
+END_EVENT_TABLE()
+
+
+// Create a new application object
+IMPLEMENT_APP(LifeApp)
+
+// ==========================================================================
+// implementation
+// ==========================================================================
+
+// --------------------------------------------------------------------------
+// LifeApp
+// --------------------------------------------------------------------------
+
+// `Main program' equivalent: the program execution "starts" here
+bool LifeApp::OnInit()
+{
+ // create the main application window
+ LifeFrame *frame = new LifeFrame();
+
+ // show it and tell the application that it's our main window
+ frame->Show(TRUE);
+ SetTopWindow(frame);
+
+ // enter the main message loop and run the app
+ return TRUE;
+}
+
+// --------------------------------------------------------------------------
+// LifeFrame
+// --------------------------------------------------------------------------
+
+// frame constructor
+LifeFrame::LifeFrame() : wxFrame((wxFrame *)0, -1, _("Life!"), wxPoint(50, 50))
+{
+ // frame icon
+ SetIcon(wxICON(mondrian));
+
+ // menu bar
+ wxMenu *menuFile = new wxMenu("", wxMENU_TEAROFF);
+
+ menuFile->Append(ID_NEWGAME, _("&New game...\tCtrl-N"), _("Start a new game"));
+ menuFile->Append(ID_CLEAR, _("&Clear\tCtrl-C"), _("Clear game board"));
+ menuFile->Append(ID_START, _("&Start\tCtrl-S"), _("Start"));
+ menuFile->Append(ID_STOP, _("S&top\tCtrl-T"), _("Stop"));
+ menuFile->AppendSeparator();
+ menuFile->Append(ID_ABOUT, _("&About...\tCtrl-A"), _("Show about dialog"));
+ menuFile->AppendSeparator();
+ menuFile->Append(ID_EXIT, _("E&xit\tAlt-X"), _("Quit this program"));
+ menuFile->Enable(ID_STOP, FALSE);
+
+ wxMenuBar *menuBar = new wxMenuBar();
+ menuBar->Append(menuFile, _("&File"));
+ SetMenuBar(menuBar);
+
+ // tool bar
+ wxBitmap tbBitmaps[3];
+ tbBitmaps[0] = wxBITMAP(reset);
+ tbBitmaps[1] = wxBITMAP(play);
+ tbBitmaps[2] = wxBITMAP(stop);
+
+ wxToolBar *toolBar = CreateToolBar();
+ toolBar->SetMargins(5, 5);
+ toolBar->SetToolBitmapSize(wxSize(16, 16));
+ ADD_TOOL(ID_CLEAR, tbBitmaps[0], _("Clear"), _("Clear game board"));
+ ADD_TOOL(ID_START, tbBitmaps[1], _("Start"), _("Start"));
+ ADD_TOOL(ID_STOP , tbBitmaps[2], _("Stop"), _("Stop"));
+ toolBar->EnableTool(ID_STOP, FALSE);
+ toolBar->Realize();
+
+ // status bar
+ CreateStatusBar(2);
+ SetStatusText(_("Welcome to Life!"));
+
+ // panel
+ wxPanel *panel = new wxPanel(this, -1);
+
+ // game
+ m_life = new Life(20, 20);
+ m_canvas = new LifeCanvas(panel, m_life);
+ m_timer = new LifeTimer(this);
+ m_interval = 500;
+ m_tics = 0;
+ m_text = new wxStaticText(panel, -1, "");
+ UpdateInfoText();
+
+ // slider
+ wxSlider *slider = new wxSlider(panel, ID_SLIDER, 5, 1, 10,
+ wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL | wxSL_AUTOTICKS);
+
+ // component layout
+ wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
+ sizer->Add(m_canvas, 1, wxGROW | wxCENTRE | wxALL, 5);
+ sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE);
+ sizer->Add(m_text, 0, wxCENTRE | wxNORTH, 5);
+ sizer->Add(slider, 0, wxCENTRE | wxALL, 5);
+ panel->SetSizer(sizer);
+ panel->SetAutoLayout(TRUE);
+ sizer->Fit(this);
+ sizer->SetSizeHints(this);
+}
+
+LifeFrame::~LifeFrame()
+{
+ delete m_timer;
+ delete m_life;
+}
+
+void LifeFrame::UpdateInfoText()
+{
+ wxString msg;
+
+ msg.Printf(_("Generation: %u, Interval: %u ms"), m_tics, m_interval);
+ m_text->SetLabel(msg);
+}
+
+// event handlers
+void LifeFrame::OnMenu(wxCommandEvent& event)
+{
+ switch (event.GetId())
+ {
+ case ID_START : OnStart(); break;
+ case ID_STOP : OnStop(); break;
+ case ID_NEWGAME : OnNewGame(); break;
+ case ID_CLEAR :
+ {
+ OnStop();
+ m_life->Clear();
+ m_canvas->DrawEverything(TRUE);
+ m_canvas->Refresh(FALSE);
+ m_tics = 0;
+ UpdateInfoText();
+ break;
+ }
+ case ID_ABOUT :
+ {
+ wxMessageBox(
+ _("This is the about dialog of the Life! sample.\n"
+ "(c) 2000 Guillermo Rodriguez Garcia"),
+ _("About Life!"),
+ wxOK | wxICON_INFORMATION,
+ this);
+ break;
+ }
+ case ID_EXIT :
+ {
+ // TRUE is to force the frame to close
+ Close(TRUE);
+ break;
+ }
+ }
+}
+
+void LifeFrame::OnSlider(wxScrollEvent& event)
+{
+ m_interval = event.GetPosition() * 100;
+
+ // restart timer if running, to set the new interval
+ if (m_running)
+ {
+ m_timer->Stop();
+ m_timer->Start(m_interval);
+ }
+
+ UpdateInfoText();
+}
+
+void LifeFrame::OnNewGame()
+{
+ int w = m_life->GetWidth();
+ int h = m_life->GetHeight();
+ int result;
+
+ // stop if it was running
+ OnStop();
+
+ // show dialog box
+ LifeNewGameDialog dialog(this, &w, &h);
+ result = dialog.ShowModal();
+
+ // create new game
+ if (result == wxID_OK)
+ {
+ // check dimensions
+ if (w >= LIFE_MIN && w <= LIFE_MAX &&
+ h >= LIFE_MIN && h <= LIFE_MAX)
+ {
+ m_life->Destroy();
+ m_life->Create(w, h);
+ m_canvas->Reset();
+ m_tics = 0;
+ UpdateInfoText();
+ }
+ else
+ {
+ wxString msg;
+ msg.Printf(_("Both dimensions must be within %u and %u.\n"),
+ LIFE_MIN, LIFE_MAX);
+ wxMessageBox(msg, _("Error!"), wxOK | wxICON_EXCLAMATION, this);
+ }
+ }
+}
+
+void LifeFrame::OnStart()
+{
+ GetToolBar()->EnableTool(ID_START, FALSE);
+ GetToolBar()->EnableTool(ID_STOP, TRUE);
+ GetMenuBar()->GetMenu(0)->Enable(ID_START, FALSE);
+ GetMenuBar()->GetMenu(0)->Enable(ID_STOP, TRUE);
+
+ m_timer->Start(m_interval);
+ m_running = TRUE;
+}
+
+void LifeFrame::OnStop()
+{
+ GetToolBar()->EnableTool(ID_START, TRUE);
+ GetToolBar()->EnableTool(ID_STOP, FALSE);
+ GetMenuBar()->GetMenu(0)->Enable(ID_START, TRUE);
+ GetMenuBar()->GetMenu(0)->Enable(ID_STOP, FALSE);
+
+ m_timer->Stop();
+ m_running = FALSE;
+}
+
+void LifeFrame::OnTimer()
+{
+ m_tics++;
+ UpdateInfoText();
+
+ m_life->NextTic();
+ m_canvas->DrawEverything();
+ m_canvas->Refresh(FALSE);
+}
+
+// --------------------------------------------------------------------------
+// LifeTimer
+// --------------------------------------------------------------------------
+
+LifeTimer::LifeTimer(LifeFrame *parent) : wxTimer()
+{
+ m_parent = parent;
+}
+
+void LifeTimer::Notify()
+{
+ m_parent->OnTimer();
+}
+
+// --------------------------------------------------------------------------
+// LifeCavas
+// --------------------------------------------------------------------------
+
+// canvas constructor
+LifeCanvas::LifeCanvas(wxWindow *parent, Life *life)
+ : wxScrolledWindow(parent, -1, wxPoint(0, 0), wxSize(100, 100))
+{
+ m_life = life;
+ m_cellsize = 8;
+ Reset();
+}
+
+LifeCanvas::~LifeCanvas()
+{
+ delete m_bmp;
+}
+
+void LifeCanvas::Reset()
+{
+ if (m_bmp)
+ delete m_bmp;
+
+ m_status = MOUSE_NOACTION;
+ m_width = CellToCoord(m_life->GetWidth()) + 1;
+ m_height = CellToCoord(m_life->GetHeight()) + 1;
+ m_bmp = new wxBitmap(m_width, m_height);
+ wxCoord w = GetSize().GetX();
+ wxCoord h = GetSize().GetY();
+ m_xoffset = (w > m_width)? ((w - m_width) / 2) : 0;
+ m_yoffset = (h > m_height)? ((h - m_height) / 2) : 0;
+
+ // redraw all, incl. background
+ DrawEverything(TRUE);
+ SetScrollbars(10, 10, (m_width + 9) / 10, (m_height + 9) / 10);
+}
+
+// draw everything
+void LifeCanvas::DrawEverything(bool force)
+{
+ wxMemoryDC dc;
+
+ dc.SelectObject(*m_bmp);
+ dc.BeginDrawing();
+
+ // cells
+ for (int j = 0; j < m_life->GetHeight(); j++)
+ for (int i = 0; i < m_life->GetWidth(); i++)
+ if (force || m_life->HasChanged(i, j))
+ DrawCell(i, j, dc);
+
+ // bounding rectangle (always drawn)
+ dc.SetPen(*wxBLACK_PEN);
+ dc.SetBrush(*wxTRANSPARENT_BRUSH);
+ dc.DrawRectangle(0, 0, m_width, m_height);
+
+ dc.EndDrawing();
+ dc.SelectObject(wxNullBitmap);
+}
+
+// draw a single cell
+void LifeCanvas::DrawCell(int i, int j)
+{
+ wxMemoryDC dc;
+
+ dc.SelectObject(*m_bmp);
+ dc.BeginDrawing();
+
+ DrawCell(i, j, dc);
+
+ dc.EndDrawing();
+ dc.SelectObject(wxNullBitmap);
+}
+
+void LifeCanvas::DrawCell(int i, int j, wxDC &dc)
+{
+ if (m_life->IsAlive(i, j))
+ {
+ dc.SetPen(*wxBLACK_PEN);
+ dc.SetBrush(*wxBLACK_BRUSH);
+ dc.DrawRectangle(CellToCoord(i),
+ CellToCoord(j),
+ m_cellsize,
+ m_cellsize);
+ }
+ else
+ {
+ dc.SetPen(*wxLIGHT_GREY_PEN);
+ dc.SetBrush(*wxTRANSPARENT_BRUSH);
+ dc.DrawRectangle(CellToCoord(i),
+ CellToCoord(j),
+ m_cellsize,
+ m_cellsize);
+ dc.SetPen(*wxWHITE_PEN);
+ dc.SetBrush(*wxWHITE_BRUSH);
+ dc.DrawRectangle(CellToCoord(i) + 1,
+ CellToCoord(j) + 1,
+ m_cellsize - 1,
+ m_cellsize - 1);
+ }
+}
+
+// event handlers
+void LifeCanvas::OnPaint(wxPaintEvent& event)
+{
+ wxPaintDC dc(this);
+ wxMemoryDC memdc;
+
+ wxRegionIterator upd(GetUpdateRegion());
+ int x, y, w, h, xx, yy;
+
+ dc.BeginDrawing();
+ memdc.SelectObject(*m_bmp);
+
+ while(upd)
+ {
+ x = upd.GetX();
+ y = upd.GetY();
+ w = upd.GetW();
+ h = upd.GetH();
+ CalcUnscrolledPosition(x, y, &xx, &yy);
+
+ dc.Blit(x, y, w, h, &memdc, xx - m_xoffset, yy - m_yoffset);
+ upd++;
+ }
+
+ memdc.SelectObject(wxNullBitmap);
+ dc.EndDrawing();
+}
+
+void LifeCanvas::OnMouse(wxMouseEvent& event)
+{
+ int x, y, xx, yy, i, j;
+
+ // which cell are we pointing at?
+ x = event.GetX();
+ y = event.GetY();
+ CalcUnscrolledPosition(x, y, &xx, &yy);
+ i = CoordToCell( xx - m_xoffset );
+ j = CoordToCell( yy - m_yoffset );
+
+ // adjust x, y to point to the upper left corner of the cell
+ CalcScrolledPosition( CellToCoord(i) + m_xoffset,
+ CellToCoord(j) + m_yoffset,
+ &x, &y );
+
+ // set cursor shape and statusbar text
+ if (i < 0 || i >= m_life->GetWidth() ||
+ j < 0 || j >= m_life->GetHeight())
+ {
+ GET_FRAME()->SetStatusText(wxEmptyString, 1);
+ SetCursor(*wxSTANDARD_CURSOR);
+ }
+ else
+ {
+ wxString msg;
+ msg.Printf(_("Cell: (%u, %u)"), i, j);
+ GET_FRAME()->SetStatusText(msg, 1);
+ SetCursor(*wxCROSS_CURSOR);
+ }
+
+ // button pressed?
+ if (!event.LeftIsDown())
+ {
+ m_status = MOUSE_NOACTION;
+ }
+ else if (i >= 0 && i < m_life->GetWidth() &&
+ j >= 0 && j < m_life->GetHeight())
+ {
+ bool alive = m_life->IsAlive(i, j);
+
+ // if just pressed, update status
+ if (m_status == MOUSE_NOACTION)
+ m_status = (alive? MOUSE_ERASING : MOUSE_DRAWING);
+
+ // toggle cell and refresh if needed
+ if (((m_status == MOUSE_ERASING) && alive) ||
+ ((m_status == MOUSE_DRAWING) && !alive))
+ {
+ wxRect rect(x, y, m_cellsize + 1, m_cellsize + 1);
+ m_life->SetCell(i, j, !alive);
+ DrawCell(i, j);
+ Refresh(FALSE, &rect);
+ }
+ }
+}
+
+void LifeCanvas::OnSize(wxSizeEvent& event)
+{
+ wxCoord w = event.GetSize().GetX();
+ wxCoord h = event.GetSize().GetY();
+ m_xoffset = (w > m_width)? ((w - m_width) / 2) : 0;
+ m_yoffset = (h > m_height)? ((h - m_height) / 2) : 0;
+
+ // allow default processing
+ event.Skip();
+}
+
+// --------------------------------------------------------------------------
+// LifeNewGameDialog
+// --------------------------------------------------------------------------
+
+LifeNewGameDialog::LifeNewGameDialog(wxWindow *parent, int *w, int *h)
+ : wxDialog(parent, -1, _("New game"),
+ wxDefaultPosition, wxDefaultSize,
+ wxDEFAULT_DIALOG_STYLE | wxDIALOG_MODAL)
+{
+ m_w = w;
+ m_h = h;
+
+ wxBoxSizer *topsizer = new wxBoxSizer( wxVERTICAL );
+
+ // text message
+ topsizer->Add( CreateTextSizer(_("Enter board dimensions")), 0, wxALL, 10 );
+ topsizer->Add( new wxStaticLine(this, -1), 0, wxGROW | wxLEFT | wxRIGHT | wxBOTTOM, 10);
+
+ // prompts and text controls
+ wxString strw, strh;
+ strw.Printf(_("%u"), *m_w);
+ strh.Printf(_("%u"), *m_h);
+ m_spinctrlw = new wxSpinCtrl( this, -1, strw );
+ m_spinctrlh = new wxSpinCtrl( this, -1, strh );
+
+ wxBoxSizer *inputsizer1 = new wxBoxSizer( wxHORIZONTAL );
+ inputsizer1->Add( new wxStaticText(this, -1, _("Width")), 1, wxCENTER | wxLEFT, 20);
+ inputsizer1->Add( m_spinctrlw, 2, wxCENTER | wxLEFT | wxRIGHT, 20 );
+ wxBoxSizer *inputsizer2 = new wxBoxSizer( wxHORIZONTAL );
+ inputsizer2->Add( new wxStaticText(this, -1, _("Height")), 1, wxCENTER | wxLEFT, 20);
+ inputsizer2->Add( m_spinctrlh, 2, wxCENTER | wxLEFT | wxRIGHT, 20 );
+
+ topsizer->Add( inputsizer1, 1, wxGROW | wxLEFT | wxRIGHT, 5 );
+ topsizer->Add( inputsizer2, 1, wxGROW | wxLEFT | wxRIGHT, 5 );
+ topsizer->Add( new wxStaticLine(this, -1), 0, wxGROW | wxLEFT | wxRIGHT | wxTOP, 10);
+
+ // buttons
+ topsizer->Add( CreateButtonSizer(wxOK | wxCANCEL), 0, wxCENTRE | wxALL, 10);
+
+ // activate
+ SetSizer(topsizer);
+ SetAutoLayout(TRUE);
+ topsizer->SetSizeHints(this);
+ topsizer->Fit(this);
+ Centre(wxBOTH);
+}
+
+void LifeNewGameDialog::OnOK(wxCommandEvent& WXUNUSED(event))
+{
+ *m_w = m_spinctrlw->GetValue();
+ *m_h = m_spinctrlh->GetValue();
+
+ EndModal(wxID_OK);
+}
+
+void LifeNewGameDialog::OnCancel(wxCommandEvent& WXUNUSED(event))
+{
+ *m_w = -1;
+ *m_h = -1;
+
+ EndModal(wxID_CANCEL);
+}
+
+// --------------------------------------------------------------------------
+// Life
+// --------------------------------------------------------------------------
+
+Life::Life(int width, int height)
+{
+ Create(width, height);
+}
+
+Life::~Life()
+{
+ Destroy();
+}
+
+void Life::Create(int width, int height)
+{
+ wxASSERT(width > 0 || height > 0);
+
+ m_width = width;
+ m_height = height;
+ m_cells = new Cell[m_width * m_height];
+ Clear();
+}
+
+void Life::Destroy()
+{
+ delete[] m_cells;
+}
+
+void Life::Clear()
+{
+ for (int i = 0; i < m_width * m_height; i++)
+ m_cells[i] = CELL_DEAD;
+}
+
+bool Life::IsAlive(int x, int y) const
+{
+ wxASSERT(x < m_width || y < m_height);
+
+ return (m_cells[y * m_width + x] & CELL_ALIVE);
+}
+
+bool Life::HasChanged(int x, int y) const
+{
+ wxASSERT(x < m_width || y < m_height);
+
+ return (m_cells[y * m_width + x] & CELL_MARK);
+}
+
+void Life::SetCell(int x, int y, bool alive)
+{
+ wxASSERT(x < m_width || y < m_height);
+
+ // set the CELL_MARK flag to notify that this cell has changed
+ m_cells[y * m_width + x] = (alive? CELL_ALIVE : CELL_DEAD);
+}
+
+void Life::NextTic()
+{
+ /* 1st pass. Find and mark deaths and births for this generation.
+ *
+ * Rules:
+ * An organism with <= 1 neighbors will die due to isolation.
+ * An organism with >= 4 neighbors will die due to starvation.
+ * New organisms are born in cells with exactly 3 neighbors.
+ */
+ for (int j = 0; j < m_height; j++)
+ for (int i = 0; i < m_width; i++)
+ {
+ int neighbors = GetNeighbors(i, j);
+ bool alive = IsAlive(i, j);
+
+ /* Set CELL_MARK if this cell must change, clear it
+ * otherwise. We cannot toggle the CELL_ALIVE bit yet
+ * because all deaths and births are simultaneous (it
+ * would affect neighbouring cells).
+ */
+ if ((!alive && neighbors == 3) ||
+ (alive && (neighbors <= 1 || neighbors >= 4)))
+ m_cells[j * m_width + i] |= CELL_MARK;
+ else
+ m_cells[j * m_width + i] &= ~CELL_MARK;
+ }
+
+ /* 2nd pass. Stabilize.
+ */
+ for (int j = 0; j < m_height; j++)
+ for (int i = 0; i < m_width; i++)
+ {
+ /* Toggle CELL_ALIVE for those cells marked in the
+ * previous pass. Do not clear the CELL_MARK bit yet;
+ * it is useful to know which cells have changed and
+ * thus must be updated in the screen.
+ */
+ if (m_cells[j * m_width + i] & CELL_MARK)
+ m_cells[j * m_width + i] ^= CELL_ALIVE;
+ }
+}
+
+int Life::GetNeighbors(int x, int y) const
+{
+ wxASSERT(x < m_width || y < m_height);
+
+ // count number of neighbors (wrap around board limits)
+ int neighbors = 0;
+ for (int j = y - 1; j <= y + 1; j++)
+ for (int i = x - 1; i <= x + 1; i++)
+ {
+ if (IsAlive( ((i < 0)? (i + m_width ) : (i % m_width)),
+ ((j < 0)? (j + m_height) : (j % m_height)) ))
+ neighbors++;
+ }
+
+ // do not count ourselves
+ if (IsAlive(x, y)) neighbors--;
+
+ return neighbors;
+}
+
+void Life::SetCell(int x, int y, Cell status)
+{
+ wxASSERT(x < m_width || y < m_height);
+
+ m_cells[y * m_width + x] = status;
+}
+