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

Demonstration of adaptation and evolution in an artificial life colony, Borland C++ ObjectWindows (OWL)

The WAdapt.cpp artificial life program creates imaginary animals (as dots) that move around the screen according to a self-contained program, eating each other or producing offspring using a genetic algorithm, such that the programs evolve by natural selection, survival determined by which ones are most effective at obtaining food or producing offspring.

It was inspired by sections of the book Complexity, by M. Mitchell Waldrop, and the concept of this type of evolutionary program is also mentioned in other books, including Chaos Under Control, by Peak and Frame.

It was written using Borland C++ 4.0 with ObjectWindows Library (OWL) 2.0 for a Win32s target. Win32s was a 16 bit Windows subsystem that emulated 32-bit Windows. The program will probably still compile and run under Win16 (Windows 3.1) with only a few or no changes.

It has some unusual features:

  1. Text status reports about the program, the cells, and the colony are helpful to view while the program is running, but where do you display them? Two options are available, depending on how the program is compiled: 1) they can be automatically appended by DDE to an open Microsoft Word document, which you can switch to for viewing at any time, or 2) they can be sent to COM1 so you can view them on a second computer or, if you have COM1 connected directly to COM2, you can view them with a terminal program on the same computer.
  2. Because the program from which this one evolved was an MDI application, this one is also MDI. You can have multiple colonies evolving simultaneously and can save and load them separately. That's sort of pointless, though, because even one colony alone is CPU intensive. Even though it uses IdleAction(), this program is not as responsive as my others. Commands can take a couple of seconds to be processed, and other applications will be significantly sluggish to respond.

This program uses SDibWindow, SDib, SDDEApplication (for DDEML Dynamic Data Exchange), 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 program listings here where they are referenced.

Download:

Click here to download wadapt.zip (about 250 KB).

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

WAdapt.cpp Source code for the Borland C++ 4.0 ObjectWindows (OWL) 2.0 Windows 3.1 version. Same as the listing on this page.
Agent.H, Agent.CPP Source code for the agent and colony classes.
wadaptprojectnodes.png The same screenshot of the project nodes shown below on this page.
WAdapt.HPJ
WAdapt.RTF
WAdapt.HLP
Files for building the program's Windows 3.1 WinHelp file, plus the precompiled Help file. The RTF file is based on my helpfile.dot MSWord template.
WAdapt.RC
WAdapt.RH
The Windows resource definitions.
WAdapt.INI The Windows 3.1 .ini file in which the program records the location of its data files.
WAdapt.DEF The Windows module definition file.
COPYING.TXT GNU GPL License, viewed best in Word or WordPad.
ADAPT.XLS A Microsoft Excel 5.0 workbook to assist with examination and analysis of the cell programs that evolve. If you open it in Excel later than 5.0, you'll get a warning about a macro that isn't digitally signed. The macro is a leftover attempt to automate a procedure. It's not useful. If you want to work on it, you should sign it with your own code signing certificate so it becomes enabled.
LIBRARY.CPP A module that pulls into the project the library classes it needs.
ADAPT.CPP
ADAPT.TXT
The earlier and simpler MSDOS version of this program. It uses Borland BGI graphics. ADAPT.TXT is a short description of the program's purpose that is displayed at program startup. I think these are the only two files required for the DOS version. The MSDOS project should be built and run in a  separate location from the Windows version because the data files of the two are incompatible with each other. Or at least that's what my old notes say. I didn't keep them separate, and now I don't know which goes with which.
ADAPT.DOC A Microsoft Word 6.0 document with commentary and project notes about this program. It is a section of COMPLEX.DOC, in which Adapt.cpp is discussed not only in its own section, but throughout the document. If you need Adapt.doc in a format other than MSWord, the Complex.doc download includes some other formats.
HELP.BMP
OPEN.BMP
SAVE.BMP
TILEVERT.BMP
Icons for WAdapt's toolbar.
WADAPLOG.DOC A Microsoft Word 6.0 document with a header page that shows a list of opcodes used by the cell programs, with their English translations. This is also the document to which status reports are sent by DDE while WAdapt.exe is running.
A.AGS
A.INI
A.LOG
A.SAV
Text data files generated during WAdapt runs of a colony I called "A". Might be useful to browse and see what sorts of data you'll be getting. However, these files aren't required, and you'll likely overwrite them, anyway.
ADAPT.PGM
STRONGST.PGM
REPORT.LOG
Some more text data files, also not required. I don't remember which version generated these.
STRONGST.SAV A collection of the best cell programs extracted from various program runs, with a note that suggests pitting them against each other for a playoff round.
MYBEST.PGM A worksheet from an attempt to manually engineer a cell program that would take over if added to an existing cell colony. It didn't do as well as expected, and the main reason why was intriguing: it was written logically like a computer program, and, like a computer program, it couldn't stand being mutated. One random mutation, and its nice logic became worthless. It wasn't robust. If real DNA seems full of repetition and duplicative junk, maybe this is the reason why.

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

Screenshot of the WAdapt.cpp project nodes as set up in the Borland 4.0 IDE.

 

wadapt.cpp

/*	wadapt.cpp			01/27/02
	Copyright (C)1995-2002 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.

	Originally modified from wshowfs.cpp.
	Built using Borland C++ 4.0 OWL 2.0 for Win32s target.

------
To do:

before any further drastic opcode changes, do a long run with the existing ones.
this now seems capable of interesting behavior that I haven't done enough runs to observe.

add a Colony member to hold length of longest pgm, updated each Report, maybe Report once
at start(?).  add an option that if Opcodes run per call == 0 in options dialog,
it uses the length of longest.  I like this better than a fixed length; it ensures
all ags can run their entire pgm, but a # as small as possible so that strategies like
"flee" have a chance to work, which they don't if chasers can go on long eating rampages.
you must test longest against each new agent and increase it if the new one was longer,
so it can run its entire pgm; also recalc each Report, where it's convenient.

add to .RTF a manual for how to interpret status display, things that take a while to
notice:  children, ate, and energy of rep.ag. are AVGS.  in a healthy pop, they are higher.
if avg energy is high, pop has lots of eaters; if low, may be many maters.   etc.

RTF: dib size affects what population is stable;
a larger dib promotes higher populations.  Too large = frequent culling.

program still suffers from hard to trace fn dependencies and inter-class tangles.
They don't cause problems, except in understanding what's going on.

See if this idea might be incorporated into WAdapt:  See GEB:508ff, (GEB = Godel, Escher, Bach),
and implement its Typogenetic code, creating a soup of enzyme strings that interact with each
other creating new strings according to  the rules.  The goal is to determine if certain strings
tend to be stable, whether populations of strings develop, whether the strings evolve, and whether
populations of codependent or symbiotic strings develop (autocatalytic sets).
Before starting, decide whether it's really worth doing (it probably is not),
and whether these things actually can conceivably develop under these conditions.

------
Notes:
--wadapt.2 and wadapt2.exe are the last working versions before bringing in SDibWindow.
--Windows and DOS Version file formats are now incompatible with each other.
--FILES: SEE HELP
--It is safest to reboot after running it. Switching between it and MSWord (to see the status
  reports) still seems to occasionally crash the Borland 4.0 IDE after exiting this program
  and returning to the IDE. I suspect it's something about Win32s, but don't really know.
--All other notes are in adapt.doc section of COMPLEX.DOC.

*/
// You MUST comment these out for any final version, since you don't want
// debug output generated if not running under debugger.
// #define __DEBUG	2
// #define __TRACE
// #define __WARN

#include <owl\owlpch.h>
#include <owl\chooseco.h>
#include <owl\opensave.h>
#include <owl\radiobut.h>
#include <owl\buttonga.h>
#include <owl\editsear.h>
#include <owl\statusba.h>
#include <owl\controlb.h>
#include <owl\inputdia.h>
#include <classlib\time.h>
#include <classlib\arrays.h>
#include <complex.h>
#include <float.h>       			// _fpreset()
#include <dir.h>                    // MAXPATH
#pragma hdrstop

#include "c:\bcs\library\ddemlapp.h"
#include "c:\bcs\library\sdibwin.h"

#include "c:\bcs\my.h"

#include "agent.h"
#include "wadapt.rh"

const char AppName[]	= "Adapt";
char INIFilename[]  	= "wadapt.ini";
char HelpFilename[] 	= "wadapt.hlp";

extern TColor MyCustomColors[16];   // in mylib.cpp, for TChooseColor

//////////////////////////////////////////////////////////////////////////////
// struct OpStruct
//----------------------------------------------------------------------------
// constructor
// these are the startup defaults for SColonyWindow
OpStruct::OpStruct()
{
trailon = TRUE;			// whether to show trail as a point moves
beepon = TRUE;	  	 	// whether to use sound effects
lookon = FALSE;			// whether to show direction of sight
displayon = TRUE;		// whether to show cells at all
fittowindow = FALSE; 	// a local copy, not used globally

dibsize[0] = 0; 		// text for dibwidth, dibheight
stepmax[0] = 0; 		// text for # of steps agents run per run() call

maxpopedit[0] = 0;
optpopedit[0] = 0;
minpopedit[0] = 0;
passlimitedit[0] = 0;
}
//----------------------------------------------------------------------------
// copy constructor
OpStruct::OpStruct(const OpStruct& other) { *this = other; }
//----------------------------------------------------------------------------
// assignment
// this may be useful for initializing an option set from a master set,
// so user doesn't have to repeatedly set the same options.
OpStruct& OpStruct::operator = (const OpStruct& other)
{
if(&other != this)
{
	trailon = other.trailon;
	beepon 	= other.beepon;
	lookon	= other.lookon;
	displayon = other.displayon;
	fittowindow = other.fittowindow;

	strcpy(dibsize,other.dibsize);
	strcpy(stepmax,other.stepmax);

	strcpy(maxpopedit,other.maxpopedit);
	strcpy(optpopedit,other.optpopedit);
	strcpy(minpopedit,other.minpopedit);
	strcpy(passlimitedit,other.passlimitedit);
}
return(*this);
}						// operator =
//----------------------------------------------------------------------------
// 						end struct OpStruct
//////////////////////////////////////////////////////////////////////////////
// class SOptionsDialog: options dialog box
//----------------------------------------------------------------------------
DEFINE_RESPONSE_TABLE1(SOptionsDialog,TDialog)
	EV_COMMAND(IDHELP,CmHelp),
