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   Ads   Donate   Humor

John Holland-style classifier system demonstrates natural selection and evolution. C++ console program for MSDOS.

Classify.cpp creates and runs a classifier system (a type of "complex system"). It was inspired by the description of John Holland's classifier system in the book Complexity, by M. Mitchell Waldrop. The book was my only source of information on how to create a classifier system, and it is NOT by any means a step-by-step manual! Its description and discussion of the method did, however, provide sufficient information to create this working model. Having been built with only one source of information and a lot of guessing, this implementation is probably unusual compared to others. Amazingly, though, it does work.

Arbitrary text strings define the "problem", the "solution", the "stimulus" that triggers each rule to respond, and the "response" that each rule gives. Weak rules that do not contribute toward a solution do not propagate and are killed off. Strong contributing rules are crossbred to create child rules, with mutations. The system eventually produces a ruleset that is able to solve the problem. Thus, the program demonstrates how variation and natural selection in the presence of environmental pressures produce evolution in a population.

It was developed with Borland C++ 4.0 for MSDOS, and uses Borland BGI graphics.

The most generally useful part of this program is probably the C++ "mating constructors" that use a genetic algorithm to create a new object that has a mix of characteristics from each of two parent objects. The methods are easily modified to other purposes, and I've used them in several other programs. One of these constructors is farther down this page, and two used for the Rule class begin on the next page.

The project is somewhat overly modular to allow for expansions that were never done. Each component in its own source file. This screenshot might help set up the project nodes:

Screenshot of the Classify.cpp project nodes in the Borland C++ 4.0 IDE.

You can display the status of the system in either text or graphics mode:

In graphic mode, each Rule is shown as a column in a bar chart. The height shows the strength (bid value), and the colors encode whether it was or wasn't a recent lottery bidder or winner. The screenshot is enlarged. Each column is actually 1 pixel wide.

Screenshot of the Classify.cpp status report in text mode.

In text mode, data about 20 of the classifiers are shown:

#     Stimulus Response   Bid        MsgBoard  Problem  Solution
----- ------------------- ---------- --------- -------- --------
   0: 1#1###1# 00111001 -         90  01100010 01010101 10011101
   1: 11##111# 01111001           90  00110101 01010101 10011101
   2: ###00### 10010011           90  10011000 01010101 10011101
   3: #0#1#001 01010111           90  11001000 01010101 10011101
   4: 01#####0 10101011           90  01000000 01010101 10011101
   5: ##1#01#0 01111101 -         90 s01010101 01010101 10011101
   6: 100#010# 10010000           90           01010101 10011101
   7: 11#00000 11011111           90           01010101 10011101
   8: ####0#1# 01100010 W         90           01010101 10011101
   9: #1#####1 10100011 -         90           01010101 10011101
  10: 10##0##0 00001101           90           01010101 10011101
  11: ###0#### 00000000 -         90           01010101 10011101
  12: 0#101#10 01011101           90           01010101 10011101
  13: #0#1#1## 01001010 -         90           01010101 10011101
  14: 1000##01 11111101           90           01010101 10011101
  15: 01#01### 01011111 -         90           01010101 10011101
  16: ####11## 00011011           90           01010101 10011101
  17: 0100##11 00110100           90           01010101 10011101
  18: 0#1#11#0 01000000           90           01010101 10011101
  19: 00#0111# 01101111           90           01010101 10011101
Child:        123. Total:       1800.  Iterations: 562.  Rules: 123
------------------------------------------------------------------------
Paused.  Press any key...

This program's purpose and methods aren't documented well in any single location. Reading Waldrop's Complexity, and then this program's project notes in Complex.doc, and then the program's source code comments would be the best route to understanding it. There is a lot of jargon related to classifier systems. As you read the above sources, you'll become familiar with the vocabulary.

Download:

Click here to download classifycpp.zip (about 66 KB)

In addition to the code listed on these pages, the zip file contains:

  • CLASSIFY.DOC The section of Complex.doc that has this program's project notes, saved as .RTF for maximum compatibility. Rename to .DOC for use with MSWord.
  • CLASSIFY.XLS, Excel 5.0 or later, which appears to have some statistical tests run while trying to find the optimum value of a parameter.
  • PROBLEMS.DAT, which I think defines a problem to solve and might be a required input file, and RULES.DAT, which is most likely just an old output file, but is included in case I'm wrong about that. Both are text files.
  • COPYING.TXT, the GNU GPL license.

main.cpp

/*	main.cpp			1-25-99
	This file is part of the CLASSIFY classifier system project.
	Copyright (C)1995-99 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 2 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.

A John Holland-style classifier system, as described in "Complexity", by M. Mitchell Waldrop.
The book was my ONLY source of information on how to create a classifier system. It is
NOT a step-by-step manual!, but its description and discussion of the method did provide
enough information for me to create this working model, which is probably quite
peculiar compared to other programs people have written to do this.

Its graphics and reports are fun and interesting to watch, but the program doesn't do anything
useful. It evolved into a sort of crude classifier system laboratory whose purpose was to compare
various possible methods and parameters in the classifier system. I never really studied it
much, though, because the classifier model proved unwieldy. You have to encode the problem,
encode the rules specific to it, encode the interpretation of the rules, and define what
outcome constitutes a "successful" solution. And all this has to be done for every
problem you want a classifier system to solve. I found this to be too much overhead
machinery to be a practical method for solving real problems. So I kept the lottery system
and the bucket brigade passback, but put them into a rule-based system, avoiding the
bit-encoded or byte-encoded classifier system itself. Using English-encoded
rules avoids the complications of encoding and decoding arbitrary strings of bits or bytes
that mean nothing to a human obsever.

NOTE that this is now the primary source file (for To Do list, etc.), not classify.cpp.

------
TO DO:

review entire program, all files, carefully.

look for // #error lines

change lottery, etc. execution order to match book.

[9/27/2006: The methods developed in this program were turned into a more rule-based type
system, generalized, and put to use in the WTalk.ide project. Its "classifier-like system"
is used to reward or punish "reply rules" according to how the human user rated the response
that was generated.]

maybe pull out bb also into its own class BulletinBoard.
One reasonable organization for Classifier is that it HAS as its members
	an environment 		external, the outside (simulation)
	a ruleset 			internal, how to deal with the outside
	a bulletinboard 	interface, where the 2 "meet".

in statustext:  numbering 0-19 pointless: issue each rule an ID#, and show those,
so you can track them.  don't repeat problem and solution all the way down the
page.  show once, and use the space to display the Classifier constants somewhere,
labeled!

Write a classify.txt file similar to adapt.txt, that describes what you'll
be watching, especially for options 1 and 2 (is it 4 consecutive problems now?, etc.)
Note also that the text status display only shows the first 20 rules, and
thus the winner may not be shown.

write the byte<->string conversions mentioned in complex.doc.

do a long evolve run from scratch to test wholeset.tim.

Before implementing any features involving variable size strings, get the
current version in a basically stable and final form.  This and some other
proposed changes may be wandering so far from the original classifier idea
that they should be implemented in completely new programs, or kept merely as
ideas for future classifier programs that have a specific use.

all other notes are in COMPLEX.DOC.

------
Files:

finalset.dat = ending constants from latest evolve run
finalset.sav = archive of sets that were particularly good (maintained manually)
finalset.tim = times from latest time trial run
wholeset.tim = times for the whole set at intervals during evolve()

*/
#include "classify.h"

