25 Years of Programming
An open source source for C, C++, OWL, BASIC, MDB, XLS, DOT, and more...
Home   Projects   Up   Sitemap   Search   Blog   Forum+Chat   About Us   Privacy   Terms of Use   Feedback   FAQ   Images   Services   Payments   Humor   Music

The Game of Life cellular automaton program, Borland C++ 4.0 ObjectWindows

Screenshot of Game of Life, Windows version. Click for full size. An unusual Game of Life screenshot, from the MSDOS version in 1-dimensional mode. Click for full size.

WLife2d.cpp plays The Game of Life, a cellular automaton originated by John Conway. It is described in chapters about artificial life in the books Chaos Under Control, by Peak and Frame, and Complexity, by Waldrop.

An initial grid is set up in which some of the squares ("cells") are "on" and some are "off". The program then cycles through successive generations. Whether a cell remains on ("survives"), or turns off ("dies"), or turns on ("is born") depends on the number of neighbors it has. Aside from whatever value it has in the study of cellular automata, the shifting patterns are fun to watch.

This version of the program was developed with Borland C++ 4.0 and ObjectWindows Library (OWL) 2.0 for Windows 3.1.

Besides the standard Life game rules, you can create custom rules of your own and see how they behave.

The screen wraps in all directions, so the left and right borders are connected, as are the top and bottom. You can specify the height and width of the field to calculate. If you use the full window, each cell is one pixel on the screen, but if you use a smaller field, it is enlarged to fill the screen so the cells are bigger.

Click on the screenshot thumbnails above to view the full size images.

Much of this program's functionality is provided by SDibWindow, SDib, and other library classes you'll find on this site, so the project winds up being large. Links to the needed source code are provided in the listings below where they are referenced.

Download:

Click here to download wlife2d.zip (about 162 KB).

The zip contains these files that are unique to this project:

WLife2d.cpp Source code for the Borland C++ 4.0 ObjectWindows 2.0 (OWL) Windows 3.1 version. Same as the listing on this page.
wlife2dprojectnodes.png The same screenshot of the project nodes shown below on this page.
WLife2d.HPJ
WLife2d.RTF
WLife2d.HLP
Files for building the Windows 3.1 WinHelp file, plus the precompiled Help file. The RTF file is based on my helpfile.dot MSWord template.
WLife2d.RC
WLife2d.RH
The Windows resource definitions.
WLife2d.INI The Windows 3.1 .ini file in which the program records the location of its data files.
COPYING.TXT GNU GPL License, viewed best in Word or WordPad.
GRAYSCAL.MAP A gray scale color map file. This program can use Fractint-compatible .MAP files, but they are not particularly useful because it only uses a few colors. Monochrome or random palettes work best.
LifeNotes.RTF Project notes and commentary about The Game of Life, plus a To Do list. It is a section of COMPLEX.DOC. It's actually a Microsoft Word document saved as RTF for greater compatibility. WordPad can open it.
Life.CPP The MSDOS version of the program. Uses Borland BGI graphics. It has a 1d (1 dimensional) option that the Windows version doesn't have.
Life.XLS An Excel 5.0 (or later) worksheet set up to graph populations of consecutive generations, plus some calculations about one of the expansion ideas in the To Do list.
SPLITRUL.BAS The MSDOS version uses a single file LIFE.RUL to store all rule sets. The Windows version stores them in individual files. This QBASIC program splits the consolidated rule file into separate files.
Lifecpp1Dscreenshot.gif The same 1D screenshot displayed here and on the MSDOS version's page.
*.RUL
*.BMP
Data files: rule sets that had interesting characteristics, plus some bitmaps that can be loaded to provide standard starting boards. Includes a glider gun, 2 glider guns set up to fire at each other, and a pentomino.

This screenshot of the project nodes in the Borland C++ 4.0 IDE might help you set up the project nodes.

Screenshot showing the project nodes in the Borland C++ 4.0 IDE.

 

wlife2d.cpp

/*	wlife2d.cpp			12-15-01
	Copyright (C)1989-2001 Steven Whitney.
	Initially published by http://25yearsofprogramming.com.

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License (GPL)
	Version 3 as published by the Free Software Foundation.
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

	Game of Life, originated by John Conway.  Example of a cellular automaton.
	Win32s target.	(Originally adapted from wshowfs.cpp)

------
To Do:

give LifeRule a static ulong counter to number the random rule sets.

add an option to load a specific bitmap to start each random rule set, e.g. onedot.bmp,
so you can see how different rules affect one particular pattern.

the initial random screen of each new set isn't displayed: starts with first CALCULATED screen
is it shown for standard games?  file-loaded? Is there an Invalidate() missing somewhere?
Force display by calling EvPaint()?

functions that need changes for a 1d option are marked.  determine whether it can be
accomodated here, or, probably, needs a separate program.  (1d appends successive
generations to the screen, while 2d uses a whole screen each generation).

When you save a screen as BMP, there is one additional cycle before you get to
do the save.  You actually save the next generation.  Would rather save this one.  How?

use .INI file for some startup options:  HxW field size, starting .MAP file to load,
mono or color mode

-----
Notes:
--all notes and all non-version-specific to do are now in Life.doc

*/
#include <owl\owlpch.h>
#include <owl\chooseco.h>
#include <owl\opensave.h>
#include <owl\radiobut.h>
#include <owl\edit.h>
#include <owl\inputdia.h>
#include <classlib\arrays.h>
#include <math.h>
#pragma hdrstop

#include "c:\bcs\my.h"
#include "c:\bcs\library\filearay.h"
#include "c:\bcs\library\stopwatc.h"
#include "c:\bcs\library\doubrect.h"
#include "c:\bcs\library\sdibwin.h"

#include "c:\bcs\mylib.cpp"
#include "c:\bcs\library\doubrect.cpp"
#include "c:\bcs\library\sdib.cpp"
#include "c:\bcs\library\filearay.cpp"
#include "c:\bcs\library\stopwatc.cpp"

#include "wlife2d.rh"

#define NUMSTATES	16		// # of possible automaton states (dib is always 256 colors)

const char AppName[] = 	"Game of Life";
char INIFilename[] = 	"wlife2d.ini";
char HelpFilename[] = 	"wlife2d.hlp";

//////////////////////////////////////////////////////////////////////////////
// A LifeRule holds the rule that determines whether a cell survives or is
// born, and has the functions for doing so.
// Adding 1d option will require some changes:
// a flag to mark as 1d or 2d
// a flag for position dependent vs. outer totalistic, for either 1d or 2d
// an array for holding neighborhood matching strings (position dependent)
// maybe a better array for holding neighborcount values (outer totalistic)
// file load routine that can also determine type of rule it's loading
// either use some lines to explicitly declare, or define by filename extension.
//----------------------------------------------------------------------------
class LifeRule
{
public:
	LifeRule();		                   	// standard game only
	LifeRule(BOOL standardgame);		// choice of standard or random