END_RESPONSE_TABLE;
//----------------------------------------------------------------------------
// constructor
SOptionsDialog::SOptionsDialog(TWindow* parent, TResId resid, OpStruct* ots)
	: TDialog(parent, resid)
{
	new TRadioButton(this,IDC_TRAILS,0);
	new TRadioButton(this,IDC_BEEPON,0);
	new TRadioButton(this,IDC_LOOKON,0);
	new TRadioButton(this,IDC_DISPLAYON,0);
	new TRadioButton(this,IDC_FITTOWINDOW,0);

	new TEdit(this,IDC_DIBSIZE,20);
	new TEdit(this,IDC_STEPMAX,10);

	new TEdit(this,IDC_MAXPOP,10);
	new TEdit(this,IDC_OPTPOP,10);
	new TEdit(this,IDC_MINPOP,10);

	new TEdit(this,IDC_PASSLIMIT,10);
	SetTransferBuffer(ots);
}
//----------------------------------------------------------------------------
// This is called when user presses the Help button.  When a
// button is clicked, a BN_CLICKED message is sent to the parent, but the
// dialog interprets it as an IDHELP command because that's the ID given to
// that button in the .rc (rh) file.
// Does a keyword search for the given partial keyword.  The advantage of
// this method is you don't have to use .HPJ[MAP] or a header file
// to make sure identifiers in your program and help file agree.
// You just have to make sure the keyword IS used in your help file,
// which is a lot easier to remember than a bunch of mapped identifiers.
// I don't like the double-cast, but it does work fine.
void SOptionsDialog::CmHelp()
{ WinHelp(HelpFilename,HELP_PARTIALKEY,(DWORD)(LPSTR)"Options Dialog"); }
//----------------------------------------------------------------------------
// 						end class SOptionsDialog
//////////////////////////////////////////////////////////////////////////////
// class SColonyWindow
//----------------------------------------------------------------------------
DEFINE_RESPONSE_TABLE1(SColonyWindow, SDibWindow)
	EV_WM_LBUTTONDOWN,
	EV_WM_LBUTTONUP,
	EV_WM_RBUTTONDOWN,
	EV_WM_SETFOCUS,
	EV_COMMAND(CM_FILESAVE,CmFileSave),
	EV_COMMAND(CM_VIEWOPTIONS,CmViewOptions),
	EV_COMMAND(CM_VIEWSTATUS,Report),
	EV_COMMAND(CM_CLRSCR,CmViewClrScr),
	EV_COMMAND(CM_PEEK,CmPeek),
END_RESPONSE_TABLE;
//----------------------------------------------------------------------------
// initialize static members

// Win32s has no serial communications capabilities.
#if !defined(__WIN32__)
SWinCommDev SColonyWindow::SerialCom;		// for status reports, all share
#endif	//!defined(__WIN32__)

int SColonyWindow::StatusReportMode = SColonyWindow::BYDDE; // BYDDE; BYSERIAL; BYNONE;
int SColonyWindow::FracwinCount = 0;
SDDEHandler* SColonyWindow::Dde = 0;

//----------------------------------------------------------------------------
// constructor
SColonyWindow::SColonyWindow(TWindow *parent) : SDibWindow(parent),
	Ag(16000,0,0)	// a round number close to physical limit. nonexpandable.
{
Strongest = MostProlific = 0;
loctotals = complex(0,0);
childcount = 0L;
runloops = 0L;		// number of entries to the for loop that calls agent::run()
agentsran = 0L;
lowpop = 16000;		// lowest population ever reached
highpop = 0;  		// highest population ever reached
passlimit = 4;		// autoclear every N passes (here for Report)
PGMLENGTH = 2;		// abs min=2 (1 step and a 0). setPGMLENGTH() sets to avg of existing.
					// small at start works fine.  There is pressure to lengthen.

MUTATIONRATE = 100;				// 1 PGM STEP in this many has a mutation
MATEMETHOD = SHUFFLESEGMENTS;

AutoSetPOPValues(1000);			// set default values for size of colony

// we want a specific dib size.  (in case default size in SDibWindow changes)
SDib* newdib = 0;
if((newdib = DibResize(dib,160,100,256,FALSE)) != 0)
{
	delete dib;
	dib = newdib;
}
dib->SetColor(BKCOLOR,TColor::Black);		// 0 background to match window
dib->SetColor(AGCOLOR,TColor::White);       // 1 agent color
dib->SetColor(LOOKCOLOR,TColor::LtBlue);	// 2 line of sight color
FirstFreeColor = 3;							// freeze reserved colors
CmColorsManip(CM_COLORSRANDOMIZE);			// don't want SDib's default gray scale

SetBkgndColor(dib->GetColor(BKCOLOR));

// initialize other variables
piccount = 0;					// used in IdleAction, for assigning BMP filenames
drawing = TRUE;					// but doesn't matter: no file open yet anyway
fittowindow = FALSE;    		// ags show as pixels, and display doesn't fill screen
AllowDirectToScreen = TRUE; 	// it is only false during very time-consuming procedures

}								// constructor
//----------------------------------------------------------------------------
// destructor
SColonyWindow::~SColonyWindow()
{
Flush();
}
//----------------------------------------------------------------------------
// output to a stream
ostream& operator << (ostream& os, SColonyWindow& ) { return(os); }
//----------------------------------------------------------------------------
// read from a stream.  be sure to set ALL members, even those not read from the stream
istream& operator >> (istream& is, SColonyWindow& ) { return(is); }
							// operator >>
