]> git.saurik.com Git - wxWidgets.git/blob - samples/life/life.cpp
9bcd0bbf58b781ceb3cbb1954ce920b78e9ddf7d
[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 20
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 // classes
67 // --------------------------------------------------------------------------
68
69 class Life;
70 class LifeShape;
71 class LifeCanvas;
72 class LifeTimer;
73 class LifeFrame;
74 class LifeApp;
75 class LifeNewGameDialog;
76 class LifeSamplesDialog;
77
78 // --------------------------------------------------------------------------
79 // non-GUI classes
80 // --------------------------------------------------------------------------
81
82 // Life
83 class Life
84 {
85 public:
86 // ctors and dtors
87 Life(int width, int height);
88 ~Life();
89 void Create(int width, int height);
90 void Destroy();
91
92 // accessors
93 inline int GetWidth() const { return m_width; };
94 inline int GetHeight() const { return m_height; };
95 inline bool IsAlive(int x, int y) const;
96 inline bool HasChanged(int x, int y) const;
97
98 // flags
99 void SetBorderWrap(bool on);
100
101 // game logic
102 void Clear();
103 void SetCell(int x, int y, bool alive = TRUE);
104 void SetShape(LifeShape &shape);
105 bool NextTic();
106
107 private:
108 enum CellFlags {
109 CELL_DEAD = 0x0000, // is dead
110 CELL_ALIVE = 0x0001, // is alive
111 CELL_MARK = 0x0002, // will change / has changed
112 };
113 typedef int Cell;
114
115 int GetNeighbors(int x, int y) const;
116 inline void SetCell(int x, int y, Cell status);
117
118 int m_width;
119 int m_height;
120 Cell *m_cells;
121 bool m_wrap;
122 };
123
124 // LifeShape
125 class LifeShape
126 {
127 public:
128 LifeShape::LifeShape(wxString name,
129 wxString desc,
130 int width, int height, char *data,
131 int fieldWidth = 20, int fieldHeight = 20,
132 bool wrap = TRUE)
133 {
134 m_name = name;
135 m_desc = desc;
136 m_width = width;
137 m_height = height;
138 m_data = data;
139 m_fieldWidth = fieldWidth;
140 m_fieldHeight = fieldHeight;
141 m_wrap = wrap;
142 }
143
144 wxString m_name;
145 wxString m_desc;
146 int m_width;
147 int m_height;
148 char *m_data;
149 int m_fieldWidth;
150 int m_fieldHeight;
151 bool m_wrap;
152 };
153
154 // --------------------------------------------------------------------------
155 // GUI classes
156 // --------------------------------------------------------------------------
157
158 // Life canvas
159 class LifeCanvas : public wxScrolledWindow
160 {
161 public:
162 // ctor and dtor
163 LifeCanvas(wxWindow* parent, Life* life, bool interactive = TRUE);
164 ~LifeCanvas();
165
166 // member functions
167 void Reset();
168 void DrawEverything(bool force = FALSE);
169 void DrawCell(int i, int j);
170 void DrawCell(int i, int j, wxDC &dc);
171 inline int CellToCoord(int i) const { return (i * m_cellsize); };
172 inline int CoordToCell(int x) const { return ((x >= 0)? (x / m_cellsize) : -1); };
173
174 // event handlers
175 void OnPaint(wxPaintEvent& event);
176 void OnMouse(wxMouseEvent& event);
177 void OnSize(wxSizeEvent& event);
178
179 private:
180 // any class wishing to process wxWindows events must use this macro
181 DECLARE_EVENT_TABLE()
182
183 enum MouseStatus {
184 MOUSE_NOACTION,
185 MOUSE_DRAWING,
186 MOUSE_ERASING
187 };
188
189 Life *m_life;
190 wxBitmap *m_bmp;
191 int m_height;
192 int m_width;
193 int m_cellsize;
194 wxCoord m_xoffset;
195 wxCoord m_yoffset;
196 MouseStatus m_status;
197 bool m_interactive;
198 };
199
200 // Life timer
201 class LifeTimer : public wxTimer
202 {
203 public:
204 void Notify();
205 };
206
207 // Life main frame
208 class LifeFrame : public wxFrame
209 {
210 public:
211 // ctor and dtor
212 LifeFrame();
213 ~LifeFrame();
214
215 // member functions
216 void UpdateInfoText();
217
218 // event handlers
219 void OnMenu(wxCommandEvent& event);
220 void OnNewGame(wxCommandEvent& event);
221 void OnSamples(wxCommandEvent& event);
222 void OnStart();
223 void OnStop();
224 void OnTimer();
225 void OnSlider(wxScrollEvent& event);
226
227 private:
228 // any class wishing to process wxWindows events must use this macro
229 DECLARE_EVENT_TABLE()
230
231 Life *m_life;
232 LifeTimer *m_timer;
233 LifeCanvas *m_canvas;
234 wxStaticText *m_text;
235 bool m_running;
236 long m_interval;
237 long m_tics;
238 };
239
240 // Life new game dialog
241 class LifeNewGameDialog : public wxDialog
242 {
243 public:
244 // ctor
245 LifeNewGameDialog(wxWindow *parent, int *w, int *h);
246
247 // event handlers
248 void OnOK(wxCommandEvent& event);
249
250 private:
251 // any class wishing to process wxWindows events must use this macro
252 DECLARE_EVENT_TABLE();
253
254 int *m_w;
255 int *m_h;
256 wxSpinCtrl *m_spinctrlw;
257 wxSpinCtrl *m_spinctrlh;
258 };
259
260 // Life sample configurations dialog
261 class LifeSamplesDialog : public wxDialog
262 {
263 public:
264 // ctor and dtor
265 LifeSamplesDialog(wxWindow *parent);
266 ~LifeSamplesDialog();
267
268 // members
269 int GetValue();
270
271 // event handlers
272 void OnListBox(wxCommandEvent &event);
273
274 private:
275 // any class wishing to process wxWindows events must use this macro
276 DECLARE_EVENT_TABLE();
277
278 int m_value;
279 wxListBox *m_list;
280 wxTextCtrl *m_text;
281 LifeCanvas *m_canvas;
282 Life *m_life;
283 };
284
285 // Life app
286 class LifeApp : public wxApp
287 {
288 public:
289 virtual bool OnInit();
290 };
291
292 // --------------------------------------------------------------------------
293 // constants
294 // --------------------------------------------------------------------------
295
296 // IDs for the controls and the menu commands
297 enum
298 {
299 // menu items and toolbar buttons
300 ID_NEWGAME = 1001,
301 ID_SAMPLES,
302 ID_ABOUT,
303 ID_EXIT,
304 ID_CLEAR,
305 ID_START,
306 ID_STEP,
307 ID_STOP,
308 ID_WRAP,
309
310 // speed selection slider
311 ID_SLIDER,
312
313 // listbox in samples dialog
314 ID_LISTBOX
315 };
316
317
318 // built-in sample games
319 #include "samples.inc"
320
321 // --------------------------------------------------------------------------
322 // event tables and other macros for wxWindows
323 // --------------------------------------------------------------------------
324
325 // Event tables
326 BEGIN_EVENT_TABLE(LifeFrame, wxFrame)
327 EVT_MENU (ID_NEWGAME, LifeFrame::OnNewGame)
328 EVT_MENU (ID_SAMPLES, LifeFrame::OnSamples)
329 EVT_MENU (ID_ABOUT, LifeFrame::OnMenu)
330 EVT_MENU (ID_EXIT, LifeFrame::OnMenu)
331 EVT_MENU (ID_CLEAR, LifeFrame::OnMenu)
332 EVT_MENU (ID_START, LifeFrame::OnMenu)
333 EVT_MENU (ID_STEP, LifeFrame::OnMenu)
334 EVT_MENU (ID_STOP, LifeFrame::OnMenu)
335 EVT_MENU (ID_WRAP, LifeFrame::OnMenu)
336 EVT_COMMAND_SCROLL (ID_SLIDER, LifeFrame::OnSlider)
337 END_EVENT_TABLE()
338
339 BEGIN_EVENT_TABLE(LifeCanvas, wxScrolledWindow)
340 EVT_PAINT ( LifeCanvas::OnPaint)
341 EVT_SIZE ( LifeCanvas::OnSize)
342 EVT_MOUSE_EVENTS ( LifeCanvas::OnMouse)
343 END_EVENT_TABLE()
344
345 BEGIN_EVENT_TABLE(LifeNewGameDialog, wxDialog)
346 EVT_BUTTON (wxID_OK, LifeNewGameDialog::OnOK)
347 END_EVENT_TABLE()
348
349 BEGIN_EVENT_TABLE(LifeSamplesDialog, wxDialog)
350 EVT_LISTBOX (ID_LISTBOX, LifeSamplesDialog::OnListBox)
351 END_EVENT_TABLE()
352
353
354 // Create a new application object
355 IMPLEMENT_APP(LifeApp)
356
357 // ==========================================================================
358 // implementation
359 // ==========================================================================
360
361 // --------------------------------------------------------------------------
362 // LifeApp
363 // --------------------------------------------------------------------------
364
365 // `Main program' equivalent: the program execution "starts" here
366 bool LifeApp::OnInit()
367 {
368 // create the main application window
369 LifeFrame *frame = new LifeFrame();
370
371 // show it and tell the application that it's our main window
372 frame->Show(TRUE);
373 SetTopWindow(frame);
374
375 // enter the main message loop and run the app
376 return TRUE;
377 }
378
379 // --------------------------------------------------------------------------
380 // LifeFrame
381 // --------------------------------------------------------------------------
382
383 // frame constructor
384 LifeFrame::LifeFrame() : wxFrame((wxFrame *)0, -1, _("Life!"), wxPoint(50, 50))
385 {
386 // frame icon
387 SetIcon(wxICON(mondrian));
388
389 // menu bar
390 wxMenu *menuFile = new wxMenu("", wxMENU_TEAROFF);
391 wxMenu *menuGame = new wxMenu("", wxMENU_TEAROFF);
392
393 menuFile->Append(ID_NEWGAME, _("New game..."), _("Start a new game"));
394 menuFile->Append(ID_SAMPLES, _("Sample game..."), _("Select a sample configuration"));
395 menuFile->AppendSeparator();
396 menuFile->Append(ID_ABOUT, _("&About...\tCtrl-A"), _("Show about dialog"));
397 menuFile->AppendSeparator();
398 menuFile->Append(ID_EXIT, _("E&xit\tAlt-X"), _("Quit this program"));
399
400 menuGame->Append(ID_CLEAR, _("&Clear\tCtrl-C"), _("Clear game field"));
401 menuGame->Append(ID_START, _("&Start\tCtrl-S"), _("Start"));
402 menuGame->Append(ID_STEP, _("&Next\tCtrl-N"), _("Single step"));
403 menuGame->Append(ID_STOP, _("S&top\tCtrl-T"), _("Stop"));
404 menuGame->Enable(ID_STOP, FALSE);
405 menuGame->AppendSeparator();
406 menuGame->Append(ID_WRAP, _("&Wraparound\tCtrl-W"), _("Wrap around borders"), TRUE);
407 menuGame->Check (ID_WRAP, TRUE);
408
409
410 wxMenuBar *menuBar = new wxMenuBar();
411 menuBar->Append(menuFile, _("&File"));
412 menuBar->Append(menuGame, _("&Game"));
413 SetMenuBar(menuBar);
414
415 // tool bar
416 wxBitmap tbBitmaps[3];
417 tbBitmaps[0] = wxBITMAP(reset);
418 tbBitmaps[1] = wxBITMAP(play);
419 tbBitmaps[2] = wxBITMAP(stop);
420
421 wxToolBar *toolBar = CreateToolBar();
422 toolBar->SetMargins(5, 5);
423 toolBar->SetToolBitmapSize(wxSize(16, 16));
424 ADD_TOOL(ID_CLEAR, tbBitmaps[0], _("Clear"), _("Clear game board"));
425 ADD_TOOL(ID_START, tbBitmaps[1], _("Start"), _("Start"));
426 ADD_TOOL(ID_STOP , tbBitmaps[2], _("Stop"), _("Stop"));
427 toolBar->EnableTool(ID_STOP, FALSE);
428 toolBar->Realize();
429
430 // status bar
431 CreateStatusBar(2);
432 SetStatusText(_("Welcome to Life!"));
433
434 // panel
435 wxPanel *panel = new wxPanel(this, -1);
436
437 // game
438 m_life = new Life(20, 20);
439 m_canvas = new LifeCanvas(panel, m_life);
440 m_timer = new LifeTimer();
441 m_interval = 500;
442 m_tics = 0;
443 m_text = new wxStaticText(panel, -1, "");
444 UpdateInfoText();
445
446 // slider
447 wxSlider *slider = new wxSlider(panel, ID_SLIDER, 5, 1, 10,
448 wxDefaultPosition, wxSize(200, -1), wxSL_HORIZONTAL | wxSL_AUTOTICKS);
449
450 // component layout
451 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
452 sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE);
453 sizer->Add(m_canvas, 1, wxGROW | wxCENTRE | wxALL, 5);
454 sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE);
455 sizer->Add(m_text, 0, wxCENTRE | wxTOP, 5);
456 sizer->Add(slider, 0, wxCENTRE | wxALL, 5);
457 panel->SetSizer(sizer);
458 panel->SetAutoLayout(TRUE);
459 sizer->Fit(this);
460 sizer->SetSizeHints(this);
461 }
462
463 LifeFrame::~LifeFrame()
464 {
465 delete m_timer;
466 delete m_life;
467 }
468
469 void LifeFrame::UpdateInfoText()
470 {
471 wxString msg;
472
473 msg.Printf(_("Generation: %u, Interval: %u ms"), m_tics, m_interval);
474 m_text->SetLabel(msg);
475 }
476
477 // event handlers
478 void LifeFrame::OnMenu(wxCommandEvent& event)
479 {
480 switch (event.GetId())
481 {
482 case ID_START : OnStart(); break;
483 case ID_STEP : OnTimer(); break;
484 case ID_STOP : OnStop(); break;
485 case ID_WRAP :
486 {
487 bool checked = GetMenuBar()->GetMenu(1)->IsChecked(ID_WRAP);
488 m_life->SetBorderWrap(checked);
489 break;
490 }
491 case ID_CLEAR :
492 {
493 OnStop();
494 m_life->Clear();
495 m_canvas->DrawEverything(TRUE);
496 m_canvas->Refresh(FALSE);
497 m_tics = 0;
498 UpdateInfoText();
499 break;
500 }
501 case ID_ABOUT :
502 {
503 wxMessageBox(
504 _("This is the about dialog of the Life! sample.\n"
505 "(c) 2000 Guillermo Rodriguez Garcia"),
506 _("About Life!"),
507 wxOK | wxICON_INFORMATION,
508 this);
509 break;
510 }
511 case ID_EXIT :
512 {
513 // TRUE is to force the frame to close
514 Close(TRUE);
515 break;
516 }
517 }
518 }
519
520 void LifeFrame::OnNewGame(wxCommandEvent& WXUNUSED(event))
521 {
522 int w = m_life->GetWidth();
523 int h = m_life->GetHeight();
524 int result;
525
526 // stop if it was running
527 OnStop();
528
529 // show dialog box
530 LifeNewGameDialog dialog(this, &w, &h);
531 result = dialog.ShowModal();
532
533 // new game?
534 if (result == wxID_OK)
535 {
536 // check dimensions
537 if (w >= LIFE_MIN && w <= LIFE_MAX &&
538 h >= LIFE_MIN && h <= LIFE_MAX)
539 {
540 // resize game field
541 m_life->Destroy();
542 m_life->Create(w, h);
543
544 // tell the canvas
545 m_canvas->Reset();
546 m_canvas->Refresh();
547 m_tics = 0;
548 UpdateInfoText();
549 }
550 else
551 {
552 wxString msg;
553 msg.Printf(_("Both dimensions must be within %u and %u.\n"),
554 LIFE_MIN, LIFE_MAX);
555 wxMessageBox(msg, _("Error!"), wxOK | wxICON_EXCLAMATION, this);
556 }
557 }
558 }
559
560 void LifeFrame::OnSamples(wxCommandEvent& WXUNUSED(event))
561 {
562 // stop if it was running
563 OnStop();
564
565 // show dialog box
566 LifeSamplesDialog dialog(this);
567
568 // new game?
569 if (dialog.ShowModal() == wxID_OK)
570 {
571 int result = dialog.GetValue();
572
573 if (result == -1)
574 return;
575
576 int gw = g_shapes[result].m_fieldWidth;
577 int gh = g_shapes[result].m_fieldHeight;
578 int wrap = g_shapes[result].m_wrap;
579
580 // set wraparound (don't ask the user)
581 m_life->SetBorderWrap(wrap);
582 GetMenuBar()->GetMenu(1)->Check(ID_WRAP, wrap);
583
584 // need to resize the game field?
585 if (gw > m_life->GetWidth() || gh > m_life->GetHeight())
586 {
587 wxString s;
588 s.Printf(_("Your game field is too small for this configuration.\n"
589 "It is recommended to resize it to %u x %u. Proceed?\n"),
590 gw, gh);
591
592 if (wxMessageBox(s, _("Question"), wxYES_NO | wxICON_QUESTION, this) == wxYES)
593 {
594 m_life->Destroy();
595 m_life->Create(gw, gh);
596 }
597 }
598
599 // put the shape
600 m_life->SetShape(g_shapes[result]);
601
602 // tell the canvas about the change
603 m_canvas->Reset();
604 m_canvas->Refresh();
605 m_tics = 0;
606 UpdateInfoText();
607 }
608 }
609
610 void LifeFrame::OnStart()
611 {
612 if (!m_running)
613 {
614 GetToolBar()->EnableTool(ID_START, FALSE);
615 GetToolBar()->EnableTool(ID_STOP, TRUE);
616 GetMenuBar()->GetMenu(1)->Enable(ID_START, FALSE);
617 GetMenuBar()->GetMenu(1)->Enable(ID_STEP, FALSE);
618 GetMenuBar()->GetMenu(1)->Enable(ID_STOP, TRUE);
619
620 m_timer->Start(m_interval);
621 m_running = TRUE;
622 }
623 }
624
625 void LifeFrame::OnStop()
626 {
627 if (m_running)
628 {
629 GetToolBar()->EnableTool(ID_START, TRUE);
630 GetToolBar()->EnableTool(ID_STOP, FALSE);
631 GetMenuBar()->GetMenu(1)->Enable(ID_START, TRUE);
632 GetMenuBar()->GetMenu(1)->Enable(ID_STEP, TRUE);
633 GetMenuBar()->GetMenu(1)->Enable(ID_STOP, FALSE);
634
635 m_timer->Stop();
636 m_running = FALSE;
637 }
638 }
639
640 void LifeFrame::OnTimer()
641 {
642 if (m_life->NextTic())
643 m_tics++;
644 else
645 OnStop();
646
647 UpdateInfoText();
648 m_canvas->DrawEverything();
649 m_canvas->Refresh(FALSE);
650 }
651
652 void LifeFrame::OnSlider(wxScrollEvent& event)
653 {
654 m_interval = event.GetPosition() * 100;
655
656 // restart timer if running, to set the new interval
657 if (m_running)
658 {
659 m_timer->Stop();
660 m_timer->Start(m_interval);
661 }
662
663 UpdateInfoText();
664 }
665
666 // --------------------------------------------------------------------------
667 // LifeTimer
668 // --------------------------------------------------------------------------
669
670 void LifeTimer::Notify()
671 {
672 GET_FRAME()->OnTimer();
673 };
674
675 // --------------------------------------------------------------------------
676 // LifeCanvas
677 // --------------------------------------------------------------------------
678
679 // canvas constructor
680 LifeCanvas::LifeCanvas(wxWindow *parent, Life *life, bool interactive)
681 : wxScrolledWindow(parent, -1, wxPoint(0, 0), wxSize(100, 100))
682 {
683 m_life = life;
684 m_interactive = interactive;
685 m_cellsize = 8;
686 m_bmp = NULL;
687 Reset();
688 }
689
690 LifeCanvas::~LifeCanvas()
691 {
692 delete m_bmp;
693 }
694
695 void LifeCanvas::Reset()
696 {
697 if (m_bmp)
698 delete m_bmp;
699
700 m_status = MOUSE_NOACTION;
701 m_width = CellToCoord(m_life->GetWidth()) + 1;
702 m_height = CellToCoord(m_life->GetHeight()) + 1;
703 m_bmp = new wxBitmap(m_width, m_height);
704 wxCoord w = GetClientSize().GetX();
705 wxCoord h = GetClientSize().GetY();
706 m_xoffset = (w > m_width)? ((w - m_width) / 2) : 0;
707 m_yoffset = (h > m_height)? ((h - m_height) / 2) : 0;
708
709 // redraw everything
710 DrawEverything(TRUE);
711 SetScrollbars(10, 10, (m_width + 9) / 10, (m_height + 9) / 10);
712 }
713
714 void LifeCanvas::DrawEverything(bool force)
715 {
716 wxMemoryDC dc;
717
718 dc.SelectObject(*m_bmp);
719 dc.BeginDrawing();
720
721 // draw cells
722 for (int j = 0; j < m_life->GetWidth(); j++)
723 for (int i = 0; i < m_life->GetHeight(); i++)
724 if (force || m_life->HasChanged(i, j))
725 DrawCell(i, j, dc);
726
727 // bounding rectangle (always drawn - better than clipping region)
728 dc.SetPen(*wxBLACK_PEN);
729 dc.SetBrush(*wxTRANSPARENT_BRUSH);
730 dc.DrawRectangle(0, 0, m_width, m_height);
731
732 dc.EndDrawing();
733 dc.SelectObject(wxNullBitmap);
734 }
735
736 void LifeCanvas::DrawCell(int i, int j)
737 {
738 wxMemoryDC dc;
739
740 dc.SelectObject(*m_bmp);
741 dc.BeginDrawing();
742
743 dc.SetClippingRegion(1, 1, m_width - 2, m_height - 2);
744 DrawCell(i, j, dc);
745
746 dc.EndDrawing();
747 dc.SelectObject(wxNullBitmap);
748 }
749
750 void LifeCanvas::DrawCell(int i, int j, wxDC &dc)
751 {
752 if (m_life->IsAlive(i, j))
753 {
754 dc.SetPen(*wxBLACK_PEN);
755 dc.SetBrush(*wxBLACK_BRUSH);
756 dc.DrawRectangle(CellToCoord(i),
757 CellToCoord(j),
758 m_cellsize,
759 m_cellsize);
760 }
761 else
762 {
763 dc.SetPen(*wxLIGHT_GREY_PEN);
764 dc.SetBrush(*wxTRANSPARENT_BRUSH);
765 dc.DrawRectangle(CellToCoord(i),
766 CellToCoord(j),
767 m_cellsize,
768 m_cellsize);
769 dc.SetPen(*wxWHITE_PEN);
770 dc.SetBrush(*wxWHITE_BRUSH);
771 dc.DrawRectangle(CellToCoord(i) + 1,
772 CellToCoord(j) + 1,
773 m_cellsize - 1,
774 m_cellsize - 1);
775 }
776 }
777
778 // event handlers
779 void LifeCanvas::OnPaint(wxPaintEvent& event)
780 {
781 wxPaintDC dc(this);
782 wxMemoryDC memdc;
783
784 wxRegionIterator upd(GetUpdateRegion());
785 wxCoord x, y, w, h, xx, yy;
786
787 dc.BeginDrawing();
788 memdc.SelectObject(*m_bmp);
789
790 while(upd)
791 {
792 x = upd.GetX();
793 y = upd.GetY();
794 w = upd.GetW();
795 h = upd.GetH();
796 CalcUnscrolledPosition(x, y, &xx, &yy);
797
798 dc.Blit(x, y, w, h, &memdc, xx - m_xoffset, yy - m_yoffset);
799 upd++;
800 }
801
802 memdc.SelectObject(wxNullBitmap);
803 dc.EndDrawing();
804 }
805
806 void LifeCanvas::OnMouse(wxMouseEvent& event)
807 {
808 if (!m_interactive)
809 return;
810
811 int x, y, xx, yy, i, j;
812
813 // which cell are we pointing at?
814 x = event.GetX();
815 y = event.GetY();
816 CalcUnscrolledPosition(x, y, &xx, &yy);
817 i = CoordToCell( xx - m_xoffset );
818 j = CoordToCell( yy - m_yoffset );
819
820 // adjust x, y to point to the upper left corner of the cell
821 CalcScrolledPosition( CellToCoord(i) + m_xoffset,
822 CellToCoord(j) + m_yoffset,
823 &x, &y );
824
825 // set cursor shape and statusbar text
826 if (i < 0 || i >= m_life->GetWidth() ||
827 j < 0 || j >= m_life->GetHeight())
828 {
829 GET_FRAME()->SetStatusText(wxEmptyString, 1);
830 SetCursor(*wxSTANDARD_CURSOR);
831 }
832 else
833 {
834 wxString msg;
835 msg.Printf(_("Cell: (%u, %u)"), i, j);
836 GET_FRAME()->SetStatusText(msg, 1);
837 SetCursor(*wxCROSS_CURSOR);
838 }
839
840 // button pressed?
841 if (!event.LeftIsDown())
842 {
843 m_status = MOUSE_NOACTION;
844 }
845 else if (i >= 0 && i < m_life->GetWidth() &&
846 j >= 0 && j < m_life->GetHeight())
847 {
848 bool alive = m_life->IsAlive(i, j);
849
850 // if just pressed, update status
851 if (m_status == MOUSE_NOACTION)
852 m_status = (alive? MOUSE_ERASING : MOUSE_DRAWING);
853
854 // toggle cell and refresh if needed
855 if (((m_status == MOUSE_ERASING) && alive) ||
856 ((m_status == MOUSE_DRAWING) && !alive))
857 {
858 wxRect rect(x, y, m_cellsize + 1, m_cellsize + 1);
859 m_life->SetCell(i, j, !alive);
860 DrawCell(i, j);
861 Refresh(FALSE, &rect);
862 }
863 }
864 }
865
866 void LifeCanvas::OnSize(wxSizeEvent& event)
867 {
868 wxCoord w = event.GetSize().GetX();
869 wxCoord h = event.GetSize().GetY();
870 m_xoffset = (w > m_width)? ((w - m_width) / 2) : 0;
871 m_yoffset = (h > m_height)? ((h - m_height) / 2) : 0;
872
873 // allow default processing
874 event.Skip();
875 }
876
877 // --------------------------------------------------------------------------
878 // LifeNewGameDialog
879 // --------------------------------------------------------------------------
880
881 LifeNewGameDialog::LifeNewGameDialog(wxWindow *parent, int *w, int *h)
882 : wxDialog(parent, -1,
883 _("New game"),
884 wxDefaultPosition,
885 wxDefaultSize,
886 wxDEFAULT_DIALOG_STYLE | wxDIALOG_MODAL)
887 {
888 m_w = w;
889 m_h = h;
890
891 // prompts and text controls
892 wxString strw, strh;
893 strw.Printf(_("%u"), *m_w);
894 strh.Printf(_("%u"), *m_h);
895 m_spinctrlw = new wxSpinCtrl( this, -1, strw );
896 m_spinctrlh = new wxSpinCtrl( this, -1, strh );
897
898 // component layout
899 wxBoxSizer *inputsizer1 = new wxBoxSizer( wxHORIZONTAL );
900 inputsizer1->Add( new wxStaticText(this, -1, _("Width")), 1, wxCENTRE | wxLEFT, 20);
901 inputsizer1->Add( m_spinctrlw, 2, wxCENTRE | wxLEFT | wxRIGHT, 20 );
902
903 wxBoxSizer *inputsizer2 = new wxBoxSizer( wxHORIZONTAL );
904 inputsizer2->Add( new wxStaticText(this, -1, _("Height")), 1, wxCENTRE | wxLEFT, 20);
905 inputsizer2->Add( m_spinctrlh, 2, wxCENTRE | wxLEFT | wxRIGHT, 20 );
906
907 wxBoxSizer *topsizer = new wxBoxSizer( wxVERTICAL );
908 topsizer->Add( CreateTextSizer(_("Enter board dimensions")), 0, wxALL, 10 );
909 topsizer->Add( new wxStaticLine(this, -1), 0, wxGROW | wxLEFT | wxRIGHT | wxBOTTOM, 10);
910 topsizer->Add( inputsizer1, 1, wxGROW | wxLEFT | wxRIGHT, 5 );
911 topsizer->Add( inputsizer2, 1, wxGROW | wxLEFT | wxRIGHT, 5 );
912 topsizer->Add( new wxStaticLine(this, -1), 0, wxGROW | wxLEFT | wxRIGHT | wxTOP, 10);
913 topsizer->Add( CreateButtonSizer(wxOK | wxCANCEL), 0, wxCENTRE | wxALL, 10);
914
915 // activate
916 SetSizer(topsizer);
917 SetAutoLayout(TRUE);
918 topsizer->SetSizeHints(this);
919 topsizer->Fit(this);
920 Centre(wxBOTH);
921 }
922
923 void LifeNewGameDialog::OnOK(wxCommandEvent& WXUNUSED(event))
924 {
925 *m_w = m_spinctrlw->GetValue();
926 *m_h = m_spinctrlh->GetValue();
927
928 EndModal(wxID_OK);
929 }
930
931 // --------------------------------------------------------------------------
932 // LifeSamplesDialog
933 // --------------------------------------------------------------------------
934
935 LifeSamplesDialog::LifeSamplesDialog(wxWindow *parent)
936 : wxDialog(parent, -1,
937 _("Sample games"),
938 wxDefaultPosition,
939 wxDefaultSize,
940 wxDEFAULT_DIALOG_STYLE | wxDIALOG_MODAL)
941 {
942 m_value = 0;
943
944 // create and populate the list of available samples
945 m_list = new wxListBox( this, ID_LISTBOX,
946 wxDefaultPosition,
947 wxDefaultSize,
948 0, NULL,
949 wxLB_SINGLE | wxLB_NEEDED_SB | wxLB_HSCROLL );
950
951 for (unsigned i = 0; i < (sizeof(g_shapes) / sizeof(LifeShape)); i++)
952 m_list->Append(g_shapes[i].m_name);
953
954 // descriptions
955 wxStaticBox *statbox = new wxStaticBox( this, -1, _("Description"));
956 m_life = new Life( 16, 16 );
957 m_life->SetShape(g_shapes[0]);
958 m_canvas = new LifeCanvas( this, m_life, FALSE );
959 m_text = new wxTextCtrl( this, -1,
960 g_shapes[0].m_desc,
961 wxDefaultPosition,
962 wxSize(300, 60),
963 wxTE_MULTILINE | wxTE_READONLY);
964
965 // layout components
966 wxStaticBoxSizer *sizer1 = new wxStaticBoxSizer( statbox, wxVERTICAL );
967 sizer1->Add( m_canvas, 2, wxGROW | wxCENTRE | wxALL, 5);
968 sizer1->Add( m_text, 1, wxGROW | wxCENTRE | wxALL, 5 );
969
970 wxBoxSizer *sizer2 = new wxBoxSizer( wxHORIZONTAL );
971 sizer2->Add( m_list, 0, wxGROW | wxCENTRE | wxALL, 5 );
972 sizer2->Add( sizer1, 1, wxGROW | wxCENTRE | wxALL, 5 );
973
974 wxBoxSizer *sizer3 = new wxBoxSizer( wxVERTICAL );
975 sizer3->Add( CreateTextSizer(_("Select one configuration")), 0, wxALL, 10 );
976 sizer3->Add( new wxStaticLine(this, -1), 0, wxGROW | wxLEFT | wxRIGHT, 10 );
977 sizer3->Add( sizer2, 1, wxGROW | wxCENTRE | wxALL, 5 );
978 sizer3->Add( new wxStaticLine(this, -1), 0, wxGROW | wxLEFT | wxRIGHT, 10 );
979 sizer3->Add( CreateButtonSizer(wxOK | wxCANCEL), 0, wxCENTRE | wxALL, 10 );
980
981 // activate
982 SetSizer(sizer3);
983 SetAutoLayout(TRUE);
984 sizer3->SetSizeHints(this);
985 sizer3->Fit(this);
986 Centre(wxBOTH);
987 }
988
989 LifeSamplesDialog::~LifeSamplesDialog()
990 {
991 m_canvas->Destroy();
992 delete m_life;
993 }
994
995 int LifeSamplesDialog::GetValue()
996 {
997 return m_value;
998 }
999
1000 void LifeSamplesDialog::OnListBox(wxCommandEvent& event)
1001 {
1002 if (event.GetSelection() != -1)
1003 {
1004 m_value = m_list->GetSelection();
1005 m_text->SetValue(g_shapes[ event.GetSelection() ].m_desc);
1006 m_life->SetShape(g_shapes[ event.GetSelection() ]);
1007
1008 m_canvas->DrawEverything(TRUE); // force redraw everything
1009 m_canvas->Refresh(FALSE); // do not erase background
1010 }
1011 }
1012
1013 // --------------------------------------------------------------------------
1014 // Life
1015 // --------------------------------------------------------------------------
1016
1017 Life::Life(int width, int height)
1018 {
1019 m_wrap = TRUE;
1020 m_cells = NULL;
1021 Create(width, height);
1022 }
1023
1024 Life::~Life()
1025 {
1026 Destroy();
1027 }
1028
1029 void Life::Create(int width, int height)
1030 {
1031 wxASSERT(width > 0 && height > 0);
1032
1033 m_width = width;
1034 m_height = height;
1035 m_cells = new Cell[m_width * m_height];
1036 Clear();
1037 }
1038
1039 void Life::Destroy()
1040 {
1041 delete[] m_cells;
1042 }
1043
1044 void Life::Clear()
1045 {
1046 for (int i = 0; i < m_width * m_height; i++)
1047 m_cells[i] = CELL_DEAD;
1048 }
1049
1050 bool Life::IsAlive(int x, int y) const
1051 {
1052 wxASSERT(x >= 0 && y >= 0 && x < m_width && y < m_height);
1053
1054 return (m_cells[y * m_width + x] & CELL_ALIVE);
1055 }
1056
1057 bool Life::HasChanged(int x, int y) const
1058 {
1059 wxASSERT(x >= 0 && y >= 0 && x < m_width && y < m_height);
1060
1061 return (m_cells[y * m_width + x] & CELL_MARK) != 0;
1062 }
1063
1064 void Life::SetBorderWrap(bool on)
1065 {
1066 m_wrap = on;
1067 }
1068
1069 void Life::SetCell(int x, int y, bool alive)
1070 {
1071 wxASSERT(x >= 0 && y >= 0 && x < m_width && y < m_height);
1072
1073 m_cells[y * m_width + x] = (alive? CELL_ALIVE : CELL_DEAD);
1074 }
1075
1076 void Life::SetShape(LifeShape& shape)
1077 {
1078 wxASSERT((m_width >= shape.m_width) && (m_height >= shape.m_height));
1079
1080 int x0 = (m_width - shape.m_width) / 2;
1081 int y0 = (m_height - shape.m_height) / 2;
1082 char *p = shape.m_data;
1083
1084 Clear();
1085 for (int j = y0; j < y0 + shape.m_height; j++)
1086 for (int i = x0; i < x0 + shape.m_width; i++)
1087 SetCell(i, j, *(p++) == '*');
1088 }
1089
1090 bool Life::NextTic()
1091 {
1092 long changed = 0;
1093 int i, j;
1094
1095 /* 1st pass. Find and mark deaths and births for this generation.
1096 *
1097 * Rules:
1098 * An organism with <= 1 neighbors will die due to isolation.
1099 * An organism with >= 4 neighbors will die due to starvation.
1100 * New organisms are born in cells with exactly 3 neighbors.
1101 */
1102 for (j = 0; j < m_height; j++)
1103 for (i = 0; i < m_width; i++)
1104 {
1105 int neighbors = GetNeighbors(i, j);
1106 bool alive = IsAlive(i, j);
1107
1108 /* Set CELL_MARK if this cell must change, clear it
1109 * otherwise. We cannot toggle the CELL_ALIVE bit yet
1110 * because all deaths and births are simultaneous (it
1111 * would affect neighbouring cells).
1112 */
1113 if ((!alive && neighbors == 3) ||
1114 (alive && (neighbors <= 1 || neighbors >= 4)))
1115 m_cells[j * m_width + i] |= CELL_MARK;
1116 else
1117 m_cells[j * m_width + i] &= ~CELL_MARK;
1118 }
1119
1120 /* 2nd pass. Stabilize.
1121 */
1122 for (j = 0; j < m_height; j++)
1123 for (i = 0; i < m_width; i++)
1124 {
1125 /* Toggle CELL_ALIVE for those cells marked in the
1126 * previous pass. Do not clear the CELL_MARK bit yet;
1127 * it is useful to know which cells have changed and
1128 * thus must be updated in the screen.
1129 */
1130 if (m_cells[j * m_width + i] & CELL_MARK)
1131 {
1132 m_cells[j * m_width + i] ^= CELL_ALIVE;
1133 changed++;
1134 }
1135 }
1136
1137 return (changed != 0);
1138 }
1139
1140 int Life::GetNeighbors(int x, int y) const
1141 {
1142 wxASSERT(x >= 0 && y >= 0 && x < m_width && y < m_height);
1143
1144 int neighbors = 0;
1145
1146 int i0 = (x)? (x - 1) : 0;
1147 int j0 = (y)? (y - 1) : 0;
1148 int i1 = (x < (m_width - 1))? (x + 1) : (m_width - 1);
1149 int j1 = (y < (m_height - 1))? (y + 1) : (m_height - 1);
1150
1151 if (m_wrap && ( !x || !y || x == (m_width - 1) || y == (m_height - 1)))
1152 {
1153 // this is an outer cell and wraparound is on
1154 for (int j = y - 1; j <= y + 1; j++)
1155 for (int i = x - 1; i <= x + 1; i++)
1156 if (IsAlive( ((i < 0)? (i + m_width ) : (i % m_width)),
1157 ((j < 0)? (j + m_height) : (j % m_height)) ))
1158 neighbors++;
1159 }
1160 else
1161 {
1162 // this is an inner cell, or wraparound is off
1163 for (int j = j0; j <= j1; j++)
1164 for (int i = i0; i <= i1; i++)
1165 if (IsAlive(i, j))
1166 neighbors++;
1167 }
1168
1169 // do not count ourselves
1170 if (IsAlive(x, y)) neighbors--;
1171
1172 return neighbors;
1173 }
1174
1175 void Life::SetCell(int x, int y, Cell status)
1176 {
1177 wxASSERT(x >= 0 && y >= 0 && x < m_width && y < m_height);
1178
1179 m_cells[y * m_width + x] = status;
1180 }
1181