	BOOL operator == (const LifeRule&) const;
	friend ostream& operator << (ostream&, const LifeRule&);  	// put to
	friend istream& operator >> (istream&, LifeRule&);			// get from

	void standardize();
	void randomize();
	void sortarrays();
	BOOL survive(int neighborcount);	// determines whether a cell survives
	BOOL born(int neighborcount);		// determines whether a cell is born

	string comment;			// user-supplied text comment about the set
	string filesource;		// name of the file this set came from, if any

protected:
	int s[9];               // counts defining when a cell will live
	int b[9];               // counts defining when a new cell is born
	int survivecount;  		// number of elements in s[] actually in use
	int borncount;          // number of elements in b[] actually in use

	static int compareints(int*,int*);	// for qsort
};
//----------------------------------------------------------------------------
// default constructor
LifeRule::LifeRule()
{
	standardize();
}						//constructor
//----------------------------------------------------------------------------
// constructor
LifeRule::LifeRule(BOOL standardgame)
{
	if(standardgame)
		standardize();
	else
		randomize();
}						//constructor
//----------------------------------------------------------------------------
// This should sort and determine if all elements of s[] and b[] are the same
// in both sets.  Could then be used to prevent duplicates.  Don't compare
// comment or filesource, but then beware when using containers of LifeRules.
BOOL LifeRule::operator == (const LifeRule& other) const
{
	return(&other == this);
}
//----------------------------------------------------------------------------
// Output to a stream.  File format (3 lines each rule set):
// It assumes s[] and b[] are already sorted, with the 10s, if any, at the end.
// 2 3 				<-Survive
// 3				<-Born
// Standard game	<-Comment
// 1d option requires changes
ostream& operator << (ostream& os, const LifeRule& r)
{
	// only write the active elements
	for(int i = 0 ; (i < 9) && (r.s[i] < 10) ; i++)
		os << r.s[i] << " ";
	os << endl;

	for(i = 0 ; (i < 9) && (r.b[i] < 10) ; i++)
		os << r.b[i] << " ";
	os << endl;

	os << r.comment << endl;

	return(os);
}
//----------------------------------------------------------------------------
// Read from a stream.
// 1d option requires changes
istream& operator >> (istream& is, LifeRule& r)
{
for(int i = 0 ; i < 9 ; i++)	// initialize arrays to non-values
	r.s[i] = r.b[i] = 10;

r.survivecount = r.borncount = 0;

string s;				// to receive unknown-length file line
int j;
char* buf;

is >> ws;				// eat whitespace in case there are blank lines

// READ THE survive VALUES

getline(is,s,'\n');            		// read the file line
buf = new char[s.length() + 1];		// make a matching size char array
strcpy(buf,s.c_str());              // copy the string to it
s.remove(0);						// free some memory
istrstream inbuf(buf);				// a stream we can read values from
i = 0;
while(inbuf >> j)					// while we can read values,
	if(i < 9)                       // (but not more than 9)
	{
		r.s[i++] = j;				// add them to the iset array
		r.survivecount++;
	}
delete[] buf;                       // deallocate buf
buf = 0;							// make it null

// READ THE born VALUES

getline(is,s,'\n');            		// read the file line
buf = new char[s.length() + 1];		// make a matching size char array
strcpy(buf,s.c_str());              // copy the string to it
s.remove(0);						// free some memory
istrstream inbuf2(buf);				// a stream we can read values from
i = 0;
while(inbuf2 >> j)					// while we can read values,
	if(i < 9)                       // (but not more than 9)
	{
		r.b[i++] = j;				// add them to the iset array
		r.borncount++;
	}
delete[] buf;                       // deallocate buf
buf = 0;							// make it null

r.comment.remove(0);
r.comment.read_file(is);	        // get new comment, if any (read to end of file)
r.filesource.remove(0);				// name of the file we read from is unknown here

r.sortarrays();						// ensure the arrays are sorted (old files weren't)

return(is);
}
//---------------------------------------------------------------------------
// determines whether a point survives, based on its rule
// 1d option requires changes
BOOL LifeRule::survive(int neighborcount)
{
for(int i = 0 ; i < survivecount ; i++)
	if(neighborcount == s[i])		// if its current neighbor count is listed,
		return(TRUE);				// then it survives.
return(FALSE);
}                     		//survive
//---------------------------------------------------------------------------
// determines whether a point is born, based on its rule
// 1d option requires changes
BOOL LifeRule::born(int neighborcount)
{
for(int i = 0 ; i < borncount ; i++)
	if(neighborcount == b[i])
		return(TRUE);
return(FALSE);
}                     		//born
//----------------------------------------------------------------------------
// initialize with born/survive rules for a standard Game of Life
// 1d option requires changes
void LifeRule::standardize()
{
	for(int i = 0 ; i < 9 ; i++)	// initialize arrays to non-values
		s[i] = b[i] = 10;

	s[0] = 2; 						// then set up as standard Life game
	s[1] = 3;
	b[0] = 3;

	survivecount = 2;
	borncount = 1;
	comment = "Standard Life Game Rules";
	filesource.remove(0);
}                       //standardize
//----------------------------------------------------------------------------
// comparison routine for qsort, sorts integers
// only works as a static or global.  Weird error as a member.
int LifeRule::compareints(int* l, int* r)
{
	return(*l - *r);
}
//----------------------------------------------------------------------------
// pack valid elements at the beginning so searches don't waste time testing unused ones
void LifeRule::sortarrays()
{
	qsort(s, 9, sizeof(s[0]),(int (*)(const void *,const void *)) compareints);
	qsort(b, 9, sizeof(b[0]),(int (*)(const void *,const void *)) compareints);
}
//----------------------------------------------------------------------------
// assign survive/birth rules randomly.  Fill from 1 to 9 slots in s[] and b[]
// with values from 0-8 without repeating any value.
// 1d option requires changes
void LifeRule::randomize()
{
int i, j, k;

for(i = 0 ; i < 9 ; i++)	// fill both arrays with entire number set 0-8
	s[i] = b[i] = i;        // This ensures no repeat values

j = random(9);				// get number of array elements to delete (max. 8 of the 9)
survivecount = 9 - j;
for(i = 0 ; i < j ; i++)
{
	do
	{
		k = random(9);		// find an array element not already deleted
	}
	while(s[k] == 10);      // it's already deleted if its count is 10
	s[k] = 10;				// delete by setting it to an impossible neighborcount of 10
}

j = random(9);				// number of array elements to delete (max. 8 of the 9)
borncount = 9 - j;
for(i = 0 ; i < j ; i++)
{
	do
	{
		k = random(9);		// get an array element not already deleted
	}
	while(b[k] == 10);
	b[k] = 10;				// delete by setting it to an impossible neighborcount
}
sortarrays();               // sort and get all the unused 10s to the end of the array
comment = "New random rule set";			// reminder
filesource.remove(0);
}                 			//randomize
//---------------------------------------------------------------------------
// 						end of class LifeRule
#if 0
// THIS CLASS WAS WORK IN PROGRESS AND WAS NEVER USED.
//////////////////////////////////////////////////////////////////////////////
// a LifeField is a gameboard in which a game of life takes place
// It contains everything needed for the maintenance and generation cycling
// of a cellular automaton.  Its purpose is to remove as much of this
// functionality as possible from the SLifeWindow so it can be used for
// more complex automata.  Should probably allow for position-dependent rules.
// 1d option requires changes. Maybe stop work on this.  It is not yet used.
class LifeField
{
public:
	LifeField();
	~LifeField();
	friend ostream& operator << (ostream&, LifeField&);  // put-to operator

protected:
	// pointers
	char** board;      			// this is the field itself, width * height