//-----------------------------------------------------------------------
// STATUS REPORT, ERROR LOG, UTILITY FUNCTIONS
//-----------------------------------------------------------------------
// a static fn, sends status report to Com1, or MSWord by DDE, or nowhere, depending on mode.
void SColonyWindow::SendStatusReport(const string& s)
{
if(!s.length())
	return;
#if !defined(__WIN32__)
// keep BYSERIAL option even if DDE proves more useful.  Having MSWord running could
// cause memory shortage and the return of slow disk-swapping when agcount nears 16000.
if(StatusReportMode == BYSERIAL)
{
	if(!FindWindow("Terminal", "Terminal - WADAPT.TRM"))
	{
		HWND w = ::GetFocus();					// save this app's window
		WinExec("terminal.exe wadapt.trm", SW_SHOWNA);
		// tried the following only briefly; it didn't even start the app
		// ShellExecute(NULL,"open","wadapt.trm",NULL,"c:\\ai\\adapt",0);
		if(w)
			::SetFocus(w);						// restore focus to this app
	}
	// old method (using shared object) is faster, reliable, and should minimize
	// Windows confusion about who is in control of Com1.
	SerialCom.Send(s);
	// an inferior alternative: output sometimes truncated, with long delays.
	// bad idea to create the object every time you need one.
	// SWinCommDev().Send(s);
}
#endif	//!defined(__WIN32__)

if(StatusReportMode == BYDDE)
{
	SDDEConv* chan1 = Dde->DDEInitiate("WINWORD","C:\\AI\\ADAPT\\WADAPLOG.DOC");
	if(chan1)
	{
		// just using Poke correctly pokes the text to wadaplog.doc even if another
		// document was active at the time.  If you use other (editing)
		// commands such as InsertPara, EndOfDocument, etc.,
		// they are applied to whatever document has the focus.
		chan1->DDEPoke("\\EndOfDoc", s + "\n");

		HWND w = ::GetFocus();	// save whatever app has focus

		// unconditionally activate the doc we want so it has the focus.
		// (if you are IN MSWord working on another document,
		// you will be returned to it automatically when this is finished!)
		chan1->DDEExecute("[Activate \"WADAPLOG.DOC\"]");

		// scroll to keep as much text as possible visible in the MSWord window.
		// this basic method works, CAN'T hang up.  26 is for FixedSys at Exactly 9pt.
		chan1->DDEExecute("[StartOfDocument][EndOfDocument][VLine - 26]");
		// my custom macro also works, but risks endless loop or error hangup.
		// chan1->DDEExecute("[EndOfDocument][ScrollToWindowBottom]");
// 		chan1->DDEExecute("[SetDocumentDirty 1]");	// optional; forces "Save?" prompt at exit

		if(w)
			::SetFocus(w);		// restore focus where it was
		Dde->DDETerminate(chan1);
	}
}
}              					//SendStatusReport
//-----------------------------------------------------------------------
// write error (if any) to disk.  returns cumulative error count.
// you can call with empty string just to get the count.
uint SColonyWindow::logerror(string error)
{
SendStatusReport(error);     				// does nothing if error is empty
return ::logerror(error,"wadapt.err",TRUE);
}								//logerror
//----------------------------------------------------------------------------
// BKMK: DISK FILE FUNCTIONS
//----------------------------------------------------------------------------
void SColonyWindow::Open(string infilename)
{
FilePathParser(infilename).ChDir(); // make this the default dir
char buf[MAXPATH];     				// see also Get/SetCurrentDirectory (Win32 only)
getcwd(buf,MAXPATH);
DATADir = buf;        				// this is the only available pgm loc to set this
if(!infilename.contains(".AGS"))   	// infilename must have .AGS extension.
{
	MessageBox("Can only open an .AGS file.","File Type Error",MB_OK | MB_ICONINFORMATION);
	return;
}
SetParentCaption(infilename);
fileroot = infilename;
fileroot.remove(fileroot.rfind(".")); 	// delete extension

// These PrivateProfile methods should probably be eliminated in a Windows XP version.
string inifile = fileroot + ".ini";
int a = GetPrivateProfileInt("info","MAXPOP",MAXPOP,inifile.c_str());
int b = GetPrivateProfileInt("info","OPTPOP",OPTPOP,inifile.c_str());
int c = GetPrivateProfileInt("info","MINPOP",MINPOP,inifile.c_str());
if((c > 0) && (b > c) && (a > b) && (a <= 16000))
{
	MAXPOP = a;
	OPTPOP = b;
	MINPOP = c;
}
getstrongestfromdisk();				// Flush, and load starting cell programs
CmViewOptions();					// set options (may affect random filling)
TopUp();  		            		// random fill

// this file is always appended; this is your only chance to empty it.
string logfile = fileroot + ".LOG";
if(MessageBox("Delete .LOG file to start fresh?",Title,MB_YESNO | MB_ICONQUESTION) == IDYES)
	unlink(logfile.c_str());
pgmstodisk("Starting",50);				// save some initial programs to .LOG
drawing = TRUE;
sw.reset();
sw.start();								// start timing the run
}        	          		   	//Open
//-----------------------------------------------------------------------
// load starting cell programs from .AGS file
void SColonyWindow::getstrongestfromdisk()
{
HCURSOR oldcursor = ::SetCursor(::LoadCursor(NULL, IDC_WAIT));
AllowDirectToScreen = FALSE;

int oldmaxpop = MAXPOP;	// allow reading ALL, no matter how many, if physically possible
MAXPOP = 16000;     	// forces AddAgent to accept all, if possible
Flush();				// empty array

string infilename = fileroot + ".AGS";
ifstream infile(infilename.c_str());
for(int i = 0 ; infile ; i++)
{
	agent* a = AddAgent();			// create a new cell
	if(!a)
	{
		string s = to_upper(infilename) + " has too many cell programs to load them all.\n"
					+ "Will proceed with only the " + tostring(i) + " that were read.";
		MessageBox(s.c_str(),"Load Error",MB_OK | MB_ICONEXCLAMATION);
		break;
	}
	infile >> *a;    				// get its program from disk
	if(!infile)		 				// Additional check ensures the read succeeded.
	{
		logerror(string("read failure in getstrongestfromdisk at agent #") + tostring(i));
		DestroyAgent(a);
		break;
	}
	infile >> ws;					// force failure if only blanks left
}
// if the number read WILL be the new limit, you must ADD 1 if physically possible,
// so that CullOrTopUp won't find the array AT MAXPOP and cull it to OPTPOP.
// (should no longer be a problem, since TopUp() is used)
if(i > oldmaxpop)
{
	if(i < MAXPOP)
		i++;
	AutoSetPOPValues(max(i,oldmaxpop));		// if broke the old limit, increase the limit.
}
else
	MAXPOP = oldmaxpop;						// not exceeded, so restore to previous value

AllowDirectToScreen = TRUE;					// restore setting
::SetCursor(oldcursor);  					// restore cursor

TRACE("Read " << i << " pgms from " << infilename);
}							//getstrongestfromdisk
//-----------------------------------------------------------------------
// write cell data to .LOG, which can become extremely large.
// only saving the top STRONGEST would be nice, but difficult because you can't delete them
// as you go.  However, maxcount allows writing only the first N.  The first are the oldest
// and usually, though not always, the strongest (i.e. those that eat).
// Sometimes the most numerous pgms are weak but produce lots of children,
// so a composite pgm is also created to represent them.
void SColonyWindow::pgmstodisk(const char *title, uint maxcount)
{
HCURSOR oldcursor = ::SetCursor(::LoadCursor(NULL, IDC_WAIT));
int count = min(maxcount,Ag.GetItemsInContainer());
ofstream outfile((fileroot + ".LOG").c_str(),ios::app);
outfile << title << ", Population = " << Ag.GetItemsInContainer();
outfile << ", Childcount = " << childcount << ", " << TTime() << endl;
for(int i = 0 ; i < count ; i++)
{
	// write data not written by <<
	outfile << "#" << setw(7) << Ag[i]->id;
	outfile << *(Ag[i]) << endl;
}
// write out a hypothetical pgm representative of the whole set
agent a(*this);								// it won't be on the screen (1/12/02 why not?)
buildrep(a);
outfile << "Representative Agent:\n        " << a << endl;
::SetCursor(oldcursor);  									// restore cursor
}							//pgmstodisk
//----------------------------------------------------------------------------
// BKMK: FUNCTIONS DEALING WITH AGENT INFORMATION, COMPARISONS, AGGREGATE TOTALS
//----------------------------------------------------------------------------
// returns pointer to strongest agent in array, or 0 if array empty
// In case of a tie, it chooses the first (oldest) cell with that energy.
agent* SColonyWindow::FindStrongest()
{
Strongest = 0;
int count = Ag.GetItemsInContainer();
if(count)
{
	Strongest = Ag[0];         			// start with first as strongest
	for(int j = 1 ; j < count ; j++)
		if(Ag[j]->energy > Strongest->energy) 	// find highest, if any
			Strongest = Ag[j];
}
return Strongest;
}       		             	//FindStrongest
//-----------------------------------------------------------------------
// returns pointer to agent with the most children, or 0 if array empty
// In case of a tie, it chooses the first (oldest) cell with that energy.
agent* SColonyWindow::FindMostProlific()
{
MostProlific = 0;
int count = Ag.GetItemsInContainer();
if(count)
{
	MostProlific = Ag[0];         			// start with first as selected
	for(int j = 1 ; j < count ; j++)
		if(Ag[j]->children > MostProlific->children) 	// find highest, if any
			MostProlific = Ag[j];
}
return MostProlific;
}               		     	//FindMostProlific
//-----------------------------------------------------------------------
// construct a pgm representative of the set, containing at each location
// the opcode most common at that location.  And assign its other members to be
// averages representing the set.  Tested, agrees with .xls version.
// the agent a that you give it must not be a real member of the set!
// use a temporary.
// this fn took a lot of work, but a StatArray might make it a lot simpler.
void SColonyWindow::buildrep(agent& a)
{
int* t = new int[agent::ABSICOUNT];		// tallies opcode incidences, 1 slot for each opcode
int i, j;								// counters
string composite;                     	// the respresentative pgm
int count = Ag.GetItemsInContainer();

// find LONGEST pgm.  you only need to tally up to that point
uint longest = 0;
for(i = 0 ; i < count ; i++)
	longest = max(Ag[i]->pgm.length(),longest);

for(i = 0 ; i < longest ; i++)				// for each possible column in the pgms,
{
	for(j = 0 ; j < agent::ABSICOUNT ; j++)	// reset the tally of each opcode to zero
		t[j] = 0;

	// count each opcode's incidence at this location
	for(j = 0 ; j < count ; j++)        	// for all cell pgms,
		if(i < Ag[j]->pgm.length())			// if i is a valid index in this pgm
			t[(int)(Ag[j]->pgm[i])]++;		// increment this OPCODE's counter

	// find which opcode was the most common at this loc.
	// A tie goes to the lowest numbered, for no particular reason.
	int opcode = 0;							// corresponding index (encodes opcode)
	for(j = 0 ; j < agent::ABSICOUNT ; j++)	// for each opcode tally slot,
		if(t[j] > t[opcode])				// if this one's tally is the highest,
			opcode = j;						// it is the highest-frequency opcode

	// give this most common opcode (or zero) its position in composite
	composite += (uchar)opcode;
}
delete[] t;

// calculate various averages
long SumAte = 0L;
long SumChildren = 0L;
double SumEnergy = 0;
for(i = 0 ; i < count ; i++)
{
	SumAte += Ag[i]->ate;
	SumChildren += Ag[i]->children;
	SumEnergy += Ag[i]->energy;
}

a.ate = 		(int)(SumAte / count);
a.children = 	(int)(SumChildren / count);
a.energy =		SumEnergy / count;
a.pgm = composite;
}		   						//buildrep
//-----------------------------------------------------------------------
void SColonyWindow::Report()
{
ostrstream os;

// tabulate stats
uint shortest = agent::MAXPGMLENGTH;
uint longest = 0;
long sum = 0;
int count = Ag.GetItemsInContainer();
for(int i = 0 ; i < count ; i++)
{
	shortest = min(shortest,Ag[i]->pgm.length());
	longest = max(longest,Ag[i]->pgm.length());
	sum += Ag[i]->pgm.length();
}
long average = (count ? (sum / count) : (shortest + longest) / 2);

os << "Pass(" << runloops << ")  Pop(" << Ag.GetItemsInContainer()
   << ")  Low(" << lowpop << ")  High(" << highpop
   << ")  Births(" << childcount << ")  " << TTime() << endl;

os << "Cell pgms: Short(" << shortest << ") Avg(" << average
   << ") Long(" << longest << ")  TotRun(" << agentsran
   << ")  Errors(" << logerror()
   << ")  Pgms/Second(" << ((double)agentsran / sw.split()) << ")" << endl;

// os << endl;

// show selected data about Strongest.
// (some other agent data only exists while IT runs its pgm.)
if(Strongest)
{
	TDibDC dibdc(*dib);

	// draw a circle around strongest, to help finding it
	dibdc.SelectObject(TPen(dib->GetColor(Strongest->color)));
	dibdc.SelectStockObject(NULL_BRUSH);					// fills w/nothing

	// nothing fancy: must guarantee only dib colors & no accidental white dots
	dibdc.SetROP2(R2_COPYPEN);
	dibdc.Ellipse(TRect(Strongest->ScrLoc.OffsetBy(-5,-5),TSize(11,11)));
	drawagents();					// in case you drew over any

	os << "Strongest Agent: ";
	os << "#" << Strongest->id << " at " << Strongest->ScrLoc << " of " << dib->Size();

	TColor c = dib->GetColor(Strongest->color);
	os << " Color(" << Strongest->color << ")[R"
		<< (int)(c.Red()) << ",G" << (int)(c.Green()) << ",B" << (int)(c.Blue()) << "]"
		<< endl;
	os << "     Ate Children  Energy        Pgm:" << endl;
	os << *Strongest << endl;
}
// make, and show, a hypothetical agent representative of the whole set
if(count)
{
	agent b(*this);							// it won't be on the screen
	buildrep(b);
	os << "Representative Agent:\n" << b << endl;
}	
os << "----------------------------------" << endl;
os << ends;
SendStatusReport(os.str());
delete[] os.str();
Invalidate(TRUE);					// 1 automatic peek
}		                    		//Report
//-----------------------------------------------------------------------
// get data about a cell, including its program translated to descriptive text.
string SColonyWindow::viewcell(agent* a)
{
string s;
if(!a)
	return s;

// adjust reported heading so it looks correct to user
int scrheading = wrap((int)(-(a->heading)), 0, 359);
TColor c = dib->GetColor(a->color);

ostrstream os;
os << "#" << a->id << " of " << childcount << " at " << a->ScrLoc << " of " << dib->Size()
	<< " Heading:" << scrheading << endl;
os << "Energy:" << a->energy  << " Ate:" << a->ate << " Children:" << a->children
	<< " Color(" << a->color << ")[R"
		<< (int)(c.Red()) << ",G" << (int)(c.Green()) << ",B" << (int)(c.Blue()) << "]"
		<< endl;
os << "AX    :" << a->ax << endl;

os << "Inview:";
if(a->inview && Ag.HasMember(a->inview))	// HasMember checks for inview still being alive
	os << (string("#") + tostring(a->inview->id) + " at " + tostring(a->inview->ScrLoc));
else
	os << "NO";
os << endl << endl;

int opcode;
int count = a->pgm.length();
for(int i = 0 ; i < count ; i++)
{
	opcode = (uint)(a->pgm[i]);
	os << setw(5) << (i+1) << ".";			// give the pgm steps line numbers
	os << setw(4) << opcode;
	string t;
	if(a->ip == i)
		t += "*";							// * shows current ip location
	if(a->maxip == i)
		t += "+";							// + shows highest step ever reached
	if(t.length() < 4)
		t += string(' ',4 - t.length());
	os << t;
	os << setw(30) << setiosflags(ios::left);
	if(opcode < agent::ABSICOUNT)			// prevent trying to print opcode text that is
		os << agent::opcodes[opcode];		// beyond end of opcodes[] array.
	else    								// (old files may contain obsolete opcodes)
		os << "NOP: obsolete opcode";
	os << setiosflags(ios::right)<< endl;
}
os << "----------------------------------" << endl;
os << ends;
s = os.str();
delete[] os.str();
return s;
}                 				//viewcell
//-----------------------------------------------------------------------
// compares attributes of 2 agents, and returns a set of flags that encodes
// the states of commonly-used BOOL tests.  this is in Colony only because
// the (first,second) syntax is easy to remember as "this is HOW first compares to second".
// consider agent::ComparedTo(agent* other);
ulong SColonyWindow::Compare(agent* first, agent* second)
{
ulong flags = ALL;                                   	// 0
if(first->energy > second->energy) flags |= STRONGER;
if(first->energy < second->energy) flags |= WEAKER;

if(first->samespecies(second)) flags |= SAMESPECIES;
else						   flags |= DIFFSPECIES;

if(first->children > second->children) flags |= MORECHILDREN;

return flags;
}                       		//Compare
//-----------------------------------------------------------------------
// use calculated flags to determine whether the FIRST agent compares to SECOND
// such that it meets the specified criteria.  (Note that if a cell wants to FIND
// a stronger cell, it puts the other cell in first and itself in second.)
// see notes with FindNearest.  may be used from FindNearest, pickadjacent, and any mate fn.
BOOL SColonyWindow::ScreenFor(agent* first, agent* second, ulong selectOR, ulong selectAND)
{
// (only if ANY testing is needed,) screen out ineligible
if((selectOR != ALL) || (selectAND != ALL))
{
	ulong c = Compare(first,second);
	if(selectOR != ALL)
		if((c & selectOR) == 0)				// did not meet ANY of the criteria
			return FALSE;
	if(selectAND != ALL)
		if((c & selectAND) != selectAND)	// did not meet ALL the criteria
			return FALSE;
}
return TRUE;
}                        	//ScreenFor
//-----------------------------------------------------------------------
// returns pointer to nearest cell neighbor that meets the given criteria, or 0 if none.
// I added the criteria flags to make it easy to implement numerous similar
// OPCODES using only this one function.
// you will probably use selectAND more than selectOR.  Normally, you use one
// or the other.  You CAN use both, but it gets overly complicated.  If used together,
// the agent must meet ALL of the AND criteria *and* one of the OR criteria.
agent* SColonyWindow::FindNearest(agent* a, ulong selectOR, ulong selectAND)
{
int count = Ag.GetItemsInContainer();
if(!a || (count < 2))							// min=2: a and a neighbor
	return(0);

// screen out obvious impossibilities (are there any more?)
// this one is too trivial to bother with;
// if((a == Strongest) && (selectOR & STRONGER))
// 	return 0;

// if a still has Nearest marked from a previous FindNearest call during THIS RUN,
// and the current criteria are the same, and the distance is <= to what it was,
// then use it.  (if it moved farther AWAY, there may be a new nearest).
// THIS CONTRIBUTES A *LOT* TO SPEED IMPROVEMENT, EVEN THOUGH
// it is confusing and may provide opportunities for error.
// Enabling this and area test together tripled pgms/second.
// the goal here is to test whether THIS FindNearest call is identical to the just-previous
// one and is certain to have an identical result.
if(a->Nearest && (selectOR == a->LastSelectOR) && (selectAND == a->LastSelectAND) &&
	a->calcdistance(a->Nearest) <= a->NearestDist)
		return a->Nearest;

// ok to set LastAND and LastOR now even if no nearest is found during this pass;
// they won't ever get USED unless a valid nearest IS found
a->SetNearest(0, MAXUINT, selectOR, selectAND);
uint distance;

// it may be desirable to do area test only when success is likely, but currently
// none of the flags make success particularly unlikely: IF density is high enough,
// it is fairly likely to find cells both stronger and weaker nearby.
// #error consider some sort of self-timer that allows it to calculate for itself,
// or learn from experience, when to use which type(s) of search, and area diameter limit.
// also consider member counters, incremented inside test sections and logged at program exit,
// to report how often each type was used, and maybe clocks for cumulative time spent in each.
// Also, the type(s) of search used seems to affect population growth direction,
// so the choice you make could have side effects.
// Enabling all 3 (as now) was about as fast as any other combination when pop was 2500.
// #error consider always using JUST the area search on the idea it has limited vision.
// and consider an opcode "hop" that instantly transports to new random loc; makes
// possible an interesting strategy: if nobody nearby, hop somewhere else.
if(count > 1000)		// for very small populations, array search is fine
{
	// new version tests pixels in expanding spiral from a's loc.  it does work properly.
	// THIS IS NOW VERY FAST. IT DOUBLED PGMS/SEC.
	agent* occupant;
	TPoint p(a->ScrLoc);
	// LIMIT SEARCH DIAMETER TO 4; BEYOND THAT (OR WHAT VALUE?), ARRAY SEARCH IS FASTER
	// for each square, you must search entire perimeter (i.e. not quit early)
	for(int i = 1 ; (a->Nearest == 0) && (i <= 4) ; i++)
	{
		p.Offset(-1,-1);			// p itself actually moves around the perimeter
		int left(p.x), top(p.y), right(a->ScrLoc.x + i), bottom(a->ScrLoc.y + i);
		for( ; p.x <= right ; p.x++) 				// traverse top
#define NEARESTTEST\
		{\
			occupant = Occupant(p);\
			if(occupant && occupant->IsAlive() && ScreenFor(occupant,a,selectOR,selectAND))\
			{\
				distance = a->calcdistance(occupant);\
				if(distance < a->NearestDist)\
				{\
					a->Nearest = occupant;\
					a->NearestDist = distance;\
				}\
			}\
		}
			NEARESTTEST
		for(p.x-- ; p.y <= bottom ; p.y++)  		// down right side
			NEARESTTEST
		for(p.y-- ; p.x >= left ; p.x--)			// back across bottom
			NEARESTTEST
		for(p.x++ ; p.y > top ; p.y--)				// up left side
			NEARESTTEST
#undef NEARESTTEST
		// p is now back where it started, for the next Offset(-1,-1)
	}	// end for(traverse the whole square)
}		// end area search method

if(a->Nearest == 0)				// if above failed or wasn't used, this is the last resort
{
	for(int i = 0 ; i < count ; i++)
	{
		// don't find itself, not eligible if dead!, or screened out
		if((Ag[i] == a) || !Ag[i]->IsAlive() || !ScreenFor(Ag[i],a,selectOR,selectAND))
			continue;
		distance = a->calcdistance(Ag[i]);
		if(distance < a->NearestDist)
		{
			a->Nearest = Ag[i];
			a->NearestDist = distance;
		}
	}
}
return(a->Nearest);
}              		 	  		//FindNearest
//-----------------------------------------------------------------------
// find the cell most similar to the given one.
agent* SColonyWindow::FindMostSimilar(agent* a)
{
int count = Ag.GetItemsInContainer();
if(count < 2)							// min=2: a and a neighbor
	return(0);

agent* mostsimilar = 0;
double similarity = 0;
double maxsimilarity = 0;
for(int i = 0 ; i < count ; i++)
{
	if(Ag[i] == a)								// don't find itself
		continue;
	if(!Ag[i]->IsAlive())						// not eligible if dead!
		continue;
	similarity = a->SimilarityTo(*Ag[i]);
	if(similarity > maxsimilarity)
	{
		maxsimilarity = similarity;
		mostsimilar = Ag[i];
	}
}
return(mostsimilar);
}                     			//FindMostSimilar
//-----------------------------------------------------------------------
// create a bitmapped map of neighboring pixels.
// each pixel encoded in 1 bit (excluding pixel occupied by itself).
// use if(mapneighbors()) to determine if *any* neighboring pixel is occupied.
// use & (bitwise and) operator on map to determine if a specific pixel is occupied.
// For use in DLA and in 2nd version of Life.
// returns the NUMBER of neighbors, and creates the map in the given int parameter.
// could be changed to return a pointer to one of the neighbors, chosen randomly,
// or to return 1-9 (not 5, itself), the loc of a neighbor, chosen randomly.
int SColonyWindow::mapneighbors(agent* a, int& map)
{
int count = 0;			// counts neighbors
map = 0;				// clear the map to start
int xi = a->ScrLoc.x;   // fast locals
int yi = a->ScrLoc.y;

TSize r = dib->Size();
int left  = (int)wrap(xi - 1L,0L,(long)r.cx);   // standard template wrap() from my.h
int right = (int)wrap(xi + 1L,0L,(long)r.cx);   // weird casts are for Win32
int above = (int)wrap(yi - 1L,0L,(long)r.cy);
int below = (int)wrap(yi + 1L,0L,(long)r.cy);

if(IsOccupied(TPoint(left, above)))	{ map |= 128; count++; } 	// above left
if(IsOccupied(TPoint(xi,above))) 	{ map |=  64; count++; } 	// above
if(IsOccupied(TPoint(right,above)))	{ map |=  32; count++; } 	// above right
if(IsOccupied(TPoint(left ,yi))) 	{ map |=  16; count++; } 	// to left
if(IsOccupied(TPoint(right,yi))) 	{ map |=   8; count++; } 	// to right
if(IsOccupied(TPoint(left ,below))) { map |=   4; count++; } 	// below left
if(IsOccupied(TPoint(xi,below))) 	{ map |=   2; count++; }	// below
if(IsOccupied(TPoint(right,below))) { map |=   1; count++; } 	// below right

return(count);
}                    			//mapneighbors
//-----------------------------------------------------------------------
// choose an adjacent cell (if there is one or more), modified from mapneighbors()
// allows choosing from multiple neighbors
// returns pointer to the chosen cell, or 0 if none
agent* SColonyWindow::pickadjacent(agent* ag)
{
int count = 0;								// counts neighbors
int map = 0;								// clear the map to start
agent* a = 0;

TSize r = dib->Size();
int left  = (int)wrap(ag->ScrLoc.x - 1L,0L,(long)r.cx);	// standard template wrap() from my.h
int right = (int)wrap(ag->ScrLoc.x + 1L,0L,(long)r.cx); // weird casts are for Win32
int above = (int)wrap(ag->ScrLoc.y - 1L,0L,(long)r.cy);
int below = (int)wrap(ag->ScrLoc.y + 1L,0L,(long)r.cy);

// precalculated points, only REFERENCES to them are passed as parameters below
TPoint AboveLeft(left ,above);
TPoint Above(ag->ScrLoc.x,above);
TPoint AboveRight(right,above);
TPoint ToLeft(left,ag->ScrLoc.y);
TPoint ToRight(right,ag->ScrLoc.y);
TPoint BelowLeft(left,below);
TPoint Below(ag->ScrLoc.x,below);
TPoint BelowRight(right,below);

// multiple neighbors is unlikely, so if count is 0, any hit is likely the only neighbor.
// In case it is, save the pointer so you can return it immediately.
// Once you know there are multiple neighbors, don't bother; you'll have to choose,
// and identifying the occupant is slow.

if(IsOccupied(AboveLeft)) 	{map |= 128; if(!count) a=Occupant(AboveLeft); 	count++; }
if(IsOccupied(Above)) 		{map |=  64; if(!count) a=Occupant(Above); 		count++; }
if(IsOccupied(AboveRight)) 	{map |=  32; if(!count) a=Occupant(AboveRight); count++; }
if(IsOccupied(ToLeft))		{map |=  16; if(!count) a=Occupant(ToLeft); 	count++; }
if(IsOccupied(ToRight))		{map |=   8; if(!count) a=Occupant(ToRight); 	count++; }
if(IsOccupied(BelowLeft)) 	{map |=   4; if(!count) a=Occupant(BelowLeft); 	count++; }
if(IsOccupied(Below)) 		{map |=   2; if(!count) a=Occupant(Below); 		count++; }
if(IsOccupied(BelowRight)) 	{map |=   1; if(!count) a=Occupant(BelowRight); count++; }

if(!map)				// no neighbors (most common)
	return(0);
if(count == 1)			// 1 neighbor, and you already know who it is (also common)
	return a;
while(1)				// multiple neighbors; you have to choose
{
	// generate random positions within map until the bit-shifted 1
	// lands on a spot mapped in map as occupied
	count = random(8);
	if(map & (1 << count))
		switch(count)
		{
			case 7: return(Occupant(AboveLeft));
			case 6: return(Occupant(Above));
			case 5: return(Occupant(AboveRight));
			case 4: return(Occupant(ToLeft));
			case 3: return(Occupant(ToRight));
			case 2: return(Occupant(BelowLeft));
			case 1: return(Occupant(Below));
			case 0: return(Occupant(BelowRight));
		}
}
}                   			//pickadjacent
//----------------------------------------------------------------------------
// BKMK: DISPLAY MANAGEMENT FUNCTIONS
//-----------------------------------------------------------------------
// wraps a point back into range usable for the display screen.
// it assumes that the double values used for x, y will be TRUNCATED when used,
// so any point from 0 to < 1 will become 0 (NOT rounded up), 1-2 is 2, 639-640 is 639, etc.
// thus, the raw values might remain slightly outside the display area, and
// the display is offset leftward by .5 compared to the actual points in space,
// but it eliminates serious complications that rounding would necessitate.
// width and height are the highest bounding values, NONinclusive, such as
// are provided by SDib.Width(), TRect.Height(), etc.  Minimum is assumed always 0.
// places the wrapped value in point, and returns TRUE if wrapping was required, FALSE if not.
BOOL SColonyWindow::screenwrap(complex& point)
{
TSize r = dib->Size();
int width = r.cx, height = r.cy;

double x = real(point);
double y = imag(point);
BOOL wrapped = FALSE;

while(x < 0) 		{ x += width; wrapped = TRUE; }
while(x >= width) 	{ x -= width; wrapped = TRUE; }

while(y < 0) 		{ y += height; wrapped = TRUE; }
while(y >= height) 	{ y -= height; wrapped = TRUE; }

point = complex(x,y);
return wrapped;
}                   		//screenwrap
//----------------------------------------------------------------------------
// allows optional drawing to the screen simultaneously with drawing to the dib.
// seeing the agents ACTUALLY move step-by-step is better for viewing.
void SColonyWindow::SetPixel(const TPoint& p, int c)
{
dib->SetPixelByte(p,(uchar)c);

// optionally update screen immediately.
// works great for viewing, but unacceptably slow when speed is important.
// assume that if trailon is TRUE, user doesn't care how slow it is.
// in fact, the slower it is, the easier it is to view.
if(HWindow && AllowDirectToScreen && ots.displayon && ots.trailon)
{
	TRect clientrect(GetClientRect());
	// very fast, when dib and screen match OR not stretching
	if(!fittowindow || (clientrect == dib->Rect()))
		TClientDC(*this).SetPixel(p,dib->GetColor(c));
	else
	{
		// scale point p to window coordinates
		TPoint wp(MulDiv(p.x, clientrect.Width() - 1, dib->Width() - 1),
				  MulDiv(p.y, clientrect.Height() - 1, dib->Height() - 1));
		// create an invalid area around wp of small fixed size,
		// but large enough for any reasonable dib:window ratio
		InvalidateRect(TRect(wp,wp).Inflate(16,10), FALSE);
		UpdateWindow();
	}
}
}                      		//SetPixel
//-----------------------------------------------------------------------
// display an agent onscreen
void SColonyWindow::ShowAgent(agent* a) { SetPixel(a->ScrLoc, AGCOLOR); }
//-----------------------------------------------------------------------
// move the DISPLAY of an agent to a new location.
// erase must be false when first creating the agent, since its ScrLoc loc is invalid.
// (actually, it's (0,0) which is always legal)
void SColonyWindow::MoveAgent(agent* a, const TPoint& newpoint, BOOL erase)
{
// make agent invisible at its current display screen location,
// replacing its pixel with the background color or its trail.
if(erase)
	SetPixel(a->ScrLoc, ots.trailon ? a->color : BKCOLOR); // formerly HideAgent(a)

loctotals -= complex(a->ScrLoc.x, a->ScrLoc.y);
a->ScrLoc = newpoint;  							// NOW set new position
loctotals += complex(a->ScrLoc.x, a->ScrLoc.y);

ShowAgent(a);          							// and show it there
}                              	//MoveAgent
//-----------------------------------------------------------------------
// redraw all the agents so they are visible.  clear screen before calling, if necessary.
void SColonyWindow::drawagents()
{
AllowDirectToScreen = FALSE;
int count = Ag.GetItemsInContainer();
for(int i = 0 ; i < count ; i++)
	ShowAgent(Ag[i]);
AllowDirectToScreen = TRUE;
}                      			//drawagents
//----------------------------------------------------------------------------
void SColonyWindow::EraseDib() { dib->Erase(dib->GetColor(BKCOLOR)); }
//-----------------------------------------------------------------------
// give an agent a random available screen location
void SColonyWindow::PlaceAgent(agent* a)
{
loctotals -= complex(a->ScrLoc.x, a->ScrLoc.y);		// if brand new, it's just (0,0)
TSize r = dib->Size();
do                      			// use the ints to find a slot
{
	a->ScrLoc = TPoint(random(r.cx),random(r.cy));
}
while(IsOccupied(a->ScrLoc));   				// test for already occupied
loctotals += complex(a->ScrLoc.x, a->ScrLoc.y);
a->Loc = complex(a->ScrLoc.x, a->ScrLoc.y);  	// set the actual loc

ShowAgent(a);
}							//PlaceAgent
//-----------------------------------------------------------------------
// reassign screen locations to all
// needed when window size changed (but probably don't call from EvSize())
void SColonyWindow::PlaceAllAgents()
{
AllowDirectToScreen = FALSE;

// first, remove all from display so they don't block placements
// you must clear the entire screen: various ScrLoc may now be offscreen.
// with screen empty, no point will return as Occupied.
EraseDib();
loctotals = complex(0,0);			// center invalid: all will be reassigned

int count = Ag.GetItemsInContainer();
for(int i = 0 ; i < count ; i++)
	PlaceAgent(Ag[i]);

AllowDirectToScreen = TRUE;
}  	                		//PlaceAllAgents
//-----------------------------------------------------------------------
// BKMK: ARRAY MANAGEMENT FUNCTIONS
//-----------------------------------------------------------------------
void SColonyWindow::Flush()
{
lowpop = 16000;
highpop = 0;
childcount = 0L;
loctotals = complex(0,0);
Strongest = MostProlific = 0;

Ag.Flush(TShouldDelete::Delete);
}                   			//Flush
//-----------------------------------------------------------------------
// tries to add a generic agent.
// you can alter the added agent after adding it (reassign pgm, etc.)
// returns ptr to it, or 0 if it fails (array full)
agent* SColonyWindow::AddAgent()
{
if(Ag.GetItemsInContainer() >= MAXPOP)
	return 0;
agent* a = new agent(*this);
if(!Ag.Add(a))						// but it should always succeed
{
	delete a;
	return 0;
}                  					// if falls through, agent was successfully added
a->color = 3 + random(256 - 3);		// random color index, except reserved
a->id = ++childcount;
PlaceAgent(a);						// assign screen loc and display it

highpop = max(highpop,Ag.GetItemsInContainer());
return a;
}								//AddAgent
//-----------------------------------------------------------------------
int SColonyWindow::DestroyAgent(agent* a)
{
SetPixel(a->ScrLoc, BKCOLOR);  					// remove from screen BEFORE destroying!
loctotals -= complex(a->ScrLoc.x, a->ScrLoc.y); // remove from screenloc totals

int i = Ag.Destroy(a);

// if this was one of the marked ones, you must find a replacement for it.
// note a is now an invalid pointer, but you can still test it against the other ptrs
if(a == Strongest)								// ok for speed: occurs rarely
	FindStrongest();
if(a == MostProlific)
	FindMostProlific();

lowpop = min(lowpop,Ag.GetItemsInContainer());
return i;
}                			   	//DestroyAgent
//----------------------------------------------------------------------------
// kills (Destroys) the weakest cell in array
// in case of a tie, it chooses the first (oldest) with that energy.
// use caution calling from within loops: when a cell is destroyed, array contracts.
// This MUST NOT be called FROM any agent member function, because it could Destroy(this).
// returns TRUE if one was killed, FALSE if not.
BOOL SColonyWindow::killweakest()
{
int count = Ag.GetItemsInContainer();
if(!count)
	return(FALSE);

agent* weakest = Ag[0];           		// start with first as the weakest
for(int i = 0 ; i < count ; i++)
	if(Ag[i]->energy < weakest->energy)
		weakest = Ag[i];

DestroyAgent(weakest);
return(TRUE);
}                       		//killweakest
//-----------------------------------------------------------------------
// returns TRUE if given point is occupied, FALSE if not.
// this version tests the screen dot, which is only set if the agent is "really" there:
// if you change it to search the array,
// it could cause problems; an agent can be temporarily invisible on screen,
// but with temporarily invalid ScrLoc that would match in the search. (see sense())
// AGCOLOR is 1, so an offscreen point never appears occupied by mistake.
BOOL SColonyWindow::IsOccupied(const TPoint& p) { return(dib->GetPixelByte(p) == AGCOLOR); }
//-----------------------------------------------------------------------
// identify which agent is occupying a pixel & return pointer
// returns pointer, or ZERO if match not found.
agent* SColonyWindow::Occupant(const TPoint& p)
{
if(!IsOccupied(p))	// quick test, quick return (must be VISIBLE to be an occupant)
	return 0;
int count = Ag.GetItemsInContainer();
for(int i = 0 ; i < count ; i++)
	if(Ag[i]->ScrLoc == p)
		return(Ag[i]);
logerror("White dot not found by Occupant()");
return(0);
}                  				//Occupant
//-----------------------------------------------------------------------
// find mass center of all points on screen = (average(x),average(y))
// this function actually calculates it from scratch each call, using Loc.
// (ScrLoc may actually be more appropriate)
complex SColonyWindow::centerofmass()
{
int count = Ag.GetItemsInContainer();
if(!count)
{
	TSize r = dib->Size();
	return complex(r.cx / 2, r.cy / 2);
}
complex sum = complex(0,0);
for(int i = 0 ; i < count ; i++)
	sum += Ag[i]->Loc;

return(sum / count);
} 		                  //centerofmass
//-----------------------------------------------------------------------
// get quick center of mass calculation, using loctotals (based on ScrLoc, not Loc).
// unused and untested.  If it DOES work, it should be just as reliable as
// centerofmass and much faster.
complex SColonyWindow::getcenterofmass()
{
int count = Ag.GetItemsInContainer();
if(!count)
{
	TSize r = dib->Size();
	return complex(r.cx / 2, r.cy / 2);
}
return(loctotals / count);
} 		                  //getcenterofmass
//-----------------------------------------------------------------------
// center of screen.  A much quicker alternative to center of mass when all you
// really want is a focal point.
complex SColonyWindow::screencenter() { return complex(dib->Width() / 2, dib->Height() / 2); }
//----------------------------------------------------------------------------
// set relative thresholds
void SColonyWindow::AutoSetPOPValues(int maxpop)
{
if(maxpop <= 16000)
{
	MAXPOP = maxpop;
	OPTPOP = MAXPOP / 2;	// optimum (medium) population
	MINPOP = MAXPOP / 4;	// refill to optimum when reduced to this level
}
}                   		//AutoSetPOPValues
//----------------------------------------------------------------------------
void SColonyWindow::Cull()
{
// if there are more agents than allowed in the array, delete the weakest.
// this is now the only thing that keeps pop below 16000
if(Ag.GetItemsInContainer() >= MAXPOP)
{
	AllowDirectToScreen = FALSE;
	SendStatusReport("== Culling array.  Please wait. (May take several minutes) ==\n");
	HCURSOR oldcursor = ::SetCursor(::LoadCursor(NULL, IDC_WAIT));
	while(Ag.GetItemsInContainer() > OPTPOP)
		killweakest();
	CmViewClrScr();
	AllowDirectToScreen = TRUE;
	::SetCursor(oldcursor);  					// restore cursor
}
}								//Cull
//----------------------------------------------------------------------------
void SColonyWindow::TopUp()
{
if(Ag.GetItemsInContainer() <= MINPOP)		  	// array nearly empty (too many dead)
{
	AllowDirectToScreen = FALSE;
	SendStatusReport("== Topping up array.  Please wait. (May take several minutes) ==\n");
	HCURSOR oldcursor = ::SetCursor(::LoadCursor(NULL, IDC_WAIT));
	if(ots.beepon)             			     	// sound warning
	{
	}
	// keep survivors competitive.  If array is depleted this badly,
	// even the strongest survivors may be weaker than the new cells.
	// restore them to STARTENERGY if necessary.
	int count = Ag.GetItemsInContainer();
	for(int i = 0 ; i < count ; i++)
		Ag[i]->energy = max(Ag[i]->energy,agent::STARTENERGY);
	FindStrongest();

	// Now top up the array with random cells.  They may not
	// be very fit, but they can serve as food and a more varied
	// gene pool for the remaining ones.
	setPGMLENGTH();								// set length for random cells
	while(Ag.GetItemsInContainer() < OPTPOP)    // fill up the array
		AddAgent();
	CmViewClrScr();
	AllowDirectToScreen = TRUE;
	::SetCursor(oldcursor);  					// restore cursor
}
}									// TopUp
//-----------------------------------------------------------------------
// new cell pgms are PGMLENGTH chars long.  At the start of a completely new
// run, this is 1, but when continuing a previous run or topping up a depleted
// array, you want new cell pgms to be about the same length as the existing
// ones.  If not (if PGMLENGTH remains unmodified), then when old cells mate
// with new ones, the child pgms are greatly mangled (collapsed) mutations of
// the existing ones.  If you adjust PGMLENGTH, as here, it is like a sudden
// influx of a new population at a similar stage of development, but with much
// wider (random) genetic variation, which is what you need for a population
// that hasn't been able to sustain itself.
// Sets PGMLENGTH to the average length of existing cells.
void SColonyWindow::setPGMLENGTH()
{
PGMLENGTH = 1;					// default for new run or when array is empty
int count = Ag.GetItemsInContainer();
if(!count)
	return;
long sum = 0;
for(int i = 0 ; i < count ; i++)
	sum += (long)(Ag[i]->pgm.length());

PGMLENGTH = max((int)(sum / (long)count),1);	// min = 1
string::initial_capacity(max(PGMLENGTH,30));	// size strings to match
}                      	//setPGMLENGTH
//----------------------------------------------------------------------------
// BKMK: AGENT PROGRAM FUNCTIONS (RUN)
//----------------------------------------------------------------------------
// run pgm of an agent chosen at random.  seems to work well.
// in rapidly expanding populations, some new cells will get to run their pgms
// before the rapidly reproducing old cells cause array to fill up and get culled.
// eliminates the bias in favor of old cells that used to be caused by running sequentially
// through the array.
void SColonyWindow::RunRandomOne()
{
if(Ag.IsEmpty())			// it changes too often to give it a variable
	return;					// empty.  nothing to do.

int current = random(Ag.GetItemsInContainer());
agent* thisone = Ag[current]; 			// get ptr: its index may change
thisone->run();							// run this agent's pgm
if(!thisone->IsAlive())					// didn't survive
{
	if(thisone->energy > 0)			// it's 0 only if eaten, and EATEN was already played;
		thisone->play(agent::DEAD);	// DEAD here means it ran out of energy
	DestroyAgent(thisone);
}
// note this is now called after every cell, catches array size out of bounds promptly
Cull();					// adjust array size
TopUp();			   	// adjust array size

agentsran++;
if(ots.displayon)
	Invalidate(FALSE);			// TRUE causes screen flicker
}								// RunRandomOne
//----------------------------------------------------------------------------
void SColonyWindow::draw(int /* drawcount */)
{
if(!drawing || Ag.IsEmpty())
	return;

TTime now;
// AUTO-REPORT AT THE END OF EACH 60 SECONDS.  (a "pass" count is now meaningless)
if(now.Minute() != referencetime.Minute())
{
	runloops++;			// now it just counts how many timer intervals have elapsed
	CmViewClrScr();		// erases screen (to elim.trails), redraws all agents
	Report();			// report to terminal
}
// SAVE CELL PGMS TO DISK EACH HOUR, USEFUL BACKUP IN CASE OF CRASH
// (RTF: the saves are only interesting if Trails is on, even if Celldisplay is off.)
if(now.Hour() != referencetime.Hour())
{
	pgmstodisk("Interim",20);   	// write FIRST 20 cell pgms to disk

	// WRITE A NUMBERED .BMP FILE.  YOU CAN LATER LOOK AT HOURLY PROGRESS
	// FORMAT = FULLPATH\AAAAA000.BMP
	FilePathParser f(fileroot); 	// allows separating path from filename
	string s = f.File;   			// get the filename
	if(s.length() > 5)				// truncate to 5 chars, if necessary
		s.remove(5);
	char buf[MAXPATH];
	ostrstream(buf,sizeof(buf)) << f.DriveDir() << s
								<< setfill('0') << setw(3) << ++piccount << ends;
	dib->WriteFile((string(buf) + ".BMP").c_str());
}
referencetime = now;		// current time becomes the new reference

if(!Strongest)				// these also ensure that these get assigned at pgm startup
	FindStrongest();		// strongest in whole colony
if(!MostProlific)
	FindMostProlific();		// the one with the most children
RunRandomOne();				// new version
}               			// draw
//----------------------------------------------------------------------------
// BKMK: TWINDOW FUNCTIONS AND EVENT HANDLERS
//----------------------------------------------------------------------------
BOOL SColonyWindow::IdleAction(long idlecount)
{
draw();
return(SDibWindow::IdleAction(idlecount));
}                        	// IdleAction
//----------------------------------------------------------------------------
void SColonyWindow::SetupWindow() { SDibWindow::SetupWindow(); }
//----------------------------------------------------------------------------
void SColonyWindow::CleanupWindow() { SDibWindow::CleanupWindow(); }
//----------------------------------------------------------------------------
void SColonyWindow::EvLButtonDown(UINT modkeys, TPoint& point)
{
drawing = FALSE;		  					// turn off calculation during drag
SDibWindow::EvLButtonDown(modkeys,point);	// must let drag occur, even if we won't use it
}							// EvLButtonDown
//----------------------------------------------------------------------------
// RELEASED left mouse button after defining a selection area.
void SColonyWindow::EvLButtonUp(UINT modkeys, TPoint& point)
{
SDibWindow::EvLButtonUp(modkeys,point);	// this saves start and end points of drag
drawing = TRUE;							// restore calculation
if(DragStart == DragEnd)                // prevent zero-area selection
	return;

// run through array finding all ags within the bounded area, and report on them.
string s;
int count = Ag.GetItemsInContainer();
for(int i = 0 ; i < count ; i++)
	if(DibDragRect.Contains(Ag[i]->ScrLoc))
	{
		s += viewcell(Ag[i]);
		if(s.length() > 40000L)		// avoid hitting string length limit.
		{
			SendStatusReport(s);
			s.remove(0);
		}
	}
SendStatusReport(s);  				// does nothing if s is empty
}								//EvLButtonUp
//----------------------------------------------------------------------------
// intentionally avoids SDibWindow's color editing, to avoid any potential problems:
// for one, I think AGCOLOR must be unique in palette.
void SColonyWindow::EvRButtonDown(UINT modkeys, TPoint& point)
{ TWindow::EvRButtonDown(modkeys,point); }
//----------------------------------------------------------------------------
void SColonyWindow::EvSetFocus(HWND hWndLostFocus)
{
SDibWindow::EvSetFocus(hWndLostFocus);	// reminder that it does exist, not documented
// added so that this fn, which I don't want to delete, does something.
CmPeek();	// it should also give 1 peek after return from a status report
}                      		 	//EvSetFocus
//----------------------------------------------------------------------------
void SColonyWindow::CmPeek() { Invalidate(TRUE); }
//----------------------------------------------------------------------------
void SColonyWindow::CmViewFitToWindow()
{
SDibWindow::CmViewFitToWindow();
Invalidate(TRUE);
}         		             	//CmViewFitToWindow
//----------------------------------------------------------------------------
void SColonyWindow::CmViewClrScr()
{
EraseDib();
drawagents();
Invalidate(TRUE);
}           			       	//CmViewClrScr
//----------------------------------------------------------------------------
// Allow user to edit options in the Options dialog box.
void SColonyWindow::CmViewOptions()
{
// so the dialog doesn't leave a hole, plus just showing the dialog is a way to get 1 peek.
Invalidate(TRUE);

// initialize transfer buffer data
ots.fittowindow = fittowindow;				// match SDibWindow's

ostrstream(ots.maxpopedit,sizeof(ots.maxpopedit)) << MAXPOP << ends;
ostrstream(ots.optpopedit,sizeof(ots.optpopedit)) << OPTPOP << ends;
ostrstream(ots.minpopedit,sizeof(ots.minpopedit)) << MINPOP << ends;
ostrstream(ots.passlimitedit,sizeof(ots.passlimitedit)) << passlimit << ends;

ostrstream(ots.dibsize,sizeof(ots.dibsize)) << dib->Width() << " " << dib->Height() << ends;
ostrstream(ots.stepmax,sizeof(ots.stepmax)) << agent::STEPMAX << ends;

SOptionsDialog od(this,TResId(OPTIONSDIALOG),&ots);
if(Parent)
	od.SetCaption(Parent->Title); // caption to match the (focus) child window it pertains to
if(od.Execute() != IDOK)
	return;

// copy transfer buffer data back
int a, b, c;
istrstream(ots.maxpopedit) >> a;
istrstream(ots.optpopedit) >> b;
istrstream(ots.minpopedit) >> c;
if((c > 0) && (b > c) && (a > b) && (a <= 16000)) 				// if valid
{
	MAXPOP = a;
	OPTPOP = b;
	MINPOP = c;
}
a = 0;
istrstream(ots.passlimitedit) >> a;		// no change if unparseable
if(a >= 0)
	passlimit = a;

if(fittowindow != ots.fittowindow)      // set SDibWindow's to match
	CmViewFitToWindow();

a = b = 0;
istrstream(ots.dibsize) >> a >> b;
if((a != dib->Width()) || (b != dib->Height()))
{
	if(a && (a < 50)) a = 50;	// impose a minimum legal size (but must allow 0)
	if(b && (b < 50)) b = 50;
	makenewdib(0,a,b,FALSE);	// if either is 0, new dib will match window size
	PlaceAllAgents();		 	// any change requires assigning new screen locations to all
}

a = 0;
istrstream(ots.stepmax) >> a;
if(a > 0)
	agent::STEPMAX = a;

// CmViewClrScr();
}								//CmViewOptions
//----------------------------------------------------------------------------
// write ALL current agent pgms to .LOG, same as (S)ave in DOS version.
// this is the only way you can save ALL the pgms into the log (or anywhere) for review,
// which is good, else it gets huge.
void SColonyWindow::CmFileSave() { pgmstodisk("Interim"); }
//----------------------------------------------------------------------------
BOOL SColonyWindow::CanClose()
{
if(!SDibWindow::CanClose())
	return(FALSE);

pgmstodisk("Ending",100);			// save some cells to .LOG file
string s = string("SAVE strongest cells to ") + to_upper(fileroot + ".AGS") +
				  " for use by next run?\n(This can take several minutes.)";
switch(MessageBox(s.c_str(),Parent->Title, MB_YESNOCANCEL | MB_ICONQUESTION))
{
	case IDNO:            	// will be discarded
		break;
	case IDCANCEL:       	// don't close
		return FALSE;
	case IDYES:
	{
		// save cell pgms to .AGS in order of decreasing energy.
		// Use this method only at END of run, because it Destroys() the cells as it
		// writes them out.  VERY slow (minutes) if there are many cells
		HCURSOR oldcursor = ::SetCursor(::LoadCursor(NULL, IDC_WAIT));
		AllowDirectToScreen = FALSE;
		ofstream outfile(backupfile(fileroot + ".AGS").c_str());
		FindStrongest();								// ensure Strongest is correct
		while(Strongest)								// until container is emptied
		{
			// if(Strongest->energy != agent::STARTENERGY) 	// can use to exclude newborns
				outfile << *Strongest << endl;				// write out its data,
			// Destroy it so next pass won't find it again.  Also auto-recalcs a new Strongest.
			DestroyAgent(Strongest);
		}
		AllowDirectToScreen = TRUE;
		::SetCursor(oldcursor);  					// restore cursor
		break;
	}
}
string inifile = fileroot + ".ini";
WritePrivateProfileString("info","MAXPOP",tostring(MAXPOP).c_str(),inifile.c_str());
WritePrivateProfileString("info","OPTPOP",tostring(OPTPOP).c_str(),inifile.c_str());
WritePrivateProfileString("info","MINPOP",tostring(MINPOP).c_str(),inifile.c_str());

return TRUE;
}                      		//CanClose
//----------------------------------------------------------------------------
// 						end class SColonyWindow
//////////////////////////////////////////////////////////////////////////////
// see wshowfs.cpp for notes about this class as a TMDIClient.
// but THIS one is the first where I've used SDDEHandler with it.
class SColonyClient : public TMDIClient, public SDDEHandler
{
public:
	SColonyClient(TModule* module = 0, DWORD instid = 0, const char* servicename = 0);
	~SColonyClient();

