]> git.saurik.com Git - wxWidgets.git/commitdiff
Life! version 2
authorGuillermo Rodriguez Garcia <guille@iies.es>
Tue, 8 Feb 2000 20:04:48 +0000 (20:04 +0000)
committerGuillermo Rodriguez Garcia <guille@iies.es>
Tue, 8 Feb 2000 20:04:48 +0000 (20:04 +0000)
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@5912 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

14 files changed:
demos/life/bitmaps/life.bmp [new file with mode: 0644]
demos/life/bitmaps/life.xpm [new file with mode: 0644]
demos/life/bitmaps/zoomin.bmp [new file with mode: 0644]
demos/life/bitmaps/zoomin.xpm [new file with mode: 0644]
demos/life/bitmaps/zoomout.bmp [new file with mode: 0644]
demos/life/bitmaps/zoomout.xpm [new file with mode: 0644]
demos/life/dialogs.cpp
demos/life/dialogs.h
demos/life/game.cpp
demos/life/game.h
demos/life/life.cpp
demos/life/life.h
demos/life/life.rc
demos/life/samples.inc

diff --git a/demos/life/bitmaps/life.bmp b/demos/life/bitmaps/life.bmp
new file mode 100644 (file)
index 0000000..46fd003
Binary files /dev/null and b/demos/life/bitmaps/life.bmp differ
diff --git a/demos/life/bitmaps/life.xpm b/demos/life/bitmaps/life.xpm
new file mode 100644 (file)
index 0000000..60328e3
--- /dev/null
@@ -0,0 +1,81 @@
+/* XPM */\r
+static char * life_xpm[] = {\r
+"151 73 5 1",\r
+"      c None",\r
+".     c #808080",\r
+"X     c Black",\r
+"o     c Gray100",\r
+"O     c #c0c0c0",\r
+"                                                                                                                                           .X          ",\r
+"                                                                                                                                        .XXo..         ",\r
+"                                                                                                                                     .XXOooooX         ",\r
+"                                                                                                                                  .XXooOoooooX         ",\r
+"                                                                                                                               XXXoooooOoooooX         ",\r
+"                                                                                                                           .XXXoooOoooooOooooX.        ",\r
+"                                                                                                                        .XXoOoooooOoooooOoooOOX        ",\r
+"                                                                                                                    .XXXooooOoooooOoooooOOOoooX        ",\r
+"                                                                                                                 .XXooOoooooOoooooOoooOOOoooooX        ",\r
+"                                                                                                             .XXXooooooooooooooooo.OOoooOoooooX        ",\r
+"                                                                                                          .XXoooOooooooOoooooOo.XXXOoooooOoooo..       ",\r
+"                                                                                                      ..XXooooooOooooooOoooOXXXXXXX.oooooOoooooX       ",\r
+"                                                                                                   .XXooooooooooOooooooO.XXXXXXXXXXXoooooOoooOOX       ",\r
+"                                                                                                .XXooOooooooooooOooo.XXXXXXXXXXXXXXXoooooOOOOooX       ",\r
+"                                                                                            .XXOoooooOoooooOoooo.XXXXXXXXXXXXXXXXXXXooOOOOoooooX.      ",\r
+"                                                                                         .XXoooOoooooOoooooOo.XXXXXXXXXXXXXXXXXXXXXXOOoooOOoooooX      ",\r
+"                                                                                     .XXoOoooooOoooooOooooOXXXXXXXXXXXXXXXXXXXXXX.oOooooooOoooooX      ",\r
+"                                                                                 .XXOooooOoooooOoooooOOOOooXXXXXXXXXXXXXXXXXX.OoooooOoooooOoooooX      ",\r
+"                                                                             .XXOooooooooOoooooOoo.XX.oooooXXXXXXXXXXXXXXX.oooOoooooOoooooOoooOOX.     ",\r
+"                                                                          .XX.ooooooooooOoooooOXXXXXXOoooooXXXXXXXXXXX.OOoooooOoooooOoooooOOOooo.X     ",\r
+"                                                                      .XXOooOoooooOoooooOoO.XXXXXXXXXOoooooXXXXXXXXoooooOoooooOoooooOooOOoOOoooooX     ",\r
+"                                                                   XXXOoooooOoooooOoooo.XXXXXXXXXXXXXOoooooXXXXXXXooooooOoooooOooooOOOoooooOoooooX     ",\r
+"                                                              .XXXooooOoooooOooooo..XXXXXXXXXXXXXXXXXOoooOOXXXXXXXooooooOoooooOOOOooOooooooOoooooX     ",\r
+"                                                          ..XXooOoooooOoooooOooOXXXXXXXXXXXXXXXXXXXXX.OOoooXXXXXXXooooooOoo.XXXooooooOoooooOoooooX.    ",\r
+"                                                      ..XXOoooooOoooooOoooooXXXXXXXXXXXXXXXXXXXXXXXOoOoooooXXXXXXXoooooOXXXXXXXooooooOoooooOoooOOOX    ",\r
+"                                                  ..XXoooOooooooooooooooOOooXXXXXXXXXXXXXXXXXXX.oooooOoooooXXXXXXXo.XXXXXXXXXXXOoooooOoooooOOOooooX    ",\r
+"                                              ..XXoOoooooOoooooOoooO.XOoooooXXXXXXXXXXXXXXX.OoOooooooOoooooXXXXXXXXXXXXXXXXXXXXXoooooOoOOOOoOoooooX    ",\r
+"                                          ..XX.ooooOoooooOooooo..XXXXXoooooOXXXXXXXXXXXXOoooooOooooooOoooooXXXXXXXXXXXXXXXXXXXXXooooOOooooooOoooooX.   ",\r
+"                                      ..XXoooOoooooOoooooOooOOoXXXXXXXooooo.XXXXXXX.OoooOoooooOooooooOooOOOXXXXXXXXXXXXXXXXXXXXXOOOooOooooooOooooo.X   ",\r
+"                                  .XXOOoooooOoooooOoooooOOOooooXXXXXXXooooo.XXXXXXooooooOoooooOoooooOOoooooXXXXXXXXXXXXXXXXXXXOOooooooOoooooOooooooX   ",\r
+"                              .XXOoooOOoooooOoooooOoOOooOooooooXXXXXX.ooooOXXXXXXXooooooOoooooOoOOOooOoooooXXXXXXXXXXXXXXX.ooooOooooooOoooooOoooOOoX   ",\r
+"                         ..XX.oOoooooOooooooOoooOOOoooooOooooo.XXXXXXOOOOooXXXXXXXooooooOooo.XXooooooOoooooXXXXXXXXXX.OooOoooooOooooooOoooooOOoooooX.  ",\r
+"                     ..XXooooooOoooooOoooooOOOooooooooooOoooooXXXXXXXOoooooXXXXXXXoooooOXXXXXXXooooooOoooooXXXXXXX.ooooooOoooooOooooooOoOOOooOooooo.X  ",\r
+"                   .XXXoOoooooOoooooOoooooOOoooooOooooooOoooooXXXXXXXooooooXXXXXXXoooO.XXXXXXXXooooooOoooooXXXXXXX.ooooooOooooooOoooo.XOoooooOooooo.X  ",\r
+"                  XoooooOoooooOoooooXOOooooOoooooOoooooOoooOOOXXXXXXXooooooXXXXXXX.XXXXXXXXXXXXooooooOoooOOXXXXXXXXooooooOooooooO.XXXXXooooooOooooooX  ",\r
+"                 XoooooOooooooOo.XXX.ooooooOoooooOoooooOOOooooXXXXXXXoooooOXXXXXXXXXXXXXXXXXXXXoooooOOOOoooXXXXXXXXooooooOooo.XXXXXXXXXOoooooOooooooX  ",\r
+"                 XoooooOooooO.XXXXXXooooooOooooooOoOOooOooooo.XXXXXXXooooo.XXXXXXXXXXXXXXXXXXXXooOOOoOooooo.XXXXXXXooooooXXXXXXXXXXXXXX.oooooOOoooOOX. ",\r
+"                ..oooooOOOoooXXXXXXXooooooOooooOOOoooooOoooooXXXXXXX.OOOoo.XXXXXXXXXXXXXXXXXXXOOoooooOooooo.XXXXXXXoO.XXXXXXXXXXXXXXXXXXooooooOOOooo.X ",\r
+"                XooOOOOoooooOXXXXXX.ooooooOOOoooOoooooOooooooXXXXXXXOoooooXXXXXXXXXXXXXXXXXoooOooooooOooooo.XXXXXXXXXXXXXXXXXXXXXXXXXXXXooOOOoOooooooX ",\r
+"               .XoooooOoooooXXXXXXXooOOOOOooooooOoooooOooooooXXXXXXXooooooXXXXXXXXXXXX.OooooooOooooooOooooo.XXXXXXXXXXXXXXXXXXXXXXXXXXXXOoooooOooooooX ",\r
+"               XoooooOooooooXXXXXXXOoooooOoooooOooooooOoOOOooXXXXXXXooooooXXXXXXXX.ooooOooooooOooooooOooOOO.XXXXXXXXXXXXXXXXXXXXXXXX.ooOooooooOooooooX ",\r
+"              .XoooooOooooo.XXXXXX.ooooooOoooooOooooOOOooooo.XXXXXXXooooooXXXXXXXooooooOooooooOoooooOOOoooo.XXXXXXXXXXXXXXXXXXX.OOoooooOOoooooOOoooooX.",\r
+"              XoooooOoooOOOXXXXXXXooooooOooooooOOOoooOOoooooXXXXXXXXoooOOOXXXXXXXooooooOooooooOoOOoooOooooo.XXXXXXXXXXXXXXXXoooooOooooooOooooooOoo.XXXX",\r
+"              XooooOOOoooooXXXXXXXooooooOooOOooOoooooOooooooXXXXXXX.OOooo.XXXXXXXooooooOoooOOOOooooooOooooooXXXXXXXXXXX.ooOooooooOooooooOooooO.XXXX    ",\r
+"             XOOOoooOoooooXXXXXXX.ooooOOOoooooOooooooOooooooXXXXXXXOooooo.XXXXXXXooooooOOoooooOooooooOooooooXXXXXX.OooooooOooooooOooooooXO.XXX.        ",\r
+"             XoooooOooooooXXXXXXXOOOoooOOoooooOooooooOooooOOXXXXXXXooooooXXXXXXXXoOOOooOooooooOooooooOoooooOXXOoooOOooooooOooooooOooo.XXXX.            ",\r
+"            XooooooOooooo.XXXXXXXooooooOooooooOoooooOOOOooo.XXXXXXXooooooXXXXXXX.ooooooOooooooOooooooOoOOOooOooooooOooooooOoooooOXXXX.                 ",\r
+"            XoooooOoooooOXXXXXXX.ooooooOooooooOooOOOOooooooXXXXXXXXooooooXXXXXXX.ooooooOooooooOooooOOOooooooOooooooOooooooOO.XXXX.                     ",\r
+"           ..oooooOOOOoooXXXXXXXoooooooOooooOOOoooooOooooooXXXXXXXXooOOooXXXXXXXOoooooOOoooooOOOOOoooOooooooOooooooOoooO.XXX..                         ",\r
+"           XoooOOOOooooo.XXXXXXXooooooOOOOoooOooooooOooooooXXXXXXX.oooooOXXXXXXXOoooooOOooOOOoOooooooOooooooOoooooOXXXXX.                              ",\r
+"          .XoooooOooooooXXXXXXX.ooOOOOOooooooOooooooOoooooOXXXXXXXooooooOXXXXXXXoooooOOOooooooOooooooOooooooOo.XXXX.                                   ",\r
+"          XooooooOoooooOXXXXXXXOoooooOOoooooOooooooOoooOOo.XXXXXXXoooooo.XXXXXXXOOOOooOoooooooOooooooOoooO.XXXX.                                       ",\r
+"         .XoooooOooooooXXXXXXXXooooooOooooooOooooO.XooooooXXXXXXXXoooooo.XXXXXOOooooooOooooooooooooo.XXXXX.                                            ",\r
+"         XooooooOoooOOOXXXXXXX.ooooooOoooooo..XXXXXXooooooXXXXXXXXoooooOXOoooooOooooooOoooooooooO.XXX.                                                 ",\r
+"         XoooooOOOoooo.XXXXXXXooooooOooo.XXXXXXXXXX.ooooooXXXXXXXXOOOoooOooooooOooooooOooooO.XXX.                                                      ",\r
+"        XOOOOooOooooooXXXXXXXXooooO.XXXXXXXXXXXXXXXooooooOXXXXXXOOooooooOooooooOooooooX.XXXX.                                                          ",\r
+"        XooooooOooooooXXXXXXXX.XXXXXXXXXXXXXXXXXXXXooooooOXOooooOoooooooOooooooOoo.XXXX.                                                               ",\r
+"       X.oooooOoooooo.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXoOOOooOooooooOooooooOOoooo.XXXX.                                                                    ",\r
+"       XooooooOoooooOXXXXXXXXXXXXXXXXXXXXXXXXXXX.OOooooooOooooooOoooooo...XXX.                                                                         ",\r
+"      X.oooooOOOOoooOXXXXXXXXXXXXXXXXXXXXXX.OooooOOooooooOooooooOoo..XXX.                                                                              ",\r
+"      XoooOOoOooooooXXXXXXXXXXXXXXXXXX.OooOooooooOooooooOOoooo.XXXX.                                                                                   ",\r
+"     .XoooooOOooooooXXXXXXXXXXXXXOOoooooooOooooooOooooooX.XXXX.                                                                                        ",\r
+"     XooooooOoooooo.XXXXXXXXOoooooOooooooOoooooooOoo.XXXX.                                                                                             ",\r
+"    .XooooooOooooooXXXXoooOooooooOOooooooOooooO.XXXX.                                                                                                  ",\r
+"    XooooooOoooOOOoOooooooOooooooOoooooooX.XXXX.                                                                                                       ",\r
+"    XoooooOOOoooooOooooooOOooooooOoO.XXXX..                                                                                                            ",\r
+"   XXOOoooOOooooooOooooooOooooO.XXXX.                                                                                                                  ",\r
+"   XooooooOooooooOooooooOX.XXX.                                                                                                                        ",\r
+"  X.ooooooOooooooOoO.XXXX.                                                                                                                             ",\r
+"  XooooooOooooO.XXX..                                                                                                                                  ",\r
+" X.oooooOXXXXX.                                                                                                                                        ",\r
+" XOO.XXX..                                                                                                                                             ",\r
+".XX.                                                                                                                                                   "};\r
diff --git a/demos/life/bitmaps/zoomin.bmp b/demos/life/bitmaps/zoomin.bmp
new file mode 100644 (file)
index 0000000..af7521a
Binary files /dev/null and b/demos/life/bitmaps/zoomin.bmp differ
diff --git a/demos/life/bitmaps/zoomin.xpm b/demos/life/bitmaps/zoomin.xpm
new file mode 100644 (file)
index 0000000..a53be85
--- /dev/null
@@ -0,0 +1,23 @@
+/* XPM */\r
+static char * zoomin_xpm[] = {\r
+"16 16 3 1",\r
+"   c None",\r
+".     c Black",\r
+"X     c Gray100",\r
+"    ....        ",\r
+"  ..XXXX..      ",\r
+" .XXXXXXXX.     ",\r
+" .XXX..XXX.     ",\r
+".XXXX..XXXX.    ",\r
+".XX......XX.    ",\r
+".XX......XX.    ",\r
+".XXXX..XXXX.    ",\r
+" .XXX..XXX.     ",\r
+" .XXXXXXXX.     ",\r
+"  ..XXXX...     ",\r
+"    .... ...    ",\r
+"          ...   ",\r
+"           ...  ",\r
+"            ... ",\r
+"             .. "\r
+};\r
diff --git a/demos/life/bitmaps/zoomout.bmp b/demos/life/bitmaps/zoomout.bmp
new file mode 100644 (file)
index 0000000..764ed9e
Binary files /dev/null and b/demos/life/bitmaps/zoomout.bmp differ
diff --git a/demos/life/bitmaps/zoomout.xpm b/demos/life/bitmaps/zoomout.xpm
new file mode 100644 (file)
index 0000000..88ea038
--- /dev/null
@@ -0,0 +1,23 @@
+/* XPM */\r
+static char * zoomout_xpm[] = {\r
+"16 16 3 1",\r
+"   c None",\r
+".     c Black",\r
+"X     c Gray100",\r
+"    ....        ",\r
+"  ..XXXX..      ",\r
+" .XXXXXXXX.     ",\r
+" .XXXXXXXX.     ",\r
+".XXXXXXXXXX.    ",\r
+".XX......XX.    ",\r
+".XX......XX.    ",\r
+".XXXXXXXXXX.    ",\r
+" .XXXXXXXX.     ",\r
+" .XXXXXXXX.     ",\r
+"  ..XXXX...     ",\r
+"    .... ...    ",\r
+"          ...   ",\r
+"           ...  ",\r
+"            ... ",\r
+"             .. "\r
+};\r
index 9e01bbfad557fa120b66fa5a00da3e923371feaa..ab1dc23f1798531aabf1d533825c0c23abab01bb 100644 (file)
 /////////////////////////////////////////////////////////////////////////////
 
 // ==========================================================================