extern unsigned _stklen = 64000U;

////////////////////////////////////////////////////////////////////////////
// global data
////////////////////////////////////////////////////////////////////////////

constream scr;      			// console screen

////////////////////////////////////////////////////////////////////////////
//
//--------------------------------------------------------------------------
// watch a single classifier learn
void watchsingle(BOOL userandom)
{
scr << Classifier::helptext();		// should always be in text mode on entry
presskey(scr);

BOOL readfromdisk = FALSE;
ifstream infile;
if(yesno("Load Classifiers sequentially from disk file FINALSET.DAT",scr))
{
	readfromdisk = TRUE;
	infile.open("finalset.dat");
}

BOOL doanother = TRUE;
while(doanother)
{
	restorecrtmode();

	Environment e;
	Classifier c(&e,userandom);	// new one each time, with a new set of starting rules

	if(readfromdisk)   			// if reading from file, override with file data
	{
		infile >> ws;       	// force fail if at end of file
		if(infile.fail())
		{
			scr << "No more file data.  Subsequent classifiers will be ";
			scr << (userandom ? "random." : "standard.") << endl;
			infile.close();
			readfromdisk = FALSE;
		}
		else
			infile >> c;
	}
	scr << c << endl;       		// show constants
	presskey(scr);

	if(Classifier::displaymode == Classifier::graphics)
		setgraphmode(VGAHI);	// restore graphics mode

	for(int i = 0 ; i < 7 ; i++)	// number of problems each Classifier solves
	{
		switch(i)
		{
			// repeating problems tests both learning and retention.
			case 0: e.setproblem(0); break;  	// first random problem
			case 1: e.setproblem(1); break;		// second random problem
			case 2: e.setproblem(0); break;		// first problem again
			case 3: e.setproblem(1); break;
			case 4: e.setproblem(0); break;
			case 5: e.setproblem(2); break;
			case 6: e.setproblem(3); break;
		}
		if(c.run() == 27)
			break;
	}
// 	c.rulestodisk();			// write rule set to disk

	restorecrtmode();
	doanother = yesno("Do another",scr);
}
}					// end watchsingle
//--------------------------------------------------------------------------
// create a set of classifiers and allow them to evolve
// A user ESC during any Classifier's run() function will cause save & return.
// A user Q will abort that Classifier, go to the next.
// All Classifiers work on the same problem set.
void evolveclassifiers()
{
Classifier::beepon = Classifier::statusreport = FALSE;	// no beep, no displays
Classifier::displaymode = Classifier::text;

const int csetsize = 8;			// number of Classifiers to create & maintain
Environment e;
TIArrayAsVector<Classifier> cset(csetsize,0,1);	// the evolving set

// this allows continuing a previous run or specifying all starting constants
if(yesno("Load starting Classifiers from disk file FINALSET.DAT",scr))
{
	ifstream infile("finalset.dat");
	Classifier c(&e);
	while(infile >> c)					// read all that are there
		cset.Add(new Classifier(c));    // uses copy constructor
}

// fill to csetsize with randoms, whether some were from file or not
while(cset.GetItemsInContainer() < csetsize)  // create initial random set
	cset.Add(new Classifier(&e,TRUE));

ofstream timerfile("wholeset.tim");
Stopwatch runtimer;					// marks off timing intervals
Stopwatch passtimer;				// times each pass
long fastest1, fastest2;			// first and second fastest times so far
runtimer.start();
for(long passcount = 0L; ; passcount++)		// each pass starts here
{
	fastest1 = fastest2 = 600L;		// even parents have to beat 10 min.
	e.flushproblems(); 			    // flush & generate first problem
	e.setproblem(3);                // set highest problem we'll need
	passtimer.start();
	int ccount = cset.GetItemsInContainer();	// for each Classifier,
	for(int i = 0 ; i < ccount ; i++)
	{
		cset[i]->rs.newruleset();	// empty trained rule sets from previous pass
		cset[i]->sw.reset();   		// time only problems solved this pass
		scr << "Memory available = " << farcoreleft() << "  " << TTime() << endl;
		scr << endl << "Classifier " << i << " constants = " << *(cset[i]) << endl;
		// if too many times are the same, you need more problems
		for(int j = 0 ; j < 7 ; j++)     	// number of problems to solve
		{
			switch(j)
			{
				// repeating problems tests both learning and retention.
				case 0: e.setproblem(0); break;  	// first random problem
				case 1: e.setproblem(1); break;		// second random problem
				case 2: e.setproblem(0); break;		// first problem again
				case 3: e.setproblem(1); break;
				case 4: e.setproblem(0); break;
				case 5: e.setproblem(2); break;
				case 6: e.setproblem(3); break;
			}
			scr << "Classifier " << i << " Problem " << j << " ";

			// allow only (time to beat) - (time you've already used up)
			int ch = cset[i]->run(fastest2 - cset[i]->sw.totaltime);
			if(ch == 27)             				// user aborted run, finished
			{
				if(yesno("Save final set to disk",scr))
				{
					ofstream outfile("finalset.dat"); 	// save final set to disk
					for(int k = 0 ; k < ccount ; k++)
						outfile << *(cset[k]) << endl;
				}
				cset.Flush(TShouldDelete::Delete);
				return;
			}
			if(ch == 'q')  	// user aborted this one, or time limit exceeded
			{
				scr << "Too slow or user aborted." << endl;
				break;
			}
		}      				// here, the Classifier has solved all 4 problems
		scr << "                                       Total time = ";
		scr << cset[i]->sw.totaltime << " seconds." << endl;
		if(cset[i]->sw.totaltime <= fastest1)
		{
			fastest2 = fastest1;
			fastest1 = cset[i]->sw.totaltime;
		}
		else
			fastest2 = min(fastest2,cset[i]->sw.totaltime);
	}										// end for(each Classifier)
	while(cset.GetItemsInContainer() > 2)   // kill weakest until only 2 left
	{
		Classifier* weakest = cset[0];       	// start first as the weakest
		int ccount = cset.GetItemsInContainer();
		for(i = 0 ; i < ccount ; i++)			// if slower,
			if(cset[i]->sw.totaltime > weakest->sw.totaltime)	// tie goes to child
				weakest = cset[i];				// make it the new weakest
		cset.Destroy(weakest);
	}

	// mate the fastest 2 and fill the set with their children
	while(cset.GetItemsInContainer() < csetsize)
		cset.Add(new Classifier(&e,cset[0],cset[1]));

	passtimer.stop();
	scr << "----------------------------------------------------------" << endl;
	scr << "Whole set: " << passtimer << endl;
	scr << "----------------------------------------------------------" << endl;

	// if sampling interval elapsed, or first pass, write time for this pass
	// interval=1 min. will write max. 600 items / 10 hours,
	// writes every slow pass, but only samples faster passes.
	if(!passcount || (runtimer.split() >= 60))
	{
		timerfile << passtimer.trialtime() << endl;
		runtimer.reset();
	}
}	 										// end while(1), each pass ends here
}               	// end evolveclassifiers
//--------------------------------------------------------------------------
// for each Classifier in finalset.dat, run it through 30 trials, and record
// the times to disk.  One trial consists of 4 problems, same as when evolving.
void timetrials()
{
int trialcount = 30;  				// might allow user to change it

scr << "This will, for each Classifier in finalset.dat," << endl;
scr << "run it through 30 trials, and record the times to finalset.tim" << endl;
scr << "for export and analysis in Excel." << endl;
presskey(scr);

Classifier::beepon = Classifier::statusreport = FALSE;		// no beep, no displays
Classifier::displaymode = Classifier::text;
ifstream infile("finalset.dat");
ofstream outfile("finalset.tim");
Environment e;
Classifier c(&e);
								// all Classifiers get the same problem set, requiring
e.setproblem(trialcount * 2);	// 60 problems for 30 passes. (60 actually makes 61)
int ccount = -1;				// counts classifiers
while(infile >> c)				// for each Classifier in the file,
{
	ccount++;
	scr << "Classifier #" << ccount << " constants = " << c << endl;
	outfile << "c=" << c << endl;
	for(int i = 0 ; i < trialcount ; i++)	// 30 passes (problem sets)
	{
		c.rs.newruleset();				// empty trained rule set from previous pass
		c.sw.reset();					// we want 1 time record for each pass
		for(int j = 0 ; j < 4 ; j++)	// number of problems to solve each pass
		{
			switch(j)
			{
				case 0: e.setproblem(i * 2); break;  	// first problem
				case 1: e.setproblem(i * 2 + 1); break;	// second problem
				case 2: e.setproblem(i * 2); break;		// first again
				case 3: e.setproblem(i * 2); break;		// and again
			}
			scr << "#" << ccount << " Trial " << i << " Problem " << j << " ";
			int ch = c.run(300L);	// 5 min limit
			switch(ch)
			{
				case 27:            // user aborted, entire run is finished
					return;
				case 'q':     		// user aborted 1 Classifier, no solution
					scr << "No solution." << endl;
					break;
			}
		} 									// end 4 problems in each pass
		outfile << c.sw.totaltime << endl;	// write time for this pass to disk
		scr << "         Total time = " << c.sw.totaltime << " seconds." << endl;
	}  										// end 30 passes
	scr << "----------------------------------------------------------" << endl;
	outfile << "----------------------------------------------------------" << endl;
	infile >> ws;							// force fail if end of file
}                              				// end while(infile >> c)
}               	// end timetrials
//--------------------------------------------------------------------------
// main
int main()
{
randomize();
string::set_case_sensitive(0);
string::set_paranoid_check(1);
string::initial_capacity(8);
TTime::PrintDate(FALSE);

scr.window(1,1,80,25);     			// set up text mode screen parameters
scr.clrscr();
scr << setclr(LIGHTGRAY);

initgraf();										// initialize graphics system, always VGAHI
restorecrtmode();								// but start out in text mode
Classifier::displaymode = Classifier::text;     // set variable to match
Classifier::beepon = TRUE;						// whether to use sound
Classifier::solutionpauselength = 1;			// tone duration when solution found
Classifier::solutiontone = Classifier::LOWTONE;	// frequency for PC speaker when solution found
Classifier::statusreport = TRUE;				// whether to show variable values each pass

scr << endl << "Remember to disable Schedule+ before long unattended runs." << endl;

while(1)
{
	restorecrtmode();
	int choice;
	do
	{
		scr << endl;
		scr << "0  Exit" << endl;
		scr << "1  Watch standard classifiers (all default values) learn" << endl;
		scr << "2  Watch random classifiers (random values) learn" << endl;
		scr << "3  Evolve classifier set" << endl;
		scr << "4  Time trials, 30 runs each, elapsed times auto-saved to disk" << endl;
		scr << endl << "Enter choice: ";
		cin >> choice;
		scr << endl;
	}
	while((choice < 0) || (choice > 4));
	switch(choice)
	{
		case 0:
			closegraph();
			return(0);
		case 1:
			watchsingle(FALSE);
			break;
		case 2:
			watchsingle(TRUE);
			break;
		case 3:
			evolveclassifiers();
			break;
		case 4:
			timetrials();
			break;
	}
}					// end while(1)
}						// end main()

