From 94e7fa5b3cdda05c4d7d7e63b06f1ff35924456b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=2E=20Urba=C5=84czyk?= Date: Thu, 6 Oct 2011 20:55:19 +0000 Subject: [PATCH] Logging battle activites. Replaying battles with client. Added an AI for answer validation (it returns junk action packets). Minor fixes. --- AI/MadAI/MadAI.vcxproj | 163 ++++++++++++++++++++++++++++++++++++++ AI/MadAI/main.cpp | 43 ++++++++++ AI/StupidAI/main.cpp | 11 --- Odpalarka/main.cpp | 6 +- StartInfo.h | 2 +- VCMI_VS10.sln | 13 +++ client/CMT.cpp | 33 +++++++- client/Client.cpp | 136 +++++++++++++++++++++++++------ client/Client.h | 2 + client/NetPacksClient.cpp | 2 +- server/CGameHandler.cpp | 25 ++++++ server/CGameHandler.h | 5 ++ server/CVCMIServer.cpp | 13 ++- 13 files changed, 408 insertions(+), 46 deletions(-) create mode 100644 AI/MadAI/MadAI.vcxproj create mode 100644 AI/MadAI/main.cpp diff --git a/AI/MadAI/MadAI.vcxproj b/AI/MadAI/MadAI.vcxproj new file mode 100644 index 000000000..be0c3d067 --- /dev/null +++ b/AI/MadAI/MadAI.vcxproj @@ -0,0 +1,163 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + RD + Win32 + + + RD + x64 + + + + {DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0} + StupidAI + + + + DynamicLibrary + true + MultiByte + + + DynamicLibrary + true + MultiByte + + + DynamicLibrary + false + true + MultiByte + + + DynamicLibrary + false + true + MultiByte + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)\AI\ + $(IncludePath) + $(LibraryPath) + + + $(SolutionDir)\AI\ + $(IncludePath) + $(LibraryPath) + + + $(SolutionDir)$(Configuration)\bin\AI\ + $(IncludePath) + $(LibraryPath) + + + $(SolutionDir)$(Configuration)\bin\AI\ + $(IncludePath) + $(LibraryPath) + + + + Level3 + Disabled + %(AdditionalIncludeDirectories) + NotUsing + stdafx.h + false + + + true + VCMI_lib.lib;%(AdditionalDependencies) + $(OutDir)..;%(AdditionalLibraryDirectories) + $(OutDir)$(TargetName).dll + + + + + Level3 + Disabled + %(AdditionalIncludeDirectories) + NotUsing + stdafx.h + + + true + VCMI_lib.lib;%(AdditionalDependencies) + $(OutDir)..;%(AdditionalLibraryDirectories) + $(OutDir)StupidAI.dll + + + + + Level3 + MaxSpeed + true + true + %(AdditionalIncludeDirectories) + NotUsing + stdafx.h + + + true + true + true + VCMI_lib.lib;%(AdditionalDependencies) + $(OutDir)..;%(AdditionalLibraryDirectories) + $(OutDir)StupidAI.dll + + + + + Level3 + MaxSpeed + true + true + %(AdditionalIncludeDirectories) + NotUsing + stdafx.h + + + true + true + true + VCMI_lib.lib;%(AdditionalDependencies) + $(OutDir)..;%(AdditionalLibraryDirectories) + $(OutDir)StupidAI.dll + + + + + + + + + \ No newline at end of file diff --git a/AI/MadAI/main.cpp b/AI/MadAI/main.cpp new file mode 100644 index 000000000..26bcfcced --- /dev/null +++ b/AI/MadAI/main.cpp @@ -0,0 +1,43 @@ +#include +#include "../../AI_Base.h" + + +const char *g_cszAiName = "Mad AI"; + + + +class CMadAI : public CBattleGameInterface +{ + CBattleCallback *cb; + virtual void init(CBattleCallback * CB) + { + cb = CB; + } + + virtual BattleAction activeStack(const CStack * stack) + { + 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; + return ba; + } +}; + +extern "C" DLL_F_EXPORT void GetAiName(char* name) +{ + strcpy_s(name, strlen(g_cszAiName) + 1, g_cszAiName); +} + +extern "C" DLL_F_EXPORT CBattleGameInterface* GetNewBattleAI() +{ + return new CMadAI(); +} + +extern "C" DLL_F_EXPORT void ReleaseBattleAI(CBattleGameInterface* i) +{ + delete (CMadAI*)i; +} diff --git a/AI/StupidAI/main.cpp b/AI/StupidAI/main.cpp index 3963b80b2..bd0dcf92b 100644 --- a/AI/StupidAI/main.cpp +++ b/AI/StupidAI/main.cpp @@ -8,22 +8,11 @@ const char *g_cszAiName = "Stupid AI 0.1"; -extern "C" DLL_F_EXPORT int GetGlobalAiVersion() -{ - return AI_INTERFACE_VER; -} - extern "C" DLL_F_EXPORT void GetAiName(char* name) { strcpy_s(name, strlen(g_cszAiName) + 1, g_cszAiName); } -extern "C" DLL_F_EXPORT char* GetAiNameS() -{ - // need to be defined - return NULL; -} - extern "C" DLL_F_EXPORT CBattleGameInterface* GetNewBattleAI() { return new CStupidAI(); diff --git a/Odpalarka/main.cpp b/Odpalarka/main.cpp index d7ef98de1..17f2f109e 100644 --- a/Odpalarka/main.cpp +++ b/Odpalarka/main.cpp @@ -17,8 +17,10 @@ int main(int argc, const char **) #else "./vcmiserver" #endif - ; - boost::thread t(boost::bind(std::system, (servername + " b1.json StupidAI StupidAI").c_str())); + ; + + std::string serverCommand = servername + " b1.json StupidAI StupidAI"; + 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())); if(argc == 2) diff --git a/StartInfo.h b/StartInfo.h index c8e6a3083..e3689fc7b 100644 --- a/StartInfo.h +++ b/StartInfo.h @@ -53,7 +53,7 @@ struct PlayerSettings /// Struct which describes the difficulty, the turn time,.. of a heroes match. struct StartInfo { - enum EMode {NEW_GAME, LOAD_GAME, CAMPAIGN, DUEL, INVALID = 255}; + enum EMode {NEW_GAME, LOAD_GAME, CAMPAIGN, DUEL, DUEL_REPLAY, INVALID = 255}; ui8 mode; //uses EMode enum ui8 difficulty; //0=easy; 4=impossible diff --git a/VCMI_VS10.sln b/VCMI_VS10.sln index 7b1455c60..8b7f31bc8 100644 --- a/VCMI_VS10.sln +++ b/VCMI_VS10.sln @@ -26,6 +26,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VCMI_client", "client\VCMI_client.vcxproj", "{8355EBA8-65C2-44A4-BC2D-78053E1BF2D6}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MadAI", "AI\MadAI\MadAI.vcxproj", "{DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -111,6 +113,17 @@ Global {8355EBA8-65C2-44A4-BC2D-78053E1BF2D6}.Release|Win32.ActiveCfg = RD|x64 {8355EBA8-65C2-44A4-BC2D-78053E1BF2D6}.Release|x64.ActiveCfg = RD|x64 {8355EBA8-65C2-44A4-BC2D-78053E1BF2D6}.Release|x64.Build.0 = RD|x64 + {DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.Debug|Win32.ActiveCfg = Debug|Win32 + {DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.Debug|Win32.Build.0 = Debug|Win32 + {DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.Debug|x64.ActiveCfg = Debug|x64 + {DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.Debug|x64.Build.0 = Debug|x64 + {DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.RD|Win32.ActiveCfg = RD|Win32 + {DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.RD|Win32.Build.0 = RD|Win32 + {DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.RD|x64.ActiveCfg = RD|x64 + {DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.RD|x64.Build.0 = RD|x64 + {DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.Release|Win32.ActiveCfg = RD|x64 + {DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.Release|x64.ActiveCfg = RD|x64 + {DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.Release|x64.Build.0 = RD|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/client/CMT.cpp b/client/CMT.cpp index 462858357..9f5395b83 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -185,6 +185,7 @@ static void prog_help(const char *progname) printf(" -v, --version display version information and exit\n"); } +CLoadFile *replayLoader; #ifdef _WIN32 int _tmain(int argc, _TCHAR* argv[]) @@ -197,7 +198,8 @@ int main(int argc, char** argv) opts.add_options() ("help,h", "display help and exit") ("version,v", "display version information and exit") - ("battle,b", po::value(), "runs game in duel mode (battle-only") + ("battle,b", po::value(), "runs game in duel mode (battle-only)") + ("replay,r", "replays a recorded battle, use together with -b"); ("nointro,i", "skips intro movies"); po::variables_map vm; @@ -284,7 +286,27 @@ int main(int argc, char** argv) else { StartInfo *si = new StartInfo(); - si->mode = StartInfo::DUEL; + if(vm.count("replay")) + { + si->mode = StartInfo::DUEL_REPLAY; + replayLoader = new CLoadFile(vm["battle"].as()); + replayLoader->smartPointerSerialization = false; + if(!replayLoader->sfile) + { + tlog1 << "Cannot find file with recorded battle (" << si->mapname << ")!\n"; + exit(1); + } + + std::string bname, ai1, ai2; + ui8 magic; + *replayLoader >> bname >> ai1 >> ai2 >> magic; + assert(magic == '$'); + + si->mapname = bname; + tlog0 << "Replaying battle between " <mode = StartInfo::DUEL; startGame(si); } mainGUIThread = new boost::thread(&CGuiHandler::run, boost::ref(GH)); @@ -718,6 +740,7 @@ void startGame(StartInfo * options, CConnection *serv/* = NULL*/) client->newGame(serv, options); break; case StartInfo::DUEL: + case StartInfo::DUEL_REPLAY: client->newDuel(serv, options); break; case StartInfo::LOAD_GAME: @@ -727,7 +750,11 @@ void startGame(StartInfo * options, CConnection *serv/* = NULL*/) break; } - client->connectionHandler = new boost::thread(&CClient::run, client); + if(client->serv) + client->connectionHandler = new boost::thread(&CClient::run, client); + else + client->connectionHandler = new boost::thread(&CClient::runReplay, client, replayLoader); + } void requestChangingResolution() diff --git a/client/Client.cpp b/client/Client.cpp index a5cb1596c..71a1955a5 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -39,6 +39,7 @@ #define NOT_LIB #include "../lib/RegisterTypes.cpp" +#include extern std::string NAME; namespace intpr = boost::interprocess; @@ -418,37 +419,46 @@ void CClient::newGame( CConnection *con, StartInfo *si ) void CClient::newDuel(CConnection *con, StartInfo *si) { - serv = con; - if(!serv) + if(si->mode == StartInfo::DUEL) { - std::string host = "127.0.0.1"; - std::string port = "3030"; - - int i = 3; - while(!serv) + serv = con; + if(!serv) { - try + std::string host = "127.0.0.1"; + std::string port = "3030"; + + int i = 3; + while(!serv) { - tlog0 << "Establishing connection...\n"; - serv = new CConnection(host, port, "DLL host"); - } - catch(...) - { - tlog1 << "\nCannot establish connection! Retrying within 2 seconds" << std::endl; - boost::this_thread::sleep(boost::posix_time::seconds(2)); - if(!--i) - exit(0); + try + { + tlog0 << "Establishing connection...\n"; + serv = new CConnection(host, port, "DLL host"); + } + catch(...) + { + tlog1 << "\nCannot establish connection! Retrying within 2 seconds" << std::endl; + boost::this_thread::sleep(boost::posix_time::seconds(2)); + if(!--i) + exit(0); + } } } + + + ui8 color; + std::string battleAIName; + *serv >> *si >> battleAIName >> color; + assert(si->mode == StartInfo::DUEL); + assert(color > 1); //we are NOT participants + //tlog0 << format("Server wants us to be %s in battle %s as side %d") % battleAIName % si.mapname % (int)color; + + + } + else + { + si->mode = StartInfo::DUEL; } - - - ui8 color; - std::string battleAIName; - *serv >> *si >> battleAIName >> color; - assert(si->mode == StartInfo::DUEL); - assert(color > 1); //we are NOT participants - //tlog0 << format("Server wants us to be %s in battle %s as side %d") % battleAIName % si.mapname % (int)color; gs = new CGameState(); const_cast(CGI)->state = gs; @@ -463,7 +473,8 @@ void CClient::newDuel(CConnection *con, StartInfo *si) p->init(new CCallback(gs, -1, this)); battleStarted(gs->curB); - serv->addStdVecItems(const_cast(CGI)->state); + if(serv) + serv->addStdVecItems(const_cast(CGI)->state); } template @@ -669,6 +680,79 @@ void CClient::invalidatePaths(const CGHeroInstance *h /*= NULL*/) pathInfo->isValid = false; } +std::string typeName(CPack * pack) +{ + try + { + return typeid(*pack).name(); + } + catch(...) + { + return "unknown type"; + //tlog1 << "\Unknown type!\t" << e.what() << std::endl; + } +} + +void CClient::runReplay(CLoadFile *f) +{ + setThreadName(-1, "CClient::runReplay"); + try + { + f->sfile->exceptions(std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit); + int i = 0; + std::vector hlp; + while(f->sfile) + { + i = hlp.size(); + tlog5 << i; + ui8 magic; + std::pair para; + para.first = 254; + para.second = NULL; + + try + { + *f >> para >> magic; + } + catch(...) + { + break; + } + if(magic != '*') + throw std::runtime_error("Bad magic byte!"); + assert(para.second); + + if(para.first != 255) + { + tlog5 << "\tIgnoring message from player " << (int)para.first + << " (" << typeName(para.second) << ")" << std::endl; + } + else + { + tlog5 << "\tRead message of type " << typeName(para.second) << std::endl; + hlp.push_back(para.second); + } + } + + i = 0; + while(!terminate && i < hlp.size()) + { + tlog1 << i << "\tHandling pack of type " << typeName(hlp[i]) << std::endl; + handlePack(hlp[i++]); + } + } + catch (const std::exception& e) + { + tlog3 << "Failure when replaying from file, ending reading thread!\n"; + tlog1 << e.what() << std::endl; + if(!terminate) //rethrow (-> boom!) only if closing connection was unexpected + { + tlog1 << "Something wrong, failed reading while game is still ongoing...\n"; + throw; + } + } +} + template void CClient::serialize( CISer &h, const int version ); template void CClient::serialize( COSer &h, const int version ); diff --git a/client/Client.h b/client/Client.h index e5046fa12..b9d846a44 100644 --- a/client/Client.h +++ b/client/Client.h @@ -31,6 +31,7 @@ class CClient; class CScriptingModule; struct CPathsInfo; namespace boost { class thread; } +class CLoadFile; void processCommand(const std::string &message, CClient *&client); @@ -96,6 +97,7 @@ public: void save(const std::string & fname); void loadGame(const std::string & fname); void run(); + void runReplay(CLoadFile *f); void finishCampaign( CCampaignState * camp ); void proposeNextMission( CCampaignState * camp ); void invalidatePaths(const CGHeroInstance *h = NULL); //invalidates paths for hero h or for any hero if h is NULL => they'll got recalculated when the next query comes diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index a0c2653e0..13bf835ca 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -655,7 +655,7 @@ void BattleResultsApplied::applyCl( CClient *cl ) INTERFACE_CALL_IF_PRESENT(player1, battleResultsApplied); INTERFACE_CALL_IF_PRESENT(player2, battleResultsApplied); INTERFACE_CALL_IF_PRESENT(254, battleResultsApplied); - if(GS(cl)->initialOpts->mode == StartInfo::DUEL) + if(GS(cl)->initialOpts->mode == StartInfo::DUEL && cl->serv) { cl->terminate = true; CloseServer cs; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 0ecce664b..e9d7925d3 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -471,6 +471,10 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer casualtiesPoints = c->AIValue * i->second; } tlog0 << boost::format("Total casualties points: %d\n") % casualtiesPoints; + + //battle ai1 ai2 winner_side winner_casualties + 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; } sendAndApply(&resultsApplied); @@ -640,6 +644,7 @@ void CGameHandler::handleConnection(std::set players, CConnection &c) while(1)//server should never shut connection first //was: while(!end2) { pack = c.retreivePack(); + receivedPack(c.connectionID, pack); int packType = typeList.getTypeID(pack); //get the id of type if(packType == typeList.getTypeID()) { @@ -1929,6 +1934,7 @@ void CGameHandler::ask( Query * sel, ui8 player, const CFunctionList void CGameHandler::sendToAllClients( CPackForClient * info ) { + broadcastedPack(info); tlog5 << "Sending to all clients a package of type " << typeid(*info).name() << std::endl; for(std::set::iterator i=conns.begin(); i!=conns.end();i++) { @@ -5247,6 +5253,25 @@ void CGameHandler::spawnWanderingMonsters(int creatureID) } } +static boost::mutex logMx; +void CGameHandler::receivedPack(ui8 connectionNr, CPack *pack) +{ + if(gameLog) + { + boost::unique_lock lock(logMx); + *gameLog << connectionNr << pack << ui8('*'); + } +} + +void CGameHandler::broadcastedPack(CPack *pack) +{ + if(gameLog) + { + boost::unique_lock lock(logMx); + *gameLog << ui8(255) << pack << ui8('*'); + } +} + CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance *army, BattleInfo *bat) { int color = army->tempOwner; diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 7c4d52089..2123993e5 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -103,6 +103,11 @@ public: std::map > garrisonCallbacks; //query id => callback - for garrison dialogs std::map > allowedExchanges; + std::string ais[2]; + CSaveFile *gameLog; + void receivedPack(ui8 connectionNr, CPack *pack); + void broadcastedPack(CPack *pack); + bool isAllowedExchange(int id1, int id2); bool isAllowedArrangePack(const ArrangeStacks *pack); void giveSpells(const CGTownInstance *t, const CGHeroInstance *h); diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 1643d7f8f..0987c0610 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -534,11 +534,14 @@ void CVCMIServer::startDuel(const std::string &battle, const std::string &leftAI tlog0 << "Preparing gh!\n"; CGameHandler *gh = new CGameHandler(); gh->init(&si,std::time(NULL)); + gh->ais[0] = leftAI; + gh->ais[1] = rightAI; BOOST_FOREACH(CConnection *c, conns) { ui8 player = gh->conns.size(); tlog0 << boost::format("Preparing connection %d!\n") % (int)player; + c->connectionID = player; c->addStdVecItems(gh->gs, VLC); gh->connections[player] = c; gh->conns.insert(c); @@ -555,18 +558,24 @@ void CVCMIServer::startDuel(const std::string &battle, const std::string &leftAI *gh->connections[1] << rightAI << ui8(1); *gh->connections[2] << std::string() << ui8(254); + std::string logFName = "duel_log.vdat"; + tlog0 << "Logging battle activities (for replay possibility) in " << logFName << std::endl; + gh->gameLog = new CSaveFile(logFName); + gh->gameLog->smartPointerSerialization = false; + *gh->gameLog << battle << leftAI << rightAI << ui8('$'); tlog0 << "Starting battle!\n"; gh->runBattle(); tlog0 << "Battle over!\n"; - delNull(gh); - tlog0 << "Removed gh!\n"; tlog0 << "Waiting for connections to close\n"; BOOST_FOREACH(boost::thread *t, threads) { t->join(); delNull(t); } + tlog0 << "Removing gh\n"; + delNull(gh); + tlog0 << "Removed gh!\n"; tlog0 << "Dying...\n"; exit(0);