-// declarations
+// headers, declarations, constants
 // ==========================================================================
 
-// --------------------------------------------------------------------------
-// headers
-// --------------------------------------------------------------------------
-
 #ifdef __GNUG__
     #pragma implementation "dialogs.h"
 #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"
 
 #include "life.h"
 #include "game.h"
 
+// --------------------------------------------------------------------------
+// resources
+// --------------------------------------------------------------------------
+
+#if defined(__WXGTK__) || defined(__WXMOTIF__)
+    // logo for the about dialog
+    #include "bitmaps/life.xpm"
+#endif
+
+// sample configurations
+#include "samples.inc"
+
 // --------------------------------------------------------------------------
 // constants
 // --------------------------------------------------------------------------
 // IDs for the controls and the menu commands
 enum
 {
+    // bmp window in about dialog
+    ID_BMPWIN = 2001,
+
     // listbox in samples dialog
-    ID_LISTBOX = 2001
+    ID_LISTBOX
 };
 
-// sample configurations
-#include "samples.inc"
-
 // --------------------------------------------------------------------------
 // event tables and other macros for wxWindows
 // --------------------------------------------------------------------------
 
 // Event tables
-BEGIN_EVENT_TABLE(LifeNewGameDialog, wxDialog)
-    EVT_BUTTON  (wxID_OK,    LifeNewGameDialog::OnOK)
-END_EVENT_TABLE()
-
 BEGIN_EVENT_TABLE(LifeSamplesDialog, wxDialog)
     EVT_LISTBOX (ID_LISTBOX, LifeSamplesDialog::OnListBox)
+    
 END_EVENT_TABLE()
 
 
@@ -72,62 +65,6 @@ END_EVENT_TABLE()
 // implementation
 // ==========================================================================
 
-// --------------------------------------------------------------------------
-// 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;
-
-    // spin ctrls
-    m_spinctrlw = new wxSpinCtrl( this, -1 );
-    m_spinctrlw->SetValue(*m_w);
-    m_spinctrlw->SetRange(LIFE_MIN, LIFE_MAX);
-
-    m_spinctrlh = new wxSpinCtrl( this, -1 );
-    m_spinctrlh->SetValue(*m_h);
-    m_spinctrlh->SetRange(LIFE_MIN, LIFE_MAX);
-
-    // component layout
-    wxBoxSizer *inputsizer1 = new wxBoxSizer( wxHORIZONTAL );
-    inputsizer1->Add( new wxStaticText(this, -1, _("Width")), 1, wxCENTRE | wxLEFT, 20);
-    inputsizer1->Add( m_spinctrlw, 2, wxCENTRE | wxLEFT | wxRIGHT, 20 );
-
-    wxBoxSizer *inputsizer2 = new wxBoxSizer( wxHORIZONTAL );
-    inputsizer2->Add( new wxStaticText(this, -1, _("Height")), 1, wxCENTRE | wxLEFT, 20);
-    inputsizer2->Add( m_spinctrlh, 2, wxCENTRE | wxLEFT | wxRIGHT, 20 );
-
-    wxBoxSizer *topsizer = new wxBoxSizer( wxVERTICAL );
-    topsizer->Add( CreateTextSizer(_("Enter board dimensions")), 0, wxALL, 10 );
-    topsizer->Add( new wxStaticLine(this, -1), 0, wxGROW | wxLEFT | wxRIGHT | wxBOTTOM, 10);
-    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);
-    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);
-}
-
 // --------------------------------------------------------------------------
 // LifeSamplesDialog
 // --------------------------------------------------------------------------
@@ -153,7 +90,7 @@ LifeSamplesDialog::LifeSamplesDialog(wxWindow *parent)
 
     // descriptions
     wxStaticBox *statbox = new wxStaticBox( this, -1, _("Description"));
-    m_life   = new Life( 16, 16 );
+    m_life   = new Life();
     m_life->SetShape(g_shapes[0]);
     m_canvas = new LifeCanvas( this, m_life, FALSE );
     m_text   = new wxTextCtrl( this, -1,
@@ -183,30 +120,73 @@ LifeSamplesDialog::LifeSamplesDialog(wxWindow *parent)
     SetAutoLayout(TRUE);
     sizer3->SetSizeHints(this);
     sizer3->Fit(this);
-    Centre(wxBOTH);
+    Centre(wxBOTH | wxCENTRE_ON_SCREEN);
 }
 
 LifeSamplesDialog::~LifeSamplesDialog()
 {
     m_canvas->Destroy();
-    delete m_life;
 }
 
-int LifeSamplesDialog::GetValue()
+const LifeShape& LifeSamplesDialog::GetShape()
 {
-    return m_value;
+    return g_shapes[m_value];
 }
 
 void LifeSamplesDialog::OnListBox(wxCommandEvent& event)
 {
-    if (event.GetSelection() != -1)
+    int sel = event.GetSelection();
+
+    if (sel != -1)
     {
         m_value = m_list->GetSelection();
-        m_text->SetValue(g_shapes[ event.GetSelection() ].m_desc);
-        m_life->SetShape(g_shapes[ event.GetSelection() ]);
-
-        m_canvas->DrawEverything(TRUE);     // force redraw everything
-        m_canvas->Refresh(FALSE);           // do not erase background
+        m_text->SetValue(g_shapes[ sel ].m_desc);
+        m_life->SetShape(g_shapes[ sel ]);
+
+        // quick and dirty :-)
+        if ((g_shapes[ sel ].m_width > 36) ||
+            (g_shapes[ sel ].m_height > 22))
+            m_canvas->SetCellSize(2);
+        else
+            m_canvas->SetCellSize(8);
     }
 }
 
+// --------------------------------------------------------------------------
+// LifeAboutDialog
+// --------------------------------------------------------------------------
+
+LifeAboutDialog::LifeAboutDialog(wxWindow *parent)
+               : wxDialog(parent, -1,
+                          _("About Life!"),
+                          wxDefaultPosition,
+                          wxDefaultSize,
+                          wxDEFAULT_DIALOG_STYLE | wxDIALOG_MODAL)
+{
+    // logo
+    wxBitmap bmp = wxBITMAP(life);
+#if !defined(__WXGTK__) && !defined(__WXMOTIF__)
+    bmp.SetMask(new wxMask(bmp, *wxBLUE));
+#endif
+    wxStaticBitmap *sbmp = new wxStaticBitmap(this, -1, bmp);
+
+    // layout components
+    wxBoxSizer *sizer = new wxBoxSizer( wxVERTICAL );
+    sizer->Add( sbmp, 0, wxCENTRE | wxALL, 10 );
+    sizer->Add( new wxStaticLine(this, -1), 0, wxGROW | wxLEFT | wxRIGHT, 5 );
+    sizer->Add( CreateTextSizer(_("Life! for wxWindows, version 2.0\n\n"
+                                  "(c) 2000 Guillermo Rodriguez Garcia\n\n"
+                                  "<guille@iies.es>\n\n"
+                                  "Portions of the code are based in XLife\n"
+                                  "XLife is (c) 1989 by Jon Bennett et al.")),
+                                  0, wxCENTRE | wxALL, 20 );
+    sizer->Add( new wxStaticLine(this, -1), 0, wxGROW | wxLEFT | wxRIGHT, 5 );
+    sizer->Add( CreateButtonSizer(wxOK), 0, wxCENTRE | wxALL, 10 );
+
+    // activate
+    SetSizer(sizer);
+    SetAutoLayout(TRUE);
+    sizer->SetSizeHints(this);
+    sizer->Fit(this);
+    Centre(wxBOTH | wxCENTRE_ON_SCREEN);
+}
index 4c503cf4e3590749c0f30e0bec7e91dc4c0704f5..95c266eb832410ed8d43cd730dd63230c3374367 100644 (file)
 #include "game.h"
 
 
-// sample configurations
-extern LifeShape g_shapes[];
-
-
-// --------------------------------------------------------------------------
-// LifeNewGameDialog
-// --------------------------------------------------------------------------
-
-class LifeNewGameDialog : public wxDialog
-{
-public:
-    // ctor
-    LifeNewGameDialog(wxWindow *parent, int *w, int *h);
-
-    // event handlers
-    void OnOK(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;
-};
-
 // --------------------------------------------------------------------------
 // LifeSamplesDialog
 // --------------------------------------------------------------------------
@@ -73,14 +46,14 @@ public:
     ~LifeSamplesDialog();
 
     // members
-    int GetValue();
+    const LifeShape& GetShape();
 
     // event handlers
     void OnListBox(wxCommandEvent &event);
 
 private:
     // any class wishing to process wxWindows events must use this macro
-    DECLARE_EVENT_TABLE();
+    DECLARE_EVENT_TABLE()
 
     int         m_value;
     wxListBox  *m_list;
@@ -89,4 +62,15 @@ private:
     Life       *m_life;
 };
 
+// --------------------------------------------------------------------------
+// LifeAboutDialog
+// --------------------------------------------------------------------------
+
+class LifeAboutDialog : public wxDialog
+{
+public:
+    // ctor
+    LifeAboutDialog(wxWindow *parent);
+};
+
 #endif  // _LIFE_DIALOGS_H_
index ce51d42bd94b3c5d1deb4687cfcc3820ef71c2bc..07a2f13401969652d64ffe60d750e5603d3d0e8f 100644 (file)
 /////////////////////////////////////////////////////////////////////////////
 
 // ==========================================================================
-// declarations
+// headers, declarations, constants
 // ==========================================================================
 
-// --------------------------------------------------------------------------
-// headers
-// --------------------------------------------------------------------------
-
 #ifdef __GNUG__
     #pragma implementation "game.h"
 #endif
 
-// for compilers that support precompilation, includes "wx/wx.h"
-#include "wx/wxprec.h"
+#include "wx/log.h"
+#include "game.h"
 
-#ifdef __BORLANDC__
-    #pragma hdrstop
-#endif
+#include <stdlib.h>           // for abort
+#include <string.h>           // for memset
 
-// for all others, include the necessary headers
-#ifndef WX_PRECOMP
-    #include "wx/wx.h"
-#endif
 
-#include "game.h"
+#define ARRAYSIZE  1024      // size of the static arrays for BeginFind & co.
+#define ALLOCBOXES 16         // number of cellboxes to alloc at once
 
 // ==========================================================================
-// implementation
+// CellBox
 // ==========================================================================
 
-// --------------------------------------------------------------------------
+#define HASH(x, y) (((x >> 3) & 0x7f) << 7) + ((y >> 3) & 0x7f)
+#define HASHSIZE   32768
+#define MAXDEAD    8
+
+
+class CellBox
+{
+public:
+    // members    
+    inline bool IsAlive(int dx, int dy) const;
+    inline bool SetCell(int dx, int dy, bool alive);
+
+    // attributes
+    wxInt32   m_x, m_y;                     // position in universe
+    wxUint32  m_live1, m_live2;             // alive cells (1 bit per cell)
+    wxUint32  m_old1, m_old2;               // old values for m_live1, 2
+    wxUint32  m_on[8];                      // neighbouring info
+    wxUint32  m_dead;                       // been dead for n generations
+    CellBox  *m_up, *m_dn, *m_lf, *m_rt;    // neighbour CellBoxes
+    CellBox  *m_prev, *m_next;              // in linked list
+    CellBox  *m_hprev, *m_hnext;            // in hash table
+};
+
+
+// IsAlive:
+//  Returns whether cell dx, dy in this box is alive
+//
+bool CellBox::IsAlive(int dx, int dy) const
+{
+    if (dy > 3)
+        return (m_live2 & 1 << ((dy - 4) * 8 + dx));
+    else
+        return (m_live1 & 1 << ((dy) * 8 + dx));
+}
+
+// SetCell:
+//  Sets cell dx, dy in this box to 'alive', returns TRUE if
+//  the previous value was different, FALSE if it was the same.
+//
+bool CellBox::SetCell(int dx, int dy, bool alive)
+{
+    if (IsAlive(dx, dy) != alive)
+    {
+       if (dy > 3)
+           m_live2 ^= 1 << ((dy - 4) * 8 + dx);
+       else
+           m_live1 ^= 1 << ((dy) * 8 + dx);
+
+       // reset this here to avoid updating problems
+       m_dead = 0;
+
+       return TRUE;
+    }
+    else
+       return FALSE;
+}
+
+
+// ==========================================================================
 // Life
