Split the sample in three source files + three header files, for improved
[wxWidgets.git] / demos / 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 // --------------------------------------------------------------------------
17 // headers
18 // --------------------------------------------------------------------------
19
20 #ifdef __GNUG__
21 #pragma implementation "life.h"
22 #endif
23
24 // for compilers that support precompilation, includes "wx/wx.h"
25 #include "wx/wxprec.h"
26
27 #ifdef __BORLANDC__
28 #pragma hdrstop
29 #endif
30
31 // for all others, include the necessary headers
32 #ifndef WX_PRECOMP
33 #include "wx/wx.h"
34 #endif
35
36 #include "wx/statline.h"
37
38 #include "life.h"
39 #include "game.h"
40 #include "dialogs.h"
41
42 // --------------------------------------------------------------------------
43 // resources
44 // --------------------------------------------------------------------------
45
46 #if defined(__WXGTK__) || defined(__WXMOTIF__)
47 // the application icon
48 #include "mondrian.xpm"
49
50 // bitmap buttons for the toolbar
51 #include "bitmaps/reset.xpm"
52 #include "bitmaps/play.xpm"
53 #include "bitmaps/stop.xpm"
54 #endif
55
56 // --------------------------------------------------------------------------
57 // constants
58 // --------------------------------------------------------------------------
59
60 // IDs for the controls and the menu commands
61 enum
62 {
63 // menu items and toolbar buttons
64 ID_NEWGAME = 1001,
65 ID_SAMPLES,
66 ID_ABOUT,
67 ID_EXIT,
68 ID_CLEAR,
69 ID_START,
70 ID_STEP,
71 ID_STOP,
72 ID_WRAP,
73
74 // speed selection slider
75 ID_SLIDER
76 };
77
78 // --------------------------------------------------------------------------
79 // event tables and other macros for wxWindows
80 // --------------------------------------------------------------------------
81
82 // Event tables
83 BEGIN_EVENT_TABLE(LifeFrame, wxFrame)
84 EVT_MENU (ID_NEWGAME, LifeFrame::OnNewGame)
85 EVT_MENU (ID_SAMPLES, LifeFrame::OnSamples)
86 EVT_MENU (ID_ABOUT, LifeFrame::OnMenu)
87 EVT_MENU (ID_EXIT, LifeFrame::OnMenu)
88 EVT_MENU (ID_CLEAR, LifeFrame::OnMenu)
89 EVT_MENU (ID_START, LifeFrame::OnMenu)
90 EVT_MENU (ID_STEP, LifeFrame::OnMenu)
91 EVT_MENU (ID_STOP, LifeFrame::OnMenu)
92 EVT_MENU (ID_WRAP, LifeFrame::OnMenu)
93 EVT_COMMAND_SCROLL (ID_SLIDER, LifeFrame::OnSlider)
94 END_EVENT_TABLE()
95
96 BEGIN_EVENT_TABLE(LifeCanvas, wxScrolledWindow)
97 EVT_PAINT ( LifeCanvas::OnPaint)
98 EVT_SIZE ( LifeCanvas::OnSize)
99 EVT_MOUSE_EVENTS ( LifeCanvas::OnMouse)
100 END_EVENT_TABLE()
101
102
103 // Create a new application object
104 IMPLEMENT_APP(LifeApp)
105
106 // ==========================================================================
107 // implementation
108 // ==========================================================================
109
110 // some shortcuts
111 #define ADD_TOOL(a, b, c, d) \
112 toolBar->AddTool(a, b, wxNullBitmap, FALSE, -1, -1, (wxObject *)0, c, d)
113
114 #define GET_FRAME() \
115 ((LifeFrame *) wxGetApp().GetTopWindow())
116
117 // --------------------------------------------------------------------------
118 // LifeApp
119 // --------------------------------------------------------------------------
120
121 // `Main program' equivalent: the program execution "starts" here
122 bool LifeApp::OnInit()
123 {
124 // create the main application window
125 LifeFrame *frame = new LifeFrame();
126
127 // show it and tell the application that it's our main window
128 frame->Show(TRUE);
129 SetTopWindow(frame);
130
131 // enter the main message loop and run the app
132 return TRUE;
133 }
134
135 // --------------------------------------------------------------------------
136 // LifeFrame
137 // --------------------------------------------------------------------------
138
139 // frame constructor
140 LifeFrame::LifeFrame() : wxFrame((wxFrame *)0, -1, _("Life!"), wxPoint(50, 50))
141 {
142 // frame icon
143 SetIcon(wxICON(mondrian));
144
145 // menu bar
146 wxMenu *menuFile = new wxMenu("", wxMENU_TEAROFF);
147 wxMenu *menuGame = new wxMenu("", wxMENU_TEAROFF);
148
149 menuFile->Append(ID_NEWGAME, _("New game..."), _("Start a new game"));
150 menuFile->Append(ID_SAMPLES, _("Sample game..."), _("Select a sample configuration"));
151 menuFile->AppendSeparator();
152 menuFile->Append(ID_ABOUT, _("&About...\tCtrl-A"), _("Show about dialog"));
153 menuFile->AppendSeparator();
154 menuFile->Append(ID_EXIT, _("E&xit\tAlt-X"), _("Quit this program"));
155
156 menuGame->Append(ID_CLEAR, _("&Clear\tCtrl-C"), _("Clear game field"));
157 menuGame->Append(ID_START, _("&Start\tCtrl-S"), _("Start"));
158 menuGame->Append(ID_STEP, _("&Next\tCtrl-N"), _("Single step"));
159 menuGame->Append(ID_STOP, _("S&top\tCtrl-T"), _("Stop"));
160 menuGame->Enable(ID_STOP, FALSE);
161 menuGame->AppendSeparator();
162 menuGame->Append(ID_WRAP, _("&Wraparound\tCtrl-W"), _("Wrap around borders"), TRUE);
163 menuGame->Check (ID_WRAP, TRUE);
164
165 wxMenuBar *menuBar = new wxMenuBar();
166 menuBar->Append(menuFile, _("&File"));
167 menuBar->Append(menuGame, _("&Game"));
168 SetMenuBar(menuBar);
169
170 // tool bar
171 wxBitmap tbBitmaps[3];
172
173 tbBitmaps[0] = wxBITMAP(reset);
174 tbBitmaps[1] = wxBITMAP(play);
175 tbBitmaps[2] = wxBITMAP(stop);
176
177 wxToolBar *toolBar = CreateToolBar();
178 toolBar->SetMargins(5, 5);
179 toolBar->SetToolBitmapSize(wxSize(16, 16));
180 ADD_TOOL(ID_CLEAR, tbBitmaps[0], _("Clear"), _("Clear game board"));
181 ADD_TOOL(ID_START, tbBitmaps[1], _("Start"), _("Start"));
182 ADD_TOOL(ID_STOP , tbBitmaps[2], _("Stop"), _("Stop"));
183 toolBar->EnableTool(ID_STOP, FALSE);
184 toolBar->Realize();
185
186 // status bar
187 CreateStatusBar(2);
188 SetStatusText(_("Welcome to Life!"));
189
190 // game
191 wxPanel *panel = new wxPanel(this, -1);
192 m_life = new Life(20, 20);
193 m_canvas = new LifeCanvas(panel, m_life);
194 m_timer = new LifeTimer();
195 m_interval = 500;
196 m_tics = 0;
197 m_text = new wxStaticText(panel, -1, "");
198 UpdateInfoText();
199
200 // slider
201 wxSlider *slider = new wxSlider(panel, ID_SLIDER,
202 5, 1, 10,
203 wxDefaultPosition,
204 wxSize(200, -1),
205 wxSL_HORIZONTAL | wxSL_AUTOTICKS);
206
207 // component layout
208 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
209 sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE);
210 sizer->Add(m_canvas, 1, wxGROW | wxCENTRE | wxALL, 5);
211 sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE);
212 sizer->Add(m_text, 0, wxCENTRE | wxTOP, 5);
213 sizer->Add(slider, 0, wxCENTRE | wxALL, 5);
214 panel->SetSizer(sizer);
215 panel->SetAutoLayout(TRUE);
216 sizer->Fit(this);
217 sizer->SetSizeHints(this);
218 }
219
220 LifeFrame::~LifeFrame()
221 {
222 delete m_timer;
223 delete m_life;
224 }
225
226 void LifeFrame::UpdateInfoText()
227 {
228 wxString msg;
229
230 msg.Printf(_("Generation: %u, Interval: %u ms"), m_tics, m_interval);
231 m_text->SetLabel(msg);
232 }
233
234 // event handlers
235 void LifeFrame::OnMenu(wxCommandEvent& event)
236 {
237 switch (event.GetId())
238 {
239 case ID_START : OnStart(); break;
240 case ID_STEP : OnTimer(); break;
241 case ID_STOP : OnStop(); break;
242 case ID_WRAP :
243 {
244 bool checked = GetMenuBar()->GetMenu(1)->IsChecked(ID_WRAP);
245 m_life->SetBorderWrap(checked);
246 break;
247 }
248 case ID_CLEAR :
249 {
250 OnStop();
251 m_life->Clear();
252 m_canvas->DrawEverything(TRUE);
253 m_canvas->Refresh(FALSE);
254 m_tics = 0;
255 UpdateInfoText();
256 break;
257 }
258 case ID_ABOUT :
259 {
260 wxMessageBox(
261 _("This is the about dialog of the Life! sample.\n"
262 "(c) 2000 Guillermo Rodriguez Garcia"),
263 _("About Life!"),
264 wxOK | wxICON_INFORMATION,
265 this);
266 break;
267 }
268 case ID_EXIT :
269 {
270 // TRUE is to force the frame to close
271 Close(TRUE);
272 break;
273 }
274 }
275 }
276
277 void LifeFrame::OnNewGame(wxCommandEvent& WXUNUSED(event))
278 {
279 int w = m_life->GetWidth();
280 int h = m_life->GetHeight();
281
282 // stop if it was running
283 OnStop();
284
285 // dialog box
286 LifeNewGameDialog dialog(this, &w, &h);
287
288 // new game?
289 if (dialog.ShowModal() == wxID_OK)
290 {
291 // check dimensions
292 if (w >= LIFE_MIN && w <= LIFE_MAX &&
293 h >= LIFE_MIN && h <= LIFE_MAX)
294 {
295 // resize game field
296 m_life->Destroy();
297 m_life->Create(w, h);
298
299 // tell the canvas
300 m_canvas->Reset();
301 m_canvas->Refresh();
302 m_tics = 0;
303 UpdateInfoText();
304 }
305 else
306 {
307 wxString msg;
308 msg.Printf(_("Both dimensions must be within %u and %u.\n"),
309 LIFE_MIN, LIFE_MAX);
310 wxMessageBox(msg, _("Error!"), wxOK | wxICON_EXCLAMATION, this);
311 }
312 }
313 }
314
315 void LifeFrame::OnSamples(wxCommandEvent& WXUNUSED(event))
316 {
317 // stop if it was running
318 OnStop();
319
320 // dialog box
321 LifeSamplesDialog dialog(this);
322
323 // new game?
324 if (dialog.ShowModal() == wxID_OK)
325 {
326 int result = dialog.GetValue();
327
328 if (result == -1)
329 return;
330
331 int gw = g_shapes[result].m_fieldWidth;
332 int gh = g_shapes[result].m_fieldHeight;
333 int wrap = g_shapes[result].m_wrap;
334
335 // set wraparound (don't ask the user)
336 m_life->SetBorderWrap(wrap);
337 GetMenuBar()->GetMenu(1)->Check(ID_WRAP, wrap);
338
339 // need to resize the game field?
340 if (gw > m_life->GetWidth() || gh > m_life->GetHeight())
341 {
342 wxString s;
343 s.Printf(_("Your game field is too small for this configuration.\n"
344 "It is recommended to resize it to %u x %u. Proceed?\n"),
345 gw, gh);
346
347 if (wxMessageBox(s, _("Question"), wxYES_NO | wxICON_QUESTION, this) == wxYES)
348 {
349 m_life->Destroy();
350 m_life->Create(gw, gh);
351 }
352 }
353
354 // put the shape
355 m_life->SetShape(g_shapes[result]);
356
357 // tell the canvas about the change
358 m_canvas->Reset();
359 m_canvas->Refresh();
360 m_tics = 0;
361 UpdateInfoText();
362 }
363 }
364
365 void LifeFrame::OnStart()
366 {
367 if (!m_running)
368 {
369 GetToolBar()->EnableTool(ID_START, FALSE);
370 GetToolBar()->EnableTool(ID_STOP, TRUE);
371 GetMenuBar()->GetMenu(1)->Enable(ID_START, FALSE);
372 GetMenuBar()->GetMenu(1)->Enable(ID_STEP, FALSE);
373 GetMenuBar()->GetMenu(1)->Enable(ID_STOP, TRUE);
374
375 m_timer->Start(m_interval);
376 m_running = TRUE;
377 }
378 }
379
380 void LifeFrame::OnStop()
381 {
382 if (m_running)
383 {
384 GetToolBar()->EnableTool(ID_START, TRUE);
385 GetToolBar()->EnableTool(ID_STOP, FALSE);
386 GetMenuBar()->GetMenu(1)->Enable(ID_START, TRUE);
387 GetMenuBar()->GetMenu(1)->Enable(ID_STEP, TRUE);
388 GetMenuBar()->GetMenu(1)->Enable(ID_STOP, FALSE);
389
390 m_timer->Stop();
391 m_running = FALSE;
392 }
393 }
394
395 void LifeFrame::OnTimer()
396 {
397 if (m_life->NextTic())
398 m_tics++;
399 else
400 OnStop();
401
402 UpdateInfoText();
403 m_canvas->DrawEverything();
404 m_canvas->Refresh(FALSE);
405 }
406
407 void LifeFrame::OnSlider(wxScrollEvent& event)
408 {
409 m_interval = event.GetPosition() * 100;
410
411 // restart timer if running, to set the new interval
412 if (m_running)
413 {
414 m_timer->Stop();
415 m_timer->Start(m_interval);
416 }
417
418 UpdateInfoText();
419 }
420
421 // --------------------------------------------------------------------------
422 // LifeTimer
423 // --------------------------------------------------------------------------
424
425 void LifeTimer::Notify()
426 {
427 GET_FRAME()->OnTimer();
428 };
429
430 // --------------------------------------------------------------------------
431 // LifeCanvas
432 // --------------------------------------------------------------------------
433
434 // canvas constructor
435 LifeCanvas::LifeCanvas(wxWindow *parent, Life *life, bool interactive)
436 : wxScrolledWindow(parent, -1, wxPoint(0, 0), wxSize(100, 100))
437 {
438 m_life = life;
439 m_interactive = interactive;
440 m_cellsize = 8;
441 m_bmp = NULL;
442 Reset();
443 }
444
445 LifeCanvas::~LifeCanvas()
446 {
447 delete m_bmp;
448 }
449
450 void LifeCanvas::Reset()
451 {
452 if (m_bmp)
453 delete m_bmp;
454
455 m_status = MOUSE_NOACTION;
456 m_width = CellToCoord(m_life->GetWidth()) + 1;
457 m_height = CellToCoord(m_life->GetHeight()) + 1;
458 m_bmp = new wxBitmap(m_width, m_height);
459 wxCoord w = GetClientSize().GetX();
460 wxCoord h = GetClientSize().GetY();
461 m_xoffset = (w > m_width)? ((w - m_width) / 2) : 0;
462 m_yoffset = (h > m_height)? ((h - m_height) / 2) : 0;
463
464 // redraw everything
465 DrawEverything(TRUE);
466 SetScrollbars(10, 10, (m_width + 9) / 10, (m_height + 9) / 10);
467 }
468
469 void LifeCanvas::DrawEverything(bool force)
470 {
471 wxMemoryDC dc;
472
473 dc.SelectObject(*m_bmp);
474 dc.BeginDrawing();
475
476 // draw cells
477 const CellArray *cells =
478 force? m_life->GetCells() : m_life->GetChangedCells();
479
480 for (unsigned i = 0; i < cells->GetCount(); i++)
481 DrawCell(cells->Item(i), dc);
482
483 // bounding rectangle (always drawn - better than clipping region)
484 dc.SetPen(*wxBLACK_PEN);
485 dc.SetBrush(*wxTRANSPARENT_BRUSH);
486 dc.DrawRectangle(0, 0, m_width, m_height);
487
488 dc.EndDrawing();
489 dc.SelectObject(wxNullBitmap);
490 }
491
492 void LifeCanvas::DrawCell(Cell c)
493 {
494 wxMemoryDC dc;
495
496 dc.SelectObject(*m_bmp);
497 dc.BeginDrawing();
498
499 dc.SetClippingRegion(1, 1, m_width - 2, m_height - 2);
500 DrawCell(c, dc);
501
502 dc.EndDrawing();
503 dc.SelectObject(wxNullBitmap);
504 }
505
506 void LifeCanvas::DrawCell(Cell c, wxDC &dc)
507 {
508 if (m_life->IsAlive(c))
509 {
510 dc.SetPen(*wxBLACK_PEN);
511 dc.SetBrush(*wxBLACK_BRUSH);
512 dc.DrawRectangle(CellToCoord( m_life->GetX(c) ),
513 CellToCoord( m_life->GetY(c) ),
514 m_cellsize,
515 m_cellsize);
516 }
517 else
518 {
519 dc.SetPen(*wxLIGHT_GREY_PEN);
520 dc.SetBrush(*wxTRANSPARENT_BRUSH);
521 dc.DrawRectangle(CellToCoord( m_life->GetX(c) ),
522 CellToCoord( m_life->GetY(c) ),
523 m_cellsize,
524 m_cellsize);
525 dc.SetPen(*wxWHITE_PEN);
526 dc.SetBrush(*wxWHITE_BRUSH);
527 dc.DrawRectangle(CellToCoord( m_life->GetX(c) ) + 1,
528 CellToCoord( m_life->GetY(c) ) + 1,
529 m_cellsize - 1,
530 m_cellsize - 1);
531 }
532 }
533
534 // event handlers
535 void LifeCanvas::OnPaint(wxPaintEvent& event)
536 {
537 wxPaintDC dc(this);
538 wxMemoryDC memdc;
539
540 wxRegionIterator upd(GetUpdateRegion());
541 wxCoord x, y, w, h, xx, yy;
542
543 dc.BeginDrawing();
544 memdc.SelectObject(*m_bmp);
545
546 while(upd)
547 {
548 x = upd.GetX();
549 y = upd.GetY();
550 w = upd.GetW();
551 h = upd.GetH();
552 CalcUnscrolledPosition(x, y, &xx, &yy);
553
554 dc.Blit(x, y, w, h, &memdc, xx - m_xoffset, yy - m_yoffset);
555 upd++;
556 }
557
558 memdc.SelectObject(wxNullBitmap);
559 dc.EndDrawing();
560 }
561
562 void LifeCanvas::OnMouse(wxMouseEvent& event)
563 {
564 if (!m_interactive)
565 return;
566
567 int x, y, xx, yy, i, j;
568
569 // which cell are we pointing at?
570 x = event.GetX();
571 y = event.GetY();
572 CalcUnscrolledPosition(x, y, &xx, &yy);
573 i = CoordToCell( xx - m_xoffset );
574 j = CoordToCell( yy - m_yoffset );
575
576 // adjust x, y to point to the upper left corner of the cell
577 CalcScrolledPosition( CellToCoord(i) + m_xoffset,
578 CellToCoord(j) + m_yoffset,
579 &x, &y );
580
581 // set cursor shape and statusbar text
582 if (i < 0 || i >= m_life->GetWidth() ||
583 j < 0 || j >= m_life->GetHeight())
584 {
585 GET_FRAME()->SetStatusText(wxEmptyString, 1);
586 SetCursor(*wxSTANDARD_CURSOR);
587 }
588 else
589 {
590 wxString msg;
591 msg.Printf(_("Cell: (%u, %u)"), i, j);
592 GET_FRAME()->SetStatusText(msg, 1);
593 SetCursor(*wxCROSS_CURSOR);
594 }
595
596 // button pressed?
597 if (!event.LeftIsDown())
598 {
599 m_status = MOUSE_NOACTION;
600 }
601 else if (i >= 0 && i < m_life->GetWidth() &&
602 j >= 0 && j < m_life->GetHeight())
603 {
604 bool alive = m_life->IsAlive(i, j);
605
606 // if just pressed, update status
607 if (m_status == MOUSE_NOACTION)
608 m_status = (alive? MOUSE_ERASING : MOUSE_DRAWING);
609
610 // toggle cell and refresh if needed
611 if (((m_status == MOUSE_ERASING) && alive) ||
612 ((m_status == MOUSE_DRAWING) && !alive))
613 {
614 wxRect rect(x, y, m_cellsize + 1, m_cellsize + 1);
615 DrawCell( m_life->SetCell(i, j, !alive) );
616 Refresh(FALSE, &rect);
617 }
618 }
619 }
620
621 void LifeCanvas::OnSize(wxSizeEvent& event)
622 {
623 wxCoord w = event.GetSize().GetX();
624 wxCoord h = event.GetSize().GetY();
625 m_xoffset = (w > m_width)? ((w - m_width) / 2) : 0;
626 m_yoffset = (h > m_height)? ((h - m_height) / 2) : 0;
627
628 // allow default processing
629 event.Skip();
630 }