library.cpp

/*	library.cpp		3/13/03
	This file is part of the CLASSIFY project.
	Copyright (C)1995-99 Steven Whitney.
	Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
	Initially published by http://25yearsofprogramming.com.

Library source for classify.cpp. It pulls in routines needed by the main program.

*/
#include <alloc.h>
#include <math.h>
#include <graphics.h>
#include <classlib\arrays.h>

#pragma hdrstop

#include "c:\bcs\my.h"
#include "c:\bcs\mylib.cpp"
#include "c:\bcs\library\stopwatc.cpp"

classify.h

/*	classify.h			1-24-99
	This file is part of the CLASSIFY project.
	Copyright (C)1995-99 Steven Whitney.
	Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
	Initially published by http://25yearsofprogramming.com.

class Classifier.

*/
#ifndef __CLASSIFY_H
#define __CLASSIFY_H

#include <math.h>
#include <graphics.h>
#include <classlib\arrays.h>
#pragma hdrstop

#include "c:\bcs\my.h"
#include "problem.h"
#include "environ.h"
#include "bboard.h"
#include "rule.h"
#include "ruleset.h"
#include "c:\bcs\library\stopwatc.h"

////////////////////////////////////////////////////////////////////////////
// a Classifier encapsulates an entire classifier system.
// It can receive input from an external environment, generate and emit
// a response, and receive feedback from the environment.
class Classifier
{
public:
	// e=0 allows this as default constructor, but for real use it must have an environment.
	Classifier(Environment* e = 0, BOOL initrandom = FALSE);
	Classifier(Environment*,Classifier*,Classifier*);	// mating constructor
	Classifier(const Classifier&);						// copy constructor
	~Classifier();                                   	// destructor

	BOOL operator == (const Classifier& other) const;
	BOOL operator < (const Classifier& other) const;  	// compares performance
	BOOL operator > (const Classifier& other) const;    // compares performance

	friend ostream& operator << (ostream& os, const Classifier&);	// write constants
	friend istream& operator >> (istream& is, Classifier&);			// reads constants

	static string helptext();           // text for user help screen
	int run(long timelimit = MAXLONG);	// start classifier working on a problem

	RuleSet rs;
	Stopwatch sw;						// for tracking this Classifier's performance

	// these settings remain in effect for each new classifier
	static enum dispmodes { text, graphics } displaymode;	// report as text or graphics
	static BOOL statusreport;			// whether to show variable values each pass
	static BOOL beepon;					// whether to use sound
	static int solutionpauselength;		// tone duration when solution found
	static enum tones { LOWTONE = 500, HIGHTONE = 12000 } solutiontone;	// tone frequency

protected:
	Environment* env; 		// environment it interacts with
	long iterations;    	// passes in run(), is a member for status reports
							// capitalized = inheritable members:
	int INITIALBID;			// bidding "currency" a rule starts out with
	int SUCCESSREWARD;		// payoff when solution is achieved.
	int PASSBACK;			// fraction paid to source rule
	int REPRODUCERATE;   	// 1 offspring produced every 1 in this many iterations
	int MUTATIONRATE;		// a random bit-flip every 1 in this many offspring
	int RULELIMIT;			// maximum allowed number of rules
	int RULECOUNT;			// starting number of rules in the system
	int MAXMSG;				// maximum # messages on bulletin board at one time