+// ==========================================================================
+
+// --------------------------------------------------------------------------
+// Ctor and dtor
 // --------------------------------------------------------------------------
 
-/* Format of the cell: (32 bit integer)
- *
- * 0yyyyyyy yyyyyy0x xxxxxxxxx xxx000ff
- *
- * y = y coordinate (13 bits)
- * x = x coordinate (13 bits)
- * f = flags (2 bits)
- *
- * OK, there is a reason for this, I promise :-). But I won't explain
- * it now; it will be more clear when I implement the optimized life
- * algorithm for large universes.
- */
-
-Life::Life(int width, int height)
+Life::Life()
 {            
-    m_wrap = TRUE;
-    Create(width, height);
+    m_numcells  = 0;
+    m_boxes     = new CellBox *[HASHSIZE];
+    m_head      = NULL;
+    m_available = NULL;
+    for (int i = 0; i < HASHSIZE; i++)
+        m_boxes[i] = NULL;
+
+    m_cells     = new Cell[ARRAYSIZE];
+    m_ncells    = 0;
+    m_findmore  = FALSE;
+    m_changed   = FALSE;
 }
 
 Life::~Life()
 {
-    Destroy();
+    Clear();
+
+    delete[] m_boxes;
+    delete[] m_cells;        
 }
 
-void Life::Create(int width, int height)
+// Clear:
+//  Clears the board, freeing all storage.
+// 
+void Life::Clear()
 {
-    wxASSERT(width > 0 && height > 0 && m_cells.IsEmpty());
+    CellBox *c, *nc;
 
-    m_width = width;
-    m_height = height;
+    m_numcells = 0;
+    
+    // clear the hash table pointers
+    for (int i = 0; i < HASHSIZE; i++)
+        m_boxes[i] = NULL;
+    
+    // free used boxes
+    c = m_head;
+    while (c)
+    {
+        nc = c->m_next;
+        delete c;
+        c = nc;   
+    }
+    m_head = NULL;
 
-    // preallocate memory to speed up initialization
-    m_cells.Alloc(m_width * m_height);
-    m_changed.Alloc(1000);
+    // free available boxes
+    c = m_available;
+    while (c)
+    {
+        nc = c->m_next;
+        delete c;
+        c = nc;   
+    }
+    m_available = NULL;
+}
+
+// --------------------------------------------------------------------------
+// Test and set individual cells
+// --------------------------------------------------------------------------
+
+// IsAlive:
+//  Returns whether cell (x, y) is alive.
+//
+bool Life::IsAlive(wxInt32 x, wxInt32 y)
+{
+    CellBox *c = LinkBox(x, y, FALSE);
 
-    // add all cells
-    for (int j = 0; j < m_height; j++)
-        for (int i = 0; i < m_width; i++)
-            m_cells.Add( MakeCell(i, j, FALSE) );
+    return (c && c->IsAlive( x - c->m_x, y - c->m_y ));
 }
 
-void Life::Destroy()
+// SetCell:
+//  Sets or clears cell (x, y), according to the 'alive' param.
+//
+void Life::SetCell(wxInt32 x, wxInt32 y, bool alive)
+{
+    CellBox *c  = LinkBox(x, y);
+    wxUint32 dx = x - c->m_x;
+    wxUint32 dy = y - c->m_y;
+
+    if (c->SetCell(dx, dy, alive))
+    {
+        if (alive)
+            m_numcells++;
+        else
+            m_numcells--;
+    }
+}    
+
+void Life::SetShape(const LifeShape& shape)
 {
-    m_cells.Clear();
-    m_changed.Clear();
+    char *p = shape.m_data;
+
+    int i0 = -(shape.m_width / 2);
+    int j0 = -(shape.m_height / 2);
+    int i1 = i0 + shape.m_width - 1;
+    int j1 = j0 + shape.m_height - 1;
+
+    Clear();
+    for (int j = j0; j <= j1; j++)
+        for (int i = i0; i <= i1; i++)
+            SetCell(i, j, *(p++) == '*');
 }
 
-void Life::Clear()
+// --------------------------------------------------------------------------
+// Cellbox management functions
+// --------------------------------------------------------------------------
+
+// CreateBox:
+//  Creates a new box in x, y, either taking it from the list
+//  of available boxes, or allocating a new one.
+//
+CellBox* Life::CreateBox(wxInt32 x, wxInt32 y, wxUint32 hv)
 {
-    // set all cells in the array to dead
-    for (int j = 0; j < m_height; j++)
-        for (int i = 0; i < m_width; i++)
-            m_cells[j * m_width + i] &= ~CELL_ALIVE;
+    CellBox *c;
+
+    // if there are no available boxes, alloc a few more
+    if (!m_available)
+        for (int i = 1; i <= ALLOCBOXES; i++)
+        {
+            c = new CellBox();
+
+            if (!c)
+            {
+                // TODO: handle memory errors. Note that right now, if we
+                // couldn't allocate at least one cellbox, we will crash
+                // before leaving CreateBox(). Probably we should try to
+                // allocate some boxes *before* the m_available list goes
+                // empty, so that we have a margin to handle errors
+                // gracefully.
+                wxLogFatalError(_("Out of memory! Aborting..."));
+
+                // the above call should abort, but it doesn't :-?
+                abort();
+
+                break;    
+            }
+    
+            c->m_next = m_available;
+            m_available = c;
+        }
+
+    // take a cellbox from the list of available boxes
+    c = m_available;
+    m_available = c->m_next;
+
+    // reset everything
+    memset((void *) c, 0, sizeof(CellBox));
+    c->m_x = x;
+    c->m_y = y;
+
+    // insert c in the list
+    c->m_next = m_head;
+    m_head = c;
+    if (c->m_next) c->m_next->m_prev = c;
+
+    // insert c in the hash table
+    c->m_hnext = m_boxes[hv];
+    m_boxes[hv] = c;
+    if (c->m_hnext) c->m_hnext->m_hprev = c;
+
+    return c;
 }
 
-Cell Life::MakeCell(int i, int j, bool alive) const
+// LinkBox:
+//  Returns a pointer to the box (x, y); if it didn't exist yet,
+//  it returns NULL or creates a new one, depending on the 'create'
+//  parameter.
+//
+CellBox* Life::LinkBox(wxInt32 x, wxInt32 y, bool create)
 {
-    return (Cell)((j << 18) | (i << 5) | (alive? CELL_ALIVE : CELL_DEAD));
+    wxUint32 hv;
+    CellBox *c;
+
+    x &= 0xfffffff8;
+    y &= 0xfffffff8;
+    hv = HASH(x, y);
+
+    // search in the hash table
+    for (c = m_boxes[hv]; c; c = c->m_hnext)
+        if ((c->m_x == x) && (c->m_y == y)) return c;
+
+    // if not found, and (create == TRUE), create a new one
+    return create? CreateBox(x, y, hv) : NULL;
 }
 
-bool Life::IsAlive(int i, int j) const
+// KillBox:
+//  Removes this box from the list and the hash table and
+//  puts it in the list of available boxes.
+//
+void Life::KillBox(CellBox *c)
 {
-    wxASSERT(i >= 0 && j >= 0 && i < m_width && j < m_height);
+    wxUint32 hv = HASH(c->m_x, c->m_y);
+
+    // remove from the list
+    if (c != m_head)
+        c->m_prev->m_next = c->m_next;
+    else
+        m_head = c->m_next;
+
+    // remove from the hash table        
+    if (c != m_boxes[hv])
+        c->m_hprev->m_hnext = c->m_hnext;
+    else
+        m_boxes[hv] = c->m_hnext;
+
+    // update neighbours    
+    if (c->m_next) c->m_next->m_prev = c->m_prev;
+    if (c->m_hnext) c->m_hnext->m_hprev = c->m_hprev;
+    if (c->m_up) c->m_up->m_dn = NULL;
+    if (c->m_dn) c->m_dn->m_up = NULL;
+    if (c->m_lf) c->m_lf->m_rt = NULL;
+    if (c->m_rt) c->m_rt->m_lf = NULL;
 
-    return (m_cells[j * m_width + i] & CELL_ALIVE);
+    // append to the list of available boxes
+    c->m_next = m_available;
+    m_available = c;
 }
 
-bool Life::IsAlive(Cell c) const { return (c & CELL_ALIVE); };
-int  Life::GetX(Cell c) const    { return (c >> 5) & 0x1fff; };
-int  Life::GetY(Cell c) const    { return (c >> 18); };
+// --------------------------------------------------------------------------
+// FindMore & co.
+// --------------------------------------------------------------------------
 
-Cell Life::SetCell(int i, int j, bool alive)
+// Post eight cells to the cell arrays (changed cells only)
+void Life::DoLine(wxInt32 i, wxInt32 j, wxUint32 live, wxUint32 old)
 {
-    wxASSERT(i >= 0 && j >= 0 && i < m_width && j < m_height);
+    wxUint32 diff = (live ^ old) & 0x000000ff;
 
-    Cell c = MakeCell(i, j, alive);
+    if (!diff) return;
 
-    m_cells[j * m_width + i] = c;
-    return c;
+    for (wxInt32 k = 8; k; k--, i++)
+    {
+        if (diff & 0x01)
+        {
+            m_cells[m_ncells].i = i;
+            m_cells[m_ncells].j = j;
+            m_ncells++;
+        }
+        diff >>= 1;
+        live >>= 1;
+    }
 }
+// Post eight cells to the cell arrays (alive cells only)
+void Life::DoLine(wxInt32 i, wxInt32 j, wxUint32 live)
+{
+    if (! (live & 0x000000ff)) return;
+
+    for (wxInt32 k = 8; k; k--, i++)
+    {
+        if (live & 0x01)
+        {
+            m_cells[m_ncells].i = i;
+            m_cells[m_ncells].j = j;
+            m_ncells++;
+        }
+        live >>= 1;
+    }
+}       
 
-void Life::SetShape(LifeShape& shape)
+void Life::BeginFind(wxInt32 i0, wxInt32 j0, wxInt32 i1, wxInt32 j1, bool changed)
 {
-    wxASSERT((m_width >= shape.m_width) && (m_height >= shape.m_height));
+    // TODO: optimize for the case where the maximum number of
+    // cellboxes that fit in the specified viewport is smaller
+    // than the current total of boxes; iterating over the list
+    // should then be faster than searching in the hash table.
+    
+    m_i0 = m_i = i0 & 0xfffffff8;
+    m_j0 = m_j = j0 & 0xfffffff8;
+    m_i1 = (i1 + 7) & 0xfffffff8;
+    m_j1 = (j1 + 7) & 0xfffffff8;
+   
+    m_findmore = TRUE;
+    m_changed = changed;
+}
 
-    int i0  = (m_width - shape.m_width) / 2;
-    int j0  = (m_height - shape.m_height) / 2;
-    char *p = shape.m_data;
+bool Life::FindMore(Cell *cells[], size_t *ncells)
+{
+    CellBox *c;
+    *cells = m_cells;
+    m_ncells = 0;
 
-    Clear();
-    for (int j = j0; j < j0 + shape.m_height; j++)
-        for (int i = i0; i < i0 + shape.m_width; i++)
-            SetCell(i, j, *(p++) == '*');
+    if (m_changed)
+    {
+        for ( ; m_j <= m_j1; m_j += 8, m_i = m_i0)
+            for ( ; m_i <= m_i1; m_i += 8)
+            {
+                if ((c = LinkBox(m_i, m_j, FALSE)) == NULL)
+                    continue;
+    
+                // check whether there is enough space left in the array
+                if (m_ncells > (ARRAYSIZE - 64))
+                {
+                    *ncells = m_ncells;
+                    return FALSE;
+                }
+                
+                DoLine(m_i, m_j    , c->m_live1,       c->m_old1      );
+                DoLine(m_i, m_j + 1, c->m_live1 >> 8,  c->m_old1 >> 8 );
+                DoLine(m_i, m_j + 2, c->m_live1 >> 16, c->m_old1 >> 16);
+                DoLine(m_i, m_j + 3, c->m_live1 >> 24, c->m_old1 >> 24);
+                DoLine(m_i, m_j + 4, c->m_live2,       c->m_old2      );
+                DoLine(m_i, m_j + 5, c->m_live2 >> 8,  c->m_old2 >> 8 );
+                DoLine(m_i, m_j + 6, c->m_live2 >> 16, c->m_old2 >> 16);
+                DoLine(m_i, m_j + 7, c->m_live2 >> 24, c->m_old2 >> 24);
+            }
+    }
+    else
+    {         
+        for ( ; m_j <= m_j1; m_j += 8, m_i = m_i0)
+            for ( ; m_i <= m_i1; m_i += 8)
+            {
+                if ((c = LinkBox(m_i, m_j, FALSE)) == NULL)
+                    continue;
+    
+                // check whether there is enough space left in the array
+                if (m_ncells > (ARRAYSIZE - 64))
+                {
+                    *ncells = m_ncells;
+                    return FALSE;
+                }
+    
+                DoLine(m_i, m_j    , c->m_live1      );
+                DoLine(m_i, m_j + 1, c->m_live1 >> 8 );
+                DoLine(m_i, m_j + 2, c->m_live1 >> 16);
+                DoLine(m_i, m_j + 3, c->m_live1 >> 24);
+                DoLine(m_i, m_j + 4, c->m_live2      );
+                DoLine(m_i, m_j + 5, c->m_live2 >> 8 );
+                DoLine(m_i, m_j + 6, c->m_live2 >> 16);
+                DoLine(m_i, m_j + 7, c->m_live2 >> 24);
+            }
+    }
+    
+    *ncells = m_ncells;
+    m_findmore = FALSE;
+    return TRUE;
 }