	// other variables
	// the rule table governing this game.  its format will have to be revised
	LifeRule liferule;

	BOOL randomrules;			// generate a new random rule set with each new screen?
	long generations;       	// # of generations
	long population;        	// count of live cells
	int statetally[NUMSTATES];	// holds tally of how many neighbors are in each state

	int width;					// size of board
	int height;
	double percentfill;			// % (0-100) of a new window to fill with random dots

	// normal functions
	BOOL initialize();
	BOOL initialize(const char* filename);  	// create rule set from file data
	void randomscreen();          	// set up screen with initial random dots
	BOOL makenewboard();			// create or recreate the board
	void eraseboard();				// erase to its background color, draw data area
	void cycle();					// calculate the next generation
	void resetcounts() { generations = population = 0L; }	// reset counting variables
	int commoneststateindex();

};
//----------------------------------------------------------------------------
// constructor
LifeField::LifeField()
{
width = 80;
height = 60;				// 80x60 and 160x120 are good
percentfill = 30.;
population = 0L;
generations = 0L;

// you must dimension it this way because since array subscripts are ints,
// you can't subscript into a single array that is longer than 32767 chars,
// which a 200x200 board would exceed.  You also can't use a string object, same reason.
// the result is (height) char arrays, each (width) wide.
// board[y][x] is a single char corresponding to a field (screen) position.
// y,x are backwards because x must vary fastest when traversing in scan-line fashion.
board = newarray2dim(char(),height,width);
}						//constructor
//----------------------------------------------------------------------------
// destructor
LifeField::~LifeField()
{
if(board)
	deletearray2dim(board,height);
}                    	//destructor
//----------------------------------------------------------------------------
BOOL LifeField::initialize()
{
// randomscreen();
return(TRUE);
}					//initialize(randomly assign)
//----------------------------------------------------------------------------
// change to determine commonest state
// determine commonest state among neighbors.
// starts search at a random point within statetally[] and loops around
// through beginning if necessary.  Thus, if two or more state tie for the
// mode, the choice of "winner" will be random, not determined by placement.
// returns the index within statetally[] of the commonest state.
// (not yet used)
int LifeField::commoneststateindex()
{
int mode;                       // the commonest state
int highcount = 0;              // the number of occurrences of the commonest state
int start = random(NUMSTATES);	// 0-15, a random starting loc. within statetally[]
int i = start;					// and save it for testing.
do
{
	if(statetally[i] > highcount)
	{
		highcount = statetally[i];
		mode = i;
	}
	if(++i >= NUMSTATES)		// reached end of array, but not back to start point,
		i = 0;					// so wrap to array start, to continue
}
while(i != start);
return(mode);
}                      	//commoneststateindex
//----------------------------------------------------------------------------
// calculate cell states for the next generation
// #error THIS IS NOWHERE NEAR DONE, AND IS NOTHING MORE THAN IDEAS OF HOW
// THIS FUNCTION MIGHT BE DONE FOR MORE COMPLICATED RULE SETS WHERE NEIGHBORS
// MAY BE NOT JUST ON OR OFF AND WHERE POSITION DEPENDENCE MAY BE USED.
void LifeField::cycle()
{
// a second board to calculate new points onto
char** newboard = newarray2dim(char(),height,width);

int x, y;					// point whose neighbors are being counted
int maxx = width - 1;
int maxy = height - 1;

// build in this array a picture of the neighborhood that takes screen-wrap into account.
// It includes a slot for the point itself.  (unsure whether it should or not)
// this array becomes the test in an "if" clause of a rule.  See complex.doc.
// (also allows a slot for a null, in case we want to make a string out of it)
// (but beware it probably contains nulls anyway)
char neighborhood[10];

for(y = 0 ; y < height ; y++)
{
	int above = wrap(y - 1,0,maxy);
	int below = wrap(y + 1,0,maxy);
	for(x = 0 ; x < width ; x++)
	{
		// first just build the neighborhood for testing.  no tallying, etc. yet
		// screen wraps in all directions.  note (Height,Width) is off screen

		int left = wrap(x - 1,0,maxx);
		int right = wrap(x + 1,0,maxx);

		neighborhood[0] = board[above][left];		// above left
		neighborhood[1] = board[above][x];          // above
		neighborhood[2] = board[above][right];      // above right
		neighborhood[3] = board[y][left];           // point to left
		neighborhood[4] = board[y][x];  			// (the point itself)
		neighborhood[5] = board[y][right];  		// point to right
		neighborhood[6] = board[below][left];       // below left
		neighborhood[7] = board[below][x];      	// below
		neighborhood[8] = board[below][right];  	// below right

		// now use neighborhood to compute various statistics we might use for rules
		// that is, neighborcount = number of non-state-0 neighbors
		// and you can tally the number of neighbors in each state into statetally[]

		// initialize counters, etc.
		for(int i = 0 ; i < NUMSTATES ; i++)          // zero out tally values
			statetally[i] = 0;
		int neighborcount = 0;

		// here, determine what the new state should be
		int newstate = 0;		// whatever the criterion is
		if(neighborcount)
			newstate = 1;

		// and set it in the NEW board
		newboard[y][x] = (char)newstate;
	}															// end for(x)
}																// end for(y)
// destroy the old board, and set its pointer to point at the new board
deletearray2dim(board,height);
board = newboard;

generations++;
}               		//cycle
//---------------------------------------------------------------------------
// 							end class LifeField
//----------------------------------------------------------------------------
#endif	// 0
//////////////////////////////////////////////////////////////////////////////
// transfer information for the rule editor dialog box
struct RuleEditorStruct
{
	RuleEditorStruct() { filechars[0] = 0; }
	char filechars[2000];
};
//////////////////////////////////////////////////////////////////////////////
// dialog box for editing the current survive/born rule set.
class SRulesEditDialog : public TDialog
{
public:
	SRulesEditDialog(TWindow*, RuleEditorStruct*, const string&);	// constructor
protected:
	void CmHelp()
		{ WinHelp(HelpFilename,HELP_PARTIALKEY,(DWORD)(LPSTR)"Rule Editor Dialog Box"); }

DECLARE_RESPONSE_TABLE(SRulesEditDialog);
};
DEFINE_RESPONSE_TABLE1(SRulesEditDialog,TDialog)
	EV_COMMAND(IDHELP,CmHelp),
