]> git.saurik.com Git - wxWidgets.git/blame - demos/life/life.cpp
small compilation fix for !wxUSE_SOCKETS
[wxWidgets.git] / demos / life / life.cpp
CommitLineData
5a1dca12
GRG
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
5a1dca12
GRG
16// --------------------------------------------------------------------------
17// headers
18// --------------------------------------------------------------------------
19
20#ifdef __GNUG__
2480be69 21 #pragma implementation "life.h"
5a1dca12
GRG
22#endif
23
2480be69 24// for compilers that support precompilation, includes "wx/wx.h"
5a1dca12
GRG
25#include "wx/wxprec.h"
26
27#ifdef __BORLANDC__
28 #pragma hdrstop
29#endif
30
ecbdd409 31// for all others, include the necessary headers
5a1dca12
GRG
32#ifndef WX_PRECOMP
33 #include "wx/wx.h"
34#endif
35
36#include "wx/statline.h"
2480be69
GRG
37
38#include "life.h"
39#include "game.h"
40#include "dialogs.h"
5a1dca12
GRG
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
5a1dca12
GRG
56// --------------------------------------------------------------------------
57// constants
58// --------------------------------------------------------------------------
59
60// IDs for the controls and the menu commands
61enum
62{
63 // menu items and toolbar buttons
a36f0f83 64 ID_NEWGAME = 1001,
087e4f4a
GRG
65 ID_SAMPLES,
66 ID_ABOUT,
67 ID_EXIT,
5a1dca12
GRG
68 ID_CLEAR,
69 ID_START,
087e4f4a 70 ID_STEP,
5a1dca12 71 ID_STOP,
087e4f4a 72 ID_WRAP,
5a1dca12 73
087e4f4a 74 // speed selection slider
2480be69 75 ID_SLIDER
5a1dca12
GRG
76};
77
78// --------------------------------------------------------------------------
79// event tables and other macros for wxWindows
80// --------------------------------------------------------------------------
81
82// Event tables
5a1dca12 83BEGIN_EVENT_TABLE(LifeFrame, wxFrame)
a36f0f83 84 EVT_MENU (ID_NEWGAME, LifeFrame::OnNewGame)
087e4f4a
GRG
85 EVT_MENU (ID_SAMPLES, LifeFrame::OnSamples)
86 EVT_MENU (ID_ABOUT, LifeFrame::OnMenu)
87 EVT_MENU (ID_EXIT, LifeFrame::OnMenu)
a36f0f83
GRG
88 EVT_MENU (ID_CLEAR, LifeFrame::OnMenu)
89 EVT_MENU (ID_START, LifeFrame::OnMenu)
087e4f4a 90 EVT_MENU (ID_STEP, LifeFrame::OnMenu)
a36f0f83 91 EVT_MENU (ID_STOP, LifeFrame::OnMenu)
087e4f4a 92 EVT_MENU (ID_WRAP, LifeFrame::OnMenu)
a36f0f83 93 EVT_COMMAND_SCROLL (ID_SLIDER, LifeFrame::OnSlider)
ecbdd409 94END_EVENT_TABLE()
5a1dca12
GRG
95
96BEGIN_EVENT_TABLE(LifeCanvas, wxScrolledWindow)
a36f0f83
GRG
97 EVT_PAINT ( LifeCanvas::OnPaint)
98 EVT_SIZE ( LifeCanvas::OnSize)
99 EVT_MOUSE_EVENTS ( LifeCanvas::OnMouse)
ecbdd409 100END_EVENT_TABLE()
5a1dca12 101
5a1dca12
GRG
102
103// Create a new application object
104IMPLEMENT_APP(LifeApp)
105
106// ==========================================================================
107// implementation
108// ==========================================================================
109
2480be69
GRG
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
5a1dca12
GRG
117// --------------------------------------------------------------------------
118// LifeApp
119// --------------------------------------------------------------------------
120
121// `Main program' equivalent: the program execution "starts" here
122bool 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
140LifeFrame::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);
087e4f4a 147 wxMenu *menuGame = new wxMenu("", wxMENU_TEAROFF);
5a1dca12 148
087e4f4a
GRG
149 menuFile->Append(ID_NEWGAME, _("New game..."), _("Start a new game"));
150 menuFile->Append(ID_SAMPLES, _("Sample game..."), _("Select a sample configuration"));
5a1dca12
GRG
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"));
087e4f4a
GRG
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
5a1dca12
GRG
165 wxMenuBar *menuBar = new wxMenuBar();
166 menuBar->Append(menuFile, _("&File"));
087e4f4a 167 menuBar->Append(menuGame, _("&Game"));
5a1dca12
GRG
168 SetMenuBar(menuBar);
169
170 // tool bar
171 wxBitmap tbBitmaps[3];
471ed537 172
5a1dca12
GRG
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
5a1dca12 190 // game
2480be69
GRG
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, "");
5a1dca12
GRG
198 UpdateInfoText();
199
200 // slider
2480be69
GRG
201 wxSlider *slider = new wxSlider(panel, ID_SLIDER,
202 5, 1, 10,
203 wxDefaultPosition,
204 wxSize(200, -1),
205 wxSL_HORIZONTAL | wxSL_AUTOTICKS);
5a1dca12
GRG
206
207 // component layout
208 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
087e4f4a 209 sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE);
5a1dca12
GRG
210 sizer->Add(m_canvas, 1, wxGROW | wxCENTRE | wxALL, 5);
211 sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE);
087e4f4a 212 sizer->Add(m_text, 0, wxCENTRE | wxTOP, 5);
5a1dca12
GRG
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
220LifeFrame::~LifeFrame()
221{
222 delete m_timer;
223 delete m_life;
224}
225
226void 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
235void LifeFrame::OnMenu(wxCommandEvent& event)
236{
237 switch (event.GetId())
238 {
239 case ID_START : OnStart(); break;
087e4f4a 240 case ID_STEP : OnTimer(); break;
5a1dca12 241 case ID_STOP : OnStop(); break;
087e4f4a
GRG
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 :
5a1dca12
GRG
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
a36f0f83 277void LifeFrame::OnNewGame(wxCommandEvent& WXUNUSED(event))
5a1dca12
GRG
278{
279 int w = m_life->GetWidth();
280 int h = m_life->GetHeight();
5a1dca12
GRG
281
282 // stop if it was running
283 OnStop();
284
471ed537 285 // dialog box
5a1dca12 286 LifeNewGameDialog dialog(this, &w, &h);
5a1dca12 287
087e4f4a 288 // new game?
471ed537 289 if (dialog.ShowModal() == wxID_OK)
5a1dca12
GRG
290 {
291 // check dimensions
292 if (w >= LIFE_MIN && w <= LIFE_MAX &&
293 h >= LIFE_MIN && h <= LIFE_MAX)
294 {
087e4f4a 295 // resize game field
5a1dca12
GRG
296 m_life->Destroy();
297 m_life->Create(w, h);
087e4f4a
GRG
298
299 // tell the canvas
5a1dca12 300 m_canvas->Reset();
087e4f4a 301 m_canvas->Refresh();
5a1dca12
GRG
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
087e4f4a
GRG
315void LifeFrame::OnSamples(wxCommandEvent& WXUNUSED(event))
316{
317 // stop if it was running
318 OnStop();
319
471ed537 320 // dialog box
087e4f4a
GRG
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
5a1dca12
GRG
365void LifeFrame::OnStart()
366{
087e4f4a
GRG
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);
5a1dca12 374
087e4f4a
GRG
375 m_timer->Start(m_interval);
376 m_running = TRUE;
377 }
5a1dca12
GRG
378}
379
380void LifeFrame::OnStop()
381{
087e4f4a
GRG
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 }
5a1dca12
GRG
393}
394
395void LifeFrame::OnTimer()
396{
a36f0f83
GRG
397 if (m_life->NextTic())
398 m_tics++;
399 else
400 OnStop();
5a1dca12 401
a36f0f83 402 UpdateInfoText();
5a1dca12
GRG
403 m_canvas->DrawEverything();
404 m_canvas->Refresh(FALSE);
405}
406
a36f0f83
GRG
407void 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
5a1dca12
GRG
421// --------------------------------------------------------------------------
422// LifeTimer
423// --------------------------------------------------------------------------
424
5a1dca12
GRG
425void LifeTimer::Notify()
426{
a36f0f83 427 GET_FRAME()->OnTimer();
087e4f4a 428};
5a1dca12
GRG
429
430// --------------------------------------------------------------------------
087e4f4a 431// LifeCanvas
5a1dca12
GRG
432// --------------------------------------------------------------------------
433
434// canvas constructor
087e4f4a 435LifeCanvas::LifeCanvas(wxWindow *parent, Life *life, bool interactive)
5a1dca12
GRG
436 : wxScrolledWindow(parent, -1, wxPoint(0, 0), wxSize(100, 100))
437{
087e4f4a
GRG
438 m_life = life;
439 m_interactive = interactive;
440 m_cellsize = 8;
441 m_bmp = NULL;
5a1dca12
GRG
442 Reset();
443}
444
445LifeCanvas::~LifeCanvas()
446{
447 delete m_bmp;
448}
449
450void 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);
087e4f4a
GRG
459 wxCoord w = GetClientSize().GetX();
460 wxCoord h = GetClientSize().GetY();
5a1dca12
GRG
461 m_xoffset = (w > m_width)? ((w - m_width) / 2) : 0;
462 m_yoffset = (h > m_height)? ((h - m_height) / 2) : 0;
463
087e4f4a 464 // redraw everything
5a1dca12
GRG
465 DrawEverything(TRUE);
466 SetScrollbars(10, 10, (m_width + 9) / 10, (m_height + 9) / 10);
467}
468
5a1dca12
GRG
469void LifeCanvas::DrawEverything(bool force)
470{
471 wxMemoryDC dc;
472
473 dc.SelectObject(*m_bmp);
474 dc.BeginDrawing();
475
087e4f4a 476 // draw cells
2480be69
GRG
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);
5a1dca12 482
087e4f4a 483 // bounding rectangle (always drawn - better than clipping region)
5a1dca12
GRG
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
2480be69 492void LifeCanvas::DrawCell(Cell c)
5a1dca12
GRG
493{
494 wxMemoryDC dc;
495
496 dc.SelectObject(*m_bmp);
497 dc.BeginDrawing();
498
087e4f4a 499 dc.SetClippingRegion(1, 1, m_width - 2, m_height - 2);
2480be69 500 DrawCell(c, dc);
5a1dca12
GRG
501
502 dc.EndDrawing();
503 dc.SelectObject(wxNullBitmap);
504}
505
2480be69 506void LifeCanvas::DrawCell(Cell c, wxDC &dc)
5a1dca12 507{
2480be69 508 if (m_life->IsAlive(c))
5a1dca12
GRG
509 {
510 dc.SetPen(*wxBLACK_PEN);
511 dc.SetBrush(*wxBLACK_BRUSH);
2480be69
GRG
512 dc.DrawRectangle(CellToCoord( m_life->GetX(c) ),
513 CellToCoord( m_life->GetY(c) ),
5a1dca12
GRG
514 m_cellsize,
515 m_cellsize);
516 }
517 else
518 {
519 dc.SetPen(*wxLIGHT_GREY_PEN);
520 dc.SetBrush(*wxTRANSPARENT_BRUSH);
2480be69
GRG
521 dc.DrawRectangle(CellToCoord( m_life->GetX(c) ),
522 CellToCoord( m_life->GetY(c) ),
5a1dca12
GRG
523 m_cellsize,
524 m_cellsize);
525 dc.SetPen(*wxWHITE_PEN);
526 dc.SetBrush(*wxWHITE_BRUSH);
2480be69
GRG
527 dc.DrawRectangle(CellToCoord( m_life->GetX(c) ) + 1,
528 CellToCoord( m_life->GetY(c) ) + 1,
5a1dca12
GRG
529 m_cellsize - 1,
530 m_cellsize - 1);
531 }
532}
ecbdd409 533
5a1dca12
GRG
534// event handlers
535void LifeCanvas::OnPaint(wxPaintEvent& event)
536{
537 wxPaintDC dc(this);
538 wxMemoryDC memdc;
539
540 wxRegionIterator upd(GetUpdateRegion());
087e4f4a 541 wxCoord x, y, w, h, xx, yy;
5a1dca12
GRG
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
ecbdd409 554 dc.Blit(x, y, w, h, &memdc, xx - m_xoffset, yy - m_yoffset);
5a1dca12
GRG
555 upd++;
556 }
557
558 memdc.SelectObject(wxNullBitmap);
559 dc.EndDrawing();
560}
561
562void LifeCanvas::OnMouse(wxMouseEvent& event)
563{
087e4f4a
GRG
564 if (!m_interactive)
565 return;
566
5a1dca12
GRG
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);
2480be69 615 DrawCell( m_life->SetCell(i, j, !alive) );
5a1dca12
GRG
616 Refresh(FALSE, &rect);
617 }
618 }
619}
620
621void 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}