	// the bulletinboard could be a separate object, including post()
	TArrayAsVector<Bulletinboardentry> bb;	// the bulletin board

	void initialize();				// set constants, in one place for any constructor
	void mutate(BOOL changeall = FALSE);				// mutate constants
	void sense();					// get problem from environment
	void post(const string& message, Rule* sourcerule);	// post bb message

	string statustext();			// show status in text mode
	void statusgraphics();          // show status in graphics mode

};
//----------------------------------------------------------------------------
// 					end class Classifier
//////////////////////////////////////////////////////////////////////////////

#endif		// CLASSIFY.H

classify.cpp

/*	classify.cpp			1-24-99
	This file is part of the CLASSIFY project.
	Copyright (C)1995-99 Steven Whitney.
	Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
	Initially published by http://25yearsofprogramming.com.

Code for class Classifier.

*/
#include "classify.h"

////////////////////////////////////////////////////////////////////////////
// global data
////////////////////////////////////////////////////////////////////////////

extern constream scr;      			// console screen

////////////////////////////////////////////////////////////////////////////
// Classifier
//----------------------------------------------------------------------------
// these static members must be defined here.  and give them values so we know
// they all are at least initialized to something valid.

BOOL Classifier::statusreport = TRUE;
enum Classifier::dispmodes Classifier::displaymode = Classifier::text;

BOOL Classifier::beepon = TRUE;
int Classifier::solutionpauselength = 1;
enum Classifier::tones Classifier::solutiontone = Classifier::LOWTONE;

