Sign in to follow this  

Start of a system for describing build/research/customization requirements

This topic is 4479 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello everyone, I just got a tidbit of code working to start solving a problem I've had for a while, and have not seen much discussion here on the forums. Hopefully this will help others, and generate a little discussion for my own enlightenment. My current hobby project is a 4X game [ala Civilization, Master of Orion...], and in that [and other] games, there are series of game situations which have pre-requisite requirements, size limitations, or similar constraints. In my particular project, there are build projects, research projects, customizable races, and possibly slightly similar things like unit bonuses, and spell effects. In tabletop games, such a system is simple. You just say 'this choice is limited by that requirement.' Alas, computers don't understand English that well yet. So the basis of the system is what words does the computer need to know to build pre-requisites and limitations? I assembled this list for the start. Supply - The definition gets stuff. The stuff is cumulative. Cost - The definition loses stuff. The losses are cumulative. Provide - The definition gets stuff. The highest provide takes effect. Degrade - The opposite of Provide. Require - Not having a requirement invalidates the definition. Invalidate - Invalidates the definition, wether other requirements are met or not. Having a Cost, Require, or Invalidate left after totalling up the entries of the definition indicates that the build is not 'done'. The left overs can be passed along to whatever code is meant to handle the post-validated stuff. For example, a build project for a starcraft marine might be something like:
cost 50 minerals
require 60 time
provide 1 marine
After the necissary stuff is provided, 'provide 1 marine' [and perhaps 'supply $extra minerals] would be passed along. Well, that's all well and good, but computers are still bad at English. How to codify that into something the computer can use? What parts will it need? Not sure what it needs, but this is what I gave it, and what I think will do for now. The keywords are enumerated out for now. To keep things open, I just made the 'minerals/time/marine' into a string. The structure also stores a number, and a string. I'm not sure what the string will be used for now, but I'm fairly certain I'll need it... Here's the code for these bits.
enum    pdefops                 {PD_LABEL, PD_SUPPLY, PD_COST, PD_PROVIDE, PD_DEGRADE, PD_REQUIRE, PD_INVALIDATE, PD_END};
const   char     *pdefstrings[]={"label", "supply", "cost", "provide", "degrade", "require", "invalidate"};

pdefops         strtoop(string s){
//
//
//
int     x;

for (x=0;x<PD_END;++x){
        if (s == pdefstrings[x]){
                return((pdefops(x)));
        }
}
return(PD_LABEL);
}


struct          pdeftag{
protected:
        long            nval;
        string          sval;
public:
        string          key;
        void            value(long      *nv){*nv=nval;}
        void            value(string    *sv){*sv=sval;}

        bool    operator==(const pdeftag &rhs) const{
                // TODO: sval?
                return(nval==rhs.nval && sval==rhs.sval && key==rhs.key);
        }
        bool    operator==(pdeftag &rhs){
                // TODO: sval?
                return(nval==rhs.nval && sval==rhs.sval && key==rhs.key);
        }

        pdeftag(string  k, long nv, string sv): key(k), nval(nv), sval(sv){}

        string          gettext(){
                stringstream    ss;

                ss << "pdeftag \"" << key << "\" " << nval << " \"" << sval << "\"";
                return(ss.str());
        }
};

struct          pdefentry{
protected:
        pdefops         op;
        pdeftag         tag;
public:
        pdefops         fetchop(){return(op);}
        pdeftag         fetchtag(){return(tag);}

        bool    operator==(const pdefentry &rhs) const{
                return(op==rhs.op && tag==rhs.tag);
        }
        bool    operator==(pdefentry &rhs){
                return(op==rhs.op && tag==rhs.tag);
        }

        pdefentry(pdefops o, string  k, long nv, string sv): tag(k,nv,sv), op(o){}
        pdefentry(string  o, string  k, long nv, string sv): tag(k,nv,sv), op(strtoop(o)){}
        string          gettext(){
                stringstream    ss;

                ss << "pdefentry: " << pdefstrings[op] << " " << tag.gettext();
                return(ss.str());
        }
};

typedef         multimap<string, pdefentry>             pdefgroup;
typedef         multimap<string, pdefentry>::iterator   pdefiterator;

string          stringify_pdefgroup(pdefgroup pdg){
//
//
//
stringstream    ss;

list<pdefentry> pdelist;

transform(pdg.begin(), pdg.end(), back_inserter(pdelist), rtn2nd());
transform(pdelist.begin(), pdelist.end(), ostream_iterator<string>(ss,"\n"), mem_fun_ref(&pdefentry::gettext));
return(ss.str());
}




Well, that's straightforward enough. A class that ignores the keyword operators [and is thus more easily expandable] and one that combines the tags with them. A few typedefs for maps, since it should be common to group the entries by keyword [find all entries involving minerals]. And the standard stringification functions to allow easy debugging. Even with these defintions, it's not going to be enough. They're dumb. There's no conditionals in simple requirements, and conditionals will be necissary to make something work well in practice. A good example is the standard 'if empire does not have tech, invalidate build option'. Such a thing could perhaps be done with a simple provide/require combo, but how would you provide all of the techs? How would that work with something more complex, like 'if build area is near a shoreline' conditions? These defintion parts need to be provided by something a little smarter. For racial customization these will be 'picks', for build projects, some standard conditions... To me, the best way would perhaps be fleshing together a big powerful scripting language to define it. Alas, that's not something I know how to do. So, I did something a little more attainable. Functors. Allow the programmer to define their own logic into the process. For this test, I'm just going to assume the functor gets to know about other definitions currently available, and is only able to return definitions. Fairly simple. Code for the functor part:
template<typename F>
struct  pickdef:
        public  pickdef_interface{
protected:
        F       f;
public:
        pdefgroup       pdefs(pdefgroup &pd){return(f(pd));}
        pickdef_interface       *clone()const {return(new pickdef<F>(F(f)));}
        pickdef(F inf=F()):f(inf){}
};

template <typename T>
pickdef_interface       *make_pickdef(T t){return(new pickdef<T>(t));}


struct  pickdef_entry{
protected:
        pickdef_interface       *pdi;
public:
        pdefgroup               pdg;
        bool                    changeflag;
        string                  pickname;

        bool                    pdupdate(pdefgroup &pd);


        pickdef_entry(const pickdef_entry &rhs){
                pdi=rhs.pdi->clone();
                changeflag=1;
                pickname=rhs.pickname;
        }
        pickdef_entry(pickdef_entry &rhs){*pdi=*(rhs.pdi); changeflag=1; pickname=rhs.pickname; }
        pickdef_entry(string pn,pickdef_interface *pd):pickname(pn), changeflag(1), pdi(pd){}
        pickdef_entry():pickname(""), changeflag(1), pdi(0){}
        ~pickdef_entry(){delete pdi;}
};

bool    pickdef_entry::pdupdate(pdefgroup &pd){
//
// Update pdg and set the changeflag if necissary.
//
if (!pdi){
        changeflag=0;
        pdg=pdefgroup();
        return(0);
}
pdefgroup       new_pdg = pdi->pdefs(pd);

if (new_pdg==pdg){
        changeflag=0;
        return(0);
}else{
        pdg=new_pdg;
        changeflag=1;
        return(1);
}
}



this was done around racial customization assumption, hence the naming, and the thoughts around it. But what's this 'update' function, and the change flag? Well, what happens when you have two functors:
f1:
provide 2 cows

f2:
provide cows * 4 milk
Should be 2 cows and 8 milk. But what happens if f2 is stored earlier in the list of functors? 2 cows and 0 milk. Oops. So the changeflag, and update process is there to support the process where the functors are called until they 'settle'. It goes like this:
f1:
provide cows *4 milk

f2: 
provide 2 cows

run f1: 0 milk, changed.
run f2: 2 cows, 0 milk, changed.
changes? yes. loop again.
run f1: 8 milk, 2 cows, changed.
run f2: 8 milk, still 2 cows, unchanged.
changes? yes. loop again.
run f1: same, unchanged.
run f2: same, unchanged.
changes? no. Done!
This requires the programmer to avoid circular dependancies, but realistically, that had to happen anyway. So one more class, this being the 'big one' to handle the various 'picks' [functors] and form the definition from them:
pdefgroup       simplify_pdefgroup(pdefgroup);
pdefgroup       simplify_pdefgroup(pdefgroup,string);


struct  pickdef_group{
protected:
        list<pickdef_entry>     picks;
        pdefgroup       form_naive_pdefgroup();
        pdefgroup       form_allbut_pdefgroup(list<pickdef_entry>::iterator);
public:
        void    add(pickdef_entry pde){picks.push_front(pde);}
        void    add(string pn, pickdef_interface *pd){pickdef_entry     pde(pn,pd);  picks.push_front(pde);}
        void    remove(string   s){
                // TODO: do this right.

                list<pickdef_entry>::iterator   it;
                for (it=picks.begin();it!=picks.end() && it->pickname!=s; ++it){}
                if (it!=picks.end()){
                        picks.erase(it);
                }
        }
        pdefgroup       form_pdefgroup();
        pdefgroup       form_simplified_pdefgroup();
};


pdefgroup       pickdef_group::form_naive_pdefgroup(){
//
// simply add cached pick results together.
//
list<pickdef_entry>::iterator   it;
pdefgroup                       rtn;

for(it=picks.begin(); it != picks.end(); ++it){
        copy(it->pdg.begin(), it->pdg.end(), inserter(rtn,rtn.end()));
}
return(rtn);
}


pdefgroup       pickdef_group::form_allbut_pdefgroup(list<pickdef_entry>::iterator      exclusion){
//
//
//
list<pickdef_entry>::iterator   it;
pdefgroup                       rtn;

for(it=picks.begin(); it!=picks.end(); ++it){
        if(it!=exclusion){
                copy(it->pdg.begin(), it->pdg.end(), inserter(rtn,rtn.end()));
        }
}
return(rtn);
}

pdefgroup       pickdef_group::form_pdefgroup(){
//
// Repeatedly call the picks until the defs stabilize.
//
bool                            flag=1;
pdefgroup                       allbutgroup;
list<pickdef_entry>::iterator   it;

while(flag){
        flag=0;
        for(it=picks.begin(); it != picks.end(); ++it){
                allbutgroup=form_allbut_pdefgroup(it);
                if(it->pdupdate(allbutgroup)){
                        flag=1;
                }
        }
}
return(form_naive_pdefgroup());
}


pdefgroup       pickdef_group::form_simplified_pdefgroup(){
//
//
//
return(simplify_pdefgroup(form_pdefgroup()));
}


bool            is_pdefgroup_ready(pdefgroup    pdg){
//
// Returns false if a cost, require, or invalidate haven't been cleared.
//
pdefiterator    it;
pdefops         op;

for(it=pdg.begin();it!=pdg.end();++it){
        op=it->second.fetchop();
        if (op==PD_COST || op==PD_REQUIRE || op==PD_INVALIDATE){
                return(0);
        }
}
return(1);
}

pdefgroup       simplify_pdefgroup(pdefgroup pdg,string k){
//
// Simplify the pdefs matching key k.
//
pdefgroup       workinggroup;
pdefiterator    ita,itb;
pdefgroup       rtn;
bool            flag=1;
long            x;
long            z;
long            y;
string          pst;
string          dst;

// copy out parts that match key k.
copy(pdg.equal_range(k).first, pdg.equal_range(k).second, inserter(workinggroup,workinggroup.end()));


// first, check for invalidations. They will override all.
for (ita=workinggroup.begin(); ita!=workinggroup.end(); ++ita){
        if (ita->second.fetchop()==PD_INVALIDATE){
                copy(ita,ita,inserter(rtn,rtn.end()));
                return(rtn);
        }
}

// Next, hit provide/degrade.
x=0;
z=0;
for(ita=workinggroup.begin(); ita!=workinggroup.end();++ita){
        ita->second.fetchtag().value(&y);
        if (ita->second.fetchop()==PD_PROVIDE){
                if(y>x){x=y;}
                ita->second.fetchtag().value(&pst);
        }else if (ita->second.fetchop()==PD_DEGRADE){
                if(y>z){z=y;}
                ita->second.fetchtag().value(&dst);
        }
}
x=x-z;
if (x>0){
        rtn.insert(make_pair(k,pdefentry(PD_PROVIDE,k,x,pst)));
}else if(x<0){
        rtn.insert(make_pair(k,pdefentry(PD_DEGRADE,k,0-x,dst)));
}

// Next, supply/cost.

x=0;
for(ita=workinggroup.begin(); ita!=workinggroup.end();++ita){
        ita->second.fetchtag().value(&y);
        if (ita->second.fetchop()==PD_SUPPLY){
                x=x+y;
                ita->second.fetchtag().value(&pst);
        }else if (ita->second.fetchop()==PD_COST){
                x=x-y;
                ita->second.fetchtag().value(&dst);

        }
}

if (x>0){
        rtn.insert(make_pair(k,pdefentry(PD_SUPPLY,k,x,pst)));
}else if(x<0){
        rtn.insert(make_pair(k,pdefentry(PD_COST,k,0-x,dst)));
}

// Finally, require.

for(ita=workinggroup.begin(); ita!=workinggroup.end();++ita){
        if(ita->second.fetchop()==PD_REQUIRE){
                flag=0;
                ita->second.fetchtag().value(&x);
                for(itb=rtn.begin();itb!=rtn.end();){
                        if(itb->second.fetchop()==PD_SUPPLY || itb->second.fetchop()==PD_PROVIDE){
                                itb->second.fetchtag().value(&y);
                                if(y>=x){
                                        flag=1;
                                }
                        }
                        if(flag){
                                itb=rtn.end();
                        }else{
                                ++itb;
                        }
                }
                if(!flag){
                        ita->second.fetchtag().value(&pst);
                        rtn.insert(make_pair(k,pdefentry(PD_REQUIRE,k,x,pst)));
                }
        }
}

return(rtn);

}
pdefgroup       simplify_pdefgroup(pdefgroup pdg){
//
//
//
pdefgroup       rtn;
pdefgroup       simp_part;
pdefiterator    it;

for(it=pdg.begin();it!=pdg.end(); it=pdg.upper_bound(it->first)){
        simp_part=simplify_pdefgroup(pdg,it->first);
        copy(simp_part.begin(), simp_part.end(), inserter(rtn,rtn.end()));
}
return(rtn);
}



As well as two global functions to simplify down the different keywords and data into 'current' status. This is important to provide the player with an up to date status of their project [how much work is left to be done, how much racial points they have left to play with.... ]. It's also important in the code to allow the various functors to easily determine status to condition upon. And that's it for now. Enough code to store, simplify, and manipulate the simple, generic 6 keyword definitions. Some working code to build upon with a better interface, something to read the definitions from file, and other neat stuff. Here's the complete code, and some test code if people want to play around with it. rtn2nd.h - required for some of the manipulation code
#ifndef RTN2ND
#define RTN2ND
#include <utility>

using   std::pair;

struct  rtn2nd{
        template<typename A, typename B>
        B       operator()(pair<A,B>    p){
                return(p.second);
        }
};
#endif


pickdef.h
#ifndef PICKDEF
#define PICKDEF

#include <string>
#include <sstream>
#include <list>
#include <map>
#include <iterator>
#include <algorithm>
#include <functional>
#include "rtn2nd.h"

using namespace std;

enum	pdefops			{PD_LABEL, PD_SUPPLY, PD_COST, PD_PROVIDE, PD_DEGRADE, PD_REQUIRE, PD_INVALIDATE, PD_END};
const 	char 	 *pdefstrings[]={"label", "supply", "cost", "provide", "degrade", "require", "invalidate"};

pdefops		strtoop(string s){
//
//
//
int	x;

for (x=0;x<PD_END;++x){
	if (s == pdefstrings[x]){
		return((pdefops(x)));
	}
}
return(PD_LABEL);
}


struct		pdeftag{
protected:
	long		nval;
	string		sval;
public:
	string		key;
	void		value(long	*nv){*nv=nval;}
	void		value(string	*sv){*sv=sval;}

	bool	operator==(const pdeftag &rhs) const{
		// TODO: sval?
		return(nval==rhs.nval && sval==rhs.sval && key==rhs.key);
	}
	bool    operator==(pdeftag &rhs){
                // TODO: sval?
                return(nval==rhs.nval && sval==rhs.sval && key==rhs.key);
        }

	pdeftag(string	k, long nv, string sv): key(k), nval(nv), sval(sv){}

	string		gettext(){
		stringstream	ss;

		ss << "pdeftag \"" << key << "\" " << nval << " \"" << sval << "\"";
		return(ss.str());
	}
};


struct		pdefentry{
protected:
	pdefops		op;
	pdeftag		tag;
public:
	pdefops		fetchop(){return(op);}
	pdeftag		fetchtag(){return(tag);}

	bool	operator==(const pdefentry &rhs) const{
		return(op==rhs.op && tag==rhs.tag);
	}
	bool	operator==(pdefentry &rhs){
		return(op==rhs.op && tag==rhs.tag);
        }

	pdefentry(pdefops o, string  k, long nv, string sv): tag(k,nv,sv), op(o){}
	pdefentry(string  o, string  k, long nv, string sv): tag(k,nv,sv), op(strtoop(o)){}
	string		gettext(){
		stringstream	ss;
	
		ss << "pdefentry: " << pdefstrings[op] << " " << tag.gettext();
		return(ss.str());
	}
};

typedef		multimap<string, pdefentry>		pdefgroup;
typedef		multimap<string, pdefentry>::iterator	pdefiterator;

string		stringify_pdefgroup(pdefgroup pdg){
//
//
//
stringstream	ss;

list<pdefentry>	pdelist;

transform(pdg.begin(), pdg.end(), back_inserter(pdelist), rtn2nd());
transform(pdelist.begin(), pdelist.end(), ostream_iterator<string>(ss,"\n"), mem_fun_ref(&pdefentry::gettext));
return(ss.str());
}

struct		pickdef_interface{
	virtual	pdefgroup		pdefs(pdefgroup &pd)=0;
	virtual pickdef_interface	*clone() const  =0;//{return(0);}
	virtual ~pickdef_interface(){}
};


template<typename F>
struct	pickdef:
	public	pickdef_interface{
protected:
	F	f;
public:
	pdefgroup	pdefs(pdefgroup &pd){return(f(pd));}
	pickdef_interface	*clone()const {return(new pickdef<F>(F(f)));}
	pickdef(F inf=F()):f(inf){}
};

template <typename T>
pickdef_interface	*make_pickdef(T t){return(new pickdef<T>(t));}


struct	pickdef_entry{
protected:
	pickdef_interface	*pdi;
public:
	pdefgroup		pdg;
	bool			changeflag;
	string			pickname;

	bool			pdupdate(pdefgroup &pd);


	pickdef_entry(const pickdef_entry &rhs){
		pdi=rhs.pdi->clone(); 
		changeflag=1; 
		pickname=rhs.pickname; 
	}
	pickdef_entry(pickdef_entry &rhs){*pdi=*(rhs.pdi); changeflag=1; pickname=rhs.pickname; }
	pickdef_entry(string pn,pickdef_interface *pd):pickname(pn), changeflag(1), pdi(pd){}
	pickdef_entry():pickname(""), changeflag(1), pdi(0){}
	~pickdef_entry(){delete pdi;}
};




bool	pickdef_entry::pdupdate(pdefgroup &pd){
//
// Update pdg and set the changeflag if necissary.
//
if (!pdi){
	changeflag=0;
	pdg=pdefgroup();
	return(0);
}
pdefgroup	new_pdg	= pdi->pdefs(pd);

if (new_pdg==pdg){
	changeflag=0;
	return(0);
}else{
	pdg=new_pdg;
	changeflag=1;
	return(1);
}
}


pdefgroup	simplify_pdefgroup(pdefgroup);
pdefgroup	simplify_pdefgroup(pdefgroup,string);


struct	pickdef_group{
protected:
	list<pickdef_entry>	picks;
	pdefgroup	form_naive_pdefgroup();
	pdefgroup	form_allbut_pdefgroup(list<pickdef_entry>::iterator);
public:
	void	add(pickdef_entry pde){picks.push_front(pde);}
	void	add(string pn, pickdef_interface *pd){pickdef_entry	pde(pn,pd);  picks.push_front(pde);}
	void	remove(string	s){
		// TODO: do this right.

		list<pickdef_entry>::iterator	it;
		for (it=picks.begin();it!=picks.end() && it->pickname!=s; ++it){}
		if (it!=picks.end()){
			picks.erase(it);
		}
	}
	pdefgroup	form_pdefgroup();
	pdefgroup	form_simplified_pdefgroup();
};


pdefgroup	pickdef_group::form_naive_pdefgroup(){
//
// simply add cached pick results together.
//
list<pickdef_entry>::iterator	it;
pdefgroup			rtn;

for(it=picks.begin(); it != picks.end(); ++it){
	copy(it->pdg.begin(), it->pdg.end(), inserter(rtn,rtn.end()));
}
return(rtn);
}


pdefgroup	pickdef_group::form_allbut_pdefgroup(list<pickdef_entry>::iterator	exclusion){
//
//
//
list<pickdef_entry>::iterator   it;
pdefgroup                       rtn;

for(it=picks.begin(); it!=picks.end(); ++it){
	if(it!=exclusion){
		copy(it->pdg.begin(), it->pdg.end(), inserter(rtn,rtn.end()));
	}
}
return(rtn);
}

pdefgroup	pickdef_group::form_pdefgroup(){
//
// Repeatedly call the picks until the defs stabilize.
//
bool				flag=1;
pdefgroup			allbutgroup;
list<pickdef_entry>::iterator	it;

while(flag){
	flag=0;
	for(it=picks.begin(); it != picks.end(); ++it){
		allbutgroup=form_allbut_pdefgroup(it);
		if(it->pdupdate(allbutgroup)){
			flag=1;
		}
	}
}
return(form_naive_pdefgroup());
}


pdefgroup	pickdef_group::form_simplified_pdefgroup(){
//
//
//
return(simplify_pdefgroup(form_pdefgroup()));
}


bool		is_pdefgroup_ready(pdefgroup	pdg){
//
// Returns false if a cost, require, or invalidate haven't been cleared.
//
pdefiterator	it;
pdefops		op;

for(it=pdg.begin();it!=pdg.end();++it){
	op=it->second.fetchop();
	if (op==PD_COST || op==PD_REQUIRE || op==PD_INVALIDATE){
		return(0);
	}
}
return(1);
}



pdefgroup	simplify_pdefgroup(pdefgroup pdg,string	k){
//
// Simplify the pdefs matching key k.
//
pdefgroup	workinggroup;
pdefiterator	ita,itb;
pdefgroup	rtn;
bool		flag=1;
long		x;
long		z;
long		y;
string		pst;
string		dst;

// copy out parts that match key k.
copy(pdg.equal_range(k).first, pdg.equal_range(k).second, inserter(workinggroup,workinggroup.end()));


// first, check for invalidations. They will override all.
for (ita=workinggroup.begin(); ita!=workinggroup.end(); ++ita){
	if (ita->second.fetchop()==PD_INVALIDATE){
		copy(ita,ita,inserter(rtn,rtn.end()));
		return(rtn);
	}
}

// Next, hit provide/degrade.
x=0;
z=0;
for(ita=workinggroup.begin(); ita!=workinggroup.end();++ita){
	ita->second.fetchtag().value(&y);
	if (ita->second.fetchop()==PD_PROVIDE){
		if(y>x){x=y;}
		ita->second.fetchtag().value(&pst);
	}else if (ita->second.fetchop()==PD_DEGRADE){
		if(y>z){z=y;}
		ita->second.fetchtag().value(&dst);
	}
}
x=x-z;
if (x>0){
	rtn.insert(make_pair(k,pdefentry(PD_PROVIDE,k,x,pst)));
}else if(x<0){
	rtn.insert(make_pair(k,pdefentry(PD_DEGRADE,k,0-x,dst)));
}

// Next, supply/cost.

x=0;
for(ita=workinggroup.begin(); ita!=workinggroup.end();++ita){
        ita->second.fetchtag().value(&y);
        if (ita->second.fetchop()==PD_SUPPLY){
                x=x+y;
                ita->second.fetchtag().value(&pst);
        }else if (ita->second.fetchop()==PD_COST){
                x=x-y;
                ita->second.fetchtag().value(&dst);

        }
}

if (x>0){
        rtn.insert(make_pair(k,pdefentry(PD_SUPPLY,k,x,pst)));
}else if(x<0){
        rtn.insert(make_pair(k,pdefentry(PD_COST,k,0-x,dst)));
}

// Finally, require.

for(ita=workinggroup.begin(); ita!=workinggroup.end();++ita){
	if(ita->second.fetchop()==PD_REQUIRE){
		flag=0;
		ita->second.fetchtag().value(&x);
		for(itb=rtn.begin();itb!=rtn.end();){
			if(itb->second.fetchop()==PD_SUPPLY || itb->second.fetchop()==PD_PROVIDE){
				itb->second.fetchtag().value(&y);
				if(y>=x){
					flag=1;
				}
			}
			if(flag){
				itb=rtn.end();
			}else{
				++itb;
			}
		}
		if(!flag){
			ita->second.fetchtag().value(&pst);
			rtn.insert(make_pair(k,pdefentry(PD_REQUIRE,k,x,pst)));
		}
	}
}
			
return(rtn);
	
}


pdefgroup	simplify_pdefgroup(pdefgroup pdg){
//
//
//
pdefgroup	rtn;
pdefgroup	simp_part;
pdefiterator	it;

for(it=pdg.begin();it!=pdg.end(); it=pdg.upper_bound(it->first)){
	simp_part=simplify_pdefgroup(pdg,it->first);
	copy(simp_part.begin(), simp_part.end(), inserter(rtn,rtn.end()));
}
return(rtn);
}

#endif



testpickdef.cc
#include <string>
#include <iostream>
#include <sstream>
#include "pickdef.h"

using	namespace	std;

struct	testone{
	pdefgroup	operator()(pdefgroup &pd){
		pdefgroup	rtn;

		rtn.insert(make_pair("cows",pdefentry("supply","cows",90,"moo.")));
		rtn.insert(make_pair("goats",pdefentry("require","goats",2,"goats for the slaughter!")));
		return(rtn);
	}
};


struct	testtwo{
	pdefgroup	operator()(pdefgroup &pd){
		pdefgroup	rtn;
		
		rtn.insert(make_pair("goats",pdefentry("provide","goats",6,"ern!")));
		return(rtn);
	}
};

struct 	testthree{
	pdefgroup	operator()(pdefgroup &pd){
		pdefgroup 	rtn;
		rtn.insert(make_pair("goats",pdefentry("degrade","goats",5,"pick 3 degrades goats.")));
		return(rtn);
	}
};


int	main(){


pickdef_group	test;

test.add( "cow pick", make_pickdef(testone()));

test.add( "goat pick", make_pickdef(testtwo()));

cout << "Unsimplified:\n" << stringify_pdefgroup(test.form_pdefgroup()) << "\n";
cout << "Simplified:\n" << stringify_pdefgroup(test.form_simplified_pdefgroup()) << "\n";


pickdef_group	test2;

test2.add("cow pick", make_pickdef(testone()));
test2.add( "goat pick", make_pickdef(testtwo()));
test2.add("deadgoat pick", make_pickdef(testthree()));

cout << "Unsimplified:\n" << stringify_pdefgroup(test2.form_pdefgroup()) << "\n";
cout << "Simplified:\n" << stringify_pdefgroup(test2.form_simplified_pdefgroup()) << "\n";


}



Share this post


Link to post
Share on other sites
Seems like a basic scripting system. Maybe you should look into lex and yacc, instead of hard coding values.

I can see the use of this system, but maybe you are limiting yourself by making it have a sole purpose? I think either a more generic and flexible scripting language, or some type of file to give it more flexibility. Possibly check out XML.

Look at the starcraft editor for instance. You can create 'events' that have a number of requirements, and a number of effects. The system is very well done, and allows for an enormous number of possibilities. The only thing is, it ties into the actual map, which is probably a good thing. You've got the right idea, but I think that these conditions and stuff would be better in a scripting system (roll your own, or look into python and ruby).

Good luck.

Share this post


Link to post
Share on other sites
Well certainly, but how does starcraft code such events and requirements and effects?

And XML could quite easily be parsed into this sort of structure. I don't see the distinction between how the data is stored, and how the data is used. They are largely seperate beasts. This is pretty much only how it's used.

But yes, I probably should look into scripting languages :/


Thank you very much for your comment.

Share this post


Link to post
Share on other sites
Hmm seems a lot more complex than I thought enforcing pre-requisites and requirements should be for the player. Looks like you're building some kind of AI planner there, or is that all just for the benefit of a human player?

Share this post


Link to post
Share on other sites
I must admit that i didn't try to understand all that code. But when reading your question the first thing i thought about was also 'scripting'.

I would first make a class that makes it easy to store strings and convert them to other data types. For example it could contain a stl map where the indices are your attribute names and the values are expressed as strings. If you want to get the values as an int,float or whatever, you can add a method for that. GetInt(std::string index) for example.
Another way would be to make a dator class. There is a nice tutorial on gamedev about that (Enginuity).
You can also add factory methods, that can make you real objects.

That's for getting data out of the class, how to get data in.
You can make your own file format to be loaded and parsed to set the string data.
When you want to use xml you can make a tree of those objects and parse the xml-data into such a tree.

i don't know much about scripting and parsing, but on various projects i noticed how convenient it is to be able to change and add data fast and textual.

Share this post


Link to post
Share on other sites
Quote:
Original post by Argus2
Hmm seems a lot more complex than I thought enforcing pre-requisites and requirements should be for the player. Looks like you're building some kind of AI planner there, or is that all just for the benefit of a human player?


It's for the benefit of the app. How do you make code to tell if something meets pre-requisites or limitations? How are those things even defined?

I want something a little more powerful than a simple requirements list.

Re: Jurgen

True. You [and others] can assume that I've already got a nifty class for [de]serialization already worked up. That's one reason I did include that string tag.

Getting a file into these structures is actually quite simple with such help [though slightly ugly in parts]:


#ifndef PICKDEF_PARSING
#define PICKDEF_PARSING

#include "vine.h"
#include "vine_syntax.h"
#include "pickdef.h"
#include <string>
#include <vector>

pdefentry *str_to_pdefentry(string s){
//
// Return new pdefentry represented by string s, or 0 if invalid somehow.
//
syntax_def pdedef("pdef string string long string");
vine *parsed=pdedef.parse(s);
vine *v;
pdefentry *pde=0;

if (parsed->count()!=4){
parsed->nuke(&parsed);
return(0);
}
pde=new pdefentry( (*(string *)(parsed->data)), (*(string *)(parsed->next->data)), (*(long *)(parsed->next->next->data)), (*(s
tring *)(parsed->next->next->next->data)));
parsed->nuke(&parsed);
return(pde);
}


pdefgroup vstr_to_pdefgroup(vector<string> strs){
//
// Each entry in strs is meant to be a pdefentry string parsable by str_to_pdefentry.
//
pdefgroup rtn;
pdefentry *pde;
vector<string>::iterator it;

for (it=strs.begin(); it!= strs.end(); ++it){
pde=str_to_pdefentry(*it);
if(pde){
rtn.insert(make_pair(pde->fetchtag().key, *pde));
}
}
return(rtn);
}

#endif



The data though I don't think is going to be the hard part. The more functional aspects seem to be more of what's in the way.

Thank you both for your posts, they help me greatly in thinking about the problem.

Share this post


Link to post
Share on other sites
Quote:
It's for the benefit of the app. How do you make code to tell if something meets pre-requisites or limitations?
Well, I guess whenever it needs to, it just looks at the game state and works out whether requirements are met. Abstracting that out may be beneficial to the app only if your requirements are varied and dynamic, and also for the use of AI. Because in any case, the interpreter for those requirements still has to go and look at the game state - you just have a convenient way to describe those requirements.

Share this post


Link to post
Share on other sites

This topic is 4479 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this