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:
parent
94e7fa5b3c
commit
c8115d41a8
@ -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;
|
||||
}
|
||||
};
|
||||
|
@ -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()));
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
@ -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);
|
||||
|
@ -15,6 +15,7 @@ public:
|
||||
BattleAction *curbaction;
|
||||
CGameState *gs;
|
||||
CBattleGameInterface *ai;
|
||||
ui8 color;
|
||||
|
||||
CClient();
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user