////////////////////////////////////////////////////////////////////////////
//--------------------------------------------------------------------------
// constructor.  For the constants, it can either use the
// default values from initialize() or generate random values.
// initrandom = whether to assign random values to all constants.
Classifier::Classifier(Environment* e, BOOL initrandom) : bb(10,0,10)
{
// if e == 0 (default value), it's useless, but not an error. (default ctor for array use)
env = e;						// find the environment object
initialize();                   // initialize all constants, and build a ruleset

if(initrandom)					// now override most constants,
	mutate(TRUE);               // and also generate a new ruleset
}
//--------------------------------------------------------------------------
// copy constructor
Classifier::Classifier(const Classifier& other) : bb(other.MAXMSG,0,other.MAXMSG)
{
env = other.env;
initialize();           	        // initialize all variables
									// now override most of them
INITIALBID 		= other.INITIALBID;
SUCCESSREWARD 	= other.SUCCESSREWARD;
PASSBACK 		= other.PASSBACK;
REPRODUCERATE 	= other.REPRODUCERATE;
MUTATIONRATE 	= other.MUTATIONRATE;
RULELIMIT		= other.RULELIMIT;
RULECOUNT		= other.RULECOUNT;
MAXMSG			= other.MAXMSG;

// for now, the other's rules are NOT copied.  maybe they should be.
rs.newruleset(RULECOUNT,RULELIMIT,INITIALBID,MUTATIONRATE);	// fill with random starting rules

// this will copy the other's rules
// int count = other.rs.GetItemsInContainer();	// how many rules to copy
// for(int i = 0 ; i < count ; i++)				// copy all the other's rules
// 	rs.Add(*(other.rs[i]));

}                 	// copy constructor
//--------------------------------------------------------------------------
// mating constructor.  each constant is inherited from one or the other parent
// could make environment inherited, especially if Classifiers can ever
// operate in more than one.  But it turns "both parents null" into an
// unrecoverable error.
Classifier::Classifier(Environment* e, Classifier* first, Classifier* second) : bb(10,0,10)
{
if(!e)
	graborts("No environment.");
env = e;				// find the environment object
initialize();			// initialize all variables

if(!first || !second)           // one or both pointers is null
{
	if(!first && !second)       // both null.  standard values at least
	{							// guarantee a valid Classifier
		// standard values already assigned in initialize()
	}
	else	// only one is null.  use the valid parent as both parents
	{
		if(!first)
			first = second;
		else
			second = first;
	}
}
								// override variables with inherited data
INITIALBID 		= (random(2) ? first->INITIALBID 	: second->INITIALBID);
SUCCESSREWARD 	= (random(2) ? first->SUCCESSREWARD : second->SUCCESSREWARD);
PASSBACK 		= (random(2) ? first->PASSBACK 		: second->PASSBACK);
REPRODUCERATE 	= (random(2) ? first->REPRODUCERATE : second->REPRODUCERATE);
MUTATIONRATE 	= (random(2) ? first->MUTATIONRATE 	: second->MUTATIONRATE);
RULELIMIT		= (random(2) ? first->RULELIMIT 	: second->RULELIMIT);
RULECOUNT		= (random(2) ? min(first->RULECOUNT,RULELIMIT)
							 : min(second->RULECOUNT,RULELIMIT));
MAXMSG			= (random(2) ? first->MAXMSG 		: second->MAXMSG);

if(!random(2))	// high rate because testing them is so slow
	mutate();

}						// mating constructor
//--------------------------------------------------------------------------
// destructor
Classifier::~Classifier()
{
bb.Flush();                         // necessary?
}
//--------------------------------------------------------------------------
// a Classifier is equal only to itself
BOOL Classifier::operator == (const Classifier& other) const
{
return(this == &other);
}
//--------------------------------------------------------------------------
// less than refers to its fitness: a weak Classifier is slower
BOOL Classifier::operator < (const Classifier& other) const
{
return(sw.totaltime > other.sw.totaltime);
}
//--------------------------------------------------------------------------
// greater than refers to its fitness: a strong Classifier is faster
BOOL Classifier::operator > (const Classifier& other) const
{
return(sw.totaltime < other.sw.totaltime);
}
//--------------------------------------------------------------------------
// write constants to a stream
ostream& operator << (ostream& os, const Classifier& c)
{
os << c.INITIALBID << " " << c.SUCCESSREWARD << " " << c.PASSBACK << " ";
os << c.REPRODUCERATE << " " << c.MUTATIONRATE << " " << c.RULECOUNT << " ";
os << c.RULELIMIT << " " << c.MAXMSG;
return(os);
}
//--------------------------------------------------------------------------
// get constants from a stream
istream& operator >> (istream& is, Classifier& c)
{
is >> c.INITIALBID >> c.SUCCESSREWARD >> c.PASSBACK >> c.REPRODUCERATE;
is >> c.MUTATIONRATE >> c.RULECOUNT >> c.RULELIMIT >> c.MAXMSG;

c.RULECOUNT = min(c.RULECOUNT,c.RULELIMIT);		// prevent error

// c already has a rule set, based on old values of the variables just read in,
// so delete & rebuild the rule set for the new values.

c.bb.Flush();		// just in case, but it should be empty
c.rs.newruleset(c.RULECOUNT,c.RULELIMIT,c.INITIALBID,c.MUTATIONRATE);

c.sw.reset();   	// any time in the timer is meaningless for the new one

// other members reset, just because in principle >> should set everything
// you must not change!: env
c.iterations = 0;  	// although it's reset each run() pass anyway

return(is);
}             				  	//  >>
//--------------------------------------------------------------------------
// help options, get text for display
string Classifier::helptext()
{
ostrstream os;
os << "While running:" << endl;
os << endl;
os << "If sound is enabled, a tone is produced when a solution is found." << endl;
os << endl;
os << "<ESC>    Quit" << endl;
os << "<space>  Pause" << endl;
os << "0,1,...9 Set tone duration (seconds) when solution found" << endl;
os << "B        Toggle use of sound on/off" << endl;
os << "D        Toggle Display of status report each iteration" << endl;
os << "G T      GRAPHICS or TEXT mode for status report" << endl;
os << "H        Show this Help screen" << endl;
os << "P        1 Peek at status screen" << endl;
os << endl;
os << ends;
string s(os.str());
delete[] os.str();
return(s);
}								// helptext
//--------------------------------------------------------------------------
// mutate one or all of the constants by giving them random values.
// This will cover a large range of values quickly, and is simple to achieve.
// Creeping variables by +/- 1 point retraces same values repeatedly,
// (a Brownian random walk), and very inefficient.
void Classifier::mutate(BOOL changeall)
{
int inheritables = 8;		// # of inheritable members, for easy changing
for(int i = 0 ; i < inheritables ; i++)
{
	// if setting all, do them in order.  otherwise choose one at random.
	int choice = (changeall ? i : random(inheritables));
	switch(choice)
	{
		case 0:
			INITIALBID = random(98) + 2;  	// must be < 100
			break;
		case 1:
			SUCCESSREWARD = random(5000) + 3;
			break;
		case 2:
			PASSBACK = random(3000) + 1;
			break;
		case 3:
			REPRODUCERATE = random(10) + 1;
			break;
		case 4:
			MUTATIONRATE = random(3) + 1;
			break;
		case 5:
			// when limit was based on count, it allowed upward ratcheting
			// because limit couldn't reduce until count did first.
			// limit should be more significant to performance, anyway.
			RULELIMIT = random(300) + 2;
			RULECOUNT = min(RULECOUNT,RULELIMIT); 	// count == limit is ok
			break;
		case 6:
			RULECOUNT = random(RULELIMIT) + 1;    	// 1 to RULELIMIT
			break;
		case 7:
			MAXMSG = random(20) + 3;
			break;
	}
	if(!changeall)		// if only changing one, quit after first pass
		break;
}

// the Classifier's copies of these constants exist so they can be experimented with.
// transfer the changed info TO the actual rule set that will use them.
// and create a new rule set using them.

rs.newruleset(RULECOUNT,RULELIMIT,INITIALBID,MUTATIONRATE);

}								// mutate
//--------------------------------------------------------------------------
// initialize in one place variables that are the same for any constructor.
void Classifier::initialize()
{
// bidding "currency" a rule starts out with
// absolute min=1: if no bid, it can't enter lottery!
// integer math: 1 - 1/2 = 1 - 0 = 1 allows preserving minimum
// a new rule only gets this many passes to get chosen in lottery
INITIALBID 		= 76;

// payoff when solution is achieved.
SUCCESSREWARD 	= 34;

// fraction paid to source rule = (1 / PASSBACK) times its bid.
PASSBACK 		= 941;

// 1 Rule child produced every 1 in this many iterations.
// if values go to 1, change the meaning to X offspring each iteration.
// then range = 1 to 10, random value = random(10) + 1
REPRODUCERATE 	= 1;

// a random mutation every 1 in this many Rule offspring
MUTATIONRATE 	= 1;

// maximum number of rules allowed
RULELIMIT		= 28;

// starting number of rules, <= limit
RULECOUNT		= 20;

// maximum # messages on bulletin board at one time
MAXMSG			= 7;

// the Classifier's copies of these constants exist so they can be experimented with.
// transfer the changed info TO the actual rule set that will use them.
// and create a new rule set using them.

rs.newruleset(RULECOUNT,RULELIMIT,INITIALBID,MUTATIONRATE);

}							// initialize
//--------------------------------------------------------------------------
// display the rules, bulletin board, and problem/solution strings.
// Don't incorporate graphics() display here: later, might want text
// status to go to H89, with graphics simultaneously to Gateway display.
string Classifier::statustext()
{
if(!env)
	graborts("No environment.");

int bbitems = bb.GetItemsInContainer();
int bbcounter = 0;
long bidtotal = 0L;

ostrstream os;
os << "#     Stimulus Response   Bid        MsgBoard  Problem  Solution\n";
os << "----- ------------------- ---------- --------- -------- --------\n";

// there should always be more rules than board messages. use "rules" for the loop
// only show (and sum bidtotal for) first 20.  Finding the strongest 20 is more
// complicated and time-consuming than it's worth.  If you want to see more,
// you must write them to disk and then go look.
int rsitems = min(rs.GetItemsInContainer(),20u);
for(int i = 0 ; i < rsitems ; i++ , bbcounter++)
{
	// winner? else bidder? else neither
	char ch = rs[i]->wasawinner ? 'W' : rs[i]->isabidder ? '-' : ' ';

	os << setw(4) << i << ": " << rs[i]->stimulus << " " << rs[i]->response;
	os << " " << ch << " " << setw(10) << rs[i]->bid;
	bidtotal += (long)(rs[i]->bid);

	if(bbcounter < bbitems)			// sensor-posted messages are prefixed with 's'
		os << " " << ((bb[bbcounter].responsible)?' ':'s') << bb[bbcounter].msg;
	else
		os << "          ";
	os << " " << env->getproblem();   			// show problem
	os << " " << env->getsolution() << endl;  	// show target solution
}
os << "Child: " << setw(10) << rs.childcount << ". Total: " << setw(10) << bidtotal;
os << ".  Iterations: " << iterations;
os << ".  Rules: " << rs.GetItemsInContainer() << endl;
os << "------------------------------------------------------------------------\n";

os << ends;
string s(os.str());
delete[] os.str();
return(s);
}                    	// statustext
//--------------------------------------------------------------------------
// graphically display the relative strengths of rules
// note: the oldest (longest surviving) rules are on the left;
// rules are born on the right.
void Classifier::statusgraphics()
{
clearviewport();

int x, y;
int ymax = getmaxy();				// bottom of screen
int rscount = rs.GetItemsInContainer();
for(int i = 0 ; i < rscount ; i++)
{
	x = i;							// can use this to adjust spacing
	if(x >= 640)					// no point in graphing beyond right edge
		break;
	y = ymax - (rs[i]->bid / 70);	// 0-32767 fits on screen
// 	y = ymax - (rs[i]->bid / 128);	// only bottom half screen, but might be fast:
									// needs only a right-shift.
	setcolor(rs[i]->wasawinner ? WHITE : rs[i]->isabidder ? RED : LIGHTBLUE);
	line(x,ymax,x,y);
}
}						// statusgraphics
//--------------------------------------------------------------------------
// post a message on the bulletin board.
// rule-posted messages scroll off the board.  sensor-posted messages do not.
// Leaving them alone ensures that sensors are always present to be responded to.
// This is to prevent rules just cycling off each other, forgetting the question
// and to allow sensor responders to develop
void Classifier::post(const string& message, Rule* sourcerule)
{
// There is no scrolling anymore:
// if(bb.GetItemsInContainer() >= MAXMSG)	// need to scroll?
// {
// 	int count = bb.GetItemsInContainer();
// 	for(int i = 0 ; i < count ; i++)
// 		if(bb[i].responsible)       // (if no responsible, it was sensor-posted)
// 		{
// 			bb.Destroy(i);			// destroy first (oldest) rule-posting
// 			break;                  // only 1 scrolls off
// 		}
// }

bb.Add(Bulletinboardentry(message,sourcerule)); 	// Add the new element
// if(sourcerule)									// might be sensor posted
// 	sourcerule->locked = TRUE;						// prevent deletion while its posted

// lock all rules with bb postings
// int count = bb.GetItemsInContainer();
// for(int i = 0 ; i < count ; i++)
// 	if(bb[i].responsible)       			// (if no responsible, it was sensor-posted)
// 		bb[i].responsible->locked = TRUE;

}							// post
//--------------------------------------------------------------------------
// post bulletin board messages that reflect the current outside world state.
// bb no longer flushed first.  you can always add sensor postings.
// This version just posts the "question".
void Classifier::sense()
{
post(env->getproblem(),0);	// the original question isn't attributed to a rule
}							// sense
//--------------------------------------------------------------------------
// start the classifier working on a problem.  Continues until it succeeds or
// user stops it, or time limit is exceeded.
// Keep a watch on when rules are locked/unlocked.  Locking is supposed to protect certain
// rules from deletion when a deletion is otherwise ok, AND protect the array from
// my own mistakes while rearranging order of execution!  So a rule should always be
// protected from deletion while its data is required, and unlocked as soon as it's not.
// returns:
// 1 = problem solved, (not now used)
// 		currently, only the following are ever tested for upon return:
// Q = User quit this Classifier's run only, (continue with others)
// 27 = User Quit pgm.
int Classifier::run(long timelimit)	// maximum number of seconds
{
if(!env)
	graborts("No environment.");

int i;
int ch = 0;		// set to 'p' to cause one initial peek regardless of mode
sw.start();		// start timer running before refilling rs

// refill to starting capacity with random rules (guesses), which may be more
// likely to find solution to a new problem than offspring of existing
// rules specialized for previous problem.
// still, the justification for this is somewhat questionable, though it is
// necessary because Sweep (upon prior exit) decimates rs.
rs.TopUp(FALSE,FALSE);

iterations = 0L;
bb.Flush();			// unnecessary, reminder that sense() doesn't do it
sense();            // start out by posting sensory state to bb
while(1)
{
	//--------------------------------------------------------------------------
	// user command section, unrelated to classifier operation
	// at this point, ch may have a value that was set anywhere later in the loop

	if(ch != 1)							// solution found is highest priority
	{
		if(kbhit())            			// last processed overrides
			ch = tolower(getch());
		if(sw.split() > timelimit)		// auto-abort: took too long
			ch = 'q';
	}

	// other options here could allow user entry of sensor-posted messages at any
	// time.  either pre-coded (e.g. 'h' means post a specific msg), or pause to prompt
	// user for msg to post.  example use = gas pipeline problem, requiring real time input
	// of changing simulated conditions.
	switch(ch)
	{
		case 0:						// most passes
			break;

		case 27:                  	// user exit or
		case 'q':           		// Quit this Classifier only
			sw.stop();              // stop timer so compare value is valid, fall through,
		case 1:						// solution was found, timer is already stopped earlier
			bb.Flush();
			rs.ZeroAllFlags();		// prevent leftover data causing problems next entry
			rs.UnlockAll();
			rs.Sweep(100);			// delete the partial solutions rules (bid < 100),
									// to free memory so other classifiers can build up
									// & use their rule sets w/o running out of memory.
			return(ch);            	// return ch so caller knows what to do

		case '0':					// set solutionfound pause duration
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			solutionpauselength = (int)ch - 0x30;
			// hopefully, more tonelike & less of a click when duration is short.
			solutiontone = solutionpauselength ? LOWTONE : HIGHTONE;
			break;
		case ' ':       				// user pause
			if(displaymode == text)
				scr << "Paused.  Press any key...";
			getch();
			if(displaymode == text)
				scr << endl;
			break;
		case 'b':						// toggle sound
			beepon = !beepon;
			break;
		case 'd':						// auto-display status
			statusreport = !statusreport;
			break;
		case 'g': 						// set status report to graphics format
			statusreport = TRUE;		// assume we want continuing display
			if(displaymode == text)
				setgraphmode(VGAHI);
			displaymode = graphics;
			break;
		case 'h':						// help
			if(displaymode == graphics)
				restorecrtmode(); 		// actual mode temporarily out of synch with displaymode
			scr << helptext();
			presskey(scr);
			if(displaymode == graphics)
				setgraphmode(VGAHI); 	// restore actual mode
			break;
		case 'p':						// peek at status
			// the peek is done later - where the bulletin board is valid.
			break;
		case 't':						// set status report to text format
			statusreport = TRUE;		// assume we want continuing display
			if(displaymode == graphics)
				restorecrtmode();
			displaymode = text;
			break;
	}
	// DON'T reset ch here (see below)
	//--------------------------------------------------------------------------
	// subtracting ante used to be here.
	//--------------------------------------------------------------------------
	// generate new rule children for the next try at the problem.
	// preferentially selects bidders as parents.  But all we know
	// is that they were bidders the last pass (maybe won't be this one).
	// Adding the new rule may cause deletion of the weakest unlocked rule that
	// isn't a source for another rule.

	if(!random(REPRODUCERATE))
		rs.Addchild(TRUE);

	// reset wasawinner, isabidder, and source for all rules
	rs.ZeroAllFlags();

	//--------------------------------------------------------------------------
	// identify which rules will be bidders in the upcoming lotteries.
	// their source is recorded, and isabidder set TRUE.
	// #error why not also lock sources here?

	while(!rs.MarkBidders(bb) && bb.GetItemsInContainer())
	{
		// if there were no bidders, RuleSet adds some new Rules,
		// one tailored to respond to each of the current bb posting(s?).
		int N = bb.GetItemsInContainer();
		for(int i = 0 ; i < N ; i++)
		{
			rs.AddCustom(bb[i].msg);
		}
	}

	// we know who wants to post.  this bb no longer needed.
	bb.Flush();

	//--------------------------------------------------------------------------
	// auction off all the bulletin board slots allowed for rule postings.
	for(i = 0 ; i < MAXMSG ; i++)
	{
		Rule* winner = rs.lottery(TRUE);	// returns zero if no winner, no bids
		if(winner)
		{
			// for debugging, a serious error if it occurs
			if(!winner->source)
				graborts("Error: winner->source is null.");

			// pay source when it wins the right to post (now)
			winner->paysource(PASSBACK);

			// lottery() should not set this. lottery is also used for other purposes.
			winner->wasawinner = TRUE;		// remember we won
			winner->isabidder = FALSE;		// can't win again
		}
		else								// no (or no more) winners
			break;
	}

	//
	rs.UnlockAll();

	// all winners post their msgs, and get locked
	rs.PostNewMessages(bb);

	//----------------------------------------------------------------------
	// effector actions probably would go here because they carry out the msgs just posted,
	// and the result (the feedback from those actions) determines the grade.
	//----------------------------------------------------------------------
	// Feedback from environment.  response is judged for correctness.

	// version 1, ALL active rules get credit for the best grade that any one achieves.
	// seems needlessly inefficient, since we DO know which rule was responsible.

// 	int grade = 0;
// 	int N = bb.GetItemsInContainer();
// 	for(int i = 0 ; i < N ; i++)
// 	{
// 		if(bb[i].responsible)							// only rule-posted msgs eligible!
// 			grade = max(grade,env->score(bb[i].msg));	// (int)0-100 percent
// 	}
// 	if(grade == 100)								// found the exact solution (100%)
// 	{
// 		sw.stop();							// stop timer
// 		rs.PayAllWinners(SUCCESSREWARD);	// pay winner
// 	}
// 	else		// reward for partial correctness is an absolute number,
// 	{			// not a percentage of successreward.
// 		// "price support": a rule's bid isn't allowed to fall below a minimum.
// 		// The minimum depends on how "right" the rule's response is.
// 		// minimum = 0-100, set bid to match it.  no passback here, for now.
// 		rs.SupportAllWinners(grade);
// 	}

	// version 2: reward individual rules by how well they did

	// we still need to know the best grade that any rule gets, for later.
	// i.e. did any rule post the exact solution?  only IT gets rewarded, but
	// other actions later depend on solution having been found.
	int grade = 0;
	int N = bb.GetItemsInContainer();
	for(int i = 0 ; i < N ; i++)
	{
		if(!bb[i].responsible)      			// ignore sensor postings (won't be any)
			continue;

		int thisgrade = env->score(bb[i].msg);	// grade for this posting only, 0-100 percent
		grade = max(grade,thisgrade);           // highest so far?
		if(thisgrade == 100)					// found the exact solution (100%)
		{
			sw.stop();										// stop timer
			bb[i].responsible->AdjustBid(SUCCESSREWARD);	// pay poster
		}
		else
		{
			// reward for partial correctness is an absolute number,
			// not a percentage of successreward.
			// "price support": a rule's bid isn't allowed to fall below a minimum.
			// The minimum depends on how "right" the rule's response is.
			// minimum = 0-100, set bid to match it.  no passback here, for now.
			bb[i].responsible->SupportBid(thisgrade);
		}
	}

	//----------------------------------------------------------------------
	// to show you can zero out the sources here because we have paid them.
	rs.ZeroSources();

	iterations++;		// iteration is complete here, except statusreport & grading

	// get sensor input for next iteration.  it is good that bb only had rule postings above,
	// for grading, so sensor postings couldn't accidentally post the right answer.
	sense();

	//--------------------------------------------------------------------------
	// status report section, doesn't affect any classifier system variables

	if(statusreport)			// status report EACH iteration
	{
		if(displaymode == text)
		{
// 			scr.clrscr();			// unstable, garbage output when solution found.
// 			scr << setxy(1,1);		// good, but also somewhat unstable.
			scr << statustext();    // display rules, bulletin board, etc.
		}
		else
			statusgraphics();		// graphic display of rule strengths
	}
	// if we're not already showing status, or it's only graphic, allow 1 peek
	if(!statusreport || (displaymode == graphics))
		if(ch == 'p')           // TEXT status report THIS iteration only
		{
			if(displaymode == graphics)
				restorecrtmode();

			scr << statustext();

			if(displaymode == graphics)
			{
				presskey(scr);
				setgraphmode(VGAHI);
			}
		}
	ch = 0;								// ok to reset: we're through with it
	//--------------------------------------------------------------------------
	// actions to take if exact solution found
	// individual rules have already been processed and rewarded.
	// this section is only for general actions.
	// it returns when the solution is found.  to fully train, you need to solve
	// a problem more than once to allow passback to work.  if you want complete
	// training, come back again later.

	if(grade == 100)             	// perfect score = exact solution found
	{
		if(displaymode == text) 	// report appears while beep sounds
		{
			// iterations gives clue how many rules chained to solve problem
			scr << "\a";           										// a click if !beepon
			scr << "Solution found!  " << iterations << " iterations.  ";
			scr << sw.trialtime() << " seconds.\n";
		}
		if(displaymode == graphics)	// (in text mode, it would just restart rapid scrolling)
			statusreport = TRUE;	// resume automatic status display

		if(beepon)              	// report the success
		{
			sound(solutiontone);
			sleep(solutionpauselength);		// pause
			nosound();
		}
		ch = 1;						// force return in switch() at loop start
	}
}						// end while(1)
}								//  run
//--------------------------------------------------------------------------
				// end class Classifier
