Adventure AI

[size=75]Split from the Milestones + Concerns about the AI[/size] thread. (Zamolxis)


I just downloaded the code, and have been working with it.

I was able to get the client to compile, at which time I discovered that the AI is a dll, which is great modularity.

I want to start working on some adventure AI, the first thing I want to do is get the enemy heroes to wander randomly, but I can’t quite figure it out.

This is my current attempt, but it doesn’t really work:

void CGeniusAI::yourTurn()
{
const CGHeroInstance * lastHero = m_cb->getHeroInfo(0,0);
MsgBox("** CGeniusAI::yourTurn “);
if(!lastHero==NULL)
{
MsgBox(”
AI moved hero **");
int3 dir;
//while(lastHero->)// check if dir is valid move
{
dir = lastHero->getSightCenter();

		dir.x+=rand()%3-1;
		dir.y+=rand()%3-1;
	}
	
	if(m_cb->moveHero(lastHero,dir)) MsgBox("** move successful **");
}
m_cb->endTurn();

}

Issue 1:
This compiles, and something does happen. The AI heroes are always shown to be in their original spots, but if you move your hero there, it just walks over it, and if you move to an adjacent spot, you enter a battle, as if the portrait never moves, but the actual hero does.

Issue 2:
This would be a pretty good start if it continued after round 1. But after the first move, the console reports that the move is invalid no matter which way it tries to move. It is as if the first move breaks the hero’s ability to move again.

Any help would be apriciated.

Hi!
The AI interface is generally quite well developed, since it’s the same interface the GUI uses. So everything possible to do with GUI now is also available to AI.
However, it has not been tested with AI and there is probably a lot of issues that will come out.

AI overrides methods from CGameInterface which will be called by engine when appropriate events occur. AI can get additional info and take actions via CCallback.
The yourTurn is special function that is called when AI receives turn. It is always called in a new thread, so it doesn’t block handling events.

There are two formats of hero position. The intuitive one (tile “really” occupied by hero) and the internal format (one tile to the right from intuitive position).
Most of functions use “internal” format, though there are a few exceptions.

When moving hero, it’s a good idea to use the pathfinder to get the path, even for one tile movement: cb->getPath (it will check if the destiantion is available, etc). That function receives “intuitive” positions and returns path with them.
The path needs to be converted to internal format (path.convert(0)) before coordinates from its nodes can be send to cb->moveHero.

AFAIK displaying of the AI heroes movement should work fine as long as it’s on the revealed part of map (not under fog of war). Check if revealing map (eg. by ‘vcmieagles’ cheat) really “solves” issue.

BattleInterface probably won’t work when AI is the attacking side. (Well, we haven’t AI that attacks…) Tow Dragon will have to fix it, I hope it won’t be much trouble.

Good luck!

I’m still having problems. This is the code that I have now.

if(!lastHero==NULL)
	{
		MsgBox("** AI moved hero **");
		int3 dir;
		CPath path;
		
		do{
			dir =lastHero->convertPosition(lastHero->pos,false);
			
			dir.x+=rand()%21-10;
			dir.y+=rand()%21-10;
		}while(! m_cb->getPath(lastHero->pos,dir,lastHero,path));
		path.convert(0);
		m_cb->moveHero(lastHero,path.nodes[0].coord);
	}

The problem now is that they rarely move. In fact of 4 AI computers, only one moves. It moves south on the first move, and south again, and it never moves again after that.

Ok, I have something working, but I think I have found a serious bug.

I’ve put the autosave into games.rar. I have also uploaded the AI dll.

Here’s what happens:

As long as the player does not move, the AI will move until they die (they can get into battles which do end, usually with their death), but as soon as the player moves, (or even plots a path but doesn’t take it), the next time the AI moves, it will crash.

(edit: I forgot to upload the AI dll)
GeniusAI.rar (56.6 KB)
games.rar (69.8 KB)

The rand() function may not work good, since it’s state is thread-local and it needs to be initialized (srand) separately for each thread.

I’m unable to say much with that. I need sources or at least debug build of the AI.
It would be best if you register at sourceforge, then we can create on repository a new project / branch for your AI.

Ok, how do I do that?

Also, I believe the crashes I am having could be due to thread issues. Unfortunately, I don’t have much experience with threads (especially boost::thread in c++).

for now, I have uploaded CGeniusAI.h/cpp
CGeniusAI.zip (3.75 KB)

First you have to create an account on SF (via sourceforge.net/account/registration/ ). Then you give me your account name and I give you rights needed to write changes to the SVN repository. Then you will be able to put your code to the repository and rest of programmers will automatically get it when updating.

I’ll take a closer look tomorrow.

Ok, it’s the same as my user name here.

I’ve fixed this issue (not needed path removing leading to crash) :slight_smile:
(r1054)

I’ve given you full subversion access.
You should probably add your code to the General AI subproject of the GeniusAI (unless you want to create separate AI project).

There are two special functions that are always called in the new thread: yourTurn and activeStack (called during battle when your stack receives turn). All other functions are called from event handling thread of client.

There is one possible difficulty: callback calls (like cb->moveHero) return immediately after sending request to the server. The request is then realized (if possible) and AI receives calls about its result (like heroMoved). When server ends handling request it sends appropriate info and AI receives call to requestRealized.

Game data should not be checked/read until the package is fully applied. So eg. reading hero->movement just after calling cb->moveHero(hero, path) is not safe. Path may be or (more probably) may be not updated. There is also a risk of conflicts between threads (eg. heroMoved and yourTurn modifying same data).

If you want (it will be probably needed) I can write some code that will make possible calling cb methods and waiting until request is fully realised before returning.

Yeah, that would be great.

In addition, I am curious about something. The class ICallback is pure virtual, and it seems that the only inheriting class is CCallback. CCallback has more goodies, for example getTownInfo() which returns a vector, and is missing from ICallback.

I assume that the AI is only given access to ICallback because there is information that could potentially be used to cheat, (i.e. a CGameState *, ) in CCallback. But something makes me think that I might need access to some of these.

Also, while it is an important goal to make the AI play honestly, it might be nice if the AI could see the entire map, and maybe other things too (just in the early stages of coding).

Also, is there a way to get a vector of visitable, visible objects on the map? The thought of checking every visible tile every turn is chilling.

Wonderful.

One issue with adventure AI involves the following code.

void CGeniusAI::battleStart(CCreatureSet *army1, CCreatureSet *army2, int3 tile, CGHeroInstance *hero1, CGHeroInstance *hero2, bool side)
{
	assert(!m_battleLogic); //************** assert fails when AI starts a battle. ***************
	m_battleLogic = new BattleAI::CBattleLogic(m_cb, army1, army2, tile, hero1, hero2, side);

	MsgBox("** CGeniusAI::battleStart **");
}

The assert fails, and the game crashes. I can’t figure it out because the only way the assertion can fail is if the same CGeniusAI has it’s battleStart() called twice before a battleEnd().

Since at the moment the AI motions are deterministic, this can be reproduced with the following save.

I have commited the AI code rev 1056.
assert.zip (79.8 KB)

If any public method of CCallback is missing from ICallback, feel free to add it.

It shouldn’t be necessary (and possible). AI and GUI should take all their information by Callback and call-ins.

AI should also be able to cheat by sending the cheat code via cb->sendMessage :wink:

There is no such info stored in the game. Adding it to the Callback would have end with checking all visible tiles (or all objects) anyway.
I’d suggest to store vector of visitable objects in the AI. You can check all visible tiles only on first AI initialization, then you can update it on tileRevealed calls. (And objectREmoved call that I’ll soon add).

Complex issue.
It’s generally caused by AI trying to move hero after stepping on monster that ends with stepping on it again and starting duplicate battle.

AI actions should be blocked when battle is triggered and AI must be informed about this. I’ll implement it within next few days. By that time AI can only avoid monsters (you can check if object with ID == 54 is present on the destination tile).

Is there a simple way to tell what race a town/hero is given CGTownInstance and CGHeroInstance?

CGTownInstance *t;
t->subID is the town type h3maparchives.celestialheavens.c … rmat_t.htm
t->town contains information about that type (always t->town->typeID == t->subID).

CGHeroInstance *h;
h->subID is the hero type: h3maparchives.celestialheavens.c … rmat_h.htm
h->type->heroType is enum with hero classes, its values follow h3maparchives.celestialheavens.c … mat_hc.htm

Is it possible to make this a priority, I’ve sorta hit a roadblock, and my current solution is kludge.

This too, I now must recreate the vector every time the AI moves a hero. (sloooow)

Done. However I haven’t tested my changes thoroughly, so if you encounter any problems, let me know.

  1. ICallback has waitTillRealize flag. By default set to false. When true, callback calls before returning wait till request is realized. So after moveHero hero (if move succeeded) will be guaranteed to be on the destination tile, his movement points will be updated, all object interaction calls over. The only exception is battle -> described below.
    waitTillRealize doesn’t work with calls answering to dialogs (is ignored, no realization info available).

  2. I’ve added to the AI m_state which wraps BattleState enum saying if this AI player is engaged into battle. Possible values:

  • NO_BATTLE
  • UPCOMING_BATTLE - triggered during hero movement handling (before returning heroMoved if waiting for realization). When set, AI cannot move hero (and generally should refrain from taking any actions). battleStart call should come soon.
  • ONGOING_BATTLE - set when battle actually starts. AI use cb to get info about battle and take only battleactions.
  • ENDING_BATTLE - battle is over but it’s result has not been saved yet (so we can still read data about army that is to be removed). When results are stored, m_state returns to NO_BATTLE.

Moving hero may look like that:

m_cb->moveHero(h,dst);
//check if movement triggered battle
if(m_state.get() != NO_BATTLE)
{
	//wait for battle end
	m_state.waitUntil(NO_BATTLE);

	//wait over, battle over too. hero might be killed. check.
	if(m_cb->getHeroSerial(h) < 0) 
	{ 
		//stop movement
	}
}
  1. I’ve added objectRemoved call-in to CGameInterface. In junction with tileRevealed and heroMoved it should be enough to update visitable objects vector without recreating it.

Hey, how hard would it be to create a mode in which all players are AI?

It would make the process of making the AI good much easier.

It is easy but without providing in-game GUI. So you would have to track the progress of the game via console / logfile output or debugger.
Otherwise it won’t be easy. I can still write this but it would need some time.

At least for now I wouldn’t need a GUI.

Not sure but maybe GUI would be nice in the future. Mostly just being able to see the map.

If it’s not too much trouble, it would be great if you would implement this for the console. Thanks.

I’ve added simple and dirty solution but it seems work fine.
Type “onlyai” command before starting game and all players will be set to AI.
Alternatively, to save typing, you may just init gOnlyAI (CMT.cpp l. 72) with false (but don’t commit that change).

It will be done eventually. I’m now working on partial restructuring GUI system to make it more independent from player interface. But it’s for improvements in pregame.
Hot-seat and in game GUI without plaer will be done in further future.

By the way: I must say you’re doing really great job! Many thanks for contribution and keep on! :slight_smile: