Browse Source

Logging battle activites. Replaying battles with client.
Added an AI for answer validation (it returns junk action packets).
Minor fixes.

Michał W. Urbańczyk 14 years ago
parent
commit
94e7fa5b3c

+ 163 - 0
AI/MadAI/MadAI.vcxproj

@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="RD|Win32">
+      <Configuration>RD</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="RD|x64">
+      <Configuration>RD</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}</ProjectGuid>
+    <RootNamespace>StupidAI</RootNamespace>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\..\VCMI_global.props" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\..\VCMI_global.props" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\..\VCMI_global.props" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\..\VCMI_global.props" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <OutDir>$(SolutionDir)\AI\</OutDir>
+    <IncludePath>$(IncludePath)</IncludePath>
+    <LibraryPath>$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <OutDir>$(SolutionDir)\AI\</OutDir>
+    <IncludePath>$(IncludePath)</IncludePath>
+    <LibraryPath>$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">
+    <OutDir>$(SolutionDir)$(Configuration)\bin\AI\</OutDir>
+    <IncludePath>$(IncludePath)</IncludePath>
+    <LibraryPath>$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'">
+    <OutDir>$(SolutionDir)$(Configuration)\bin\AI\</OutDir>
+    <IncludePath>$(IncludePath)</IncludePath>
+    <LibraryPath>$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile>
+      <MinimalRebuild>false</MinimalRebuild>
+    </ClCompile>
+    <Link>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>$(OutDir)..;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <OutputFile>$(OutDir)$(TargetName).dll</OutputFile>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile>
+    </ClCompile>
+    <Link>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>$(OutDir)..;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <OutputFile>$(OutDir)StupidAI.dll</OutputFile>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile>
+    </ClCompile>
+    <Link>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>$(OutDir)..;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <OutputFile>$(OutDir)StupidAI.dll</OutputFile>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile>
+    </ClCompile>
+    <Link>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>$(OutDir)..;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <OutputFile>$(OutDir)StupidAI.dll</OutputFile>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClCompile Include="main.cpp" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>

+ 43 - 0
AI/MadAI/main.cpp

@@ -0,0 +1,43 @@
+#include <cstring>
+#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;
+}

+ 0 - 11
AI/StupidAI/main.cpp