	// virtuals: TMDIClient
	virtual TMDIChild* InitChild();

	// virtuals: TWindow
	virtual BOOL IdleAction(long idlecount);
	virtual BOOL CanClose();

	// functions
	void Startup();				// interpret command line, open initial child windows, etc.
	void OpenFile(const string& filename);
	void TileOrMaximize();		// depending on MDIChild count

	FileArray FileList;					// list of files to create windows for

protected:
	// TWindow overridden virtuals
	virtual void SetupWindow();
	virtual void CleanupWindow();

	// event handlers
	void EvDropFiles(TDropInfo dropInfo);
	void CmFileExit() { GetApplication()->GetMainWindow()->CloseWindow(); }
	void CmFileOpen();
	void CmHelpIndex();
	void CmHelpAbout();
// 	void CmFileClose();	// a reminder NOT to try to close children this way.
						// CloseWindow() crashes the app.  and methods from
						// mdiclient.cpp won't compile!
	// variables
	HWND MSWordAtStartup;

DECLARE_RESPONSE_TABLE(SColonyClient);
};
//----------------------------------------------------------------------------
DEFINE_RESPONSE_TABLE1(SColonyClient,TMDIClient)
	EV_WM_DROPFILES,
	EV_COMMAND(CM_FILEEXIT,CmFileExit),
	EV_COMMAND(CM_FILEOPEN,CmFileOpen),
	EV_COMMAND(CM_HELPINDEX,CmHelpIndex),
	EV_COMMAND(CM_HELPABOUT,CmHelpAbout),