+                  
+// --------------------------------------------------------------------------
+// Evolution engine
+// --------------------------------------------------------------------------
 
+extern int g_tab1[];
+extern int g_tab2[];
+
+// NextTic:
+//  Advance one step in evolution :-)
+// 
 bool Life::NextTic()
 {
-    int i, j;
+    CellBox  *c, *up, *dn, *lf, *rt;
+    wxUint32 t1, t2, t3, t4;
+    bool     changed = FALSE;
+    
+    m_numcells = 0;
+    
+    // Stage 1:
+    // Compute neighbours of each cell
+    // 
+    // WARNING: unrolled loops and lengthy code follows!
+    //    
+    c = m_head;
 
-    m_changed.Empty();
+    while (c)
+    {
+        if (! (c->m_live1 || c->m_live2))
+        {
+            c = c->m_next;
+            continue;    
+        }        
+        up = c->m_up;
+        dn = c->m_dn;
+        lf = c->m_lf;
+        rt = c->m_rt;
 
-    /* 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 (j = 0; j < m_height; j++)
-        for (i = 0; i < m_width; i++)
+        // up        
+        t1 = c->m_live1 & 0x000000ff;
+        if (t1)
         {
-            int neighbors = GetNeighbors(i, j);
-            bool alive    = IsAlive(i, j);
+            if (!up)
+            {
+                up = LinkBox(c->m_x, c->m_y - 8);
+                up->m_dn = c;    
+            }
+            t2 = g_tab1[t1];
+            up->m_on[7] += t2;
+            c->m_on[1] += t2;        
+            c->m_on[0] += g_tab2[t1];
+        }
 
-            /* 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)))
+        // down
+        t1 = (c->m_live2 & 0xff000000) >> 24;
+        if (t1)
+        {
+            if (!dn)
             {
-                m_cells[j * m_width + i] |= CELL_MARK;
-                m_changed.Add( MakeCell(i, j, !alive) );
+                dn = LinkBox(c->m_x, c->m_y + 8);
+                dn->m_up = c;    
+            }
+            t2 = g_tab1[t1];
+            dn->m_on[0] += t2;
+            c->m_on[6] += t2;        
+            c->m_on[7] += g_tab2[t1];
+        }
+        
+        t1 = c->m_live1;
+        t2 = c->m_live2;
+        
+        // left
+        if (t1 & 0x01010101)
+        {
+            if (!lf)
+            {
+                lf = LinkBox(c->m_x - 8, c->m_y);    
+                lf->m_rt = c;
+            }
+            if (t1 & 0x00000001)
+            {
+               if (!lf->m_up)
+               {
+                   lf->m_up = LinkBox(c->m_x - 8, c->m_y - 8);    
+                   lf->m_up->m_dn = lf;
+               }
+               lf->m_up->m_on[7] += 0x10000000;
+               lf->m_on[0] += 0x10000000;
+               lf->m_on[1] += 0x10000000;
+            }
+            if (t1 & 0x00000100)
+            {
+               lf->m_on[0] += 0x10000000;   
+               lf->m_on[1] += 0x10000000;   
+               lf->m_on[2] += 0x10000000;   
+            }   
+            if (t1 & 0x00010000)
+            {
+               lf->m_on[1] += 0x10000000;   
+               lf->m_on[2] += 0x10000000;   
+               lf->m_on[3] += 0x10000000;   
+            }
+            if (t1 & 0x01000000)
+            {
+               lf->m_on[2] += 0x10000000;   
+               lf->m_on[3] += 0x10000000;   
+               lf->m_on[4] += 0x10000000;   
+            }
+        }
+        if (t2 & 0x01010101)
+        {
+            if (!lf)
+            {
+                lf = LinkBox(c->m_x - 8, c->m_y);    
+                lf->m_rt = c;
+            }
+            if (t2 & 0x00000001)
+            {
+               lf->m_on[3] += 0x10000000;   
+               lf->m_on[4] += 0x10000000;   
+               lf->m_on[5] += 0x10000000;   
+            }   
+            if (t2 & 0x00000100)
+            {
+               lf->m_on[4] += 0x10000000;   
+               lf->m_on[5] += 0x10000000;   
+               lf->m_on[6] += 0x10000000;   
+            }   
+            if (t2 & 0x00010000)
+            {
+               lf->m_on[5] += 0x10000000;   
+               lf->m_on[6] += 0x10000000;   
+               lf->m_on[7] += 0x10000000;   
+            }
+            if (t2 & 0x01000000) 
+            {
+               if (!lf->m_dn)
+               {
+                   lf->m_dn = LinkBox(c->m_x - 8, c->m_y + 8);    
+                   lf->m_dn->m_up = lf;
+               }
+               lf->m_on[6] += 0x10000000;
+               lf->m_on[7] += 0x10000000;
+               lf->m_dn->m_on[0] += 0x10000000;
             }
-            else
-                m_cells[j * m_width + i] &= ~CELL_MARK;
         }
 
-    /* 2nd pass. Stabilize.
-     */
-    for (j = 0; j < m_height; j++)
-        for (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;
+        // right
+        if (t1 & 0x80808080)
+        {
+            if (!rt)
+            {
+                rt = LinkBox(c->m_x + 8, c->m_y);    
+                rt->m_lf = c;
+            }
+            if (t1 & 0x00000080)
+            {
+               if (!rt->m_up)
+               {
+                   rt->m_up = LinkBox(c->m_x + 8, c->m_y - 8);    
+                   rt->m_up->m_dn = rt;
+               }
+               rt->m_up->m_on[7] += 0x00000001;
+               rt->m_on[0] += 0x00000001;
+               rt->m_on[1] += 0x00000001;
+            }
+            if (t1 & 0x00008000)
+            {
+               rt->m_on[0] += 0x00000001;   
+               rt->m_on[1] += 0x00000001;   
+               rt->m_on[2] += 0x00000001;   
+            }   
+            if (t1 & 0x00800000)
+            {
+               rt->m_on[1] += 0x00000001;   
+               rt->m_on[2] += 0x00000001;   
+               rt->m_on[3] += 0x00000001;   
+            }
+            if (t1 & 0x80000000)
+            {
+               rt->m_on[2] += 0x00000001;   
+               rt->m_on[3] += 0x00000001;   
+               rt->m_on[4] += 0x00000001;   
+            }
+        }
+        if (t2 & 0x80808080)
+        {
+            if (!rt)
+            {
+                rt = LinkBox(c->m_x + 8, c->m_y);    
+                rt->m_lf = c;
+            }
+            if (t2 & 0x00000080)
+            {
+               rt->m_on[3] += 0x00000001;   
+               rt->m_on[4] += 0x00000001;   
+               rt->m_on[5] += 0x00000001;   
+            }   
+            if (t2 & 0x00008000)
+            {
+               rt->m_on[4] += 0x00000001;   
+               rt->m_on[5] += 0x00000001;   
+               rt->m_on[6] += 0x00000001;   
+            }   
+            if (t2 & 0x00800000)
+            {
+               rt->m_on[5] += 0x00000001;   
+               rt->m_on[6] += 0x00000001;   
+               rt->m_on[7] += 0x00000001;   
+            }
+            if (t2 & 0x80000000) 
+            {
+               if (!rt->m_dn)
+               {
+                   rt->m_dn = LinkBox(c->m_x + 8, c->m_y + 8);    
+                   rt->m_dn->m_up = rt;
+               }
+               rt->m_on[6] += 0x00000001;
+               rt->m_on[7] += 0x00000001;
+               rt->m_dn->m_on[0] += 0x00000001;
+            }
+        }
+         
+        // inner cells
+        for (int i = 1; i <= 3; i++)
+        {
+            t1 = ((c->m_live1) >> (i * 8)) & 0x000000ff;
+            if (t1)
+            {
+                c->m_on[i - 1] += g_tab1[t1];
+                c->m_on[i    ] += g_tab2[t1];
+                c->m_on[i + 1] += g_tab1[t1];
+            }
+        }
+        for (int i = 0; i <= 2; i++)
+        {
+            t1 = ((c->m_live2) >> (i * 8)) & 0x000000ff;
+            if (t1)
+            {
+                c->m_on[i + 3] += g_tab1[t1];
+                c->m_on[i + 4] += g_tab2[t1];
+                c->m_on[i + 5] += g_tab1[t1];
+            }
         }
 
-    return (!m_changed.IsEmpty());
-}
+        c->m_up = up;
+        c->m_dn = dn;
+        c->m_lf = lf;
+        c->m_rt = rt;
+        c = c->m_next;
+    }
 
-int Life::GetNeighbors(int x, int y) const
-{
-    wxASSERT(x >= 0 && y >= 0 && x < m_width && y < m_height);
+    // Stage 2:
+    // Stabilize
+    //
+    // WARNING: to be optimized and unrolled soon.
+    // 
+    c = m_head;
+    
+    while (c)
+    {
+        t1 = c->m_live1;
+        c->m_old1 = t1;
+        t2 = 0;
+        for (int i = 0; i <= 3; i++)
+        {
+            t3 = c->m_on[i];
+            if (!t3)
+            {
+                t1 >>= 8;
+                t2 >>= 8;
+                continue;
+            }
 
-    int neighbors = 0;
+            for (int j = 0; j < 8; j++)
+            {
+                t2 >>= 1;
+                t4 = t3 & 0x0000000f;
+                
+                if ((t4 == 3) || ((t4 == 2) && (t1 & 0x00000001)))
+                {
+                    t2 |= 0x80000000;
+                    m_numcells++;
+                }
+                
+                t3 >>= 4;
+                t1 >>= 1;
+            }
+            c->m_on[i] = 0;
+        }
+        c->m_live1 = t2;
 
-    int i0 = (x)? (x - 1) : 0;
-    int j0 = (y)? (y - 1) : 0;
-    int i1 = (x < (m_width - 1))? (x + 1) : (m_width - 1);
-    int j1 = (y < (m_height - 1))? (y + 1) : (m_height - 1);
+        t1 = c->m_live2;
+        c->m_old2 = t1;
+        t2 = 0;
+        for (int i = 4; i <= 7; i++)
+        {
+            t3 = c->m_on[i];
+            if (!t3)
+            {
+                t1 >>= 8;
+                t2 >>= 8;
+                continue;
+            }
 
-    if (m_wrap && ( !x || !y || x == (m_width - 1) || y == (m_height - 1)))
-    {
-        // this is an outer cell and wraparound is on
-        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++;
-    }
-    else
-    {
-        // this is an inner cell, or wraparound is off
-        for (int j = j0; j <= j1; j++)
-            for (int i = i0; i <= i1; i++)
-                if (IsAlive(i, j))
-                     neighbors++;
-    }
+            for (int j = 0; j < 8; j++)
+            {
+                t2 >>= 1;
+                t4 = t3 & 0x0000000f;
+                
+                if ((t4 == 3) || ((t4 == 2) && (t1 & 0x00000001)))
+                {
+                    t2 |= 0x80000000;
+                    m_numcells++;
+                }
+                
+                t3 >>= 4;
+                t1 >>= 1;
+            }
+            c->m_on[i] = 0;
+        }
+        c->m_live2 = t2;
 
-    // do not count ourselves
-    if (IsAlive(x, y)) neighbors--;
+        // keep track of changes
+        changed |= ((c->m_live1 ^ c->m_old1) || (c->m_live2 ^ c->m_old2));
+        
+        // mark, and discard if necessary, dead boxes
+        if (c->m_live1 || c->m_live2)
+        {
+            c->m_dead = 0;
+            c = c->m_next;
+        }
+        else
+        {
+            CellBox *aux = c->m_next;
+            if (c->m_dead++ > MAXDEAD)
+               KillBox(c);    
+               
+            c = aux;
+        }
+    }
 
-    return neighbors;
+    return changed;
 }
 
+// --------------------------------------------------------------------------
+// Lookup tables - these will be generated on-the-fly soon.
+// --------------------------------------------------------------------------
+
+// This table converts from bits (like in live1, live2) to number
+// of neighbors for each cell in the upper or lower row.
+//
+int g_tab1[]=
+{
+    0x00000000,
+    0x00000011,
+    0x00000111,
+    0x00000122,
+    0x00001110,
+    0x00001121,
+    0x00001221,
+    0x00001232,
+    0x00011100,
+    0x00011111,
+    0x00011211,
+    0x00011222,
+    0x00012210,
+    0x00012221,
+    0x00012321,
+    0x00012332,
+    0x00111000,
+    0x00111011,
+    0x00111111,
+    0x00111122,
+    0x00112110,
+    0x00112121,
+    0x00112221,
+    0x00112232,
+    0x00122100,
+    0x00122111,
+    0x00122211,
+    0x00122222,
+    0x00123210,
+    0x00123221,
+    0x00123321,
+    0x00123332,
+    0x01110000,
+    0x01110011,
+    0x01110111,
+    0x01110122,
+    0x01111110,
+    0x01111121,
+    0x01111221,
+    0x01111232,
+    0x01121100,
+    0x01121111,
+    0x01121211,
+    0x01121222,
+    0x01122210,
+    0x01122221,
+    0x01122321,
+    0x01122332,
+    0x01221000,
+    0x01221011,
+    0x01221111,
+    0x01221122,
+    0x01222110,
+    0x01222121,
+    0x01222221,
+    0x01222232,
+    0x01232100,
+    0x01232111,
+    0x01232211,
+    0x01232222,
+    0x01233210,
+    0x01233221,
+    0x01233321,
+    0x01233332,
+    0x11100000,
+    0x11100011,
+    0x11100111,
+    0x11100122,
+    0x11101110,
+    0x11101121,
+    0x11101221,
+    0x11101232,
+    0x11111100,
+    0x11111111,
+    0x11111211,
+    0x11111222,
+    0x11112210,
+    0x11112221,
+    0x11112321,
+    0x11112332,
+    0x11211000,
+    0x11211011,
+    0x11211111,
+    0x11211122,
+    0x11212110,
+    0x11212121,
+    0x11212221,
+    0x11212232,
+    0x11222100,
+    0x11222111,
+    0x11222211,
+    0x11222222,
+    0x11223210,
+    0x11223221,
+    0x11223321,
+    0x11223332,
+    0x12210000,
+    0x12210011,
+    0x12210111,
+    0x12210122,
+    0x12211110,
+    0x12211121,
+    0x12211221,
+    0x12211232,
+    0x12221100,
+    0x12221111,
+    0x12221211,
+    0x12221222,
+    0x12222210,
+    0x12222221,
+    0x12222321,
+    0x12222332,
+    0x12321000,
+    0x12321011,
+    0x12321111,
+    0x12321122,
+    0x12322110,
+    0x12322121,
+    0x12322221,
+    0x12322232,
+    0x12332100,
+    0x12332111,
+    0x12332211,
+    0x12332222,
+    0x12333210,
+    0x12333221,
+    0x12333321,
+    0x12333332,
+    0x11000000,
+    0x11000011,
+    0x11000111,
+    0x11000122,
+    0x11001110,
+    0x11001121,
+    0x11001221,
+    0x11001232,
+    0x11011100,
+    0x11011111,
+    0x11011211,
+    0x11011222,
+    0x11012210,
+    0x11012221,
+    0x11012321,
+    0x11012332,
+    0x11111000,
+    0x11111011,
+    0x11111111,
+    0x11111122,
+    0x11112110,
+    0x11112121,
+    0x11112221,
+    0x11112232,
+    0x11122100,
+    0x11122111,
+    0x11122211,
+    0x11122222,
+    0x11123210,
+    0x11123221,
+    0x11123321,
+    0x11123332,
+    0x12110000,
+    0x12110011,
+    0x12110111,
+    0x12110122,
+    0x12111110,
+    0x12111121,
+    0x12111221,
+    0x12111232,
+    0x12121100,
+    0x12121111,
+    0x12121211,
+    0x12121222,
+    0x12122210,
+    0x12122221,
+    0x12122321,
+    0x12122332,
+    0x12221000,
+    0x12221011,
+    0x12221111,
+    0x12221122,
+    0x12222110,
+    0x12222121,
+    0x12222221,
+    0x12222232,
+    0x12232100,
+    0x12232111,
+    0x12232211,
+    0x12232222,
+    0x12233210,
+    0x12233221,
+    0x12233321,
+    0x12233332,
+    0x22100000,
+    0x22100011,
+    0x22100111,
+    0x22100122,
+    0x22101110,
+    0x22101121,
+    0x22101221,
+    0x22101232,
+    0x22111100,
+    0x22111111,
+    0x22111211,
+    0x22111222,
+    0x22112210,
+    0x22112221,
+    0x22112321,
+    0x22112332,
+    0x22211000,
+    0x22211011,
+    0x22211111,
+    0x22211122,
+    0x22212110,
+    0x22212121,
+    0x22212221,
+    0x22212232,
+    0x22222100,
+    0x22222111,
+    0x22222211,
+    0x22222222,
+    0x22223210,
+    0x22223221,
+    0x22223321,
+    0x22223332,
+    0x23210000,
+    0x23210011,
+    0x23210111,
+    0x23210122,
+    0x23211110,
+    0x23211121,
+    0x23211221,
+    0x23211232,
+    0x23221100,
+    0x23221111,
+    0x23221211,
+    0x23221222,
+    0x23222210,
+    0x23222221,
+    0x23222321,
+    0x23222332,
+    0x23321000,
+    0x23321011,
+    0x23321111,
+    0x23321122,
+    0x23322110,
+    0x23322121,
+    0x23322221,
+    0x23322232,
+    0x23332100,
+    0x23332111,
+    0x23332211,
+    0x23332222,
+    0x23333210,
+    0x23333221,
+    0x23333321,
+    0x23333332
+};
+
+// This table converts from bits (like in live1, live2) to number
+// of neighbors for each cell in the same row (excluding ourselves)
+//
+int g_tab2[]=
+{
+    0x00000000,
+    0x00000010,
+    0x00000101,
+    0x00000111,
+    0x00001010,
+    0x00001020,
+    0x00001111,
+    0x00001121,
+    0x00010100,
+    0x00010110,
+    0x00010201,
+    0x00010211,
+    0x00011110,
+    0x00011120,
+    0x00011211,
+    0x00011221,
+    0x00101000,
+    0x00101010,
+    0x00101101,
+    0x00101111,
+    0x00102010,
+    0x00102020,
+    0x00102111,
+    0x00102121,
+    0x00111100,
+    0x00111110,
+    0x00111201,
+    0x00111211,
+    0x00112110,
+    0x00112120,
+    0x00112211,
+    0x00112221,
+    0x01010000,
+    0x01010010,
+    0x01010101,
+    0x01010111,
+    0x01011010,
+    0x01011020,
+    0x01011111,
+    0x01011121,
+    0x01020100,
+    0x01020110,
+    0x01020201,
+    0x01020211,
+    0x01021110,
+    0x01021120,
+    0x01021211,
+    0x01021221,
+    0x01111000,
+    0x01111010,
+    0x01111101,
+    0x01111111,
+    0x01112010,
+    0x01112020,
+    0x01112111,
+    0x01112121,
+    0x01121100,
+    0x01121110,
+    0x01121201,
+    0x01121211,
+    0x01122110,
+    0x01122120,
+    0x01122211,
+    0x01122221,
+    0x10100000,
+    0x10100010,
+    0x10100101,
+    0x10100111,
+    0x10101010,
+    0x10101020,
+    0x10101111,
+    0x10101121,
+    0x10110100,
+    0x10110110,
+    0x10110201,
+    0x10110211,
+    0x10111110,
+    0x10111120,
+    0x10111211,
+    0x10111221,
+    0x10201000,
+    0x10201010,
+    0x10201101,
+    0x10201111,
+    0x10202010,
+    0x10202020,
+    0x10202111,
+    0x10202121,
+    0x10211100,
+    0x10211110,
+    0x10211201,
+    0x10211211,
+    0x10212110,
+    0x10212120,
+    0x10212211,
+    0x10212221,
+    0x11110000,
+    0x11110010,
+    0x11110101,
+    0x11110111,
+    0x11111010,
+    0x11111020,
+    0x11111111,
+    0x11111121,
+    0x11120100,
+    0x11120110,
+    0x11120201,
+    0x11120211,
+    0x11121110,
+    0x11121120,
+    0x11121211,
+    0x11121221,
+    0x11211000,
+    0x11211010,
+    0x11211101,
+    0x11211111,
+    0x11212010,
+    0x11212020,
+    0x11212111,
+    0x11212121,
+    0x11221100,
+    0x11221110,
+    0x11221201,
+    0x11221211,
+    0x11222110,
+    0x11222120,
+    0x11222211,
+    0x11222221,
+    0x01000000,
+    0x01000010,
+    0x01000101,
+    0x01000111,
+    0x01001010,
+    0x01001020,
+    0x01001111,
+    0x01001121,
+    0x01010100,
+    0x01010110,
+    0x01010201,
+    0x01010211,
+    0x01011110,
+    0x01011120,
+    0x01011211,
+    0x01011221,
+    0x01101000,
+    0x01101010,
+    0x01101101,
+    0x01101111,
+    0x01102010,
+    0x01102020,
+    0x01102111,
+    0x01102121,
+    0x01111100,
+    0x01111110,
+    0x01111201,
+    0x01111211,
+    0x01112110,
+    0x01112120,
+    0x01112211,
+    0x01112221,
+    0x02010000,
+    0x02010010,
+    0x02010101,
+    0x02010111,
+    0x02011010,
+    0x02011020,
+    0x02011111,
+    0x02011121,
+    0x02020100,
+    0x02020110,
+    0x02020201,
+    0x02020211,
+    0x02021110,
+    0x02021120,
+    0x02021211,
+    0x02021221,
+    0x02111000,
+    0x02111010,
+    0x02111101,
+    0x02111111,
+    0x02112010,
+    0x02112020,
+    0x02112111,
+    0x02112121,
+    0x02121100,
+    0x02121110,
+    0x02121201,
+    0x02121211,
+    0x02122110,
+    0x02122120,
+    0x02122211,
+    0x02122221,
+    0x11100000,
+    0x11100010,
+    0x11100101,
+    0x11100111,
+    0x11101010,
+    0x11101020,
+    0x11101111,
+    0x11101121,
+    0x11110100,
+    0x11110110,
+    0x11110201,
+    0x11110211,
+    0x11111110,
+    0x11111120,
+    0x11111211,
+    0x11111221,
+    0x11201000,
+    0x11201010,
+    0x11201101,
+    0x11201111,
+    0x11202010,
+    0x11202020,
+    0x11202111,
+    0x11202121,
+    0x11211100,
+    0x11211110,
+    0x11211201,
+    0x11211211,
+    0x11212110,
+    0x11212120,
+    0x11212211,
+    0x11212221,
+    0x12110000,
+    0x12110010,
+    0x12110101,
+    0x12110111,
+    0x12111010,
+    0x12111020,
+    0x12111111,
+    0x12111121,
+    0x12120100,
+    0x12120110,
+    0x12120201,
+    0x12120211,
+    0x12121110,
+    0x12121120,
+    0x12121211,
+    0x12121221,
+    0x12211000,
+    0x12211010,
+    0x12211101,
+    0x12211111,
+    0x12212010,
+    0x12212020,
+    0x12212111,
+    0x12212121,
+    0x12221100,
+    0x12221110,
+    0x12221201,
+    0x12221211,
+    0x12222110,
+    0x12222120,
+    0x12222211,
+    0x12222221
+};
index dea8c45572acaeb702018815ad38653440fc0d63..9238e6d0abb2e57d50369c2a35627bfed408bfde 100644 (file)
@@ -1,6 +1,6 @@
 /////////////////////////////////////////////////////////////////////////////
 // Name:        game.h
-// Purpose:     Life! game logic
+// Purpose:     Life! game logic, version 2
 // Author:      Guillermo Rodriguez Garcia, <guille@iies.es>
 // Modified by:
 // Created:     Jan/2000
 #endif
 
 // --------------------------------------------------------------------------
-// constants
+// Cell
 // --------------------------------------------------------------------------
 
-// minimum and maximum table size, in each dimension
-#define LIFE_MIN 20
-#define LIFE_MAX 200
-
-// --------------------------------------------------------------------------
-// Cell and CellArray;
-// --------------------------------------------------------------------------
-
-typedef long        Cell;
-typedef wxArrayLong CellArray;
+// A Cell is just a struct which contains a pair of (i, j) coords.
+// These structs are not used internally anywhere; they are just
+// used to pass cell coordinates around.
+struct Cell
+{
+    wxInt32 i;
+    wxInt32 j;
+};       
 
 // --------------------------------------------------------------------------
 // LifeShape
 // --------------------------------------------------------------------------
 
+// A class which holds a pattern
 class LifeShape
 {
 public:
     LifeShape::LifeShape(wxString name,
                          wxString desc,
-                         int width, int height, char *data,
-                         int fieldWidth = 20, int fieldHeight = 20,
-                         bool wrap = TRUE)
+                         int width,
+                         int height,
+                         char *data)
     {
-        m_name        = name;
-        m_desc        = desc;
-        m_width       = width;
-        m_height      = height;
-        m_data        = data;
-        m_fieldWidth  = fieldWidth;
-        m_fieldHeight = fieldHeight;
-        m_wrap        = wrap;
+        m_name   = name;
+        m_desc   = desc;
+        m_width  = width;
+        m_height = height;
+        m_data   = data;
     }
 
     wxString  m_name;
@@ -71,59 +67,77 @@ public:
     int       m_width;
     int       m_height;
     char     *m_data;
-    int       m_fieldWidth;
-    int       m_fieldHeight;
-    bool      m_wrap;
 };
 