END_RESPONSE_TABLE;
//----------------------------------------------------------------------------
// constructor
SRulesEditDialog::SRulesEditDialog(TWindow* parent, RuleEditorStruct* tts,
	const string& filename) : TDialog(parent,TResId(RULEFILEEDITOR))
{
	// if caption is wider than the box, it's left-aligned, truncated at right
	SetCaption(filename.c_str());		// replaces resource's generic caption
	new TEdit(this,IDC_RULESEDIT,sizeof(tts->filechars));
	SetTransferBuffer(tts);
}
//----------------------------------------------------------------------------
//					end class SRulesEditDialog
//////////////////////////////////////////////////////////////////////////////
// transfer struct for the ViewOptionsDialog
struct ViewOpStruct
{
	ViewOpStruct();    			// ctor
								// most of these BOOLS are used "as is" throughout pgm
	BOOL autocycle;				// whether to automatically calc a generation each IdleAction
	BOOL autorandomizecolors;	// whether to randomize colors with each new rule set
	BOOL autoaborttests;		// tests for dead system (no births or deaths)
	BOOL randomrules;       	// whether to use a new random rule set with each new screen
	BOOL byneighborcount;       // YOU MUST SET dispmode TO MATCH WHAT THIS DICTATES
	BOOL beepon;                // use sound effects
	BOOL fittowindow;           // BUT YOU MUST SET SDIBWINDOW'S TO MATCH THIS ONE

	char boardsize[20]; 		// text for dibwidth, dibheight
	char percentfill[10]; 		// text for percentfill
	char timelimit[10]; 		// text for time limit
};								//ViewOpStruct
//----------------------------------------------------------------------------
// constructor
ViewOpStruct::ViewOpStruct()
{
autocycle = TRUE;			// set these to what you want for the program startup defaults
autorandomizecolors = FALSE;
autoaborttests = TRUE;
randomrules = FALSE;        // always starts with standard game
byneighborcount = FALSE;
beepon = TRUE;
fittowindow = TRUE;

boardsize[0] = percentfill[0] = timelimit[0] = 0;
}                       		//constructor
//----------------------------------------------------------------------------
//							end class ViewOpStruct
//////////////////////////////////////////////////////////////////////////////
// Options dialog for the View menu.
class ViewOptionsDialog : public TDialog
{
public:
	ViewOptionsDialog(TWindow* parent, ViewOpStruct* ots);

protected:
	void CmHelp();

DECLARE_RESPONSE_TABLE(ViewOptionsDialog);
};
//----------------------------------------------------------------------------
DEFINE_RESPONSE_TABLE1(ViewOptionsDialog,TDialog)
	EV_COMMAND(IDHELP,CmHelp),
END_RESPONSE_TABLE;
//----------------------------------------------------------------------------
// constructor
ViewOptionsDialog::ViewOptionsDialog(TWindow* parent, ViewOpStruct* ots)
	: TDialog(parent,TResId(VIEWOPTIONS))
{
new TRadioButton(this,IDC_AUTOCYCLE,0);  	// 0 = TGroupBox*
new TRadioButton(this,IDC_RANDCOLORS,0);
new TRadioButton(this,IDC_ABORTTESTS,0);
new TRadioButton(this,IDC_RANDRULES,0);
new TRadioButton(this,IDC_BYNEIGHBORCOUNT,0);
new TRadioButton(this,IDC_SOUND,0);
new TRadioButton(this,IDC_FITTOWINDOW,0);

new TEdit(this,IDC_BOARDSIZE,20);
new TEdit(this,IDC_PCTFILL,10);
new TEdit(this,IDC_TIMELIMIT,10);

SetTransferBuffer(ots);
}                            	// constructor
//----------------------------------------------------------------------------
void ViewOptionsDialog::CmHelp()
{ WinHelp(HelpFilename,HELP_PARTIALKEY,(DWORD)(LPSTR)"View Menu"); }
								// CmHelp
//----------------------------------------------------------------------------
//					// end class ViewOptionsDialog
//////////////////////////////////////////////////////////////////////////////
// A window in which a game of life takes place
class SLifeWindow : public SDibWindow
{
public:
	SLifeWindow(TWindow* parent = 0);
	~SLifeWindow();
	friend ostream& operator << (ostream&, SLifeWindow&);  // put-to operator

	BOOL IdleAction(long idlecount);

protected:
	// variables
	LifeRule liferule;		// the born/survive rules governing the current game
	ViewOpStruct ots;		// contains several View options variables

	enum dispmodes { colorbyneighborcount, monochrome } dispmode;
	long generations;       // # of generations
	long population;        // count of live cells
	long lastpop[10];		// previous generations live cell counts

	int dibwidth;			// size of dib, if size is user-specified
	int dibheight;			// if either is 0, dib is sized to current window
	double percentfill;		// % (0-100) of a new window to fill with random dots
	uint timelimit;			// in seconds; when exceeded, a new game starts

	// functions
	BOOL initialize(const char* filename);  // create rule set from file data
	void randomscreen();          			// set up screen with initial random dots
	void erasedib();						// erase to its background color
	void draw();
	void resetcounts();						// reset counting variables
	void setdispmode(dispmodes);			// changes dispmode
	void UpdateViewMenu();        			// check menu items to match settings
	void SetupCaption();					// builds window caption and sets it

	// overridden virtuals
	BOOL CanClose();

