From 5a1dca125f7689dd0e9b6433337cfc72af14797d Mon Sep 17 00:00:00 2001
From: Guillermo Rodriguez Garcia <guille@iies.es>
Date: Mon, 3 Jan 2000 08:56:57 +0000
Subject: [PATCH] Life!

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@5190 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
---
 samples/life/Makefile.in       |  22 +
 samples/life/bitmaps/play.bmp  | Bin 0 -> 246 bytes
 samples/life/bitmaps/play.xpm  |  23 +
 samples/life/bitmaps/reset.bmp | Bin 0 -> 822 bytes
 samples/life/bitmaps/reset.xpm |  24 +
 samples/life/bitmaps/stop.bmp  | Bin 0 -> 246 bytes
 samples/life/bitmaps/stop.xpm  |  24 +
 samples/life/life.cpp          | 901 +++++++++++++++++++++++++++++++++
 samples/life/life.def          |   7 +
 samples/life/life.rc           |   6 +
 samples/life/makefile.b32      |  16 +
 samples/life/makefile.bcc      |  19 +
 samples/life/makefile.dos      |  17 +
 samples/life/makefile.g95      |  16 +
 samples/life/makefile.vc       |  18 +
 samples/life/makefile.wat      |  15 +
 samples/life/mondrian.ico      | Bin 0 -> 766 bytes
 samples/life/mondrian.xpm      |  44 ++
 18 files changed, 1152 insertions(+)
 create mode 100644 samples/life/Makefile.in
 create mode 100644 samples/life/bitmaps/play.bmp
 create mode 100644 samples/life/bitmaps/play.xpm
 create mode 100644 samples/life/bitmaps/reset.bmp
 create mode 100644 samples/life/bitmaps/reset.xpm
 create mode 100644 samples/life/bitmaps/stop.bmp
 create mode 100644 samples/life/bitmaps/stop.xpm
 create mode 100644 samples/life/life.cpp
 create mode 100644 samples/life/life.def
 create mode 100644 samples/life/life.rc
 create mode 100644 samples/life/makefile.b32
 create mode 100644 samples/life/makefile.bcc
 create mode 100644 samples/life/makefile.dos
 create mode 100644 samples/life/makefile.g95
 create mode 100644 samples/life/makefile.vc
 create mode 100644 samples/life/makefile.wat
 create mode 100644 samples/life/mondrian.ico
 create mode 100644 samples/life/mondrian.xpm

diff --git a/samples/life/Makefile.in b/samples/life/Makefile.in
new file mode 100644
index 0000000000..5e9184ba9a
--- /dev/null
+++ b/samples/life/Makefile.in
@@ -0,0 +1,22 @@
+#
+# File:		makefile.unx
+# Author:	Julian Smart
+# Created:	1998
+# Updated:	
+# Copyright:	(c) 1998 Julian Smart
+#
+# "%W% %G%"
+#
+# Makefile for toolbar example (UNIX).
+
+top_srcdir = @top_srcdir@
+top_builddir = ../..
+program_dir = samples/life
+
+PROGRAM=life
+
+OBJECTS=$(PROGRAM).o
+
+include ../../src/makeprog.env
+
+
diff --git a/samples/life/bitmaps/play.bmp b/samples/life/bitmaps/play.bmp
new file mode 100644
index 0000000000000000000000000000000000000000..41f944923a2c9aad20cde0c02293e11181c9d274
GIT binary patch
literal 246
zcmZ?r{l)+RWk5;;hy|dSk%0v)(Eucm@If$G08Rj9fQX@?0YU*uAd}(1fdfDqB=H}N
t82<kUv49u|K|(;HoFHI_s$hUJ7@!QGL0}Oe4Q3$t5QQ-HF#Xsq0st+=NWlOA

literal 0
HcmV?d00001