+
 // --------------------------------------------------------------------------
 // Life
 // --------------------------------------------------------------------------
 
+class CellBox;
+
 class Life
 {
 public:
     // ctor and dtor
-    Life(int width, int height);
+    Life();
     ~Life();
-    void Create(int width, int height);
-    void Destroy();
-
-    // game field
-    inline int       GetWidth() const        { return m_width; };
-    inline int       GetHeight() const       { return m_height; };
-    inline void      SetBorderWrap(bool on)  { m_wrap = on; };
-
-    // cells
-    bool             IsAlive(int i, int j) const;
-    bool             IsAlive(Cell c) const;
-    int              GetX(Cell c) const;
-    int              GetY(Cell c) const;
-    const CellArray* GetCells() const        { return &m_cells; };
-    const CellArray* GetChangedCells() const { return &m_changed; };
-
-    // game logic
+
+    // accessors
+    inline wxUint32 GetNumCells() const { return m_numcells; };
+    bool IsAlive (wxInt32 x, wxInt32 y);
+    void SetCell (wxInt32 x, wxInt32 y, bool alive = TRUE);
+    void SetShape(const LifeShape &shape);
+
+    // game control
     void Clear();
-    Cell SetCell(int i, int j, bool alive = TRUE);
-    void SetShape(LifeShape &shape);
     bool NextTic();
 
-private:
-    int         GetNeighbors(int i, int j) const;
-    inline Cell MakeCell(int i, int j, bool alive) const;
-
-    enum CellFlags
-    {
-        CELL_DEAD    = 0x0000,  // is dead
-        CELL_ALIVE   = 0x0001,  // is alive
-        CELL_MARK    = 0x0002,  // will change / has changed
-    };
+    // The following functions find cells within a given viewport; either
+    // all alive cells, or only those cells which have changed since last
+    // generation. You first call BeginFind() to specify the viewport,
+    // then keep calling FindMore() until it returns TRUE.
+    //
+    // BeginFind:
+    //  Specify the viewport and whether to look for alive cells or for
+    //  cells which have changed since the last generation and thus need
+    //  to be repainted. In this latter case, there is no distinction
+    //  between newborn or just-dead cells.
+    //
+    // FindMore:
+    //  Fills an array with cells that match the specification given with
+    //  BeginFind(). The array itself belongs to the Life object and must
+    //  not be modified or freed by the caller. If this function returns
+    //  FALSE, then the operation is not complete: just process all cells
+    //  and call FillMore() again.
+    //
+    void BeginFind(wxInt32 i0, wxInt32 j0,
+                   wxInt32 i1, wxInt32 j1,
+                   bool changed);
+    bool FindMore(Cell *cells[], size_t *ncells);
 
-    int       m_width;
-    int       m_height;
-    CellArray m_cells;
-    CellArray m_changed;
-    bool      m_wrap;
+private:
+    // cellbox-related
+    CellBox *CreateBox(wxInt32 x, wxInt32 y, wxUint32 hv);
+    CellBox *LinkBox(wxInt32 x, wxInt32 y, bool create = TRUE);
+    void KillBox(CellBox *c);
+
+    // helpers for FindMore & co.
+    void DoLine(wxInt32 i, wxInt32 j, wxUint32 alive, wxUint32 old);
+    void DoLine(wxInt32 i, wxInt32 j, wxUint32 alive);
+
+
+    CellBox  *m_head;           // list of alive boxes
+    CellBox  *m_available;      // list of reusable dead boxes
+    CellBox **m_boxes;          // hash table of alive boxes
+    wxUint32  m_numcells;       // population (number of alive cells)
+    Cell     *m_cells;          // cell array for FindMore()
+    size_t    m_ncells;         // number of valid cells in cell array
+    wxInt32   m_i, m_j,         // state vars for FindMore()
+              m_i0, m_j0,
+              m_i1, m_j1;
+    bool      m_changed;
+    bool      m_findmore;
 };
 
 #endif  // _LIFE_GAME_H_
