From e0a4029251c1d80d50f9cb0bd152d1fca7914bc4 Mon Sep 17 00:00:00 2001 From: Guillermo Rodriguez Garcia Date: Tue, 8 Feb 2000 20:04:48 +0000 Subject: [PATCH] Life! version 2 git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@5912 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- demos/life/bitmaps/life.bmp | Bin 0 -> 5666 bytes demos/life/bitmaps/life.xpm | 81 ++ demos/life/bitmaps/zoomin.bmp | Bin 0 -> 246 bytes demos/life/bitmaps/zoomin.xpm | 23 + demos/life/bitmaps/zoomout.bmp | Bin 0 -> 246 bytes demos/life/bitmaps/zoomout.xpm | 23 + demos/life/dialogs.cpp | 164 ++-- demos/life/dialogs.h | 42 +- demos/life/game.cpp | 1366 ++++++++++++++++++++++++++++---- demos/life/game.h | 136 ++-- demos/life/life.cpp | 651 +++++++++------ demos/life/life.h | 77 +- demos/life/life.rc | 9 +- demos/life/samples.inc | 129 ++- 14 files changed, 2071 insertions(+), 630 deletions(-) create mode 100644 demos/life/bitmaps/life.bmp create mode 100644 demos/life/bitmaps/life.xpm create mode 100644 demos/life/bitmaps/zoomin.bmp create mode 100644 demos/life/bitmaps/zoomin.xpm create mode 100644 demos/life/bitmaps/zoomout.bmp create mode 100644 demos/life/bitmaps/zoomout.xpm diff --git a/demos/life/bitmaps/life.bmp b/demos/life/bitmaps/life.bmp new file mode 100644 index 0000000000000000000000000000000000000000..46fd00342c4748d8ac7367487bd1d4d632e6c5d0 GIT binary patch literal 5666 zcmb`KJ96wc5JllCR}BM|C$bmd$m?+Cz1WO<{rhW5|NY`WtLUei5=W}l zFPyne<2Z6v!(vBT7TxjVPgR`iFnv`S$6V9yYiQ&6x-5?=y?g;~oVLp{FUwOJ?#7MN zfNBNaT+^M>jnfz`E|2vtUtw*^k~{C|4(Y~8f@PfKnDvvdw@K%$d6_c{U_35|+oXe4 z9dbz9ZkyKidg)+=_-`ac!buL9I4@Tl2Wv0E7%f&xtVHJx*BJ*3@l7meaei*LF~c>+ ziAA)|tR$J#%20ewW#Ze#K_^xScEpl#_!>+5w!17Nj754g(!_6;rL@f@ScDyIpU0e7 zk=`bL?J(v1#wKz3K3C3I8#h51i!9@b#f?@t-2H4>jfG&dt(k4Lg~g+2|In3H307Ii zuMb>pEc$1Ok91OL$8E5vz}1k>Haf|2t{6+v%G?J4@(f79gwB$kn5`}5mE4 z-@Qgbv4CXuxyG`}cdj~X&j1J#3>F~S#-WPsTy)_HyXJKsbM@qH=qw3FXUz}Qj8cZS z)ex)ITl6$n^NFh~3xcu;sD+k$^&M+W6@uIJ8MU$o<+r0l|f=XXOqo&m>jI_>gZy#*Uoam)J}_L zx44i2s9qhU2mA1dYRyE!lesK;c*j~wT^CXjM^{+59)$(8lmc8P<%gVAT?Ui`K;)|ya0vSaS!htQkJ)OmKOsD5Gl-^mJ#^Ghv%M+k(+Llvn zmtqvl^vF=RlU#scEb4kLu#AW4J&VsoeS!IbZ5rjV87$UuW%YL*{m-$cX@}0{Dw(&k z_>S@!sFj^j%uZ9|j9v0|=hj2y>kGT|=A<-M_mMiYQ>@_!EYX^2bETu9BscdoG`)<) z{;%qFFO03{=;EF(D!H*dUz}wjA2F{O`iLdln9h%|OP5X9<({{Yob$&lTZO=dVuG=i z`ik%^8<`~h%MbNx&Q`>AoIazh5P@39#TrO-n#GzvWznO1>IzAsW|wf%>Qa`XH%u!} zGAvjq)%+(#F0)JCR&Cri7Vm=PpY4t#LFy$wnsR$N>e(Q!d{lXDrm?GD%wi9LhAcXK z-!K@76}X2hSfT~%c+XDSrG&eVC0a8rmeg$XuZ3$_ECRy2b=5dJ>1Co&y&%4((^9u` zN^UipD@d>J_rlYm-#_7~v;1YE1enQ$n^=~mCN~IN)SK2q^#;3SDX);?!o4YUVd;bV zdLPANMM)tq{L>2efyx_M79Sw&(ChCm7LNAq2-ux0$<|G~n`N>YPN90cUW`>4ExfdQ j-OrMv5ngxOg*n=dzQ`_Ub!-3o-5g&MsF(eNZpZ%t>lQE_ literal 0 HcmV?d00001 diff --git a/demos/life/bitmaps/life.xpm b/demos/life/bitmaps/life.xpm new file mode 100644 index 0000000000..60328e37cf --- /dev/null +++ b/demos/life/bitmaps/life.xpm @@ -0,0 +1,81 @@ +/* XPM */ +static char * life_xpm[] = { +"151 73 5 1", +" c None", +". c #808080", +"X c Black", +"o c Gray100", +"O c #c0c0c0", +" .X ", +" .XXo.. ", +" .XXOooooX ", +" .XXooOoooooX ", +" XXXoooooOoooooX ", +" .XXXoooOoooooOooooX. ", +" .XXoOoooooOoooooOoooOOX ", +" .XXXooooOoooooOoooooOOOoooX ", +" .XXooOoooooOoooooOoooOOOoooooX ", +" .XXXooooooooooooooooo.OOoooOoooooX ", +" .XXoooOooooooOoooooOo.XXXOoooooOoooo.. ", +" ..XXooooooOooooooOoooOXXXXXXX.oooooOoooooX ", +" .XXooooooooooOooooooO.XXXXXXXXXXXoooooOoooOOX ", +" .XXooOooooooooooOooo.XXXXXXXXXXXXXXXoooooOOOOooX ", +" .XXOoooooOoooooOoooo.XXXXXXXXXXXXXXXXXXXooOOOOoooooX. ", +" .XXoooOoooooOoooooOo.XXXXXXXXXXXXXXXXXXXXXXOOoooOOoooooX ", +" .XXoOoooooOoooooOooooOXXXXXXXXXXXXXXXXXXXXXX.oOooooooOoooooX ", +" .XXOooooOoooooOoooooOOOOooXXXXXXXXXXXXXXXXXX.OoooooOoooooOoooooX ", +" .XXOooooooooOoooooOoo.XX.oooooXXXXXXXXXXXXXXX.oooOoooooOoooooOoooOOX. ", +" .XX.ooooooooooOoooooOXXXXXXOoooooXXXXXXXXXXX.OOoooooOoooooOoooooOOOooo.X ", +" .XXOooOoooooOoooooOoO.XXXXXXXXXOoooooXXXXXXXXoooooOoooooOoooooOooOOoOOoooooX ", +" XXXOoooooOoooooOoooo.XXXXXXXXXXXXXOoooooXXXXXXXooooooOoooooOooooOOOoooooOoooooX ", +" .XXXooooOoooooOooooo..XXXXXXXXXXXXXXXXXOoooOOXXXXXXXooooooOoooooOOOOooOooooooOoooooX ", +" ..XXooOoooooOoooooOooOXXXXXXXXXXXXXXXXXXXXX.OOoooXXXXXXXooooooOoo.XXXooooooOoooooOoooooX. ", +" ..XXOoooooOoooooOoooooXXXXXXXXXXXXXXXXXXXXXXXOoOoooooXXXXXXXoooooOXXXXXXXooooooOoooooOoooOOOX ", +" ..XXoooOooooooooooooooOOooXXXXXXXXXXXXXXXXXXX.oooooOoooooXXXXXXXo.XXXXXXXXXXXOoooooOoooooOOOooooX ", +" ..XXoOoooooOoooooOoooO.XOoooooXXXXXXXXXXXXXXX.OoOooooooOoooooXXXXXXXXXXXXXXXXXXXXXoooooOoOOOOoOoooooX ", +" ..XX.ooooOoooooOooooo..XXXXXoooooOXXXXXXXXXXXXOoooooOooooooOoooooXXXXXXXXXXXXXXXXXXXXXooooOOooooooOoooooX. ", +" ..XXoooOoooooOoooooOooOOoXXXXXXXooooo.XXXXXXX.OoooOoooooOooooooOooOOOXXXXXXXXXXXXXXXXXXXXXOOOooOooooooOooooo.X ", +" .XXOOoooooOoooooOoooooOOOooooXXXXXXXooooo.XXXXXXooooooOoooooOoooooOOoooooXXXXXXXXXXXXXXXXXXXOOooooooOoooooOooooooX ", +" .XXOoooOOoooooOoooooOoOOooOooooooXXXXXX.ooooOXXXXXXXooooooOoooooOoOOOooOoooooXXXXXXXXXXXXXXX.ooooOooooooOoooooOoooOOoX ", +" ..XX.oOoooooOooooooOoooOOOoooooOooooo.XXXXXXOOOOooXXXXXXXooooooOooo.XXooooooOoooooXXXXXXXXXX.OooOoooooOooooooOoooooOOoooooX. ", +" ..XXooooooOoooooOoooooOOOooooooooooOoooooXXXXXXXOoooooXXXXXXXoooooOXXXXXXXooooooOoooooXXXXXXX.ooooooOoooooOooooooOoOOOooOooooo.X ", +" .XXXoOoooooOoooooOoooooOOoooooOooooooOoooooXXXXXXXooooooXXXXXXXoooO.XXXXXXXXooooooOoooooXXXXXXX.ooooooOooooooOoooo.XOoooooOooooo.X ", +" XoooooOoooooOoooooXOOooooOoooooOoooooOoooOOOXXXXXXXooooooXXXXXXX.XXXXXXXXXXXXooooooOoooOOXXXXXXXXooooooOooooooO.XXXXXooooooOooooooX ", +" XoooooOooooooOo.XXX.ooooooOoooooOoooooOOOooooXXXXXXXoooooOXXXXXXXXXXXXXXXXXXXXoooooOOOOoooXXXXXXXXooooooOooo.XXXXXXXXXOoooooOooooooX ", +" XoooooOooooO.XXXXXXooooooOooooooOoOOooOooooo.XXXXXXXooooo.XXXXXXXXXXXXXXXXXXXXooOOOoOooooo.XXXXXXXooooooXXXXXXXXXXXXXX.oooooOOoooOOX. ", +" ..oooooOOOoooXXXXXXXooooooOooooOOOoooooOoooooXXXXXXX.OOOoo.XXXXXXXXXXXXXXXXXXXOOoooooOooooo.XXXXXXXoO.XXXXXXXXXXXXXXXXXXooooooOOOooo.X ", +" XooOOOOoooooOXXXXXX.ooooooOOOoooOoooooOooooooXXXXXXXOoooooXXXXXXXXXXXXXXXXXoooOooooooOooooo.XXXXXXXXXXXXXXXXXXXXXXXXXXXXooOOOoOooooooX ", +" .XoooooOoooooXXXXXXXooOOOOOooooooOoooooOooooooXXXXXXXooooooXXXXXXXXXXXX.OooooooOooooooOooooo.XXXXXXXXXXXXXXXXXXXXXXXXXXXXOoooooOooooooX ", +" XoooooOooooooXXXXXXXOoooooOoooooOooooooOoOOOooXXXXXXXooooooXXXXXXXX.ooooOooooooOooooooOooOOO.XXXXXXXXXXXXXXXXXXXXXXXX.ooOooooooOooooooX ", +" .XoooooOooooo.XXXXXX.ooooooOoooooOooooOOOooooo.XXXXXXXooooooXXXXXXXooooooOooooooOoooooOOOoooo.XXXXXXXXXXXXXXXXXXX.OOoooooOOoooooOOoooooX.", +" XoooooOoooOOOXXXXXXXooooooOooooooOOOoooOOoooooXXXXXXXXoooOOOXXXXXXXooooooOooooooOoOOoooOooooo.XXXXXXXXXXXXXXXXoooooOooooooOooooooOoo.XXXX", +" XooooOOOoooooXXXXXXXooooooOooOOooOoooooOooooooXXXXXXX.OOooo.XXXXXXXooooooOoooOOOOooooooOooooooXXXXXXXXXXX.ooOooooooOooooooOooooO.XXXX ", +" XOOOoooOoooooXXXXXXX.ooooOOOoooooOooooooOooooooXXXXXXXOooooo.XXXXXXXooooooOOoooooOooooooOooooooXXXXXX.OooooooOooooooOooooooXO.XXX. ", +" XoooooOooooooXXXXXXXOOOoooOOoooooOooooooOooooOOXXXXXXXooooooXXXXXXXXoOOOooOooooooOooooooOoooooOXXOoooOOooooooOooooooOooo.XXXX. ", +" XooooooOooooo.XXXXXXXooooooOooooooOoooooOOOOooo.XXXXXXXooooooXXXXXXX.ooooooOooooooOooooooOoOOOooOooooooOooooooOoooooOXXXX. ", +" XoooooOoooooOXXXXXXX.ooooooOooooooOooOOOOooooooXXXXXXXXooooooXXXXXXX.ooooooOooooooOooooOOOooooooOooooooOooooooOO.XXXX. ", +" ..oooooOOOOoooXXXXXXXoooooooOooooOOOoooooOooooooXXXXXXXXooOOooXXXXXXXOoooooOOoooooOOOOOoooOooooooOooooooOoooO.XXX.. ", +" XoooOOOOooooo.XXXXXXXooooooOOOOoooOooooooOooooooXXXXXXX.oooooOXXXXXXXOoooooOOooOOOoOooooooOooooooOoooooOXXXXX. ", +" .XoooooOooooooXXXXXXX.ooOOOOOooooooOooooooOoooooOXXXXXXXooooooOXXXXXXXoooooOOOooooooOooooooOooooooOo.XXXX. ", +" XooooooOoooooOXXXXXXXOoooooOOoooooOooooooOoooOOo.XXXXXXXoooooo.XXXXXXXOOOOooOoooooooOooooooOoooO.XXXX. ", +" .XoooooOooooooXXXXXXXXooooooOooooooOooooO.XooooooXXXXXXXXoooooo.XXXXXOOooooooOooooooooooooo.XXXXX. ", +" XooooooOoooOOOXXXXXXX.ooooooOoooooo..XXXXXXooooooXXXXXXXXoooooOXOoooooOooooooOoooooooooO.XXX. ", +" XoooooOOOoooo.XXXXXXXooooooOooo.XXXXXXXXXX.ooooooXXXXXXXXOOOoooOooooooOooooooOooooO.XXX. ", +" XOOOOooOooooooXXXXXXXXooooO.XXXXXXXXXXXXXXXooooooOXXXXXXOOooooooOooooooOooooooX.XXXX. ", +" XooooooOooooooXXXXXXXX.XXXXXXXXXXXXXXXXXXXXooooooOXOooooOoooooooOooooooOoo.XXXX. ", +" X.oooooOoooooo.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXoOOOooOooooooOooooooOOoooo.XXXX. ", +" XooooooOoooooOXXXXXXXXXXXXXXXXXXXXXXXXXXX.OOooooooOooooooOoooooo...XXX. ", +" X.oooooOOOOoooOXXXXXXXXXXXXXXXXXXXXXX.OooooOOooooooOooooooOoo..XXX. ", +" XoooOOoOooooooXXXXXXXXXXXXXXXXXX.OooOooooooOooooooOOoooo.XXXX. ", +" .XoooooOOooooooXXXXXXXXXXXXXOOoooooooOooooooOooooooX.XXXX. ", +" XooooooOoooooo.XXXXXXXXOoooooOooooooOoooooooOoo.XXXX. ", +" .XooooooOooooooXXXXoooOooooooOOooooooOooooO.XXXX. ", +" XooooooOoooOOOoOooooooOooooooOoooooooX.XXXX. ", +" XoooooOOOoooooOooooooOOooooooOoO.XXXX.. ", +" XXOOoooOOooooooOooooooOooooO.XXXX. ", +" XooooooOooooooOooooooOX.XXX. ", +" X.ooooooOooooooOoO.XXXX. ", +" XooooooOooooO.XXX.. ", +" X.oooooOXXXXX. ", +" XOO.XXX.. ", +".XX. "}; diff --git a/demos/life/bitmaps/zoomin.bmp b/demos/life/bitmaps/zoomin.bmp new file mode 100644 index 0000000000000000000000000000000000000000..af7521a4c207acb65b777247d2d75539968e9b9d GIT binary patch literal 246 zcmYj~yA6Oa3`7qJiH)}Yew zHKfL8obaO70G%mXYorfM7iOMOJl>)eMP7mk=FwIOsPqj^GnY*4$(fAoWCzlpqo19> Kz2fiaGYBsu5L8M4 literal 0 HcmV?d00001 diff --git a/demos/life/bitmaps/zoomin.xpm b/demos/life/bitmaps/zoomin.xpm new file mode 100644 index 0000000000..a53be858a4 --- /dev/null +++ b/demos/life/bitmaps/zoomin.xpm @@ -0,0 +1,23 @@ +/* XPM */ +static char * zoomin_xpm[] = { +"16 16 3 1", +" c None", +". c Black", +"X c Gray100", +" .... ", +" ..XXXX.. ", +" .XXXXXXXX. ", +" .XXX..XXX. ", +".XXXX..XXXX. ", +".XX......XX. ", +".XX......XX. ", +".XXXX..XXXX. ", +" .XXX..XXX. ", +" .XXXXXXXX. ", +" ..XXXX... ", +" .... ... ", +" ... ", +" ... ", +" ... ", +" .. " +}; diff --git a/demos/life/bitmaps/zoomout.bmp b/demos/life/bitmaps/zoomout.bmp new file mode 100644 index 0000000000000000000000000000000000000000..764ed9ee3776880be2f571986268bebc07f08ece GIT binary patch literal 246 zcmZ8au?>JQ3^NiE8|4h{VCR=kWbJ7kIH(hAaFPmDq_LgEO?#XVA}-2KY#kG3HwyV) z13vhO4ilw>kXfSEN;N}0p=uSuVSetValue(*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" + "\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); +} diff --git a/demos/life/dialogs.h b/demos/life/dialogs.h index 4c503cf4e3..95c266eb83 100644 --- a/demos/life/dialogs.h +++ b/demos/life/dialogs.h @@ -34,33 +34,6 @@ #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_ diff --git a/demos/life/game.cpp b/demos/life/game.cpp index ce51d42bd9..07a2f13401 100644 --- a/demos/life/game.cpp +++ b/demos/life/game.cpp @@ -10,217 +10,1301 @@ ///////////////////////////////////////////////////////////////////////////// // ========================================================================== -// 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 // for abort +#include // 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 +}; diff --git a/demos/life/game.h b/demos/life/game.h index dea8c45572..9238e6d0ab 100644 --- a/demos/life/game.h +++ b/demos/life/game.h @@ -1,6 +1,6 @@ ///////////////////////////////////////////////////////////////////////////// // Name: game.h -// Purpose: Life! game logic +// Purpose: Life! game logic, version 2 // Author: Guillermo Rodriguez Garcia, // Modified by: // Created: Jan/2000 @@ -29,41 +29,37 @@ #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_ diff --git a/demos/life/life.cpp b/demos/life/life.cpp index d03ad5e579..b65451f8b5 100644 --- a/demos/life/life.cpp +++ b/demos/life/life.cpp @@ -10,29 +10,13 @@ ///////////////////////////////////////////////////////////////////////////// // ========================================================================== -// 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 // -------------------------------------------------------------------------- @@ -61,15 +47,17 @@ 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¢er\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. +} diff --git a/demos/life/life.h b/demos/life/life.h index 946e84838a..4f87eee981 100644 --- a/demos/life/life.h +++ b/demos/life/life.h @@ -34,30 +34,46 @@ // 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; }; diff --git a/demos/life/life.rc b/demos/life/life.rc index 96782b26a3..0d8955774b 100644 --- a/demos/life/life.rc +++ b/demos/life/life.rc @@ -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" diff --git a/demos/life/samples.inc b/demos/life/samples.inc index 41425e856a..22fe76d5e2 100644 --- a/demos/life/samples.inc +++ b/demos/life/samples.inc @@ -17,14 +17,13 @@ * * 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, + ".....*.*....................." + "....*..*....................." + "...**........................" + "..*.........................." + ".****........................" + "*....*......................." + "*..*........................." + "*..*........................." + ".*.........***...***........." + "..****.*..*..*...*..*........" + "...*...*.....*...*..........." + "....*........*...*..........." + "....*.*......*...*..........." + "............................." + "...***.....***...***........." + "...**.......*.....*.........." + "...***......*******.........." + "...........*.......*........." + "....*.*...***********........" + "...*..*..*............**....." + "...*.....************...*...." + "...*...*.............*...*..." + "....*...************.....*..." + ".....**............*..*..*..." + "........***********...*.*...." + ".........*.......*..........." + "..........*******......***..." + "..........*.....*.......**..." + ".........***...***.....***..." + "............................." + "...........*...*......*.*...." + "...........*...*........*...." + "...........*...*.....*...*..." + "........*..*...*..*..*.****.." + ".........***...***.........*." + ".........................*..*" + ".........................*..*" + ".......................*....*" + "........................****." + "..........................*.." + "........................**..." + ".....................*..*...." + ".....................*.*....." ) }; -- 2.45.2