	// event handlers
	void CmFileSave();
	void CmFileLoadBMP();
	void CmFileNew();
	void CmFileOpen();
	void CmFileEditAgain();
	void CmColorsRandomize(); 	// different from SDibWindow's
	void CmEditTransforms();
	void CmEditMonoColor();
	void CmViewAutoCycle();
	void CmViewOptions();
	void CmViewPopulation();
	void CmHelpAbout();

DECLARE_RESPONSE_TABLE(SLifeWindow);
};
DEFINE_RESPONSE_TABLE1(SLifeWindow, SDibWindow)
	EV_COMMAND(CM_EDITTRANSFORMS,CmEditTransforms),
	EV_COMMAND(CM_FILENEW,CmFileNew),
	EV_COMMAND(CM_FILEOPEN,CmFileOpen),
	EV_COMMAND(CM_FILEEDITAGAIN,CmFileEditAgain),
	EV_COMMAND(CM_FILESAVE,CmFileSave),
	EV_COMMAND(CM_FILELOADBMP,CmFileLoadBMP),
	EV_COMMAND(CM_COLORSRANDOMIZE,CmColorsRandomize),
	EV_COMMAND(CM_EDITMONOCOLOR,CmEditMonoColor),
	EV_COMMAND(CM_VIEWAUTOCYCLE,CmViewAutoCycle),
	EV_COMMAND(CM_VIEWOPTIONS,CmViewOptions),
	EV_COMMAND(CM_VIEWPOPULATION,CmViewPopulation),
	EV_COMMAND(CM_HELPABOUT,CmHelpAbout),