index d03ad5e579eccb999fa16d5722ea1ea17d0f0f3d..b65451f8b557a2712327a178c986fc60fb7e66d1 100644 (file)
 /////////////////////////////////////////////////////////////////////////////
 
 // ==========================================================================
-// declarations
+// headers, declarations, constants
 // ==========================================================================
 
-// --------------------------------------------------------------------------
-// headers
-// --------------------------------------------------------------------------
-
 #ifdef __GNUG__
     #pragma implementation "life.h"
 #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 "life.h"
@@ -51,6 +35,8 @@
     #include "bitmaps/reset.xpm"
     #include "bitmaps/play.xpm"
     #include "bitmaps/stop.xpm"
+    #include "bitmaps/zoomin.xpm"
+    #include "bitmaps/zoomout.xpm"
 #endif
 
 // --------------------------------------------------------------------------
 enum
 {
     // menu items and toolbar buttons
-    ID_NEWGAME = 1001,
+    ID_RESET = 1001,
     ID_SAMPLES,
     ID_ABOUT,
     ID_EXIT,
-    ID_CLEAR,
+    ID_CENTER,
     ID_START,
     ID_STEP,
     ID_STOP,
-    ID_WRAP,
+    ID_ZOOMIN,
+    ID_ZOOMOUT,
+    ID_TOPSPEED,
 
     // speed selection slider
     ID_SLIDER
@@ -81,44 +69,49 @@ enum
 
 // Event tables
 BEGIN_EVENT_TABLE(LifeFrame, wxFrame)
-    EVT_MENU           (ID_NEWGAME, LifeFrame::OnNewGame)
-    EVT_MENU           (ID_SAMPLES, LifeFrame::OnSamples)
-    EVT_MENU           (ID_ABOUT,   LifeFrame::OnMenu)
-    EVT_MENU           (ID_EXIT,    LifeFrame::OnMenu)
-    EVT_MENU           (ID_CLEAR,   LifeFrame::OnMenu)
-    EVT_MENU           (ID_START,   LifeFrame::OnMenu)
-    EVT_MENU           (ID_STEP,    LifeFrame::OnMenu)
-    EVT_MENU           (ID_STOP,    LifeFrame::OnMenu)
-    EVT_MENU           (ID_WRAP,    LifeFrame::OnMenu)
-    EVT_COMMAND_SCROLL (ID_SLIDER,  LifeFrame::OnSlider)
+    EVT_MENU            (ID_SAMPLES,  LifeFrame::OnSamples)
+    EVT_MENU            (ID_RESET,    LifeFrame::OnMenu)
+    EVT_MENU            (ID_ABOUT,    LifeFrame::OnMenu)
+    EVT_MENU            (ID_EXIT,     LifeFrame::OnMenu)
+    EVT_MENU            (ID_CENTER,   LifeFrame::OnMenu)
+    EVT_MENU            (ID_START,    LifeFrame::OnMenu)
+    EVT_MENU            (ID_STEP,     LifeFrame::OnMenu)
+    EVT_MENU            (ID_STOP,     LifeFrame::OnMenu)
+    EVT_MENU            (ID_ZOOMIN,   LifeFrame::OnMenu)
+    EVT_MENU            (ID_ZOOMOUT,  LifeFrame::OnMenu)
+    EVT_MENU            (ID_TOPSPEED, LifeFrame::OnMenu)
+    EVT_COMMAND_SCROLL  (ID_SLIDER,   LifeFrame::OnSlider)
+    EVT_CLOSE           (             LifeFrame::OnClose)
 END_EVENT_TABLE()
 
-BEGIN_EVENT_TABLE(LifeCanvas, wxScrolledWindow)
-    EVT_PAINT          (            LifeCanvas::OnPaint)
-    EVT_SIZE           (            LifeCanvas::OnSize)
-    EVT_MOUSE_EVENTS   (            LifeCanvas::OnMouse)
+BEGIN_EVENT_TABLE(LifeCanvas, wxWindow)
+    EVT_PAINT           (             LifeCanvas::OnPaint)
+    EVT_SCROLLWIN       (             LifeCanvas::OnScroll)
+    EVT_SIZE            (             LifeCanvas::OnSize)
+    EVT_MOUSE_EVENTS    (             LifeCanvas::OnMouse)
+    EVT_ERASE_BACKGROUND(             LifeCanvas::OnEraseBackground)
 END_EVENT_TABLE()
 
 
 // Create a new application object
 IMPLEMENT_APP(LifeApp)
 
+
 // ==========================================================================
 // implementation
 // ==========================================================================
 
 // some shortcuts
-#define ADD_TOOL(a, b, c, d) \
-    toolBar->AddTool(a, b, wxNullBitmap, FALSE, -1, -1, (wxObject *)0, c, d)
+#define ADD_TOOL(id, bmp, tooltip, help)     \
+    toolBar->AddTool(id, bmp, wxNullBitmap, FALSE, -1, -1, (wxObject *)0, tooltip, help)
 
-#define GET_FRAME() \
-    ((LifeFrame *) wxGetApp().GetTopWindow())
+#define GET_FRAME() ((LifeFrame *) wxGetApp().GetTopWindow())
 
 // --------------------------------------------------------------------------
 // LifeApp
 // --------------------------------------------------------------------------
 
-// `Main program' equivalent: the program execution "starts" here
+// 'Main program' equivalent: the program execution "starts" here
 bool LifeApp::OnInit()
 {
     // create the main application window
@@ -146,21 +139,23 @@ LifeFrame::LifeFrame() : wxFrame((wxFrame *)0, -1, _("Life!"), wxPoint(50, 50))
     wxMenu *menuFile = new wxMenu("", wxMENU_TEAROFF);
     wxMenu *menuGame = new wxMenu("", wxMENU_TEAROFF);
 
-    menuFile->Append(ID_NEWGAME, _("New game..."), _("Start a new game"));
+    menuFile->Append(ID_RESET, _("Reset"), _("Start a new game"));
     menuFile->Append(ID_SAMPLES, _("Sample game..."), _("Select a sample configuration"));
     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"));
 
-    menuGame->Append(ID_CLEAR, _("&Clear\tCtrl-C"), _("Clear game field"));
+    menuGame->Append(ID_CENTER, _("Re&center\tCtrl-C"), _("Go to (0, 0)"));
     menuGame->Append(ID_START, _("&Start\tCtrl-S"), _("Start"));
     menuGame->Append(ID_STEP, _("&Next\tCtrl-N"), _("Single step"));
     menuGame->Append(ID_STOP, _("S&top\tCtrl-T"), _("Stop"));
     menuGame->Enable(ID_STOP, FALSE);
     menuGame->AppendSeparator();
-    menuGame->Append(ID_WRAP, _("&Wraparound\tCtrl-W"), _("Wrap around borders"), TRUE);
-    menuGame->Check (ID_WRAP, TRUE);
+    menuGame->Append(ID_TOPSPEED, _("Top speed!"), _("Go as fast as possible"));
+    menuGame->AppendSeparator();
+    menuGame->Append(ID_ZOOMIN, _("Zoom &in\tCtrl-I"));
+    menuGame->Append(ID_ZOOMOUT, _("Zoom &out\tCtrl-O"));
 
     wxMenuBar *menuBar = new wxMenuBar();
     menuBar->Append(menuFile, _("&File"));
@@ -168,36 +163,43 @@ LifeFrame::LifeFrame() : wxFrame((wxFrame *)0, -1, _("Life!"), wxPoint(50, 50))
     SetMenuBar(menuBar);
 
     // tool bar
-    wxBitmap tbBitmaps[3];
+    wxBitmap tbBitmaps[5];
 
     tbBitmaps[0] = wxBITMAP(reset);
     tbBitmaps[1] = wxBITMAP(play);
     tbBitmaps[2] = wxBITMAP(stop);
+    tbBitmaps[3] = wxBITMAP(zoomin);
+    tbBitmaps[4] = wxBITMAP(zoomout);
 
     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);
+    ADD_TOOL(ID_RESET,   tbBitmaps[0], _("Reset"), _("Start a new game"));
+    ADD_TOOL(ID_START,   tbBitmaps[1], _("Start"), _("Start"));
+    ADD_TOOL(ID_STOP,    tbBitmaps[2], _("Stop"),  _("Stop"));
+    toolBar->AddSeparator();
+    ADD_TOOL(ID_ZOOMIN,  tbBitmaps[3], _("Zoom in"),  _("Zoom in"));
+    ADD_TOOL(ID_ZOOMOUT, tbBitmaps[4], _("Zoom out"),  _("Zoom out"));
     toolBar->Realize();
+    toolBar->EnableTool(ID_STOP, FALSE);    // must be after Realize() !
 
     // status bar
     CreateStatusBar(2);
     SetStatusText(_("Welcome to Life!"));
 
-    // game
+    // game and canvas
     wxPanel *panel = new wxPanel(this, -1);
-    m_life         = new Life(20, 20);
+    m_life         = new Life();
     m_canvas       = new LifeCanvas(panel, m_life);
     m_timer        = new LifeTimer();
+    m_running      = FALSE;
+    m_topspeed     = FALSE;
     m_interval     = 500;
     m_tics         = 0;
     m_text         = new wxStaticText(panel, -1, "");
     UpdateInfoText();
 
-    // slider
+    // speed selection slider
     wxSlider *slider = new wxSlider(panel, ID_SLIDER,
         5, 1, 10,
         wxDefaultPosition,
@@ -220,49 +222,80 @@ LifeFrame::LifeFrame() : wxFrame((wxFrame *)0, -1, _("Life!"), wxPoint(50, 50))
 LifeFrame::~LifeFrame()
 {
     delete m_timer;
-    delete m_life;
 }
 
 void LifeFrame::UpdateInfoText()
 {
     wxString msg;
 
-    msg.Printf(_("Generation: %u,  Interval: %u ms"), m_tics, m_interval);
+    msg.Printf(_("  Generation: %u (T: %u ms),  Population: %u  "),
+               m_tics,
+               m_topspeed? 0 : m_interval,
+               m_life->GetNumCells());
     m_text->SetLabel(msg);
 }
 
+// Enable or disable tools and menu entries according to the current
+// state. See also wxEVT_UPDATE_UI events for a slightly different
+// way to do this.
+void LifeFrame::UpdateUI()
+{
+    GetToolBar()->EnableTool(ID_START, !m_running);
+    GetToolBar()->EnableTool(ID_STOP,  m_running);
+    GetMenuBar()->GetMenu(1)->Enable(ID_START, !m_running);
+    GetMenuBar()->GetMenu(1)->Enable(ID_STEP,  !m_running);
+    GetMenuBar()->GetMenu(1)->Enable(ID_STOP,  m_running);
+}
+
 // event handlers
 void LifeFrame::OnMenu(wxCommandEvent& event)
 {
     switch (event.GetId())
     {
+        case ID_CENTER  : m_canvas->Recenter(0, 0); break;
         case ID_START   : OnStart(); break;
         case ID_STEP    : OnTimer(); break;
         case ID_STOP    : OnStop(); break;
-        case ID_WRAP    :
+        case ID_ZOOMIN  :
         {
-            bool checked = GetMenuBar()->GetMenu(1)->IsChecked(ID_WRAP);
-            m_life->SetBorderWrap(checked);
+            int cellsize = m_canvas->GetCellSize();
+            if (cellsize < 32)
+                m_canvas->SetCellSize(cellsize * 2);
             break;
         }
-        case ID_CLEAR   : 
+        case ID_ZOOMOUT :
         {
+            int cellsize = m_canvas->GetCellSize();
+            if (cellsize > 1)
+                m_canvas->SetCellSize(cellsize / 2);
+            break;
+        }
+        case ID_TOPSPEED:
+        {
+            m_running = TRUE;
+            m_topspeed = TRUE;
+            UpdateUI();
+            while (m_running && m_topspeed)           
+            {
+                OnTimer();
+                wxYield();
+            }
+            break;
+        }
+        case ID_RESET:
+        {
+            // stop if it was running
             OnStop();
             m_life->Clear();
-            m_canvas->DrawEverything(TRUE);
-            m_canvas->Refresh(FALSE);
+            m_canvas->Recenter(0, 0);
             m_tics = 0;
             UpdateInfoText();
             break;
         }
-        case ID_ABOUT :
+        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);
+            LifeAboutDialog dialog(this);
+            dialog.ShowModal();
             break;
         }
         case ID_EXIT :
@@ -274,42 +307,14 @@ void LifeFrame::OnMenu(wxCommandEvent& event)
     }
 }
 
-void LifeFrame::OnNewGame(wxCommandEvent& WXUNUSED(event))
+void LifeFrame::OnClose(wxCloseEvent& WXUNUSED(event))
 {
-    int w = m_life->GetWidth();
-    int h = m_life->GetHeight();
-
-    // stop if it was running
+    // Stop if it was running; this is absolutely needed because
+    // the frame won't be actually destroyed until there are no
+    // more pending events, and this in turn won't ever happen
+    // if the timer is running faster than the window can redraw.
     OnStop();
-
-    // dialog box
-    LifeNewGameDialog dialog(this, &w, &h);
-
-    // new game?
-    if (dialog.ShowModal() == wxID_OK)
-    {
-        // check dimensions
-        if (w >= LIFE_MIN && w <= LIFE_MAX &&
-            h >= LIFE_MIN && h <= LIFE_MAX)
-        {
-            // resize game field
-            m_life->Destroy();
-            m_life->Create(w, h);
-            
-            // tell the canvas
-            m_canvas->Reset();
-            m_canvas->Refresh();
-            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);
-        }
-    }
+    Destroy();   
 }
 
 void LifeFrame::OnSamples(wxCommandEvent& WXUNUSED(event))