////////////////////////////////////////////////////////////////////////////

bboard.h

/*	bboard.h			1-12-99
	This file is part of the CLASSIFY project.
	Copyright (C)1995-99 Steven Whitney.
	Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
	Initially published by http://25yearsofprogramming.com.

*/
#ifndef __BBOARD_H
#define __BBOARD_H

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

class Rule;	// forward ref.

////////////////////////////////////////////////////////////////////////////
// a bulletinboardentry is an entry on the bulletin board
// for now, the entry is an 8-letter string of chars for ease of visual review
class Bulletinboardentry
{
public:
	Bulletinboardentry();
	Bulletinboardentry(const string& m, Rule* r);

	BOOL operator == (const Bulletinboardentry& other) const;

	string msg;
	Rule *responsible;	// we must know which rule posted the msg, to reward it.
						// OR, 0 = a sensor posted msg
};
//----------------------------------------------------------------------------
//					end class Bulletinboardentry
//////////////////////////////////////////////////////////////////////////////

#endif		// BBOARD.H

bboard.cpp

/*	bboard.cpp			1-6-99
	This file is part of the CLASSIFY project.
	Copyright (C)1995-99 Steven Whitney.
	Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
	Initially published by http://25yearsofprogramming.com.

*/
#include "bboard.h"

//////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------
Bulletinboardentry::Bulletinboardentry()
{
	responsible = 0;
}
//----------------------------------------------------------------------------
Bulletinboardentry::Bulletinboardentry(const string& m, Rule* r)
{
	msg = m;
	responsible = r;
}
//----------------------------------------------------------------------------
// &other == this would be faster, but believe choice of which to use here
// isn't particularly important.  Doesn't affect scrolling.
BOOL Bulletinboardentry::operator == (const Bulletinboardentry& other) const
{
	return((msg == other.msg) && (responsible == other.responsible));
}
//////////////////////////////////////////////////////////////////////////////