END_RESPONSE_TABLE;
//----------------------------------------------------------------------------
// constructor
SColonyClient::SColonyClient(TModule* module, DWORD instid, const char* servicename)
	: TMDIClient(module), SDDEHandler(instid, servicename)
{
MSWordAtStartup = FindWindow("OpusApp", NULL);
unlink("wadapt.err");					// new error log each run

// initialize other class(es)'s static pointers to point to this sddehandler
SColonyWindow::Dde = (SDDEHandler*)this;
}
//----------------------------------------------------------------------------
// destructor
SColonyClient::~SColonyClient()
{
}
//----------------------------------------------------------------------------
void SColonyClient::SetupWindow()
{
TMDIClient::SetupWindow();
DragAcceptFiles(TRUE);
}						//SetupWindow
//----------------------------------------------------------------------------
void SColonyClient::CleanupWindow()
{
// pointless?  one example does it, in another it's commented out.
// DragAcceptFiles(FALSE);
TMDIClient::CleanupWindow();    		// must do base last
}                      	//CleanupWindow
//----------------------------------------------------------------------------
BOOL SColonyClient::CanClose()
{
if(!TWindow::CanClose())
	return(FALSE);

// projects share error log (wadapt.err), so report only once from global location:
uint err = SColonyWindow::logerror();
if(err)
{
	string s = string("There were ") + tostring(err) + " errors.  See WADAPT.ERR.";
	MessageBox(s.c_str(),GetApplication()->GetName(), MB_OK | MB_ICONINFORMATION);
}

// a system crash seems to occur (LATER!) if this app launches another app, but this one closes first.
// so we must try to decide IF this app launched the other.
// (it actually occurs inside DDEInitiate).
// if MSWord wasn't running at startup, but is now, OR
// if the MSWord instance that WAS running at startup is different from the one running now,
// assume that we launched its current instance.
// Not foolproof, but it's the best guess we can make.
// Unsure this is the best loc.  Our Handler must still be valid just in case MSWord
// or Windows causes any DDEML cleanup activity at shutdown.
// Not thoroughly tested, but this did seem to stop the crashes at shutdown.
HWND MSWordAtClose = FindWindow("OpusApp", NULL);
if(MSWordAtClose)
	if(!MSWordAtStartup || (MSWordAtStartup != MSWordAtClose))
	{
		SDDEConv* chan1 = DDEInitiate("WINWORD","SYSTEM");
		if(chan1)
		{
			chan1->DDEExecute("[FileExit 0]");
			DDETerminate(chan1);
		}
	}
return TRUE;
}                   	   		//CanClose
//----------------------------------------------------------------------------
// this EvDrop is called if you drop the files in the Client area, and opens
// a new window for each dropped file.  adds dropped files to list, but
// initialize is done in IdleAction.
void SColonyClient::EvDropFiles(TDropInfo dropinfo)
{
int count = dropinfo.DragQueryFileCount();
for(int i = 0 ; i < count ; i++)
{
	// seems wasteful to new each one, but this will still work if path exceeds MAXPATH.
	int length = dropinfo.DragQueryFileNameLen(i) + 1;
	char* filename = new char[length];
	dropinfo.DragQueryFile(i,filename,length);
	FileList.AddFile(string(filename));
	delete[] filename;
}
dropinfo.DragFinish();
}     			       			//EvDropFiles
//----------------------------------------------------------------------------
TMDIChild* SColonyClient::InitChild()
{
return new TMDIChild(*this, "Untitled", new SColonyWindow);
}
//----------------------------------------------------------------------------
// open file in a new window
void SColonyClient::OpenFile(const string& filename)
{
TMDIChild* child = dynamic_cast<TMDIChild*>(CreateChild());
if(child)
{
	SColonyWindow* s = dynamic_cast<SColonyWindow*>(child->GetClientWindow());
	if(s)
		s->Open(filename);
}
TileOrMaximize();    			// size them to fill the window
}								//OpenFile
//----------------------------------------------------------------------------
void SColonyClient::CmFileOpen()
{
// note chdir(DATADir) method that would usually go here is not accessible to SColonyClient.
// flags checked, ok
TOpenSaveDialog::TData filedata(OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_CREATEPROMPT,
								 "Agent Files (*.AGS)|*.AGS|",0,0,"AGS");
strcpy(filedata.FileName,"A.AGS");	// always suggest the same name as default.
if((TFileOpenDialog(this,filedata)).Execute() == IDOK)
	OpenFile(filedata.FileName);
else
	if(filedata.Error)
	{
		string s = string("Too many files?\nError code: ") + tostring(filedata.Error);
		MessageBox(s.c_str(),"Dialog box error",MB_OK | MB_ICONEXCLAMATION);
	}
}							//CmFileOpen
//----------------------------------------------------------------------------
void SColonyClient::CmHelpIndex() { WinHelp(HelpFilename,HELP_INDEX,0); }
//----------------------------------------------------------------------------
void SColonyClient::CmHelpAbout()
{
MessageBox("Copyright (C)1995-2002 Steven Whitney. GNU GPL Version 3.",GetApplication()->GetName(),
			MB_OK | MB_ICONINFORMATION);
}
//----------------------------------------------------------------------------
// call IdleAction for all children.  plus, does any top-level IdleAction behavior.
BOOL SColonyClient::IdleAction(long idlecount)
{
// if any files have been added to the list, open windows for them now.
// no IsIconic test because if app is minimized, Client isn't showing, anyway.
// only the app could accept them, and it doesn't.
while(FileList)
	OpenFile(FileList.GetNext());

idlecount = 0L;							// force each TFrame to iterate thru its children
for(TWindow* child = GetFirstChild() ; child ; child = child->Next())
{
	child->IdleAction(idlecount);
	if(child == GetLastChild())
		break;
}
return TRUE;
}							//IdleAction
//----------------------------------------------------------------------------
void SColonyClient::TileOrMaximize()
{
int count = 0;
TMDIChild* mdichild = 0;
for(TWindow* w = GetFirstChild() ; w ; w = w->Next())
{
	TMDIChild* tempchild;
	if((tempchild = dynamic_cast<TMDIChild*>(w)) != 0)
	{
		mdichild = tempchild;
		count++;
	}
	if(w == GetLastChild())
		break;
}
switch(count)
{
	case 0: return;
	case 1: mdichild->Show(SW_SHOWMAXIMIZED); break;
	default: TileChildren(); break;
}
}               			//TileOrMaximize
//----------------------------------------------------------------------------
// do initial setup when application first starts,
// interpret command line, open initial child windows, etc.
void SColonyClient::Startup()
{
// I think the SDibWindow does this, too, so each one gets an (unused) copy of this list.
// get command line arguments (list of files to display)
#if defined(__WIN32__)
	// discard program name, then the rest are file names
	string pgmname;
	istrstream(GetCommandLine()) >> pgmname >> FileList;
#else
	for(int i = 1 ; i < _argc ; i++)
		FileList.AddFile(string(_argv[i]));
#endif	//(__WIN32__)

if(FileList)							// create a child for each filename read
{
	while(FileList)
		OpenFile(FileList.GetNext());
}
else 									// no files
{
	// return to last-saved data directory only if there were no files on command line:
	// if there were files, assume user wants THEIR dir.
	char buf[MAXPATH];            		// filename from .INI file
	if(getcwd(buf,sizeof(buf)))      	// temporary use to get current logged dir
	{                                   // (getcwd shouldn't ever fail)
		string cwd = buf;				// copy it to use as the default parameter
		GetPrivateProfileString("info","datadir",cwd.c_str(),buf,sizeof(buf),INIFilename);
		string s(buf);
		if(cwd != s)			// don't bother if not found or same as current cwd
			FilePathParser(s + "\\").ChDir();
	}
	CmFileOpen();		// show FileOpen dialog and get a file name from user
}						// end else
TileOrMaximize();		// size them to fill the window
}								//Startup
//----------------------------------------------------------------------------
// 						end class SColonyClient
//////////////////////////////////////////////////////////////////////////////
class TMyApp : public SDDEApplication
{
public:
	TMyApp(const char far *title, DDESides ddeside = CLIENT, const char* servicename = 0);

protected:
	virtual BOOL IdleAction(long idlecount);
	virtual void InitInstance();
	virtual int TermInstance(int status);
	virtual void InitMainWindow();

DECLARE_RESPONSE_TABLE(TMyApp);
};
//----------------------------------------------------------------------------
DEFINE_RESPONSE_TABLE1(TMyApp,SDDEApplication)
END_RESPONSE_TABLE;
//----------------------------------------------------------------------------
// constructor.  title only used in case of error
TMyApp::TMyApp(const char far *title, DDESides ddeside, const char* servicename)
	: SDDEApplication(title, ddeside, servicename)
{
}
//----------------------------------------------------------------------------
BOOL TMyApp::IdleAction(long /* idlecount */ )
{
SDDEApplication::IdleAction(0);
return TRUE;
}
//----------------------------------------------------------------------------
void TMyApp::InitInstance()
{
SDDEApplication::InitInstance();					// calls InitMainWindow
// any addl code goes AFTER base class call

TDecoratedMDIFrame* frame = dynamic_cast<TDecoratedMDIFrame*>(GetMainWindow());
if(frame)
{
	SColonyClient* client = dynamic_cast<SColonyClient*>(frame->GetClientWindow());
	if(client)
		client->Startup();

	// the frame CANNOT get dropped files because it owns the entire main window;
	// it would intercept a drop before any other window could process it.
	// so you could not drop into an SColonyWindow.  Because of this, however,
	// you can't drop files into the app when it's iconized on the desktop.
	// frame->DragAcceptFiles(TRUE);

}	// end if(frame)

}                       	//InitInstance
//----------------------------------------------------------------------------
void TMyApp::InitMainWindow()
{
EnableCtl3d(TRUE);			// these are better.  makes all controls automatically 3d.

const char* p = ((DDESide == SERVER) && BaseServiceName.length()) ? BaseServiceName.c_str() : 0;
SColonyClient* client = new SColonyClient(0, InstId, p);
TDecoratedMDIFrame* frame = new TDecoratedMDIFrame(Name, TResId(MAINMENU), *client, TRUE);
frame->Attr.AccelTable = TResId(MAINMENU);

TStatusBar* sb = new TStatusBar(frame, TGadget::Recessed);
frame->Insert(*sb, TDecoratedFrame::Bottom);

// Construct a control bar
TControlBar *cb = new TControlBar(frame);
cb->Insert(*new TButtonGadget(TResId(CM_FILEOPEN),CM_FILEOPEN,TButtonGadget::Command));
cb->Insert(*new TButtonGadget(TResId(CM_FILESAVE),CM_FILESAVE,TButtonGadget::Command));
cb->Insert(*new TSeparatorGadget(24));
cb->Insert(*new TButtonGadget(TResId(CM_TILECHILDREN),CM_TILECHILDREN,TButtonGadget::Command));
cb->Insert(*new TSeparatorGadget(24));
cb->Insert(*new TButtonGadget(TResId(CM_HELPINDEX),CM_HELPINDEX,TButtonGadget::Command));
cb->SetHintMode(TGadgetWindow::EnterHints);
frame->Insert(*cb, TDecoratedFrame::Top);

nCmdShow = SW_SHOW;			// SW_SHOWMAXIMIZED;
SetMainWindow(frame);
}							//InitMainWindow
//----------------------------------------------------------------------------
// if there are multiple instances running, .INI file gets info from the last closed.
int TMyApp::TermInstance(int status)
{
// write to the .INI file:
// default dir is our current dir
char buf[MAXPATH];
if(getcwd(buf,sizeof(buf)))
	WritePrivateProfileString("info","datadir",buf,INIFilename);

return SDDEApplication::TermInstance(status);
}                      		//TermInstance
//----------------------------------------------------------------------------
// 						end class TMyApp
//////////////////////////////////////////////////////////////////////////////
// OwlMain
int OwlMain(int /* argc */, char* /* argv */ [])
{
randomize();
TTime::PrintDate(TRUE);
TDate::SetPrintOption(TDate::Numbers);
string::set_case_sensitive(0);
string::set_paranoid_check(1); 		// #error probably remove this

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

return(retval);
}

Additional code for this project is on the next page.

 

 

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 03/16/2009.