diff --git a/samples/life/bitmaps/play.xpm b/samples/life/bitmaps/play.xpm
new file mode 100644
index 0000000000..26d6d51f1e
--- /dev/null
+++ b/samples/life/bitmaps/play.xpm
@@ -0,0 +1,23 @@
+/* XPM */
+static char *play_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 2 1",
+"  c None",
+". c Black",
+/* pixels */
+"                ",
+"                ",
+"                ",
+"    .           ",
+"    ...         ",
+"    .....       ",
+"    .......     ",
+"    .........   ",
+"    .......     ",
+"    .....       ",
+"    ...         ",
+"    .           ",
+"                ",
+"                ",
+"                "
+};
diff --git a/samples/life/bitmaps/reset.bmp b/samples/life/bitmaps/reset.bmp
new file mode 100644
index 0000000000000000000000000000000000000000..41ea143e80b3bdd14bc09ac2b704be967c27a1dc
GIT binary patch
literal 822
zcmZ?rHDhJ~12Z700mK4O%*Y@C7H0s;AK`;whyVk_fx!wO4#adFesQ2IvKE*xQ3T-%
z&}7lLaAn90xEeSESr#iBt`f+=ssK%pB=yLOA#60$khyTh5J5C9vPvKut1MPQWYf?L
Lft!OVK1f6W`g6_}

literal 0
HcmV?d00001

diff --git a/samples/life/bitmaps/reset.xpm b/samples/life/bitmaps/reset.xpm
new file mode 100644
index 0000000000..b9ebd02762
--- /dev/null
+++ b/samples/life/bitmaps/reset.xpm
@@ -0,0 +1,24 @@
+/* XPM */
+static char *reset_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 2 1",
+"  c None",
+". c Black",
+/* pixels */
+"                ",
+"                ",
+"                ",
+"   ..      ..   ",
+"   ...    ...   ",
+"    ...  ...    ",
+"     ......     ",
+"      ....      ",
+"      ....      ",
+"     ......     ",
+"    ...  ...    ",
+"   ...    ...   ",
+"   ..      ..   ",
+"                ",
+"                ",
+"                "
+};
diff --git a/samples/life/bitmaps/stop.bmp b/samples/life/bitmaps/stop.bmp
new file mode 100644
index 0000000000000000000000000000000000000000..a0941ea551f3558253b90d741eeb917583fe575c
GIT binary patch
literal 246
zcmZ?r{l)+RWk5;;hy|dSk%0v)(Eucm@If$G08Rj9fQX@?0YU*uAd}(1fdfDqB=H}N
b82<kUv49u|K|(;HoFD*d0IQ;u#%>V+?Ds=u

literal 0
HcmV?d00001

diff --git a/samples/life/bitmaps/stop.xpm b/samples/life/bitmaps/stop.xpm
new file mode 100644
index 0000000000..cf7fd0b42f
--- /dev/null
+++ b/samples/life/bitmaps/stop.xpm
@@ -0,0 +1,24 @@
+/* XPM */
+static char *stop_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 2 1",
+"  c None",
+". c Black",
+/* pixels */
+"                ",
+"                ",
+"                ",
+"    ........    ",
+"    ........    ",
+"    ........    ",
+"    ........    ",
+"    ........    ",
+"    ........    ",
+"    ........    ",
+"    ........    ",
+"    ........    ",
+"                ",
+"                ",
+"                ",
+"                "
+};
diff --git a/samples/life/life.cpp b/samples/life/life.cpp
new file mode 100644
index 0000000000..89c513f840
--- /dev/null
+++ b/samples/life/life.cpp
@@ -0,0 +1,901 @@
+/////////////////////////////////////////////////////////////////////////////
+// Name:        life.cpp
+// Purpose:     The game of life, created by J. H. Conway
+// Author:      Guillermo Rodriguez Garcia, <guille@iies.es>
+// Modified by:
+// Created:     Jan/2000
+// RCS-ID:      $Id$
+// Copyright:   (c) 2000, Guillermo Rodriguez Garcia
+// Licence:     wxWindows licence
+/////////////////////////////////////////////////////////////////////////////
+
+// ==========================================================================
+// declarations
+// ==========================================================================
+
+// minimum and maximum table size, in each dimension
+#define LIFE_MIN 10
+#define LIFE_MAX 200
+
+// some shortcuts
+#define ADD_TOOL(a, b, c, d) \
+    toolBar->AddTool(a, b, wxNullBitmap, FALSE, -1, -1, (wxObject *)0, c, d)
+
+#define GET_FRAME() \
+    ((wxFrame *) wxGetApp().GetTopWindow())
+
+// --------------------------------------------------------------------------
+// headers
+// --------------------------------------------------------------------------
+
+#ifdef __GNUG__
+    #pragma implementation "life.cpp"
+    #pragma interface "life.cpp"
+#endif
+
+// for compilers that support precompilation, includes "wx/wx.h".
+#include "wx/wxprec.h"
+
+#ifdef __BORLANDC__
+    #pragma hdrstop
+#endif
+
+// for all others, include the necessary headers 
+#ifndef WX_PRECOMP
+    #include "wx/wx.h"
+#endif
+
+#include "wx/statline.h"
+#include "wx/spinctrl.h"
+
+// --------------------------------------------------------------------------
+// resources
+// --------------------------------------------------------------------------
+
+#if defined(__WXGTK__) || defined(__WXMOTIF__)
+    // the application icon
+    #include "mondrian.xpm"
+
+    // bitmap buttons for the toolbar
+    #include "bitmaps/reset.xpm"
+    #include "bitmaps/play.xpm"
+    #include "bitmaps/stop.xpm"
+#endif
+
+// --------------------------------------------------------------------------
+// private classes
+// --------------------------------------------------------------------------
+
+class Life;
+class LifeCanvas;
+class LifeTimer;
+class LifeFrame;
+class LifeApp;
+
+
+// Life
+class Life
+{
+public:
+    // ctors and dtors
+    Life(int width, int height);
+    ~Life();
+    void Create(int width, int height);
+    void Destroy();
+
+    // public accessors
+    inline int  GetWidth()  const { return m_width; };
+    inline int  GetHeight() const { return m_height; };
+    inline bool IsAlive(int x, int y) const;
+    inline bool HasChanged(int x, int y) const;
+    inline void SetCell(int x, int y, bool alive = TRUE);
+
+    // game operations
+    void Clear();
+    void NextTic();
+
+private:
+    enum CellFlags {
+        CELL_DEAD    = 0x0000,  // is dead
+        CELL_ALIVE   = 0x0001,  // is alive
+        CELL_MARK    = 0x0002,  // will change / has changed
+    };
+    typedef int Cell;
+
+    int GetNeighbors(int x, int y) const;
+    inline void SetCell(int x, int y, Cell status);
+
+    int   m_width;
+    int   m_height;
+    Cell *m_cells;
+};
+
+// Life canvas
+class LifeCanvas : public wxScrolledWindow
+{
+public:
+    // ctor and dtor
+    LifeCanvas(wxWindow* parent, Life* life);
+    ~LifeCanvas();
+
+    // member functions
+    void Reset();
+    void DrawEverything(bool force = FALSE);
+    void DrawCell(int i, int j);
+    void DrawCell(int i, int j, wxDC &dc);
+    inline int CellToCoord(int i) const { return (i * m_cellsize); };
+    inline int CoordToCell(int x) const { return ((x >= 0)? (x / m_cellsize) : -1); };
+
+    // event handlers
+    void OnPaint(wxPaintEvent& event);
+    void OnMouse(wxMouseEvent& event);
+    void OnSize(wxSizeEvent& event);
+
+private:
+    // any class wishing to process wxWindows events must use this macro
+    DECLARE_EVENT_TABLE()
+
+    enum MouseStatus {
+        MOUSE_NOACTION,
+        MOUSE_DRAWING,
+        MOUSE_ERASING
+    };
+
+    Life        *m_life;
+    wxFrame     *m_frame;
+    wxBitmap    *m_bmp;
+    int          m_height;
+    int          m_width;
+    int          m_cellsize;
+    wxCoord      m_xoffset;
+    wxCoord      m_yoffset;
+    MouseStatus  m_status;
+};
+
+// Life timer
+class LifeTimer : public wxTimer
+{
+public:
+    LifeTimer(LifeFrame *parent);
+    void Notify();
+
+private:
+    LifeFrame *m_parent;
+};
+
+// Life main frame
+class LifeFrame : public wxFrame
+{
+public:
+    // ctor and dtor
+    LifeFrame();
+    ~LifeFrame();
+
+    // member functions
+    void UpdateInfoText();
+
+    // event handlers
+    void OnMenu(wxCommandEvent& event);
+    void OnSlider(wxScrollEvent& event);
+    void OnNewGame();
+    void OnStart();
+    void OnStop();
+    void OnTimer();
+
+private:
+    // any class wishing to process wxWindows events must use this macro
+    DECLARE_EVENT_TABLE()
+
+    Life         *m_life;
+    LifeTimer    *m_timer;
+    LifeCanvas   *m_canvas;
+    wxStaticText *m_text;
+    bool          m_running;
+    long          m_interval;
+    long          m_tics;
+};
+
+// Life new game dialog
+class LifeNewGameDialog : public wxDialog
+{
+public:
+    // ctor
+    LifeNewGameDialog(wxWindow *parent, int *w, int *h);
+
+    // event handlers
+    void OnOK(wxCommandEvent& event);
+    void OnCancel(wxCommandEvent& event);
+
+private:
+    // any class wishing to process wxWindows events must use this macro
+    DECLARE_EVENT_TABLE();
+
+    int        *m_w;
+    int        *m_h;
+    wxSpinCtrl *m_spinctrlw;
+    wxSpinCtrl *m_spinctrlh;
+};
+
+// Life app
+class LifeApp : public wxApp
+{
+public:
+    virtual bool OnInit();
+};
+
+
+// --------------------------------------------------------------------------
+// constants
+// --------------------------------------------------------------------------
+
+// IDs for the controls and the menu commands
+enum
+{
+    // menu items and toolbar buttons
+    ID_NEWGAME = 101,
+    ID_CLEAR,
+    ID_START,
+    ID_STOP,
+    ID_ABOUT,
+    ID_EXIT,
+
+    // slider
+    ID_SLIDER
+};
+
+// --------------------------------------------------------------------------
+// event tables and other macros for wxWindows
+// --------------------------------------------------------------------------
+
+// Event tables
+
+BEGIN_EVENT_TABLE(LifeFrame, wxFrame)
+    EVT_MENU_RANGE      (ID_NEWGAME, ID_ABOUT,  LifeFrame::OnMenu)
+    EVT_COMMAND_SCROLL  (ID_SLIDER,             LifeFrame::OnSlider)
+END_EVENT_TABLE()
+
+BEGIN_EVENT_TABLE(LifeCanvas, wxScrolledWindow)
+    EVT_PAINT           (                       LifeCanvas::OnPaint)
+    EVT_SIZE            (                       LifeCanvas::OnSize)
+    EVT_MOUSE_EVENTS    (                       LifeCanvas::OnMouse)
+END_EVENT_TABLE()                     
+
+BEGIN_EVENT_TABLE(LifeNewGameDialog, wxDialog)
+    EVT_BUTTON          (wxID_OK,               LifeNewGameDialog::OnOK)
+    EVT_BUTTON          (wxID_CANCEL,           LifeNewGameDialog::OnCancel)
+END_EVENT_TABLE()
+
+
+// Create a new application object
+IMPLEMENT_APP(LifeApp)
+
+// ==========================================================================
+// implementation
+// ==========================================================================
+
+// --------------------------------------------------------------------------
+// LifeApp
+// --------------------------------------------------------------------------
+
+// `Main program' equivalent: the program execution "starts" here
+bool LifeApp::OnInit()
+{
+    // create the main application window
+    LifeFrame *frame = new LifeFrame();
+
+    // show it and tell the application that it's our main window
+    frame->Show(TRUE);
+    SetTopWindow(frame);
+
+    // enter the main message loop and run the app
+    return TRUE;
+}
+
+// --------------------------------------------------------------------------
+// LifeFrame
+// --------------------------------------------------------------------------
+
+// frame constructor
+LifeFrame::LifeFrame() : wxFrame((wxFrame *)0, -1, _("Life!"), wxPoint(50, 50))
+{
+    // frame icon
+    SetIcon(wxICON(mondrian));
+
+    // menu bar
+    wxMenu *menuFile = new wxMenu("", wxMENU_TEAROFF);
+
+    menuFile->Append(ID_NEWGAME, _("&New game...\tCtrl-N"), _("Start a new game"));
+    menuFile->Append(ID_CLEAR, _("&Clear\tCtrl-C"), _("Clear game board"));
+    menuFile->Append(ID_START, _("&Start\tCtrl-S"), _("Start"));
+    menuFile->Append(ID_STOP, _("S&top\tCtrl-T"), _("Stop"));
+    menuFile->AppendSeparator();
+    menuFile->Append(ID_ABOUT, _("&About...\tCtrl-A"), _("Show about dialog"));
+    menuFile->AppendSeparator();
+    menuFile->Append(ID_EXIT, _("E&xit\tAlt-X"), _("Quit this program"));
+    menuFile->Enable(ID_STOP, FALSE);
+
+    wxMenuBar *menuBar = new wxMenuBar();
+    menuBar->Append(menuFile, _("&File"));
+    SetMenuBar(menuBar);
+
+    // tool bar
+    wxBitmap tbBitmaps[3];
+    tbBitmaps[0] = wxBITMAP(reset);
+    tbBitmaps[1] = wxBITMAP(play);
+    tbBitmaps[2] = wxBITMAP(stop);
+
+    wxToolBar *toolBar = CreateToolBar();
+    toolBar->SetMargins(5, 5);
+    toolBar->SetToolBitmapSize(wxSize(16, 16));
+    ADD_TOOL(ID_CLEAR, tbBitmaps[0], _("Clear"), _("Clear game board"));
+    ADD_TOOL(ID_START, tbBitmaps[1], _("Start"), _("Start"));
+    ADD_TOOL(ID_STOP , tbBitmaps[2], _("Stop"),  _("Stop"));
+    toolBar->EnableTool(ID_STOP, FALSE);
+    toolBar->Realize();
+
+    // status bar
+    CreateStatusBar(2);
+    SetStatusText(_("Welcome to Life!"));
+
+    // panel
+    wxPanel *panel = new wxPanel(this, -1);
+
+    // game
+    m_life     = new Life(20, 20);
+    m_canvas   = new LifeCanvas(panel, m_life);
+    m_timer    = new LifeTimer(this);
+    m_interval = 500;
+    m_tics     = 0;
+    m_text     = new wxStaticText(panel, -1, "");
+    UpdateInfoText();
+
+    // slider
+    wxSlider *slider = new wxSlider(panel, ID_SLIDER, 5, 1, 10,
+        wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL | wxSL_AUTOTICKS);
+
+    // component layout
+    wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
+    sizer->Add(m_canvas, 1, wxGROW | wxCENTRE | wxALL, 5);
+    sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE);
+    sizer->Add(m_text, 0, wxCENTRE | wxNORTH, 5);
+    sizer->Add(slider, 0, wxCENTRE | wxALL, 5);
+    panel->SetSizer(sizer);
+    panel->SetAutoLayout(TRUE);
+    sizer->Fit(this);
+    sizer->SetSizeHints(this);
+}
+
+LifeFrame::~LifeFrame()
+{
+    delete m_timer;
+    delete m_life;
+}
+
+void LifeFrame::UpdateInfoText()
+{
+    wxString msg;
+
+    msg.Printf(_("Generation: %u,  Interval: %u ms"), m_tics, m_interval);
+    m_text->SetLabel(msg);
+}
+
+// event handlers
+void LifeFrame::OnMenu(wxCommandEvent& event)
+{
+    switch (event.GetId())
+    {
+        case ID_START   : OnStart(); break;
+        case ID_STOP    : OnStop(); break;
+        case ID_NEWGAME : OnNewGame(); break;
+        case ID_CLEAR   :
+        {
+            OnStop();
+            m_life->Clear();
+            m_canvas->DrawEverything(TRUE);
+            m_canvas->Refresh(FALSE);
+            m_tics = 0;
+            UpdateInfoText();
+            break;
+        }
+        case ID_ABOUT :
+        {
+            wxMessageBox(
+                _("This is the about dialog of the Life! sample.\n"
+                  "(c) 2000 Guillermo Rodriguez Garcia"),
+                _("About Life!"),
+                wxOK | wxICON_INFORMATION,
+                this);
+            break;
+        }
+        case ID_EXIT :
+        {
+            // TRUE is to force the frame to close
+            Close(TRUE);
+            break;
+        }
+    }
+}
+
+void LifeFrame::OnSlider(wxScrollEvent& event)
+{
+    m_interval = event.GetPosition() * 100;
+
+    // restart timer if running, to set the new interval
+    if (m_running)
+    {
+        m_timer->Stop();
+        m_timer->Start(m_interval);
+    }
+
+    UpdateInfoText();
+}
+
+void LifeFrame::OnNewGame()
+{
+    int w = m_life->GetWidth();
+    int h = m_life->GetHeight();
+    int result;
+
+    // stop if it was running
+    OnStop();
+
+    // show dialog box
+    LifeNewGameDialog dialog(this, &w, &h);
+    result = dialog.ShowModal();
+
+    // create new game
+    if (result == wxID_OK)
+    {
+        // check dimensions
+        if (w >= LIFE_MIN && w <= LIFE_MAX &&
+            h >= LIFE_MIN && h <= LIFE_MAX)
+        {
+            m_life->Destroy();
+            m_life->Create(w, h);
+            m_canvas->Reset();
+            m_tics = 0;
+            UpdateInfoText();
+        }
+        else
+        {
+            wxString msg;
+            msg.Printf(_("Both dimensions must be within %u and %u.\n"),
+                LIFE_MIN, LIFE_MAX);
+            wxMessageBox(msg, _("Error!"), wxOK | wxICON_EXCLAMATION, this);
+        }
+    }
+}
+
+void LifeFrame::OnStart()
+{
+    GetToolBar()->EnableTool(ID_START, FALSE);
+    GetToolBar()->EnableTool(ID_STOP,  TRUE);
+    GetMenuBar()->GetMenu(0)->Enable(ID_START, FALSE);
+    GetMenuBar()->GetMenu(0)->Enable(ID_STOP,  TRUE);
+
+    m_timer->Start(m_interval);
+    m_running = TRUE;
+}
+
+void LifeFrame::OnStop()
+{
+    GetToolBar()->EnableTool(ID_START, TRUE);
+    GetToolBar()->EnableTool(ID_STOP,  FALSE);
+    GetMenuBar()->GetMenu(0)->Enable(ID_START, TRUE);
+    GetMenuBar()->GetMenu(0)->Enable(ID_STOP,  FALSE);
+
+    m_timer->Stop();
+    m_running = FALSE;
+}
+
+void LifeFrame::OnTimer()
+{
+    m_tics++;
+    UpdateInfoText();
+
+    m_life->NextTic();
+    m_canvas->DrawEverything();
+    m_canvas->Refresh(FALSE);
+}
+
+// --------------------------------------------------------------------------
+// LifeTimer
+// --------------------------------------------------------------------------
+
+LifeTimer::LifeTimer(LifeFrame *parent) : wxTimer()
+{
+    m_parent = parent;
+}
+
+void LifeTimer::Notify()
+{
+    m_parent->OnTimer();
+}
+
+// --------------------------------------------------------------------------
+// LifeCavas
+// --------------------------------------------------------------------------
+
+// canvas constructor
+LifeCanvas::LifeCanvas(wxWindow *parent, Life *life)
+          : wxScrolledWindow(parent, -1, wxPoint(0, 0), wxSize(100, 100))
+{
+    m_life     = life;
+    m_cellsize = 8;
+    Reset();
+}
+
+LifeCanvas::~LifeCanvas()
+{
+    delete m_bmp;
+}
+
+void LifeCanvas::Reset()
+{
+    if (m_bmp)
+        delete m_bmp;
+
+    m_status   = MOUSE_NOACTION;
+    m_width    = CellToCoord(m_life->GetWidth()) + 1;
+    m_height   = CellToCoord(m_life->GetHeight()) + 1;
+    m_bmp      = new wxBitmap(m_width, m_height);
+    wxCoord w  = GetSize().GetX();
+    wxCoord h  = GetSize().GetY();
+    m_xoffset  = (w > m_width)?  ((w - m_width) / 2)  : 0;
+    m_yoffset  = (h > m_height)? ((h - m_height) / 2) : 0;
+
+    // redraw all, incl. background
+    DrawEverything(TRUE);
+    SetScrollbars(10, 10, (m_width + 9) / 10, (m_height + 9) / 10);
+}
+
+// draw everything
+void LifeCanvas::DrawEverything(bool force)
+{
+    wxMemoryDC dc;
+
+    dc.SelectObject(*m_bmp);
+    dc.BeginDrawing();
+
+    // cells
+    for (int j = 0; j < m_life->GetHeight(); j++)
+        for (int i = 0; i < m_life->GetWidth(); i++)
+            if (force || m_life->HasChanged(i, j))
+                DrawCell(i, j, dc);
+
+    // bounding rectangle (always drawn)
+    dc.SetPen(*wxBLACK_PEN);
+    dc.SetBrush(*wxTRANSPARENT_BRUSH);
+    dc.DrawRectangle(0, 0, m_width, m_height);
+
+    dc.EndDrawing();
+    dc.SelectObject(wxNullBitmap);
+}
+
+// draw a single cell
+void LifeCanvas::DrawCell(int i, int j)
+{
+    wxMemoryDC dc;
+
+    dc.SelectObject(*m_bmp);
+    dc.BeginDrawing();
+
+    DrawCell(i, j, dc);
+
+    dc.EndDrawing();
+    dc.SelectObject(wxNullBitmap);
+}
+
+void LifeCanvas::DrawCell(int i, int j, wxDC &dc)
+{
+    if (m_life->IsAlive(i, j))
+    {
+        dc.SetPen(*wxBLACK_PEN);
+        dc.SetBrush(*wxBLACK_BRUSH);
+        dc.DrawRectangle(CellToCoord(i),
+                         CellToCoord(j),
+                         m_cellsize,
+                         m_cellsize);
+    }
+    else
+    {
+        dc.SetPen(*wxLIGHT_GREY_PEN);
+        dc.SetBrush(*wxTRANSPARENT_BRUSH);
+        dc.DrawRectangle(CellToCoord(i),
+                         CellToCoord(j),
+                         m_cellsize,
+                         m_cellsize);
+        dc.SetPen(*wxWHITE_PEN);
+        dc.SetBrush(*wxWHITE_BRUSH);
+        dc.DrawRectangle(CellToCoord(i) + 1,
+                         CellToCoord(j) + 1,
+                         m_cellsize - 1,
+                         m_cellsize - 1);
+    }
+}
+ 
+// event handlers
+void LifeCanvas::OnPaint(wxPaintEvent& event)
+{
+    wxPaintDC dc(this);
+    wxMemoryDC memdc;
+
+    wxRegionIterator upd(GetUpdateRegion());
+    int x, y, w, h, xx, yy;
+
+    dc.BeginDrawing();
+    memdc.SelectObject(*m_bmp);
+
+    while(upd)
+    {
+        x = upd.GetX();
+        y = upd.GetY();
+        w = upd.GetW();
+        h = upd.GetH();
+        CalcUnscrolledPosition(x, y, &xx, &yy);
+
+        dc.Blit(x, y, w, h, &memdc, xx - m_xoffset, yy - m_yoffset); 
+        upd++;
+    }
+
+    memdc.SelectObject(wxNullBitmap);
+    dc.EndDrawing();
+}
+
+void LifeCanvas::OnMouse(wxMouseEvent& event)
+{
+    int x, y, xx, yy, i, j;
+
+    // which cell are we pointing at?
+    x = event.GetX();
+    y = event.GetY();
+    CalcUnscrolledPosition(x, y, &xx, &yy);
+    i = CoordToCell( xx - m_xoffset );
+    j = CoordToCell( yy - m_yoffset );
+
+    // adjust x, y to point to the upper left corner of the cell
+    CalcScrolledPosition( CellToCoord(i) + m_xoffset,
+                          CellToCoord(j) + m_yoffset,
+                          &x, &y );
+
+    // set cursor shape and statusbar text
+    if (i < 0 || i >= m_life->GetWidth() ||
+        j < 0 || j >= m_life->GetHeight())
+    {
+        GET_FRAME()->SetStatusText(wxEmptyString, 1);
+        SetCursor(*wxSTANDARD_CURSOR);
+    }
+    else
+    {
+        wxString msg;
+        msg.Printf(_("Cell: (%u, %u)"), i, j);
+        GET_FRAME()->SetStatusText(msg, 1);
+        SetCursor(*wxCROSS_CURSOR);
+    }
+
+    // button pressed?
+    if (!event.LeftIsDown())
+    {
+        m_status = MOUSE_NOACTION;
+    }
+    else if (i >= 0 && i < m_life->GetWidth() &&
+             j >= 0 && j < m_life->GetHeight())
+    {
+        bool alive = m_life->IsAlive(i, j);
+
+        // if just pressed, update status
+        if (m_status == MOUSE_NOACTION)
+            m_status = (alive? MOUSE_ERASING : MOUSE_DRAWING);
+
+        // toggle cell and refresh if needed
+        if (((m_status == MOUSE_ERASING) && alive) ||
+            ((m_status == MOUSE_DRAWING) && !alive))
+        {
+            wxRect rect(x, y, m_cellsize + 1, m_cellsize + 1);
+            m_life->SetCell(i, j, !alive);
+            DrawCell(i, j);
+            Refresh(FALSE, &rect);
+        }
+    }
+}
+
+void LifeCanvas::OnSize(wxSizeEvent& event)
+{
+    wxCoord w = event.GetSize().GetX();
+    wxCoord h = event.GetSize().GetY();
+    m_xoffset = (w > m_width)?  ((w - m_width) / 2)  : 0;
+    m_yoffset = (h > m_height)? ((h - m_height) / 2) : 0;
+
+    // allow default processing
+    event.Skip();
+}
+
+// --------------------------------------------------------------------------
+// LifeNewGameDialog
+// --------------------------------------------------------------------------
+
+LifeNewGameDialog::LifeNewGameDialog(wxWindow *parent, int *w, int *h)
+                 : wxDialog(parent, -1, _("New game"),
+                            wxDefaultPosition, wxDefaultSize,
+                            wxDEFAULT_DIALOG_STYLE | wxDIALOG_MODAL)
+{
+    m_w = w;
+    m_h = h;
+
+    wxBoxSizer *topsizer = new wxBoxSizer( wxVERTICAL );
+
+    // text message
+    topsizer->Add( CreateTextSizer(_("Enter board dimensions")), 0, wxALL, 10 );
+    topsizer->Add( new wxStaticLine(this, -1), 0, wxGROW | wxLEFT | wxRIGHT | wxBOTTOM, 10);
+
+    // prompts and text controls
+    wxString strw, strh;
+    strw.Printf(_("%u"), *m_w);
+    strh.Printf(_("%u"), *m_h);
+    m_spinctrlw = new wxSpinCtrl( this, -1, strw );
+    m_spinctrlh = new wxSpinCtrl( this, -1, strh );
+
+    wxBoxSizer *inputsizer1 = new wxBoxSizer( wxHORIZONTAL );
+    inputsizer1->Add( new wxStaticText(this, -1, _("Width")), 1, wxCENTER | wxLEFT, 20);
+    inputsizer1->Add( m_spinctrlw, 2, wxCENTER | wxLEFT | wxRIGHT, 20 );
+    wxBoxSizer *inputsizer2 = new wxBoxSizer( wxHORIZONTAL );
+    inputsizer2->Add( new wxStaticText(this, -1, _("Height")), 1, wxCENTER | wxLEFT, 20);
+    inputsizer2->Add( m_spinctrlh, 2, wxCENTER | wxLEFT | wxRIGHT, 20 );
+
+    topsizer->Add( inputsizer1, 1, wxGROW | wxLEFT | wxRIGHT, 5 );
+    topsizer->Add( inputsizer2, 1, wxGROW | wxLEFT | wxRIGHT, 5 );
+    topsizer->Add( new wxStaticLine(this, -1), 0, wxGROW | wxLEFT | wxRIGHT | wxTOP, 10);
+
+    // buttons
+    topsizer->Add( CreateButtonSizer(wxOK | wxCANCEL), 0, wxCENTRE | wxALL, 10);
+
+    // activate
+    SetSizer(topsizer);
+    SetAutoLayout(TRUE);
+    topsizer->SetSizeHints(this);
+    topsizer->Fit(this);
+    Centre(wxBOTH);
+}
+
+void LifeNewGameDialog::OnOK(wxCommandEvent& WXUNUSED(event))
+{
+    *m_w = m_spinctrlw->GetValue();
+    *m_h = m_spinctrlh->GetValue();
+
+    EndModal(wxID_OK);
+}
+
+void LifeNewGameDialog::OnCancel(wxCommandEvent& WXUNUSED(event))
+{
+    *m_w = -1;
+    *m_h = -1;
+
+    EndModal(wxID_CANCEL);
+}
+
+// --------------------------------------------------------------------------
+// Life
+// --------------------------------------------------------------------------
+
+Life::Life(int width, int height)
+{            
+    Create(width, height);
+}
+
+Life::~Life()
+{
+    Destroy();
+}
+
+void Life::Create(int width, int height)
+{
+    wxASSERT(width > 0 || height > 0);
+
+    m_width  = width;
+    m_height = height;
+    m_cells  = new Cell[m_width * m_height];
+    Clear();
+}
+
+void Life::Destroy()
+{
+    delete[] m_cells;
+}
+
+void Life::Clear()
+{
+    for (int i = 0; i < m_width * m_height; i++)
+        m_cells[i] = CELL_DEAD;
+}
+
+bool Life::IsAlive(int x, int y) const
+{
+    wxASSERT(x < m_width || y < m_height);
+
+    return (m_cells[y * m_width + x] & CELL_ALIVE);
+}
+
+bool Life::HasChanged(int x, int y) const
+{
+    wxASSERT(x < m_width || y < m_height);
+
+    return (m_cells[y * m_width + x] & CELL_MARK);
+}
+
+void Life::SetCell(int x, int y, bool alive)
+{
+    wxASSERT(x < m_width || y < m_height);
+
+    // set the CELL_MARK flag to notify that this cell has changed
+    m_cells[y * m_width + x] = (alive? CELL_ALIVE : CELL_DEAD);
+}
+
+void Life::NextTic()
+{
+    /* 1st pass. Find and mark deaths and births for this generation.
+     *
+     * Rules:
+     *   An organism with <= 1 neighbors will die due to isolation.
+     *   An organism with >= 4 neighbors will die due to starvation.
+     *   New organisms are born in cells with exactly 3 neighbors.
+     */
+    for (int j = 0; j < m_height; j++)
+        for (int i = 0; i < m_width; i++)
+        {
+            int neighbors = GetNeighbors(i, j);
+            bool alive    = IsAlive(i, j);
+
+            /* Set CELL_MARK if this cell must change, clear it
+             * otherwise. We cannot toggle the CELL_ALIVE bit yet
+             * because all deaths and births are simultaneous (it
+             * would affect neighbouring cells).
+             */
+            if ((!alive && neighbors == 3) ||
+                (alive && (neighbors <= 1 || neighbors >= 4)))
+                m_cells[j * m_width + i] |= CELL_MARK;
+            else
+                m_cells[j * m_width + i] &= ~CELL_MARK;  
+        }
+
+    /* 2nd pass. Stabilize.
+     */
+    for (int j = 0; j < m_height; j++)
+        for (int i = 0; i < m_width; i++)
+        {
+            /* Toggle CELL_ALIVE for those cells marked in the
+             * previous pass. Do not clear the CELL_MARK bit yet;
+             * it is useful to know which cells have changed and
+             * thus must be updated in the screen.
+             */
+            if (m_cells[j * m_width + i] & CELL_MARK)
+                m_cells[j * m_width + i] ^= CELL_ALIVE;
+        }
+}
+
+int Life::GetNeighbors(int x, int y) const
+{
+    wxASSERT(x < m_width || y < m_height);
+
+    // count number of neighbors (wrap around board limits)
+    int neighbors = 0;
+    for (int j = y - 1; j <= y + 1; j++)
+        for (int i = x - 1; i <= x + 1; i++)
+        {
+            if (IsAlive( ((i < 0)? (i + m_width ) : (i % m_width)),
+                         ((j < 0)? (j + m_height) : (j % m_height)) ))
+                neighbors++;
+        }
+
+    // do not count ourselves
+    if (IsAlive(x, y)) neighbors--;
+
+    return neighbors;
+}    
+
+void Life::SetCell(int x, int y, Cell status)
+{
+    wxASSERT(x < m_width || y < m_height);
+
+    m_cells[y * m_width + x] = status;
+}
+
diff --git a/samples/life/life.def b/samples/life/life.def
new file mode 100644
index 0000000000..5f7709fc36
--- /dev/null
+++ b/samples/life/life.def
@@ -0,0 +1,7 @@
+NAME         Life
+DESCRIPTION  'Life! wxWindows application'
+EXETYPE      WINDOWS
+CODE         PRELOAD MOVEABLE DISCARDABLE
+DATA         PRELOAD MOVEABLE MULTIPLE
+HEAPSIZE     4048
+STACKSIZE    16000
diff --git a/samples/life/life.rc b/samples/life/life.rc
new file mode 100644
index 0000000000..96782b26a3
--- /dev/null
+++ b/samples/life/life.rc
@@ -0,0 +1,6 @@
+mondrian ICON "mondrian.ico"
+#include "wx/msw/wx.rc"
+
+reset BITMAP "bitmaps/reset.bmp"
+play  BITMAP "bitmaps/play.bmp"
+stop  BITMAP "bitmaps/stop.bmp"
diff --git a/samples/life/makefile.b32 b/samples/life/makefile.b32
new file mode 100644
index 0000000000..22869de03c
--- /dev/null
+++ b/samples/life/makefile.b32
@@ -0,0 +1,16 @@
+#
+# File:		makefile.b32
+# Author:	Julian Smart
+# Created:	1999
+# Updated:	
+# Copyright:
+#
+# Makefile : Builds sample for 32-bit BC++
+
+WXDIR = $(WXWIN)
+
+TARGET=life
+OBJECTS = $(TARGET).obj
+
+!include $(WXDIR)\src\makeprog.b32
+
diff --git a/samples/life/makefile.bcc b/samples/life/makefile.bcc
new file mode 100644
index 0000000000..2b70fcfb01
--- /dev/null
+++ b/samples/life/makefile.bcc
@@ -0,0 +1,19 @@
+#
+# File:		makefile.bcc
+# Author:	Julian Smart
+# Created:	1998
+# Updated:	
+#
+# Builds a BC++ 16-bit sample
+
+!if "$(WXWIN)" == ""
+!error You must define the WXWIN variable in autoexec.bat, e.g. WXWIN=c:\wx
+!endif
+
+WXDIR = $(WXWIN)
+
+TARGET=life
+OBJECTS=$(TARGET).obj
+
+!include $(WXDIR)\src\makeprog.bcc
+
diff --git a/samples/life/makefile.dos b/samples/life/makefile.dos
new file mode 100644
index 0000000000..6d660900e3
--- /dev/null
+++ b/samples/life/makefile.dos
@@ -0,0 +1,17 @@
+#
+# File:		makefile.dos
+# Author:	Julian Smart
+# Created:	1998
+# Updated:	
+#
+# Makefile : Builds 16-bit sample, VC++ 1.5
+# Use FINAL=1 argument to nmake to build final version with no debugging
+# info
+
+WXDIR = $(WXWIN)
+
+TARGET=life
+OBJECTS = $(TARGET).obj
+
+!include $(WXDIR)\src\makeprog.msc
+
diff --git a/samples/life/makefile.g95 b/samples/life/makefile.g95
new file mode 100644
index 0000000000..909171297f
--- /dev/null
+++ b/samples/life/makefile.g95
@@ -0,0 +1,16 @@
+#
+# File:         makefile.g95
+# Author:       Julian Smart
+# Created:      1999
+# Updated:
+# Copyright:    (c) Julian Smart, 1999
+#
+# Makefile for wxWindows sample (Cygwin/Mingw32).
+
+WXDIR = ../..
+
+TARGET=life
+OBJECTS = $(TARGET).o
+
+include $(WXDIR)/src/makeprog.g95
+
diff --git a/samples/life/makefile.vc b/samples/life/makefile.vc
new file mode 100644
index 0000000000..2e9f094ac0
--- /dev/null
+++ b/samples/life/makefile.vc
@@ -0,0 +1,18 @@
+#
+# File:		makefile.vc
+# Author:	Julian Smart
+# Created:	1999
+# Updated:	
+# Copyright:	(c) Julian Smart
+#
+# Makefile : Builds sample (VC++, WIN32)
+# Use FINAL=1 argument to nmake to build final version with no debug info.
+
+# Set WXDIR for your system
+WXDIR = $(WXWIN)
+
+PROGRAM=life
+OBJECTS = $(PROGRAM).obj
+
+!include $(WXDIR)\src\makeprog.vc
+
diff --git a/samples/life/makefile.wat b/samples/life/makefile.wat
new file mode 100644
index 0000000000..b959230311
--- /dev/null
+++ b/samples/life/makefile.wat
@@ -0,0 +1,15 @@
+#
+# Makefile for WATCOM
+#
+# Created by Julian Smart, January 1999
+# 
+#
+
+WXDIR = $(%WXWIN)
+
+PROGRAM = life
+OBJECTS = $(PROGRAM).obj
+
+!include $(WXDIR)\src\makeprog.wat
+
+
diff --git a/samples/life/mondrian.ico b/samples/life/mondrian.ico
new file mode 100644
index 0000000000000000000000000000000000000000..2310c5d275a87af295d5ea8dc79ea417a5e74c53
GIT binary patch
literal 766
zcmZQzU<5)11px*Sc)`TLAO@s0fLH;D9e|jTfdxnc0Z<M**w4TKL=5})Lnt5#WHKB$
zaDbtqp#doIAB-6O{|B*v7zjZ^AOa2W|NsACHyAJ}De?dRKnp(HN~rljcR;{k;{zQE
i@;}UZ|9Q?Fpp*~yJCwmWbLIrN`9Qm9%}2L?p!oo$cZ4ed

