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 // ==========================================================================
13 // headers, declarations, constants
14 // ==========================================================================
17 #pragma implementation "life.h"
20 // For compilers that support precompilation, includes "wx/wx.h".
21 #include "wx/wxprec.h"
31 #include "wx/statline.h"
37 // --------------------------------------------------------------------------
39 // --------------------------------------------------------------------------
41 #if defined(__WXGTK__) || defined(__WXMOTIF__)
42 // the application icon
43 #include "mondrian.xpm"
45 // bitmap buttons for the toolbar
46 #include "bitmaps/reset.xpm"
47 #include "bitmaps/play.xpm"
48 #include "bitmaps/stop.xpm"
49 #include "bitmaps/zoomin.xpm"
50 #include "bitmaps/zoomout.xpm"
53 // --------------------------------------------------------------------------
55 // --------------------------------------------------------------------------
57 // IDs for the controls and the menu commands
60 // menu items and toolbar buttons
73 // speed selection slider
77 // --------------------------------------------------------------------------
78 // event tables and other macros for wxWindows
79 // --------------------------------------------------------------------------
82 BEGIN_EVENT_TABLE(LifeFrame
, wxFrame
)
83 EVT_MENU (ID_SAMPLES
, LifeFrame::OnSamples
)
84 EVT_MENU (ID_RESET
, LifeFrame::OnMenu
)
85 EVT_MENU (ID_ABOUT
, LifeFrame::OnMenu
)
86 EVT_MENU (ID_EXIT
, LifeFrame::OnMenu
)
87 EVT_MENU (ID_CENTER
, LifeFrame::OnMenu
)
88 EVT_MENU (ID_START
, LifeFrame::OnMenu
)
89 EVT_MENU (ID_STEP
, LifeFrame::OnMenu
)
90 EVT_MENU (ID_STOP
, LifeFrame::OnMenu
)
91 EVT_MENU (ID_ZOOMIN
, LifeFrame::OnMenu
)
92 EVT_MENU (ID_ZOOMOUT
, LifeFrame::OnMenu
)
93 EVT_MENU (ID_TOPSPEED
, LifeFrame::OnMenu
)
94 EVT_COMMAND_SCROLL (ID_SLIDER
, LifeFrame::OnSlider
)
95 EVT_CLOSE ( LifeFrame::OnClose
)
98 BEGIN_EVENT_TABLE(LifeCanvas
, wxWindow
)
99 EVT_PAINT ( LifeCanvas::OnPaint
)
100 EVT_SCROLLWIN ( LifeCanvas::OnScroll
)
101 EVT_SIZE ( LifeCanvas::OnSize
)
102 EVT_MOUSE_EVENTS ( LifeCanvas::OnMouse
)
103 EVT_ERASE_BACKGROUND( LifeCanvas::OnEraseBackground
)
107 // Create a new application object
108 IMPLEMENT_APP(LifeApp
)
111 // ==========================================================================
113 // ==========================================================================
116 #define ADD_TOOL(id, bmp, tooltip, help) \
117 toolBar->AddTool(id, bmp, wxNullBitmap, FALSE, -1, -1, (wxObject *)0, tooltip, help)
119 #define GET_FRAME() ((LifeFrame *) wxGetApp().GetTopWindow())
121 // --------------------------------------------------------------------------
123 // --------------------------------------------------------------------------
125 // 'Main program' equivalent: the program execution "starts" here
126 bool LifeApp::OnInit()
128 // create the main application window
129 LifeFrame
*frame
= new LifeFrame();
131 // show it and tell the application that it's our main window
135 // enter the main message loop and run the app
139 // --------------------------------------------------------------------------
141 // --------------------------------------------------------------------------
144 LifeFrame::LifeFrame() : wxFrame((wxFrame
*)0, -1, _("Life!"), wxPoint(50, 50))
147 SetIcon(wxICON(mondrian
));
150 wxMenu
*menuFile
= new wxMenu("", wxMENU_TEAROFF
);
151 wxMenu
*menuGame
= new wxMenu("", wxMENU_TEAROFF
);
153 menuFile
->Append(ID_RESET
, _("Reset"), _("Start a new game"));
154 menuFile
->Append(ID_SAMPLES
, _("Sample game..."), _("Select a sample configuration"));
155 menuFile
->AppendSeparator();
156 menuFile
->Append(ID_ABOUT
, _("&About...\tCtrl-A"), _("Show about dialog"));
157 menuFile
->AppendSeparator();
158 menuFile
->Append(ID_EXIT
, _("E&xit\tAlt-X"), _("Quit this program"));
160 menuGame
->Append(ID_CENTER
, _("Re¢er\tCtrl-C"), _("Go to (0, 0)"));
161 menuGame
->Append(ID_START
, _("&Start\tCtrl-S"), _("Start"));
162 menuGame
->Append(ID_STEP
, _("&Next\tCtrl-N"), _("Single step"));
163 menuGame
->Append(ID_STOP
, _("S&top\tCtrl-T"), _("Stop"));
164 menuGame
->Enable(ID_STOP
, FALSE
);
165 menuGame
->AppendSeparator();
166 menuGame
->Append(ID_TOPSPEED
, _("Top speed!"), _("Go as fast as possible"));
167 menuGame
->AppendSeparator();
168 menuGame
->Append(ID_ZOOMIN
, _("Zoom &in\tCtrl-I"));
169 menuGame
->Append(ID_ZOOMOUT
, _("Zoom &out\tCtrl-O"));
171 wxMenuBar
*menuBar
= new wxMenuBar();
172 menuBar
->Append(menuFile
, _("&File"));
173 menuBar
->Append(menuGame
, _("&Game"));
177 wxBitmap tbBitmaps
[5];
179 tbBitmaps
[0] = wxBITMAP(reset
);
180 tbBitmaps
[1] = wxBITMAP(play
);
181 tbBitmaps
[2] = wxBITMAP(stop
);
182 tbBitmaps
[3] = wxBITMAP(zoomin
);
183 tbBitmaps
[4] = wxBITMAP(zoomout
);
185 wxToolBar
*toolBar
= CreateToolBar();
186 toolBar
->SetMargins(5, 5);
187 toolBar
->SetToolBitmapSize(wxSize(16, 16));
188 ADD_TOOL(ID_RESET
, tbBitmaps
[0], _("Reset"), _("Start a new game"));
189 ADD_TOOL(ID_START
, tbBitmaps
[1], _("Start"), _("Start"));
190 ADD_TOOL(ID_STOP
, tbBitmaps
[2], _("Stop"), _("Stop"));
191 toolBar
->AddSeparator();
192 ADD_TOOL(ID_ZOOMIN
, tbBitmaps
[3], _("Zoom in"), _("Zoom in"));
193 ADD_TOOL(ID_ZOOMOUT
, tbBitmaps
[4], _("Zoom out"), _("Zoom out"));
195 toolBar
->EnableTool(ID_STOP
, FALSE
); // must be after Realize() !
199 SetStatusText(_("Welcome to Life!"));
202 wxPanel
*panel
= new wxPanel(this, -1);
204 m_canvas
= new LifeCanvas(panel
, m_life
);
205 m_timer
= new LifeTimer();
210 m_text
= new wxStaticText(panel
, -1, "");
213 // speed selection slider
214 wxSlider
*slider
= new wxSlider(panel
, ID_SLIDER
,
218 wxSL_HORIZONTAL
| wxSL_AUTOTICKS
);
221 wxBoxSizer
*sizer
= new wxBoxSizer(wxVERTICAL
);
222 sizer
->Add(new wxStaticLine(panel
, -1), 0, wxGROW
| wxCENTRE
);
223 sizer
->Add(m_canvas
, 1, wxGROW
| wxCENTRE
| wxALL
, 2);
224 sizer
->Add(new wxStaticLine(panel
, -1), 0, wxGROW
| wxCENTRE
);
225 sizer
->Add(m_text
, 0, wxCENTRE
| wxTOP
, 4);
226 sizer
->Add(slider
, 0, wxCENTRE
| wxALL
, 4);
227 panel
->SetSizer(sizer
);
228 panel
->SetAutoLayout(TRUE
);
230 sizer
->SetSizeHints(this);
233 LifeFrame::~LifeFrame()
238 void LifeFrame::UpdateInfoText()
242 msg
.Printf(_(" Generation: %u (T: %u ms), Population: %u "),
244 m_topspeed
? 0 : m_interval
,
245 m_life
->GetNumCells());
246 m_text
->SetLabel(msg
);
249 // Enable or disable tools and menu entries according to the current
250 // state. See also wxEVT_UPDATE_UI events for a slightly different
252 void LifeFrame::UpdateUI()
255 GetToolBar()->EnableTool(ID_START
, !m_running
);
256 GetToolBar()->EnableTool(ID_STOP
, m_running
);
257 GetMenuBar()->GetMenu(1)->Enable(ID_START
, !m_running
);
258 GetMenuBar()->GetMenu(1)->Enable(ID_STEP
, !m_running
);
259 GetMenuBar()->GetMenu(1)->Enable(ID_STOP
, m_running
);
262 int cellsize
= m_canvas
->GetCellSize();
263 GetToolBar()->EnableTool(ID_ZOOMIN
, cellsize
< 32);
264 GetToolBar()->EnableTool(ID_ZOOMOUT
, cellsize
> 1);
265 GetMenuBar()->GetMenu(1)->Enable(ID_ZOOMIN
, cellsize
< 32);
266 GetMenuBar()->GetMenu(1)->Enable(ID_ZOOMOUT
, cellsize
> 1);
270 void LifeFrame::OnMenu(wxCommandEvent
& event
)
272 switch (event
.GetId())
274 case ID_CENTER
: m_canvas
->Recenter(0, 0); break;
275 case ID_START
: OnStart(); break;
276 case ID_STEP
: OnTimer(); break;
277 case ID_STOP
: OnStop(); break;
280 int cellsize
= m_canvas
->GetCellSize();
283 m_canvas
->SetCellSize(cellsize
* 2);
290 int cellsize
= m_canvas
->GetCellSize();
293 m_canvas
->SetCellSize(cellsize
/ 2);
303 while (m_running
&& m_topspeed
)
312 // stop if it was running
315 m_canvas
->Recenter(0, 0);
322 LifeAboutDialog
dialog(this);
328 // TRUE is to force the frame to close
335 void LifeFrame::OnClose(wxCloseEvent
& WXUNUSED(event
))
337 // Stop if it was running; this is absolutely needed because
338 // the frame won't be actually destroyed until there are no
339 // more pending events, and this in turn won't ever happen
340 // if the timer is running faster than the window can redraw.
345 void LifeFrame::OnSamples(wxCommandEvent
& WXUNUSED(event
))
347 // stop if it was running
351 LifeSamplesDialog
dialog(this);
354 if (dialog
.ShowModal() == wxID_OK
)
356 const LifeShape shape
= dialog
.GetShape();
360 m_life
->SetShape(shape
);
363 m_canvas
->Recenter(0, 0);
369 void LifeFrame::OnStart()
373 m_timer
->Start(m_interval
);
379 void LifeFrame::OnStop()
390 void LifeFrame::OnTimer()
392 if (m_life
->NextTic())
397 m_canvas
->DrawChanged();
401 void LifeFrame::OnSlider(wxScrollEvent
& event
)
403 m_interval
= event
.GetPosition() * 100;
414 // --------------------------------------------------------------------------
416 // --------------------------------------------------------------------------
418 void LifeTimer::Notify()
420 GET_FRAME()->OnTimer();
423 // --------------------------------------------------------------------------
425 // --------------------------------------------------------------------------
427 // canvas constructor
428 LifeCanvas::LifeCanvas(wxWindow
*parent
, Life
*life
, bool interactive
)
429 : wxWindow(parent
, -1, wxPoint(0, 0), wxSize(100, 100),
433 m_interactive
= interactive
;
435 m_status
= MOUSE_NOACTION
;
442 SetCursor(*wxCROSS_CURSOR
);
444 // reduce flicker if wxEVT_ERASE_BACKGROUND is not available
445 SetBackgroundColour(*wxWHITE
);
448 LifeCanvas::~LifeCanvas()
453 // recenter at the given position
454 void LifeCanvas::Recenter(wxInt32 i
, wxInt32 j
)
456 m_viewportX
= i
- m_viewportW
/ 2;
457 m_viewportY
= j
- m_viewportH
/ 2;
463 // set the cell size and refresh display
464 void LifeCanvas::SetCellSize(int cellsize
)
466 m_cellsize
= cellsize
;
468 // find current center
469 wxInt32 cx
= m_viewportX
+ m_viewportW
/ 2;
470 wxInt32 cy
= m_viewportY
+ m_viewportH
/ 2;
472 // get current canvas size and adjust viewport accordingly
474 GetClientSize(&w
, &h
);
475 m_viewportW
= (w
+ m_cellsize
- 1) / m_cellsize
;
476 m_viewportH
= (h
+ m_cellsize
- 1) / m_cellsize
;
479 m_viewportX
= cx
- m_viewportW
/ 2;
480 m_viewportY
= cy
- m_viewportH
/ 2;
485 SetScrollbar(wxHORIZONTAL
, m_viewportW
, m_viewportW
, 3 * m_viewportW
);
486 SetScrollbar(wxVERTICAL
, m_viewportH
, m_viewportH
, 3 * m_viewportH
);
487 m_thumbX
= m_viewportW
;
488 m_thumbY
= m_viewportH
;
495 void LifeCanvas::DrawCell(wxInt32 i
, wxInt32 j
, bool alive
)
499 dc
.SetPen(alive
? *wxBLACK_PEN
: *wxWHITE_PEN
);
500 dc
.SetBrush(alive
? *wxBLACK_BRUSH
: *wxWHITE_BRUSH
);
507 void LifeCanvas::DrawCell(wxInt32 i
, wxInt32 j
, wxDC
&dc
)
509 wxCoord x
= CellToX(i
);
510 wxCoord y
= CellToY(j
);
512 // if cellsize is 1 or 2, there will be no grid
519 dc
.DrawRectangle(x
, y
, 2, 2);
522 dc
.DrawRectangle(x
+ 1, y
+ 1, m_cellsize
- 1, m_cellsize
- 1);
526 // draw all changed cells
527 void LifeCanvas::DrawChanged()
535 m_life
->BeginFind(m_viewportX
,
537 m_viewportX
+ m_viewportW
,
538 m_viewportY
+ m_viewportH
,
545 dc
.SetPen(*wxBLACK_PEN
);
549 dc
.SetPen(*wxTRANSPARENT_PEN
);
550 dc
.SetBrush(*wxBLACK_BRUSH
);
552 dc
.SetLogicalFunction(wxINVERT
);
556 done
= m_life
->FindMore(&cells
, &ncells
);
558 for (size_t m
= 0; m
< ncells
; m
++)
559 DrawCell(cells
[m
].i
, cells
[m
].j
, dc
);
565 void LifeCanvas::OnPaint(wxPaintEvent
& event
)
568 wxRect rect
= GetUpdateRegion().GetBox();
570 wxInt32 i0
, j0
, i1
, j1
;
576 h
= rect
.GetHeight();
580 i1
= XToCell(x
+ w
- 1);
581 j1
= YToCell(y
+ h
- 1);
587 m_life
->BeginFind(i0
, j0
, i1
, j1
, FALSE
);
588 done
= m_life
->FindMore(&cells
, &ncells
);
590 // erase all damaged cells and draw the grid
592 dc
.SetBrush(*wxWHITE_BRUSH
);
597 dc
.SetPen(*wxWHITE_PEN
);
598 dc
.DrawRectangle(x
, y
, w
, h
);
604 w
= CellToX(i1
+ 1) - x
+ 1;
605 h
= CellToY(j1
+ 1) - y
+ 1;
607 dc
.SetPen(*wxLIGHT_GREY_PEN
);
608 for (wxInt32 yy
= y
; yy
<= (y
+ h
- m_cellsize
); yy
+= m_cellsize
)
609 dc
.DrawRectangle(x
, yy
, w
, m_cellsize
+ 1);
610 for (wxInt32 xx
= x
; xx
<= (x
+ w
- m_cellsize
); xx
+= m_cellsize
)
611 dc
.DrawLine(xx
, y
, xx
, y
+ h
);
614 // draw all alive cells
615 dc
.SetPen(*wxBLACK_PEN
);
616 dc
.SetBrush(*wxBLACK_BRUSH
);
620 for (size_t m
= 0; m
< ncells
; m
++)
621 DrawCell(cells
[m
].i
, cells
[m
].j
, dc
);
623 done
= m_life
->FindMore(&cells
, &ncells
);
627 for (size_t m
= 0; m
< ncells
; m
++)
628 DrawCell(cells
[m
].i
, cells
[m
].j
, dc
);
633 void LifeCanvas::OnMouse(wxMouseEvent
& event
)
638 // which cell are we pointing at?
639 wxInt32 i
= XToCell( event
.GetX() );
640 wxInt32 j
= YToCell( event
.GetY() );
642 // set statusbar text
644 msg
.Printf(_("Cell: (%d, %d)"), i
, j
);
645 GET_FRAME()->SetStatusText(msg
, 1);
648 if (!event
.LeftIsDown())
650 m_status
= MOUSE_NOACTION
;
654 // button just pressed?
655 if (m_status
== MOUSE_NOACTION
)
657 // yes, update status and toggle this cell
658 m_status
= (m_life
->IsAlive(i
, j
)? MOUSE_ERASING
: MOUSE_DRAWING
);
662 m_life
->SetCell(i
, j
, m_status
== MOUSE_DRAWING
);
663 DrawCell(i
, j
, m_status
== MOUSE_DRAWING
);
665 else if ((m_mi
!= i
) || (m_mj
!= j
))
667 bool alive
= (m_status
== MOUSE_DRAWING
);
669 // prepare DC and pen + brush to optimize drawing
671 dc
.SetPen(alive
? *wxBLACK_PEN
: *wxWHITE_PEN
);
672 dc
.SetBrush(alive
? *wxBLACK_BRUSH
: *wxWHITE_BRUSH
);
675 // draw a line of cells using Bresenham's algorithm
676 wxInt32 d
, ii
, jj
, di
, ai
, si
, dj
, aj
, sj
;
679 si
= (di
< 0)? -1 : 1;
682 sj
= (dj
< 0)? -1 : 1;
694 m_life
->SetCell(ii
, jj
, alive
);
695 DrawCell(ii
, jj
, dc
);
712 m_life
->SetCell(ii
, jj
, alive
);
713 DrawCell(ii
, jj
, dc
);
725 m_life
->SetCell(ii
, jj
, alive
);
726 DrawCell(ii
, jj
, dc
);
733 GET_FRAME()->UpdateInfoText();
736 void LifeCanvas::OnSize(wxSizeEvent
& event
)
739 wxInt32 cx
= m_viewportX
+ m_viewportW
/ 2;
740 wxInt32 cy
= m_viewportY
+ m_viewportH
/ 2;
743 wxCoord w
= event
.GetSize().GetX();
744 wxCoord h
= event
.GetSize().GetY();
745 m_viewportW
= (w
+ m_cellsize
- 1) / m_cellsize
;
746 m_viewportH
= (h
+ m_cellsize
- 1) / m_cellsize
;
749 m_viewportX
= cx
- m_viewportW
/ 2;
750 m_viewportY
= cy
- m_viewportH
/ 2;
755 SetScrollbar(wxHORIZONTAL
, m_viewportW
, m_viewportW
, 3 * m_viewportW
);
756 SetScrollbar(wxVERTICAL
, m_viewportH
, m_viewportH
, 3 * m_viewportH
);
757 m_thumbX
= m_viewportW
;
758 m_thumbY
= m_viewportH
;
761 // allow default processing
765 void LifeCanvas::OnScroll(wxScrollWinEvent
& event
)
767 WXTYPE type
= event
.GetEventType();
768 int pos
= event
.GetPosition();
769 int orient
= event
.GetOrientation();
771 // calculate scroll increment
775 case wxEVT_SCROLLWIN_TOP
:
777 if (orient
== wxHORIZONTAL
)
778 scrollinc
= -m_viewportW
;
780 scrollinc
= -m_viewportH
;
783 case wxEVT_SCROLLWIN_BOTTOM
:
785 if (orient
== wxHORIZONTAL
)
786 scrollinc
= m_viewportW
;
788 scrollinc
= m_viewportH
;
791 case wxEVT_SCROLLWIN_LINEUP
: scrollinc
= -1; break;
792 case wxEVT_SCROLLWIN_LINEDOWN
: scrollinc
= +1; break;
793 case wxEVT_SCROLLWIN_PAGEUP
: scrollinc
= -10; break;
794 case wxEVT_SCROLLWIN_PAGEDOWN
: scrollinc
= +10; break;
795 case wxEVT_SCROLLWIN_THUMBTRACK
:
797 if (orient
== wxHORIZONTAL
)
799 scrollinc
= pos
- m_thumbX
;
804 scrollinc
= pos
- m_thumbY
;
809 case wxEVT_SCROLLWIN_THUMBRELEASE
:
811 m_thumbX
= m_viewportW
;
812 m_thumbY
= m_viewportH
;
816 #ifdef __WXGTK__ // what about Motif?
817 // wxGTK updates the thumb automatically (wxMSW doesn't); reset it back
818 if (type
!= wxEVT_SCROLLWIN_THUMBTRACK
)
820 SetScrollbar(wxHORIZONTAL
, m_viewportW
, m_viewportW
, 3 * m_viewportW
);
821 SetScrollbar(wxVERTICAL
, m_viewportH
, m_viewportH
, 3 * m_viewportH
);
825 if (scrollinc
== 0) return;
827 // scroll the window and adjust the viewport
828 if (orient
== wxHORIZONTAL
)
830 m_viewportX
+= scrollinc
;
831 ScrollWindow( -m_cellsize
* scrollinc
, 0, (const wxRect
*) NULL
);
835 m_viewportY
+= scrollinc
;
836 ScrollWindow( 0, -m_cellsize
* scrollinc
, (const wxRect
*) NULL
);
840 void LifeCanvas::OnEraseBackground(wxEraseEvent
& WXUNUSED(event
))
842 // do nothing. I just don't want the background to be erased, you know.