]>
Commit | Line | Data |
---|---|---|
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 | ||
16 | // minimum and maximum table size, in each dimension | |
17 | #define LIFE_MIN 10 | |
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 | ((wxFrame *) 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 | // private classes | |
67 | // -------------------------------------------------------------------------- | |
68 | ||
69 | class Life; | |
70 | class LifeCanvas; | |
71 | class LifeTimer; | |
72 | class LifeFrame; | |
73 | class LifeApp; | |
74 | ||
75 | ||
76 | // Life | |
77 | class Life | |
78 | { | |
79 | public: | |
80 | // ctors and dtors | |
81 | Life(int width, int height); | |
82 | ~Life(); | |
83 | void Create(int width, int height); | |
84 | void Destroy(); | |
85 | ||
86 | // public accessors | |
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); | |
92 | ||
93 | // game operations | |
94 | void Clear(); | |
95 | void NextTic(); | |
96 | ||
97 | private: | |
98 | enum CellFlags { | |
99 | CELL_DEAD = 0x0000, // is dead | |
100 | CELL_ALIVE = 0x0001, // is alive | |
101 | CELL_MARK = 0x0002, // will change / has changed | |
102 | }; | |
103 | typedef int Cell; | |
104 | ||
105 | int GetNeighbors(int x, int y) const; | |
106 | inline void SetCell(int x, int y, Cell status); | |
107 | ||
108 | int m_width; | |
109 | int m_height; | |
110 | Cell *m_cells; | |
111 | }; | |
112 | ||
113 | // Life canvas | |
114 | class LifeCanvas : public wxScrolledWindow | |
115 | { | |
116 | public: | |
117 | // ctor and dtor | |
118 | LifeCanvas(wxWindow* parent, Life* life); | |
119 | ~LifeCanvas(); | |
120 | ||
121 | // member functions | |
122 | void Reset(); | |
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); }; | |
128 | ||
129 | // event handlers | |
130 | void OnPaint(wxPaintEvent& event); | |
131 | void OnMouse(wxMouseEvent& event); | |
132 | void OnSize(wxSizeEvent& event); | |
133 | ||
134 | private: | |
135 | // any class wishing to process wxWindows events must use this macro | |
136 | DECLARE_EVENT_TABLE() | |
137 | ||
138 | enum MouseStatus { | |
139 | MOUSE_NOACTION, | |
140 | MOUSE_DRAWING, | |
141 | MOUSE_ERASING | |
142 | }; | |
143 | ||
144 | Life *m_life; | |
145 | wxFrame *m_frame; | |
146 | wxBitmap *m_bmp; | |
147 | int m_height; | |
148 | int m_width; | |
149 | int m_cellsize; | |
150 | wxCoord m_xoffset; | |
151 | wxCoord m_yoffset; | |
152 | MouseStatus m_status; | |
153 | }; | |
154 | ||
155 | // Life timer | |
156 | class LifeTimer : public wxTimer | |
157 | { | |
158 | public: | |
159 | LifeTimer(LifeFrame *parent); | |
160 | void Notify(); | |
161 | ||
162 | private: | |
163 | LifeFrame *m_parent; | |
164 | }; | |
165 | ||
166 | // Life main frame | |
167 | class LifeFrame : public wxFrame | |
168 | { | |
169 | public: | |
170 | // ctor and dtor | |
171 | LifeFrame(); | |
172 | ~LifeFrame(); | |
173 | ||
174 | // member functions | |
175 | void UpdateInfoText(); | |
176 | ||
177 | // event handlers | |
178 | void OnMenu(wxCommandEvent& event); | |
179 | void OnSlider(wxScrollEvent& event); | |
180 | void OnNewGame(); | |
181 | void OnStart(); | |
182 | void OnStop(); | |
183 | void OnTimer(); | |
184 | ||
185 | private: | |
186 | // any class wishing to process wxWindows events must use this macro | |
187 | DECLARE_EVENT_TABLE() | |
188 | ||
189 | Life *m_life; | |
190 | LifeTimer *m_timer; | |
191 | LifeCanvas *m_canvas; | |
192 | wxStaticText *m_text; | |
193 | bool m_running; | |
194 | long m_interval; | |
195 | long m_tics; | |
196 | }; | |
197 | ||
198 | // Life new game dialog | |
199 | class LifeNewGameDialog : public wxDialog | |
200 | { | |
201 | public: | |
202 | // ctor | |
203 | LifeNewGameDialog(wxWindow *parent, int *w, int *h); | |
204 | ||
205 | // event handlers | |
206 | void OnOK(wxCommandEvent& event); | |
207 | void OnCancel(wxCommandEvent& event); | |
208 | ||
209 | private: | |
210 | // any class wishing to process wxWindows events must use this macro | |
211 | DECLARE_EVENT_TABLE(); | |
212 | ||
213 | int *m_w; | |
214 | int *m_h; | |
215 | wxSpinCtrl *m_spinctrlw; | |
216 | wxSpinCtrl *m_spinctrlh; | |
217 | }; | |
218 | ||
219 | // Life app | |
220 | class LifeApp : public wxApp | |
221 | { | |
222 | public: | |
223 | virtual bool OnInit(); | |
224 | }; | |
225 | ||
226 | ||
227 | // -------------------------------------------------------------------------- | |
228 | // constants | |
229 | // -------------------------------------------------------------------------- | |
230 | ||
231 | // IDs for the controls and the menu commands | |
232 | enum | |
233 | { | |
234 | // menu items and toolbar buttons | |
235 | ID_NEWGAME = 101, | |
236 | ID_CLEAR, | |
237 | ID_START, | |
238 | ID_STOP, | |
239 | ID_ABOUT, | |
240 | ID_EXIT, | |
241 | ||
242 | // slider | |
243 | ID_SLIDER | |
244 | }; | |
245 | ||
246 | // -------------------------------------------------------------------------- | |
247 | // event tables and other macros for wxWindows | |
248 | // -------------------------------------------------------------------------- | |
249 | ||
250 | // Event tables | |
251 | ||
252 | BEGIN_EVENT_TABLE(LifeFrame, wxFrame) | |
253 | EVT_MENU_RANGE (ID_NEWGAME, ID_ABOUT, LifeFrame::OnMenu) | |
254 | EVT_COMMAND_SCROLL (ID_SLIDER, LifeFrame::OnSlider) | |
255 | END_EVENT_TABLE() | |
256 | ||
257 | BEGIN_EVENT_TABLE(LifeCanvas, wxScrolledWindow) | |
258 | EVT_PAINT ( LifeCanvas::OnPaint) | |
259 | EVT_SIZE ( LifeCanvas::OnSize) | |
260 | EVT_MOUSE_EVENTS ( LifeCanvas::OnMouse) | |
261 | END_EVENT_TABLE() | |
262 | ||
263 | BEGIN_EVENT_TABLE(LifeNewGameDialog, wxDialog) | |
264 | EVT_BUTTON (wxID_OK, LifeNewGameDialog::OnOK) | |
265 | EVT_BUTTON (wxID_CANCEL, LifeNewGameDialog::OnCancel) | |
266 | END_EVENT_TABLE() | |
267 | ||
268 | ||
269 | // Create a new application object | |
270 | IMPLEMENT_APP(LifeApp) | |
271 | ||
272 | // ========================================================================== | |
273 | // implementation | |
274 | // ========================================================================== | |
275 | ||
276 | // -------------------------------------------------------------------------- | |
277 | // LifeApp | |
278 | // -------------------------------------------------------------------------- | |
279 | ||
280 | // `Main program' equivalent: the program execution "starts" here | |
281 | bool LifeApp::OnInit() | |
282 | { | |
283 | // create the main application window | |
284 | LifeFrame *frame = new LifeFrame(); | |
285 | ||
286 | // show it and tell the application that it's our main window | |
287 | frame->Show(TRUE); | |
288 | SetTopWindow(frame); | |
289 | ||
290 | // enter the main message loop and run the app | |
291 | return TRUE; | |
292 | } | |
293 | ||
294 | // -------------------------------------------------------------------------- | |
295 | // LifeFrame | |
296 | // -------------------------------------------------------------------------- | |
297 | ||
298 | // frame constructor | |
299 | LifeFrame::LifeFrame() : wxFrame((wxFrame *)0, -1, _("Life!"), wxPoint(50, 50)) | |
300 | { | |
301 | // frame icon | |
302 | SetIcon(wxICON(mondrian)); | |
303 | ||
304 | // menu bar | |
305 | wxMenu *menuFile = new wxMenu("", wxMENU_TEAROFF); | |
306 | ||
307 | menuFile->Append(ID_NEWGAME, _("&New game...\tCtrl-N"), _("Start a new game")); | |
308 | menuFile->Append(ID_CLEAR, _("&Clear\tCtrl-C"), _("Clear game board")); | |
309 | menuFile->Append(ID_START, _("&Start\tCtrl-S"), _("Start")); | |
310 | menuFile->Append(ID_STOP, _("S&top\tCtrl-T"), _("Stop")); | |
311 | menuFile->AppendSeparator(); | |
312 | menuFile->Append(ID_ABOUT, _("&About...\tCtrl-A"), _("Show about dialog")); | |
313 | menuFile->AppendSeparator(); | |
314 | menuFile->Append(ID_EXIT, _("E&xit\tAlt-X"), _("Quit this program")); | |
315 | menuFile->Enable(ID_STOP, FALSE); | |
316 | ||
317 | wxMenuBar *menuBar = new wxMenuBar(); | |
318 | menuBar->Append(menuFile, _("&File")); | |
319 | SetMenuBar(menuBar); | |
320 | ||
321 | // tool bar | |
322 | wxBitmap tbBitmaps[3]; | |
323 | tbBitmaps[0] = wxBITMAP(reset); | |
324 | tbBitmaps[1] = wxBITMAP(play); | |
325 | tbBitmaps[2] = wxBITMAP(stop); | |
326 | ||
327 | wxToolBar *toolBar = CreateToolBar(); | |
328 | toolBar->SetMargins(5, 5); | |
329 | toolBar->SetToolBitmapSize(wxSize(16, 16)); | |
330 | ADD_TOOL(ID_CLEAR, tbBitmaps[0], _("Clear"), _("Clear game board")); | |
331 | ADD_TOOL(ID_START, tbBitmaps[1], _("Start"), _("Start")); | |
332 | ADD_TOOL(ID_STOP , tbBitmaps[2], _("Stop"), _("Stop")); | |
333 | toolBar->EnableTool(ID_STOP, FALSE); | |
334 | toolBar->Realize(); | |
335 | ||
336 | // status bar | |
337 | CreateStatusBar(2); | |
338 | SetStatusText(_("Welcome to Life!")); | |
339 | ||
340 | // panel | |
341 | wxPanel *panel = new wxPanel(this, -1); | |
342 | ||
343 | // game | |
344 | m_life = new Life(20, 20); | |
345 | m_canvas = new LifeCanvas(panel, m_life); | |
346 | m_timer = new LifeTimer(this); | |
347 | m_interval = 500; | |
348 | m_tics = 0; | |
349 | m_text = new wxStaticText(panel, -1, ""); | |
350 | UpdateInfoText(); | |
351 | ||
352 | // slider | |
353 | wxSlider *slider = new wxSlider(panel, ID_SLIDER, 5, 1, 10, | |
354 | wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL | wxSL_AUTOTICKS); | |
355 | ||
356 | // component layout | |
357 | wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL); | |
358 | sizer->Add(m_canvas, 1, wxGROW | wxCENTRE | wxALL, 5); | |
359 | sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE); | |
360 | sizer->Add(m_text, 0, wxCENTRE | wxNORTH, 5); | |
361 | sizer->Add(slider, 0, wxCENTRE | wxALL, 5); | |
362 | panel->SetSizer(sizer); | |
363 | panel->SetAutoLayout(TRUE); | |
364 | sizer->Fit(this); | |
365 | sizer->SetSizeHints(this); | |
366 | } | |
367 | ||
368 | LifeFrame::~LifeFrame() | |
369 | { | |
370 | delete m_timer; | |
371 | delete m_life; | |
372 | } | |
373 | ||
374 | void LifeFrame::UpdateInfoText() | |
375 | { | |
376 | wxString msg; | |
377 | ||
378 | msg.Printf(_("Generation: %u, Interval: %u ms"), m_tics, m_interval); | |
379 | m_text->SetLabel(msg); | |
380 | } | |
381 | ||
382 | // event handlers | |
383 | void LifeFrame::OnMenu(wxCommandEvent& event) | |
384 | { | |
385 | switch (event.GetId()) | |
386 | { | |
387 | case ID_START : OnStart(); break; | |
388 | case ID_STOP : OnStop(); break; | |
389 | case ID_NEWGAME : OnNewGame(); break; | |
390 | case ID_CLEAR : | |
391 | { | |
392 | OnStop(); | |
393 | m_life->Clear(); | |
394 | m_canvas->DrawEverything(TRUE); | |
395 | m_canvas->Refresh(FALSE); | |
396 | m_tics = 0; | |
397 | UpdateInfoText(); | |
398 | break; | |
399 | } | |
400 | case ID_ABOUT : | |
401 | { | |
402 | wxMessageBox( | |
403 | _("This is the about dialog of the Life! sample.\n" | |
404 | "(c) 2000 Guillermo Rodriguez Garcia"), | |
405 | _("About Life!"), | |
406 | wxOK | wxICON_INFORMATION, | |
407 | this); | |
408 | break; | |
409 | } | |
410 | case ID_EXIT : | |
411 | { | |
412 | // TRUE is to force the frame to close | |
413 | Close(TRUE); | |
414 | break; | |
415 | } | |
416 | } | |
417 | } | |
418 | ||
419 | void LifeFrame::OnSlider(wxScrollEvent& event) | |
420 | { | |
421 | m_interval = event.GetPosition() * 100; | |
422 | ||
423 | // restart timer if running, to set the new interval | |
424 | if (m_running) | |
425 | { | |
426 | m_timer->Stop(); | |
427 | m_timer->Start(m_interval); | |
428 | } | |
429 | ||
430 | UpdateInfoText(); | |
431 | } | |
432 | ||
433 | void LifeFrame::OnNewGame() | |
434 | { | |
435 | int w = m_life->GetWidth(); | |
436 | int h = m_life->GetHeight(); | |
437 | int result; | |
438 | ||
439 | // stop if it was running | |
440 | OnStop(); | |
441 | ||
442 | // show dialog box | |
443 | LifeNewGameDialog dialog(this, &w, &h); | |
444 | result = dialog.ShowModal(); | |
445 | ||
446 | // create new game | |
447 | if (result == wxID_OK) | |
448 | { | |
449 | // check dimensions | |
450 | if (w >= LIFE_MIN && w <= LIFE_MAX && | |
451 | h >= LIFE_MIN && h <= LIFE_MAX) | |
452 | { | |
453 | m_life->Destroy(); | |
454 | m_life->Create(w, h); | |
455 | m_canvas->Reset(); | |
456 | m_tics = 0; | |
457 | UpdateInfoText(); | |
458 | } | |
459 | else | |
460 | { | |
461 | wxString msg; | |
462 | msg.Printf(_("Both dimensions must be within %u and %u.\n"), | |
463 | LIFE_MIN, LIFE_MAX); | |
464 | wxMessageBox(msg, _("Error!"), wxOK | wxICON_EXCLAMATION, this); | |
465 | } | |
466 | } | |
467 | } | |
468 | ||
469 | void LifeFrame::OnStart() | |
470 | { | |
471 | GetToolBar()->EnableTool(ID_START, FALSE); | |
472 | GetToolBar()->EnableTool(ID_STOP, TRUE); | |
473 | GetMenuBar()->GetMenu(0)->Enable(ID_START, FALSE); | |
474 | GetMenuBar()->GetMenu(0)->Enable(ID_STOP, TRUE); | |
475 | ||
476 | m_timer->Start(m_interval); | |
477 | m_running = TRUE; | |
478 | } | |
479 | ||
480 | void LifeFrame::OnStop() | |
481 | { | |
482 | GetToolBar()->EnableTool(ID_START, TRUE); | |
483 | GetToolBar()->EnableTool(ID_STOP, FALSE); | |
484 | GetMenuBar()->GetMenu(0)->Enable(ID_START, TRUE); | |
485 | GetMenuBar()->GetMenu(0)->Enable(ID_STOP, FALSE); | |
486 | ||
487 | m_timer->Stop(); | |
488 | m_running = FALSE; | |
489 | } | |
490 | ||
491 | void LifeFrame::OnTimer() | |
492 | { | |
493 | m_tics++; | |
494 | UpdateInfoText(); | |
495 | ||
496 | m_life->NextTic(); | |
497 | m_canvas->DrawEverything(); | |
498 | m_canvas->Refresh(FALSE); | |
499 | } | |
500 | ||
501 | // -------------------------------------------------------------------------- | |
502 | // LifeTimer | |
503 | // -------------------------------------------------------------------------- | |
504 | ||
505 | LifeTimer::LifeTimer(LifeFrame *parent) : wxTimer() | |
506 | { | |
507 | m_parent = parent; | |
508 | } | |
509 | ||
510 | void LifeTimer::Notify() | |
511 | { | |
512 | m_parent->OnTimer(); | |
513 | } | |
514 | ||
515 | // -------------------------------------------------------------------------- | |
516 | // LifeCavas | |
517 | // -------------------------------------------------------------------------- | |
518 | ||
519 | // canvas constructor | |
520 | LifeCanvas::LifeCanvas(wxWindow *parent, Life *life) | |
521 | : wxScrolledWindow(parent, -1, wxPoint(0, 0), wxSize(100, 100)) | |
522 | { | |
523 | m_life = life; | |
524 | m_cellsize = 8; | |
525 | Reset(); | |
526 | } | |
527 | ||
528 | LifeCanvas::~LifeCanvas() | |
529 | { | |
530 | delete m_bmp; | |
531 | } | |
532 | ||
533 | void LifeCanvas::Reset() | |
534 | { | |
535 | if (m_bmp) | |
536 | delete m_bmp; | |
537 | ||
538 | m_status = MOUSE_NOACTION; | |
539 | m_width = CellToCoord(m_life->GetWidth()) + 1; | |
540 | m_height = CellToCoord(m_life->GetHeight()) + 1; | |
541 | m_bmp = new wxBitmap(m_width, m_height); | |
542 | wxCoord w = GetSize().GetX(); | |
543 | wxCoord h = GetSize().GetY(); | |
544 | m_xoffset = (w > m_width)? ((w - m_width) / 2) : 0; | |
545 | m_yoffset = (h > m_height)? ((h - m_height) / 2) : 0; | |
546 | ||
547 | // redraw all, incl. background | |
548 | DrawEverything(TRUE); | |
549 | SetScrollbars(10, 10, (m_width + 9) / 10, (m_height + 9) / 10); | |
550 | } | |
551 | ||
552 | // draw everything | |
553 | void LifeCanvas::DrawEverything(bool force) | |
554 | { | |
555 | wxMemoryDC dc; | |
556 | ||
557 | dc.SelectObject(*m_bmp); | |
558 | dc.BeginDrawing(); | |
559 | ||
560 | // cells | |
561 | for (int j = 0; j < m_life->GetHeight(); j++) | |
562 | for (int i = 0; i < m_life->GetWidth(); i++) | |
563 | if (force || m_life->HasChanged(i, j)) | |
564 | DrawCell(i, j, dc); | |
565 | ||
566 | // bounding rectangle (always drawn) | |
567 | dc.SetPen(*wxBLACK_PEN); | |
568 | dc.SetBrush(*wxTRANSPARENT_BRUSH); | |
569 | dc.DrawRectangle(0, 0, m_width, m_height); | |
570 | ||
571 | dc.EndDrawing(); | |
572 | dc.SelectObject(wxNullBitmap); | |
573 | } | |
574 | ||
575 | // draw a single cell | |
576 | void LifeCanvas::DrawCell(int i, int j) | |
577 | { | |
578 | wxMemoryDC dc; | |
579 | ||
580 | dc.SelectObject(*m_bmp); | |
581 | dc.BeginDrawing(); | |
582 | ||
583 | DrawCell(i, j, dc); | |
584 | ||
585 | dc.EndDrawing(); | |
586 | dc.SelectObject(wxNullBitmap); | |
587 | } | |
588 | ||
589 | void LifeCanvas::DrawCell(int i, int j, wxDC &dc) | |
590 | { | |
591 | if (m_life->IsAlive(i, j)) | |
592 | { | |
593 | dc.SetPen(*wxBLACK_PEN); | |
594 | dc.SetBrush(*wxBLACK_BRUSH); | |
595 | dc.DrawRectangle(CellToCoord(i), | |
596 | CellToCoord(j), | |
597 | m_cellsize, | |
598 | m_cellsize); | |
599 | } | |
600 | else | |
601 | { | |
602 | dc.SetPen(*wxLIGHT_GREY_PEN); | |
603 | dc.SetBrush(*wxTRANSPARENT_BRUSH); | |
604 | dc.DrawRectangle(CellToCoord(i), | |
605 | CellToCoord(j), | |
606 | m_cellsize, | |
607 | m_cellsize); | |
608 | dc.SetPen(*wxWHITE_PEN); | |
609 | dc.SetBrush(*wxWHITE_BRUSH); | |
610 | dc.DrawRectangle(CellToCoord(i) + 1, | |
611 | CellToCoord(j) + 1, | |
612 | m_cellsize - 1, | |
613 | m_cellsize - 1); | |
614 | } | |
615 | } | |
616 | ||
617 | // event handlers | |
618 | void LifeCanvas::OnPaint(wxPaintEvent& event) | |
619 | { | |
620 | wxPaintDC dc(this); | |
621 | wxMemoryDC memdc; | |
622 | ||
623 | wxRegionIterator upd(GetUpdateRegion()); | |
624 | int x, y, w, h, xx, yy; | |
625 | ||
626 | dc.BeginDrawing(); | |
627 | memdc.SelectObject(*m_bmp); | |
628 | ||
629 | while(upd) | |
630 | { | |
631 | x = upd.GetX(); | |
632 | y = upd.GetY(); | |
633 | w = upd.GetW(); | |
634 | h = upd.GetH(); | |
635 | CalcUnscrolledPosition(x, y, &xx, &yy); | |
636 | ||
637 | dc.Blit(x, y, w, h, &memdc, xx - m_xoffset, yy - m_yoffset); | |
638 | upd++; | |
639 | } | |
640 | ||
641 | memdc.SelectObject(wxNullBitmap); | |
642 | dc.EndDrawing(); | |
643 | } | |
644 | ||
645 | void LifeCanvas::OnMouse(wxMouseEvent& event) | |
646 | { | |
647 | int x, y, xx, yy, i, j; | |
648 | ||
649 | // which cell are we pointing at? | |
650 | x = event.GetX(); | |
651 | y = event.GetY(); | |
652 | CalcUnscrolledPosition(x, y, &xx, &yy); | |
653 | i = CoordToCell( xx - m_xoffset ); | |
654 | j = CoordToCell( yy - m_yoffset ); | |
655 | ||
656 | // adjust x, y to point to the upper left corner of the cell | |
657 | CalcScrolledPosition( CellToCoord(i) + m_xoffset, | |
658 | CellToCoord(j) + m_yoffset, | |
659 | &x, &y ); | |
660 | ||
661 | // set cursor shape and statusbar text | |
662 | if (i < 0 || i >= m_life->GetWidth() || | |
663 | j < 0 || j >= m_life->GetHeight()) | |
664 | { | |
665 | GET_FRAME()->SetStatusText(wxEmptyString, 1); | |
666 | SetCursor(*wxSTANDARD_CURSOR); | |
667 | } | |
668 | else | |
669 | { | |
670 | wxString msg; | |
671 | msg.Printf(_("Cell: (%u, %u)"), i, j); | |
672 | GET_FRAME()->SetStatusText(msg, 1); | |
673 | SetCursor(*wxCROSS_CURSOR); | |
674 | } | |
675 | ||
676 | // button pressed? | |
677 | if (!event.LeftIsDown()) | |
678 | { | |
679 | m_status = MOUSE_NOACTION; | |
680 | } | |
681 | else if (i >= 0 && i < m_life->GetWidth() && | |
682 | j >= 0 && j < m_life->GetHeight()) | |
683 | { | |
684 | bool alive = m_life->IsAlive(i, j); | |
685 | ||
686 | // if just pressed, update status | |
687 | if (m_status == MOUSE_NOACTION) | |
688 | m_status = (alive? MOUSE_ERASING : MOUSE_DRAWING); | |
689 | ||
690 | // toggle cell and refresh if needed | |
691 | if (((m_status == MOUSE_ERASING) && alive) || | |
692 | ((m_status == MOUSE_DRAWING) && !alive)) | |
693 | { | |
694 | wxRect rect(x, y, m_cellsize + 1, m_cellsize + 1); | |
695 | m_life->SetCell(i, j, !alive); | |
696 | DrawCell(i, j); | |
697 | Refresh(FALSE, &rect); | |
698 | } | |
699 | } | |
700 | } | |
701 | ||
702 | void LifeCanvas::OnSize(wxSizeEvent& event) | |
703 | { | |
704 | wxCoord w = event.GetSize().GetX(); | |
705 | wxCoord h = event.GetSize().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 | // allow default processing | |
710 | event.Skip(); | |
711 | } | |
712 | ||
713 | // -------------------------------------------------------------------------- | |
714 | // LifeNewGameDialog | |
715 | // -------------------------------------------------------------------------- | |
716 | ||
717 | LifeNewGameDialog::LifeNewGameDialog(wxWindow *parent, int *w, int *h) | |
718 | : wxDialog(parent, -1, _("New game"), | |
719 | wxDefaultPosition, wxDefaultSize, | |
720 | wxDEFAULT_DIALOG_STYLE | wxDIALOG_MODAL) | |
721 | { | |
722 | m_w = w; | |
723 | m_h = h; | |
724 | ||
725 | wxBoxSizer *topsizer = new wxBoxSizer( wxVERTICAL ); | |
726 | ||
727 | // text message | |
728 | topsizer->Add( CreateTextSizer(_("Enter board dimensions")), 0, wxALL, 10 ); | |
729 | topsizer->Add( new wxStaticLine(this, -1), 0, wxGROW | wxLEFT | wxRIGHT | wxBOTTOM, 10); | |
730 | ||
731 | // prompts and text controls | |
732 | wxString strw, strh; | |
733 | strw.Printf(_("%u"), *m_w); | |
734 | strh.Printf(_("%u"), *m_h); | |
735 | m_spinctrlw = new wxSpinCtrl( this, -1, strw ); | |
736 | m_spinctrlh = new wxSpinCtrl( this, -1, strh ); | |
737 | ||
738 | wxBoxSizer *inputsizer1 = new wxBoxSizer( wxHORIZONTAL ); | |
739 | inputsizer1->Add( new wxStaticText(this, -1, _("Width")), 1, wxCENTER | wxLEFT, 20); | |
740 | inputsizer1->Add( m_spinctrlw, 2, wxCENTER | wxLEFT | wxRIGHT, 20 ); | |
741 | wxBoxSizer *inputsizer2 = new wxBoxSizer( wxHORIZONTAL ); | |
742 | inputsizer2->Add( new wxStaticText(this, -1, _("Height")), 1, wxCENTER | wxLEFT, 20); | |
743 | inputsizer2->Add( m_spinctrlh, 2, wxCENTER | wxLEFT | wxRIGHT, 20 ); | |
744 | ||
745 | topsizer->Add( inputsizer1, 1, wxGROW | wxLEFT | wxRIGHT, 5 ); | |
746 | topsizer->Add( inputsizer2, 1, wxGROW | wxLEFT | wxRIGHT, 5 ); | |
747 | topsizer->Add( new wxStaticLine(this, -1), 0, wxGROW | wxLEFT | wxRIGHT | wxTOP, 10); | |
748 | ||
749 | // buttons | |
750 | topsizer->Add( CreateButtonSizer(wxOK | wxCANCEL), 0, wxCENTRE | wxALL, 10); | |
751 | ||
752 | // activate | |
753 | SetSizer(topsizer); | |
754 | SetAutoLayout(TRUE); | |
755 | topsizer->SetSizeHints(this); | |
756 | topsizer->Fit(this); | |
757 | Centre(wxBOTH); | |
758 | } | |
759 | ||
760 | void LifeNewGameDialog::OnOK(wxCommandEvent& WXUNUSED(event)) | |
761 | { | |
762 | *m_w = m_spinctrlw->GetValue(); | |
763 | *m_h = m_spinctrlh->GetValue(); | |
764 | ||
765 | EndModal(wxID_OK); | |
766 | } | |
767 | ||
768 | void LifeNewGameDialog::OnCancel(wxCommandEvent& WXUNUSED(event)) | |
769 | { | |
770 | *m_w = -1; | |
771 | *m_h = -1; | |
772 | ||
773 | EndModal(wxID_CANCEL); | |
774 | } | |
775 | ||
776 | // -------------------------------------------------------------------------- | |
777 | // Life | |
778 | // -------------------------------------------------------------------------- | |
779 | ||
780 | Life::Life(int width, int height) | |
781 | { | |
782 | Create(width, height); | |
783 | } | |
784 | ||
785 | Life::~Life() | |
786 | { | |
787 | Destroy(); | |
788 | } | |
789 | ||
790 | void Life::Create(int width, int height) | |
791 | { | |
792 | wxASSERT(width > 0 || height > 0); | |
793 | ||
794 | m_width = width; | |
795 | m_height = height; | |
796 | m_cells = new Cell[m_width * m_height]; | |
797 | Clear(); | |
798 | } | |
799 | ||
800 | void Life::Destroy() | |
801 | { | |
802 | delete[] m_cells; | |
803 | } | |
804 | ||
805 | void Life::Clear() | |
806 | { | |
807 | for (int i = 0; i < m_width * m_height; i++) | |
808 | m_cells[i] = CELL_DEAD; | |
809 | } | |
810 | ||
811 | bool Life::IsAlive(int x, int y) const | |
812 | { | |
813 | wxASSERT(x < m_width || y < m_height); | |
814 | ||
815 | return (m_cells[y * m_width + x] & CELL_ALIVE); | |
816 | } | |
817 | ||
818 | bool Life::HasChanged(int x, int y) const | |
819 | { | |
820 | wxASSERT(x < m_width || y < m_height); | |
821 | ||
822 | return (m_cells[y * m_width + x] & CELL_MARK); | |
823 | } | |
824 | ||
825 | void Life::SetCell(int x, int y, bool alive) | |
826 | { | |
827 | wxASSERT(x < m_width || y < m_height); | |
828 | ||
829 | // set the CELL_MARK flag to notify that this cell has changed | |
830 | m_cells[y * m_width + x] = (alive? CELL_ALIVE : CELL_DEAD); | |
831 | } | |
832 | ||
833 | void Life::NextTic() | |
834 | { | |
835 | /* 1st pass. Find and mark deaths and births for this generation. | |
836 | * | |
837 | * Rules: | |
838 | * An organism with <= 1 neighbors will die due to isolation. | |
839 | * An organism with >= 4 neighbors will die due to starvation. | |
840 | * New organisms are born in cells with exactly 3 neighbors. | |
841 | */ | |
842 | for (int j = 0; j < m_height; j++) | |
843 | for (int i = 0; i < m_width; i++) | |
844 | { | |
845 | int neighbors = GetNeighbors(i, j); | |
846 | bool alive = IsAlive(i, j); | |
847 | ||
848 | /* Set CELL_MARK if this cell must change, clear it | |
849 | * otherwise. We cannot toggle the CELL_ALIVE bit yet | |
850 | * because all deaths and births are simultaneous (it | |
851 | * would affect neighbouring cells). | |
852 | */ | |
853 | if ((!alive && neighbors == 3) || | |
854 | (alive && (neighbors <= 1 || neighbors >= 4))) | |
855 | m_cells[j * m_width + i] |= CELL_MARK; | |
856 | else | |
857 | m_cells[j * m_width + i] &= ~CELL_MARK; | |
858 | } | |
859 | ||
860 | /* 2nd pass. Stabilize. | |
861 | */ | |
862 | for (int j = 0; j < m_height; j++) | |
863 | for (int i = 0; i < m_width; i++) | |
864 | { | |
865 | /* Toggle CELL_ALIVE for those cells marked in the | |
866 | * previous pass. Do not clear the CELL_MARK bit yet; | |
867 | * it is useful to know which cells have changed and | |
868 | * thus must be updated in the screen. | |
869 | */ | |
870 | if (m_cells[j * m_width + i] & CELL_MARK) | |
871 | m_cells[j * m_width + i] ^= CELL_ALIVE; | |
872 | } | |
873 | } | |
874 | ||
875 | int Life::GetNeighbors(int x, int y) const | |
876 | { | |
877 | wxASSERT(x < m_width || y < m_height); | |
878 | ||
879 | // count number of neighbors (wrap around board limits) | |
880 | int neighbors = 0; | |
881 | for (int j = y - 1; j <= y + 1; j++) | |
882 | for (int i = x - 1; i <= x + 1; i++) | |
883 | { | |
884 | if (IsAlive( ((i < 0)? (i + m_width ) : (i % m_width)), | |
885 | ((j < 0)? (j + m_height) : (j % m_height)) )) | |
886 | neighbors++; | |
887 | } | |
888 | ||
889 | // do not count ourselves | |
890 | if (IsAlive(x, y)) neighbors--; | |
891 | ||
892 | return neighbors; | |
893 | } | |
894 | ||
895 | void Life::SetCell(int x, int y, Cell status) | |
896 | { | |
897 | wxASSERT(x < m_width || y < m_height); | |
898 | ||
899 | m_cells[y * m_width + x] = status; | |
900 | } | |
901 |