@@ -8,22 +8,11 @@
 
 
 const char *g_cszAiName = "Stupid AI 0.1";
 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)
 extern "C" DLL_F_EXPORT void GetAiName(char* name)
 {
 {
 	strcpy_s(name, strlen(g_cszAiName) + 1, g_cszAiName);
 	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()
 extern "C" DLL_F_EXPORT CBattleGameInterface* GetNewBattleAI()
 {
 {
 	return new CStupidAI();
 	return new CStupidAI();

+ 4 - 2
Odpalarka/main.cpp

@@ -17,8 +17,10 @@ int main(int argc, const char **)
 #else
 #else
 		"./vcmiserver"
 		"./vcmiserver"
 #endif
 #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 tt(boost::bind(std::system, runnername.c_str()));
 	boost::thread ttt(boost::bind(std::system, runnername.c_str()));
 	boost::thread ttt(boost::bind(std::system, runnername.c_str()));
 	if(argc == 2)
 	if(argc == 2)

+ 1 - 1
StartInfo.h

@@ -53,7 +53,7 @@ struct PlayerSettings
 /// Struct which describes the difficulty, the turn time,.. of a heroes match.
 /// Struct which describes the difficulty, the turn time,.. of a heroes match.
 struct StartInfo
 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 mode; //uses EMode enum
 	ui8 difficulty; //0=easy; 4=impossible
 	ui8 difficulty; //0=easy; 4=impossible

+ 13 - 0
VCMI_VS10.sln

@@ -26,6 +26,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
 EndProject
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VCMI_client", "client\VCMI_client.vcxproj", "{8355EBA8-65C2-44A4-BC2D-78053E1BF2D6}"
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VCMI_client", "client\VCMI_client.vcxproj", "{8355EBA8-65C2-44A4-BC2D-78053E1BF2D6}"
 EndProject
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MadAI", "AI\MadAI\MadAI.vcxproj", "{DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}"
+EndProject
 Global
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Win32 = Debug|Win32
 		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|Win32.ActiveCfg = RD|x64
 		{8355EBA8-65C2-44A4-BC2D-78053E1BF2D6}.Release|x64.ActiveCfg = RD|x64
 		{8355EBA8-65C2-44A4-BC2D-78053E1BF2D6}.Release|x64.ActiveCfg = RD|x64
 		{8355EBA8-65C2-44A4-BC2D-78053E1BF2D6}.Release|x64.Build.0 = 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
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 		HideSolutionNode = FALSE

+ 30 - 3
client/CMT.cpp

@@ -185,6 +185,7 @@ static void prog_help(const char *progname)
 	printf("  -v, --version     display version information and exit\n");
 	printf("  -v, --version     display version information and exit\n");
 }
 }
 
 
+CLoadFile *replayLoader;
 
 
 #ifdef _WIN32
 #ifdef _WIN32
 int _tmain(int argc, _TCHAR* argv[])
 int _tmain(int argc, _TCHAR* argv[])
@@ -197,7 +198,8 @@ int main(int argc, char** argv)
 	opts.add_options()
 	opts.add_options()
 		("help,h", "display help and exit")
 		("help,h", "display help and exit")
 		("version,v", "display version information and exit")
 		("version,v", "display version information and exit")
-		("battle,b", po::value<std::string>(), "runs game in duel mode (battle-only")
+		("battle,b", po::value<std::string>(), "runs game in duel mode (battle-only)")
+		("replay,r", "replays a recorded battle, use together with -b");
 		("nointro,i", "skips intro movies");
 		("nointro,i", "skips intro movies");
 
 
 	po::variables_map vm;
 	po::variables_map vm;
@@ -284,7 +286,27 @@ int main(int argc, char** argv)
 	else
 	else
 	{
 	{
 		StartInfo *si = new StartInfo();
 		StartInfo *si = new StartInfo();
-		si->mode = StartInfo::DUEL;
+		if(vm.count("replay"))
+		{
+			si->mode = StartInfo::DUEL_REPLAY;
+			replayLoader = new CLoadFile(vm["battle"].as<std::string>());
+			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 " <<ai1 << " and "  << ai2 << " on " << bname << std::endl;
+		}
+		else
+			si->mode = StartInfo::DUEL;
 		startGame(si);
 		startGame(si);
 	}
 	}
 	mainGUIThread = new boost::thread(&CGuiHandler::run, boost::ref(GH));
 	mainGUIThread = new boost::thread(&CGuiHandler::run, boost::ref(GH));
@@ -718,6 +740,7 @@ void startGame(StartInfo * options, CConnection *serv/* = NULL*/)
 		client->newGame(serv, options);
 		client->newGame(serv, options);
 		break;
 		break;
 	case StartInfo::DUEL:
 	case StartInfo::DUEL:
+	case StartInfo::DUEL_REPLAY:
 		client->newDuel(serv, options);
 		client->newDuel(serv, options);
 		break;
 		break;
 	case StartInfo::LOAD_GAME:
 	case StartInfo::LOAD_GAME:
@@ -727,7 +750,11 @@ void startGame(StartInfo * options, CConnection *serv/* = NULL*/)
 		break;
 		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()
 void requestChangingResolution()

+ 109 - 25
client/Client.cpp

@@ -39,6 +39,7 @@
 
 
 #define NOT_LIB
 #define NOT_LIB
 #include "../lib/RegisterTypes.cpp"
 #include "../lib/RegisterTypes.cpp"
+#include <fstream>
 
 
 extern std::string NAME;
 extern std::string NAME;
 namespace intpr = boost::interprocess;
 namespace intpr = boost::interprocess;
@@ -418,37 +419,46 @@ void CClient::newGame( CConnection *con, StartInfo *si )
 
 
 void CClient::newDuel(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
-			{
-				tlog0 << "Establishing connection...\n";
-				serv = new CConnection(host, port, "DLL host");
-			}
-			catch(...)
+			std::string host = "127.0.0.1";
+			std::string port = "3030";
+
+			int i = 3;
+			while(!serv)
 			{
 			{
-				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;
+		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;
+	}
 
 
 	gs = new CGameState();
 	gs = new CGameState();
 	const_cast<CGameInfo*>(CGI)->state = gs;
 	const_cast<CGameInfo*>(CGI)->state = gs;
@@ -463,7 +473,8 @@ void CClient::newDuel(CConnection *con, StartInfo *si)
 	p->init(new CCallback(gs, -1, this));
 	p->init(new CCallback(gs, -1, this));
 	battleStarted(gs->curB);
 	battleStarted(gs->curB);
 
 
-	serv->addStdVecItems(const_cast<CGameInfo*>(CGI)->state);
+	if(serv)
+		serv->addStdVecItems(const_cast<CGameInfo*>(CGI)->state);
 }
 }
 
 
 template <typename Handler>
 template <typename Handler>
@@ -669,6 +680,79 @@ void CClient::invalidatePaths(const CGHeroInstance *h /*= NULL*/)
 		pathInfo->isValid = false;
 		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<CPack *> hlp;
+		while(f->sfile)
+		{
+			i = hlp.size();
+			tlog5 << i;
+			ui8 magic;
+			std::pair<ui8, CPack *> 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<CLoadFile> &h, const int version );
 template void CClient::serialize( CISer<CLoadFile> &h, const int version );
 template void CClient::serialize( COSer<CSaveFile> &h, const int version );
 template void CClient::serialize( COSer<CSaveFile> &h, const int version );
 
 

+ 2 - 0
client/Client.h

@@ -31,6 +31,7 @@ class CClient;
 class CScriptingModule;
 class CScriptingModule;
 struct CPathsInfo;
 struct CPathsInfo;
 namespace boost { class thread; }
 namespace boost { class thread; }
+class CLoadFile;
 
 
 void processCommand(const std::string &message, CClient *&client);
 void processCommand(const std::string &message, CClient *&client);
 
 
@@ -96,6 +97,7 @@ public:
 	void save(const std::string & fname);
 	void save(const std::string & fname);
 	void loadGame(const std::string & fname);
 	void loadGame(const std::string & fname);
 	void run();
 	void run();
+	void runReplay(CLoadFile *f);
 	void finishCampaign( CCampaignState * camp );
 	void finishCampaign( CCampaignState * camp );
 	void proposeNextMission( 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
 	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

+ 1 - 1
client/NetPacksClient.cpp

@@ -655,7 +655,7 @@ void BattleResultsApplied::applyCl( CClient *cl )
 	INTERFACE_CALL_IF_PRESENT(player1, battleResultsApplied);
 	INTERFACE_CALL_IF_PRESENT(player1, battleResultsApplied);
 	INTERFACE_CALL_IF_PRESENT(player2, battleResultsApplied);
 	INTERFACE_CALL_IF_PRESENT(player2, battleResultsApplied);
 	INTERFACE_CALL_IF_PRESENT(254, 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;
 		cl->terminate = true;
 		CloseServer cs;
 		CloseServer cs;

+ 25 - 0
server/CGameHandler.cpp

@@ -471,6 +471,10 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 			casualtiesPoints = c->AIValue * i->second;
 			casualtiesPoints = c->AIValue * i->second;
 		}
 		}
 		tlog0 << boost::format("Total casualties points: %d\n") % casualtiesPoints;
 		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);
 	sendAndApply(&resultsApplied);
@@ -640,6 +644,7 @@ void CGameHandler::handleConnection(std::set<int> players, CConnection &c)
 		while(1)//server should never shut connection first //was: while(!end2)
 		while(1)//server should never shut connection first //was: while(!end2)
 		{
 		{
 			pack = c.retreivePack();
 			pack = c.retreivePack();
+			receivedPack(c.connectionID, pack);
 			int packType = typeList.getTypeID(pack); //get the id of type
 			int packType = typeList.getTypeID(pack); //get the id of type
 			if(packType == typeList.getTypeID<CloseServer>())
 			if(packType == typeList.getTypeID<CloseServer>())
 			{
 			{
@@ -1929,6 +1934,7 @@ void CGameHandler::ask( Query * sel, ui8 player, const CFunctionList<void(ui32)>
 
 
 void CGameHandler::sendToAllClients( CPackForClient * info )
 void CGameHandler::sendToAllClients( CPackForClient * info )
 {
 {
+	broadcastedPack(info);
 	tlog5 << "Sending to all clients a package of type " << typeid(*info).name() << std::endl;
 	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++)
 	for(std::set<CConnection*>::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<boost::mutex> lock(logMx);
+		*gameLog << connectionNr << pack << ui8('*');
+	}
+}
+
+void CGameHandler::broadcastedPack(CPack *pack)
+{
+	if(gameLog)
+	{
+		boost::unique_lock<boost::mutex> lock(logMx);
+		*gameLog << ui8(255) << pack << ui8('*');
+	}
+}
+
 CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance *army, BattleInfo *bat)
 CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance *army, BattleInfo *bat)
 {
 {
 	int color = army->tempOwner;
 	int color = army->tempOwner;

+ 5 - 0
server/CGameHandler.h

@@ -103,6 +103,11 @@ public:
 	std::map<ui32, boost::function<void()> > garrisonCallbacks; //query id => callback - for garrison dialogs
 	std::map<ui32, boost::function<void()> > garrisonCallbacks; //query id => callback - for garrison dialogs
 	std::map<ui32, std::pair<si32,si32> > allowedExchanges;
 	std::map<ui32, std::pair<si32,si32> > allowedExchanges;
 
 
+	std::string ais[2];
+	CSaveFile *gameLog;
+	void receivedPack(ui8 connectionNr, CPack *pack);
+	void broadcastedPack(CPack *pack);
+
 	bool isAllowedExchange(int id1, int id2);
 	bool isAllowedExchange(int id1, int id2);
 	bool isAllowedArrangePack(const ArrangeStacks *pack);
 	bool isAllowedArrangePack(const ArrangeStacks *pack);
 	void giveSpells(const CGTownInstance *t, const CGHeroInstance *h);
 	void giveSpells(const CGTownInstance *t, const CGHeroInstance *h);

+ 11 - 2
server/CVCMIServer.cpp

@@ -534,11 +534,14 @@ void CVCMIServer::startDuel(const std::string &battle, const std::string &leftAI
 	tlog0 << "Preparing gh!\n";
 	tlog0 << "Preparing gh!\n";
 	CGameHandler *gh = new CGameHandler();
 	CGameHandler *gh = new CGameHandler();
 	gh->init(&si,std::time(NULL));
 	gh->init(&si,std::time(NULL));
+	gh->ais[0] = leftAI;
+	gh->ais[1] = rightAI;
 
 
 	BOOST_FOREACH(CConnection *c, conns)
 	BOOST_FOREACH(CConnection *c, conns)
 	{
 	{
 		ui8 player = gh->conns.size();
 		ui8 player = gh->conns.size();
 		tlog0 << boost::format("Preparing connection %d!\n") % (int)player;
 		tlog0 << boost::format("Preparing connection %d!\n") % (int)player;
+		c->connectionID = player;
 		c->addStdVecItems(gh->gs, VLC);
 		c->addStdVecItems(gh->gs, VLC);
 		gh->connections[player] = c;
 		gh->connections[player] = c;
 		gh->conns.insert(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[1] << rightAI << ui8(1);
 	*gh->connections[2] << std::string() << ui8(254);
 	*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";
 	tlog0 << "Starting battle!\n";
 	gh->runBattle();
 	gh->runBattle();
 	tlog0 << "Battle over!\n";
 	tlog0 << "Battle over!\n";
-	delNull(gh);
-	tlog0 << "Removed gh!\n";
 	tlog0 << "Waiting for connections to close\n";
 	tlog0 << "Waiting for connections to close\n";
 	BOOST_FOREACH(boost::thread *t, threads)
 	BOOST_FOREACH(boost::thread *t, threads)
 	{
 	{
 		t->join();
 		t->join();
 		delNull(t);
 		delNull(t);
 	}
 	}
+	tlog0 << "Removing gh\n";
+	delNull(gh);
+	tlog0 << "Removed gh!\n";
 
 
 	tlog0 << "Dying...\n";
 	tlog0 << "Dying...\n";
 	exit(0);
 	exit(0);