]> git.saurik.com Git - wxWidgets.git/blob - samples/life/life.cpp
Corrected some things in, and some thing revealed by
[wxWidgets.git] / samples / life / life.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: life.cpp
3 // Purpose: The game of life, created by J. H. Conway
4 // Author: Guillermo Rodriguez Garcia, <guille@iies.es>
5 // Modified by:
6 // Created: Jan/2000
7 // RCS-ID: $Id$
8 // Copyright: (c) 2000, Guillermo Rodriguez Garcia
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ==========================================================================
13 // declarations
14 // ==========================================================================
15
16 // minimum and maximum table size, in each dimension
17 #define LIFE_MIN 10
18 #define LIFE_MAX 200
19
20 // some shortcuts
21 #define ADD_TOOL(a, b, c, d) \
22 toolBar->AddTool(a, b, wxNullBitmap, FALSE, -1, -1, (wxObject *)0, c, d)
23
24 #define GET_FRAME() \
25 ((wxFrame *) wxGetApp().GetTopWindow())
26
27 // --------------------------------------------------------------------------
28 // headers
29 // --------------------------------------------------------------------------
30
31 #ifdef __GNUG__
32 #pragma implementation "life.cpp"
33 #pragma interface "life.cpp"
34 #endif
35
36 // for compilers that support precompilation, includes "wx/wx.h".
37 #include "wx/wxprec.h"
38
39 #ifdef __BORLANDC__
40 #pragma hdrstop
41 #endif
42
43 // for all others, include the necessary headers
44 #ifndef WX_PRECOMP
45 #include "wx/wx.h"
46 #endif
47
48 #include "wx/statline.h"
49 #include "wx/spinctrl.h"
50
51 // --------------------------------------------------------------------------
52 // resources
53 // --------------------------------------------------------------------------
54
55 #if defined(__WXGTK__) || defined(__WXMOTIF__)
56 // the application icon
57 #include "mondrian.xpm"
58
59 // bitmap buttons for the toolbar
60 #include "bitmaps/reset.xpm"
61 #include "bitmaps/play.xpm"
62 #include "bitmaps/stop.xpm"
63 #endif
64
65 // --------------------------------------------------------------------------
66 // private classes
67 // --------------------------------------------------------------------------
68
69 class Life;
70 class LifeCanvas;
71 class LifeTimer;
72 class LifeFrame;
73 class LifeApp;
74
75
76 // Life
77 class Life
78 {
79 public:
80 // ctors and dtors
81 Life(int width, int height);
82 ~Life();
83 void Create(int width, int height);
84 void Destroy();
85
86 // public accessors
87 inline int GetWidth() const { return m_width; };
88 inline int GetHeight() const { return m_height; };
89 inline bool IsAlive(int x, int y) const;
90 inline bool HasChanged(int x, int y) const;
91 inline void SetCell(int x, int y, bool alive = TRUE);
92
93 // game operations
94 void Clear();
95 void NextTic();
96
97 private:
98 enum CellFlags {
99 CELL_DEAD = 0x0000, // is dead
100 CELL_ALIVE = 0x0001, // is alive
101 CELL_MARK = 0x0002, // will change / has changed
102 };
103 typedef int Cell;
104
105 int GetNeighbors(int x, int y) const;
106 inline void SetCell(int x, int y, Cell status);
107
108 int m_width;
109 int m_height;
110 Cell *m_cells;
111 };
112
113 // Life canvas
114 class LifeCanvas : public wxScrolledWindow
115 {
116 public:
117 // ctor and dtor
118 LifeCanvas(wxWindow* parent, Life* life);
119 ~LifeCanvas();
120
121 // member functions
122 void Reset();
123 void DrawEverything(bool force = FALSE);
124 void DrawCell(int i, int j);
125 void DrawCell(int i, int j, wxDC &dc);
126 inline int CellToCoord(int i) const { return (i * m_cellsize); };
127 inline int CoordToCell(int x) const { return ((x >= 0)? (x / m_cellsize) : -1); };
128
129 // event handlers
130 void OnPaint(wxPaintEvent& event);
131 void OnMouse(wxMouseEvent& event);
132 void OnSize(wxSizeEvent& event);
133
134 private:
135 // any class wishing to process wxWindows events must use this macro
136 DECLARE_EVENT_TABLE()
137
138 enum MouseStatus {
139 MOUSE_NOACTION,
140 MOUSE_DRAWING,
141 MOUSE_ERASING
142 };
143
144 Life *m_life;
145 wxFrame *m_frame;
146 wxBitmap *m_bmp;
147 int m_height;
148 int m_width;
149 int m_cellsize;
150 wxCoord m_xoffset;
151 wxCoord m_yoffset;
152 MouseStatus m_status;
153 };
154
155 // Life timer
156 class LifeTimer : public wxTimer
157 {
158 public:
159 LifeTimer(LifeFrame *parent);
160 void Notify();
161
162 private:
163 LifeFrame *m_parent;
164 };
165
166 // Life main frame
167 class LifeFrame : public wxFrame
168 {
169 public:
170 // ctor and dtor
171 LifeFrame();
172 ~LifeFrame();
173
174 // member functions
175 void UpdateInfoText();
176
177 // event handlers
178 void OnMenu(wxCommandEvent& event);
179 void OnSlider(wxScrollEvent& event);
180 void OnNewGame();
181 void OnStart();
182 void OnStop();
183 void OnTimer();
184
185 private:
186 // any class wishing to process wxWindows events must use this macro
187 DECLARE_EVENT_TABLE()
188
189 Life *m_life;
190 LifeTimer *m_timer;
191 LifeCanvas *m_canvas;
192 wxStaticText *m_text;
193 bool m_running;
194 long m_interval;
195 long m_tics;
196 };
197
198 // Life new game dialog
199 class LifeNewGameDialog : public wxDialog
200 {
201 public:
202 // ctor
203 LifeNewGameDialog(wxWindow *parent, int *w, int *h);
204
205 // event handlers
206 void OnOK(wxCommandEvent& event);
207 void OnCancel(wxCommandEvent& event);
208
209 private:
210 // any class wishing to process wxWindows events must use this macro
211 DECLARE_EVENT_TABLE();
212
213 int *m_w;
214 int *m_h;
215 wxSpinCtrl *m_spinctrlw;
216 wxSpinCtrl *m_spinctrlh;
217 };
218
219 // Life app
220 class LifeApp : public wxApp
221 {
222 public:
223 virtual bool OnInit();
224 };
225
226
227 // --------------------------------------------------------------------------
228 // constants
229 // --------------------------------------------------------------------------
230
231 // IDs for the controls and the menu commands
232 enum
233 {
234 // menu items and toolbar buttons
235 ID_NEWGAME = 101,
236 ID_CLEAR,
237 ID_START,
238 ID_STOP,
239 ID_EXIT,
240 ID_ABOUT,
241
242 // slider
243 ID_SLIDER
244 };
245
246 // --------------------------------------------------------------------------
247 // event tables and other macros for wxWindows
248 // --------------------------------------------------------------------------
249
250 // Event tables
251
252 BEGIN_EVENT_TABLE(LifeFrame, wxFrame)
253 EVT_MENU_RANGE (ID_NEWGAME, ID_ABOUT, LifeFrame::OnMenu)
254 EVT_COMMAND_SCROLL (ID_SLIDER, LifeFrame::OnSlider)
255 END_EVENT_TABLE()
256
257 BEGIN_EVENT_TABLE(LifeCanvas, wxScrolledWindow)
258 EVT_PAINT ( LifeCanvas::OnPaint)
259 EVT_SIZE ( LifeCanvas::OnSize)
260 EVT_MOUSE_EVENTS ( LifeCanvas::OnMouse)
261 END_EVENT_TABLE()
262
263 BEGIN_EVENT_TABLE(LifeNewGameDialog, wxDialog)
264 EVT_BUTTON (wxID_OK, LifeNewGameDialog::OnOK)
265 EVT_BUTTON (wxID_CANCEL, LifeNewGameDialog::OnCancel)
266 END_EVENT_TABLE()
267
268
269 // Create a new application object
270 IMPLEMENT_APP(LifeApp)
271
272 // ==========================================================================
273 // implementation
274 // ==========================================================================
275
276 // --------------------------------------------------------------------------
277 // LifeApp
278 // --------------------------------------------------------------------------
279
280 // `Main program' equivalent: the program execution "starts" here
281 bool LifeApp::OnInit()
282 {
283 // create the main application window
284 LifeFrame *frame = new LifeFrame();
285
286 // show it and tell the application that it's our main window
287 frame->Show(TRUE);
288 SetTopWindow(frame);
289
290 // enter the main message loop and run the app
291 return TRUE;
292 }
293
294 // --------------------------------------------------------------------------
295 // LifeFrame
296 // --------------------------------------------------------------------------
297
298 // frame constructor
299 LifeFrame::LifeFrame() : wxFrame((wxFrame *)0, -1, _("Life!"), wxPoint(50, 50))
300 {
301 // frame icon
302 SetIcon(wxICON(mondrian));
303
304 // menu bar
305 wxMenu *menuFile = new wxMenu("", wxMENU_TEAROFF);
306
307 menuFile->Append(ID_NEWGAME, _("&New game...\tCtrl-N"), _("Start a new game"));
308 menuFile->Append(ID_CLEAR, _("&Clear\tCtrl-C"), _("Clear game board"));
309 menuFile->Append(ID_START, _("&Start\tCtrl-S"), _("Start"));
310 menuFile->Append(ID_STOP, _("S&top\tCtrl-T"), _("Stop"));
311 menuFile->AppendSeparator();
312 menuFile->Append(ID_ABOUT, _("&About...\tCtrl-A"), _("Show about dialog"));
313 menuFile->AppendSeparator();
314 menuFile->Append(ID_EXIT, _("E&xit\tAlt-X"), _("Quit this program"));
315 menuFile->Enable(ID_STOP, FALSE);
316
317 wxMenuBar *menuBar = new wxMenuBar();
318 menuBar->Append(menuFile, _("&File"));
319 SetMenuBar(menuBar);
320
321 // tool bar
322 wxBitmap tbBitmaps[3];
323 tbBitmaps[0] = wxBITMAP(reset);
324 tbBitmaps[1] = wxBITMAP(play);
325 tbBitmaps[2] = wxBITMAP(stop);
326
327 wxToolBar *toolBar = CreateToolBar();
328 toolBar->SetMargins(5, 5);
329 toolBar->SetToolBitmapSize(wxSize(16, 16));
330 ADD_TOOL(ID_CLEAR, tbBitmaps[0], _("Clear"), _("Clear game board"));
331 ADD_TOOL(ID_START, tbBitmaps[1], _("Start"), _("Start"));
332 ADD_TOOL(ID_STOP , tbBitmaps[2], _("Stop"), _("Stop"));
333 toolBar->EnableTool(ID_STOP, FALSE);
334 toolBar->Realize();
335
336 // status bar
337 CreateStatusBar(2);
338 SetStatusText(_("Welcome to Life!"));
339
340 // panel
341 wxPanel *panel = new wxPanel(this, -1);
342
343 // game
344 m_life = new Life(20, 20);
345 m_canvas = new LifeCanvas(panel, m_life);
346 m_timer = new LifeTimer(this);
347 m_interval = 500;
348 m_tics = 0;
349 m_text = new wxStaticText(panel, -1, "");
350 UpdateInfoText();
351
352 // slider
353 wxSlider *slider = new wxSlider(panel, ID_SLIDER, 5, 1, 10,
354 wxDefaultPosition, wxSize(150,-1), wxSL_HORIZONTAL | wxSL_AUTOTICKS);
355
356 // component layout
357 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
358 sizer->Add(m_canvas, 1, wxGROW | wxCENTRE | wxALL, 5);
359 sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE);
360 sizer->Add(m_text, 0, wxCENTRE | wxNORTH, 5);
361 sizer->Add(slider, 0, wxCENTRE | wxALL, 5);
362 panel->SetSizer(sizer);
363 panel->SetAutoLayout(TRUE);
364 sizer->Fit(this);
365 sizer->SetSizeHints(this);
366 }
367
368 LifeFrame::~LifeFrame()
369 {
370 delete m_timer;
371 delete m_life;
372 }
373
374 void LifeFrame::UpdateInfoText()
375 {
376 wxString msg;
377
378 msg.Printf(_("Generation: %u, Interval: %u ms"), m_tics, m_interval);
379 m_text->SetLabel(msg);
380 }
381
382 // event handlers
383 void LifeFrame::OnMenu(wxCommandEvent& event)
384 {
385 switch (event.GetId())
386 {
387 case ID_START : OnStart(); break;
388 case ID_STOP : OnStop(); break;
389 case ID_NEWGAME : OnNewGame(); break;
390 case ID_CLEAR :
391 {
392 OnStop();
393 m_life->Clear();
394 m_canvas->DrawEverything(TRUE);
395 m_canvas->Refresh(FALSE);
396 m_tics = 0;
397 UpdateInfoText();
398 break;
399 }
400 case ID_ABOUT :
401 {
402 wxMessageBox(
403 _("This is the about dialog of the Life! sample.\n"
404 "(c) 2000 Guillermo Rodriguez Garcia"),
405 _("About Life!"),
406 wxOK | wxICON_INFORMATION,
407 this);
408 break;
409 }
410 case ID_EXIT :
411 {
412 // TRUE is to force the frame to close
413 Close(TRUE);
414 break;
415 }
416 }
417 }
418
419 void LifeFrame::OnSlider(wxScrollEvent& event)
420 {
421 m_interval = event.GetPosition() * 100;
422
423 // restart timer if running, to set the new interval
424 if (m_running)
425 {
426 m_timer->Stop();
427 m_timer->Start(m_interval);
428 }
429
430 UpdateInfoText();
431 }
432
433 void LifeFrame::OnNewGame()
434 {
435 int w = m_life->GetWidth();
436 int h = m_life->GetHeight();
437 int result;
438
439 // stop if it was running
440 OnStop();
441
442 // show dialog box
443 LifeNewGameDialog dialog(this, &w, &h);
444 result = dialog.ShowModal();
445
446 // create new game
447 if (result == wxID_OK)
448 {
449 // check dimensions
450 if (w >= LIFE_MIN && w <= LIFE_MAX &&
451 h >= LIFE_MIN && h <= LIFE_MAX)
452 {
453 m_life->Destroy();
454 m_life->Create(w, h);
455 m_canvas->Reset();
456 m_tics = 0;
457 UpdateInfoText();
458 m_canvas->Refresh();
459 }
460 else
461 {
462 wxString msg;
463 msg.Printf(_("Both dimensions must be within %u and %u.\n"),
464 LIFE_MIN, LIFE_MAX);
465 wxMessageBox(msg, _("Error!"), wxOK | wxICON_EXCLAMATION, this);
466 }
467 }
468 }
469
470 void LifeFrame::OnStart()
471 {
472 GetToolBar()->EnableTool(ID_START, FALSE);
473 GetToolBar()->EnableTool(ID_STOP, TRUE);
474 GetMenuBar()->GetMenu(0)->Enable(ID_START, FALSE);
475 GetMenuBar()->GetMenu(0)->Enable(ID_STOP, TRUE);
476
477 m_timer->Start(m_interval);
478 m_running = TRUE;
479 }
480
481 void LifeFrame::OnStop()
482 {
483 GetToolBar()->EnableTool(ID_START, TRUE);
484 GetToolBar()->EnableTool(ID_STOP, FALSE);
485 GetMenuBar()->GetMenu(0)->Enable(ID_START, TRUE);
486 GetMenuBar()->GetMenu(0)->Enable(ID_STOP, FALSE);
487
488 m_timer->Stop();
489 m_running = FALSE;
490 }
491
492 void LifeFrame::OnTimer()
493 {
494 m_tics++;
495 UpdateInfoText();
496
497 m_life->NextTic();
498 m_canvas->DrawEverything();
499 m_canvas->Refresh(FALSE);
500 }
501
502 // --------------------------------------------------------------------------
503 // LifeTimer
504 // --------------------------------------------------------------------------
505
506 LifeTimer::LifeTimer(LifeFrame *parent) : wxTimer()
507 {
508 m_parent = parent;
509 }
510
511 void LifeTimer::Notify()
512 {
513 m_parent->OnTimer();
514 }
515
516 // --------------------------------------------------------------------------
517 // LifeCavas
518 // --------------------------------------------------------------------------
519
520 // canvas constructor
521 LifeCanvas::LifeCanvas(wxWindow *parent, Life *life)
522 : wxScrolledWindow(parent, -1, wxPoint(0, 0), wxSize(100, 100))
523 {
524 m_life = life;
525 m_cellsize = 8;
526 Reset();
527 }
528
529 LifeCanvas::~LifeCanvas()
530 {
531 delete m_bmp;
532 }
533
534 void LifeCanvas::Reset()
535 {
536 if (m_bmp)
537 delete m_bmp;
538
539 m_status = MOUSE_NOACTION;
540 m_width = CellToCoord(m_life->GetWidth()) + 1;
541 m_height = CellToCoord(m_life->GetHeight()) + 1;
542 m_bmp = new wxBitmap(m_width, m_height);
543 wxCoord w = GetSize().GetX();
544 wxCoord h = GetSize().GetY();
545 m_xoffset = (w > m_width)? ((w - m_width) / 2) : 0;
546 m_yoffset = (h > m_height)? ((h - m_height) / 2) : 0;
547
548 // redraw all, incl. background
549 DrawEverything(TRUE);
550 SetScrollbars(10, 10, (m_width + 9) / 10, (m_height + 9) / 10);
551 }
552
553 // draw everything
554 void LifeCanvas::DrawEverything(bool force)
555 {
556 wxMemoryDC dc;
557
558 dc.SelectObject(*m_bmp);
559 dc.BeginDrawing();
560
561 // cells
562 for (int j = 0; j < m_life->GetHeight(); j++)
563 for (int i = 0; i < m_life->GetWidth(); i++)
564 if (force || m_life->HasChanged(i, j))
565 DrawCell(i, j, dc);
566
567 // bounding rectangle (always drawn)
568 dc.SetPen(*wxBLACK_PEN);
569 dc.SetBrush(*wxTRANSPARENT_BRUSH);
570 dc.DrawRectangle(0, 0, m_width, m_height);
571
572 dc.EndDrawing();
573 dc.SelectObject(wxNullBitmap);
574 }
575
576 // draw a single cell
577 void LifeCanvas::DrawCell(int i, int j)
578 {
579 wxMemoryDC dc;
580
581 dc.SelectObject(*m_bmp);
582 dc.BeginDrawing();
583
584 DrawCell(i, j, dc);
585
586 dc.EndDrawing();
587 dc.SelectObject(wxNullBitmap);
588 }
589
590 void LifeCanvas::DrawCell(int i, int j, wxDC &dc)
591 {
592 if (m_life->IsAlive(i, j))
593 {
594 dc.SetPen(*wxBLACK_PEN);
595 dc.SetBrush(*wxBLACK_BRUSH);
596 dc.DrawRectangle(CellToCoord(i),
597 CellToCoord(j),
598 m_cellsize,
599 m_cellsize);
600 }
601 else
602 {
603 dc.SetPen(*wxLIGHT_GREY_PEN);
604 dc.SetBrush(*wxTRANSPARENT_BRUSH);
605 dc.DrawRectangle(CellToCoord(i),
606 CellToCoord(j),
607 m_cellsize,
608 m_cellsize);
609 dc.SetPen(*wxWHITE_PEN);
610 dc.SetBrush(*wxWHITE_BRUSH);
611 dc.DrawRectangle(CellToCoord(i) + 1,
612 CellToCoord(j) + 1,
613 m_cellsize - 1,
614 m_cellsize - 1);
615 }
616 }
617
618 // event handlers
619 void LifeCanvas::OnPaint(wxPaintEvent& event)
620 {
621 wxPaintDC dc(this);
622 wxMemoryDC memdc;
623
624 wxRegionIterator upd(GetUpdateRegion());
625 int x, y, w, h, xx, yy;
626
627 dc.BeginDrawing();
628 memdc.SelectObject(*m_bmp);
629
630 while(upd)
631 {
632 x = upd.GetX();
633 y = upd.GetY();
634 w = upd.GetW();
635 h = upd.GetH();
636 CalcUnscrolledPosition(x, y, &xx, &yy);
637
638 dc.Blit(x, y, w, h, &memdc, xx - m_xoffset, yy - m_yoffset);
639 upd++;
640 }
641
642 memdc.SelectObject(wxNullBitmap);
643 dc.EndDrawing();
644 }
645
646 void LifeCanvas::OnMouse(wxMouseEvent& event)
647 {
648 int x, y, xx, yy, i, j;
649
650 // which cell are we pointing at?
651 x = event.GetX();
652 y = event.GetY();
653 CalcUnscrolledPosition(x, y, &xx, &yy);
654 i = CoordToCell( xx - m_xoffset );
655 j = CoordToCell( yy - m_yoffset );
656
657 // adjust x, y to point to the upper left corner of the cell
658 CalcScrolledPosition( CellToCoord(i) + m_xoffset,
659 CellToCoord(j) + m_yoffset,
660 &x, &y );
661
662 // set cursor shape and statusbar text
663 if (i < 0 || i >= m_life->GetWidth() ||
664 j < 0 || j >= m_life->GetHeight())
665 {
666 GET_FRAME()->SetStatusText(wxEmptyString, 1);
667 SetCursor(*wxSTANDARD_CURSOR);
668 }
669 else
670 {
671 wxString msg;
672 msg.Printf(_("Cell: (%u, %u)"), i, j);
673 GET_FRAME()->SetStatusText(msg, 1);
674 SetCursor(*wxCROSS_CURSOR);
675 }
676
677 // button pressed?
678 if (!event.LeftIsDown())
679 {
680 m_status = MOUSE_NOACTION;
681 }
682 else if (i >= 0 && i < m_life->GetWidth() &&
683 j >= 0 && j < m_life->GetHeight())
684 {
685 bool alive = m_life->IsAlive(i, j);
686
687 // if just pressed, update status
688 if (m_status == MOUSE_NOACTION)
689 m_status = (alive? MOUSE_ERASING : MOUSE_DRAWING);
690
691 // toggle cell and refresh if needed
692 if (((m_status == MOUSE_ERASING) && alive) ||
693 ((m_status == MOUSE_DRAWING) && !alive))
694 {
695 wxRect rect(x, y, m_cellsize + 1, m_cellsize + 1);
696 m_life->SetCell(i, j, !alive);
697 DrawCell(i, j);
698 Refresh(FALSE, &rect);
699 }
700 }
701 }
702
703 void LifeCanvas::OnSize(wxSizeEvent& event)
704 {
705 wxCoord w = event.GetSize().GetX();
706 wxCoord h = event.GetSize().GetY();
707 m_xoffset = (w > m_width)? ((w - m_width) / 2) : 0;
708 m_yoffset = (h > m_height)? ((h - m_height) / 2) : 0;
709
710 // allow default processing
711 event.Skip();
712 }
713
714 // --------------------------------------------------------------------------
715 // LifeNewGameDialog
716 // --------------------------------------------------------------------------
717
718 LifeNewGameDialog::LifeNewGameDialog(wxWindow *parent, int *w, int *h)
719 : wxDialog(parent, -1, _("New game"),
720 wxDefaultPosition, wxDefaultSize,
721 wxDEFAULT_DIALOG_STYLE | wxDIALOG_MODAL)
722 {
723 m_w = w;
724 m_h = h;
725
726 wxBoxSizer *topsizer = new wxBoxSizer( wxVERTICAL );
727
728 // text message
729 topsizer->Add( CreateTextSizer(_("Enter board dimensions")), 0, wxALL, 10 );
730 topsizer->Add( new wxStaticLine(this, -1), 0, wxGROW | wxLEFT | wxRIGHT | wxBOTTOM, 10);
731
732 // prompts and text controls
733 wxString strw, strh;
734 strw.Printf(_("%u"), *m_w);
735 strh.Printf(_("%u"), *m_h);
736 m_spinctrlw = new wxSpinCtrl( this, -1, strw );
737 m_spinctrlh = new wxSpinCtrl( this, -1, strh );
738
739 wxBoxSizer *inputsizer1 = new wxBoxSizer( wxHORIZONTAL );
740 inputsizer1->Add( new wxStaticText(this, -1, _("Width")), 1, wxCENTER | wxLEFT, 20);
741 inputsizer1->Add( m_spinctrlw, 2, wxCENTER | wxLEFT | wxRIGHT, 20 );
742 wxBoxSizer *inputsizer2 = new wxBoxSizer( wxHORIZONTAL );
743 inputsizer2->Add( new wxStaticText(this, -1, _("Height")), 1, wxCENTER | wxLEFT, 20);
744 inputsizer2->Add( m_spinctrlh, 2, wxCENTER | wxLEFT | wxRIGHT, 20 );
745
746 topsizer->Add( inputsizer1, 1, wxGROW | wxLEFT | wxRIGHT, 5 );
747 topsizer->Add( inputsizer2, 1, wxGROW | wxLEFT | wxRIGHT, 5 );
748 topsizer->Add( new wxStaticLine(this, -1), 0, wxGROW | wxLEFT | wxRIGHT | wxTOP, 10);
749
750 // buttons
751 topsizer->Add( CreateButtonSizer(wxOK | wxCANCEL), 0, wxCENTRE | wxALL, 10);
752
753 // activate
754 SetSizer(topsizer);
755 SetAutoLayout(TRUE);
756 topsizer->SetSizeHints(this);
757 topsizer->Fit(this);
758 Centre(wxBOTH);
759 }
760
761 void LifeNewGameDialog::OnOK(wxCommandEvent& WXUNUSED(event))
762 {
763 *m_w = m_spinctrlw->GetValue();
764 *m_h = m_spinctrlh->GetValue();
765
766 EndModal(wxID_OK);
767 }
768
769 void LifeNewGameDialog::OnCancel(wxCommandEvent& WXUNUSED(event))
770 {
771 *m_w = -1;
772 *m_h = -1;
773
774 EndModal(wxID_CANCEL);
775 }
776
777 // --------------------------------------------------------------------------
778 // Life
779 // --------------------------------------------------------------------------
780
781 Life::Life(int width, int height)
782 {
783 Create(width, height);
784 }
785
786 Life::~Life()
787 {
788 Destroy();
789 }
790
791 void Life::Create(int width, int height)
792 {
793 wxASSERT(width > 0 || height > 0);
794
795 m_width = width;
796 m_height = height;
797 m_cells = new Cell[m_width * m_height];
798 Clear();
799 }
800
801 void Life::Destroy()
802 {
803 delete[] m_cells;
804 }
805
806 void Life::Clear()
807 {
808 for (int i = 0; i < m_width * m_height; i++)
809 m_cells[i] = CELL_DEAD;
810 }
811
812 bool Life::IsAlive(int x, int y) const
813 {
814 wxASSERT(x < m_width || y < m_height);
815
816 return (m_cells[y * m_width + x] & CELL_ALIVE);
817 }
818
819 bool Life::HasChanged(int x, int y) const
820 {
821 wxASSERT(x < m_width || y < m_height);
822
823 return (m_cells[y * m_width + x] & CELL_MARK);
824 }
825
826 void Life::SetCell(int x, int y, bool alive)
827 {
828 wxASSERT(x < m_width || y < m_height);
829
830 // set the CELL_MARK flag to notify that this cell has changed
831 m_cells[y * m_width + x] = (alive? CELL_ALIVE : CELL_DEAD);
832 }
833
834 void Life::NextTic()
835 {
836 /* 1st pass. Find and mark deaths and births for this generation.
837 *
838 * Rules:
839 * An organism with <= 1 neighbors will die due to isolation.
840 * An organism with >= 4 neighbors will die due to starvation.
841 * New organisms are born in cells with exactly 3 neighbors.
842 */
843 for (int j = 0; j < m_height; j++)
844 for (int i = 0; i < m_width; i++)
845 {
846 int neighbors = GetNeighbors(i, j);
847 bool alive = IsAlive(i, j);
848
849 /* Set CELL_MARK if this cell must change, clear it
850 * otherwise. We cannot toggle the CELL_ALIVE bit yet
851 * because all deaths and births are simultaneous (it
852 * would affect neighbouring cells).
853 */
854 if ((!alive && neighbors == 3) ||
855 (alive && (neighbors <= 1 || neighbors >= 4)))
856 m_cells[j * m_width + i] |= CELL_MARK;
857 else
858 m_cells[j * m_width + i] &= ~CELL_MARK;
859 }
860
861 /* 2nd pass. Stabilize.
862 */
863 for (int j = 0; j < m_height; j++)
864 for (int i = 0; i < m_width; i++)
865 {
866 /* Toggle CELL_ALIVE for those cells marked in the
867 * previous pass. Do not clear the CELL_MARK bit yet;
868 * it is useful to know which cells have changed and
869 * thus must be updated in the screen.
870 */
871 if (m_cells[j * m_width + i] & CELL_MARK)
872 m_cells[j * m_width + i] ^= CELL_ALIVE;
873 }
874 }
875
876 int Life::GetNeighbors(int x, int y) const
877 {
878 wxASSERT(x < m_width || y < m_height);
879
880 // count number of neighbors (wrap around board limits)
881 int neighbors = 0;
882 for (int j = y - 1; j <= y + 1; j++)
883 for (int i = x - 1; i <= x + 1; i++)
884 {
885 if (IsAlive( ((i < 0)? (i + m_width ) : (i % m_width)),
886 ((j < 0)? (j + m_height) : (j % m_height)) ))
887 neighbors++;
888 }
889
890 // do not count ourselves
891 if (IsAlive(x, y)) neighbors--;
892
893 return neighbors;
894 }
895
896 void Life::SetCell(int x, int y, Cell status)
897 {
898 wxASSERT(x < m_width || y < m_height);
899
900 m_cells[y * m_width + x] = status;
901 }
902