From 69f27f003106fecd765885f2816e7dca6436d6f6 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 21 Sep 2023 17:15:57 +0200 Subject: [PATCH 1/7] Add motion blocks test --- test/blocks/CMakeLists.txt | 16 ++++++++++++ test/blocks/motion_blocks_test.cpp | 40 ++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 test/blocks/motion_blocks_test.cpp diff --git a/test/blocks/CMakeLists.txt b/test/blocks/CMakeLists.txt index 9fe74ae2..7c31bd50 100644 --- a/test/blocks/CMakeLists.txt +++ b/test/blocks/CMakeLists.txt @@ -109,3 +109,19 @@ target_link_libraries( ) gtest_discover_tests(sensing_blocks_test) + +# motion_blocks_test +add_executable( + motion_blocks_test + motion_blocks_test.cpp +) + +target_link_libraries( + motion_blocks_test + GTest::gtest_main + GTest::gmock_main + scratchcpp + scratchcpp_mocks +) + +gtest_discover_tests(motion_blocks_test) diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp new file mode 100644 index 00000000..5f38114a --- /dev/null +++ b/test/blocks/motion_blocks_test.cpp @@ -0,0 +1,40 @@ +#include +#include +#include + +#include "../common.h" +#include "blocks/motionblocks.h" +#include "engine/internal/engine.h" + +using namespace libscratchcpp; + +using ::testing::Return; + +class MotionBlocksTest : public testing::Test +{ + public: + void SetUp() override + { + m_section = std::make_unique(); + m_section->registerBlocks(&m_engine); + } + + std::unique_ptr m_section; + EngineMock m_engineMock; + Engine m_engine; +}; + +TEST_F(MotionBlocksTest, Name) +{ + ASSERT_EQ(m_section->name(), "Motion"); +} + +TEST_F(MotionBlocksTest, CategoryVisible) +{ + ASSERT_TRUE(m_section->categoryVisible()); +} + +TEST_F(MotionBlocksTest, RegisterBlocks) +{ + m_section->registerBlocks(&m_engineMock); +} From d3a2c9cae06e1206db7421fa1662571b04cc4583 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 21 Sep 2023 18:14:12 +0200 Subject: [PATCH 2/7] Implement motion_movesteps block --- src/blocks/motionblocks.cpp | 31 +++++++++++++++ src/blocks/motionblocks.h | 9 +++++ test/blocks/motion_blocks_test.cpp | 62 ++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index ce9ed794..82c21509 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -1,9 +1,15 @@ // SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + #include "motionblocks.h" using namespace libscratchcpp; +static const double pi = std::acos(-1); // TODO: Use std::numbers::pi in C++20 + std::string MotionBlocks::name() const { return "Motion"; @@ -11,4 +17,29 @@ std::string MotionBlocks::name() const void MotionBlocks::registerBlocks(IEngine *engine) { + // Blocks + engine->addCompileFunction(this, "motion_movesteps", &compileMoveSteps); + + // Inputs + engine->addInput(this, "STEPS", STEPS); +} + +void MotionBlocks::compileMoveSteps(Compiler *compiler) +{ + compiler->addInput(STEPS); + compiler->addFunctionCall(&moveSteps); +} + +unsigned int MotionBlocks::moveSteps(VirtualMachine *vm) +{ + Sprite *sprite = dynamic_cast(vm->target()); + + if (sprite) { + double dir = sprite->direction(); + double steps = vm->getInput(0, 1)->toDouble(); + sprite->setX(sprite->x() + std::sin(dir * pi / 180) * steps); + sprite->setY(sprite->y() + std::cos(dir * pi / 180) * steps); + } + + return 1; } diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index 225a088d..c40d5293 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -11,9 +11,18 @@ namespace libscratchcpp class MotionBlocks : public IBlockSection { public: + enum Inputs + { + STEPS + }; + std::string name() const override; void registerBlocks(IEngine *engine) override; + + static void compileMoveSteps(Compiler *compiler); + + static unsigned int moveSteps(VirtualMachine *vm); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index 5f38114a..79a6cc18 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include "../common.h" @@ -19,6 +21,18 @@ class MotionBlocksTest : public testing::Test m_section->registerBlocks(&m_engine); } + // For any motion block + std::shared_ptr createMotionBlock(const std::string &id, const std::string &opcode) const { return std::make_shared(id, opcode); } + + void addValueInput(std::shared_ptr block, const std::string &name, MotionBlocks::Inputs id, const Value &value) const + { + auto input = std::make_shared(name, Input::Type::Shadow); + input->setPrimaryValue(value); + input->setInputId(id); + block->addInput(input); + block->updateInputMap(); + } + std::unique_ptr m_section; EngineMock m_engineMock; Engine m_engine; @@ -36,5 +50,53 @@ TEST_F(MotionBlocksTest, CategoryVisible) TEST_F(MotionBlocksTest, RegisterBlocks) { + // Blocks + EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_movesteps", &MotionBlocks::compileMoveSteps)); + + // Inputs + EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS)); + m_section->registerBlocks(&m_engineMock); } + +TEST_F(MotionBlocksTest, MoveSteps) +{ + Compiler compiler(&m_engineMock); + + // move (30.25) steps + auto block = std::make_shared("a", "motion_movesteps"); + addValueInput(block, "STEPS", MotionBlocks::STEPS, 30.25); + + EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::moveSteps)).WillOnce(Return(0)); + + compiler.init(); + compiler.setBlock(block); + MotionBlocks::compileMoveSteps(&compiler); + compiler.end(); + + ASSERT_EQ(compiler.bytecode(), std::vector({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT })); + ASSERT_EQ(compiler.constValues().size(), 1); + ASSERT_EQ(compiler.constValues()[0].toDouble(), 30.25); +} + +TEST_F(MotionBlocksTest, MoveStepsImpl) +{ + static unsigned int bytecode[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT }; + static BlockFunc functions[] = { &MotionBlocks::moveSteps }; + static Value constValues[] = { 30.25 }; + + Sprite sprite; + sprite.setX(5.2); + sprite.setY(-0.25); + sprite.setDirection(-61.42); + + VirtualMachine vm(&sprite, nullptr, nullptr); + vm.setBytecode(bytecode); + vm.setFunctions(functions); + vm.setConstValues(constValues); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(std::round(sprite.x() * 100) / 100, -21.36); + ASSERT_EQ(std::round(sprite.y() * 100) / 100, 14.22); +} From cc8103384c3dea00245ad4bd60d14bea2122f1cf Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 21 Sep 2023 18:41:22 +0200 Subject: [PATCH 3/7] Implement motion_turnright block --- src/blocks/motionblocks.cpp | 18 +++++++++++++ src/blocks/motionblocks.h | 5 +++- test/blocks/motion_blocks_test.cpp | 41 ++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index 82c21509..7dc18dee 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -19,9 +19,11 @@ void MotionBlocks::registerBlocks(IEngine *engine) { // Blocks engine->addCompileFunction(this, "motion_movesteps", &compileMoveSteps); + engine->addCompileFunction(this, "motion_turnright", &compileTurnRight); // Inputs engine->addInput(this, "STEPS", STEPS); + engine->addInput(this, "DEGREES", DEGREES); } void MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -30,6 +32,12 @@ void MotionBlocks::compileMoveSteps(Compiler *compiler) compiler->addFunctionCall(&moveSteps); } +void MotionBlocks::compileTurnRight(Compiler *compiler) +{ + compiler->addInput(DEGREES); + compiler->addFunctionCall(&turnRight); +} + unsigned int MotionBlocks::moveSteps(VirtualMachine *vm) { Sprite *sprite = dynamic_cast(vm->target()); @@ -43,3 +51,13 @@ unsigned int MotionBlocks::moveSteps(VirtualMachine *vm) return 1; } + +unsigned int MotionBlocks::turnRight(VirtualMachine *vm) +{ + Sprite *sprite = dynamic_cast(vm->target()); + + if (sprite) + sprite->setDirection(sprite->direction() + vm->getInput(0, 1)->toDouble()); + + return 1; +} diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index c40d5293..c3b3116c 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -13,7 +13,8 @@ class MotionBlocks : public IBlockSection public: enum Inputs { - STEPS + STEPS, + DEGREES }; std::string name() const override; @@ -21,8 +22,10 @@ class MotionBlocks : public IBlockSection void registerBlocks(IEngine *engine) override; static void compileMoveSteps(Compiler *compiler); + static void compileTurnRight(Compiler *compiler); static unsigned int moveSteps(VirtualMachine *vm); + static unsigned int turnRight(VirtualMachine *vm); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index 79a6cc18..f3c3846f 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -52,9 +52,11 @@ TEST_F(MotionBlocksTest, RegisterBlocks) { // Blocks EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_movesteps", &MotionBlocks::compileMoveSteps)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_turnright", &MotionBlocks::compileTurnRight)); // Inputs EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS)); + EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DEGREES", MotionBlocks::DEGREES)); m_section->registerBlocks(&m_engineMock); } @@ -100,3 +102,42 @@ TEST_F(MotionBlocksTest, MoveStepsImpl) ASSERT_EQ(std::round(sprite.x() * 100) / 100, -21.36); ASSERT_EQ(std::round(sprite.y() * 100) / 100, 14.22); } + +TEST_F(MotionBlocksTest, TurnRight) +{ + Compiler compiler(&m_engineMock); + + // turn right (12.05) degrees + auto block = std::make_shared("a", "motion_turnright"); + addValueInput(block, "DEGREES", MotionBlocks::DEGREES, 12.05); + + EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::turnRight)).WillOnce(Return(0)); + + compiler.init(); + compiler.setBlock(block); + MotionBlocks::compileTurnRight(&compiler); + compiler.end(); + + ASSERT_EQ(compiler.bytecode(), std::vector({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT })); + ASSERT_EQ(compiler.constValues().size(), 1); + ASSERT_EQ(compiler.constValues()[0].toDouble(), 12.05); +} + +TEST_F(MotionBlocksTest, TurnRightImpl) +{ + static unsigned int bytecode[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT }; + static BlockFunc functions[] = { &MotionBlocks::turnRight }; + static Value constValues[] = { 12.05 }; + + Sprite sprite; + sprite.setDirection(124.37); + + VirtualMachine vm(&sprite, nullptr, nullptr); + vm.setBytecode(bytecode); + vm.setFunctions(functions); + vm.setConstValues(constValues); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(std::round(sprite.direction() * 100) / 100, 136.42); +} From 16fed38b4eb198f1b266fb56d2796bba7763d9c8 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 21 Sep 2023 18:48:01 +0200 Subject: [PATCH 4/7] Implement motion_turnleft block --- src/blocks/motionblocks.cpp | 17 +++++++++++++ src/blocks/motionblocks.h | 2 ++ test/blocks/motion_blocks_test.cpp | 40 ++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index 7dc18dee..a74a8cd0 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -20,6 +20,7 @@ void MotionBlocks::registerBlocks(IEngine *engine) // Blocks engine->addCompileFunction(this, "motion_movesteps", &compileMoveSteps); engine->addCompileFunction(this, "motion_turnright", &compileTurnRight); + engine->addCompileFunction(this, "motion_turnleft", &compileTurnLeft); // Inputs engine->addInput(this, "STEPS", STEPS); @@ -38,6 +39,12 @@ void MotionBlocks::compileTurnRight(Compiler *compiler) compiler->addFunctionCall(&turnRight); } +void MotionBlocks::compileTurnLeft(Compiler *compiler) +{ + compiler->addInput(DEGREES); + compiler->addFunctionCall(&turnLeft); +} + unsigned int MotionBlocks::moveSteps(VirtualMachine *vm) { Sprite *sprite = dynamic_cast(vm->target()); @@ -61,3 +68,13 @@ unsigned int MotionBlocks::turnRight(VirtualMachine *vm) return 1; } + +unsigned int MotionBlocks::turnLeft(VirtualMachine *vm) +{ + Sprite *sprite = dynamic_cast(vm->target()); + + if (sprite) + sprite->setDirection(sprite->direction() - vm->getInput(0, 1)->toDouble()); + + return 1; +} diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index c3b3116c..1ccd4904 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -23,9 +23,11 @@ class MotionBlocks : public IBlockSection static void compileMoveSteps(Compiler *compiler); static void compileTurnRight(Compiler *compiler); + static void compileTurnLeft(Compiler *compiler); static unsigned int moveSteps(VirtualMachine *vm); static unsigned int turnRight(VirtualMachine *vm); + static unsigned int turnLeft(VirtualMachine *vm); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index f3c3846f..415a9dc7 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -53,6 +53,7 @@ TEST_F(MotionBlocksTest, RegisterBlocks) // Blocks EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_movesteps", &MotionBlocks::compileMoveSteps)); EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_turnright", &MotionBlocks::compileTurnRight)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_turnleft", &MotionBlocks::compileTurnLeft)); // Inputs EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS)); @@ -141,3 +142,42 @@ TEST_F(MotionBlocksTest, TurnRightImpl) ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(std::round(sprite.direction() * 100) / 100, 136.42); } + +TEST_F(MotionBlocksTest, TurnLeft) +{ + Compiler compiler(&m_engineMock); + + // turn left (12.05) degrees + auto block = std::make_shared("a", "motion_turnleft"); + addValueInput(block, "DEGREES", MotionBlocks::DEGREES, 12.05); + + EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::turnLeft)).WillOnce(Return(0)); + + compiler.init(); + compiler.setBlock(block); + MotionBlocks::compileTurnLeft(&compiler); + compiler.end(); + + ASSERT_EQ(compiler.bytecode(), std::vector({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT })); + ASSERT_EQ(compiler.constValues().size(), 1); + ASSERT_EQ(compiler.constValues()[0].toDouble(), 12.05); +} + +TEST_F(MotionBlocksTest, TurnLeftImpl) +{ + static unsigned int bytecode[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT }; + static BlockFunc functions[] = { &MotionBlocks::turnLeft }; + static Value constValues[] = { 12.05 }; + + Sprite sprite; + sprite.setDirection(124.37); + + VirtualMachine vm(&sprite, nullptr, nullptr); + vm.setBytecode(bytecode); + vm.setFunctions(functions); + vm.setConstValues(constValues); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(std::round(sprite.direction() * 100) / 100, 112.32); +} From 80a651e4046dbd3945bf5e4f494ea10cd82ac98c Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 22 Sep 2023 23:04:40 +0200 Subject: [PATCH 5/7] Implement motion_pointindirection block --- src/blocks/motionblocks.cpp | 18 +++++++++++++ src/blocks/motionblocks.h | 5 +++- test/blocks/motion_blocks_test.cpp | 41 ++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index a74a8cd0..c3da42b6 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -21,10 +21,12 @@ void MotionBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "motion_movesteps", &compileMoveSteps); engine->addCompileFunction(this, "motion_turnright", &compileTurnRight); engine->addCompileFunction(this, "motion_turnleft", &compileTurnLeft); + engine->addCompileFunction(this, "motion_pointindirection", &compilePointInDirection); // Inputs engine->addInput(this, "STEPS", STEPS); engine->addInput(this, "DEGREES", DEGREES); + engine->addInput(this, "DIRECTION", DIRECTION); } void MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -45,6 +47,12 @@ void MotionBlocks::compileTurnLeft(Compiler *compiler) compiler->addFunctionCall(&turnLeft); } +void MotionBlocks::compilePointInDirection(Compiler *compiler) +{ + compiler->addInput(DIRECTION); + compiler->addFunctionCall(&pointInDirection); +} + unsigned int MotionBlocks::moveSteps(VirtualMachine *vm) { Sprite *sprite = dynamic_cast(vm->target()); @@ -78,3 +86,13 @@ unsigned int MotionBlocks::turnLeft(VirtualMachine *vm) return 1; } + +unsigned int MotionBlocks::pointInDirection(VirtualMachine *vm) +{ + Sprite *sprite = dynamic_cast(vm->target()); + + if (sprite) + sprite->setDirection(vm->getInput(0, 1)->toDouble()); + + return 1; +} diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index 1ccd4904..b756a963 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -14,7 +14,8 @@ class MotionBlocks : public IBlockSection enum Inputs { STEPS, - DEGREES + DEGREES, + DIRECTION }; std::string name() const override; @@ -24,10 +25,12 @@ class MotionBlocks : public IBlockSection static void compileMoveSteps(Compiler *compiler); static void compileTurnRight(Compiler *compiler); static void compileTurnLeft(Compiler *compiler); + static void compilePointInDirection(Compiler *compiler); static unsigned int moveSteps(VirtualMachine *vm); static unsigned int turnRight(VirtualMachine *vm); static unsigned int turnLeft(VirtualMachine *vm); + static unsigned int pointInDirection(VirtualMachine *vm); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index 415a9dc7..f3771703 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -54,10 +54,12 @@ TEST_F(MotionBlocksTest, RegisterBlocks) EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_movesteps", &MotionBlocks::compileMoveSteps)); EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_turnright", &MotionBlocks::compileTurnRight)); EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_turnleft", &MotionBlocks::compileTurnLeft)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_pointindirection", &MotionBlocks::compilePointInDirection)); // Inputs EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS)); EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DEGREES", MotionBlocks::DEGREES)); + EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DIRECTION", MotionBlocks::DIRECTION)); m_section->registerBlocks(&m_engineMock); } @@ -181,3 +183,42 @@ TEST_F(MotionBlocksTest, TurnLeftImpl) ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(std::round(sprite.direction() * 100) / 100, 112.32); } + +TEST_F(MotionBlocksTest, PointInDirection) +{ + Compiler compiler(&m_engineMock); + + // point in direction (-60.5) + auto block = std::make_shared("a", "motion_pointindirection"); + addValueInput(block, "DIRECTION", MotionBlocks::DIRECTION, -60.5); + + EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::pointInDirection)).WillOnce(Return(0)); + + compiler.init(); + compiler.setBlock(block); + MotionBlocks::compilePointInDirection(&compiler); + compiler.end(); + + ASSERT_EQ(compiler.bytecode(), std::vector({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT })); + ASSERT_EQ(compiler.constValues().size(), 1); + ASSERT_EQ(compiler.constValues()[0].toDouble(), -60.5); +} + +TEST_F(MotionBlocksTest, PointInDirectionImpl) +{ + static unsigned int bytecode[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT }; + static BlockFunc functions[] = { &MotionBlocks::pointInDirection }; + static Value constValues[] = { -60.5 }; + + Sprite sprite; + sprite.setDirection(50.02); + + VirtualMachine vm(&sprite, nullptr, nullptr); + vm.setBytecode(bytecode); + vm.setFunctions(functions); + vm.setConstValues(constValues); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(sprite.direction(), -60.5); +} From 734a172521e68c0540b457b4b5b65a8c33ed2733 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 23 Sep 2023 13:58:54 +0200 Subject: [PATCH 6/7] Implement motion_pointtowards block --- src/blocks/motionblocks.cpp | 107 +++++++++++++++ src/blocks/motionblocks.h | 24 +++- test/blocks/motion_blocks_test.cpp | 214 +++++++++++++++++++++++++++++ 3 files changed, 344 insertions(+), 1 deletion(-) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index c3da42b6..9b2325f5 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -3,13 +3,17 @@ #include #include #include +#include #include "motionblocks.h" +#include "../engine/internal/randomgenerator.h" using namespace libscratchcpp; static const double pi = std::acos(-1); // TODO: Use std::numbers::pi in C++20 +IRandomGenerator *MotionBlocks::rng = nullptr; + std::string MotionBlocks::name() const { return "Motion"; @@ -22,11 +26,13 @@ void MotionBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "motion_turnright", &compileTurnRight); engine->addCompileFunction(this, "motion_turnleft", &compileTurnLeft); engine->addCompileFunction(this, "motion_pointindirection", &compilePointInDirection); + engine->addCompileFunction(this, "motion_pointtowards", &compilePointTowards); // Inputs engine->addInput(this, "STEPS", STEPS); engine->addInput(this, "DEGREES", DEGREES); engine->addInput(this, "DIRECTION", DIRECTION); + engine->addInput(this, "TOWARDS", TOWARDS); } void MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -53,6 +59,29 @@ void MotionBlocks::compilePointInDirection(Compiler *compiler) compiler->addFunctionCall(&pointInDirection); } +void MotionBlocks::compilePointTowards(Compiler *compiler) +{ + Input *input = compiler->input(TOWARDS); + + if (input->type() != Input::Type::ObscuredShadow) { + assert(input->pointsToDropdownMenu()); + std::string value = input->selectedMenuItem(); + + if (value == "_mouse_") + compiler->addFunctionCall(&pointTowardsMousePointer); + else if (value == "_random_") + compiler->addFunctionCall(&pointTowardsRandomPosition); + else { + int index = compiler->engine()->findTarget(value); + compiler->addConstValue(index); + compiler->addFunctionCall(&pointTowardsByIndex); + } + } else { + compiler->addInput(input); + compiler->addFunctionCall(&pointTowards); + } +} + unsigned int MotionBlocks::moveSteps(VirtualMachine *vm) { Sprite *sprite = dynamic_cast(vm->target()); @@ -96,3 +125,81 @@ unsigned int MotionBlocks::pointInDirection(VirtualMachine *vm) return 1; } + +void MotionBlocks::pointTowardsPos(Sprite *sprite, double x, double y) +{ + if (!sprite) + return; + + // https://en.scratch-wiki.info/wiki/Point_Towards_()_(block)#Workaround + double deltaX = x - sprite->x(); + double deltaY = y - sprite->y(); + + if (deltaY == 0) { + if (deltaX < 0) + sprite->setDirection(-90); + else + sprite->setDirection(90); + } else if (deltaY < 0) + sprite->setDirection(180 + (180 / pi) * std::atan(deltaX / deltaY)); + else + sprite->setDirection((180 / pi) * std::atan(deltaX / deltaY)); +} + +unsigned int MotionBlocks::pointTowards(VirtualMachine *vm) +{ + std::string value = vm->getInput(0, 1)->toString(); + Target *target; + + if (value == "_mouse_") + pointTowardsPos(dynamic_cast(vm->target()), vm->engine()->mouseX(), vm->engine()->mouseY()); + else if (value == "_random_") { + // TODO: Read stage size from engine (#224) + static const unsigned int stageWidth = 480; + static const unsigned int stageHeight = 360; + + if (!rng) + rng = RandomGenerator::instance().get(); + + pointTowardsPos(dynamic_cast(vm->target()), rng->randint(-static_cast(stageWidth / 2), stageWidth / 2), rng->randint(-static_cast(stageHeight / 2), stageHeight / 2)); + } else { + target = vm->engine()->targetAt(vm->engine()->findTarget(value)); + Sprite *sprite = dynamic_cast(target); + + if (sprite) + pointTowardsPos(dynamic_cast(vm->target()), sprite->x(), sprite->y()); + } + + return 1; +} + +unsigned int MotionBlocks::pointTowardsByIndex(VirtualMachine *vm) +{ + Target *target = vm->engine()->targetAt(vm->getInput(0, 1)->toInt()); + Sprite *sprite = dynamic_cast(target); + + if (sprite) + pointTowardsPos(dynamic_cast(vm->target()), sprite->x(), sprite->y()); + + return 1; +} + +unsigned int MotionBlocks::pointTowardsMousePointer(VirtualMachine *vm) +{ + pointTowardsPos(dynamic_cast(vm->target()), vm->engine()->mouseX(), vm->engine()->mouseY()); + return 0; +} + +unsigned int MotionBlocks::pointTowardsRandomPosition(VirtualMachine *vm) +{ + // TODO: Read stage size from engine (#224) + static const unsigned int stageWidth = 480; + static const unsigned int stageHeight = 360; + + if (!rng) + rng = RandomGenerator::instance().get(); + + pointTowardsPos(dynamic_cast(vm->target()), rng->randint(-static_cast(stageWidth / 2), stageWidth / 2), rng->randint(-static_cast(stageHeight / 2), stageHeight / 2)); + + return 0; +} diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index b756a963..2630acf8 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -7,6 +7,9 @@ namespace libscratchcpp { +class Sprite; +class IRandomGenerator; + /*! \brief The MotionBlocks class contains the implementation of motion blocks. */ class MotionBlocks : public IBlockSection { @@ -15,7 +18,16 @@ class MotionBlocks : public IBlockSection { STEPS, DEGREES, - DIRECTION + DIRECTION, + TOWARDS + }; + + enum Fields + { + }; + + enum FieldValues + { }; std::string name() const override; @@ -26,11 +38,21 @@ class MotionBlocks : public IBlockSection static void compileTurnRight(Compiler *compiler); static void compileTurnLeft(Compiler *compiler); static void compilePointInDirection(Compiler *compiler); + static void compilePointTowards(Compiler *compiler); static unsigned int moveSteps(VirtualMachine *vm); static unsigned int turnRight(VirtualMachine *vm); static unsigned int turnLeft(VirtualMachine *vm); static unsigned int pointInDirection(VirtualMachine *vm); + + static void pointTowardsPos(Sprite *sprite, double x, double y); + + static unsigned int pointTowards(VirtualMachine *vm); + static unsigned int pointTowardsByIndex(VirtualMachine *vm); + static unsigned int pointTowardsMousePointer(VirtualMachine *vm); + static unsigned int pointTowardsRandomPosition(VirtualMachine *vm); + + static IRandomGenerator *rng; }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index f3771703..47bd7526 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -1,11 +1,14 @@ #include #include #include +#include #include #include +#include #include "../common.h" #include "blocks/motionblocks.h" +#include "blocks/operatorblocks.h" #include "engine/internal/engine.h" using namespace libscratchcpp; @@ -33,6 +36,46 @@ class MotionBlocksTest : public testing::Test block->updateInputMap(); } + void addObscuredInput(std::shared_ptr block, const std::string &name, MotionBlocks::Inputs id, std::shared_ptr valueBlock) const + { + auto input = std::make_shared(name, Input::Type::ObscuredShadow); + input->setValueBlock(valueBlock); + input->setInputId(id); + block->addInput(input); + block->updateInputMap(); + } + + std::shared_ptr addNullInput(std::shared_ptr block, const std::string &name, MotionBlocks::Inputs id) const + { + auto input = std::make_shared(name, Input::Type::Shadow); + input->setInputId(id); + block->addInput(input); + block->updateInputMap(); + + return input; + } + + void addDropdownInput(std::shared_ptr block, const std::string &name, MotionBlocks::Inputs id, const std::string &selectedValue, std::shared_ptr valueBlock = nullptr) const + { + if (valueBlock) + addObscuredInput(block, name, id, valueBlock); + else { + auto input = addNullInput(block, name, id); + auto menu = createMotionBlock(block->id() + "_menu", block->opcode() + "_menu"); + input->setValueBlock(menu); + addDropdownField(menu, "CLONE_OPTION", static_cast(-1), selectedValue, static_cast(-1)); + } + } + + void addDropdownField(std::shared_ptr block, const std::string &name, MotionBlocks::Fields id, const std::string &value, MotionBlocks::FieldValues valueId) const + { + auto field = std::make_shared(name, value); + field->setFieldId(id); + field->setSpecialValueId(valueId); + block->addField(field); + block->updateFieldMap(); + } + std::unique_ptr m_section; EngineMock m_engineMock; Engine m_engine; @@ -55,11 +98,13 @@ TEST_F(MotionBlocksTest, RegisterBlocks) EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_turnright", &MotionBlocks::compileTurnRight)); EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_turnleft", &MotionBlocks::compileTurnLeft)); EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_pointindirection", &MotionBlocks::compilePointInDirection)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_pointtowards", &MotionBlocks::compilePointTowards)); // Inputs EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS)); EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DEGREES", MotionBlocks::DEGREES)); EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DIRECTION", MotionBlocks::DIRECTION)); + EXPECT_CALL(m_engineMock, addInput(m_section.get(), "TOWARDS", MotionBlocks::TOWARDS)); m_section->registerBlocks(&m_engineMock); } @@ -222,3 +267,172 @@ TEST_F(MotionBlocksTest, PointInDirectionImpl) ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(sprite.direction(), -60.5); } + +TEST_F(MotionBlocksTest, PointTowards) +{ + Compiler compiler(&m_engineMock); + + // point towards (mouse-pointer) + auto block1 = std::make_shared("a", "motion_pointtowards"); + addDropdownInput(block1, "TOWARDS", MotionBlocks::TOWARDS, "_mouse_"); + + // point towards (random position) + auto block2 = std::make_shared("b", "motion_pointtowards"); + addDropdownInput(block2, "TOWARDS", MotionBlocks::TOWARDS, "_random_"); + + // point towards (Sprite2) + auto block3 = std::make_shared("c", "motion_pointtowards"); + addDropdownInput(block3, "TOWARDS", MotionBlocks::TOWARDS, "Sprite2"); + + // point towards (join "" "") + auto joinBlock = std::make_shared("e", "operator_join"); + joinBlock->setCompileFunction(&OperatorBlocks::compileJoin); + auto block4 = std::make_shared("d", "motion_pointtowards"); + addDropdownInput(block4, "TOWARDS", MotionBlocks::TOWARDS, "", joinBlock); + + compiler.init(); + + EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::pointTowardsMousePointer)).WillOnce(Return(0)); + compiler.setBlock(block1); + MotionBlocks::compilePointTowards(&compiler); + + EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::pointTowardsRandomPosition)).WillOnce(Return(1)); + compiler.setBlock(block2); + MotionBlocks::compilePointTowards(&compiler); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(5)); + EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::pointTowardsByIndex)).WillOnce(Return(2)); + compiler.setBlock(block3); + MotionBlocks::compilePointTowards(&compiler); + + EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::pointTowards)).WillOnce(Return(3)); + compiler.setBlock(block4); + MotionBlocks::compilePointTowards(&compiler); + + compiler.end(); + + ASSERT_EQ( + compiler.bytecode(), + std::vector({ vm::OP_START, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_CONST, 0, vm::OP_EXEC, 2, vm::OP_NULL, vm::OP_NULL, vm::OP_STR_CONCAT, vm::OP_EXEC, 3, vm::OP_HALT })); + ASSERT_EQ(compiler.constValues().size(), 1); + ASSERT_EQ(compiler.constValues()[0].toDouble(), 5); +} + +TEST_F(MotionBlocksTest, PointTowardsImpl) +{ + static unsigned int bytecode1[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode2[] = { vm::OP_START, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode3[] = { vm::OP_START, vm::OP_CONST, 2, vm::OP_EXEC, 0, vm::OP_HALT }; + static unsigned int bytecode4[] = { vm::OP_START, vm::OP_CONST, 3, vm::OP_EXEC, 1, vm::OP_HALT }; + static unsigned int bytecode5[] = { vm::OP_START, vm::OP_EXEC, 2, vm::OP_HALT }; + static unsigned int bytecode6[] = { vm::OP_START, vm::OP_EXEC, 3, vm::OP_HALT }; + static BlockFunc functions[] = { &MotionBlocks::pointTowards, &MotionBlocks::pointTowardsByIndex, &MotionBlocks::pointTowardsMousePointer, &MotionBlocks::pointTowardsRandomPosition }; + static Value constValues[] = { "_mouse_", "_random_", "Sprite2", 3 }; + + Sprite sprite, anotherSprite; + sprite.setX(70.1); + sprite.setY(-100.025); + + VirtualMachine vm(&sprite, &m_engineMock, nullptr); + vm.setFunctions(functions); + vm.setConstValues(constValues); + + RandomGeneratorMock rng; + MotionBlocks::rng = &rng; + + static const std::vector> positions = { { -45.12, -123.48 }, { 125.23, -3.21 }, { 30.15, -100.025 }, { 70.1, -100.025 }, { 150.9, -100.025 } }; + static const std::vector results = { -101.51, 29.66, -90, 90, 90 }; + static const std::vector intPosResults = { -101.31, 29.55, -90, 90, 90 }; + + // point towards (join "_mouse_" "") + for (int i = 0; i < positions.size(); i++) { + EXPECT_CALL(m_engineMock, mouseX()).WillOnce(Return(positions[i].first)); + EXPECT_CALL(m_engineMock, mouseY()).WillOnce(Return(positions[i].second)); + + // TODO: Move setBytecode() out of the loop and use reset() after task #215 is completed + vm.setBytecode(bytecode1); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(std::round(sprite.direction() * 100) / 100, results[i]); + } + + // point towards (join "_random_" "") + sprite.setX(std::round(sprite.x())); + sprite.setY(std::round(sprite.y())); + + for (int i = 0; i < positions.size(); i++) { + EXPECT_CALL(rng, randint(-240, 240)).WillOnce(Return(std::round(positions[i].first))); + EXPECT_CALL(rng, randint(-180, 180)).WillOnce(Return(std::round(positions[i].second))); + + // TODO: Move setBytecode() out of the loop and use reset() after task #215 is completed + vm.setBytecode(bytecode2); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(std::round(sprite.direction() * 100) / 100, intPosResults[i]); + } + + sprite.setX(70.1); + sprite.setY(-100.025); + + // point towards (join "Sprite2" "") + for (int i = 0; i < positions.size(); i++) { + anotherSprite.setX(positions[i].first); + anotherSprite.setY(positions[i].second); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(3)); + EXPECT_CALL(m_engineMock, targetAt(3)).WillOnce(Return(&anotherSprite)); + + // TODO: Move setBytecode() out of the loop and use reset() after task #215 is completed + vm.setBytecode(bytecode3); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(std::round(sprite.direction() * 100) / 100, results[i]); + } + + // point towards (Sprite2) + for (int i = 0; i < positions.size(); i++) { + anotherSprite.setX(positions[i].first); + anotherSprite.setY(positions[i].second); + + EXPECT_CALL(m_engineMock, targetAt(3)).WillOnce(Return(&anotherSprite)); + + // TODO: Move setBytecode() out of the loop and use reset() after task #215 is completed + vm.setBytecode(bytecode4); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(std::round(sprite.direction() * 100) / 100, results[i]); + } + + // point towards (mouse-pointer) + for (int i = 0; i < positions.size(); i++) { + EXPECT_CALL(m_engineMock, mouseX()).WillOnce(Return(positions[i].first)); + EXPECT_CALL(m_engineMock, mouseY()).WillOnce(Return(positions[i].second)); + + // TODO: Move setBytecode() out of the loop and use reset() after task #215 is completed + vm.setBytecode(bytecode5); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(std::round(sprite.direction() * 100) / 100, results[i]); + } + + // point towards (random position) + sprite.setX(std::round(sprite.x())); + sprite.setY(std::round(sprite.y())); + + for (int i = 0; i < positions.size(); i++) { + EXPECT_CALL(rng, randint(-240, 240)).WillOnce(Return(std::round(positions[i].first))); + EXPECT_CALL(rng, randint(-180, 180)).WillOnce(Return(std::round(positions[i].second))); + + // TODO: Move setBytecode() out of the loop and use reset() after task #215 is completed + vm.setBytecode(bytecode6); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(std::round(sprite.direction() * 100) / 100, intPosResults[i]); + } +} From 191d617917094ccc01913b9a3317d6c9cec47af9 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 23 Sep 2023 14:21:40 +0200 Subject: [PATCH 7/7] Implement motion_gotoxy block --- src/blocks/motionblocks.cpp | 22 ++++++++++++++++ src/blocks/motionblocks.h | 7 ++++- test/blocks/motion_blocks_test.cpp | 42 ++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index 9b2325f5..82984fad 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -27,12 +27,15 @@ void MotionBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "motion_turnleft", &compileTurnLeft); engine->addCompileFunction(this, "motion_pointindirection", &compilePointInDirection); engine->addCompileFunction(this, "motion_pointtowards", &compilePointTowards); + engine->addCompileFunction(this, "motion_gotoxy", &compileGoToXY); // Inputs engine->addInput(this, "STEPS", STEPS); engine->addInput(this, "DEGREES", DEGREES); engine->addInput(this, "DIRECTION", DIRECTION); engine->addInput(this, "TOWARDS", TOWARDS); + engine->addInput(this, "X", X); + engine->addInput(this, "Y", Y); } void MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -82,6 +85,13 @@ void MotionBlocks::compilePointTowards(Compiler *compiler) } } +void MotionBlocks::compileGoToXY(Compiler *compiler) +{ + compiler->addInput(X); + compiler->addInput(Y); + compiler->addFunctionCall(&goToXY); +} + unsigned int MotionBlocks::moveSteps(VirtualMachine *vm) { Sprite *sprite = dynamic_cast(vm->target()); @@ -203,3 +213,15 @@ unsigned int MotionBlocks::pointTowardsRandomPosition(VirtualMachine *vm) return 0; } + +unsigned int MotionBlocks::goToXY(VirtualMachine *vm) +{ + Sprite *sprite = dynamic_cast(vm->target()); + + if (sprite) { + sprite->setX(vm->getInput(0, 2)->toDouble()); + sprite->setY(vm->getInput(1, 2)->toDouble()); + } + + return 2; +} diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index 2630acf8..0b880be7 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -19,7 +19,9 @@ class MotionBlocks : public IBlockSection STEPS, DEGREES, DIRECTION, - TOWARDS + TOWARDS, + X, + Y }; enum Fields @@ -39,6 +41,7 @@ class MotionBlocks : public IBlockSection static void compileTurnLeft(Compiler *compiler); static void compilePointInDirection(Compiler *compiler); static void compilePointTowards(Compiler *compiler); + static void compileGoToXY(Compiler *compiler); static unsigned int moveSteps(VirtualMachine *vm); static unsigned int turnRight(VirtualMachine *vm); @@ -52,6 +55,8 @@ class MotionBlocks : public IBlockSection static unsigned int pointTowardsMousePointer(VirtualMachine *vm); static unsigned int pointTowardsRandomPosition(VirtualMachine *vm); + static unsigned int goToXY(VirtualMachine *vm); + static IRandomGenerator *rng; }; diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index 47bd7526..32373dc4 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -99,12 +99,15 @@ TEST_F(MotionBlocksTest, RegisterBlocks) EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_turnleft", &MotionBlocks::compileTurnLeft)); EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_pointindirection", &MotionBlocks::compilePointInDirection)); EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_pointtowards", &MotionBlocks::compilePointTowards)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_gotoxy", &MotionBlocks::compileGoToXY)); // Inputs EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS)); EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DEGREES", MotionBlocks::DEGREES)); EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DIRECTION", MotionBlocks::DIRECTION)); EXPECT_CALL(m_engineMock, addInput(m_section.get(), "TOWARDS", MotionBlocks::TOWARDS)); + EXPECT_CALL(m_engineMock, addInput(m_section.get(), "X", MotionBlocks::X)); + EXPECT_CALL(m_engineMock, addInput(m_section.get(), "Y", MotionBlocks::Y)); m_section->registerBlocks(&m_engineMock); } @@ -436,3 +439,42 @@ TEST_F(MotionBlocksTest, PointTowardsImpl) ASSERT_EQ(std::round(sprite.direction() * 100) / 100, intPosResults[i]); } } + +TEST_F(MotionBlocksTest, GoToXY) +{ + Compiler compiler(&m_engineMock); + + // turn right (12.05) degrees + auto block = std::make_shared("a", "motion_gotoxy"); + addValueInput(block, "X", MotionBlocks::X, 95.2); + addValueInput(block, "Y", MotionBlocks::Y, -175.9); + + EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::goToXY)).WillOnce(Return(0)); + + compiler.init(); + compiler.setBlock(block); + MotionBlocks::compileGoToXY(&compiler); + compiler.end(); + + ASSERT_EQ(compiler.bytecode(), std::vector({ vm::OP_START, vm::OP_CONST, 0, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_HALT })); + ASSERT_EQ(compiler.constValues(), std::vector({ 95.2, -175.9 })); +} + +TEST_F(MotionBlocksTest, GoToXYImpl) +{ + static unsigned int bytecode[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_HALT }; + static BlockFunc functions[] = { &MotionBlocks::goToXY }; + static Value constValues[] = { 95.2, -175.9 }; + + Sprite sprite; + + VirtualMachine vm(&sprite, nullptr, nullptr); + vm.setBytecode(bytecode); + vm.setFunctions(functions); + vm.setConstValues(constValues); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(sprite.x(), 95.2); + ASSERT_EQ(sprite.y(), -175.9); +}