environ.h

/*	environ.h			1-5-99
	This file is part of the CLASSIFY project.
	Copyright (C)1995-99 Steven Whitney.
	Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
	Initially published by http://25yearsofprogramming.com.

*/
#ifndef __ENVIRON_H
#define __ENVIRON_H

#include <classlib\arrays.h>
#pragma hdrstop

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

////////////////////////////////////////////////////////////////////////////
// the Environment is the entity the classifier interacts with.
// It poses the problem (creates the problem situation), can transmit the
// state of the environment to the classifier, receive the classifier's
// responses, and give the classifier's responses graded scores as to
// whether, or how closely, it solved the problem.
// This class should be envisioned as a template, possibly later to be
// used as a base class.  That is, there are certain functions that any
// environment will have to have or be able to do.
class Environment
{
public:
	Environment();
	~Environment();

	void flushproblems();				// empty problems array
	void addproblem();               	// generate a new problem & add to list
	void setproblem(int index);			// set current to a specific problem
	int problemstodisk();
	int problemsfromdisk();
	const string& getproblem();			// allows viewing, prevents changes
	const string& getsolution();		// allows viewing, prevents changes
	int score(const string& proposed); 	// how nearly correct proposed is

protected:
	TIArrayAsVector<Problem> problems;	// problems for classifier to solve
	Problem* current;					// the problem currently presented
	BOOL fromdisk;			// whether to load problems and solutions from disk

};