@@ -323,40 +328,14 @@ void LifeFrame::OnSamples(wxCommandEvent& WXUNUSED(event))
     // new game?
     if (dialog.ShowModal() == wxID_OK)
     {
-        int result = dialog.GetValue();
-
-        if (result == -1)
-            return;
-
-        int gw   = g_shapes[result].m_fieldWidth;
-        int gh   = g_shapes[result].m_fieldHeight;
-        int wrap = g_shapes[result].m_wrap;
-
-        // set wraparound (don't ask the user)
-        m_life->SetBorderWrap(wrap);
-        GetMenuBar()->GetMenu(1)->Check(ID_WRAP, wrap);
-
-        // need to resize the game field?
-        if (gw > m_life->GetWidth() || gh > m_life->GetHeight())
-        {
-            wxString s;
-            s.Printf(_("Your game field is too small for this configuration.\n"
-                       "It is recommended to resize it to %u x %u. Proceed?\n"),
-                       gw, gh);
-
-            if (wxMessageBox(s, _("Question"), wxYES_NO | wxICON_QUESTION, this) == wxYES)
-            {
-                m_life->Destroy();
-                m_life->Create(gw, gh);
-            }
-        }
+        const LifeShape shape = dialog.GetShape();
 
         // put the shape
-        m_life->SetShape(g_shapes[result]);
+        m_life->Clear();
+        m_life->SetShape(shape);
 
-        // tell the canvas about the change
-        m_canvas->Reset();
-        m_canvas->Refresh();
+        // recenter canvas
+        m_canvas->Recenter(0, 0);
         m_tics = 0;
         UpdateInfoText();
     }
@@ -366,14 +345,9 @@ void LifeFrame::OnStart()
 {
     if (!m_running)
     {
-        GetToolBar()->EnableTool(ID_START, FALSE);
-        GetToolBar()->EnableTool(ID_STOP,  TRUE);
-        GetMenuBar()->GetMenu(1)->Enable(ID_START, FALSE);
-        GetMenuBar()->GetMenu(1)->Enable(ID_STEP,  FALSE);
-        GetMenuBar()->GetMenu(1)->Enable(ID_STOP,  TRUE);
-
         m_timer->Start(m_interval);
         m_running = TRUE;
+        UpdateUI();
     }
 }
 
@@ -381,14 +355,10 @@ void LifeFrame::OnStop()
 {
     if (m_running)
     {
-        GetToolBar()->EnableTool(ID_START, TRUE);
-        GetToolBar()->EnableTool(ID_STOP,  FALSE);
-        GetMenuBar()->GetMenu(1)->Enable(ID_START, TRUE);
-        GetMenuBar()->GetMenu(1)->Enable(ID_STEP,  TRUE);
-        GetMenuBar()->GetMenu(1)->Enable(ID_STOP,  FALSE);
-        
         m_timer->Stop();
         m_running = FALSE;
+        m_topspeed = FALSE;
+        UpdateUI();
     }
 }
 
@@ -399,22 +369,20 @@ void LifeFrame::OnTimer()
     else
         OnStop();
 
+    m_canvas->DrawChanged();
     UpdateInfoText();
-    m_canvas->DrawEverything();
-    m_canvas->Refresh(FALSE);
 }
 
 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);
+        OnStop();
+        OnStart();
     }
-
+    
     UpdateInfoText();
 }
 
@@ -433,129 +401,209 @@ void LifeTimer::Notify()
 
 // canvas constructor
 LifeCanvas::LifeCanvas(wxWindow *parent, Life *life, bool interactive)
-          : wxScrolledWindow(parent, -1, wxPoint(0, 0), wxSize(100, 100))
+          : wxWindow(parent, -1, wxPoint(0, 0), wxSize(100, 100),
+            wxSUNKEN_BORDER)
 {
     m_life        = life;
     m_interactive = interactive;
     m_cellsize    = 8;
-    m_bmp         = NULL;
-    Reset();
+    m_status      = MOUSE_NOACTION;
+    m_viewportX   = 0;
+    m_viewportY   = 0;
+    m_viewportH   = 0;
+    m_viewportW   = 0;
+
+    if (m_interactive)
+        SetCursor(*wxCROSS_CURSOR);
+
+    // reduce flicker if wxEVT_ERASE_BACKGROUND is not available
+    SetBackgroundColour(*wxWHITE);       
 }
 
 LifeCanvas::~LifeCanvas()
 {
-    delete m_bmp;
+    delete m_life;   
 }
 
-void LifeCanvas::Reset()
+// recenter at the given position
+void LifeCanvas::Recenter(wxInt32 i, wxInt32 j)
 {
-    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  = GetClientSize().GetX();
-    wxCoord h  = GetClientSize().GetY();
-    m_xoffset  = (w > m_width)?  ((w - m_width) / 2)  : 0;
-    m_yoffset  = (h > m_height)? ((h - m_height) / 2) : 0;
+    m_viewportX = i - m_viewportW / 2;
+    m_viewportY = j - m_viewportH / 2;
 
     // redraw everything
-    DrawEverything(TRUE);
-    SetScrollbars(10, 10, (m_width + 9) / 10, (m_height + 9) / 10);
+    Refresh(FALSE);
 }
 
-void LifeCanvas::DrawEverything(bool force)
+// set the cell size and refresh display
+void LifeCanvas::SetCellSize(int cellsize)
 {
-    wxMemoryDC dc;
-
-    dc.SelectObject(*m_bmp);
-    dc.BeginDrawing();
-
-    // draw cells
-    const CellArray *cells =
-        force? m_life->GetCells() : m_life->GetChangedCells();
+    m_cellsize = cellsize;
+
+    // find current center
+    wxInt32 cx = m_viewportX + m_viewportW / 2;
+    wxInt32 cy = m_viewportY + m_viewportH / 2;
+
+    // get current canvas size and adjust viewport accordingly
+    wxCoord w, h;
+    GetClientSize(&w, &h);
+    m_viewportW = (w + m_cellsize - 1) / m_cellsize;
+    m_viewportH = (h + m_cellsize - 1) / m_cellsize;
+    
+    // recenter
+    m_viewportX = cx - m_viewportW / 2;
+    m_viewportY = cy - m_viewportH / 2;
+
+    // adjust scrollbars
+    if (m_interactive)
+    {
+        SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
+        SetScrollbar(wxVERTICAL,   m_viewportH, m_viewportH, 3 * m_viewportH);
+        m_thumbX = m_viewportW;
+        m_thumbY = m_viewportH;
+    }
+    
+    Refresh(FALSE);
+}
 
-    for (unsigned i = 0; i < cells->GetCount(); i++)
-        DrawCell(cells->Item(i), dc);
+// draw a cell
+void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, bool alive)
+{
+    wxClientDC dc(this);
 
-    // bounding rectangle (always drawn - better than clipping region)
-    dc.SetPen(*wxBLACK_PEN);
-    dc.SetBrush(*wxTRANSPARENT_BRUSH);
-    dc.DrawRectangle(0, 0, m_width, m_height);
+    dc.SetPen(alive? *wxBLACK_PEN : *wxWHITE_PEN);
+    dc.SetBrush(alive? *wxBLACK_BRUSH : *wxWHITE_BRUSH);
 
+    dc.BeginDrawing();
+    DrawCell(i, j, dc);
     dc.EndDrawing();
-    dc.SelectObject(wxNullBitmap);
 }
 
-void LifeCanvas::DrawCell(Cell c)
+void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, wxDC &dc)
 {
-    wxMemoryDC dc;
-
-    dc.SelectObject(*m_bmp);
-    dc.BeginDrawing();
-
-    dc.SetClippingRegion(1, 1, m_width - 2, m_height - 2);
-    DrawCell(c, dc);
+    wxCoord x = CellToX(i);
+    wxCoord y = CellToY(j);
 
-    dc.EndDrawing();
-    dc.SelectObject(wxNullBitmap);
+    // if cellsize is 1 or 2, there will be no grid
+    switch (m_cellsize)
+    {
+        case 1:
+            dc.DrawPoint(x, y);
+            break;
+        case 2:
+            dc.DrawRectangle(x, y, 2, 2);
+            break;
+        default:
+            dc.DrawRectangle(x + 1, y + 1, m_cellsize - 1, m_cellsize - 1);
+    }
 }
 
-void LifeCanvas::DrawCell(Cell c, wxDC &dc)
+// draw all changed cells
+void LifeCanvas::DrawChanged()
 {
-    if (m_life->IsAlive(c))
+    wxClientDC dc(this);
+
+    size_t ncells;
+    Cell *cells;
+    bool done = FALSE;
+
+    m_life->BeginFind(m_viewportX,
+                      m_viewportY,
+                      m_viewportX + m_viewportW,
+                      m_viewportY + m_viewportH,
+                      TRUE);
+   
+    dc.BeginDrawing();
+    dc.SetLogicalFunction(wxINVERT);
+
+    if (m_cellsize == 1)
     {
+        // drawn using DrawPoint
         dc.SetPen(*wxBLACK_PEN);
-        dc.SetBrush(*wxBLACK_BRUSH);
-        dc.DrawRectangle(CellToCoord( m_life->GetX(c) ),
-                         CellToCoord( m_life->GetY(c) ),
-                         m_cellsize,
-                         m_cellsize);
     }
     else
     {
-        dc.SetPen(*wxLIGHT_GREY_PEN);
-        dc.SetBrush(*wxTRANSPARENT_BRUSH);
-        dc.DrawRectangle(CellToCoord( m_life->GetX(c) ),
-                         CellToCoord( m_life->GetY(c) ),
-                         m_cellsize,
-                         m_cellsize);
-        dc.SetPen(*wxWHITE_PEN);
-        dc.SetBrush(*wxWHITE_BRUSH);
-        dc.DrawRectangle(CellToCoord( m_life->GetX(c) ) + 1,
-                         CellToCoord( m_life->GetY(c) ) + 1,
-                         m_cellsize - 1,
-                         m_cellsize - 1);
+        // drawn using DrawRectangle
+        dc.SetPen(*wxTRANSPARENT_PEN);
+        dc.SetBrush(*wxBLACK_BRUSH);
+    }
+
+    while (!done)
+    {
+        done = m_life->FindMore(&cells, &ncells);
+
+        for (size_t m = 0; m < ncells; m++)
+            DrawCell(cells[m].i, cells[m].j, dc);
     }
+    dc.EndDrawing();
 }
 
 // event handlers
 void LifeCanvas::OnPaint(wxPaintEvent& event)
 {
     wxPaintDC dc(this);
-    wxMemoryDC memdc;
+    wxRect  rect = GetUpdateRegion().GetBox();
+    wxCoord x, y, w, h;
+    wxInt32 i0, j0, i1, j1;
+
+    // find damaged area
+    x = rect.GetX();
+    y = rect.GetY();
+    w = rect.GetWidth();
+    h = rect.GetHeight();
+
+    i0 = XToCell(x);
+    j0 = YToCell(y);
+    i1 = XToCell(x + w - 1);
+    j1 = YToCell(y + h - 1);
+
+    size_t ncells;
+    Cell *cells;
+    bool done = FALSE;
 
-    wxRegionIterator upd(GetUpdateRegion());
-    wxCoord x, y, w, h, xx, yy;
+    m_life->BeginFind(i0, j0, i1, j1, FALSE);
+    done = m_life->FindMore(&cells, &ncells);
 
+    // erase all damaged cells and draw the grid
     dc.BeginDrawing();
-    memdc.SelectObject(*m_bmp);
+    dc.SetBrush(*wxWHITE_BRUSH);
 
-    while(upd)
+    if (m_cellsize <= 2)
     {
-        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++;
+       // no grid
+       dc.SetPen(*wxWHITE_PEN);
+       dc.DrawRectangle(x, y, w, h);
     }
+    else
+    {
+        x = CellToX(i0);
+        y = CellToY(j0);
+        w = CellToX(i1 + 1) - x + 1;
+        h = CellToY(j1 + 1) - y + 1;
+
+        dc.SetPen(*wxLIGHT_GREY_PEN);
+        for (wxInt32 yy = y; yy <= (y + h - m_cellsize); yy += m_cellsize)
+            dc.DrawRectangle(x, yy, w, m_cellsize + 1);
+        for (wxInt32 xx = x; xx <= (x + w - m_cellsize); xx += m_cellsize)
+            dc.DrawLine(xx, y, xx, y + h);
+    }
+
+    // draw all alive cells
+    dc.SetPen(*wxBLACK_PEN);
+    dc.SetBrush(*wxBLACK_BRUSH);
+
+    while (!done)
+    {
+        for (size_t m = 0; m < ncells; m++)
+            DrawCell(cells[m].i, cells[m].j, dc);
+
+        done = m_life->FindMore(&cells, &ncells);
+    }
+
+    // last set
+    for (size_t m = 0; m < ncells; m++)
+        DrawCell(cells[m].i, cells[m].j, dc);
 
-    memdc.SelectObject(wxNullBitmap);
     dc.EndDrawing();
 }
 
@@ -564,42 +612,21 @@ void LifeCanvas::OnMouse(wxMouseEvent& event)
     if (!m_interactive)
         return;
 
-    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);
-    }
+    wxInt32 i = XToCell( event.GetX() );
+    wxInt32 j = YToCell( event.GetY() );
+
+    // set statusbar text
+    wxString msg;
+    msg.Printf(_("Cell: (%d, %d)"), i, j);
+    GET_FRAME()->SetStatusText(msg, 1);
 
     // button pressed?
     if (!event.LeftIsDown())
     {
         m_status = MOUSE_NOACTION;
     }
-    else if (i >= 0 && i < m_life->GetWidth() &&
-             j >= 0 && j < m_life->GetHeight())
+    else
     {
         bool alive = m_life->IsAlive(i, j);
 
@@ -611,20 +638,124 @@ void LifeCanvas::OnMouse(wxMouseEvent& event)
         if (((m_status == MOUSE_ERASING) && alive) ||
             ((m_status == MOUSE_DRAWING) && !alive))
         {
-            wxRect rect(x, y, m_cellsize + 1, m_cellsize + 1);
-            DrawCell( m_life->SetCell(i, j, !alive) );
-            Refresh(FALSE, &rect);
+            m_life->SetCell(i, j, !alive);
+            DrawCell(i, j, !alive);
+            GET_FRAME()->UpdateInfoText();
         }
     }
 }
 
 void LifeCanvas::OnSize(wxSizeEvent& event)
 {
+    // find center
+    wxInt32 cx = m_viewportX + m_viewportW / 2;
+    wxInt32 cy = m_viewportY + m_viewportH / 2;
+
+    // get new size
     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;
+    m_viewportW = (w + m_cellsize - 1) / m_cellsize;
+    m_viewportH = (h + m_cellsize - 1) / m_cellsize;
+
+    // recenter
+    m_viewportX = cx - m_viewportW / 2;
+    m_viewportY = cy - m_viewportH / 2;
+
+    // scrollbars
+    if (m_interactive)
+    {
+        SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
+        SetScrollbar(wxVERTICAL,   m_viewportH, m_viewportH, 3 * m_viewportH);
+        m_thumbX = m_viewportW;
+        m_thumbY = m_viewportH;
+    }
 
     // allow default processing
     event.Skip();
 }