literal 0
HcmV?d00001

diff --git a/samples/life/mondrian.xpm b/samples/life/mondrian.xpm
new file mode 100644
index 0000000000..409f27a843
--- /dev/null
+++ b/samples/life/mondrian.xpm
@@ -0,0 +1,44 @@
+/* XPM */
+static char *mondrian_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 6 1",
+"  c Black",
+". c Blue",
+"X c #00bf00",
+"o c Red",
+"O c Yellow",
+"+ c Gray100",
+/* pixels */
+"                                ",
+" oooooo +++++++++++++++++++++++ ",
+" oooooo +++++++++++++++++++++++ ",
+" oooooo +++++++++++++++++++++++ ",
+" oooooo +++++++++++++++++++++++ ",
+" oooooo +++++++++++++++++++++++ ",
+" oooooo +++++++++++++++++++++++ ",
+" oooooo +++++++++++++++++++++++ ",
+"                                ",
+" ++++++ ++++++++++++++++++ .... ",
+" ++++++ ++++++++++++++++++ .... ",
+" ++++++ ++++++++++++++++++ .... ",
+" ++++++ ++++++++++++++++++ .... ",
+" ++++++ ++++++++++++++++++ .... ",
+" ++++++ ++++++++++++++++++      ",
+" ++++++ ++++++++++++++++++ ++++ ",
+" ++++++ ++++++++++++++++++ ++++ ",
+" ++++++ ++++++++++++++++++ ++++ ",
+" ++++++ ++++++++++++++++++ ++++ ",
+" ++++++ ++++++++++++++++++ ++++ ",
+" ++++++ ++++++++++++++++++ ++++ ",
+" ++++++ ++++++++++++++++++ ++++ ",
+" ++++++ ++++++++++++++++++ ++++ ",
+" ++++++ ++++++++++++++++++ ++++ ",
+" ++++++                    ++++ ",
+" ++++++ OOOOOOOOOOOO XXXXX ++++ ",
+" ++++++ OOOOOOOOOOOO XXXXX ++++ ",
+" ++++++ OOOOOOOOOOOO XXXXX ++++ ",
+" ++++++ OOOOOOOOOOOO XXXXX ++++ ",
+" ++++++ OOOOOOOOOOOO XXXXX ++++ ",
+" ++++++ OOOOOOOOOOOO XXXXX ++++ ",
+"                                "
+};
-- 
2.50.0

