1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-26 22:57:00 +02:00

Imposed time limits on AIs.

Simple action validation.
This commit is contained in:
Michał W. Urbańczyk 2011-10-11 13:16:28 +00:00
parent 94e7fa5b3c
commit c8115d41a8
11 changed files with 267 additions and 53 deletions

View File

@ -1,5 +1,6 @@
#include <cstring>
#include "../../AI_Base.h"
#include "../../lib/CBattleCallback.h"
const char *g_cszAiName = "Mad AI";
@ -16,13 +17,17 @@ class CMadAI : public CBattleGameInterface
virtual BattleAction activeStack(const CStack * stack)
{
// int *g = 0;
// *g = 4;
// while(1);
srand(time(NULL));
BattleAction ba;
ba.actionType = rand() % 14;
ba.additionalInfo = rand() % BFIELD_SIZE + 5;
ba.side = rand() % 7;
ba.destinationTile = rand() % BFIELD_SIZE + 5;
ba.stackNumber = rand() % 500;
ba.stackNumber = rand() % cb->battleGetAllStacks().size() + 1;
return ba;
}
};

View File

@ -2,7 +2,7 @@
#include <boost/thread.hpp>
#include <boost/bind.hpp>
int main(int argc, const char **)
int main(int argc, const char **argv)
{
std::string runnername =
#ifdef _WIN32
@ -19,7 +19,7 @@ int main(int argc, const char **)
#endif
;
std::string serverCommand = servername + " b1.json StupidAI StupidAI";
std::string serverCommand = servername + " b1.json StupidAI MadAI"; // StupidAI MadAI
boost::thread t(boost::bind(std::system, serverCommand.c_str()));
boost::thread tt(boost::bind(std::system, runnername.c_str()));
boost::thread ttt(boost::bind(std::system, runnername.c_str()));

View File

@ -1,43 +1,94 @@
#pragma once
#include "../global.h"
#include <ctime>
#ifndef _WIN32
#include <sys/time.h> // for gettimeofday()
#ifdef _WIN32
#include <ctime>
typedef time_t TTime;
#define GET_TIME(var) (var = clock())
#else
#include <sys/time.h> // for gettimeofday()
typedef timeval TTime;
#define GET_TIME(var) (gettimeofday(&var, NULL))
#endif
struct CheckTime
{
#ifdef _WIN32
time_t t0;
#else
timeval t1, t2;
#endif
TTime start;
std::string msg;
CheckTime(const std::string & Msg) : msg(Msg)
#ifdef _WIN32
, t0(clock())
#endif
CheckTime(const std::string & Msg = "") : msg(Msg)
{
#ifndef _WIN32
gettimeofday(&t1, NULL);
#endif
GET_TIME(start);
}
int timeDiff(const TTime &t1, const TTime &t2)
{
int ret = 0;
#ifdef _WIN32
ret = (float)(t2 - t1) / (CLOCKS_PER_SEC / 1000);
#else
ret += (t2.tv_sec - t1.tv_sec) * 1000.0; // sec to ms
ret += (t2.tv_usec - t1.tv_usec) / 1000.0; // us to ms
#endif
//TODO abs?
return ret;
}
int timeSinceStart()
{
TTime now;
GET_TIME(now);
return timeDiff(start, now);
}
~CheckTime()
{
float liczyloSie = 0;
#ifdef _WIN32
liczyloSie = (float)(clock() - t0) / (CLOCKS_PER_SEC / 1000);
#else
// stop timer
gettimeofday(&t2, NULL);
liczyloSie = (t2.tv_sec - t1.tv_sec) * 1000.0; // sec to ms
liczyloSie += (t2.tv_usec - t1.tv_usec) / 1000.0; // us to ms
#endif
tlog0 << msg << ": " << liczyloSie << "ms" << std::endl;;
if(msg.size())
{
float liczyloSie = timeSinceStart();
tlog0 << msg << ": " << liczyloSie << "ms" << std::endl;
}
}
};
//all ms
const int PROCESS_INFO_TIME = 5;
const int MAKE_DECIDION_TIME = 75;
const int MEASURE_MARGIN = 1;
const int HANGUP_TIME = 50;
void postInfoCall(int timeUsed);
void postDecisionCall(int timeUsed);
struct Bomb
{
int armed;
void run(int time)
{
boost::this_thread::sleep(boost::posix_time::milliseconds(time));
if(armed)
{
tlog1 << "BOOOM! The bomb exploded! AI was thinking for too long!\n";
exit(1);
}
delete this;
}
Bomb(int timer)
{
boost::thread t(&Bomb::run, this, timer);
t.detach();
}
void disarm()
{
armed = 0;
}
};

View File

@ -5,8 +5,9 @@
#include "../lib/BattleAction.h"
#include "../lib/CGameInterface.h"
#include "CheckTime.h"
#include "../lib/BattleState.h"
#define NOT_LIB
//#define NOT_LIB
#include "../lib/RegisterTypes.cpp"
template <typename T> class CApplyOnCL;
@ -104,14 +105,44 @@ void CClient::requestMoveFromAIWorker(const CStack *s)
try
{
CheckTime timer("AI was thinking for ");
Bomb *b = new Bomb(MAKE_DECIDION_TIME + HANGUP_TIME);
CheckTime timer;
ba = ai->activeStack(s);
MakeAction temp_action(ba);
*serv << &temp_action;
postDecisionCall(timer.timeSinceStart());
b->disarm();
}
catch(...)
{
tlog0 << "AI thrown an exception!\n";
//TODO: disqualify?
ba = BattleAction::makeDefend(s);
}
try
{
MakeAction temp_action(ba);
tlog0 << "Checking if action looks valid... ";
bool valid = gs->isValidAction(temp_action, true);
if(gs->curB->sides[temp_action.ba.side] != color)
{
tlog1 << "Wrong side set!\n";
valid = false;
}
if(!valid)
{
tlog1 << "Warning: action seems to be invalid! Stack will defend\n";
temp_action.ba = BattleAction::makeDefend(s);
}
else
tlog1 << "Doesn't look suspicious.\n";
*serv << &temp_action;
}
catch(...)
{
tlog1 << "Failed sending action!\n";
}
}
@ -122,6 +153,7 @@ CClient::CClient()
ai = NULL;
curbaction = NULL;
terminate = false;
color = 250;
applier = new CApplier<CBaseForCLApply>;
registerTypes2(*applier);