+
+void LifeCanvas::OnScroll(wxScrollWinEvent& event)
+{
+    WXTYPE type = event.GetEventType();
+    int pos     = event.GetPosition();
+    int orient  = event.GetOrientation();
+    bool scrolling = event.IsScrolling();
+    int  scrollinc = 0;
+
+    // calculate scroll increment
+    switch (type)
+    {
+        case wxEVT_SCROLLWIN_TOP:
+        {
+            if (orient == wxHORIZONTAL)
+                scrollinc = -m_viewportW;
+            else
+                scrollinc = -m_viewportH;
+            break;    
+        }
+        case wxEVT_SCROLLWIN_BOTTOM:
+        {
+            if (orient == wxHORIZONTAL)
+                scrollinc = m_viewportW;
+            else
+                scrollinc = m_viewportH;
+            break;    
+        }
+        case wxEVT_SCROLLWIN_LINEUP:   scrollinc = -1; break;
+        case wxEVT_SCROLLWIN_LINEDOWN: scrollinc = +1; break;
+        case wxEVT_SCROLLWIN_PAGEUP:   scrollinc = -10; break;
+        case wxEVT_SCROLLWIN_PAGEDOWN: scrollinc = +10; break;
+        case wxEVT_SCROLLWIN_THUMBTRACK:
+        {
+            if (scrolling)
+            {
+                // user is dragging the thumb in the scrollbar
+                if (orient == wxHORIZONTAL)
+                {
+                    scrollinc = pos - m_thumbX;
+                    m_thumbX = pos;
+                }
+                else
+                {
+                    scrollinc = pos - m_thumbY;
+                    m_thumbY = pos;
+                }
+            }
+            else
+            {
+                // user released the thumb after dragging
+                m_thumbX = m_viewportW;
+                m_thumbY = m_viewportH;
+            }
+            break;
+        }
+    }
+
+#ifdef __WXGTK__ // what about Motif?
+    // wxGTK updates the thumb automatically (wxMSW doesn't); reset it back
+    if ((type != wxEVT_SCROLLWIN_THUMBTRACK) || !scrolling)
+    {
+        SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
+        SetScrollbar(wxVERTICAL,   m_viewportH, m_viewportH, 3 * m_viewportH);
+    }
+#endif
+
+    if (scrollinc == 0) return;
+    
+    // scroll the window and adjust the viewport
+    if (orient == wxHORIZONTAL)
+    {
+        m_viewportX += scrollinc;
+        ScrollWindow( -m_cellsize * scrollinc, 0, (const wxRect *) NULL);
+    }
+    else
+    {
+        m_viewportY += scrollinc;    
+        ScrollWindow( 0, -m_cellsize * scrollinc, (const wxRect *) NULL);
+    }
+}
+
+void LifeCanvas::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
+{
+    // do nothing. I just don't want the background to be erased, you know.
+}
index 946e84838a25445793a742101429bec9bda29f11..4f87eee9813571fa9a95a0cd560fed4ba285df1b 100644 (file)
 // LifeCanvas
 // --------------------------------------------------------------------------
 
-class LifeCanvas : public wxScrolledWindow
+/* Note that in LifeCanvas, all cell coordinates are
+ * named i, j, while screen coordinates are named x, y.
+ */
+class LifeCanvas : public wxWindow
 {
 public:
     // ctor and dtor
     LifeCanvas(wxWindow* parent, Life* life, bool interactive = TRUE);
     ~LifeCanvas();
 
-    // member functions
-    void Reset();
-    void DrawEverything(bool force = FALSE);
-    void DrawCell(Cell c);
-    void DrawCell(Cell c, 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); };
+    // view management
+    int  GetCellSize() const { return m_cellsize; };
+    void SetCellSize(int cellsize);
+    void Recenter(wxInt32 i, wxInt32 j);
+
+    // drawing
+    void DrawChanged();
+    void DrawCell(wxInt32 i, wxInt32 j, bool alive);
+
+private:
+    // any class wishing to process wxWindows events must use this macro
+    DECLARE_EVENT_TABLE()
+
+    // draw a cell (parametrized by DC)
+    void DrawCell(wxInt32 i, wxInt32 j, wxDC &dc);
 
     // event handlers
     void OnPaint(wxPaintEvent& event);
     void OnMouse(wxMouseEvent& event);
     void OnSize(wxSizeEvent& event);
+    void OnScroll(wxScrollWinEvent& event);
+    void OnEraseBackground(wxEraseEvent& event);
 
-private:
-    // any class wishing to process wxWindows events must use this macro
-    DECLARE_EVENT_TABLE()
+    // conversion between cell and screen coordinates
+    inline wxInt32 XToCell(wxCoord x) const { return (x / m_cellsize) + m_viewportX; };
+    inline wxInt32 YToCell(wxCoord y) const { return (y / m_cellsize) + m_viewportY; };
+    inline wxCoord CellToX(wxInt32 i) const { return (i - m_viewportX) * m_cellsize; };
+    inline wxCoord CellToY(wxInt32 j) const { return (j - m_viewportY) * m_cellsize; };
 
+    // what is the user doing?
     enum MouseStatus
     {
         MOUSE_NOACTION,
@@ -65,15 +81,16 @@ private:
         MOUSE_ERASING
     };
 
-    Life        *m_life;
-    wxBitmap    *m_bmp;
-    int          m_height;
-    int          m_width;
-    int          m_cellsize;
-    wxCoord      m_xoffset;
-    wxCoord      m_yoffset;
-    MouseStatus  m_status;
-    bool         m_interactive;
+    Life        *m_life;            // Life object
+    int          m_cellsize;        // current cell size, in pixels
+    bool         m_interactive;     // is this canvas interactive?
+    MouseStatus  m_status;          // what is the user doing?
+    wxInt32      m_viewportX;       // first visible cell (x coord)
+    wxInt32      m_viewportY;       // first visible cell (y coord)
+    wxInt32      m_viewportW;       // number of visible cells (w)
+    wxInt32      m_viewportH;       // number of visible cells (h)
+    int          m_thumbX;          // horiz. scrollbar thumb position 
+    int          m_thumbY;          // vert. scrollbar thumb position 
 };
 
 // --------------------------------------------------------------------------
@@ -100,25 +117,27 @@ public:
 
     // member functions
     void UpdateInfoText();
+    void UpdateUI();
+    void OnTimer();
+
+private:
+    // any class wishing to process wxWindows events must use this macro
+    DECLARE_EVENT_TABLE()
 
     // event handlers
     void OnMenu(wxCommandEvent& event);
-    void OnNewGame(wxCommandEvent& event);
     void OnSamples(wxCommandEvent& event);
+    void OnSlider(wxScrollEvent& event);
+    void OnClose(wxCloseEvent& event);
     void OnStart();
     void OnStop();
-    void OnTimer();
-    void OnSlider(wxScrollEvent& event);
-
-private:
-    // any class wishing to process wxWindows events must use this macro
-    DECLARE_EVENT_TABLE()
 
-    Life         *m_life;
-    LifeTimer    *m_timer;
+    Life         *m_life;  
+    LifeTimer    *m_timer; 
     LifeCanvas   *m_canvas;
     wxStaticText *m_text;
     bool          m_running;
+    bool          m_topspeed;
     long          m_interval;
     long          m_tics;
 };
index 96782b26a337d440b05df40fb3d90930f5955ad2..0d8955774b02e73454a6d58ee123eca59b343f57 100644 (file)
@@ -1,6 +1,9 @@
 mondrian ICON "mondrian.ico"
 #include "wx/msw/wx.rc"
 
-reset BITMAP "bitmaps/reset.bmp"
-play  BITMAP "bitmaps/play.bmp"
-stop  BITMAP "bitmaps/stop.bmp"
+reset   BITMAP "bitmaps/reset.bmp"
+play    BITMAP "bitmaps/play.bmp"
+stop    BITMAP "bitmaps/stop.bmp"
+zoomin  BITMAP "bitmaps/zoomin.bmp"
+zoomout BITMAP "bitmaps/zoomout.bmp"
+life    BITMAP "bitmaps/life.bmp"
index 41425e856a09b392acdd97833fbec853613a29f5..22fe76d5e23bd87db44deaac6e7baa8012f2b476 100644 (file)
  *
  * Name,
  * Description,
- * Width, Height,
- * Data,                        ('*' = alive, '.' = dead)
- * Field width, Field height,   (optional, defaults to 20 x 20)
- * Wraparound                   (optional, defaults to TRUE)
+ * Width,
+ * Height,
+ * Data ('*' = alive, '.' = dead)
  * 
  */
 
-LifeShape g_shapes[] =
+const LifeShape g_shapes[] =
 {
     LifeShape( _("Glider"),
                _("The glider is the first of a series of life forms, known "
@@ -111,8 +110,7 @@ LifeShape g_shapes[] =
                3, 3,
                ".**"
                "**."
-               ".*.",
-               80, 80, FALSE ),
+               ".*." ),
     LifeShape( _("Thunderbird"),
                _("The thunderbird is another popular methuselah, which "
                  "doesn't stabilize until the 243th generation. Note that "
@@ -124,17 +122,14 @@ LifeShape g_shapes[] =
                "..."
                ".*."
                ".*."
-               ".*.",
-               60, 60, FALSE ),
+               ".*." ),
     LifeShape( _("Accorn"),
                _("Probably the most popular methuselah, the accorn lives "
-                 "for 5206 (!) generations. To see it in action, a very "
-                 "large game field is needed."),
+                 "for 5206 (!) generations."),
                7, 3,
                ".*....."
                "...*..."
-               "**..***",
-               150, 150, FALSE ),
+               "**..***" ),
     LifeShape( _("Galaxy"),
                _("One from my personal collection. It is really beautiful "
                  "to see this configuration expand and shrink periodically "
@@ -152,6 +147,110 @@ LifeShape g_shapes[] =
                "*.....*......"
                "......*......"
                "......*......"
-               ".......***...",
-               80, 80, FALSE )
+               ".......***..." ),
+    LifeShape( _("Glider gun"),
+               _("A gun is a stationary pattern that emits spaceships "
+                 "forever. The glider gun shown here was the first known "
+                 "gun, and indeed the first known finite pattern with "
+                 "unbounded growth. It was found by Bill Gosper in "
+                 "November 1970. Many new guns have since been found."),
+               36, 9,
+               ".........................*.........."
+               "......................****....*....."
+               ".............*.......****.....*....."
+               "............*.*......*..*.........**"
+               "...........*...**....****.........**"
+               "**.........*...**.....****.........."
+               "**.........*...**........*.........."
+               "............*.*....................."
+               ".............*......................" ),
+    LifeShape( _("Puffer train"),
+               _("A puffer is an object that moves like a spaceship, except "
+                 "that it leaves a trail of debris behind. The puffer train "
+                 "is one of the best-known puffers. Originally found by "
+                 "Bill Gosper, this is a very dirty puffer; the tail does "
+                 "not stabilize until generation 5533. It consists of a "
+                 "B-heptomino (the middle pattern) escorted by two light "
+                 "weight space ships."),
+               5, 18,
+               "...*."
+               "....*"
+               "*...*"
+               ".****"
+               "....."
+               "....."
+               "....."
+               "*...."
+               ".**.."
+               "..*.."
+               "..*.."
+               ".*..."
+               "....."
+               "....."
+               "...*."
+               "....*"
+               "*...*"
+               ".****" ),
+    LifeShape( _("Max"),
+               _("Max is the fastest-growing known pattern in Conway's Game "
+                 "of Life (possibly the fastest possible). It fills space to "
+                 "a density of 1/2, conjectured to be the maximum density, "
+                 "and does it at a speed of c/2 in each of the 4 directions, "
+                 "which has been proven to be the maximum possible speed.\n"
+                 "\n"
+                 "Population growth is:\n"
+                 "[(t+19)^2+463]/4 for t divisible by 4;\n"
+                 "[(t+19)^2+487]/4 for t even, not div. by 4;\n"
+                 "[(t+18)^2+639]/4 for t odd.\n"
+                 "\n"
+                 "Original construction, top/bottom stretchers by Hartmut "
+                 "Holzwart; Size optimization, left/right stretchers by David "
+                 "Bell; Original idea, middle part, left/right stretcher "
+                 "connection by Al Hensel.\n"
+                 "\n"
+                 "This spacefiller by David Bell, September 1993."),
+               29, 43,
+               ".....*.*....................."
+               "....*..*....................."
+               "...**........................"
+               "..*.........................."
+               ".****........................"
+               "*....*......................."
+               "*..*........................."
+               "*..*........................."
+               ".*.........***...***........."
+               "..****.*..*..*...*..*........"
+               "...*...*.....*...*..........."
+               "....*........*...*..........."
+               "....*.*......*...*..........."
+               "............................."
+               "...***.....***...***........."
+               "...**.......*.....*.........."
+               "...***......*******.........."
+               "...........*.......*........."
+               "....*.*...***********........"
+               "...*..*..*............**....."
+               "...*.....************...*...."
+               "...*...*.............*...*..."
+               "....*...************.....*..."
+               ".....**............*..*..*..."
+               "........***********...*.*...."
+               ".........*.......*..........."
+               "..........*******......***..."
+               "..........*.....*.......**..."
+               ".........***...***.....***..."
+               "............................."
+               "...........*...*......*.*...."
+               "...........*...*........*...."
+               "...........*...*.....*...*..."
+               "........*..*...*..*..*.****.."
+               ".........***...***.........*."
+               ".........................*..*"
+               ".........................*..*"
+               ".......................*....*"
+               "........................****."
+               "..........................*.."
+               "........................**..."
+               ".....................*..*...."
+               ".....................*.*....." )
 };