]>
git.saurik.com Git - wxWidgets.git/blob - samples/life/life.cpp
29a7d4d7549fc94b689f6a3e3e76205a1da00091
1 /////////////////////////////////////////////////////////////////////////////
3 // Purpose: The game of life, created by J. H. Conway
4 // Author: Guillermo Rodriguez Garcia, <guille@iies.es>
8 // Copyright: (c) 2000, Guillermo Rodriguez Garcia
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // ==========================================================================
14 // ==========================================================================
16 // minimum and maximum table size, in each dimension
21 #define ADD_TOOL(a, b, c, d) \
22 toolBar->AddTool(a, b, wxNullBitmap, FALSE, -1, -1, (wxObject *)0, c, d)
25 ((LifeFrame *) wxGetApp().GetTopWindow())
27 // --------------------------------------------------------------------------
29 // --------------------------------------------------------------------------
32 #pragma implementation "life.cpp"
33 #pragma interface "life.cpp"
36 // for compilers that support precompilation, includes "wx/wx.h".
37 #include "wx/wxprec.h"
43 // for all others, include the necessary headers
48 #include "wx/statline.h"
49 #include "wx/spinctrl.h"
51 // --------------------------------------------------------------------------
53 // --------------------------------------------------------------------------
55 #if defined(__WXGTK__) || defined(__WXMOTIF__)
56 // the application icon
57 #include "mondrian.xpm"
59 // bitmap buttons for the toolbar
60 #include "bitmaps/reset.xpm"
61 #include "bitmaps/play.xpm"
62 #include "bitmaps/stop.xpm"
65 // --------------------------------------------------------------------------
67 // --------------------------------------------------------------------------
81 Life(int width
, int height
);
83 void Create(int width
, int height
);
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
);
99 CELL_DEAD
= 0x0000, // is dead
100 CELL_ALIVE
= 0x0001, // is alive
101 CELL_MARK
= 0x0002, // will change / has changed
105 int GetNeighbors(int x
, int y
) const;
106 inline void SetCell(int x
, int y
, Cell status
);
114 class LifeCanvas
: public wxScrolledWindow
118 LifeCanvas(wxWindow
* parent
, Life
* life
);
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); };
130 void OnPaint(wxPaintEvent
& event
);
131 void OnMouse(wxMouseEvent
& event
);
132 void OnSize(wxSizeEvent
& event
);
135 // any class wishing to process wxWindows events must use this macro
136 DECLARE_EVENT_TABLE()
151 MouseStatus m_status
;
155 class LifeTimer
: public wxTimer
162 class LifeFrame
: public wxFrame
170 void UpdateInfoText();
173 void OnMenu(wxCommandEvent
& event
);
174 void OnNewGame(wxCommandEvent
& event
);
178 void OnSlider(wxScrollEvent
& event
);
181 // any class wishing to process wxWindows events must use this macro
182 DECLARE_EVENT_TABLE()
186 LifeCanvas
*m_canvas
;
187 wxStaticText
*m_text
;
193 // Life new game dialog
194 class LifeNewGameDialog
: public wxDialog
198 LifeNewGameDialog(wxWindow
*parent
, int *w
, int *h
);
201 void OnOK(wxCommandEvent
& event
);
204 // any class wishing to process wxWindows events must use this macro
205 DECLARE_EVENT_TABLE();
209 wxSpinCtrl
*m_spinctrlw
;
210 wxSpinCtrl
*m_spinctrlh
;
214 class LifeApp
: public wxApp
217 virtual bool OnInit();
221 // --------------------------------------------------------------------------
223 // --------------------------------------------------------------------------
225 // IDs for the controls and the menu commands
228 // menu items and toolbar buttons
240 // --------------------------------------------------------------------------
241 // event tables and other macros for wxWindows
242 // --------------------------------------------------------------------------
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
)
255 BEGIN_EVENT_TABLE(LifeCanvas
, wxScrolledWindow
)
256 EVT_PAINT ( LifeCanvas::OnPaint
)
257 EVT_SIZE ( LifeCanvas::OnSize
)
258 EVT_MOUSE_EVENTS ( LifeCanvas::OnMouse
)
261 BEGIN_EVENT_TABLE(LifeNewGameDialog
, wxDialog
)
262 EVT_BUTTON (wxID_OK
, LifeNewGameDialog::OnOK
)
266 // Create a new application object
267 IMPLEMENT_APP(LifeApp
)
269 // ==========================================================================
271 // ==========================================================================
273 // --------------------------------------------------------------------------
275 // --------------------------------------------------------------------------
277 // `Main program' equivalent: the program execution "starts" here
278 bool LifeApp::OnInit()
280 // create the main application window
281 LifeFrame
*frame
= new LifeFrame();
283 // show it and tell the application that it's our main window
287 // enter the main message loop and run the app
291 // --------------------------------------------------------------------------
293 // --------------------------------------------------------------------------
296 LifeFrame::LifeFrame() : wxFrame((wxFrame
*)0, -1, _("Life!"), wxPoint(50, 50))
299 SetIcon(wxICON(mondrian
));
302 wxMenu
*menuFile
= new wxMenu("", wxMENU_TEAROFF
);
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
);
314 wxMenuBar
*menuBar
= new wxMenuBar();
315 menuBar
->Append(menuFile
, _("&File"));
319 wxBitmap tbBitmaps
[3];
320 tbBitmaps
[0] = wxBITMAP(reset
);
321 tbBitmaps
[1] = wxBITMAP(play
);
322 tbBitmaps
[2] = wxBITMAP(stop
);
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
);
335 SetStatusText(_("Welcome to Life!"));
338 wxPanel
*panel
= new wxPanel(this, -1);
341 m_life
= new Life(20, 20);
342 m_canvas
= new LifeCanvas(panel
, m_life
);
343 m_timer
= new LifeTimer();
346 m_text
= new wxStaticText(panel
, -1, "");
350 wxSlider
*slider
= new wxSlider(panel
, ID_SLIDER
, 5, 1, 10,
351 wxDefaultPosition
, wxSize(200,-1), wxSL_HORIZONTAL
| wxSL_AUTOTICKS
);
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
);
362 sizer
->SetSizeHints(this);
365 LifeFrame::~LifeFrame()
371 void LifeFrame::UpdateInfoText()
375 msg
.Printf(_("Generation: %u, Interval: %u ms"), m_tics
, m_interval
);
376 m_text
->SetLabel(msg
);
380 void LifeFrame::OnMenu(wxCommandEvent
& event
)
382 switch (event
.GetId())
384 case ID_START
: OnStart(); break;
385 case ID_STOP
: OnStop(); break;
390 m_canvas
->DrawEverything(TRUE
);
391 m_canvas
->Refresh(FALSE
);
399 _("This is the about dialog of the Life! sample.\n"
400 "(c) 2000 Guillermo Rodriguez Garcia"),
402 wxOK
| wxICON_INFORMATION
,
408 // TRUE is to force the frame to close
415 void LifeFrame::OnNewGame(wxCommandEvent
& WXUNUSED(event
))
417 int w
= m_life
->GetWidth();
418 int h
= m_life
->GetHeight();
421 // stop if it was running
425 LifeNewGameDialog
dialog(this, &w
, &h
);
426 result
= dialog
.ShowModal();
429 if (result
== wxID_OK
)
432 if (w
>= LIFE_MIN
&& w
<= LIFE_MAX
&&
433 h
>= LIFE_MIN
&& h
<= LIFE_MAX
)
436 m_life
->Create(w
, h
);
445 msg
.Printf(_("Both dimensions must be within %u and %u.\n"),
447 wxMessageBox(msg
, _("Error!"), wxOK
| wxICON_EXCLAMATION
, this);
452 void LifeFrame::OnStart()
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
);
459 m_timer
->Start(m_interval
);
463 void LifeFrame::OnStop()
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
);
474 void LifeFrame::OnTimer()
476 if (m_life
->NextTic())
482 m_canvas
->DrawEverything();
483 m_canvas
->Refresh(FALSE
);
486 void LifeFrame::OnSlider(wxScrollEvent
& event
)
488 m_interval
= event
.GetPosition() * 100;
490 // restart timer if running, to set the new interval
494 m_timer
->Start(m_interval
);
500 // --------------------------------------------------------------------------
502 // --------------------------------------------------------------------------
505 LifeTimer::LifeTimer() : wxTimer()
510 void LifeTimer::Notify()
512 GET_FRAME()->OnTimer();
515 // --------------------------------------------------------------------------
517 // --------------------------------------------------------------------------
519 // canvas constructor
520 LifeCanvas::LifeCanvas(wxWindow
*parent
, Life
*life
)
521 : wxScrolledWindow(parent
, -1, wxPoint(0, 0), wxSize(100, 100))
529 LifeCanvas::~LifeCanvas()
534 void LifeCanvas::Reset()
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;
548 // redraw all, incl. background
549 DrawEverything(TRUE
);
550 SetScrollbars(10, 10, (m_width
+ 9) / 10, (m_height
+ 9) / 10);
554 void LifeCanvas::DrawEverything(bool force
)
558 dc
.SelectObject(*m_bmp
);
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
))
567 // bounding rectangle (always drawn)
568 dc
.SetPen(*wxBLACK_PEN
);
569 dc
.SetBrush(*wxTRANSPARENT_BRUSH
);
570 dc
.DrawRectangle(0, 0, m_width
, m_height
);
573 dc
.SelectObject(wxNullBitmap
);
576 // draw a single cell
577 void LifeCanvas::DrawCell(int i
, int j
)
581 dc
.SelectObject(*m_bmp
);
587 dc
.SelectObject(wxNullBitmap
);
590 void LifeCanvas::DrawCell(int i
, int j
, wxDC
&dc
)
592 if (m_life
->IsAlive(i
, j
))
594 dc
.SetPen(*wxBLACK_PEN
);
595 dc
.SetBrush(*wxBLACK_BRUSH
);
596 dc
.DrawRectangle(CellToCoord(i
),
603 dc
.SetPen(*wxLIGHT_GREY_PEN
);
604 dc
.SetBrush(*wxTRANSPARENT_BRUSH
);
605 dc
.DrawRectangle(CellToCoord(i
),
609 dc
.SetPen(*wxWHITE_PEN
);
610 dc
.SetBrush(*wxWHITE_BRUSH
);
611 dc
.DrawRectangle(CellToCoord(i
) + 1,
619 void LifeCanvas::OnPaint(wxPaintEvent
& event
)
624 wxRegionIterator
upd(GetUpdateRegion());
625 int x
, y
, w
, h
, xx
, yy
;
628 memdc
.SelectObject(*m_bmp
);
636 CalcUnscrolledPosition(x
, y
, &xx
, &yy
);
638 dc
.Blit(x
, y
, w
, h
, &memdc
, xx
- m_xoffset
, yy
- m_yoffset
);
642 memdc
.SelectObject(wxNullBitmap
);
646 void LifeCanvas::OnMouse(wxMouseEvent
& event
)
648 int x
, y
, xx
, yy
, i
, j
;
650 // which cell are we pointing at?
653 CalcUnscrolledPosition(x
, y
, &xx
, &yy
);
654 i
= CoordToCell( xx
- m_xoffset
);
655 j
= CoordToCell( yy
- m_yoffset
);
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
,
662 // set cursor shape and statusbar text
663 if (i
< 0 || i
>= m_life
->GetWidth() ||
664 j
< 0 || j
>= m_life
->GetHeight())
666 GET_FRAME()->SetStatusText(wxEmptyString
, 1);
667 SetCursor(*wxSTANDARD_CURSOR
);
672 msg
.Printf(_("Cell: (%u, %u)"), i
, j
);
673 GET_FRAME()->SetStatusText(msg
, 1);
674 SetCursor(*wxCROSS_CURSOR
);
678 if (!event
.LeftIsDown())
680 m_status
= MOUSE_NOACTION
;
682 else if (i
>= 0 && i
< m_life
->GetWidth() &&
683 j
>= 0 && j
< m_life
->GetHeight())
685 bool alive
= m_life
->IsAlive(i
, j
);
687 // if just pressed, update status
688 if (m_status
== MOUSE_NOACTION
)
689 m_status
= (alive
? MOUSE_ERASING
: MOUSE_DRAWING
);
691 // toggle cell and refresh if needed
692 if (((m_status
== MOUSE_ERASING
) && alive
) ||
693 ((m_status
== MOUSE_DRAWING
) && !alive
))
695 wxRect
rect(x
, y
, m_cellsize
+ 1, m_cellsize
+ 1);
696 m_life
->SetCell(i
, j
, !alive
);
698 Refresh(FALSE
, &rect
);
703 void LifeCanvas::OnSize(wxSizeEvent
& event
)
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;
710 // allow default processing
714 // --------------------------------------------------------------------------
716 // --------------------------------------------------------------------------
718 LifeNewGameDialog::LifeNewGameDialog(wxWindow
*parent
, int *w
, int *h
)
719 : wxDialog(parent
, -1, _("New game"),
720 wxDefaultPosition
, wxDefaultSize
,
721 wxDEFAULT_DIALOG_STYLE
| wxDIALOG_MODAL
)
726 wxBoxSizer
*topsizer
= new wxBoxSizer( wxVERTICAL
);
729 topsizer
->Add( CreateTextSizer(_("Enter board dimensions")), 0, wxALL
, 10 );
730 topsizer
->Add( new wxStaticLine(this, -1), 0, wxGROW
| wxLEFT
| wxRIGHT
| wxBOTTOM
, 10);
732 // prompts and text controls
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
);
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 );
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);
751 topsizer
->Add( CreateButtonSizer(wxOK
| wxCANCEL
), 0, wxCENTRE
| wxALL
, 10);
756 topsizer
->SetSizeHints(this);
761 void LifeNewGameDialog::OnOK(wxCommandEvent
& WXUNUSED(event
))
763 *m_w
= m_spinctrlw
->GetValue();
764 *m_h
= m_spinctrlh
->GetValue();
769 // --------------------------------------------------------------------------
771 // --------------------------------------------------------------------------
773 Life::Life(int width
, int height
)
775 Create(width
, height
);
783 void Life::Create(int width
, int height
)
785 wxASSERT(width
> 0 || height
> 0);
789 m_cells
= new Cell
[m_width
* m_height
];
800 for (int i
= 0; i
< m_width
* m_height
; i
++)
801 m_cells
[i
] = CELL_DEAD
;
804 bool Life::IsAlive(int x
, int y
) const
806 wxASSERT(x
< m_width
|| y
< m_height
);
808 return (m_cells
[y
* m_width
+ x
] & CELL_ALIVE
);
811 bool Life::HasChanged(int x
, int y
) const
813 wxASSERT(x
< m_width
|| y
< m_height
);
815 return (m_cells
[y
* m_width
+ x
] & CELL_MARK
);
818 void Life::SetCell(int x
, int y
, bool alive
)
820 wxASSERT(x
< m_width
|| y
< m_height
);
822 m_cells
[y
* m_width
+ x
] = (alive
? CELL_ALIVE
: CELL_DEAD
);
830 /* 1st pass. Find and mark deaths and births for this generation.
833 * An organism with <= 1 neighbors will die due to isolation.
834 * An organism with >= 4 neighbors will die due to starvation.
835 * New organisms are born in cells with exactly 3 neighbors.
837 for (j
= 0; j
< m_height
; j
++)
838 for (i
= 0; i
< m_width
; i
++)
840 int neighbors
= GetNeighbors(i
, j
);
841 bool alive
= IsAlive(i
, j
);
843 /* Set CELL_MARK if this cell must change, clear it
844 * otherwise. We cannot toggle the CELL_ALIVE bit yet
845 * because all deaths and births are simultaneous (it
846 * would affect neighbouring cells).
848 if ((!alive
&& neighbors
== 3) ||
849 (alive
&& (neighbors
<= 1 || neighbors
>= 4)))
850 m_cells
[j
* m_width
+ i
] |= CELL_MARK
;
852 m_cells
[j
* m_width
+ i
] &= ~CELL_MARK
;
855 /* 2nd pass. Stabilize.
857 for (j
= 0; j
< m_height
; j
++)
858 for (i
= 0; i
< m_width
; i
++)
860 /* Toggle CELL_ALIVE for those cells marked in the
861 * previous pass. Do not clear the CELL_MARK bit yet;
862 * it is useful to know which cells have changed and
863 * thus must be updated in the screen.
865 if (m_cells
[j
* m_width
+ i
] & CELL_MARK
)
867 m_cells
[j
* m_width
+ i
] ^= CELL_ALIVE
;
872 return (changed
!= 0);
875 int Life::GetNeighbors(int x
, int y
) const
877 wxASSERT(x
< m_width
|| y
< m_height
);
879 // count number of neighbors (wrap around board limits)
881 for (int j
= y
- 1; j
<= y
+ 1; j
++)
882 for (int i
= x
- 1; i
<= x
+ 1; i
++)
884 if (IsAlive( ((i
< 0)? (i
+ m_width
) : (i
% m_width
)),
885 ((j
< 0)? (j
+ m_height
) : (j
% m_height
)) ))
889 // do not count ourselves
890 if (IsAlive(x
, y
)) neighbors
--;
895 void Life::SetCell(int x
, int y
, Cell status
)
897 wxASSERT(x
< m_width
|| y
< m_height
);
899 m_cells
[y
* m_width
+ x
] = status
;