From ff81f7a8a37820a0ede51521d08a9566570c1dad Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 26 Feb 2025 11:35:01 +0100 Subject: [PATCH 01/21] Implement motion_movesteps block --- src/blocks/motionblocks.cpp | 24 +++++++++++++++ src/blocks/motionblocks.h | 3 ++ test/blocks/motion_blocks_test.cpp | 47 +++++++++++++++++++++++++++++- 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index a249cdff..e1112aea 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -1,9 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 +#include +#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"; @@ -21,4 +28,21 @@ Rgb MotionBlocks::color() const void MotionBlocks::registerBlocks(IEngine *engine) { + engine->addCompileFunction(this, "motion_movesteps", &compileMoveSteps); +} + +CompilerValue *MotionBlocks::compileMoveSteps(Compiler *compiler) +{ + if (!compiler->target()->isStage()) { + CompilerValue *steps = compiler->addInput("STEPS"); + compiler->addTargetFunctionCall("motion_movesteps", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { steps }); + } + + return nullptr; +} + +extern "C" void motion_movesteps(Sprite *sprite, double steps) +{ + double dir = sprite->direction(); + sprite->setPosition(sprite->x() + std::sin(dir * pi / 180) * steps, sprite->y() + std::cos(dir * pi / 180) * steps); } diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index 44d7c0bd..fc9434b0 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -15,6 +15,9 @@ class MotionBlocks : public IExtension Rgb color() const override; void registerBlocks(IEngine *engine) override; + + private: + static CompilerValue *compileMoveSteps(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index f5e4ecf8..13941993 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -1,15 +1,60 @@ +#include +#include +#include +#include #include #include "../common.h" #include "blocks/motionblocks.h" using namespace libscratchcpp; +using namespace libscratchcpp::test; class MotionBlocksTest : public testing::Test { public: - void SetUp() override { m_extension = std::make_unique(); } + void SetUp() override + { + m_extension = std::make_unique(); + m_engine = m_project.engine().get(); + m_extension->registerBlocks(m_engine); + } std::unique_ptr m_extension; + Project m_project; + IEngine *m_engine = nullptr; EngineMock m_engineMock; }; + +TEST_F(MotionBlocksTest, MoveSteps) +{ + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("motion_movesteps"); + builder.addValueInput("STEPS", 30.25); + + sprite->setX(5.2); + sprite->setY(-0.25); + sprite->setDirection(-61.42); + + builder.build(); + builder.run(); + ASSERT_EQ(std::round(sprite->x() * 100) / 100, -21.36); + ASSERT_EQ(std::round(sprite->y() * 100) / 100, 14.22); + } + + m_engine->clear(); + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_movesteps"); + builder.addValueInput("STEPS", 30.25); + + builder.build(); + builder.run(); + } +} From dfdcab9227014ed8d2b02257af3f123f1a37dfc0 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 26 Feb 2025 14:01:17 +0100 Subject: [PATCH 02/21] Implement motion_turnright block --- src/blocks/motionblocks.cpp | 16 ++++++++++++++++ src/blocks/motionblocks.h | 1 + test/blocks/motion_blocks_test.cpp | 30 ++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index e1112aea..f2a362d6 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -29,6 +29,7 @@ Rgb MotionBlocks::color() const void MotionBlocks::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "motion_movesteps", &compileMoveSteps); + engine->addCompileFunction(this, "motion_turnright", &compileTurnRight); } CompilerValue *MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -41,8 +42,23 @@ CompilerValue *MotionBlocks::compileMoveSteps(Compiler *compiler) return nullptr; } +CompilerValue *MotionBlocks::compileTurnRight(Compiler *compiler) +{ + if (!compiler->target()->isStage()) { + CompilerValue *degrees = compiler->addInput("DEGREES"); + compiler->addTargetFunctionCall("motion_turnright", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { degrees }); + } + + return nullptr; +} + extern "C" void motion_movesteps(Sprite *sprite, double steps) { double dir = sprite->direction(); sprite->setPosition(sprite->x() + std::sin(dir * pi / 180) * steps, sprite->y() + std::cos(dir * pi / 180) * steps); } + +extern "C" void motion_turnright(Sprite *sprite, double degrees) +{ + sprite->setDirection(sprite->direction() + degrees); +} diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index fc9434b0..a71ee41b 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -18,6 +18,7 @@ class MotionBlocks : public IExtension private: static CompilerValue *compileMoveSteps(Compiler *compiler); + static CompilerValue *compileTurnRight(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index 13941993..ec6fb84b 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -58,3 +58,33 @@ TEST_F(MotionBlocksTest, MoveSteps) builder.run(); } } + +TEST_F(MotionBlocksTest, TurnRight) +{ + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("motion_turnright"); + builder.addValueInput("DEGREES", 12.05); + + sprite->setDirection(124.37); + + builder.build(); + builder.run(); + ASSERT_EQ(std::round(sprite->direction() * 100) / 100, 136.42); + } + + m_engine->clear(); + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_turnright"); + builder.addValueInput("DEGREES", 12.05); + + builder.build(); + builder.run(); + } +} From 9ceee87b705d913cd0bc07da2a98fd3d84273fe4 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 26 Feb 2025 16:09:02 +0100 Subject: [PATCH 03/21] Implement motion_turnleft block --- src/blocks/motionblocks.cpp | 16 ++++++++++++++++ src/blocks/motionblocks.h | 1 + test/blocks/motion_blocks_test.cpp | 30 ++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index f2a362d6..b989931c 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -30,6 +30,7 @@ void MotionBlocks::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "motion_movesteps", &compileMoveSteps); engine->addCompileFunction(this, "motion_turnright", &compileTurnRight); + engine->addCompileFunction(this, "motion_turnleft", &compileTurnLeft); } CompilerValue *MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -52,6 +53,16 @@ CompilerValue *MotionBlocks::compileTurnRight(Compiler *compiler) return nullptr; } +CompilerValue *MotionBlocks::compileTurnLeft(Compiler *compiler) +{ + if (!compiler->target()->isStage()) { + CompilerValue *degrees = compiler->addInput("DEGREES"); + compiler->addTargetFunctionCall("motion_turnleft", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { degrees }); + } + + return nullptr; +} + extern "C" void motion_movesteps(Sprite *sprite, double steps) { double dir = sprite->direction(); @@ -62,3 +73,8 @@ extern "C" void motion_turnright(Sprite *sprite, double degrees) { sprite->setDirection(sprite->direction() + degrees); } + +extern "C" void motion_turnleft(Sprite *sprite, double degrees) +{ + sprite->setDirection(sprite->direction() - degrees); +} diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index a71ee41b..b189cbcd 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -19,6 +19,7 @@ class MotionBlocks : public IExtension private: static CompilerValue *compileMoveSteps(Compiler *compiler); static CompilerValue *compileTurnRight(Compiler *compiler); + static CompilerValue *compileTurnLeft(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index ec6fb84b..084322d4 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -88,3 +88,33 @@ TEST_F(MotionBlocksTest, TurnRight) builder.run(); } } + +TEST_F(MotionBlocksTest, TurnLeft) +{ + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("motion_turnleft"); + builder.addValueInput("DEGREES", 12.05); + + sprite->setDirection(124.37); + + builder.build(); + builder.run(); + ASSERT_EQ(std::round(sprite->direction() * 100) / 100, 112.32); + } + + m_engine->clear(); + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_turnleft"); + builder.addValueInput("DEGREES", 12.05); + + builder.build(); + builder.run(); + } +} From d4fc9937686715222cb0c771133f296866299444 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 26 Feb 2025 16:13:13 +0100 Subject: [PATCH 04/21] Implement motion_pointindirection block --- src/blocks/motionblocks.cpp | 16 ++++++++++++++++ src/blocks/motionblocks.h | 1 + test/blocks/motion_blocks_test.cpp | 30 ++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index b989931c..90b9c493 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -31,6 +31,7 @@ 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); } CompilerValue *MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -63,6 +64,16 @@ CompilerValue *MotionBlocks::compileTurnLeft(Compiler *compiler) return nullptr; } +CompilerValue *MotionBlocks::compilePointInDirection(Compiler *compiler) +{ + if (!compiler->target()->isStage()) { + CompilerValue *direction = compiler->addInput("DIRECTION"); + compiler->addTargetFunctionCall("motion_pointindirection", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { direction }); + } + + return nullptr; +} + extern "C" void motion_movesteps(Sprite *sprite, double steps) { double dir = sprite->direction(); @@ -78,3 +89,8 @@ extern "C" void motion_turnleft(Sprite *sprite, double degrees) { sprite->setDirection(sprite->direction() - degrees); } + +extern "C" void motion_pointindirection(Sprite *sprite, double direction) +{ + sprite->setDirection(direction); +} diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index b189cbcd..cc801448 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -20,6 +20,7 @@ class MotionBlocks : public IExtension static CompilerValue *compileMoveSteps(Compiler *compiler); static CompilerValue *compileTurnRight(Compiler *compiler); static CompilerValue *compileTurnLeft(Compiler *compiler); + static CompilerValue *compilePointInDirection(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index 084322d4..755c0583 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -118,3 +118,33 @@ TEST_F(MotionBlocksTest, TurnLeft) builder.run(); } } + +TEST_F(MotionBlocksTest, PointInDirection) +{ + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("motion_pointindirection"); + builder.addValueInput("DIRECTION", -60.5); + + sprite->setDirection(50.02); + + builder.build(); + builder.run(); + ASSERT_EQ(sprite->direction(), -60.5); + } + + m_engine->clear(); + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_pointindirection"); + builder.addValueInput("DIRECTION", -60.5); + + builder.build(); + builder.run(); + } +} From 727bf6a2e773204fae5c1132008693f06d83081c Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 27 Feb 2025 11:46:19 +0100 Subject: [PATCH 05/21] Implement motion_pointtowards block --- src/blocks/motionblocks.cpp | 102 +++++++++ src/blocks/motionblocks.h | 1 + test/blocks/motion_blocks_test.cpp | 338 +++++++++++++++++++++++++++++ 3 files changed, 441 insertions(+) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index 90b9c493..fb5dfed4 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -2,8 +2,17 @@ #include #include +#include #include +#include +#include +#include +#include +#include +#include +#include #include +#include #include "motionblocks.h" @@ -32,6 +41,7 @@ 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); } CompilerValue *MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -74,6 +84,35 @@ CompilerValue *MotionBlocks::compilePointInDirection(Compiler *compiler) return nullptr; } +CompilerValue *MotionBlocks::compilePointTowards(Compiler *compiler) +{ + if (compiler->target()->isStage()) + return nullptr; + + Input *input = compiler->input("TOWARDS"); + + if (input->pointsToDropdownMenu()) { + std::string value = input->selectedMenuItem(); + + if (value == "_mouse_") + compiler->addTargetFunctionCall("motion_point_towards_mouse"); + else if (value == "_random_") + compiler->addFunctionCallWithCtx("motion_point_towards_random_pos"); + else { + int index = compiler->engine()->findTarget(value); + Target *anotherTarget = compiler->engine()->targetAt(index); + + if (anotherTarget && !anotherTarget->isStage()) + compiler->addTargetFunctionCall("motion_point_towards_target_by_index", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { compiler->addConstValue(index) }); + } + } else { + CompilerValue *towards = compiler->addInput(input); + compiler->addFunctionCallWithCtx("motion_pointtowards", Compiler::StaticType::Void, { Compiler::StaticType::String }, { towards }); + } + + return nullptr; +} + extern "C" void motion_movesteps(Sprite *sprite, double steps) { double dir = sprite->direction(); @@ -94,3 +133,66 @@ extern "C" void motion_pointindirection(Sprite *sprite, double direction) { sprite->setDirection(direction); } + +inline void motion_point_towards_pos(Sprite *sprite, double x, double y) +{ + // 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)); +} + +extern "C" void motion_point_towards_mouse(Sprite *sprite) +{ + IEngine *engine = sprite->engine(); + motion_point_towards_pos(sprite, engine->mouseX(), engine->mouseY()); +} + +extern "C" void motion_point_towards_random_pos(ExecutionContext *ctx) +{ + Sprite *sprite = static_cast(ctx->thread()->target()); + IEngine *engine = ctx->engine(); + const int stageWidth = engine->stageWidth(); + const int stageHeight = engine->stageHeight(); + IRandomGenerator *rng = ctx->rng(); + motion_point_towards_pos(sprite, rng->randintDouble(-stageWidth / 2.0, stageWidth / 2.0), rng->randintDouble(-stageHeight / 2.0, stageHeight / 2.0)); +} + +extern "C" void motion_point_towards_target_by_index(Sprite *sprite, double index) +{ + Sprite *anotherSprite = static_cast(sprite->engine()->targetAt(index)); + motion_point_towards_pos(sprite, anotherSprite->x(), anotherSprite->y()); +} + +extern "C" void motion_pointtowards(ExecutionContext *ctx, const StringPtr *towards) +{ + static const StringPtr MOUSE_STR("_mouse_"); + static const StringPtr RANDOM_STR("_random_"); + + Sprite *sprite = static_cast(ctx->thread()->target()); + + if (string_compare_case_sensitive(towards, &MOUSE_STR) == 0) + motion_point_towards_mouse(sprite); + else if (string_compare_case_sensitive(towards, &RANDOM_STR) == 0) + motion_point_towards_random_pos(ctx); + else { + // TODO: Use UTF-16 in engine + std::string u8name = utf8::utf16to8(std::u16string(towards->data)); + IEngine *engine = ctx->engine(); + Target *anotherTarget = engine->targetAt(engine->findTarget(u8name)); + + if (anotherTarget && !anotherTarget->isStage()) { + Sprite *anotherSprite = static_cast(anotherTarget); + motion_point_towards_pos(sprite, anotherSprite->x(), anotherSprite->y()); + } + } +} diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index cc801448..160aff2b 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -21,6 +21,7 @@ class MotionBlocks : public IExtension static CompilerValue *compileTurnRight(Compiler *compiler); static CompilerValue *compileTurnLeft(Compiler *compiler); static CompilerValue *compilePointInDirection(Compiler *compiler); + static CompilerValue *compilePointTowards(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index 755c0583..781c0a77 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -2,7 +2,13 @@ #include #include #include +#include +#include +#include +#include +#include #include +#include #include "../common.h" #include "blocks/motionblocks.h" @@ -10,6 +16,8 @@ using namespace libscratchcpp; using namespace libscratchcpp::test; +using ::testing::Return; + class MotionBlocksTest : public testing::Test { public: @@ -148,3 +156,333 @@ TEST_F(MotionBlocksTest, PointInDirection) builder.run(); } } + +TEST_F(MotionBlocksTest, PointTowardsMouse) +{ + { + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + sprite->setDirection(21.58); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_pointtowards"); + builder.addDropdownInput("TOWARDS", "_mouse_"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, mouseX()).WillOnce(Return(-45.12)); + EXPECT_CALL(m_engineMock, mouseY()).WillOnce(Return(-123.48)); + thread.run(); + ASSERT_EQ(std::round(sprite->direction() * 100) / 100, -101.51); + } + + { + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + sprite->setDirection(21.58); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_pointtowards"); + builder.addValueInput("TOWARDS", "_mouse_"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, mouseX()).WillOnce(Return(125.23)); + EXPECT_CALL(m_engineMock, mouseY()).WillOnce(Return(-3.21)); + thread.run(); + ASSERT_EQ(std::round(sprite->direction() * 100) / 100, 29.66); + } + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_pointtowards"); + builder.addDropdownInput("TOWARDS", "_mouse_"); + builder.addBlock("motion_pointtowards"); + builder.addValueInput("TOWARDS", "_mouse_"); + + builder.build(); + builder.run(); + } +} + +TEST_F(MotionBlocksTest, PointTowardsRandomPosition) +{ + { + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + sprite->setDirection(21.58); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_pointtowards"); + builder.addDropdownInput("TOWARDS", "_random_"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + RandomGeneratorMock rng; + ctx->setRng(&rng); + + EXPECT_CALL(m_engineMock, stageWidth()).WillOnce(Return(640)); + EXPECT_CALL(m_engineMock, stageHeight()).WillOnce(Return(500)); + EXPECT_CALL(rng, randintDouble(-320, 320)).WillOnce(Return(95.2)); + EXPECT_CALL(rng, randintDouble(-250, 250)).WillOnce(Return(-100.025)); + code->run(ctx.get()); + ASSERT_EQ(std::round(sprite->direction() * 100) / 100, 90); + } + + { + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + sprite->setDirection(21.58); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_pointtowards"); + builder.addValueInput("TOWARDS", "_random_"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + RandomGeneratorMock rng; + ctx->setRng(&rng); + + EXPECT_CALL(m_engineMock, stageWidth()).WillOnce(Return(640)); + EXPECT_CALL(m_engineMock, stageHeight()).WillOnce(Return(500)); + EXPECT_CALL(rng, randintDouble(-320, 320)).WillOnce(Return(-21.28)); + EXPECT_CALL(rng, randintDouble(-250, 250)).WillOnce(Return(-100.025)); + code->run(ctx.get()); + ASSERT_EQ(std::round(sprite->direction() * 100) / 100, -90); + } + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_pointtowards"); + builder.addDropdownInput("TOWARDS", "_random_"); + builder.addBlock("motion_pointtowards"); + builder.addValueInput("TOWARDS", "_random_"); + + builder.build(); + builder.run(); + } +} + +TEST_F(MotionBlocksTest, PointTowardsSprite) +{ + { + auto sprite = std::make_shared(); + sprite->setName("abc"); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + sprite->setDirection(21.58); + + auto anotherSprite = std::make_shared(); + anotherSprite->setName("def"); + anotherSprite->setEngine(&m_engineMock); + anotherSprite->setX(-45.12); + anotherSprite->setY(-123.48); + anotherSprite->setDirection(-2.5); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_pointtowards"); + builder.addDropdownInput("TOWARDS", "def"); + auto block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("def")).WillOnce(Return(2)); + EXPECT_CALL(m_engineMock, targetAt(2)).WillRepeatedly(Return(anotherSprite.get())); + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + thread.run(); + ASSERT_EQ(std::round(sprite->direction() * 100) / 100, -101.51); + } + + { + auto sprite = std::make_shared(); + sprite->setName("abc"); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + sprite->setDirection(21.58); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_pointtowards"); + builder.addDropdownInput("TOWARDS", "def"); + auto block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("def")).WillOnce(Return(2)); + EXPECT_CALL(m_engineMock, targetAt(2)).WillRepeatedly(Return(nullptr)); + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + thread.run(); + ASSERT_EQ(sprite->direction(), 21.58); + } + + { + auto sprite = std::make_shared(); + sprite->setName("abc"); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + sprite->setDirection(21.58); + + auto stage = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_pointtowards"); + builder.addDropdownInput("TOWARDS", "_stage_"); + auto block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillOnce(Return(0)); + EXPECT_CALL(m_engineMock, targetAt(0)).WillOnce(Return(stage.get())); + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + thread.run(); + ASSERT_EQ(sprite->direction(), 21.58); + } + + { + auto sprite = std::make_shared(); + sprite->setName("abc"); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + sprite->setDirection(21.58); + + auto anotherSprite = std::make_shared(); + anotherSprite->setName("def"); + anotherSprite->setEngine(&m_engineMock); + anotherSprite->setX(125.23); + anotherSprite->setY(-3.21); + anotherSprite->setDirection(82.54); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_pointtowards"); + builder.addValueInput("TOWARDS", "def"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("def")).WillOnce(Return(5)); + EXPECT_CALL(m_engineMock, targetAt(5)).WillOnce(Return(anotherSprite.get())); + thread.run(); + ASSERT_EQ(std::round(sprite->direction() * 100) / 100, 29.66); + } + + { + auto sprite = std::make_shared(); + sprite->setName("abc"); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + sprite->setDirection(21.58); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_pointtowards"); + builder.addValueInput("TOWARDS", "def"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("def")).WillOnce(Return(5)); + EXPECT_CALL(m_engineMock, targetAt(5)).WillOnce(Return(nullptr)); + thread.run(); + ASSERT_EQ(sprite->direction(), 21.58); + } + + { + auto sprite = std::make_shared(); + sprite->setName("abc"); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + sprite->setDirection(21.58); + + auto stage = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_pointtowards"); + builder.addValueInput("TOWARDS", "_stage_"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillOnce(Return(0)); + EXPECT_CALL(m_engineMock, targetAt(0)).WillOnce(Return(stage.get())); + thread.run(); + ASSERT_EQ(std::round(sprite->direction() * 100) / 100, 21.58); + } + + { + auto stage = std::make_shared(); + + auto sprite = std::make_shared(); + sprite->setName("Test"); + sprite->setEngine(&m_engineMock); + sprite->setX(153.2); + sprite->setY(59.27); + sprite->setDirection(-21.58); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_pointtowards"); + builder.addDropdownInput("TOWARDS", "Test"); + builder.addBlock("motion_pointtowards"); + builder.addValueInput("TOWARDS", "Test"); + + builder.build(); + builder.run(); + } +} From 4967268511a4470ebd0c2e5eabc9ce7409d9cfa8 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 27 Feb 2025 12:20:21 +0100 Subject: [PATCH 06/21] Implement motion_gotoxy block --- src/blocks/motionblocks.cpp | 17 +++++++++++++++ src/blocks/motionblocks.h | 1 + test/blocks/motion_blocks_test.cpp | 34 ++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index fb5dfed4..4ca638d8 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -42,6 +42,7 @@ 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); } CompilerValue *MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -113,6 +114,17 @@ CompilerValue *MotionBlocks::compilePointTowards(Compiler *compiler) return nullptr; } +CompilerValue *MotionBlocks::compileGoToXY(Compiler *compiler) +{ + if (!compiler->target()->isStage()) { + CompilerValue *x = compiler->addInput("X"); + CompilerValue *y = compiler->addInput("Y"); + compiler->addTargetFunctionCall("motion_gotoxy", Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Number }, { x, y }); + } + + return nullptr; +} + extern "C" void motion_movesteps(Sprite *sprite, double steps) { double dir = sprite->direction(); @@ -196,3 +208,8 @@ extern "C" void motion_pointtowards(ExecutionContext *ctx, const StringPtr *towa } } } + +extern "C" void motion_gotoxy(Sprite *sprite, double x, double y) +{ + sprite->setPosition(x, y); +} diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index 160aff2b..906350ac 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -22,6 +22,7 @@ class MotionBlocks : public IExtension static CompilerValue *compileTurnLeft(Compiler *compiler); static CompilerValue *compilePointInDirection(Compiler *compiler); static CompilerValue *compilePointTowards(Compiler *compiler); + static CompilerValue *compileGoToXY(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index 781c0a77..cbf1ef45 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -486,3 +486,37 @@ TEST_F(MotionBlocksTest, PointTowardsSprite) builder.run(); } } + +TEST_F(MotionBlocksTest, GoToXY) +{ + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("motion_gotoxy"); + builder.addValueInput("X", -55.2); + builder.addValueInput("Y", 23.254); + + sprite->setX(51.28); + sprite->setY(-0.5); + + builder.build(); + builder.run(); + ASSERT_EQ(sprite->x(), -55.2); + ASSERT_EQ(sprite->y(), 23.254); + } + + m_engine->clear(); + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_gotoxy"); + builder.addValueInput("X", -55.2); + builder.addValueInput("Y", 23.254); + + builder.build(); + builder.run(); + } +} From 311410aea4e9440373a009087db6497d5293dd42 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 27 Feb 2025 22:08:35 +0100 Subject: [PATCH 07/21] Implement motion_goto block --- src/blocks/motionblocks.cpp | 76 +++++++ src/blocks/motionblocks.h | 1 + test/blocks/motion_blocks_test.cpp | 327 +++++++++++++++++++++++++++++ 3 files changed, 404 insertions(+) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index 4ca638d8..32cc940f 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -43,6 +43,7 @@ void MotionBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "motion_pointindirection", &compilePointInDirection); engine->addCompileFunction(this, "motion_pointtowards", &compilePointTowards); engine->addCompileFunction(this, "motion_gotoxy", &compileGoToXY); + engine->addCompileFunction(this, "motion_goto", &compileGoTo); } CompilerValue *MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -125,6 +126,35 @@ CompilerValue *MotionBlocks::compileGoToXY(Compiler *compiler) return nullptr; } +CompilerValue *MotionBlocks::compileGoTo(Compiler *compiler) +{ + if (compiler->target()->isStage()) + return nullptr; + + Input *input = compiler->input("TO"); + + if (input->pointsToDropdownMenu()) { + std::string value = input->selectedMenuItem(); + + if (value == "_mouse_") + compiler->addTargetFunctionCall("motion_go_to_mouse"); + else if (value == "_random_") + compiler->addFunctionCallWithCtx("motion_go_to_random_pos"); + else { + int index = compiler->engine()->findTarget(value); + Target *anotherTarget = compiler->engine()->targetAt(index); + + if (anotherTarget && !anotherTarget->isStage()) + compiler->addTargetFunctionCall("motion_go_to_target_by_index", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { compiler->addConstValue(index) }); + } + } else { + CompilerValue *to = compiler->addInput(input); + compiler->addFunctionCallWithCtx("motion_goto", Compiler::StaticType::Void, { Compiler::StaticType::String }, { to }); + } + + return nullptr; +} + extern "C" void motion_movesteps(Sprite *sprite, double steps) { double dir = sprite->direction(); @@ -213,3 +243,49 @@ extern "C" void motion_gotoxy(Sprite *sprite, double x, double y) { sprite->setPosition(x, y); } + +extern "C" void motion_go_to_mouse(Sprite *sprite) +{ + IEngine *engine = sprite->engine(); + sprite->setPosition(engine->mouseX(), engine->mouseY()); +} + +extern "C" void motion_go_to_random_pos(ExecutionContext *ctx) +{ + Sprite *sprite = static_cast(ctx->thread()->target()); + IEngine *engine = ctx->engine(); + const int stageWidth = engine->stageWidth(); + const int stageHeight = engine->stageHeight(); + IRandomGenerator *rng = ctx->rng(); + sprite->setPosition(rng->randintDouble(-stageWidth / 2.0, stageWidth / 2.0), rng->randintDouble(-stageHeight / 2.0, stageHeight / 2.0)); +} + +extern "C" void motion_go_to_target_by_index(Sprite *sprite, double index) +{ + Sprite *anotherSprite = static_cast(sprite->engine()->targetAt(index)); + sprite->setPosition(anotherSprite->x(), anotherSprite->y()); +} + +extern "C" void motion_goto(ExecutionContext *ctx, const StringPtr *towards) +{ + static const StringPtr MOUSE_STR("_mouse_"); + static const StringPtr RANDOM_STR("_random_"); + + Sprite *sprite = static_cast(ctx->thread()->target()); + + if (string_compare_case_sensitive(towards, &MOUSE_STR) == 0) + motion_go_to_mouse(sprite); + else if (string_compare_case_sensitive(towards, &RANDOM_STR) == 0) + motion_go_to_random_pos(ctx); + else { + // TODO: Use UTF-16 in engine + std::string u8name = utf8::utf16to8(std::u16string(towards->data)); + IEngine *engine = ctx->engine(); + Target *anotherTarget = engine->targetAt(engine->findTarget(u8name)); + + if (anotherTarget && !anotherTarget->isStage()) { + Sprite *anotherSprite = static_cast(anotherTarget); + sprite->setPosition(anotherSprite->x(), anotherSprite->y()); + } + } +} diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index 906350ac..4f846e76 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -23,6 +23,7 @@ class MotionBlocks : public IExtension static CompilerValue *compilePointInDirection(Compiler *compiler); static CompilerValue *compilePointTowards(Compiler *compiler); static CompilerValue *compileGoToXY(Compiler *compiler); + static CompilerValue *compileGoTo(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index cbf1ef45..e942b180 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -520,3 +520,330 @@ TEST_F(MotionBlocksTest, GoToXY) builder.run(); } } + +TEST_F(MotionBlocksTest, GoToMouse) +{ + { + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_goto"); + builder.addDropdownInput("TO", "_mouse_"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, mouseX()).WillOnce(Return(-45.12)); + EXPECT_CALL(m_engineMock, mouseY()).WillOnce(Return(-123.48)); + thread.run(); + ASSERT_EQ(sprite->x(), -45.12); + ASSERT_EQ(sprite->y(), -123.48); + } + + { + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_goto"); + builder.addValueInput("TO", "_mouse_"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, mouseX()).WillOnce(Return(125.23)); + EXPECT_CALL(m_engineMock, mouseY()).WillOnce(Return(-3.21)); + thread.run(); + ASSERT_EQ(sprite->x(), 125.23); + ASSERT_EQ(sprite->y(), -3.21); + } + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_goto"); + builder.addDropdownInput("TO", "_mouse_"); + builder.addBlock("motion_goto"); + builder.addValueInput("TO", "_mouse_"); + + builder.build(); + builder.run(); + } +} + +TEST_F(MotionBlocksTest, GoToRandomPosition) +{ + { + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_goto"); + builder.addDropdownInput("TO", "_random_"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + RandomGeneratorMock rng; + ctx->setRng(&rng); + + EXPECT_CALL(m_engineMock, stageWidth()).WillOnce(Return(640)); + EXPECT_CALL(m_engineMock, stageHeight()).WillOnce(Return(500)); + EXPECT_CALL(rng, randintDouble(-320, 320)).WillOnce(Return(95.2)); + EXPECT_CALL(rng, randintDouble(-250, 250)).WillOnce(Return(-100.025)); + code->run(ctx.get()); + ASSERT_EQ(sprite->x(), 95.2); + ASSERT_EQ(sprite->y(), -100.025); + } + + { + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_goto"); + builder.addValueInput("TO", "_random_"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + RandomGeneratorMock rng; + ctx->setRng(&rng); + + EXPECT_CALL(m_engineMock, stageWidth()).WillOnce(Return(640)); + EXPECT_CALL(m_engineMock, stageHeight()).WillOnce(Return(500)); + EXPECT_CALL(rng, randintDouble(-320, 320)).WillOnce(Return(-21.28)); + EXPECT_CALL(rng, randintDouble(-250, 250)).WillOnce(Return(-100.025)); + code->run(ctx.get()); + ASSERT_EQ(sprite->x(), -21.28); + ASSERT_EQ(sprite->y(), -100.025); + } + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_goto"); + builder.addDropdownInput("TO", "_random_"); + builder.addBlock("motion_goto"); + builder.addValueInput("TO", "_random_"); + + builder.build(); + builder.run(); + } +} + +TEST_F(MotionBlocksTest, GoToSprite) +{ + { + auto sprite = std::make_shared(); + sprite->setName("abc"); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + + auto anotherSprite = std::make_shared(); + anotherSprite->setName("def"); + anotherSprite->setEngine(&m_engineMock); + anotherSprite->setX(-45.12); + anotherSprite->setY(-123.48); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_goto"); + builder.addDropdownInput("TO", "def"); + auto block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("def")).WillOnce(Return(2)); + EXPECT_CALL(m_engineMock, targetAt(2)).WillRepeatedly(Return(anotherSprite.get())); + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + thread.run(); + ASSERT_EQ(sprite->x(), -45.12); + ASSERT_EQ(sprite->y(), -123.48); + } + + { + auto sprite = std::make_shared(); + sprite->setName("abc"); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_goto"); + builder.addDropdownInput("TO", "def"); + auto block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("def")).WillOnce(Return(2)); + EXPECT_CALL(m_engineMock, targetAt(2)).WillRepeatedly(Return(nullptr)); + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + thread.run(); + ASSERT_EQ(sprite->x(), 70.1); + ASSERT_EQ(sprite->y(), -100.025); + } + + { + auto sprite = std::make_shared(); + sprite->setName("abc"); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + + auto stage = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_goto"); + builder.addDropdownInput("TO", "_stage_"); + auto block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillOnce(Return(0)); + EXPECT_CALL(m_engineMock, targetAt(0)).WillOnce(Return(stage.get())); + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + thread.run(); + ASSERT_EQ(sprite->x(), 70.1); + ASSERT_EQ(sprite->y(), -100.025); + } + + { + auto sprite = std::make_shared(); + sprite->setName("abc"); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + + auto anotherSprite = std::make_shared(); + anotherSprite->setName("def"); + anotherSprite->setEngine(&m_engineMock); + anotherSprite->setX(125.23); + anotherSprite->setY(-3.21); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_goto"); + builder.addValueInput("TO", "def"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("def")).WillOnce(Return(5)); + EXPECT_CALL(m_engineMock, targetAt(5)).WillOnce(Return(anotherSprite.get())); + thread.run(); + ASSERT_EQ(sprite->x(), 125.23); + ASSERT_EQ(sprite->y(), -3.21); + } + + { + auto sprite = std::make_shared(); + sprite->setName("abc"); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_goto"); + builder.addValueInput("TO", "def"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("def")).WillOnce(Return(5)); + EXPECT_CALL(m_engineMock, targetAt(5)).WillOnce(Return(nullptr)); + thread.run(); + ASSERT_EQ(sprite->x(), 70.1); + ASSERT_EQ(sprite->y(), -100.025); + } + + { + auto sprite = std::make_shared(); + sprite->setName("abc"); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + + auto stage = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_goto"); + builder.addValueInput("TO", "_stage_"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillOnce(Return(0)); + EXPECT_CALL(m_engineMock, targetAt(0)).WillOnce(Return(stage.get())); + thread.run(); + ASSERT_EQ(sprite->x(), 70.1); + ASSERT_EQ(sprite->y(), -100.025); + } + + { + auto stage = std::make_shared(); + + auto sprite = std::make_shared(); + sprite->setName("Test"); + sprite->setEngine(&m_engineMock); + sprite->setX(153.2); + sprite->setY(59.27); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_goto"); + builder.addDropdownInput("TO", "Test"); + builder.addBlock("motion_goto"); + builder.addValueInput("TO", "Test"); + + builder.build(); + builder.run(); + } +} From f19c0d43cab472250a1635a60fad0f277943e4ff Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 28 Feb 2025 14:33:28 +0100 Subject: [PATCH 08/21] Add elapsedTime() method to IStackTimer --- include/scratchcpp/istacktimer.h | 3 +++ src/engine/internal/stacktimer.cpp | 8 ++++++++ src/engine/internal/stacktimer.h | 1 + test/mocks/stacktimermock.h | 1 + test/timer/stacktimer_test.cpp | 13 +++++++++---- 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/include/scratchcpp/istacktimer.h b/include/scratchcpp/istacktimer.h index c482042b..d81c28f5 100644 --- a/include/scratchcpp/istacktimer.h +++ b/include/scratchcpp/istacktimer.h @@ -28,6 +28,9 @@ class LIBSCRATCHCPP_EXPORT IStackTimer /*! Returns true if the timer has elapsed. */ virtual bool elapsed() const = 0; + + /*! Returns the elapsed time in seconds. */ + virtual double elapsedTime() const = 0; }; } // namespace libscratchcpp diff --git a/src/engine/internal/stacktimer.cpp b/src/engine/internal/stacktimer.cpp index 01ae3bc7..3aa6e449 100644 --- a/src/engine/internal/stacktimer.cpp +++ b/src/engine/internal/stacktimer.cpp @@ -43,3 +43,11 @@ bool StackTimer::elapsed() const return std::chrono::duration_cast(m_clock->currentSteadyTime() - m_startTime).count() >= m_timeLimit; } + +double StackTimer::elapsedTime() const +{ + if (m_stopped) + return 0; + + return std::chrono::duration_cast(m_clock->currentSteadyTime() - m_startTime).count() / 1000.0; +} diff --git a/src/engine/internal/stacktimer.h b/src/engine/internal/stacktimer.h index 54ece0c8..e99a1a3a 100644 --- a/src/engine/internal/stacktimer.h +++ b/src/engine/internal/stacktimer.h @@ -22,6 +22,7 @@ class StackTimer : public IStackTimer bool stopped() const override; bool elapsed() const override; + double elapsedTime() const override; private: std::chrono::steady_clock::time_point m_startTime; diff --git a/test/mocks/stacktimermock.h b/test/mocks/stacktimermock.h index 9a614d3e..90de3ed9 100644 --- a/test/mocks/stacktimermock.h +++ b/test/mocks/stacktimermock.h @@ -13,4 +13,5 @@ class StackTimerMock : public IStackTimer MOCK_METHOD(bool, stopped, (), (const, override)); MOCK_METHOD(bool, elapsed, (), (const, override)); + MOCK_METHOD(double, elapsedTime, (), (const, override)); }; diff --git a/test/timer/stacktimer_test.cpp b/test/timer/stacktimer_test.cpp index 6d06da90..b8f61224 100644 --- a/test/timer/stacktimer_test.cpp +++ b/test/timer/stacktimer_test.cpp @@ -20,27 +20,32 @@ TEST(StackTimerTest, StartStopElapsed) EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time2)); timer.start(0.5); - EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time2)); + EXPECT_CALL(clock, currentSteadyTime()).Times(2).WillRepeatedly(Return(time2)); ASSERT_FALSE(timer.elapsed()); ASSERT_FALSE(timer.stopped()); + ASSERT_EQ(timer.elapsedTime(), 0); std::chrono::steady_clock::time_point time3(std::chrono::milliseconds(520)); - EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time3)); + EXPECT_CALL(clock, currentSteadyTime()).Times(2).WillRepeatedly(Return(time3)); ASSERT_FALSE(timer.elapsed()); ASSERT_FALSE(timer.stopped()); + ASSERT_EQ(timer.elapsedTime(), 0.447); std::chrono::steady_clock::time_point time4(std::chrono::milliseconds(573)); - EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time4)); + EXPECT_CALL(clock, currentSteadyTime()).Times(2).WillRepeatedly(Return(time4)); ASSERT_TRUE(timer.elapsed()); ASSERT_FALSE(timer.stopped()); + ASSERT_EQ(timer.elapsedTime(), 0.5); std::chrono::steady_clock::time_point time5(std::chrono::milliseconds(580)); - EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time5)); + EXPECT_CALL(clock, currentSteadyTime()).Times(2).WillRepeatedly(Return(time5)); ASSERT_TRUE(timer.elapsed()); ASSERT_FALSE(timer.stopped()); + ASSERT_EQ(timer.elapsedTime(), 0.507); timer.stop(); EXPECT_CALL(clock, currentSteadyTime).Times(0); ASSERT_FALSE(timer.elapsed()); ASSERT_TRUE(timer.stopped()); + ASSERT_EQ(timer.elapsedTime(), 0); } From 9b42803684ce8d1b212d2606f9a7df8bab47eb00 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 28 Feb 2025 14:35:43 +0100 Subject: [PATCH 09/21] LLVMCodeBuilder: Do not free strings before suspending They're freed when the thread is destroyed. --- src/engine/internal/llvm/llvmcodebuilder.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/engine/internal/llvm/llvmcodebuilder.cpp b/src/engine/internal/llvm/llvmcodebuilder.cpp index d676187f..e94e0701 100644 --- a/src/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/engine/internal/llvm/llvmcodebuilder.cpp @@ -1022,7 +1022,6 @@ std::shared_ptr LLVMCodeBuilder::finalize() } case LLVMInstruction::Type::Yield: - // TODO: Do not allow use after suspend (use after free) createSuspend(coro.get(), warpArg, targetVariables); break; @@ -3167,7 +3166,6 @@ void LLVMCodeBuilder::createSuspend(LLVMCoroutine *coro, llvm::Value *warpArg, l m_builder.SetInsertPoint(suspendBranch); } - freeScopeHeap(); syncVariables(targetVariables); coro->createSuspend(); reloadVariables(targetVariables); From 3fe0fe6c2c1f044c59fa70c501fa1ae60d550686 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 28 Feb 2025 14:36:49 +0100 Subject: [PATCH 10/21] Implement motion_glidesecstoxy block --- src/blocks/motionblocks.cpp | 64 ++++++++++++++++++++ src/blocks/motionblocks.h | 1 + test/blocks/motion_blocks_test.cpp | 93 ++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index 32cc940f..cd558704 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,7 @@ void MotionBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "motion_pointtowards", &compilePointTowards); engine->addCompileFunction(this, "motion_gotoxy", &compileGoToXY); engine->addCompileFunction(this, "motion_goto", &compileGoTo); + engine->addCompileFunction(this, "motion_glidesecstoxy", &compileGlideSecsToXY); } CompilerValue *MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -155,6 +157,27 @@ CompilerValue *MotionBlocks::compileGoTo(Compiler *compiler) return nullptr; } +CompilerValue *MotionBlocks::compileGlideSecsToXY(Compiler *compiler) +{ + Target *target = compiler->target(); + CompilerValue *duration = compiler->addInput("SECS"); + CompilerValue *startX = target->isStage() ? compiler->addConstValue(0) : compiler->addTargetFunctionCall("motion_xposition", Compiler::StaticType::Number); + CompilerValue *startY = target->isStage() ? compiler->addConstValue(0) : compiler->addTargetFunctionCall("motion_yposition", Compiler::StaticType::Number); + CompilerValue *endX = compiler->addInput("X"); + CompilerValue *endY = compiler->addInput("Y"); + + compiler->addFunctionCallWithCtx("motion_start_glide", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { duration }); + compiler->createYield(); + + compiler->beginLoopCondition(); + auto numType = Compiler::StaticType::Number; + CompilerValue *elapsed = compiler->addFunctionCallWithCtx("motion_glide", Compiler::StaticType::Bool, { numType, numType, numType, numType, numType }, { duration, startX, startY, endX, endY }); + compiler->beginRepeatUntilLoop(elapsed); + compiler->endLoop(); + + return nullptr; +} + extern "C" void motion_movesteps(Sprite *sprite, double steps) { double dir = sprite->direction(); @@ -289,3 +312,44 @@ extern "C" void motion_goto(ExecutionContext *ctx, const StringPtr *towards) } } } + +extern "C" void motion_start_glide(ExecutionContext *ctx, double duration) +{ + ctx->stackTimer()->start(duration); +} + +extern "C" bool motion_glide(ExecutionContext *ctx, double duration, double startX, double startY, double endX, double endY) +{ + IStackTimer *timer = ctx->stackTimer(); + Target *target = ctx->thread()->target(); + Sprite *sprite = nullptr; + double elapsedTime = timer->elapsedTime(); + + if (!target->isStage()) + sprite = static_cast(target); + + if (elapsedTime >= duration) { + if (sprite) + sprite->setPosition(endX, endY); + + return true; + } else { + if (sprite) { + double factor = elapsedTime / duration; + assert(factor >= 0 && factor < 1); + sprite->setPosition(startX + (endX - startX) * factor, startY + (endY - startY) * factor); + } + + return false; + } +} + +extern "C" double motion_xposition(Sprite *sprite) +{ + return sprite->x(); +} + +extern "C" double motion_yposition(Sprite *sprite) +{ + return sprite->y(); +} diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index 4f846e76..cda7b4e7 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -24,6 +24,7 @@ class MotionBlocks : public IExtension static CompilerValue *compilePointTowards(Compiler *compiler); static CompilerValue *compileGoToXY(Compiler *compiler); static CompilerValue *compileGoTo(Compiler *compiler); + static CompilerValue *compileGlideSecsToXY(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index e942b180..66d44add 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "../common.h" #include "blocks/motionblocks.h" @@ -847,3 +848,95 @@ TEST_F(MotionBlocksTest, GoToSprite) builder.run(); } } + +TEST_F(MotionBlocksTest, GlideSecsToXY) +{ + { + auto sprite = std::make_shared(); + sprite->setX(51.28); + sprite->setY(-0.5); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("motion_glidesecstoxy"); + builder.addValueInput("SECS", 2.5); + builder.addValueInput("X", -55.2); + builder.addValueInput("Y", 23.254); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + StackTimerMock timer; + ctx->setStackTimer(&timer); + + EXPECT_CALL(timer, start(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(0.5)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + ASSERT_EQ(std::round(sprite->x() * 100) / 100, 29.98); + ASSERT_EQ(std::round(sprite->y() * 100) / 100, 4.25); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(1.75)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + ASSERT_EQ(std::round(sprite->x() * 100) / 100, -23.26); + ASSERT_EQ(std::round(sprite->y() * 100) / 100, 16.13); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(2.55)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + ASSERT_EQ(sprite->x(), -55.2); + ASSERT_EQ(sprite->y(), 23.254); + } + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_glidesecstoxy"); + builder.addValueInput("SECS", 2.5); + builder.addValueInput("X", -55.2); + builder.addValueInput("Y", 23.254); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(stage.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + StackTimerMock timer; + ctx->setStackTimer(&timer); + + EXPECT_CALL(timer, start(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(0.5)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(1.75)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(2.55)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + } +} From f0fa83f421da88b77e2ead0a7657b896bbc2a2ad Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 1 Mar 2025 00:37:53 +0100 Subject: [PATCH 11/21] Implement motion_glideto block --- src/blocks/motionblocks.cpp | 166 ++++++++ src/blocks/motionblocks.h | 1 + test/blocks/motion_blocks_test.cpp | 658 +++++++++++++++++++++++++++++ 3 files changed, 825 insertions(+) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index cd558704..f426ae05 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -46,6 +46,7 @@ void MotionBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "motion_gotoxy", &compileGoToXY); engine->addCompileFunction(this, "motion_goto", &compileGoTo); engine->addCompileFunction(this, "motion_glidesecstoxy", &compileGlideSecsToXY); + engine->addCompileFunction(this, "motion_glideto", &compileGlideTo); } CompilerValue *MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -178,6 +179,73 @@ CompilerValue *MotionBlocks::compileGlideSecsToXY(Compiler *compiler) return nullptr; } +CompilerValue *MotionBlocks::compileGlideTo(Compiler *compiler) +{ + Target *target = compiler->target(); + CompilerValue *duration = compiler->addInput("SECS"); + CompilerValue *startX = target->isStage() ? compiler->addConstValue(0) : compiler->addTargetFunctionCall("motion_xposition", Compiler::StaticType::Number); + CompilerValue *startY = target->isStage() ? compiler->addConstValue(0) : compiler->addTargetFunctionCall("motion_yposition", Compiler::StaticType::Number); + CompilerValue *endX = target->isStage() ? compiler->addConstValue(0) : nullptr; + CompilerValue *endY = target->isStage() ? compiler->addConstValue(0) : nullptr; + + Input *input = compiler->input("TO"); + bool ifStatement = false; + + if (input->pointsToDropdownMenu()) { + std::string value = input->selectedMenuItem(); + + if (value == "_mouse_") { + if (!target->isStage()) { + endX = compiler->addFunctionCallWithCtx("motion_get_mouse_x", Compiler::StaticType::Number); + endY = compiler->addFunctionCallWithCtx("motion_get_mouse_y", Compiler::StaticType::Number); + } + } else if (value == "_random_") { + if (!target->isStage()) { + endX = compiler->addFunctionCallWithCtx("motion_get_random_x", Compiler::StaticType::Number); + endY = compiler->addFunctionCallWithCtx("motion_get_random_y", Compiler::StaticType::Number); + } + } else { + int index = compiler->engine()->findTarget(value); + Target *anotherTarget = compiler->engine()->targetAt(index); + + if (anotherTarget && !anotherTarget->isStage()) { + if (!target->isStage()) { + endX = compiler->addFunctionCallWithCtx("motion_get_sprite_x_by_index", Compiler::StaticType::Number, { Compiler::StaticType::Number }, { compiler->addConstValue(index) }); + endY = compiler->addFunctionCallWithCtx("motion_get_sprite_y_by_index", Compiler::StaticType::Number, { Compiler::StaticType::Number }, { compiler->addConstValue(index) }); + } + } else + return nullptr; + } + } else { + CompilerValue *to = compiler->addInput(input); + CompilerValue *valid = compiler->addFunctionCallWithCtx("motion_is_target_valid", Compiler::StaticType::Bool, { Compiler::StaticType::String }, { to }); + ifStatement = true; + compiler->beginIfStatement(valid); + + if (!target->isStage()) { + endX = compiler->addFunctionCallWithCtx("motion_get_target_x", Compiler::StaticType::Number, { Compiler::StaticType::String }, { to }); + endY = compiler->addFunctionCallWithCtx("motion_get_target_y", Compiler::StaticType::Number, { Compiler::StaticType::String }, { to }); + } + } + + assert(endX && endY); + assert(!target->isStage() || (target->isStage() && endX->isConst() && endY->isConst())); + + compiler->addFunctionCallWithCtx("motion_start_glide", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { duration }); + compiler->createYield(); + + compiler->beginLoopCondition(); + auto numType = Compiler::StaticType::Number; + CompilerValue *elapsed = compiler->addFunctionCallWithCtx("motion_glide", Compiler::StaticType::Bool, { numType, numType, numType, numType, numType }, { duration, startX, startY, endX, endY }); + compiler->beginRepeatUntilLoop(elapsed); + compiler->endLoop(); + + if (ifStatement) + compiler->endIf(); + + return nullptr; +} + extern "C" void motion_movesteps(Sprite *sprite, double steps) { double dir = sprite->direction(); @@ -344,6 +412,104 @@ extern "C" bool motion_glide(ExecutionContext *ctx, double duration, double star } } +extern "C" double motion_get_mouse_x(ExecutionContext *ctx) +{ + return ctx->engine()->mouseX(); +} + +extern "C" double motion_get_mouse_y(ExecutionContext *ctx) +{ + return ctx->engine()->mouseY(); +} + +extern "C" double motion_get_random_x(ExecutionContext *ctx) +{ + const int stageWidth = ctx->engine()->stageWidth(); + return ctx->rng()->randintDouble(-stageWidth / 2.0, stageWidth / 2.0); +} + +extern "C" double motion_get_random_y(ExecutionContext *ctx) +{ + const int stageHeight = ctx->engine()->stageHeight(); + return ctx->rng()->randintDouble(-stageHeight / 2.0, stageHeight / 2.0); +} + +extern "C" double motion_get_sprite_x_by_index(ExecutionContext *ctx, double index) +{ + assert(!ctx->engine()->targetAt(index)->isStage()); + Sprite *sprite = static_cast(ctx->engine()->targetAt(index)); + return sprite->x(); +} + +extern "C" double motion_get_sprite_y_by_index(ExecutionContext *ctx, double index) +{ + assert(!ctx->engine()->targetAt(index)->isStage()); + Sprite *sprite = static_cast(ctx->engine()->targetAt(index)); + return sprite->y(); +} + +extern "C" double motion_get_target_x(ExecutionContext *ctx, const StringPtr *name) +{ + static const StringPtr MOUSE_STR("_mouse_"); + static const StringPtr RANDOM_STR("_random_"); + + if (string_compare_case_sensitive(name, &MOUSE_STR) == 0) + return ctx->engine()->mouseX(); + else if (string_compare_case_sensitive(name, &RANDOM_STR) == 0) + return motion_get_random_x(ctx); + else { + // TODO: Use UTF-16 in engine + std::string u8name = utf8::utf16to8(std::u16string(name->data)); + IEngine *engine = ctx->engine(); + Target *target = engine->targetAt(engine->findTarget(u8name)); + + if (target && !target->isStage()) { + Sprite *sprite = static_cast(target); + return sprite->x(); + } else + return 0; + } +} + +extern "C" double motion_get_target_y(ExecutionContext *ctx, const StringPtr *name) +{ + static const StringPtr MOUSE_STR("_mouse_"); + static const StringPtr RANDOM_STR("_random_"); + + if (string_compare_case_sensitive(name, &MOUSE_STR) == 0) + return ctx->engine()->mouseY(); + else if (string_compare_case_sensitive(name, &RANDOM_STR) == 0) + return motion_get_random_y(ctx); + else { + // TODO: Use UTF-16 in engine + std::string u8name = utf8::utf16to8(std::u16string(name->data)); + IEngine *engine = ctx->engine(); + Target *target = engine->targetAt(engine->findTarget(u8name)); + + if (target && !target->isStage()) { + Sprite *sprite = static_cast(target); + return sprite->y(); + } else + return 0; + } +} + +extern "C" bool motion_is_target_valid(ExecutionContext *ctx, const StringPtr *name) +{ + static const StringPtr MOUSE_STR("_mouse_"); + static const StringPtr RANDOM_STR("_random_"); + + if (string_compare_case_sensitive(name, &MOUSE_STR) == 0 || string_compare_case_sensitive(name, &RANDOM_STR) == 0) + return true; + else { + // TODO: Use UTF-16 in engine + std::string u8name = utf8::utf16to8(std::u16string(name->data)); + IEngine *engine = ctx->engine(); + Target *target = engine->targetAt(engine->findTarget(u8name)); + return (target && !target->isStage()); + } +} + extern "C" double motion_xposition(Sprite *sprite) { return sprite->x(); diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index cda7b4e7..e39dbe5a 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -25,6 +25,7 @@ class MotionBlocks : public IExtension static CompilerValue *compileGoToXY(Compiler *compiler); static CompilerValue *compileGoTo(Compiler *compiler); static CompilerValue *compileGlideSecsToXY(Compiler *compiler); + static CompilerValue *compileGlideTo(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index 66d44add..09b07505 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -940,3 +940,661 @@ TEST_F(MotionBlocksTest, GlideSecsToXY) ASSERT_TRUE(code->isFinished(ctx.get())); } } + +TEST_F(MotionBlocksTest, GlideToMouse) +{ + { + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_glideto"); + builder.addValueInput("SECS", 2.5); + builder.addDropdownInput("TO", "_mouse_"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + StackTimerMock timer; + ctx->setStackTimer(&timer); + + EXPECT_CALL(timer, start(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + EXPECT_CALL(m_engineMock, mouseX()).WillOnce(Return(-45.12)); + EXPECT_CALL(m_engineMock, mouseY()).WillOnce(Return(-123.48)); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(0.5)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + ASSERT_EQ(std::round(sprite->x() * 100) / 100, 47.06); + ASSERT_EQ(std::round(sprite->y() * 100) / 100, -104.72); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(1.75)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + ASSERT_EQ(std::round(sprite->x() * 100) / 100, -10.55); + ASSERT_EQ(std::round(sprite->y() * 100) / 100, -116.44); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(2.55)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + ASSERT_EQ(sprite->x(), -45.12); + ASSERT_EQ(sprite->y(), -123.48); + } + + { + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_glideto"); + builder.addValueInput("SECS", 2.5); + builder.addValueInput("TO", "_mouse_"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + StackTimerMock timer; + ctx->setStackTimer(&timer); + + EXPECT_CALL(timer, start(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + EXPECT_CALL(m_engineMock, mouseX()).WillOnce(Return(-45.12)); + EXPECT_CALL(m_engineMock, mouseY()).WillOnce(Return(-123.48)); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(0.5)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + ASSERT_EQ(std::round(sprite->x() * 100) / 100, 47.06); + ASSERT_EQ(std::round(sprite->y() * 100) / 100, -104.72); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(1.75)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + ASSERT_EQ(std::round(sprite->x() * 100) / 100, -10.55); + ASSERT_EQ(std::round(sprite->y() * 100) / 100, -116.44); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(2.55)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + ASSERT_EQ(sprite->x(), -45.12); + ASSERT_EQ(sprite->y(), -123.48); + } + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_glideto"); + builder.addValueInput("SECS", 2.5); + builder.addDropdownInput("TO", "_mouse_"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(stage.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + StackTimerMock timer; + ctx->setStackTimer(&timer); + + EXPECT_CALL(m_engineMock, requestRedraw).Times(0); + + EXPECT_CALL(timer, start(2.5)); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(1.3)); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + } + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_glideto"); + builder.addValueInput("SECS", 2.5); + builder.addValueInput("TO", "_mouse_"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(stage.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + StackTimerMock timer; + ctx->setStackTimer(&timer); + + EXPECT_CALL(m_engineMock, requestRedraw).Times(0); + + EXPECT_CALL(timer, start(2.5)); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(2.51)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + } +} + +TEST_F(MotionBlocksTest, GlideToRandomPosition) +{ + { + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_glideto"); + builder.addValueInput("SECS", 2.5); + builder.addDropdownInput("TO", "_random_"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + RandomGeneratorMock rng; + StackTimerMock timer; + ctx->setRng(&rng); + ctx->setStackTimer(&timer); + + EXPECT_CALL(timer, start(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + EXPECT_CALL(m_engineMock, stageWidth()).WillOnce(Return(640)); + EXPECT_CALL(m_engineMock, stageHeight()).WillOnce(Return(500)); + EXPECT_CALL(rng, randintDouble(-320, 320)).WillOnce(Return(-45.12)); + EXPECT_CALL(rng, randintDouble(-250, 250)).WillOnce(Return(-123.48)); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(0.5)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + ASSERT_EQ(std::round(sprite->x() * 100) / 100, 47.06); + ASSERT_EQ(std::round(sprite->y() * 100) / 100, -104.72); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(2.55)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + ASSERT_EQ(sprite->x(), -45.12); + ASSERT_EQ(sprite->y(), -123.48); + } + + { + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_glideto"); + builder.addValueInput("SECS", 2.5); + builder.addValueInput("TO", "_random_"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + RandomGeneratorMock rng; + StackTimerMock timer; + ctx->setRng(&rng); + ctx->setStackTimer(&timer); + + EXPECT_CALL(timer, start(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + EXPECT_CALL(m_engineMock, stageWidth()).WillOnce(Return(640)); + EXPECT_CALL(m_engineMock, stageHeight()).WillOnce(Return(500)); + EXPECT_CALL(rng, randintDouble(-320, 320)).WillOnce(Return(-45.12)); + EXPECT_CALL(rng, randintDouble(-250, 250)).WillOnce(Return(-123.48)); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(0.5)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + ASSERT_EQ(std::round(sprite->x() * 100) / 100, 47.06); + ASSERT_EQ(std::round(sprite->y() * 100) / 100, -104.72); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(2.55)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + ASSERT_EQ(sprite->x(), -45.12); + ASSERT_EQ(sprite->y(), -123.48); + } + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_glideto"); + builder.addValueInput("SECS", 2.5); + builder.addDropdownInput("TO", "_random_"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(stage.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + StackTimerMock timer; + ctx->setStackTimer(&timer); + + EXPECT_CALL(m_engineMock, requestRedraw).Times(0); + + EXPECT_CALL(timer, start(2.5)); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(1.3)); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + } + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_glideto"); + builder.addValueInput("SECS", 2.5); + builder.addValueInput("TO", "_random_"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(stage.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + StackTimerMock timer; + ctx->setStackTimer(&timer); + + EXPECT_CALL(m_engineMock, requestRedraw).Times(0); + + EXPECT_CALL(timer, start(2.5)); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(2.51)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + } +} + +TEST_F(MotionBlocksTest, GlideToSprite) +{ + { + auto sprite = std::make_shared(); + sprite->setName("abc"); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + + auto anotherSprite = std::make_shared(); + anotherSprite->setName("def"); + anotherSprite->setEngine(&m_engineMock); + anotherSprite->setX(-45.12); + anotherSprite->setY(-123.48); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_glideto"); + builder.addValueInput("SECS", 2.5); + builder.addDropdownInput("TO", "def"); + auto block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("def")).WillOnce(Return(2)); + EXPECT_CALL(m_engineMock, targetAt(2)).WillRepeatedly(Return(anotherSprite.get())); + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + StackTimerMock timer; + ctx->setStackTimer(&timer); + + EXPECT_CALL(timer, start(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(0.5)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + ASSERT_EQ(std::round(sprite->x() * 100) / 100, 47.06); + ASSERT_EQ(std::round(sprite->y() * 100) / 100, -104.72); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(2.55)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + ASSERT_EQ(sprite->x(), -45.12); + ASSERT_EQ(sprite->y(), -123.48); + } + + { + auto sprite = std::make_shared(); + sprite->setName("abc"); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_glideto"); + builder.addValueInput("SECS", 2.5); + builder.addDropdownInput("TO", "def"); + auto block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("def")).WillOnce(Return(2)); + EXPECT_CALL(m_engineMock, targetAt(2)).WillRepeatedly(Return(nullptr)); + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + StackTimerMock timer; + ctx->setStackTimer(&timer); + + EXPECT_CALL(m_engineMock, requestRedraw).Times(0); + EXPECT_CALL(timer, start).Times(0); + + code->run(ctx.get()); + ASSERT_EQ(sprite->x(), 70.1); + ASSERT_EQ(sprite->y(), -100.025); + } + + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + + { + auto sprite = std::make_shared(); + sprite->setName("abc"); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + + auto stage = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_glideto"); + builder.addValueInput("SECS", 2.5); + builder.addDropdownInput("TO", "_stage_"); + auto block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillOnce(Return(0)); + EXPECT_CALL(m_engineMock, targetAt(0)).WillOnce(Return(stage.get())); + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + StackTimerMock timer; + ctx->setStackTimer(&timer); + + EXPECT_CALL(m_engineMock, requestRedraw).Times(0); + EXPECT_CALL(timer, start).Times(0); + + code->run(ctx.get()); + ASSERT_EQ(sprite->x(), 70.1); + ASSERT_EQ(sprite->y(), -100.025); + } + + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + + { + auto sprite = std::make_shared(); + sprite->setName("abc"); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + + auto anotherSprite = std::make_shared(); + anotherSprite->setName("def"); + anotherSprite->setEngine(&m_engineMock); + anotherSprite->setX(-45.12); + anotherSprite->setY(-123.48); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_glideto"); + builder.addValueInput("SECS", 2.5); + builder.addValueInput("TO", "def"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + StackTimerMock timer; + ctx->setStackTimer(&timer); + + EXPECT_CALL(m_engineMock, findTarget("def")).WillRepeatedly(Return(5)); + EXPECT_CALL(m_engineMock, targetAt(5)).WillRepeatedly(Return(anotherSprite.get())); + + EXPECT_CALL(timer, start(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(0.5)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + ASSERT_EQ(std::round(sprite->x() * 100) / 100, 47.06); + ASSERT_EQ(std::round(sprite->y() * 100) / 100, -104.72); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(2.55)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + ASSERT_EQ(sprite->x(), -45.12); + ASSERT_EQ(sprite->y(), -123.48); + } + + { + auto sprite = std::make_shared(); + sprite->setName("abc"); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_glideto"); + builder.addValueInput("SECS", 2.5); + builder.addValueInput("TO", "def"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + StackTimerMock timer; + ctx->setStackTimer(&timer); + + EXPECT_CALL(m_engineMock, requestRedraw).Times(0); + EXPECT_CALL(timer, start).Times(0); + + EXPECT_CALL(m_engineMock, findTarget("def")).WillOnce(Return(5)); + EXPECT_CALL(m_engineMock, targetAt(5)).WillOnce(Return(nullptr)); + code->run(ctx.get()); + ASSERT_EQ(sprite->x(), 70.1); + ASSERT_EQ(sprite->y(), -100.025); + } + + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + + { + auto sprite = std::make_shared(); + sprite->setName("abc"); + sprite->setEngine(&m_engineMock); + sprite->setX(70.1); + sprite->setY(-100.025); + + auto stage = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("motion_glideto"); + builder.addValueInput("SECS", 2.5); + builder.addValueInput("TO", "_stage_"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + StackTimerMock timer; + ctx->setStackTimer(&timer); + + EXPECT_CALL(m_engineMock, requestRedraw).Times(0); + EXPECT_CALL(timer, start).Times(0); + + EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillOnce(Return(0)); + EXPECT_CALL(m_engineMock, targetAt(0)).WillOnce(Return(stage.get())); + code->run(ctx.get()); + ASSERT_EQ(sprite->x(), 70.1); + ASSERT_EQ(sprite->y(), -100.025); + } + + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + + { + auto stage = std::make_shared(); + + auto sprite = std::make_shared(); + sprite->setName("Test"); + sprite->setEngine(&m_engineMock); + sprite->setX(153.2); + sprite->setY(59.27); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_glideto"); + builder.addValueInput("SECS", 2.5); + builder.addDropdownInput("TO", "Test"); + auto block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("Test")).WillOnce(Return(9)); + EXPECT_CALL(m_engineMock, targetAt(9)).WillOnce(Return(sprite.get())); + Compiler compiler(&m_engineMock, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(stage.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + StackTimerMock timer; + ctx->setStackTimer(&timer); + + EXPECT_CALL(m_engineMock, requestRedraw).Times(0); + + EXPECT_CALL(timer, start(2.5)); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(1.3)); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + } + + { + auto stage = std::make_shared(); + + auto sprite = std::make_shared(); + sprite->setName("Test"); + sprite->setEngine(&m_engineMock); + sprite->setX(153.2); + sprite->setY(59.27); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_glideto"); + builder.addValueInput("SECS", 2.5); + builder.addValueInput("TO", "Test"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(stage.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + StackTimerMock timer; + ctx->setStackTimer(&timer); + + EXPECT_CALL(m_engineMock, requestRedraw).Times(0); + + EXPECT_CALL(m_engineMock, findTarget("Test")).WillOnce(Return(9)); + EXPECT_CALL(m_engineMock, targetAt(9)).WillOnce(Return(sprite.get())); + EXPECT_CALL(timer, start(2.5)); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(1.3)); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsedTime()).WillOnce(Return(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + } +} From 606006423877c4cb6b6d5f4dc86f59192920a63b Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 1 Mar 2025 10:44:10 +0100 Subject: [PATCH 12/21] Implement motion_changexby block --- src/blocks/motionblocks.cpp | 16 +++++++++++++++ src/blocks/motionblocks.h | 1 + test/blocks/motion_blocks_test.cpp | 33 ++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index f426ae05..a8d3924a 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -47,6 +47,7 @@ void MotionBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "motion_goto", &compileGoTo); engine->addCompileFunction(this, "motion_glidesecstoxy", &compileGlideSecsToXY); engine->addCompileFunction(this, "motion_glideto", &compileGlideTo); + engine->addCompileFunction(this, "motion_changexby", &compileChangeXBy); } CompilerValue *MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -246,6 +247,16 @@ CompilerValue *MotionBlocks::compileGlideTo(Compiler *compiler) return nullptr; } +CompilerValue *MotionBlocks::compileChangeXBy(Compiler *compiler) +{ + if (!compiler->target()->isStage()) { + CompilerValue *dx = compiler->addInput("DX"); + compiler->addTargetFunctionCall("motion_changexby", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { dx }); + } + + return nullptr; +} + extern "C" void motion_movesteps(Sprite *sprite, double steps) { double dir = sprite->direction(); @@ -510,6 +521,11 @@ extern "C" bool motion_is_target_valid(ExecutionContext *ctx, const StringPtr *n } } +extern "C" void motion_changexby(Sprite *sprite, double dx) +{ + sprite->setX(sprite->x() + dx); +} + extern "C" double motion_xposition(Sprite *sprite) { return sprite->x(); diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index e39dbe5a..0e591183 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -26,6 +26,7 @@ class MotionBlocks : public IExtension static CompilerValue *compileGoTo(Compiler *compiler); static CompilerValue *compileGlideSecsToXY(Compiler *compiler); static CompilerValue *compileGlideTo(Compiler *compiler); + static CompilerValue *compileChangeXBy(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index 09b07505..d5ce6aff 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -1598,3 +1598,36 @@ TEST_F(MotionBlocksTest, GlideToSprite) ASSERT_TRUE(code->isFinished(ctx.get())); } } + +TEST_F(MotionBlocksTest, ChangeXBy) +{ + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("motion_changexby"); + builder.addValueInput("DX", 30.25); + + sprite->setX(5.2); + sprite->setY(-0.25); + sprite->setDirection(-61.42); + + builder.build(); + builder.run(); + ASSERT_EQ(sprite->x(), 35.45); + ASSERT_EQ(sprite->y(), -0.25); + } + + m_engine->clear(); + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_changexby"); + builder.addValueInput("DX", 30.25); + + builder.build(); + builder.run(); + } +} From d7caa976aaac4cfb2588569659bf079e4ce92592 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 1 Mar 2025 10:47:53 +0100 Subject: [PATCH 13/21] Implement motion_setx block --- src/blocks/motionblocks.cpp | 16 +++++++++++++++ src/blocks/motionblocks.h | 1 + test/blocks/motion_blocks_test.cpp | 33 ++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index a8d3924a..e908632d 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -48,6 +48,7 @@ void MotionBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "motion_glidesecstoxy", &compileGlideSecsToXY); engine->addCompileFunction(this, "motion_glideto", &compileGlideTo); engine->addCompileFunction(this, "motion_changexby", &compileChangeXBy); + engine->addCompileFunction(this, "motion_setx", &compileSetX); } CompilerValue *MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -257,6 +258,16 @@ CompilerValue *MotionBlocks::compileChangeXBy(Compiler *compiler) return nullptr; } +CompilerValue *MotionBlocks::compileSetX(Compiler *compiler) +{ + if (!compiler->target()->isStage()) { + CompilerValue *x = compiler->addInput("X"); + compiler->addTargetFunctionCall("motion_setx", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { x }); + } + + return nullptr; +} + extern "C" void motion_movesteps(Sprite *sprite, double steps) { double dir = sprite->direction(); @@ -526,6 +537,11 @@ extern "C" void motion_changexby(Sprite *sprite, double dx) sprite->setX(sprite->x() + dx); } +extern "C" void motion_setx(Sprite *sprite, double x) +{ + sprite->setX(x); +} + extern "C" double motion_xposition(Sprite *sprite) { return sprite->x(); diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index 0e591183..25c1c024 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -27,6 +27,7 @@ class MotionBlocks : public IExtension static CompilerValue *compileGlideSecsToXY(Compiler *compiler); static CompilerValue *compileGlideTo(Compiler *compiler); static CompilerValue *compileChangeXBy(Compiler *compiler); + static CompilerValue *compileSetX(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index d5ce6aff..2cae3f23 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -1631,3 +1631,36 @@ TEST_F(MotionBlocksTest, ChangeXBy) builder.run(); } } + +TEST_F(MotionBlocksTest, SetX) +{ + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("motion_setx"); + builder.addValueInput("X", 30.25); + + sprite->setX(5.2); + sprite->setY(-0.25); + sprite->setDirection(-61.42); + + builder.build(); + builder.run(); + ASSERT_EQ(sprite->x(), 30.25); + ASSERT_EQ(sprite->y(), -0.25); + } + + m_engine->clear(); + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_setx"); + builder.addValueInput("X", 30.25); + + builder.build(); + builder.run(); + } +} From 8dbd3f2dfbdfb73c8f08e44d124a977eeffb7c39 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 1 Mar 2025 10:52:24 +0100 Subject: [PATCH 14/21] Implement motion_changeyby block --- src/blocks/motionblocks.cpp | 16 +++++++++++++++ src/blocks/motionblocks.h | 1 + test/blocks/motion_blocks_test.cpp | 33 ++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index e908632d..07382ab6 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -49,6 +49,7 @@ void MotionBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "motion_glideto", &compileGlideTo); engine->addCompileFunction(this, "motion_changexby", &compileChangeXBy); engine->addCompileFunction(this, "motion_setx", &compileSetX); + engine->addCompileFunction(this, "motion_changeyby", &compileChangeYBy); } CompilerValue *MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -268,6 +269,16 @@ CompilerValue *MotionBlocks::compileSetX(Compiler *compiler) return nullptr; } +CompilerValue *MotionBlocks::compileChangeYBy(Compiler *compiler) +{ + if (!compiler->target()->isStage()) { + CompilerValue *dy = compiler->addInput("DY"); + compiler->addTargetFunctionCall("motion_changeyby", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { dy }); + } + + return nullptr; +} + extern "C" void motion_movesteps(Sprite *sprite, double steps) { double dir = sprite->direction(); @@ -542,6 +553,11 @@ extern "C" void motion_setx(Sprite *sprite, double x) sprite->setX(x); } +extern "C" void motion_changeyby(Sprite *sprite, double dy) +{ + sprite->setY(sprite->y() + dy); +} + extern "C" double motion_xposition(Sprite *sprite) { return sprite->x(); diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index 25c1c024..4773c995 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -28,6 +28,7 @@ class MotionBlocks : public IExtension static CompilerValue *compileGlideTo(Compiler *compiler); static CompilerValue *compileChangeXBy(Compiler *compiler); static CompilerValue *compileSetX(Compiler *compiler); + static CompilerValue *compileChangeYBy(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index 2cae3f23..0a3bfdef 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -1664,3 +1664,36 @@ TEST_F(MotionBlocksTest, SetX) builder.run(); } } + +TEST_F(MotionBlocksTest, ChangeYBy) +{ + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("motion_changeyby"); + builder.addValueInput("DY", 30.25); + + sprite->setX(5.2); + sprite->setY(-0.25); + sprite->setDirection(-61.42); + + builder.build(); + builder.run(); + ASSERT_EQ(sprite->x(), 5.2); + ASSERT_EQ(sprite->y(), 30); + } + + m_engine->clear(); + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_changeyby"); + builder.addValueInput("DY", 30.25); + + builder.build(); + builder.run(); + } +} From e5bba3789ae9d77f61f15f3fdfc84d1dbcd0a5bb Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 1 Mar 2025 10:57:17 +0100 Subject: [PATCH 15/21] Implement motion_sety block --- src/blocks/motionblocks.cpp | 16 +++++++++++++++ src/blocks/motionblocks.h | 1 + test/blocks/motion_blocks_test.cpp | 33 ++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index 07382ab6..2a483480 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -50,6 +50,7 @@ void MotionBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "motion_changexby", &compileChangeXBy); engine->addCompileFunction(this, "motion_setx", &compileSetX); engine->addCompileFunction(this, "motion_changeyby", &compileChangeYBy); + engine->addCompileFunction(this, "motion_sety", &compileSetY); } CompilerValue *MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -279,6 +280,16 @@ CompilerValue *MotionBlocks::compileChangeYBy(Compiler *compiler) return nullptr; } +CompilerValue *MotionBlocks::compileSetY(Compiler *compiler) +{ + if (!compiler->target()->isStage()) { + CompilerValue *y = compiler->addInput("Y"); + compiler->addTargetFunctionCall("motion_sety", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { y }); + } + + return nullptr; +} + extern "C" void motion_movesteps(Sprite *sprite, double steps) { double dir = sprite->direction(); @@ -558,6 +569,11 @@ extern "C" void motion_changeyby(Sprite *sprite, double dy) sprite->setY(sprite->y() + dy); } +extern "C" void motion_sety(Sprite *sprite, double y) +{ + sprite->setY(y); +} + extern "C" double motion_xposition(Sprite *sprite) { return sprite->x(); diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index 4773c995..45958ee4 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -29,6 +29,7 @@ class MotionBlocks : public IExtension static CompilerValue *compileChangeXBy(Compiler *compiler); static CompilerValue *compileSetX(Compiler *compiler); static CompilerValue *compileChangeYBy(Compiler *compiler); + static CompilerValue *compileSetY(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index 0a3bfdef..c2745969 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -1697,3 +1697,36 @@ TEST_F(MotionBlocksTest, ChangeYBy) builder.run(); } } + +TEST_F(MotionBlocksTest, SetY) +{ + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("motion_sety"); + builder.addValueInput("Y", 30.25); + + sprite->setX(5.2); + sprite->setY(-0.25); + sprite->setDirection(-61.42); + + builder.build(); + builder.run(); + ASSERT_EQ(sprite->x(), 5.2); + ASSERT_EQ(sprite->y(), 30.25); + } + + m_engine->clear(); + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_sety"); + builder.addValueInput("Y", 30.25); + + builder.build(); + builder.run(); + } +} From 25c70147a7a900c77b6acd804943b3cc4de51526 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 1 Mar 2025 11:13:57 +0100 Subject: [PATCH 16/21] Implement motion_ifonedgebounce block --- src/blocks/motionblocks.cpp | 93 ++++++++++++++++++++++++++++++ src/blocks/motionblocks.h | 1 + test/blocks/motion_blocks_test.cpp | 92 +++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index 2a483480..d6497b90 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -51,6 +51,7 @@ void MotionBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "motion_setx", &compileSetX); engine->addCompileFunction(this, "motion_changeyby", &compileChangeYBy); engine->addCompileFunction(this, "motion_sety", &compileSetY); + engine->addCompileFunction(this, "motion_ifonedgebounce", &compileIfOnEdgeBounce); } CompilerValue *MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -290,6 +291,14 @@ CompilerValue *MotionBlocks::compileSetY(Compiler *compiler) return nullptr; } +CompilerValue *MotionBlocks::compileIfOnEdgeBounce(Compiler *compiler) +{ + if (!compiler->target()->isStage()) + compiler->addTargetFunctionCall("motion_ifonedgebounce"); + + return nullptr; +} + extern "C" void motion_movesteps(Sprite *sprite, double steps) { double dir = sprite->direction(); @@ -574,6 +583,90 @@ extern "C" void motion_sety(Sprite *sprite, double y) sprite->setY(y); } +extern "C" void motion_ifonedgebounce(Sprite *sprite) +{ + // https://github.com/scratchfoundation/scratch-vm/blob/c37745e97e6d8a77ad1dc31a943ea728dd17ba78/src/blocks/scratch3_motion.js#L186-L240 + IEngine *engine = sprite->engine(); + Rect bounds = sprite->boundingRect(); + + // Measure distance to edges + // Values are zero when the sprite is beyond + double stageWidth = engine->stageWidth(); + double stageHeight = engine->stageHeight(); + double distLeft = std::max(0.0, (stageWidth / 2.0) + bounds.left()); + double distTop = std::max(0.0, (stageHeight / 2.0) - bounds.top()); + double distRight = std::max(0.0, (stageWidth / 2.0) - bounds.right()); + double distBottom = std::max(0.0, (stageHeight / 2.0) + bounds.bottom()); + + // Find the nearest edge + // 1 - left + // 2 - top + // 3 - right + // 4 - bottom + unsigned short nearestEdge = 0; + double minDist = std::numeric_limits::infinity(); + + if (distLeft < minDist) { + minDist = distLeft; + nearestEdge = 1; + } + + if (distTop < minDist) { + minDist = distTop; + nearestEdge = 2; + } + + if (distRight < minDist) { + minDist = distRight; + nearestEdge = 3; + } + + if (distBottom < minDist) { + minDist = distBottom; + nearestEdge = 4; + } + + if (minDist > 0) + return; // Not touching any edge + + assert(nearestEdge != 0); + + // Point away from the nearest edge + double radians = (90 - sprite->direction()) * pi / 180; + double dx = std::cos(radians); + double dy = -std::sin(radians); + + switch (nearestEdge) { + case 1: + // Left + dx = std::max(0.2, std::abs(dx)); + break; + + case 2: + // Top + dy = std::max(0.2, std::abs(dy)); + break; + + case 3: + // Right + dx = 0 - std::max(0.2, std::abs(dx)); + break; + + case 4: + // Bottom + dy = 0 - std::max(0.2, std::abs(dy)); + break; + } + + double newDirection = (180 / pi) * (std::atan2(dy, dx)) + 90; + sprite->setDirection(newDirection); + + // Keep within the stage + double fencedX, fencedY; + sprite->keepInFence(sprite->x(), sprite->y(), &fencedX, &fencedY); + sprite->setPosition(fencedX, fencedY); +} + extern "C" double motion_xposition(Sprite *sprite) { return sprite->x(); diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index 45958ee4..5b9d2e18 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -30,6 +30,7 @@ class MotionBlocks : public IExtension static CompilerValue *compileSetX(Compiler *compiler); static CompilerValue *compileChangeYBy(Compiler *compiler); static CompilerValue *compileSetY(Compiler *compiler); + static CompilerValue *compileIfOnEdgeBounce(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index c2745969..749b3721 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "../common.h" #include "blocks/motionblocks.h" @@ -1730,3 +1731,94 @@ TEST_F(MotionBlocksTest, SetY) builder.run(); } } + +TEST_F(MotionBlocksTest, IfOnEdgeBounce) +{ + { + auto sprite = std::make_shared(); + SpriteHandlerMock handler; + sprite->setInterface(&handler); + sprite->setEngine(&m_engineMock); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("motion_ifonedgebounce"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, stageWidth()).WillRepeatedly(Return(480)); + EXPECT_CALL(m_engineMock, stageHeight()).WillRepeatedly(Return(360)); + EXPECT_CALL(m_engineMock, spriteFencingEnabled()).WillRepeatedly(Return(false)); + + // No edge + EXPECT_CALL(handler, boundingRect()).WillOnce(Return(Rect(80, 80, 120, 40))); + sprite->setX(100); + sprite->setY(60); + sprite->setDirection(-45); + thread.run(); + + ASSERT_EQ(sprite->x(), 100); + ASSERT_EQ(sprite->y(), 60); + ASSERT_EQ(sprite->direction(), -45); + + // Left edge + EXPECT_CALL(handler, boundingRect()).Times(2).WillRepeatedly(Return(Rect(-260, 80, -220, 40))); + sprite->setX(-240); + sprite->setY(60); + thread.reset(); + thread.run(); + + ASSERT_EQ(sprite->x(), -220); + ASSERT_EQ(sprite->y(), 60); + ASSERT_EQ(std::round(sprite->direction() * 100) / 100, 45); + + // Top edge + EXPECT_CALL(handler, boundingRect()).Times(2).WillRepeatedly(Return(Rect(80, 200, 120, 160))); + sprite->setX(100); + sprite->setY(180); + sprite->setDirection(45); + thread.reset(); + thread.run(); + + ASSERT_EQ(sprite->x(), 100); + ASSERT_EQ(sprite->y(), 160); + ASSERT_EQ(sprite->direction(), 135); + + // Right edge + EXPECT_CALL(handler, boundingRect()).Times(2).WillRepeatedly(Return(Rect(220, 80, 260, 40))); + sprite->setX(240); + sprite->setY(60); + thread.reset(); + thread.run(); + + ASSERT_EQ(sprite->x(), 220); + ASSERT_EQ(sprite->y(), 60); + ASSERT_EQ(sprite->direction(), -135); + + // Bottom edge + EXPECT_CALL(handler, boundingRect()).Times(2).WillRepeatedly(Return(Rect(-120, -160, -80, -200))); + sprite->setX(-100); + sprite->setY(-180); + thread.reset(); + thread.run(); + + ASSERT_EQ(sprite->x(), -100); + ASSERT_EQ(sprite->y(), -160); + ASSERT_EQ(std::round(sprite->direction() * 100) / 100, -45); + } + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_ifonedgebounce"); + + builder.build(); + builder.run(); + } +} From 0c39def30835569079f613e032c4cd4647f266d0 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 1 Mar 2025 11:27:57 +0100 Subject: [PATCH 17/21] Implement motion_setrotationstyle block --- src/blocks/motionblocks.cpp | 43 ++++++++++++++++ src/blocks/motionblocks.h | 1 + test/blocks/motion_blocks_test.cpp | 80 ++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index d6497b90..ffc0ecd3 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,7 @@ void MotionBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "motion_changeyby", &compileChangeYBy); engine->addCompileFunction(this, "motion_sety", &compileSetY); engine->addCompileFunction(this, "motion_ifonedgebounce", &compileIfOnEdgeBounce); + engine->addCompileFunction(this, "motion_setrotationstyle", &compileSetRotationStyle); } CompilerValue *MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -299,6 +301,32 @@ CompilerValue *MotionBlocks::compileIfOnEdgeBounce(Compiler *compiler) return nullptr; } +CompilerValue *MotionBlocks::compileSetRotationStyle(Compiler *compiler) +{ + Target *target = compiler->target(); + + if (target->isStage()) + return nullptr; + + Sprite *sprite = static_cast(target); + Field *field = compiler->field("STYLE"); + + if (!field) + return nullptr; + + std::string option = field->value().toString(); + sprite->setRotationStyle(field->value().toString()); + + if (option == "left-right") + compiler->addTargetFunctionCall("motion_set_left_right_style"); + else if (option == "don't rotate") + compiler->addTargetFunctionCall("motion_set_do_not_rotate_style"); + else if (option == "all around") + compiler->addTargetFunctionCall("motion_set_all_around_style"); + + return nullptr; +} + extern "C" void motion_movesteps(Sprite *sprite, double steps) { double dir = sprite->direction(); @@ -667,6 +695,21 @@ extern "C" void motion_ifonedgebounce(Sprite *sprite) sprite->setPosition(fencedX, fencedY); } +extern "C" void motion_set_left_right_style(Sprite *sprite) +{ + sprite->setRotationStyle(Sprite::RotationStyle::LeftRight); +} + +extern "C" void motion_set_do_not_rotate_style(Sprite *sprite) +{ + sprite->setRotationStyle(Sprite::RotationStyle::DoNotRotate); +} + +extern "C" void motion_set_all_around_style(Sprite *sprite) +{ + sprite->setRotationStyle(Sprite::RotationStyle::AllAround); +} + extern "C" double motion_xposition(Sprite *sprite) { return sprite->x(); diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index 5b9d2e18..3ab5399f 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -31,6 +31,7 @@ class MotionBlocks : public IExtension static CompilerValue *compileChangeYBy(Compiler *compiler); static CompilerValue *compileSetY(Compiler *compiler); static CompilerValue *compileIfOnEdgeBounce(Compiler *compiler); + static CompilerValue *compileSetRotationStyle(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index 749b3721..2ac662c9 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -1822,3 +1822,83 @@ TEST_F(MotionBlocksTest, IfOnEdgeBounce) builder.run(); } } + +TEST_F(MotionBlocksTest, SetRotationStyle) +{ + // left-right + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("motion_setrotationstyle"); + builder.addDropdownField("STYLE", "left-right"); + + builder.build(); + builder.run(); + ASSERT_EQ(sprite->rotationStyle(), Sprite::RotationStyle::LeftRight); + } + + m_engine->clear(); + + // don't rotate + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("motion_setrotationstyle"); + builder.addDropdownField("STYLE", "don't rotate"); + + builder.build(); + builder.run(); + ASSERT_EQ(sprite->rotationStyle(), Sprite::RotationStyle::DoNotRotate); + } + + m_engine->clear(); + + // all around + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("motion_setrotationstyle"); + builder.addDropdownField("STYLE", "all around"); + builder.build(); + + sprite->setRotationStyle(Sprite::RotationStyle::DoNotRotate); + builder.run(); + ASSERT_EQ(sprite->rotationStyle(), Sprite::RotationStyle::AllAround); + } + + m_engine->clear(); + + // invalid + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("motion_setrotationstyle"); + builder.addDropdownField("STYLE", "invalid"); + builder.build(); + + sprite->setRotationStyle(Sprite::RotationStyle::LeftRight); + builder.run(); + ASSERT_EQ(sprite->rotationStyle(), Sprite::RotationStyle::LeftRight); + + sprite->setRotationStyle(Sprite::RotationStyle::AllAround); + builder.run(); + ASSERT_EQ(sprite->rotationStyle(), Sprite::RotationStyle::AllAround); + } + + m_engine->clear(); + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_setrotationstyle"); + builder.addDropdownField("STYLE", "all around"); + + builder.build(); + builder.run(); + } +} From c109fc70bb0dfbd922162623c0d3f1fea488e2cf Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 1 Mar 2025 11:32:11 +0100 Subject: [PATCH 18/21] Implement motion_xposition block --- src/blocks/motionblocks.cpp | 9 +++++++ src/blocks/motionblocks.h | 1 + test/blocks/motion_blocks_test.cpp | 39 ++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index ffc0ecd3..8fe3ec05 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -54,6 +54,7 @@ void MotionBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "motion_sety", &compileSetY); engine->addCompileFunction(this, "motion_ifonedgebounce", &compileIfOnEdgeBounce); engine->addCompileFunction(this, "motion_setrotationstyle", &compileSetRotationStyle); + engine->addCompileFunction(this, "motion_xposition", &compileXPosition); } CompilerValue *MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -327,6 +328,14 @@ CompilerValue *MotionBlocks::compileSetRotationStyle(Compiler *compiler) return nullptr; } +CompilerValue *MotionBlocks::compileXPosition(Compiler *compiler) +{ + if (compiler->target()->isStage()) + return compiler->addConstValue(0); + else + return compiler->addTargetFunctionCall("motion_xposition", Compiler::StaticType::Number); +} + extern "C" void motion_movesteps(Sprite *sprite, double steps) { double dir = sprite->direction(); diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index 3ab5399f..31b64eda 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -32,6 +32,7 @@ class MotionBlocks : public IExtension static CompilerValue *compileSetY(Compiler *compiler); static CompilerValue *compileIfOnEdgeBounce(Compiler *compiler); static CompilerValue *compileSetRotationStyle(Compiler *compiler); + static CompilerValue *compileXPosition(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index 2ac662c9..1929a712 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -1902,3 +1903,41 @@ TEST_F(MotionBlocksTest, SetRotationStyle) builder.run(); } } + +TEST_F(MotionBlocksTest, XPosition) +{ + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("motion_xposition"); + builder.captureBlockReturnValue(); + builder.build(); + + sprite->setX(5.2); + sprite->setY(-0.25); + sprite->setDirection(-61.42); + builder.run(); + + List *list = builder.capturedValues(); + ASSERT_EQ(list->size(), 1); + ASSERT_EQ(Value(list->data()[0]).toDouble(), 5.2); + } + + m_engine->clear(); + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_xposition"); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *list = builder.capturedValues(); + ASSERT_EQ(list->size(), 1); + ASSERT_EQ(Value(list->data()[0]).toDouble(), 0); + } +} From edd51ca725d809d0e43c3d64b022b4260c176fee Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 1 Mar 2025 11:47:12 +0100 Subject: [PATCH 19/21] Implement motion_yposition block --- src/blocks/motionblocks.cpp | 9 +++++++ src/blocks/motionblocks.h | 1 + test/blocks/motion_blocks_test.cpp | 38 ++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index 8fe3ec05..afe1456b 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -55,6 +55,7 @@ void MotionBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "motion_ifonedgebounce", &compileIfOnEdgeBounce); engine->addCompileFunction(this, "motion_setrotationstyle", &compileSetRotationStyle); engine->addCompileFunction(this, "motion_xposition", &compileXPosition); + engine->addCompileFunction(this, "motion_yposition", &compileYPosition); } CompilerValue *MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -336,6 +337,14 @@ CompilerValue *MotionBlocks::compileXPosition(Compiler *compiler) return compiler->addTargetFunctionCall("motion_xposition", Compiler::StaticType::Number); } +CompilerValue *MotionBlocks::compileYPosition(Compiler *compiler) +{ + if (compiler->target()->isStage()) + return compiler->addConstValue(0); + else + return compiler->addTargetFunctionCall("motion_yposition", Compiler::StaticType::Number); +} + extern "C" void motion_movesteps(Sprite *sprite, double steps) { double dir = sprite->direction(); diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index 31b64eda..519ac1e1 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -33,6 +33,7 @@ class MotionBlocks : public IExtension static CompilerValue *compileIfOnEdgeBounce(Compiler *compiler); static CompilerValue *compileSetRotationStyle(Compiler *compiler); static CompilerValue *compileXPosition(Compiler *compiler); + static CompilerValue *compileYPosition(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index 1929a712..46fa1f7e 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -1941,3 +1941,41 @@ TEST_F(MotionBlocksTest, XPosition) ASSERT_EQ(Value(list->data()[0]).toDouble(), 0); } } + +TEST_F(MotionBlocksTest, YPosition) +{ + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("motion_yposition"); + builder.captureBlockReturnValue(); + builder.build(); + + sprite->setX(5.2); + sprite->setY(-0.25); + sprite->setDirection(-61.42); + builder.run(); + + List *list = builder.capturedValues(); + ASSERT_EQ(list->size(), 1); + ASSERT_EQ(Value(list->data()[0]).toDouble(), -0.25); + } + + m_engine->clear(); + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_yposition"); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *list = builder.capturedValues(); + ASSERT_EQ(list->size(), 1); + ASSERT_EQ(Value(list->data()[0]).toDouble(), 0); + } +} From 57855e8a67edea6554434004c187bcf58d5b8ea6 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 1 Mar 2025 11:49:59 +0100 Subject: [PATCH 20/21] Implement motion_direction block --- src/blocks/motionblocks.cpp | 14 +++++++++++ src/blocks/motionblocks.h | 1 + test/blocks/motion_blocks_test.cpp | 38 ++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index afe1456b..1c989898 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -56,6 +56,7 @@ void MotionBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "motion_setrotationstyle", &compileSetRotationStyle); engine->addCompileFunction(this, "motion_xposition", &compileXPosition); engine->addCompileFunction(this, "motion_yposition", &compileYPosition); + engine->addCompileFunction(this, "motion_direction", &compileDirection); } CompilerValue *MotionBlocks::compileMoveSteps(Compiler *compiler) @@ -345,6 +346,14 @@ CompilerValue *MotionBlocks::compileYPosition(Compiler *compiler) return compiler->addTargetFunctionCall("motion_yposition", Compiler::StaticType::Number); } +CompilerValue *MotionBlocks::compileDirection(Compiler *compiler) +{ + if (compiler->target()->isStage()) + return compiler->addConstValue(90); + else + return compiler->addTargetFunctionCall("motion_direction", Compiler::StaticType::Number); +} + extern "C" void motion_movesteps(Sprite *sprite, double steps) { double dir = sprite->direction(); @@ -737,3 +746,8 @@ extern "C" double motion_yposition(Sprite *sprite) { return sprite->y(); } + +extern "C" double motion_direction(Sprite *sprite) +{ + return sprite->direction(); +} diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index 519ac1e1..bf81b123 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -34,6 +34,7 @@ class MotionBlocks : public IExtension static CompilerValue *compileSetRotationStyle(Compiler *compiler); static CompilerValue *compileXPosition(Compiler *compiler); static CompilerValue *compileYPosition(Compiler *compiler); + static CompilerValue *compileDirection(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index 46fa1f7e..f33deec8 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -1979,3 +1979,41 @@ TEST_F(MotionBlocksTest, YPosition) ASSERT_EQ(Value(list->data()[0]).toDouble(), 0); } } + +TEST_F(MotionBlocksTest, Direction) +{ + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("motion_direction"); + builder.captureBlockReturnValue(); + builder.build(); + + sprite->setX(5.2); + sprite->setY(-0.25); + sprite->setDirection(-61.42); + builder.run(); + + List *list = builder.capturedValues(); + ASSERT_EQ(list->size(), 1); + ASSERT_EQ(Value(list->data()[0]).toDouble(), -61.42); + } + + m_engine->clear(); + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("motion_direction"); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *list = builder.capturedValues(); + ASSERT_EQ(list->size(), 1); + ASSERT_EQ(Value(list->data()[0]).toDouble(), 90); + } +} From c9fd5efd9a2ca5eed98519cd70e55e93c3f6a12d Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 1 Mar 2025 12:16:57 +0100 Subject: [PATCH 21/21] Set random direction in point towards random block --- src/blocks/motionblocks.cpp | 11 ++++------- test/blocks/motion_blocks_test.cpp | 16 +++++----------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index 1c989898..ae565a28 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -112,7 +112,7 @@ CompilerValue *MotionBlocks::compilePointTowards(Compiler *compiler) if (value == "_mouse_") compiler->addTargetFunctionCall("motion_point_towards_mouse"); else if (value == "_random_") - compiler->addFunctionCallWithCtx("motion_point_towards_random_pos"); + compiler->addFunctionCallWithCtx("motion_point_towards_random_direction"); else { int index = compiler->engine()->findTarget(value); Target *anotherTarget = compiler->engine()->targetAt(index); @@ -398,14 +398,11 @@ extern "C" void motion_point_towards_mouse(Sprite *sprite) motion_point_towards_pos(sprite, engine->mouseX(), engine->mouseY()); } -extern "C" void motion_point_towards_random_pos(ExecutionContext *ctx) +extern "C" void motion_point_towards_random_direction(ExecutionContext *ctx) { Sprite *sprite = static_cast(ctx->thread()->target()); - IEngine *engine = ctx->engine(); - const int stageWidth = engine->stageWidth(); - const int stageHeight = engine->stageHeight(); IRandomGenerator *rng = ctx->rng(); - motion_point_towards_pos(sprite, rng->randintDouble(-stageWidth / 2.0, stageWidth / 2.0), rng->randintDouble(-stageHeight / 2.0, stageHeight / 2.0)); + sprite->setDirection(rng->randint(-180, 179)); } extern "C" void motion_point_towards_target_by_index(Sprite *sprite, double index) @@ -424,7 +421,7 @@ extern "C" void motion_pointtowards(ExecutionContext *ctx, const StringPtr *towa if (string_compare_case_sensitive(towards, &MOUSE_STR) == 0) motion_point_towards_mouse(sprite); else if (string_compare_case_sensitive(towards, &RANDOM_STR) == 0) - motion_point_towards_random_pos(ctx); + motion_point_towards_random_direction(ctx); else { // TODO: Use UTF-16 in engine std::string u8name = utf8::utf16to8(std::u16string(towards->data)); diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index f33deec8..a6788c57 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -224,7 +224,7 @@ TEST_F(MotionBlocksTest, PointTowardsMouse) } } -TEST_F(MotionBlocksTest, PointTowardsRandomPosition) +TEST_F(MotionBlocksTest, PointTowardsRandomDirection) { { auto sprite = std::make_shared(); @@ -247,12 +247,9 @@ TEST_F(MotionBlocksTest, PointTowardsRandomPosition) RandomGeneratorMock rng; ctx->setRng(&rng); - EXPECT_CALL(m_engineMock, stageWidth()).WillOnce(Return(640)); - EXPECT_CALL(m_engineMock, stageHeight()).WillOnce(Return(500)); - EXPECT_CALL(rng, randintDouble(-320, 320)).WillOnce(Return(95.2)); - EXPECT_CALL(rng, randintDouble(-250, 250)).WillOnce(Return(-100.025)); + EXPECT_CALL(rng, randint(-180, 179)).WillOnce(Return(95)); code->run(ctx.get()); - ASSERT_EQ(std::round(sprite->direction() * 100) / 100, 90); + ASSERT_EQ(sprite->direction(), 95); } { @@ -276,12 +273,9 @@ TEST_F(MotionBlocksTest, PointTowardsRandomPosition) RandomGeneratorMock rng; ctx->setRng(&rng); - EXPECT_CALL(m_engineMock, stageWidth()).WillOnce(Return(640)); - EXPECT_CALL(m_engineMock, stageHeight()).WillOnce(Return(500)); - EXPECT_CALL(rng, randintDouble(-320, 320)).WillOnce(Return(-21.28)); - EXPECT_CALL(rng, randintDouble(-250, 250)).WillOnce(Return(-100.025)); + EXPECT_CALL(rng, randint(-180, 179)).WillOnce(Return(-42)); code->run(ctx.get()); - ASSERT_EQ(std::round(sprite->direction() * 100) / 100, -90); + ASSERT_EQ(sprite->direction(), -42); } {