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