#endif		// ENVIRON.H

environ.cpp

/*	environ.cpp			1-5-99
	This file is part of the CLASSIFY project.
	Copyright (C)1995-99 Steven Whitney.
	Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
	Initially published by http://25yearsofprogramming.com.

*/
#include "environ.h"

//////////////////////////////////////////////////////////////////////////////
//--------------------------------------------------------------------------
// constructor
Environment::Environment() : problems(20,0,1)
{
current = 0;
fromdisk = FALSE;

addproblem();		// get starting problem and its solution
}             		// Environment constructor
//--------------------------------------------------------------------------
// destructor
Environment::~Environment()
{
// problemstodisk();						// write to disk before destroying
problems.Flush(TShouldDelete::Delete);
}             		// Environment destructor
//--------------------------------------------------------------------------
// empty problems array and restart with 1 problem
void Environment::flushproblems()
{
// problemstodisk();						// write to disk first
problems.Flush(TShouldDelete::Delete);		// empty the array
addproblem();								// start with 1 problem
}							// flushproblems
//--------------------------------------------------------------------------
// save entire problem set to disk
int Environment::problemstodisk()
{
ofstream outfile("problems.dat");
int count = problems.GetItemsInContainer();
for(int i = 0 ; i < count ; i++)
	outfile << *(problems[i]) << endl;
return(count);
}            		     	// rulestodisk
//--------------------------------------------------------------------------
// load rule set from disk
int Environment::problemsfromdisk()
{
problems.Flush(TShouldDelete::Delete);	// empty it before filling

Problem p;
ifstream infile("problems.dat");
while(infile >> p)
	problems.Add(new Problem(p));

int count = problems.GetItemsInContainer();
if(!count)
	graborts("File PROBLEMS.DAT corrupt or not found.  Repair and rerun.");
return(count);
}    		        		//  rulesfromdisk
//--------------------------------------------------------------------------
// allow access to the problem string, but prevent changes to it
const string& Environment::getproblem()
{
return(current->problem);
}							//  getproblem
//--------------------------------------------------------------------------
// allow access to the solution string, but prevent changes to it
const string& Environment::getsolution()
{
return(current->solution);
}							//  getsolution
//--------------------------------------------------------------------------
// set current to a specific index.  If the index isn't valid, it adds
// problems until it is.  This allows a Classifier to request re-presentation
// of a previous problem.
void Environment::setproblem(int index)
{
while(problems.GetItemsInContainer() <= index)		// index won't be valid
	problems.Add(new Problem);						// add elements until it is
current = problems[index];							// set current problem
}  		                  	//  setproblem
//--------------------------------------------------------------------------
// generate a new problem, add it to the list, and make it the current problem
void Environment::addproblem()
{
current = new Problem;
problems.Add(current);
}							//  addproblem
//--------------------------------------------------------------------------
// determine how nearly correct a proposed solution is.  It implements
// "operant conditioning": system is rewarded for steps in the right direction
// toward solution.
// This may be a lot more difficult for more complicated tasks.
// The environment would really only be able to return its current state,
// and the Classifier would have to determine if that's what it wanted.
// returns: 0-100 representing the percent that the proposed solution is correct.
// This version compares a response string with the solution[] string.
int Environment::score(const string& proposed)
{
int correct = 0;
int maxlen = min(proposed.length(),current->solution.length());	// # of chars in shortest
for(int i = 0 ; i < maxlen ; i++)
	if(proposed[i] == current->solution[i])
		correct++;

if(maxlen == 0)		// either proposed or solution was empty
	return(0);		// prevent divide by zero

int score = 100 * correct / current->solution.length();	// determine the score

if(score == 100)			// if solution was found,
	current->solved = TRUE;

return(score);	// percent correct
}								//  score
//--------------------------------------------------------------------------
// 						end class Environment
//////////////////////////////////////////////////////////////////////////////

problem.h

/*	problem.h			1-6-99
	This file is part of the CLASSIFY project.
	Copyright (C)1995-99 Steven Whitney.
	Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
	Initially published by http://25yearsofprogramming.com.

*/
#ifndef __PROBLEM_H
#define __PROBLEM_H

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

////////////////////////////////////////////////////////////////////////////
// a Problem encapsulates a problem string with its solution string,
// so they can be stored in an array.
// some other functions from Environment might be moved here: score()
class Problem
{
public:
	Problem();
	Problem(const string& p, const string& s);
	Problem(const Problem&);

	BOOL operator == (const Problem& other) const;

	friend ostream& operator << (ostream& os, const Problem&);
	friend istream& operator >> (istream& is, Problem&);

	string problem;		// the problem for the classifier to solve
	string solution;	// the correct answer to problem
	BOOL solved;		// whether this one has ever been solved
						// currently not used
protected:

};

#endif		// problem.h

problem.cpp

/*	problem.cpp			1-4-99
	This file is part of the CLASSIFY project.
	Copyright (C)1995-99 Steven Whitney.
	Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
	Initially published by http://25yearsofprogramming.com.

*/
#include "problem.h"

//--------------------------------------------------------------------------
// default constructor
Problem::Problem()
{
for(int i = 0 ; i < 8 ; i++)
{
	problem += (random(2) ? '0' : '1');
	solution += (random(2) ? '0' : '1');
}
solved = FALSE;
}
//--------------------------------------------------------------------------
// constructor
Problem::Problem(const string& p, const string& s)
{
problem = p;
solution = s;
solved = FALSE;
}
//--------------------------------------------------------------------------
// copy constructor
Problem::Problem(const Problem& other)
{
problem = other.problem;
solution = other.solution;
solved = other.solved;
}
//--------------------------------------------------------------------------
// I've not used &other == this because it might be desirable to judge 2
// DIFFERENT Problems to be equal, such as for preventing duplicates in an array.
BOOL Problem::operator == (const Problem& other) const
{
return( (problem == other.problem) 	 &&
		(solution == other.solution) &&
		(solved == other.solved) );
}
//--------------------------------------------------------------------------
// write a problem to any ostream
ostream& operator << (ostream& os, const Problem& p)
{
os << p.problem << " " << p.solution << " " << (p.solved ? "YES" : "NO");
return(os);
}
//--------------------------------------------------------------------------
// read a problem from an istream
istream& operator >> (istream& is, Problem& p)
{
string solved;								// solved is irrelevant on input
                                            // and is text in the file, anyway
p.solved = FALSE;                           // but ensure it gets set
is >> p.problem >> p.solution >> solved;

return(is);
}
//--------------------------------------------------------------------------
				// end class Problem

The rest of this program's code, for the Rule and RuleSet classes, is on the next page.

 

 

Valid HTML 4.01 Transitional Valid CSS
View content labeling at ICRA.
Copyright ©2008 Steven Whitney. Last modified 10/05/2008.