END_RESPONSE_TABLE;
//----------------------------------------------------------------------------
// constructor
SLifeWindow::SLifeWindow(TWindow* parent) : SDibWindow(parent)
{
dibwidth = 160;						// 0,0 means size the dib to window later
dibheight = 120;					// 80x60 and 160x120 are good

// discard the dib created by SDibWindow, and recreate it in the size we want.
delete dib;
dib = 0;
dib = new SDib(dibwidth,dibheight,256,DIB_RGB_COLORS);
dib->SetColor(0,TColor::Black);		// background to match window
dib->SetColor(1,TColor::LtGreen);	// a good starting foreground color
dib->RandomizeColors(2);			// preserve the 2 colors we just set

// initialize other variables
dispmode = monochrome;			// don't use setdispmode because window isn't valid yet
percentfill = 30.;				// good for standard rule set
timelimit = 60000;				// startup default (it's a standard game)
resetcounts();
}								//constructor
//----------------------------------------------------------------------------
// destructor
SLifeWindow::~SLifeWindow()
{
}            		        	//destructor
//----------------------------------------------------------------------------
// build and set window caption
void SLifeWindow::SetupCaption()
{
// if it came from a file, use its name, else the comment, either "standard" or "random"
string caption = string(GetApplication()->GetName()) + string(" - ");
caption += liferule.filesource.length() ? liferule.filesource : liferule.comment;
SetParentCaption(caption);
}		                  		//SetupCaption
//----------------------------------------------------------------------------
// reset counting variables
void SLifeWindow::resetcounts()
{
generations = population = 0L;
for(int i = 0 ; i < 10 ; i++)
	lastpop[i] = 0L;
}                            	//resetcounts
//----------------------------------------------------------------------------
// Initialize a new rule set, read from a file.
// If it fails, it reverts liferule to a standard Life game.
// returns TRUE if successful, FALSE if not.
BOOL SLifeWindow::initialize(const char* filename)
{
string buf(filename);		// create string containing file name
buf.to_upper();				// toupper for error messages
if(!buf.contains(".RUL"))
{
	string s = buf + "\nFile extension must be .RUL";
	MessageBox(s.c_str(),"Cannot Load File",MB_OK);
	return(FALSE);
}
ifstream infile(buf.c_str());
if(!infile)
{
	string s = "File " + buf + "\nnot found.\n";
	MessageBox(s.c_str(),"File Error",MB_OK);
	return(FALSE);
}
infile >> liferule;
liferule.filesource = filename;	// remember which file was read

ots.randomrules = FALSE;		// prevents replacing the set we've just loaded!
ots.autoaborttests = FALSE;		// just draw this design until user quits

randomscreen();					// give this set a new starting screen
return(TRUE);
}								//initialize(read from file)
//----------------------------------------------------------------------------
// fill the dib with color(0) - seems there should be an easier way to do this
// note color(0) instead of Black in SDib::Erase()
void SLifeWindow::erasedib()
{
dib->Erase(dib->GetColor(0));
Invalidate(FALSE);
}  	        					//erasedib
//----------------------------------------------------------------------------
// use an existing .BMP file as the source for a new playing field (dib)
// (intentionally does not use SDibWindow's LoadBMP fn)
void SLifeWindow::CmFileLoadBMP()
{
chdir(DATADir.c_str());

// I don't plan to allow multiple SLifeWindows, but remember
// #error: note this may be shared by all of them.
static TOpenSaveDialog::TData inbfd(OFN_FILEMUSTEXIST,				// flags ok
	"Bitmap Files (*.BMP)|*.BMP|All Files (*.*)|*.*|",0,0,"BMP");
if((TFileOpenDialog(this,inbfd)).Execute() == IDOK)
{
	if(makenewdib(inbfd.FileName,dibwidth,dibheight,FALSE))
	{
		if(ots.autorandomizecolors)
			CmColorsRandomize();
		Invalidate(FALSE);
		resetcounts();			// blank screen: reset all to zero
	}
	char buf[MAXPATH];     		// see also Get/SetCurrentDirectory (Win32 only)
	getcwd(buf,MAXPATH);
	DATADir = buf;
}
else
	if(inbfd.Error)
		MessageBox("Dialog box returned error.","Warning",MB_OK);
}                 				//CmFileLoadBMP
//---------------------------------------------------------------------------
// set up a new screen filled with random dots by the percentage specified by percentfill
// 1d option maybe requires changes
void SLifeWindow::randomscreen()
{
makenewdib(0,dibwidth,dibheight,FALSE);			// creates new blank dib
if(ots.autorandomizecolors)
	CmColorsRandomize();
erasedib();
resetcounts();									// blank screen: reset all to zero
population = dib->RandomizeField(percentfill); 	// this creates the random dots
// this is a central place to do it: randomscreen() is called for both file and random sets.
SetupCaption();
Invalidate(FALSE);

// enable this when writing successive populations to disk (also see end of draw())
// ofstream outfile("life.pop");
// outfile << (liferule.filesource.length() ? liferule.filesource : liferule.comment) << endl;
// outfile << population << endl;
}                   			//randomscreen
//----------------------------------------------------------------------------
// If liferule is ever a container of multiple LifeRules, this could be used to
// write the entire set, so it can be reconstructed later.
ostream& operator << (ostream& os, SLifeWindow&)
{
	return(os);
}
//----------------------------------------------------------------------------
// calculate and display 1 generation: calculate it FROM one dib to another,
// then let Paint() blt the dib to the screen.
// This should always do one entire screenful before returning,
// so that the dib will always be fully-drawn (not in progress) when it's painted.
// 1d option requires changes, if goal is to grow downward on screen.
void SLifeWindow::draw()
{
if(!ots.autocycle)					// do nothing if user turned it off
	return;

// don't waste system time if this isn't the active application, or is minimized
if(IsIconic() || (GetActiveWindow() != GetApplication()->GetMainWindow()->HWindow))
	return;

Invalidate(FALSE);		// force unconditional screen display refresh

// if system is dead, start a new game.  also causes the first game to load at startup.
// not an exact test.  even a dead system can have cycles going that fool the test for the
// same populations.  The 10 levels of test prevent it thinking it's dead when it isn't.
if(ots.autoaborttests)
{
	BOOL reset = TRUE;
	for(int i = 0 ; i < 10 ; i++)
		if(population != lastpop[i])
		{
			reset = FALSE;
			break;
		}
	if(sw.split() >= timelimit)		// timer overrides even if pop still changing
		reset = TRUE;
	if(reset)
	{
		if(ots.beepon)
			sndPlaySound("sldwst.wav",SND_SYNC);

// THESE ARE INTERESTING POSSIBILITIES COPIED FROM WINBROT:
// 		// here so you only do it if pgm is in "auto-run" mode (not for every CmFileNew)
// 		if(MapFiles.GetItemsInContainer())		// choose a .MAP file at random, for variety
// 			dib->LoadColors(MapFiles.Random());
// 		if(!random(2))					// doubles the available palettes
// 			dib->ReverseColors();       // avoid the Invalidate() in CmColorsReverse();
// 		dib->RotateColors(random(256));	// whichever palette you have, rotate it a bit
// 		if(autorotatecolors)			// only if user has turned autorotate ON, set a direction
// 		{
// 			autorotatecolors = random(2);	// set to 0 or 1, then 1 more to get 1 or -1,
// 			CmViewAutoRotateColors();		// and force menu item to match
// 		}

		// old method, is probably fine.
// 		CmFileNew();

		// new method, seems to work, not extensively tested yet:
		// draw() is already time-greedy while Windows messages pile up for all apps.
		// instead of invoking a lot of hard-to-trace code that may generate still more
		// messages during this period when processing is suspended,
		// just post the exact same message as if user pressed File|New, for handling by
		// the MessageLoop after this IdleAction returns.
		PostMessage(WM_COMMAND, CM_FILENEW);
		return;
	}
}									// end if(ots.autoaborttests)

for(int i = 9 ; i > 0 ; i--)     	// archive populations for next dead system test
	lastpop[i] = lastpop[i - 1];
lastpop[0] = population;
SDib source(*dib);					// a copy of dib as a source for our calculations
int x, y;							// point whose neighbors are being counted
int neighborcount;					// number of live neighbors
BOOL isalive;						// true if a point either survives or is born
for(y = 0 ; y < source.Height() ; y++)
{
	// screen wraps in all directions.  note (Height,Width) is off screen
	int above = wrap(y - 1,0,source.Height() - 1);
	int below = wrap(y + 1,0,source.Height() - 1);
	for(x = 0 ; x < source.Width() ; x++)
	{
		int left = wrap(x - 1,0,source.Width() - 1);
		int right = wrap(x + 1,0,source.Width() - 1);

		neighborcount = 0;

		if(source.GetPixelByte(left,above) != 0)   		// above left
			neighborcount++;
		if(source.GetPixelByte(x,above) != 0)       	// above
			neighborcount++;
		if(source.GetPixelByte(right,above) != 0)  		// above right
			neighborcount++;
		if(source.GetPixelByte(left,y) != 0)       		// point to left
			neighborcount++;
//        (source.GetPixelByte(x,y) != 0)  				// (the point itself)
		if(source.GetPixelByte(right,y) != 0)       	// point to right
			neighborcount++;
		if(source.GetPixelByte(left,below) != 0)   		// below left
			neighborcount++;
		if(source.GetPixelByte(x,below) != 0)      		// below
			neighborcount++;
		if(source.GetPixelByte(right,below) != 0)   	// below right
			neighborcount++;

		if(source.GetPixelByte(x,y) != 0)   			// if point already alive,
		{                             					// see if it remains alive
			isalive = liferule.survive(neighborcount);
			if(!isalive)
				population--;
		}
		else                            				// if cell is dead,
		{                             					// see whether it is born
			isalive = liferule.born(neighborcount);
			if(isalive)
				population++;
		}
		// set pixel in the destination dib in monochrome, either on or off
		dib->SetPixelByte(x,y,isalive ? (uchar)1 : (uchar)0);
// 		dibdc.SetPixel(x,y,isalive ? dib->GetColor(1) : dib->GetColor(0));
	}															// end for(x)
}																// end for(y)
// recolor points according to how many neighbors they have NOW.
// increases calculation time
// This makes pretty designs, but creates a somewhat misleading display.  In a 2-state
// automaton, there are only 2 states, and so should only be 2 colors (monochrome).
// Using more makes you think there are more states than there are, and it only
// works because the calculation considers ANY non-state-zero to be state 1.
// If multi-state automata are made possible by using LifeField, this
// color-by-neighborcount option will be lost.
if(dispmode == colorbyneighborcount)
{
	for(y = 0 ; y < dib->Height() ; y++)
	{
		// screen wraps in all directions.  note (Height,Width) is off screen
		int above = wrap(y - 1,0,dib->Height() - 1);
		int below = wrap(y + 1,0,dib->Height() - 1);

		for(x = 0 ; x < dib->Width() ; x++)
		{
			// skip everything if the point is off (dead) anyway
			if(dib->GetPixelByte(x,y) == 0)
				continue;

			int left = wrap(x - 1,0,dib->Width() - 1);
			int right = wrap(x + 1,0,dib->Width() - 1);

			neighborcount = 0;

			if(dib->GetPixelByte(left,above) != 0)  // above left
				neighborcount++;
			if(dib->GetPixelByte(x,above) != 0)   	// above
				neighborcount++;
			if(dib->GetPixelByte(right,above) != 0) // above right
				neighborcount++;
			if(dib->GetPixelByte(left,y) != 0)    	// point to left
				neighborcount++;
			if(dib->GetPixelByte(right,y) != 0)    	// point to right
				neighborcount++;
			if(dib->GetPixelByte(left,below) != 0) 	// below left
				neighborcount++;
			if(dib->GetPixelByte(x,below) != 0)     // below
				neighborcount++;
			if(dib->GetPixelByte(right,below) != 0) // below right
				neighborcount++;

			// remember if neighborcount is 0, you can't use that color!
			dib->SetPixelByte(x,y,(uchar)max(neighborcount,1)); 		// revise its color
// 			dibdc.SetPixel(x,y,dib->GetColor(max(neighborcount,1))); 	// revise its color
		}														// end for(x)
	}															// end for(y)
}										// end recolor by new neighborcount
// enable this to write successive populations to disk
// ofstream("life.pop",ios::app) << population << endl;

generations++;
}               		//draw
//----------------------------------------------------------------------------
// user sets view options in a dialog
void SLifeWindow::CmViewOptions()
{
// explicitly set only the variables not used "in place" in the ots by pgm
ots.byneighborcount = (dispmode == colorbyneighborcount);
ots.fittowindow = fittowindow;								// match SDibWindow's
ostrstream(ots.boardsize,sizeof(ots.boardsize)) << dib->Width() << " " << dib->Height() << ends;
ostrstream(ots.percentfill,sizeof(ots.percentfill)) << percentfill << ends;
ostrstream(ots.timelimit,sizeof(ots.timelimit)) << ((double)timelimit / 60.) << ends;

if(ViewOptionsDialog(this,&ots).Execute() != IDOK)
	return;

setdispmode(ots.byneighborcount ? colorbyneighborcount : monochrome);
if(fittowindow != ots.fittowindow)      // set SDibWindow's to match
	CmViewFitToWindow();

double d = percentfill;
istrstream(ots.percentfill) >> d;
if((d > 0.) && (d < 100.))
	percentfill = d;               		// must set this before randomscreen(), below

int x = 0, y = 0;
istrstream(ots.boardsize) >> x >> y;
if((x >= 0) && (y >= 0))				// if both are valid
{
	dibwidth = x;
	dibheight = y;
}

d = 0.;
istrstream(ots.timelimit) >> d;
d *= 60.;           	// convert to seconds
if(d < 0.)     			// #error use range()?
	d = 0.;
if(d > MAXUINT)
	d = MAXUINT;
timelimit = (uint)d;

// disabled to allow changing options for the current rule set AND screen.
// randomscreen();		// make new dib and fill it up, but keep the current rule set
UpdateViewMenu();	// update the View menu checkmarks
}								//CmViewOptions
//----------------------------------------------------------------------------
// check or uncheck the View Menu items, as needed, to match current state.
// these View Menu items are all handy to have available through accelerators
void SLifeWindow::UpdateViewMenu()
{
TMenu menu(*(GetApplication()->GetMainWindow()));
menu.CheckMenuItem(CM_VIEWAUTOCYCLE,
					MF_BYCOMMAND | (ots.autocycle ? MF_CHECKED : MF_UNCHECKED));
}              					// UpdateViewMenu
//----------------------------------------------------------------------------
// reset the display mode and do whatever is required to implement the new mode
void SLifeWindow::setdispmode(dispmodes newmode)
{
dispmode = newmode;
UpdateViewMenu();
}                		//setdispmode
//----------------------------------------------------------------------------
void SLifeWindow::CmViewAutoCycle()
{
ots.autocycle = !ots.autocycle;
UpdateViewMenu();
}
//----------------------------------------------------------------------------
// set monochrome mode and specify its color
void SLifeWindow::CmEditMonoColor()
{
TChooseColorDialog::TData choose;		// variable declaration: a structure
choose.Flags = CC_FULLOPEN | CC_RGBINIT;
choose.Color = dib->GetColor(1);
choose.CustColors = MyCustomColors;		// a global colors array in my.h
if(TChooseColorDialog(this, choose).Execute() == IDOK)
{
	dib->SetColor(0,TColor::Black);		// start with black background; user can change it.
	dib->SetColor(1,choose.Color);   	// obviously, if we specified the color,
	setdispmode(monochrome);			// we want monochrome mode
	ots.autorandomizecolors = FALSE;	// and no autorandomize
}
}                   	//CmEditMonoColor
//----------------------------------------------------------------------------
void SLifeWindow::CmColorsRandomize()
{
int startcolor = 0;

// this test is for pgm startup
// comment is only this at program start.  also monochrome at start.
if((liferule.comment == "Standard Life Game Rules") && (dispmode == monochrome))
{
	dib->SetColor(0,TColor::Black);				// background black
	dib->SetColor(1,TColor::LtGreen);			// a good starting foreground color
	startcolor = 2;
}
dib->RandomizeColors(startcolor);
}                     	//CmColorsRandomize
//----------------------------------------------------------------------------
// view stats about the current game
void SLifeWindow::CmViewPopulation()
{
ostrstream os;
os << "Generations: " << generations << endl;
os << "Population: " << population << endl;
os << "Field, Width " << dib->Width() << " x Height " << dib->Height();
os << " = Total: " << dib->Height() * dib->Width();
os << ends;
MessageBox(os.str(),"Statistics",MB_OK);
delete[] os.str();
}                		//CmViewPopulation
//----------------------------------------------------------------------------
// Edit the rule set file most recently opened (edited or loaded)
// in a dialog box with a single multi-line edit control.
// #error looks like it's (current rule set), not (edited or loaded).  comment obsolete?
// After editing, the new design is automatically displayed.
void SLifeWindow::CmFileEditAgain()
{
static RuleEditorStruct tts;    		// static: it's big, and also allows buffer carryover
string filename = liferule.filesource; 	// edit the current rule set
if(!filename.length())					// but if it didn't come from a file,
{   									// we must save it first.
	if(MessageBox("Must save first.  Save now?",
				"Current rule set is not from a file",MB_YESNO) != IDYES)
		return;
	CmFileSave();						// so make user save it first,
	filename = liferule.filesource;		// and retrieve the name it now has
	if(!filename.length())				// if user aborted the save
		return;							// just quit.
}
ifstream infile(filename.c_str(),ios::binary);	// binary preserves CR/LF
if(infile)										// if file already exists, read its data
{
	// put chars from file into transfer struct
	infile.get(tts.filechars,sizeof(tts.filechars),'\0');
	infile.close();							// must close before renaming
}
// else                         // if remmed out, the previous edit buffer is automatically
// 	tts.filechars[0] = '\0';	// carried over & inserted: can be helpful, or confusing.

// the editing is performed on the tts buffer; the code block writes the text to the file
if(SRulesEditDialog(this,&tts,filename).Execute() == IDOK)
{
	// save the text into new file; binary because it already has CRLFs
	ofstream outfile(backupfile(filename).c_str(),ios::binary);
	outfile << tts.filechars;
	outfile.close();                // must close before initialize reads it
	initialize(filename.c_str());	// reload the just-edited file
}
}                    		//CmFileEditAgain
//----------------------------------------------------------------------------
// choose file to edit (and for subsequent re-edits)
// maybe this should be eliminated:
// just require that a file be File|Opened (loaded) before you can File|Edit Again
void SLifeWindow::CmEditTransforms()
{
chdir(DATADir.c_str());
static TOpenSaveDialog::TData filedata(    			// flags ok
	OFN_PATHMUSTEXIST | OFN_CREATEPROMPT | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY,
	"Rule Files (*.RUL)|*.RUL|All Files (*.*)|*.*|",0,0,"RUL");
if((TFileOpenDialog(this,filedata)).Execute() == IDOK)	// select a file
{
	string filename(filedata.FileName);	// can only edit .RUL
	if(filename.contains(".rul"))
	{
		// initialize here to make sure THIS file is the one loaded, although
		// the initialize() does a lot of work that will just be redone after editing
		if(initialize(filename.c_str()))
			CmFileEditAgain();
	}
	else
		MessageBox("Selected file not .RUL","Cannot Edit",MB_OK);
	char buf[MAXPATH];     		// see also Get/SetCurrentDirectory (Win32 only)
	getcwd(buf,MAXPATH);
	DATADir = buf;
}
else
	if(filedata.Error)
		MessageBox("Dialog box returned an error code.","Warning",MB_OK);
}                     		//CmEditTransforms
//----------------------------------------------------------------------------
// Load next rule set in the list.  If a file fails, it goes on to the next one.
void SLifeWindow::CmFileNew()
{
sw.reset();					// every new rule set stops the timer
if(!ots.autocycle)			// unsure if this IS desirable: might want to save board first.
	CmViewAutoCycle();		// regardless of source, we want it on.
while(FileList.GetItemsInContainer())
{
	// copy a name from the list, delete it, try to use it to initialize
	if(initialize(FileList.GetNext().c_str()))
		return;
}
// last resort, just creates new random screen for the current liferule.
// thus when the list becomes empty, the most recent liferule remains in effect.
if(ots.randomrules)		// With each new field, you also have the option of a new rule set.
	liferule.randomize();
randomscreen();
sw.start();				// every new random rule set starts the timer
}                         	//CmFileNew
//----------------------------------------------------------------------------
// save the current rule set to a .RUL file.
// Do not merge handling for File|Save and File|SaveAs into 1 fn; they're completely different,
// and see FileSave calls where you must force user to save .RUL: saving as BMP won't suffice.
void SLifeWindow::CmFileSave()
{
chdir(DATADir.c_str());
static TOpenSaveDialog::TData filedata(    			// flags ok
		OFN_PATHMUSTEXIST | OFN_CREATEPROMPT | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY,
		"Rule Files (*.RUL)|*.RUL|All Files (*.*)|*.*|",0,0,"RUL");
if(TFileSaveDialog(this,filedata).Execute() == IDOK)		// select file
{
	char buf[MAXPATH] = {0}; 		// also used later; see below

	// only prompt for a comment if the set isn't already from a file
	// if it's from a file, it likely already has one, which can be any artibrary length
	if(!liferule.filesource.length())
	{
		// comment will be standard or random
		ostrstream(buf,sizeof(buf)) << liferule.comment << ends;
		if(TInputDialog(this,"Add Comment for Rule Set File?",
							 "Enter desired annotation, if any:",
							 buf,sizeof(buf)).Execute() == IDOK)
		{
			liferule.comment = buf;
		}
	}
	ofstream(filedata.FileName) << liferule;
	liferule.filesource = filedata.FileName;	// now it has an associated file
	SetupCaption();								// file name (for caption) may have changed
	getcwd(buf,MAXPATH);
	DATADir = buf;
}
}                     			//CmFileSave
//----------------------------------------------------------------------------
void SLifeWindow::CmFileOpen()
{
chdir(DATADir.c_str());
TOpenSaveDialog::TData filedata(OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT, // flags ok
	"Rule Files (*.RUL)|*.RUL|All Files (*.*)|*.*|",0,0,"RUL");
if((TFileOpenDialog(this,filedata)).Execute() == IDOK)
{
	FileList.AddDialogList(filedata.FileName);
	CmFileNew();					// initialize & display the first (valid) one
	char buf[MAXPATH];
	getcwd(buf,MAXPATH);
	DATADir = buf;
}
else
	if(filedata.Error)
		MessageBox("Dialog box returned error.","Warning",MB_OK);
}                       	//CmFileOpen
//----------------------------------------------------------------------------
void SLifeWindow::CmHelpAbout()
{ MessageBox("Copyright 1989-2001 by Steven Whitney",GetApplication()->GetName(),MB_OK); }
//----------------------------------------------------------------------------
BOOL SLifeWindow::CanClose()
{
if(!SDibWindow::CanClose())
	return(FALSE);
return(TRUE);
}                 		    	//CanClose
//----------------------------------------------------------------------------
BOOL SLifeWindow::IdleAction(long idlecount)
{
SDibWindow::IdleAction(idlecount);
draw();
return(TRUE);
}
//----------------------------------------------------------------------------
// 						end of class SLifeWindow
//////////////////////////////////////////////////////////////////////////////
class TMyApp : public TApplication
{
public:
	TMyApp(const char far *title) : TApplication(title) {}	// title used in case of error

protected:
	virtual BOOL IdleAction(long idlecount);
	virtual void InitMainWindow()
	{
		TFrameWindow* frame = new TFrameWindow(0,GetName(),new SLifeWindow);
		frame->AssignMenu(TResId(MENU_1));
		frame->Attr.AccelTable = TResId(MENU_1);
		nCmdShow = SW_SHOWMAXIMIZED;
		SetMainWindow(frame);
		EnableCtl3d(TRUE);
	}
};
//----------------------------------------------------------------------------
// a more complicated, hopefully faster, IdleAction bug fix:
// if idlecount is 0, let TFrame do everything it normally does now.
// this preserves the frequency with which TFrame calls IdleAction for *all* its children,
// and does its menu stuff, but the rest of the time just calls IdleAction for the client
// window (child), which does this app's continuous calculations.
BOOL TMyApp::IdleAction(long /* idlecount */)
{
// start with this because it changes as little as possible.
TApplication::IdleAction(0);
return TRUE;					// return TRUE to get called back unconditionally

// this alternative should work
// #error something I did while testing this caused a fatal Kernel error in the IDE after
// exiting this pgm.  suspect it wasn't this fn, but test more, and keep it in mind.
#if 0
if(idlecount == 0)
	TApplication::IdleAction(0);
else
	if(GetMainWindow() && GetMainWindow()->GetClientWindow())
		return(GetMainWindow()->GetClientWindow()->IdleAction(idlecount));
return TRUE;
#endif // 0
}
//----------------------------------------------------------------------------
// 					end class TMyApp
//////////////////////////////////////////////////////////////////////////////
// OwlMain
int OwlMain(int /* argc */, char** /* argv */)
{
randomize();
string::set_case_sensitive(0);

// Run() calls: InitApplication(), InitInstance() { (which calls: InitMainWindow() },
// then displays main window.
TMyApp* myapp = new TMyApp(AppName);
int retval = myapp->Run();
delete myapp;

return(retval);
}              				//OwlMain

 

 

Valid HTML 4.01 Transitional Valid CSS
Yahoo! Search
Search the web Search this site
View content labeling at ICRA.
Copyright ©2009 Steven Whitney. Last modified 06/16/2009.