View File

@ -15,6 +15,7 @@ public:
BattleAction *curbaction;
CGameState *gs;
CBattleGameInterface *ai;
ui8 color;
CClient();

View File

@ -12,19 +12,40 @@
#include "CheckTime.h"
void postInfoCall(int timeUsed)
{
tlog0 << "AI was processing info for " << timeUsed << " ms.\n";
if(timeUsed > PROCESS_INFO_TIME + MEASURE_MARGIN)
{
tlog1 << "That's too long! AI is disqualified!\n";
exit(1);
}
}
void postDecisionCall(int timeUsed)
{
tlog0 << "AI was thinking over an action for " << timeUsed << " ms.\n";
if(timeUsed > MAKE_DECIDION_TIME + MEASURE_MARGIN)
{
tlog1 << "That's too long! AI is disqualified!\n";
exit(1);
}
}
//macros to avoid code duplication - calls given method with given arguments if interface for specific player is present
//awaiting variadic templates...
#define BATTLE_INTERFACE_CALL_IF_PRESENT(function,...) \
do \
{ \
if(cl->ai) \
{ \
CheckTime("AI was processing info for "); \
cl->ai->function(__VA_ARGS__); \
} \
#define BATTLE_INTERFACE_CALL_IF_PRESENT(function,...) \
do \
{ \
int timeUsed = 0; \
if(cl->ai) \
{ \
Bomb *b = new Bomb(PROCESS_INFO_TIME + HANGUP_TIME);\
CheckTime pr; \
cl->ai->function(__VA_ARGS__); \
postInfoCall(pr.timeSinceStart()); \
b->disarm(); \
} \
} while(0)
#define UNEXPECTED_PACK assert(0)
@ -257,7 +278,7 @@ void GarrisonDialog::applyCl(CClient *cl)
void BattleStart::applyCl( CClient *cl )
{
//TODO!!!!
BATTLE_INTERFACE_CALL_IF_PRESENT(battleStart, info->belligerents[0], info->belligerents[1], info->tile, info->heroes[0], info->heroes[1], cl->ai->playerID);
BATTLE_INTERFACE_CALL_IF_PRESENT(battleStart, info->belligerents[0], info->belligerents[1], info->tile, info->heroes[0], info->heroes[1], cl->color);
}
void BattleNextRound::applyFirstCl(CClient *cl)
@ -279,7 +300,7 @@ void BattleSetActiveStack::applyCl( CClient *cl )
else
playerToCall = activated->owner;
if(cl->ai && cl->ai->playerID == playerToCall)
if(cl->ai && cl->color == playerToCall)
cl->requestMoveFromAI(activated);
}

