diff --git a/AI/VCAI/FuzzyEngines.h b/AI/VCAI/FuzzyEngines.h index 95a58c482..483d0e06c 100644 --- a/AI/VCAI/FuzzyEngines.h +++ b/AI/VCAI/FuzzyEngines.h @@ -13,7 +13,7 @@ class CArmedInstance; -class engineBase //subclasses create fuzzylite variables with "new" that are not freed - this is desired as fl::Engine wants to destroy these... +class DLL_EXPORT engineBase //subclasses create fuzzylite variables with "new" that are not freed - this is desired as fl::Engine wants to destroy these... { protected: fl::Engine engine; @@ -24,7 +24,7 @@ public: engineBase(); }; -class TacticalAdvantageEngine : public engineBase //TODO: rework this engine, it does not work well (example: AI hero with 140 beholders attacked 150 beholders - engine lowered danger 50000 -> 35000) +class DLL_EXPORT TacticalAdvantageEngine : public engineBase //TODO: rework this engine, it does not work well (example: AI hero with 140 beholders attacked 150 beholders - engine lowered danger 50000 -> 35000) { public: TacticalAdvantageEngine(); @@ -37,7 +37,7 @@ private: fl::OutputVariable * threat; }; -class HeroMovementGoalEngineBase : public engineBase //in future - maybe derive from some (GoalEngineBase : public engineBase) class for handling non-movement goals with common utility for goal engines +class DLL_EXPORT HeroMovementGoalEngineBase : public engineBase //in future - maybe derive from some (GoalEngineBase : public engineBase) class for handling non-movement goals with common utility for goal engines { public: HeroMovementGoalEngineBase(); @@ -55,14 +55,14 @@ private: float calculateTurnDistanceInputValue(const Goals::AbstractGoal & goal) const; }; -class VisitTileEngine : public HeroMovementGoalEngineBase +class DLL_EXPORT VisitTileEngine : public HeroMovementGoalEngineBase { public: VisitTileEngine(); float evaluate(Goals::VisitTile & goal); }; -class VisitObjEngine : public HeroMovementGoalEngineBase +class DLL_EXPORT VisitObjEngine : public HeroMovementGoalEngineBase { public: VisitObjEngine(); diff --git a/scripting/erm/ERMInterpreter.cpp b/scripting/erm/ERMInterpreter.cpp index a1cf90bc8..340bcc80d 100644 --- a/scripting/erm/ERMInterpreter.cpp +++ b/scripting/erm/ERMInterpreter.cpp @@ -774,10 +774,48 @@ namespace ERMConverter putLine(fmt.str()); } break; + case 'U': + { + if(!trig.params.is_initialized() || trig.params.get().size() != 1) + throw EScriptExecError("VR:H/U need 1 parameter!"); + + std::string opt = boost::apply_visitor(VR_S(), trig.params.get()[0]); + boost::format fmt("ERM.VR(%s):%c(%s)"); + fmt % v.str() % (trig.optionCode) % opt; + putLine(fmt.str()); + } + break; case 'M': //string operations { - //TODO - putLine("--VR:M not implemented"); + if(!trig.params.is_initialized() || trig.params.get().size() < 2) + throw EScriptExecError("VR:M needs at least 2 parameters!"); + + std::string opt = boost::apply_visitor(VR_X(), trig.params.get()[0]); + int paramIndex = 1; + + if(opt == "3") + { + boost::format fmt("%s = ERM.VR(%s):M3("); + fmt % v.str() % v.str(); + put(fmt.str()); + } + else + { + auto target = boost::apply_visitor(VR_X(), trig.params.get()[paramIndex++]); + + boost::format fmt("%s = ERM.VR(%s):M%s("); + fmt % target % v.str() % opt; + put(fmt.str()); + } + + for(int i = paramIndex; i < trig.params.get().size(); i++) + { + opt = boost::apply_visitor(VR_X(), trig.params.get()[i]); + if(i > paramIndex) put(","); + put(opt); + } + + putLine(")"); } break; case 'X': //bit xor @@ -788,8 +826,7 @@ namespace ERMConverter std::string opt = boost::apply_visitor(VR_X(), trig.params.get()[0]); boost::format fmt("%s = bit.bxor(%s, %s)"); - fmt % v.str() % v.str() % opt; - putLine(fmt.str()); + fmt % v.str() % v.str() % opt;putLine(fmt.str()); } break; case 'R': //random variables @@ -816,16 +853,15 @@ namespace ERMConverter putLine("--VR:T not implemented"); } break; - case 'U': //search for a substring - { - //TODO - putLine("--VR:U not implemented"); - } - break; case 'V': //convert string to value { - //TODO - putLine("--VR:V not implemented"); + if(!trig.params.is_initialized() || trig.params.get().size() != 1) + throw EScriptExecError("VR:V option takes exactly 1 parameter!"); + + std::string opt = boost::apply_visitor(VR_X(), trig.params.get()[0]); + boost::format fmt("%s = tostring(%s)"); + fmt % v.str() % opt; + putLine(fmt.str()); } break; default: diff --git a/scripting/lua/StdInc.h b/scripting/lua/StdInc.h index 6d4d7523d..494217b40 100644 --- a/scripting/lua/StdInc.h +++ b/scripting/lua/StdInc.h @@ -2,7 +2,7 @@ #include "../../Global.h" -#include +#include // This header should be treated as a pre compiled header file(PCH) in the compiler building settings. diff --git a/scripts/lib/erm/VR.lua b/scripts/lib/erm/VR.lua index 8ed5ca7d2..dfe7d1f38 100644 --- a/scripts/lib/erm/VR.lua +++ b/scripts/lib/erm/VR.lua @@ -18,5 +18,61 @@ function VR:H(flagIndex) self.ERM.F[flagIndex] = v ~= '' end +function VR:U(subString) + self.ERM.F['1'] = string.find(self.v, subString) > 0 +end + +function VR:M1(startIndex, length) + return string.sub(self.v, startIndex - 1, startIndex - 1 + length) +end + +function VR:M2(wordIndex) + local words = string.gmatch(self.v, "[^%s]+") + local i = 0 + + for w in words do + if i == wordIndex then + return w + end + i = i + 1 + end +end + +function VR:M3(val, radix) + radix = radix or 10 + + if(type(val) ~= "number") then + error("The first parameter should be of numeric type") + end + + if(type(radix) ~= "number") then + error("The second parameter should be of numeric type. Default value is 10.") + end + + if radix == 10 then + return tostring(val) + elseif radix == 16 then + return string.format("%x", val) + else + error("The second parameter value is invalid. Only 10 and 16 radix are supported for now.") + end +end + +function VR:M4() + return string.len(self.v) +end + +function VR:M5() + local firstPos = string.find(str, "[^%s]+") + + return firstPos +end + +function VR:M6() + local lastPos = 1 + string.len(self.v) - string.find(string.reverse(self.v), "[^%s]+") + + return lastPos +end + return VR diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7d3a4c18d..c5e3b9af3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -153,11 +153,30 @@ set(mock_HEADERS vcai/mock_VCAI_CGoal.h ) +if(NOT FORCE_BUNDLED_FL) + find_package(FuzzyLite) +else() + set(FL_FOUND FALSE) +endif() + +if(NOT FL_FOUND) + set(FL_BUILD_BINARY OFF CACHE BOOL "") + set(FL_BUILD_SHARED OFF CACHE BOOL "") + set(FL_BUILD_TESTS OFF CACHE BOOL "") + add_subdirectory(AI/FuzzyLite/fuzzylite EXCLUDE_FROM_ALL) +endif() + add_subdirectory_with_folder("3rdparty" googletest EXCLUDE_FROM_ALL) add_executable(vcmitest ${test_SRCS} ${test_HEADERS} ${mock_HEADERS}) target_link_libraries(vcmitest PRIVATE gtest gmock vcmi ${SYSTEM_LIBS} VCAI) +if(FL_FOUND) + target_link_libraries(vcmitest PRIVATE ${FL_LIBRARIES}) +else() + target_link_libraries(vcmitest PRIVATE fl-static) +endif() + target_include_directories(vcmitest PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} PRIVATE ${GTestSrc} diff --git a/test/erm/ERM_VR.cpp b/test/erm/ERM_VR.cpp index 6a230c4a2..f61223cbf 100644 --- a/test/erm/ERM_VR.cpp +++ b/test/erm/ERM_VR.cpp @@ -73,6 +73,24 @@ TEST_F(ERM_VR, H) EXPECT_EQ(f["202"], JsonUtils::boolNode(false)) << actualState.toJson(true); } +TEST_F(ERM_VR, U) +{ + std::stringstream source; + source << "VERM" << std::endl; + source << "!?PI;" << std::endl; + source << "!!VRz100:S^Test!^;" << std::endl; + source << "!!VRz101:S^est^;" << std::endl; + source << "!!VRz100:Uz101;" << std::endl; + + JsonNode actualState = runScript(VLC->scriptHandler->erm, source.str()); + + SCOPED_TRACE("\n" + subject->code); + + const JsonNode & f = actualState["ERM"]["F"]; + + EXPECT_EQ(f["1"], JsonUtils::boolNode(true)) << actualState.toJson(true); +} + } } diff --git a/test/erm/interpretter/ERM_VR.cpp b/test/erm/interpretter/ERM_VR.cpp index 7841d860d..900c0cf2c 100644 --- a/test/erm/interpretter/ERM_VR.cpp +++ b/test/erm/interpretter/ERM_VR.cpp @@ -73,13 +73,21 @@ TEST(ERM_VR_H, CheckEmptyString_ShouldGenerateCheckAndSetStatement) ASSERT_EQ(lua.lines.size(), 9) << lua.text; EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "ERM.VR(z['101']):H('302')"); } +/* should it work? +TEST(ERM_VR_H, CheckEmptyStringWithFlagIndexInVariable_ShouldGenerateCheckAndSetStatement) +{ + LuaStrings lua = ErmRunner::convertErmToLua({"!#VRz101:Hy1;"}); + ASSERT_EQ(lua.lines.size(), 9) << lua.text; + EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "ERM.VR(z['101']):H(y['1'])"); +} +*/ TEST(ERM_VR_M1, AnyString_ShouldGenerateSubstringAndSetStatement) { LuaStrings lua = ErmRunner::convertErmToLua({"!#VRz101:M1/z102/2/5;"}); ASSERT_EQ(lua.lines.size(), 9) << lua.text; - EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "--VR:M not implemented"); + EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "z['102'] = ERM.VR(z['101']):M1(2,5)"); } TEST(ERM_VR_M1, WithVariables_ShouldGenerateSubstringAndSetStatement) @@ -87,7 +95,7 @@ TEST(ERM_VR_M1, WithVariables_ShouldGenerateSubstringAndSetStatement) LuaStrings lua = ErmRunner::convertErmToLua({"!#VRz101:M1/z102/y1/y2;"}); ASSERT_EQ(lua.lines.size(), 9) << lua.text; - EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "--VR:M not implemented"); + EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "z['102'] = ERM.VR(z['101']):M1(y['1'],y['2'])"); } TEST(ERM_VR_M2, AnyString_ShouldGenerateWordSplitAndSetStatement) @@ -95,7 +103,7 @@ TEST(ERM_VR_M2, AnyString_ShouldGenerateWordSplitAndSetStatement) LuaStrings lua = ErmRunner::convertErmToLua({"!#VRz101:M2/z102/2;"}); ASSERT_EQ(lua.lines.size(), 9) << lua.text; - EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "--VR:M not implemented"); + EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "z['102'] = ERM.VR(z['101']):M2(2)"); } TEST(ERM_VR_M3, AnyInteger_ShouldGenerateIntToStringConversionAndSetStatement) @@ -103,7 +111,7 @@ TEST(ERM_VR_M3, AnyInteger_ShouldGenerateIntToStringConversionAndSetStatement) LuaStrings lua = ErmRunner::convertErmToLua({"!#VRz101:M3/102/16;"}); ASSERT_EQ(lua.lines.size(), 9) << lua.text; - EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "--VR:M not implemented"); + EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "z['101'] = ERM.VR(z['101']):M3(102,16)"); } //V TEST(ERM_VR_M3, IntegerVariable_ShouldGenerateIntToStringConversionAndSetStatement) @@ -111,7 +119,7 @@ TEST(ERM_VR_M3, IntegerVariable_ShouldGenerateIntToStringConversionAndSetStateme LuaStrings lua = ErmRunner::convertErmToLua({"!#VRz101:M3/v1/10;"}); ASSERT_EQ(lua.lines.size(), 9) << lua.text; - EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "--VR:M not implemented"); + EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "z['101'] = ERM.VR(z['101']):M3(v['1'],10)"); } TEST(ERM_VR_M4, AnyString_ShouldGenerateStringLengthAndSetStatement) @@ -119,7 +127,7 @@ TEST(ERM_VR_M4, AnyString_ShouldGenerateStringLengthAndSetStatement) LuaStrings lua = ErmRunner::convertErmToLua({"!#VRz101:M4/v2;"}); ASSERT_EQ(lua.lines.size(), 9) << lua.text; - EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "--VR:M not implemented"); + EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "v['2'] = ERM.VR(z['101']):M4()"); } TEST(ERM_VR_M5, AnyString_ShouldGenerateFindFirstNonSpaceCharPositionAndSetStatement) @@ -127,7 +135,7 @@ TEST(ERM_VR_M5, AnyString_ShouldGenerateFindFirstNonSpaceCharPositionAndSetState LuaStrings lua = ErmRunner::convertErmToLua({"!#VRz101:M5/v2;"}); ASSERT_EQ(lua.lines.size(), 9) << lua.text; - EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "--VR:M not implemented"); + EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "v['2'] = ERM.VR(z['101']):M5()"); } TEST(ERM_VR_M6, AnyString_ShouldGenerateFindLastNonSpaceCharPositionAndSetStatement) @@ -135,7 +143,7 @@ TEST(ERM_VR_M6, AnyString_ShouldGenerateFindLastNonSpaceCharPositionAndSetStatem LuaStrings lua = ErmRunner::convertErmToLua({"!#VRz101:M6/v2;"}); ASSERT_EQ(lua.lines.size(), 9) << lua.text; - EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "--VR:M not implemented"); + EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "v['2'] = ERM.VR(z['101']):M6()"); } TEST(ERM_VR_R, AnyVariable_ShouldGenerateRngAndSetStatement) @@ -174,8 +182,8 @@ TEST(ERM_VR_U, StringVariable_ShouldGenerateSubstringFindAndSetStatement) { LuaStrings lua = ErmRunner::convertErmToLua({"!#VRz2:Uz3;"}); - ASSERT_EQ(lua.lines.size(), 9) << lua.text; - EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "--VR:U not implemented"); + ASSERT_EQ(lua.lines.size(), 9) << lua.lines[0]; + EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "ERM.VR(z['2']):U(z['3'])"); } TEST(ERM_VR_U, StringConstant_ShouldGenerateSubstringFindAndSetStatement) @@ -183,7 +191,7 @@ TEST(ERM_VR_U, StringConstant_ShouldGenerateSubstringFindAndSetStatement) LuaStrings lua = ErmRunner::convertErmToLua({"!#VRz2:U^teest^;"}); ASSERT_EQ(lua.lines.size(), 9) << lua.text; - EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "--VR:U not implemented"); + EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "ERM.VR(z['2']):U([===[teest]===])"); } TEST(ERM_VR_BIT, LogicalAndOperator_ShouldGenerateSetStatement) @@ -234,7 +242,7 @@ TEST(ERM_VR_MINUS, MinusOperator_ShouldGenerateSetStatement) EXPECT_EQ(lua.lines[ErmRunner::REGULAR_INSTRUCTION_FIRST_LINE], "v['1'] = v['1'] - v['2']"); } -TEST(ERM_VR_MULT, NultiplicationOperator_ShouldGenerateSetStatement) +TEST(ERM_VR_MULT, MultiplicationOperator_ShouldGenerateSetStatement) { LuaStrings lua = ErmRunner::convertErmToLua({"!#VRv1:*vy2;"});