View File

@ -84,7 +84,7 @@ int main(int argc, char** argv)
if(battleAIName.size())
{
cl.ai = CDynLibHandler::getNewBattleAI(battleAIName);
cl.ai->playerID = color;
cl.color = color;
tlog0 << "AI created\n";
cl.ai->init(cbc);
}

View File

@ -32,6 +32,7 @@
#include "BattleState.h"
#include "../lib/JsonNode.h"
#include <boost/algorithm/string/predicate.hpp>
#include "BattleAction.h"
boost::rand48 ran;
class CGObjectInstance;
@ -2465,6 +2466,75 @@ void CGameState::attachArmedObjects()
}
}
bool CGameState::isValidAction(const MakeAction &ma, bool verbose) const
{
#define PROBLEM(txt) do{if(verbose) tlog1 << "Action invalid: " << txt << std::endl; return false;} while(0);
const CStack *stack = curB->getStack(ma.ba.stackNumber);
if(ma.ba.actionType != BattleAction::RETREAT && ma.ba.actionType != BattleAction::SURRENDER)
{
if(!stack)
PROBLEM("There is no such stack!");
if(stack->ID != curB->activeStack)
PROBLEM("Action has to be about the active stack!");
}
switch(ma.ba.actionType)
{
case BattleAction::NO_ACTION:
PROBLEM("No action is not a valid action. Use DEFEND to do nothing!");
case BattleAction::HERO_SPELL:
PROBLEM("Casting spells by hero must be done as a custom action!");
case BattleAction::WALK:
if(!vstd::contains(curB->getAccessibility(stack, true), ma.ba.destinationTile))
PROBLEM("Destination tile is not accessible!");
return true;
case BattleAction::DEFEND:
return true;
case BattleAction::RETREAT:
return true;
case BattleAction::SURRENDER:
PROBLEM("SURRENDER is not considered to be a valid action. Use RETREAT instead!");
case BattleAction::WALK_AND_ATTACK:
{
std::vector<THex> attackable;
if(!vstd::contains(curB->getAccessibility(stack, true, &attackable), ma.ba.destinationTile))
PROBLEM("Destination tile is not accessible!");
if(!vstd::contains(attackable, ma.ba.additionalInfo))
PROBLEM("Target tile is not attackable!");
}
return true;
case BattleAction::SHOOT:
if(!curB->battleCanShoot(stack, ma.ba.destinationTile))
PROBLEM("Stack cannot make shot!");
return true;
case BattleAction::WAIT:
if(vstd::contains(stack->state, WAITING))
PROBLEM("Stack can be ordered to wait only once in a turn!");
return true;
case BattleAction::CATAPULT:
//TODO czy aktywna jest katapulta
//czy bohater posiada balistyke
// czy celuje w mur
// czy segment w ktory celuje nie jest juz zniszczony
return true;
case BattleAction::MONSTER_SPELL:
PROBLEM("Monster spells are not supported!");
case BattleAction::BAD_MORALE:
PROBLEM("Player can't decide when stack has a bad morale!");
case BattleAction::STACK_HEAL:
//TODO namiot
return true;
default:
PROBLEM("Action of invalid type!");
}
}
int3 CPath::startPos() const
{
return nodes[nodes.size()-1].coord;

View File

@ -67,6 +67,7 @@ class CCampaign;
class CCampaignState;
class IModableArt;
class CGGarrison;
struct MakeAction;
namespace boost
{
@ -405,6 +406,8 @@ public:
bmap<ui32, ConstTransitivePtr<CGHeroInstance> > unusedHeroesFromPool(); //heroes pool without heroes that are available in taverns
BattleInfo * setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance *town);
bool isValidAction(const MakeAction &ma, bool verbose) const;
void buildBonusSystemTree();
void attachArmedObjects();
void buildGlobalTeamPlayerTree();

View File

@ -473,8 +473,11 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
tlog0 << boost::format("Total casualties points: %d\n") % casualtiesPoints;
//battle ai1 ai2 winner_side winner_casualties
time_t czas;
time(&czas);
std::string resultTypes[] = {"SIDE_DEFEATED", "SIDE_RETREATED", "SIDE_SURRENDERED", "SIDE_DISQUALIFIED"};
std::ofstream resultsList("results.txt", std::fstream::out | std::fstream::app);
resultsList << boost::format("\n%s\t%s\t%s\t%d\t%d") % gs->scenarioOps->mapname % ais[0] % ais[1] % (int)battleResult.data->winner % casualtiesPoints;
resultsList << boost::format("%s\t%s\t%s\t%d\t%d\t%s\t%s") % gs->scenarioOps->mapname % ais[0] % ais[1] % (int)battleResult.data->winner % casualtiesPoints % resultTypes[battleResult.data->result] % asctime(localtime(&czas));
}
sendAndApply(&resultsApplied);
@ -634,13 +637,30 @@ void CGameHandler::applyBattleEffects(BattleAttack &bat, const CStack *att, cons
bat.bsa.push_back(bsa);
}
}
void CGameHandler::disqualifyPlayer(int side)
{
tlog0 << "The side " << (int)side << " will be disqualified!\n";
boost::unique_lock<boost::shared_mutex> lock(*gs->mx);
setBattleResult(3, !side);
battleMadeAction.setn(true);
}
void CGameHandler::handleConnection(std::set<int> players, CConnection &c)
{
setThreadName(-1, "CGameHandler::handleConnection");
srand(time(NULL));
CPack *pack = NULL;
boost::function<void()> onException;
try
{
if(gs->curB && gs->initialOpts->mode == StartInfo::DUEL)
{
onException = boost::bind(&CGameHandler::disqualifyPlayer, this, *players.begin());
}
while(1)//server should never shut connection first //was: while(!end2)
{
pack = c.retreivePack();
@ -668,6 +688,7 @@ void CGameHandler::handleConnection(std::set<int> players, CConnection &c)
}
else if(apply)
{
boost::unique_lock<boost::recursive_mutex> lock(gsm);
bool result = apply->applyOnGH(this,&c,pack);
tlog5 << "Message successfully applied (result=" << result << ")!\n";
@ -690,11 +711,16 @@ void CGameHandler::handleConnection(std::set<int> players, CConnection &c)
}
catch(boost::system::system_error &e) //for boost errors just log, not crash - probably client shut down connection
{
assert(!c.connected); //make sure that connection has been marked as broken
boost::unique_lock<boost::recursive_mutex> lock(gsm);
if(gs->scenarioOps->mode != StartInfo::DUEL)
{
assert(!c.connected); //make sure that connection has been marked as broken
}
tlog1 << e.what() << std::endl;
end2 = true;
if(onException) onException();
}
HANDLE_EXCEPTION(end2 = true);
HANDLE_EXCEPTIONC(boost::unique_lock<boost::recursive_mutex> lock(gsm);end2 = true; if(onException) {onException();return;});
tlog1 << "Ended handling connection\n";
}
@ -1938,8 +1964,11 @@ void CGameHandler::sendToAllClients( CPackForClient * info )
tlog5 << "Sending to all clients a package of type " << typeid(*info).name() << std::endl;
for(std::set<CConnection*>::iterator i=conns.begin(); i!=conns.end();i++)
{
boost::unique_lock<boost::mutex> lock(*(*i)->wmx);
**i << info;
if((**i).connected)
{
boost::unique_lock<boost::mutex> lock(*(*i)->wmx);
**i << info;
}
}
}
@ -2955,7 +2984,7 @@ static EndAction end_action;
bool CGameHandler::makeBattleAction( BattleAction &ba )
{
tlog1 << "\tMaking action of type " << ba.actionType << std::endl;
tlog1 << "\tMaking action of type " << (int)ba.actionType << std::endl;
bool ok = true;
switch(ba.actionType)

View File

@ -123,6 +123,8 @@ public:
//const CArmedInstance * bEndArmy1, * bEndArmy2;
bool visitObjectAfterVictory;
//
void disqualifyPlayer(int side);
void endBattle(int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2); //ends battle
void prepareAttack(BattleAttack &bat, const CStack *att, const CStack *def, int distance, int targetHex); //distance - number of hexes travelled before attacking
void applyBattleEffects(BattleAttack &bat, const CStack *att, const CStack *def, int distance, bool secondary); //damage, drain life & fire shield