From 3877ac0d08546586345db87e00ae0e06edf07e1a Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 18 Sep 2023 09:11:53 +0200
Subject: [PATCH 01/49] Fix gcc 12 build errors

---
 include/scratchcpp/virtualmachine.h | 1 +
 src/engine/compiler_p.h             | 1 +
 src/scratch/block_p.h               | 1 +
 3 files changed, 3 insertions(+)

diff --git a/include/scratchcpp/virtualmachine.h b/include/scratchcpp/virtualmachine.h
index 41dd40ea..a293829b 100644
--- a/include/scratchcpp/virtualmachine.h
+++ b/include/scratchcpp/virtualmachine.h
@@ -2,6 +2,7 @@
 
 #pragma once
 
+#include <vector>
 #include <scratchcpp/value.h>
 
 #include "spimpl.h"
diff --git a/src/engine/compiler_p.h b/src/engine/compiler_p.h
index 2b99935b..0fab8fbe 100644
--- a/src/engine/compiler_p.h
+++ b/src/engine/compiler_p.h
@@ -2,6 +2,7 @@
 
 #pragma once
 
+#include <unordered_map>
 #include <scratchcpp/compiler.h>
 #include <scratchcpp/inputvalue.h>
 
diff --git a/src/scratch/block_p.h b/src/scratch/block_p.h
index 082e24a2..a8b36b97 100644
--- a/src/scratch/block_p.h
+++ b/src/scratch/block_p.h
@@ -2,6 +2,7 @@
 
 #pragma once
 
+#include <unordered_map>
 #include <scratchcpp/blockprototype.h>
 #include <scratchcpp/inputvalue.h>
 

From b573dd9ae3dcfe9a6c383c221e460704018aeecc Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 18 Sep 2023 09:45:47 +0200
Subject: [PATCH 02/49] Add a way to change FPS

---
 include/scratchcpp/iengine.h   |  6 ++++++
 src/engine/internal/engine.cpp | 20 ++++++++++++++++++--
 src/engine/internal/engine.h   |  7 +++++++
 test/engine/engine_test.cpp    |  9 +++++++++
 test/mocks/enginemock.h        |  3 +++
 5 files changed, 43 insertions(+), 2 deletions(-)

diff --git a/include/scratchcpp/iengine.h b/include/scratchcpp/iengine.h
index b622f665..ac4b7ce8 100644
--- a/include/scratchcpp/iengine.h
+++ b/include/scratchcpp/iengine.h
@@ -85,6 +85,12 @@ class LIBSCRATCHCPP_EXPORT IEngine
          */
         virtual void run() = 0;
 
+        /*! Returns the framerate of the project. */
+        virtual double fps() const = 0;
+
+        /*! Sets the framerate of the project. */
+        virtual void setFps(double fps) = 0;
+
         /*! Returns true if there are any running script of the broadcast with the given index. */
         virtual bool broadcastRunning(unsigned int index, VirtualMachine *sourceScript) = 0;
 
diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp
index 135d928d..de5f9809 100644
--- a/src/engine/internal/engine.cpp
+++ b/src/engine/internal/engine.cpp
@@ -317,7 +317,7 @@ void Engine::initClone(Sprite *clone)
 
 void Engine::run()
 {
-    auto frameDuration = std::chrono::milliseconds(33);
+    updateFrameDuration();
     start();
 
     while (true) {
@@ -332,7 +332,7 @@ void Engine::run()
         // Sleep until the time for the next frame
         auto currentTime = std::chrono::steady_clock::now();
         auto elapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - lastFrameTime);
-        auto sleepTime = frameDuration - elapsedTime;
+        auto sleepTime = m_frameDuration - elapsedTime;
         bool timeOut = sleepTime <= std::chrono::milliseconds::zero();
 
         if (!timeOut && !m_skipFrame)
@@ -346,6 +346,17 @@ void Engine::run()
     }
 }
 
+double Engine::fps() const
+{
+    return m_fps;
+}
+
+void Engine::setFps(double fps)
+{
+    m_fps = fps;
+    updateFrameDuration();
+}
+
 bool Engine::broadcastRunning(unsigned int index, VirtualMachine *sourceScript)
 {
     const auto &scripts = m_runningBroadcastMap[index];
@@ -757,3 +768,8 @@ BlockSectionContainer *Engine::blockSectionContainer(IBlockSection *section) con
 
     return nullptr;
 }
+
+void Engine::updateFrameDuration()
+{
+    m_frameDuration = std::chrono::milliseconds(static_cast<long>(1000 / m_fps));
+}
diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h
index 04f519ba..94795894 100644
--- a/src/engine/internal/engine.h
+++ b/src/engine/internal/engine.h
@@ -36,6 +36,9 @@ class Engine : public IEngine
         void initClone(libscratchcpp::Sprite *clone) override;
         void run() override;
 
+        double fps() const override;
+        void setFps(double fps) override;
+
         bool broadcastRunning(unsigned int index, VirtualMachine *sourceScript) override;
 
         void breakFrame() override;
@@ -91,6 +94,8 @@ class Engine : public IEngine
         std::shared_ptr<Entity> getEntity(const std::string &id);
         std::shared_ptr<IBlockSection> blockSection(const std::string &opcode) const;
 
+        void updateFrameDuration();
+
         std::unordered_map<std::shared_ptr<IBlockSection>, std::unique_ptr<BlockSectionContainer>> m_sections;
         std::vector<std::shared_ptr<Target>> m_targets;
         std::vector<std::shared_ptr<Broadcast>> m_broadcasts;
@@ -107,6 +112,8 @@ class Engine : public IEngine
 
         std::unique_ptr<ITimer> m_defaultTimer;
         ITimer *m_timer = nullptr;
+        double m_fps = 30;                         // default FPS
+        std::chrono::milliseconds m_frameDuration; // will be computed in run()
 
         bool m_breakFrame = false;
         bool m_skipFrame = false;
diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp
index 12620f4a..e862ec7a 100644
--- a/test/engine/engine_test.cpp
+++ b/test/engine/engine_test.cpp
@@ -34,6 +34,15 @@ TEST(EngineTest, Clear)
     ASSERT_TRUE(engine.registeredSections().empty());
 }
 
+TEST(EngineTest, Fps)
+{
+    Engine engine;
+    ASSERT_EQ(engine.fps(), 30);
+
+    engine.setFps(60.25);
+    ASSERT_EQ(engine.fps(), 60.25);
+}
+
 TEST(EngineTest, BreakFrame)
 {
     Engine engine;
diff --git a/test/mocks/enginemock.h b/test/mocks/enginemock.h
index 0beda8ec..2d874d79 100644
--- a/test/mocks/enginemock.h
+++ b/test/mocks/enginemock.h
@@ -24,6 +24,9 @@ class EngineMock : public IEngine
         MOCK_METHOD(void, initClone, (Sprite *), (override));
         MOCK_METHOD(void, run, (), (override));
 
+        MOCK_METHOD(double, fps, (), (const, override));
+        MOCK_METHOD(void, setFps, (double fps), (override));
+
         MOCK_METHOD(bool, broadcastRunning, (unsigned int, VirtualMachine *), (override));
 
         MOCK_METHOD(void, breakFrame, (), (override));

From 3fc9ff84f3fdaf84190c731af2e708743a4af697 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 18 Sep 2023 09:48:02 +0200
Subject: [PATCH 03/49] README: Mark custom FPS as finished

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 368812fc..420d40ae 100644
--- a/README.md
+++ b/README.md
@@ -135,7 +135,7 @@ int main(int argc, char **argv) {
 - [ ] Project metadata
 - [ ] Turbo mode
 - [ ] Multithreading (experimental)
-- [ ] Custom FPS in the default event loop (`libscratchcpp::Engine::run()`)
+- [x] Custom FPS in the default event loop (`libscratchcpp::Engine::run()`)
 - [ ] Scratch 2.0 to 3.0 converter (help needed)
 - [ ] Scratch 1.4 and below to 3.0 converter (help needed)
 - [ ] API for comments

From 609fdf9c7240ef10271942a7d12fca9b5e040dea Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 18 Sep 2023 09:53:17 +0200
Subject: [PATCH 04/49] Asset: Add virtual destructor

---
 include/scratchcpp/asset.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/include/scratchcpp/asset.h b/include/scratchcpp/asset.h
index 98826f76..0b2f04c9 100644
--- a/include/scratchcpp/asset.h
+++ b/include/scratchcpp/asset.h
@@ -18,6 +18,8 @@ class LIBSCRATCHCPP_EXPORT Asset : public Entity
         Asset(const std::string &name, const std::string &id, const std::string &format);
         Asset(const Asset &) = delete;
 
+        virtual ~Asset() { }
+
         void setId(const std::string &id);
 
         const std::string &name() const;

From 69d770e90811957066432bd7ff306c318ae84d0f Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 18 Sep 2023 16:40:10 +0200
Subject: [PATCH 05/49] fix #186: Keep track of whether the project is running

---
 src/engine/internal/engine.cpp | 9 ++++++++-
 src/engine/internal/engine.h   | 1 +
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp
index 135d928d..7176d70d 100644
--- a/src/engine/internal/engine.cpp
+++ b/src/engine/internal/engine.cpp
@@ -132,7 +132,7 @@ void Engine::frame()
 
         do {
             script->run();
-            if (script->atEnd()) {
+            if (script->atEnd() && m_running) {
                 for (auto &[key, value] : m_runningBroadcastMap) {
                     size_t index = 0;
 
@@ -152,6 +152,8 @@ void Engine::frame()
         } while (!script->atEnd() && !m_breakFrame);
     }
 
+    assert(m_running || m_scriptsToRemove.empty());
+
     for (auto script : m_scriptsToRemove) {
         size_t index = -1;
         for (size_t i = 0; i < m_runningScripts.size(); i++) {
@@ -169,6 +171,7 @@ void Engine::frame()
 void Engine::start()
 {
     m_timer->reset();
+    m_running = true;
 
     for (auto target : m_targets) {
         auto gfBlocks = target->greenFlagBlocks();
@@ -180,6 +183,8 @@ void Engine::start()
 void Engine::stop()
 {
     m_runningScripts.clear();
+    m_scriptsToRemove.clear();
+    m_running = false;
 }
 
 void Engine::startScript(std::shared_ptr<Block> topLevelBlock, std::shared_ptr<Target> target)
@@ -344,6 +349,8 @@ void Engine::run()
 
         lastFrameTime = currentTime;
     }
+
+    stop();
 }
 
 bool Engine::broadcastRunning(unsigned int index, VirtualMachine *sourceScript)
diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h
index 04f519ba..eb9f5c89 100644
--- a/src/engine/internal/engine.h
+++ b/src/engine/internal/engine.h
@@ -108,6 +108,7 @@ class Engine : public IEngine
         std::unique_ptr<ITimer> m_defaultTimer;
         ITimer *m_timer = nullptr;
 
+        bool m_running = false;
         bool m_breakFrame = false;
         bool m_skipFrame = false;
         bool m_lockFrame = false;

From 65463e459ffa1fe349b470e5ef0d245cbe2085b1 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 18 Sep 2023 16:40:34 +0200
Subject: [PATCH 06/49] Add test for #186

---
 test/engine/engine_test.cpp                    |   8 ++++++++
 test/regtest_projects/186_crash_after_stop.sb3 | Bin 0 -> 1025 bytes
 2 files changed, 8 insertions(+)
 create mode 100644 test/regtest_projects/186_crash_after_stop.sb3

diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp
index 12620f4a..443d29f4 100644
--- a/test/engine/engine_test.cpp
+++ b/test/engine/engine_test.cpp
@@ -398,3 +398,11 @@ TEST(EngineTest, Clones)
             ASSERT_EQ((*list)[i].toString(), "1 2"); // TODO: Change this to "12" after #188 is fixed
     }
 }
+
+TEST(EngineTest, NoCrashAfterStop)
+{
+    // Regtest for #186
+    Project p("regtest_projects/186_crash_after_stop.sb3");
+    ASSERT_TRUE(p.load());
+    p.run();
+}
diff --git a/test/regtest_projects/186_crash_after_stop.sb3 b/test/regtest_projects/186_crash_after_stop.sb3
new file mode 100644
index 0000000000000000000000000000000000000000..5ca02a980edd6bbef4865a3c6b5b4cfcb40152c9
GIT binary patch

From 999608b7ef6412f48673500dc188f30f29d7a338 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 19 Sep 2023 18:52:00 +0200
Subject: [PATCH 07/49] fix #213: Fix a build error in the str_at instruction

---
 src/engine/virtualmachine_p.cpp | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/src/engine/virtualmachine_p.cpp b/src/engine/virtualmachine_p.cpp
index 2fb13ad6..27ce7cda 100644
--- a/src/engine/virtualmachine_p.cpp
+++ b/src/engine/virtualmachine_p.cpp
@@ -653,12 +653,14 @@ do_list_get_item : {
 
 do_str_at : {
     size_t index = READ_REG(1, 2)->toLong() - 1;
-    std::u16string str = READ_REG(0, 2)->toUtf16();
-    if (index < 0 || index >= str.size())
-        REPLACE_RET_VALUE("", 2);
-    else
-        REPLACE_RET_VALUE(utf8::utf16to8(std::u16string({ str[index] })), 2);
-    FREE_REGS(1);
+    {
+        std::u16string str = READ_REG(0, 2)->toUtf16();
+        if (index < 0 || index >= str.size())
+            REPLACE_RET_VALUE("", 2);
+        else
+            REPLACE_RET_VALUE(utf8::utf16to8(std::u16string({ str[index] })), 2);
+        FREE_REGS(1);
+    }
     DISPATCH();
 }
 

From e8c3f5b4206d989cbcedcb75d1b77eab67fca495 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 19 Sep 2023 19:10:45 +0200
Subject: [PATCH 08/49] fix #210: Initialize clock before using it in
 SensingBlocks

---
 src/blocks/sensingblocks.cpp        |  5 ++++-
 test/blocks/sensing_blocks_test.cpp | 13 ++++++++++---
 2 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/src/blocks/sensingblocks.cpp b/src/blocks/sensingblocks.cpp
index 24e2b056..ae783086 100644
--- a/src/blocks/sensingblocks.cpp
+++ b/src/blocks/sensingblocks.cpp
@@ -10,7 +10,7 @@
 
 using namespace libscratchcpp;
 
-IClock *SensingBlocks::clock = Clock::instance().get();
+IClock *SensingBlocks::clock = nullptr;
 
 std::string SensingBlocks::name() const
 {
@@ -54,6 +54,9 @@ unsigned int SensingBlocks::resetTimer(VirtualMachine *vm)
 
 unsigned int SensingBlocks::daysSince2000(VirtualMachine *vm)
 {
+    if (!clock)
+        clock = Clock::instance().get();
+
     auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(clock->currentSystemTime().time_since_epoch()).count();
     vm->addReturnValue(ms / 86400000.0 - 10957);
 
diff --git a/test/blocks/sensing_blocks_test.cpp b/test/blocks/sensing_blocks_test.cpp
index c0b93753..c1d6a17b 100644
--- a/test/blocks/sensing_blocks_test.cpp
+++ b/test/blocks/sensing_blocks_test.cpp
@@ -138,15 +138,22 @@ TEST_F(SensingBlocksTest, DaysSince2000Impl)
 
     VirtualMachine vm(nullptr, &m_engineMock, nullptr);
     vm.setFunctions(functions);
+    vm.setBytecode(bytecode);
 
     std::chrono::system_clock::time_point time(std::chrono::milliseconds(1011243120562)); // Jan 17 2002 04:52:00
     EXPECT_CALL(m_clockMock, currentSystemTime()).WillOnce(Return(time));
 
+    vm.run(); // Test with the default clock - shouldn't crash (see #210)
+
+    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
+    ASSERT_EQ(vm.registerCount(), 1);
+    ASSERT_EQ(std::round(vm.getInput(0, 1)->toDouble() * 100) / 100, std::round((ms / 86400000.0 - 10957) * 100) / 100);
+
     SensingBlocks::clock = &m_clockMock;
-    vm.setBytecode(bytecode);
+    vm.reset();
     vm.run();
-    SensingBlocks::clock = Clock::instance().get();
+    SensingBlocks::clock = nullptr;
 
-    ASSERT_EQ(vm.registerCount(), 1);
+    ASSERT_EQ(vm.registerCount(), 2); // TODO: Change this to 1 after task #215 is completed
     ASSERT_EQ(vm.getInput(0, 1)->toDouble(), 747.20278428240817);
 }

From 61129faa8840a91b73cf9a531ced29f61d283d4a Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 20 Sep 2023 19:48:28 +0200
Subject: [PATCH 09/49] Implement sensing_current block

---
 src/blocks/sensingblocks.cpp        | 115 ++++++++++++++++
 src/blocks/sensingblocks.h          |  24 ++++
 test/blocks/sensing_blocks_test.cpp | 206 ++++++++++++++++++++++++++++
 3 files changed, 345 insertions(+)

diff --git a/src/blocks/sensingblocks.cpp b/src/blocks/sensingblocks.cpp
index ae783086..168aa1ec 100644
--- a/src/blocks/sensingblocks.cpp
+++ b/src/blocks/sensingblocks.cpp
@@ -4,6 +4,7 @@
 #include <scratchcpp/compiler.h>
 #include <scratchcpp/iengine.h>
 #include <scratchcpp/itimer.h>
+#include <scratchcpp/field.h>
 #include "sensingblocks.h"
 
 #include "../engine/internal/clock.h"
@@ -22,7 +23,20 @@ void SensingBlocks::registerBlocks(IEngine *engine)
     // Blocks
     engine->addCompileFunction(this, "sensing_timer", &compileTimer);
     engine->addCompileFunction(this, "sensing_resettimer", &compileResetTimer);
+    engine->addCompileFunction(this, "sensing_current", &compileCurrent);
     engine->addCompileFunction(this, "sensing_dayssince2000", &compileDaysSince2000);
+
+    // Fields
+    engine->addField(this, "CURRENTMENU", CURRENTMENU);
+
+    // Field values
+    engine->addFieldValue(this, "YEAR", YEAR);
+    engine->addFieldValue(this, "MONTH", MONTH);
+    engine->addFieldValue(this, "DATE", DATE);
+    engine->addFieldValue(this, "DAYOFWEEK", DAYOFWEEK);
+    engine->addFieldValue(this, "HOUR", HOUR);
+    engine->addFieldValue(this, "MINUTE", MINUTE);
+    engine->addFieldValue(this, "SECOND", SECOND);
 }
 
 void SensingBlocks::compileTimer(Compiler *compiler)
@@ -35,6 +49,44 @@ void SensingBlocks::compileResetTimer(Compiler *compiler)
     compiler->addFunctionCall(&resetTimer);
 }
 
+void SensingBlocks::compileCurrent(Compiler *compiler)
+{
+    int id = compiler->field(CURRENTMENU)->specialValueId();
+
+    switch (id) {
+        case YEAR:
+            compiler->addFunctionCall(&currentYear);
+            break;
+
+        case MONTH:
+            compiler->addFunctionCall(&currentMonth);
+            break;
+
+        case DATE:
+            compiler->addFunctionCall(&currentDate);
+            break;
+
+        case DAYOFWEEK:
+            compiler->addFunctionCall(&currentDayOfWeek);
+            break;
+
+        case HOUR:
+            compiler->addFunctionCall(&currentHour);
+            break;
+
+        case MINUTE:
+            compiler->addFunctionCall(&currentMinute);
+            break;
+
+        case SECOND:
+            compiler->addFunctionCall(&currentSecond);
+            break;
+
+        default:
+            break;
+    }
+}
+
 void SensingBlocks::compileDaysSince2000(Compiler *compiler)
 {
     compiler->addFunctionCall(&daysSince2000);
@@ -52,6 +104,69 @@ unsigned int SensingBlocks::resetTimer(VirtualMachine *vm)
     return 0;
 }
 
+unsigned int SensingBlocks::currentYear(VirtualMachine *vm)
+{
+    time_t now = time(0);
+    tm *ltm = localtime(&now);
+    vm->addReturnValue(ltm->tm_year + 1900);
+
+    return 0;
+}
+
+unsigned int SensingBlocks::currentMonth(VirtualMachine *vm)
+{
+    time_t now = time(0);
+    tm *ltm = localtime(&now);
+    vm->addReturnValue(ltm->tm_mon + 1);
+
+    return 0;
+}
+
+unsigned int SensingBlocks::currentDate(VirtualMachine *vm)
+{
+    time_t now = time(0);
+    tm *ltm = localtime(&now);
+    vm->addReturnValue(ltm->tm_mday);
+
+    return 0;
+}
+
+unsigned int SensingBlocks::currentDayOfWeek(VirtualMachine *vm)
+{
+    time_t now = time(0);
+    tm *ltm = localtime(&now);
+    vm->addReturnValue(ltm->tm_wday + 1);
+
+    return 0;
+}
+
+unsigned int SensingBlocks::currentHour(VirtualMachine *vm)
+{
+    time_t now = time(0);
+    tm *ltm = localtime(&now);
+    vm->addReturnValue(ltm->tm_hour);
+
+    return 0;
+}
+
+unsigned int SensingBlocks::currentMinute(VirtualMachine *vm)
+{
+    time_t now = time(0);
+    tm *ltm = localtime(&now);
+    vm->addReturnValue(ltm->tm_min);
+
+    return 0;
+}
+
+unsigned int SensingBlocks::currentSecond(VirtualMachine *vm)
+{
+    time_t now = time(0);
+    tm *ltm = localtime(&now);
+    vm->addReturnValue(ltm->tm_sec);
+
+    return 0;
+}
+
 unsigned int SensingBlocks::daysSince2000(VirtualMachine *vm)
 {
     if (!clock)
diff --git a/src/blocks/sensingblocks.h b/src/blocks/sensingblocks.h
index 6fcada74..bbb8918d 100644
--- a/src/blocks/sensingblocks.h
+++ b/src/blocks/sensingblocks.h
@@ -12,16 +12,40 @@ namespace libscratchcpp
 class SensingBlocks : public IBlockSection
 {
     public:
+        enum Fields
+        {
+            CURRENTMENU
+        };
+
+        enum FieldValues
+        {
+            YEAR,
+            MONTH,
+            DATE,
+            DAYOFWEEK,
+            HOUR,
+            MINUTE,
+            SECOND
+        };
+
         std::string name() const override;
 
         void registerBlocks(IEngine *engine) override;
 
         static void compileTimer(Compiler *compiler);
         static void compileResetTimer(Compiler *compiler);
+        static void compileCurrent(Compiler *compiler);
         static void compileDaysSince2000(Compiler *compiler);
 
         static unsigned int timer(VirtualMachine *vm);
         static unsigned int resetTimer(VirtualMachine *vm);
+        static unsigned int currentYear(VirtualMachine *vm);
+        static unsigned int currentMonth(VirtualMachine *vm);
+        static unsigned int currentDate(VirtualMachine *vm);
+        static unsigned int currentDayOfWeek(VirtualMachine *vm);
+        static unsigned int currentHour(VirtualMachine *vm);
+        static unsigned int currentMinute(VirtualMachine *vm);
+        static unsigned int currentSecond(VirtualMachine *vm);
         static unsigned int daysSince2000(VirtualMachine *vm);
 
         static IClock *clock;
diff --git a/test/blocks/sensing_blocks_test.cpp b/test/blocks/sensing_blocks_test.cpp
index c1d6a17b..e7fd5bae 100644
--- a/test/blocks/sensing_blocks_test.cpp
+++ b/test/blocks/sensing_blocks_test.cpp
@@ -1,5 +1,6 @@
 #include <scratchcpp/compiler.h>
 #include <scratchcpp/block.h>
+#include <scratchcpp/field.h>
 #include <enginemock.h>
 #include <timermock.h>
 #include <clockmock.h>
@@ -21,6 +22,18 @@ class SensingBlocksTest : public testing::Test
             m_section->registerBlocks(&m_engine);
         }
 
+        std::shared_ptr<Block> createSensingCurrentBlock(const std::string &id, const std::string &optionStr, SensingBlocks::FieldValues option)
+        {
+            auto block = std::make_shared<Block>(id, "sensing_current");
+            auto optionField = std::make_shared<Field>("CURRENTMENU", optionStr);
+            optionField->setFieldId(SensingBlocks::CURRENTMENU);
+            optionField->setSpecialValueId(option);
+            block->addField(optionField);
+            block->updateFieldMap();
+
+            return block;
+        }
+
         std::unique_ptr<IBlockSection> m_section;
         EngineMock m_engineMock;
         Engine m_engine;
@@ -43,8 +56,21 @@ TEST_F(SensingBlocksTest, RegisterBlocks)
     // Blocks
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_timer", &SensingBlocks::compileTimer)).Times(1);
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_resettimer", &SensingBlocks::compileResetTimer)).Times(1);
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_current", &SensingBlocks::compileCurrent)).Times(1);
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_dayssince2000", &SensingBlocks::compileDaysSince2000)).Times(1);
 
+    // Fields
+    EXPECT_CALL(m_engineMock, addField(m_section.get(), "CURRENTMENU", SensingBlocks::CURRENTMENU));
+
+    // Field values
+    EXPECT_CALL(m_engineMock, addFieldValue(m_section.get(), "YEAR", SensingBlocks::YEAR));
+    EXPECT_CALL(m_engineMock, addFieldValue(m_section.get(), "MONTH", SensingBlocks::MONTH));
+    EXPECT_CALL(m_engineMock, addFieldValue(m_section.get(), "DATE", SensingBlocks::DATE));
+    EXPECT_CALL(m_engineMock, addFieldValue(m_section.get(), "DAYOFWEEK", SensingBlocks::DAYOFWEEK));
+    EXPECT_CALL(m_engineMock, addFieldValue(m_section.get(), "HOUR", SensingBlocks::HOUR));
+    EXPECT_CALL(m_engineMock, addFieldValue(m_section.get(), "MINUTE", SensingBlocks::MINUTE));
+    EXPECT_CALL(m_engineMock, addFieldValue(m_section.get(), "SECOND", SensingBlocks::SECOND));
+
     m_section->registerBlocks(&m_engineMock);
 }
 
@@ -115,6 +141,186 @@ TEST_F(SensingBlocksTest, ResetTimerImpl)
     ASSERT_EQ(vm.registerCount(), 0);
 }
 
+TEST_F(SensingBlocksTest, Current)
+{
+    Compiler compiler(&m_engineMock);
+
+    // current [year]
+    auto block1 = createSensingCurrentBlock("a", "YEAR", SensingBlocks::YEAR);
+
+    // current [month]
+    auto block2 = createSensingCurrentBlock("b", "MONTH", SensingBlocks::MONTH);
+
+    // current [date]
+    auto block3 = createSensingCurrentBlock("c", "DATE", SensingBlocks::DATE);
+
+    // current [day of week]
+    auto block4 = createSensingCurrentBlock("d", "DAYOFWEEK", SensingBlocks::DAYOFWEEK);
+
+    // current [hour]
+    auto block5 = createSensingCurrentBlock("e", "HOUR", SensingBlocks::HOUR);
+
+    // current [minute]
+    auto block6 = createSensingCurrentBlock("f", "MINUTE", SensingBlocks::MINUTE);
+
+    // current [second]
+    auto block7 = createSensingCurrentBlock("g", "SECOND", SensingBlocks::SECOND);
+
+    compiler.init();
+
+    EXPECT_CALL(m_engineMock, functionIndex(&SensingBlocks::currentYear)).WillOnce(Return(0));
+    compiler.setBlock(block1);
+    SensingBlocks::compileCurrent(&compiler);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&SensingBlocks::currentMonth)).WillOnce(Return(1));
+    compiler.setBlock(block2);
+    SensingBlocks::compileCurrent(&compiler);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&SensingBlocks::currentDate)).WillOnce(Return(2));
+    compiler.setBlock(block3);
+    SensingBlocks::compileCurrent(&compiler);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&SensingBlocks::currentDayOfWeek)).WillOnce(Return(3));
+    compiler.setBlock(block4);
+    SensingBlocks::compileCurrent(&compiler);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&SensingBlocks::currentHour)).WillOnce(Return(4));
+    compiler.setBlock(block5);
+    SensingBlocks::compileCurrent(&compiler);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&SensingBlocks::currentMinute)).WillOnce(Return(5));
+    compiler.setBlock(block6);
+    SensingBlocks::compileCurrent(&compiler);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&SensingBlocks::currentSecond)).WillOnce(Return(6));
+    compiler.setBlock(block7);
+    SensingBlocks::compileCurrent(&compiler);
+    compiler.end();
+
+    ASSERT_EQ(
+        compiler.bytecode(),
+        std::vector<unsigned int>({ vm::OP_START, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_EXEC, 3, vm::OP_EXEC, 4, vm::OP_EXEC, 5, vm::OP_EXEC, 6, vm::OP_HALT }));
+}
+
+TEST_F(SensingBlocksTest, CurrentYear)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &SensingBlocks::currentYear };
+
+    VirtualMachine vm;
+    vm.setBytecode(bytecode);
+    vm.setFunctions(functions);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 1);
+
+    time_t now = time(0);
+    tm *ltm = localtime(&now);
+    ASSERT_EQ(vm.getInput(0, 1)->toDouble(), ltm->tm_year + 1900);
+}
+
+TEST_F(SensingBlocksTest, CurrentMonth)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &SensingBlocks::currentMonth };
+
+    VirtualMachine vm;
+    vm.setBytecode(bytecode);
+    vm.setFunctions(functions);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 1);
+
+    time_t now = time(0);
+    tm *ltm = localtime(&now);
+    ASSERT_EQ(vm.getInput(0, 1)->toDouble(), ltm->tm_mon + 1);
+}
+
+TEST_F(SensingBlocksTest, CurrentDate)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &SensingBlocks::currentDate };
+
+    VirtualMachine vm;
+    vm.setBytecode(bytecode);
+    vm.setFunctions(functions);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 1);
+
+    time_t now = time(0);
+    tm *ltm = localtime(&now);
+    ASSERT_EQ(vm.getInput(0, 1)->toDouble(), ltm->tm_mday);
+}
+
+TEST_F(SensingBlocksTest, CurrentDayOfWeek)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &SensingBlocks::currentDayOfWeek };
+
+    VirtualMachine vm;
+    vm.setBytecode(bytecode);
+    vm.setFunctions(functions);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 1);
+
+    time_t now = time(0);
+    tm *ltm = localtime(&now);
+    ASSERT_EQ(vm.getInput(0, 1)->toDouble(), ltm->tm_wday + 1);
+}
+
+TEST_F(SensingBlocksTest, CurrentHour)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &SensingBlocks::currentHour };
+
+    VirtualMachine vm;
+    vm.setBytecode(bytecode);
+    vm.setFunctions(functions);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 1);
+
+    time_t now = time(0);
+    tm *ltm = localtime(&now);
+    ASSERT_EQ(vm.getInput(0, 1)->toDouble(), ltm->tm_hour);
+}
+
+TEST_F(SensingBlocksTest, CurrentMinute)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &SensingBlocks::currentMinute };
+
+    VirtualMachine vm;
+    vm.setBytecode(bytecode);
+    vm.setFunctions(functions);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 1);
+
+    time_t now = time(0);
+    tm *ltm = localtime(&now);
+    ASSERT_EQ(vm.getInput(0, 1)->toDouble(), ltm->tm_min);
+}
+
+TEST_F(SensingBlocksTest, CurrentSecond)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &SensingBlocks::currentSecond };
+
+    VirtualMachine vm;
+    vm.setBytecode(bytecode);
+    vm.setFunctions(functions);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 1);
+
+    time_t now = time(0);
+    tm *ltm = localtime(&now);
+    ASSERT_EQ(vm.getInput(0, 1)->toDouble(), ltm->tm_sec);
+}
+
 TEST_F(SensingBlocksTest, DaysSince2000)
 {
     Compiler compiler(&m_engineMock);

From 69f27f003106fecd765885f2816e7dca6436d6f6 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 21 Sep 2023 17:15:57 +0200
Subject: [PATCH 10/49] Add motion blocks test

---
 test/blocks/CMakeLists.txt         | 16 ++++++++++++
 test/blocks/motion_blocks_test.cpp | 40 ++++++++++++++++++++++++++++++
 2 files changed, 56 insertions(+)
 create mode 100644 test/blocks/motion_blocks_test.cpp

diff --git a/test/blocks/CMakeLists.txt b/test/blocks/CMakeLists.txt
index 9fe74ae2..7c31bd50 100644
--- a/test/blocks/CMakeLists.txt
+++ b/test/blocks/CMakeLists.txt
@@ -109,3 +109,19 @@ target_link_libraries(
 )
 
 gtest_discover_tests(sensing_blocks_test)
+
+# motion_blocks_test
+add_executable(
+  motion_blocks_test
+  motion_blocks_test.cpp
+)
+
+target_link_libraries(
+  motion_blocks_test
+  GTest::gtest_main
+  GTest::gmock_main
+  scratchcpp
+  scratchcpp_mocks
+)
+
+gtest_discover_tests(motion_blocks_test)
diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
new file mode 100644
index 00000000..5f38114a
--- /dev/null
+++ b/test/blocks/motion_blocks_test.cpp
@@ -0,0 +1,40 @@
+#include <scratchcpp/compiler.h>
+#include <scratchcpp/block.h>
+#include <enginemock.h>
+
+#include "../common.h"
+#include "blocks/motionblocks.h"
+#include "engine/internal/engine.h"
+
+using namespace libscratchcpp;
+
+using ::testing::Return;
+
+class MotionBlocksTest : public testing::Test
+{
+    public:
+        void SetUp() override
+        {
+            m_section = std::make_unique<MotionBlocks>();
+            m_section->registerBlocks(&m_engine);
+        }
+
+        std::unique_ptr<IBlockSection> m_section;
+        EngineMock m_engineMock;
+        Engine m_engine;
+};
+
+TEST_F(MotionBlocksTest, Name)
+{
+    ASSERT_EQ(m_section->name(), "Motion");
+}
+
+TEST_F(MotionBlocksTest, CategoryVisible)
+{
+    ASSERT_TRUE(m_section->categoryVisible());
+}
+
+TEST_F(MotionBlocksTest, RegisterBlocks)
+{
+    m_section->registerBlocks(&m_engineMock);
+}

From d3a2c9cae06e1206db7421fa1662571b04cc4583 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 21 Sep 2023 18:14:12 +0200
Subject: [PATCH 11/49] Implement motion_movesteps block

---
 src/blocks/motionblocks.cpp        | 31 +++++++++++++++
 src/blocks/motionblocks.h          |  9 +++++
 test/blocks/motion_blocks_test.cpp | 62 ++++++++++++++++++++++++++++++
 3 files changed, 102 insertions(+)

diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp
index ce9ed794..82c21509 100644
--- a/src/blocks/motionblocks.cpp
+++ b/src/blocks/motionblocks.cpp
@@ -1,9 +1,15 @@
 // SPDX-License-Identifier: Apache-2.0
 
+#include <scratchcpp/iengine.h>
+#include <scratchcpp/compiler.h>
+#include <scratchcpp/sprite.h>
+
 #include "motionblocks.h"
 
 using namespace libscratchcpp;
 
+static const double pi = std::acos(-1); // TODO: Use std::numbers::pi in C++20
+
 std::string MotionBlocks::name() const
 {
     return "Motion";
@@ -11,4 +17,29 @@ std::string MotionBlocks::name() const
 
 void MotionBlocks::registerBlocks(IEngine *engine)
 {
+    // Blocks
+    engine->addCompileFunction(this, "motion_movesteps", &compileMoveSteps);
+
+    // Inputs
+    engine->addInput(this, "STEPS", STEPS);
+}
+
+void MotionBlocks::compileMoveSteps(Compiler *compiler)
+{
+    compiler->addInput(STEPS);
+    compiler->addFunctionCall(&moveSteps);
+}
+
+unsigned int MotionBlocks::moveSteps(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (sprite) {
+        double dir = sprite->direction();
+        double steps = vm->getInput(0, 1)->toDouble();
+        sprite->setX(sprite->x() + std::sin(dir * pi / 180) * steps);
+        sprite->setY(sprite->y() + std::cos(dir * pi / 180) * steps);
+    }
+
+    return 1;
 }
diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h
index 225a088d..c40d5293 100644
--- a/src/blocks/motionblocks.h
+++ b/src/blocks/motionblocks.h
@@ -11,9 +11,18 @@ namespace libscratchcpp
 class MotionBlocks : public IBlockSection
 {
     public:
+        enum Inputs
+        {
+            STEPS
+        };
+
         std::string name() const override;
 
         void registerBlocks(IEngine *engine) override;
+
+        static void compileMoveSteps(Compiler *compiler);
+
+        static unsigned int moveSteps(VirtualMachine *vm);
 };
 
 } // namespace libscratchcpp
diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
index 5f38114a..79a6cc18 100644
--- a/test/blocks/motion_blocks_test.cpp
+++ b/test/blocks/motion_blocks_test.cpp
@@ -1,5 +1,7 @@
 #include <scratchcpp/compiler.h>
 #include <scratchcpp/block.h>
+#include <scratchcpp/input.h>
+#include <scratchcpp/sprite.h>
 #include <enginemock.h>
 
 #include "../common.h"
@@ -19,6 +21,18 @@ class MotionBlocksTest : public testing::Test
             m_section->registerBlocks(&m_engine);
         }
 
+        // For any motion block
+        std::shared_ptr<Block> createMotionBlock(const std::string &id, const std::string &opcode) const { return std::make_shared<Block>(id, opcode); }
+
+        void addValueInput(std::shared_ptr<Block> block, const std::string &name, MotionBlocks::Inputs id, const Value &value) const
+        {
+            auto input = std::make_shared<Input>(name, Input::Type::Shadow);
+            input->setPrimaryValue(value);
+            input->setInputId(id);
+            block->addInput(input);
+            block->updateInputMap();
+        }
+
         std::unique_ptr<IBlockSection> m_section;
         EngineMock m_engineMock;
         Engine m_engine;
@@ -36,5 +50,53 @@ TEST_F(MotionBlocksTest, CategoryVisible)
 
 TEST_F(MotionBlocksTest, RegisterBlocks)
 {
+    // Blocks
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_movesteps", &MotionBlocks::compileMoveSteps));
+
+    // Inputs
+    EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS));
+
     m_section->registerBlocks(&m_engineMock);
 }
+
+TEST_F(MotionBlocksTest, MoveSteps)
+{
+    Compiler compiler(&m_engineMock);
+
+    // move (30.25) steps
+    auto block = std::make_shared<Block>("a", "motion_movesteps");
+    addValueInput(block, "STEPS", MotionBlocks::STEPS, 30.25);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::moveSteps)).WillOnce(Return(0));
+
+    compiler.init();
+    compiler.setBlock(block);
+    MotionBlocks::compileMoveSteps(&compiler);
+    compiler.end();
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT }));
+    ASSERT_EQ(compiler.constValues().size(), 1);
+    ASSERT_EQ(compiler.constValues()[0].toDouble(), 30.25);
+}
+
+TEST_F(MotionBlocksTest, MoveStepsImpl)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &MotionBlocks::moveSteps };
+    static Value constValues[] = { 30.25 };
+
+    Sprite sprite;
+    sprite.setX(5.2);
+    sprite.setY(-0.25);
+    sprite.setDirection(-61.42);
+
+    VirtualMachine vm(&sprite, nullptr, nullptr);
+    vm.setBytecode(bytecode);
+    vm.setFunctions(functions);
+    vm.setConstValues(constValues);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(std::round(sprite.x() * 100) / 100, -21.36);
+    ASSERT_EQ(std::round(sprite.y() * 100) / 100, 14.22);
+}

From cc8103384c3dea00245ad4bd60d14bea2122f1cf Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 21 Sep 2023 18:41:22 +0200
Subject: [PATCH 12/49] Implement motion_turnright block

---
 src/blocks/motionblocks.cpp        | 18 +++++++++++++
 src/blocks/motionblocks.h          |  5 +++-
 test/blocks/motion_blocks_test.cpp | 41 ++++++++++++++++++++++++++++++
 3 files changed, 63 insertions(+), 1 deletion(-)

diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp
index 82c21509..7dc18dee 100644
--- a/src/blocks/motionblocks.cpp
+++ b/src/blocks/motionblocks.cpp
@@ -19,9 +19,11 @@ void MotionBlocks::registerBlocks(IEngine *engine)
 {
     // Blocks
     engine->addCompileFunction(this, "motion_movesteps", &compileMoveSteps);
+    engine->addCompileFunction(this, "motion_turnright", &compileTurnRight);
 
     // Inputs
     engine->addInput(this, "STEPS", STEPS);
+    engine->addInput(this, "DEGREES", DEGREES);
 }
 
 void MotionBlocks::compileMoveSteps(Compiler *compiler)
@@ -30,6 +32,12 @@ void MotionBlocks::compileMoveSteps(Compiler *compiler)
     compiler->addFunctionCall(&moveSteps);
 }
 
+void MotionBlocks::compileTurnRight(Compiler *compiler)
+{
+    compiler->addInput(DEGREES);
+    compiler->addFunctionCall(&turnRight);
+}
+
 unsigned int MotionBlocks::moveSteps(VirtualMachine *vm)
 {
     Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
@@ -43,3 +51,13 @@ unsigned int MotionBlocks::moveSteps(VirtualMachine *vm)
 
     return 1;
 }
+
+unsigned int MotionBlocks::turnRight(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (sprite)
+        sprite->setDirection(sprite->direction() + vm->getInput(0, 1)->toDouble());
+
+    return 1;
+}
diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h
index c40d5293..c3b3116c 100644
--- a/src/blocks/motionblocks.h
+++ b/src/blocks/motionblocks.h
@@ -13,7 +13,8 @@ class MotionBlocks : public IBlockSection
     public:
         enum Inputs
         {
-            STEPS
+            STEPS,
+            DEGREES
         };
 
         std::string name() const override;
@@ -21,8 +22,10 @@ class MotionBlocks : public IBlockSection
         void registerBlocks(IEngine *engine) override;
 
         static void compileMoveSteps(Compiler *compiler);
+        static void compileTurnRight(Compiler *compiler);
 
         static unsigned int moveSteps(VirtualMachine *vm);
+        static unsigned int turnRight(VirtualMachine *vm);
 };
 
 } // namespace libscratchcpp
diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
index 79a6cc18..f3c3846f 100644
--- a/test/blocks/motion_blocks_test.cpp
+++ b/test/blocks/motion_blocks_test.cpp
@@ -52,9 +52,11 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
 {
     // Blocks
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_movesteps", &MotionBlocks::compileMoveSteps));
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_turnright", &MotionBlocks::compileTurnRight));
 
     // Inputs
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS));
+    EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DEGREES", MotionBlocks::DEGREES));
 
     m_section->registerBlocks(&m_engineMock);
 }
@@ -100,3 +102,42 @@ TEST_F(MotionBlocksTest, MoveStepsImpl)
     ASSERT_EQ(std::round(sprite.x() * 100) / 100, -21.36);
     ASSERT_EQ(std::round(sprite.y() * 100) / 100, 14.22);
 }
+
+TEST_F(MotionBlocksTest, TurnRight)
+{
+    Compiler compiler(&m_engineMock);
+
+    // turn right (12.05) degrees
+    auto block = std::make_shared<Block>("a", "motion_turnright");
+    addValueInput(block, "DEGREES", MotionBlocks::DEGREES, 12.05);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::turnRight)).WillOnce(Return(0));
+
+    compiler.init();
+    compiler.setBlock(block);
+    MotionBlocks::compileTurnRight(&compiler);
+    compiler.end();
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT }));
+    ASSERT_EQ(compiler.constValues().size(), 1);
+    ASSERT_EQ(compiler.constValues()[0].toDouble(), 12.05);
+}
+
+TEST_F(MotionBlocksTest, TurnRightImpl)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &MotionBlocks::turnRight };
+    static Value constValues[] = { 12.05 };
+
+    Sprite sprite;
+    sprite.setDirection(124.37);
+
+    VirtualMachine vm(&sprite, nullptr, nullptr);
+    vm.setBytecode(bytecode);
+    vm.setFunctions(functions);
+    vm.setConstValues(constValues);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(std::round(sprite.direction() * 100) / 100, 136.42);
+}

From 16fed38b4eb198f1b266fb56d2796bba7763d9c8 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 21 Sep 2023 18:48:01 +0200
Subject: [PATCH 13/49] Implement motion_turnleft block

---
 src/blocks/motionblocks.cpp        | 17 +++++++++++++
 src/blocks/motionblocks.h          |  2 ++
 test/blocks/motion_blocks_test.cpp | 40 ++++++++++++++++++++++++++++++
 3 files changed, 59 insertions(+)

diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp
index 7dc18dee..a74a8cd0 100644
--- a/src/blocks/motionblocks.cpp
+++ b/src/blocks/motionblocks.cpp
@@ -20,6 +20,7 @@ void MotionBlocks::registerBlocks(IEngine *engine)
     // Blocks
     engine->addCompileFunction(this, "motion_movesteps", &compileMoveSteps);
     engine->addCompileFunction(this, "motion_turnright", &compileTurnRight);
+    engine->addCompileFunction(this, "motion_turnleft", &compileTurnLeft);
 
     // Inputs
     engine->addInput(this, "STEPS", STEPS);
@@ -38,6 +39,12 @@ void MotionBlocks::compileTurnRight(Compiler *compiler)
     compiler->addFunctionCall(&turnRight);
 }
 
+void MotionBlocks::compileTurnLeft(Compiler *compiler)
+{
+    compiler->addInput(DEGREES);
+    compiler->addFunctionCall(&turnLeft);
+}
+
 unsigned int MotionBlocks::moveSteps(VirtualMachine *vm)
 {
     Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
@@ -61,3 +68,13 @@ unsigned int MotionBlocks::turnRight(VirtualMachine *vm)
 
     return 1;
 }
+
+unsigned int MotionBlocks::turnLeft(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (sprite)
+        sprite->setDirection(sprite->direction() - vm->getInput(0, 1)->toDouble());
+
+    return 1;
+}
diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h
index c3b3116c..1ccd4904 100644
--- a/src/blocks/motionblocks.h
+++ b/src/blocks/motionblocks.h
@@ -23,9 +23,11 @@ class MotionBlocks : public IBlockSection
 
         static void compileMoveSteps(Compiler *compiler);
         static void compileTurnRight(Compiler *compiler);
+        static void compileTurnLeft(Compiler *compiler);
 
         static unsigned int moveSteps(VirtualMachine *vm);
         static unsigned int turnRight(VirtualMachine *vm);
+        static unsigned int turnLeft(VirtualMachine *vm);
 };
 
 } // namespace libscratchcpp
diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
index f3c3846f..415a9dc7 100644
--- a/test/blocks/motion_blocks_test.cpp
+++ b/test/blocks/motion_blocks_test.cpp
@@ -53,6 +53,7 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
     // Blocks
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_movesteps", &MotionBlocks::compileMoveSteps));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_turnright", &MotionBlocks::compileTurnRight));
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_turnleft", &MotionBlocks::compileTurnLeft));
 
     // Inputs
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS));
@@ -141,3 +142,42 @@ TEST_F(MotionBlocksTest, TurnRightImpl)
     ASSERT_EQ(vm.registerCount(), 0);
     ASSERT_EQ(std::round(sprite.direction() * 100) / 100, 136.42);
 }
+
+TEST_F(MotionBlocksTest, TurnLeft)
+{
+    Compiler compiler(&m_engineMock);
+
+    // turn left (12.05) degrees
+    auto block = std::make_shared<Block>("a", "motion_turnleft");
+    addValueInput(block, "DEGREES", MotionBlocks::DEGREES, 12.05);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::turnLeft)).WillOnce(Return(0));
+
+    compiler.init();
+    compiler.setBlock(block);
+    MotionBlocks::compileTurnLeft(&compiler);
+    compiler.end();
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT }));
+    ASSERT_EQ(compiler.constValues().size(), 1);
+    ASSERT_EQ(compiler.constValues()[0].toDouble(), 12.05);
+}
+
+TEST_F(MotionBlocksTest, TurnLeftImpl)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &MotionBlocks::turnLeft };
+    static Value constValues[] = { 12.05 };
+
+    Sprite sprite;
+    sprite.setDirection(124.37);
+
+    VirtualMachine vm(&sprite, nullptr, nullptr);
+    vm.setBytecode(bytecode);
+    vm.setFunctions(functions);
+    vm.setConstValues(constValues);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(std::round(sprite.direction() * 100) / 100, 112.32);
+}

From 25a49d92223e21297fcd6200d6e61e46709febaa Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 22 Sep 2023 16:36:12 +0200
Subject: [PATCH 14/49] Add RandomGenerator class

---
 src/engine/CMakeLists.txt                     |  3 ++
 src/engine/internal/engine.cpp                |  1 -
 src/engine/internal/irandomgenerator.h        | 17 +++++++
 src/engine/internal/randomgenerator.cpp       | 43 ++++++++++++++++++
 src/engine/internal/randomgenerator.h         | 30 +++++++++++++
 test/CMakeLists.txt                           |  1 +
 test/mocks/randomgeneratormock.h              | 13 ++++++
 test/randomgenerator/CMakeLists.txt           | 12 +++++
 test/randomgenerator/randomgenerator_test.cpp | 45 +++++++++++++++++++
 9 files changed, 164 insertions(+), 1 deletion(-)
 create mode 100644 src/engine/internal/irandomgenerator.h
 create mode 100644 src/engine/internal/randomgenerator.cpp
 create mode 100644 src/engine/internal/randomgenerator.h
 create mode 100644 test/mocks/randomgeneratormock.h
 create mode 100644 test/randomgenerator/CMakeLists.txt
 create mode 100644 test/randomgenerator/randomgenerator_test.cpp

diff --git a/src/engine/CMakeLists.txt b/src/engine/CMakeLists.txt
index 6ab5078d..b63e7277 100644
--- a/src/engine/CMakeLists.txt
+++ b/src/engine/CMakeLists.txt
@@ -19,4 +19,7 @@ target_sources(scratchcpp
     internal/blocksectioncontainer.cpp
     internal/blocksectioncontainer.h
     internal/global.h
+    internal/randomgenerator.h
+    internal/randomgenerator.cpp
+    internal/irandomgenerator.h
 )
diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp
index 79551a2e..95513bef 100644
--- a/src/engine/internal/engine.cpp
+++ b/src/engine/internal/engine.cpp
@@ -27,7 +27,6 @@ Engine::Engine() :
     m_defaultTimer(std::make_unique<Timer>()),
     m_timer(m_defaultTimer.get())
 {
-    srand(time(NULL));
 }
 
 void Engine::clear()
diff --git a/src/engine/internal/irandomgenerator.h b/src/engine/internal/irandomgenerator.h
new file mode 100644
index 00000000..c8d4a177
--- /dev/null
+++ b/src/engine/internal/irandomgenerator.h
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+namespace libscratchcpp
+{
+
+class IRandomGenerator
+{
+    public:
+        virtual ~IRandomGenerator() { }
+
+        virtual long randint(long start, long end) const = 0;
+        virtual double randintDouble(double start, double end) const = 0;
+};
+
+} // namespace libscratchcpp
diff --git a/src/engine/internal/randomgenerator.cpp b/src/engine/internal/randomgenerator.cpp
new file mode 100644
index 00000000..06e169e7
--- /dev/null
+++ b/src/engine/internal/randomgenerator.cpp
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <random>
+
+#include "randomgenerator.h"
+
+using namespace libscratchcpp;
+
+std::shared_ptr<RandomGenerator> RandomGenerator::m_instance = std::make_shared<RandomGenerator>();
+
+RandomGenerator::RandomGenerator() :
+    m_generator(std::make_unique<std::mt19937>(m_device()))
+{
+}
+
+std::shared_ptr<RandomGenerator> RandomGenerator::instance()
+{
+    return m_instance;
+}
+
+long RandomGenerator::randint(long start, long end) const
+{
+    if (start > end) {
+        long tmp = start;
+        start = end;
+        end = tmp;
+    }
+
+    std::uniform_int_distribution<long> distribution(start, end);
+    return distribution(*m_generator);
+}
+
+double RandomGenerator::randintDouble(double start, double end) const
+{
+    if (start > end) {
+        double tmp = start;
+        start = end;
+        end = tmp;
+    }
+
+    std::uniform_real_distribution<double> distribution(start, end);
+    return distribution(*m_generator);
+}
diff --git a/src/engine/internal/randomgenerator.h b/src/engine/internal/randomgenerator.h
new file mode 100644
index 00000000..6cb72586
--- /dev/null
+++ b/src/engine/internal/randomgenerator.h
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include <memory>
+#include <random>
+
+#include "irandomgenerator.h"
+
+namespace libscratchcpp
+{
+
+class RandomGenerator : public IRandomGenerator
+{
+    public:
+        RandomGenerator();
+        RandomGenerator(const RandomGenerator &) = delete;
+
+        static std::shared_ptr<RandomGenerator> instance();
+
+        long randint(long start, long end) const override;
+        double randintDouble(double start, double end) const override;
+
+    private:
+        static std::shared_ptr<RandomGenerator> m_instance;
+        std::random_device m_device;
+        std::unique_ptr<std::mt19937> m_generator;
+};
+
+} // namespace libscratchcpp
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index efa66c18..47a4a127 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -30,3 +30,4 @@ add_subdirectory(extensions)
 add_subdirectory(engine)
 add_subdirectory(clock)
 add_subdirectory(timer)
+add_subdirectory(randomgenerator)
diff --git a/test/mocks/randomgeneratormock.h b/test/mocks/randomgeneratormock.h
new file mode 100644
index 00000000..42d6e999
--- /dev/null
+++ b/test/mocks/randomgeneratormock.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <engine/internal/irandomgenerator.h>
+#include <gmock/gmock.h>
+
+using namespace libscratchcpp;
+
+class RandomGeneratorMock : public IRandomGenerator
+{
+    public:
+        MOCK_METHOD(long, randint, (long, long), (const, override));
+        MOCK_METHOD(double, randintDouble, (double, double), (const, override));
+};
diff --git a/test/randomgenerator/CMakeLists.txt b/test/randomgenerator/CMakeLists.txt
new file mode 100644
index 00000000..55db9bf9
--- /dev/null
+++ b/test/randomgenerator/CMakeLists.txt
@@ -0,0 +1,12 @@
+add_executable(
+  randomgenerator_test
+  randomgenerator_test.cpp
+)
+
+target_link_libraries(
+  randomgenerator_test
+  GTest::gtest_main
+  scratchcpp
+)
+
+gtest_discover_tests(randomgenerator_test)
diff --git a/test/randomgenerator/randomgenerator_test.cpp b/test/randomgenerator/randomgenerator_test.cpp
new file mode 100644
index 00000000..45f220af
--- /dev/null
+++ b/test/randomgenerator/randomgenerator_test.cpp
@@ -0,0 +1,45 @@
+#include <engine/internal/randomgenerator.h>
+
+#include "../common.h"
+
+using namespace libscratchcpp;
+
+TEST(RandomGeneratorTest, RandInt)
+{
+    auto rng = RandomGenerator::instance();
+    ASSERT_TRUE(rng);
+
+    long num;
+
+    for (int i = 0; i < 25; i++) {
+        num = rng->randint(-2, 3);
+        ASSERT_GE(num, -2);
+        ASSERT_LE(num, 3);
+    }
+
+    for (int i = 0; i < 25; i++) {
+        num = rng->randint(5, -3);
+        ASSERT_GE(num, -3);
+        ASSERT_LE(num, 5);
+    }
+}
+
+TEST(RandomGeneratorTest, RandIntDouble)
+{
+    auto rng = RandomGenerator::instance();
+    ASSERT_TRUE(rng);
+
+    long num;
+
+    for (int i = 0; i < 1000; i++) {
+        num = rng->randintDouble(-2.23, 3.875);
+        ASSERT_GE(num, -2.23);
+        ASSERT_LE(num, 3.875);
+    }
+
+    for (int i = 0; i < 1000; i++) {
+        num = rng->randintDouble(5.081, -2.903);
+        ASSERT_GE(num, -2.903);
+        ASSERT_LE(num, 5.081);
+    }
+}

From 5e5410ff426e691d163e2d146591b9cf8fcb520a Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 22 Sep 2023 22:51:11 +0200
Subject: [PATCH 15/49] Use RandomGenerator to generate random numbers

---
 src/engine/CMakeLists.txt                     |  1 -
 src/engine/internal/global.h                  | 31 ----------------
 src/engine/script_p.h                         |  2 -
 src/engine/virtualmachine_p.cpp               | 17 ++++++---
 src/engine/virtualmachine_p.h                 |  3 ++
 test/virtual_machine/virtual_machine_test.cpp | 37 +++++++++++++++++++
 6 files changed, 51 insertions(+), 40 deletions(-)
 delete mode 100644 src/engine/internal/global.h

diff --git a/src/engine/CMakeLists.txt b/src/engine/CMakeLists.txt
index b63e7277..e7ee439a 100644
--- a/src/engine/CMakeLists.txt
+++ b/src/engine/CMakeLists.txt
@@ -18,7 +18,6 @@ target_sources(scratchcpp
     internal/timer.h
     internal/blocksectioncontainer.cpp
     internal/blocksectioncontainer.h
-    internal/global.h
     internal/randomgenerator.h
     internal/randomgenerator.cpp
     internal/irandomgenerator.h
diff --git a/src/engine/internal/global.h b/src/engine/internal/global.h
deleted file mode 100644
index fe5fc290..00000000
--- a/src/engine/internal/global.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-
-#pragma once
-
-#include <functional>
-#include <random>
-
-namespace libscratchcpp
-{
-
-/*! Generates a random number in the given interval like the random.randint() function in Python. */
-template<typename T>
-inline T randint(T start, T end)
-{
-    if (start > end) {
-        auto tmp = start;
-        start = end;
-        end = tmp;
-    }
-    std::random_device d;
-    std::default_random_engine generator(d());
-    if constexpr (std::is_integral<T>()) {
-        std::uniform_int_distribution<T> distribution(start, end);
-        return distribution(generator);
-    } else {
-        std::uniform_real_distribution<T> distribution(start, end);
-        return distribution(generator);
-    }
-}
-
-} // namespace libscratchcpp
diff --git a/src/engine/script_p.h b/src/engine/script_p.h
index 7b643429..f80e30ad 100644
--- a/src/engine/script_p.h
+++ b/src/engine/script_p.h
@@ -5,8 +5,6 @@
 #include <vector>
 #include <scratchcpp/value.h>
 
-#include "engine/internal/global.h"
-
 namespace libscratchcpp
 {
 
diff --git a/src/engine/virtualmachine_p.cpp b/src/engine/virtualmachine_p.cpp
index 27ce7cda..f28f76fc 100644
--- a/src/engine/virtualmachine_p.cpp
+++ b/src/engine/virtualmachine_p.cpp
@@ -8,7 +8,7 @@
 #include <cassert>
 
 #include "virtualmachine_p.h"
-#include "internal/global.h"
+#include "internal/randomgenerator.h"
 
 #define MAX_REG_COUNT 1024
 
@@ -29,6 +29,8 @@ using namespace vm;
 
 static const double pi = std::acos(-1); // TODO: Use std::numbers::pi in C++20
 
+IRandomGenerator *VirtualMachinePrivate::rng = nullptr;
+
 const unsigned int VirtualMachinePrivate::instruction_arg_count[] = {
     0, // OP_START
     0, // OP_HALT
@@ -106,6 +108,9 @@ VirtualMachinePrivate::VirtualMachinePrivate(VirtualMachine *vm, Target *target,
         regs[i] = new Value();
     loops.reserve(256);
     callTree.reserve(1024);
+
+    if (!rng)
+        rng = RandomGenerator::instance().get();
 }
 
 VirtualMachinePrivate::~VirtualMachinePrivate()
@@ -360,7 +365,7 @@ do_loop_end : {
     DISPATCH();
 
 do_random:
-    REPLACE_RET_VALUE(randint<long>(READ_REG(0, 2)->toDouble(), READ_REG(1, 2)->toDouble()), 2);
+    REPLACE_RET_VALUE(rng->randint(READ_REG(0, 2)->toDouble(), READ_REG(1, 2)->toDouble()), 2);
     FREE_REGS(1);
     DISPATCH();
 
@@ -539,7 +544,7 @@ do_list_del : {
             index = 0;
         } else if (str == "random") {
             size_t size = list->size();
-            index = size == 0 ? 0 : randint<size_t>(1, size);
+            index = size == 0 ? 0 : rng->randint(1, size);
         } else
             index = 0;
     } else {
@@ -567,7 +572,7 @@ do_list_insert : {
             index = 0;
         } else if (str == "random") {
             size_t size = list->size();
-            index = size == 0 ? 1 : randint<size_t>(1, size);
+            index = size == 0 ? 1 : rng->randint(1, size);
         } else
             index = 0;
     } else {
@@ -594,7 +599,7 @@ do_list_replace : {
             index = list->size();
         else if (str == "random") {
             size_t size = list->size();
-            index = size == 0 ? 0 : randint<size_t>(1, size);
+            index = size == 0 ? 0 : rng->randint(1, size);
         } else
             index = 0;
     } else {
@@ -617,7 +622,7 @@ do_list_get_item : {
             index = list->size();
         else if (str == "random") {
             size_t size = list->size();
-            index = size == 0 ? 0 : randint<size_t>(1, size);
+            index = size == 0 ? 0 : rng->randint(1, size);
         } else
             index = 0;
     } else {
diff --git a/src/engine/virtualmachine_p.h b/src/engine/virtualmachine_p.h
index 262926b7..18dfaffa 100644
--- a/src/engine/virtualmachine_p.h
+++ b/src/engine/virtualmachine_p.h
@@ -15,6 +15,7 @@ class IEngine;
 class Script;
 class Value;
 class List;
+class IRandomGenerator;
 
 struct VirtualMachinePrivate
 {
@@ -65,6 +66,8 @@ struct VirtualMachinePrivate
 
         Value **regs = nullptr;
         size_t regCount = 0;
+
+        static IRandomGenerator *rng;
 };
 
 } // namespace libscratchcpp
diff --git a/test/virtual_machine/virtual_machine_test.cpp b/test/virtual_machine/virtual_machine_test.cpp
index a770957c..7b4b1a19 100644
--- a/test/virtual_machine/virtual_machine_test.cpp
+++ b/test/virtual_machine/virtual_machine_test.cpp
@@ -2,13 +2,18 @@
 #include <scratchcpp/list.h>
 #include <scratchcpp/script.h>
 #include <enginemock.h>
+#include <randomgeneratormock.h>
 
+#include "engine/virtualmachine_p.h"
 #include "engine/internal/engine.h"
+#include "engine/internal/randomgenerator.h"
 #include "../common.h"
 
 using namespace libscratchcpp;
 using namespace vm;
 
+using ::testing::Return;
+
 TEST(VirtualMachineTest, Constructors)
 {
     VirtualMachine vm1;
@@ -867,11 +872,19 @@ TEST(VirtualMachineTest, OP_LIST_DEL)
     list1.push_back("h");
     List *lists[] = { &list1 };
 
+    RandomGeneratorMock rng;
+    VirtualMachinePrivate::rng = &rng;
+
     VirtualMachine vm;
     vm.setBytecode(bytecode);
     vm.setConstValues(constValues);
     vm.setLists(lists);
+
+    EXPECT_CALL(rng, randint(1, 4)).WillOnce(Return(2));
     vm.run();
+
+    VirtualMachinePrivate::rng = RandomGenerator::instance().get();
+
     ASSERT_EQ(vm.registerCount(), 14);
     ASSERT_EQ(vm.getInput(0, 14)->toString(), "a b d e f g h");
     ASSERT_EQ(vm.getInput(1, 14)->toString(), "b d e f g h");
@@ -931,11 +944,19 @@ TEST(VirtualMachineTest, OP_LIST_INSERT)
     list1.push_back("h");
     List *lists[] = { &list1 };
 
+    RandomGeneratorMock rng;
+    VirtualMachinePrivate::rng = &rng;
+
     VirtualMachine vm;
     vm.setBytecode(bytecode);
     vm.setConstValues(constValues);
     vm.setLists(lists);
+
+    EXPECT_CALL(rng, randint(1, 12)).WillOnce(Return(5));
     vm.run();
+
+    VirtualMachinePrivate::rng = RandomGenerator::instance().get();
+
     ASSERT_EQ(vm.registerCount(), 13);
     ASSERT_EQ(vm.getInput(0, 13)->toString(), "a b new item c d e f g h");
     ASSERT_EQ(vm.getInput(1, 13)->toString(), "new item a b new item c d e f g h");
@@ -976,11 +997,19 @@ TEST(VirtualMachineTest, OP_LIST_REPLACE)
     list1.push_back("h");
     List *lists[] = { &list1 };
 
+    RandomGeneratorMock rng;
+    VirtualMachinePrivate::rng = &rng;
+
     VirtualMachine vm;
     vm.setBytecode(bytecode);
     vm.setConstValues(constValues);
     vm.setLists(lists);
+
+    EXPECT_CALL(rng, randint(1, 8)).WillOnce(Return(7));
     vm.run();
+
+    VirtualMachinePrivate::rng = RandomGenerator::instance().get();
+
     ASSERT_EQ(vm.registerCount(), 13);
     ASSERT_EQ(vm.getInput(0, 13)->toString(), "a b new item d e f g h");
     ASSERT_EQ(vm.getInput(1, 13)->toString(), "new item b new item d e f g h");
@@ -1016,11 +1045,19 @@ TEST(VirtualMachineTest, OP_LIST_GET_ITEM)
     list1.push_back("h");
     List *lists[] = { &list1 };
 
+    RandomGeneratorMock rng;
+    VirtualMachinePrivate::rng = &rng;
+
     VirtualMachine vm;
     vm.setBytecode(bytecode);
     vm.setConstValues(constValues);
     vm.setLists(lists);
+
+    EXPECT_CALL(rng, randint(1, 8)).WillOnce(Return(1));
     vm.run();
+
+    VirtualMachinePrivate::rng = RandomGenerator::instance().get();
+
     ASSERT_EQ(vm.registerCount(), 13);
     ASSERT_EQ(vm.getInput(0, 13)->toString(), "c");
     ASSERT_EQ(vm.getInput(1, 13)->toString(), "a");

From 80a651e4046dbd3945bf5e4f494ea10cd82ac98c Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 22 Sep 2023 23:04:40 +0200
Subject: [PATCH 16/49] Implement motion_pointindirection block

---
 src/blocks/motionblocks.cpp        | 18 +++++++++++++
 src/blocks/motionblocks.h          |  5 +++-
 test/blocks/motion_blocks_test.cpp | 41 ++++++++++++++++++++++++++++++
 3 files changed, 63 insertions(+), 1 deletion(-)

diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp
index a74a8cd0..c3da42b6 100644
--- a/src/blocks/motionblocks.cpp
+++ b/src/blocks/motionblocks.cpp
@@ -21,10 +21,12 @@ void MotionBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "motion_movesteps", &compileMoveSteps);
     engine->addCompileFunction(this, "motion_turnright", &compileTurnRight);
     engine->addCompileFunction(this, "motion_turnleft", &compileTurnLeft);
+    engine->addCompileFunction(this, "motion_pointindirection", &compilePointInDirection);
 
     // Inputs
     engine->addInput(this, "STEPS", STEPS);
     engine->addInput(this, "DEGREES", DEGREES);
+    engine->addInput(this, "DIRECTION", DIRECTION);
 }
 
 void MotionBlocks::compileMoveSteps(Compiler *compiler)
@@ -45,6 +47,12 @@ void MotionBlocks::compileTurnLeft(Compiler *compiler)
     compiler->addFunctionCall(&turnLeft);
 }
 
+void MotionBlocks::compilePointInDirection(Compiler *compiler)
+{
+    compiler->addInput(DIRECTION);
+    compiler->addFunctionCall(&pointInDirection);
+}
+
 unsigned int MotionBlocks::moveSteps(VirtualMachine *vm)
 {
     Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
@@ -78,3 +86,13 @@ unsigned int MotionBlocks::turnLeft(VirtualMachine *vm)
 
     return 1;
 }
+
+unsigned int MotionBlocks::pointInDirection(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (sprite)
+        sprite->setDirection(vm->getInput(0, 1)->toDouble());
+
+    return 1;
+}
diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h
index 1ccd4904..b756a963 100644
--- a/src/blocks/motionblocks.h
+++ b/src/blocks/motionblocks.h
@@ -14,7 +14,8 @@ class MotionBlocks : public IBlockSection
         enum Inputs
         {
             STEPS,
-            DEGREES
+            DEGREES,
+            DIRECTION
         };
 
         std::string name() const override;
@@ -24,10 +25,12 @@ class MotionBlocks : public IBlockSection
         static void compileMoveSteps(Compiler *compiler);
         static void compileTurnRight(Compiler *compiler);
         static void compileTurnLeft(Compiler *compiler);
+        static void compilePointInDirection(Compiler *compiler);
 
         static unsigned int moveSteps(VirtualMachine *vm);
         static unsigned int turnRight(VirtualMachine *vm);
         static unsigned int turnLeft(VirtualMachine *vm);
+        static unsigned int pointInDirection(VirtualMachine *vm);
 };
 
 } // namespace libscratchcpp
diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
index 415a9dc7..f3771703 100644
--- a/test/blocks/motion_blocks_test.cpp
+++ b/test/blocks/motion_blocks_test.cpp
@@ -54,10 +54,12 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_movesteps", &MotionBlocks::compileMoveSteps));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_turnright", &MotionBlocks::compileTurnRight));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_turnleft", &MotionBlocks::compileTurnLeft));
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_pointindirection", &MotionBlocks::compilePointInDirection));
 
     // Inputs
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS));
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DEGREES", MotionBlocks::DEGREES));
+    EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DIRECTION", MotionBlocks::DIRECTION));
 
     m_section->registerBlocks(&m_engineMock);
 }
@@ -181,3 +183,42 @@ TEST_F(MotionBlocksTest, TurnLeftImpl)
     ASSERT_EQ(vm.registerCount(), 0);
     ASSERT_EQ(std::round(sprite.direction() * 100) / 100, 112.32);
 }
+
+TEST_F(MotionBlocksTest, PointInDirection)
+{
+    Compiler compiler(&m_engineMock);
+
+    // point in direction (-60.5)
+    auto block = std::make_shared<Block>("a", "motion_pointindirection");
+    addValueInput(block, "DIRECTION", MotionBlocks::DIRECTION, -60.5);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::pointInDirection)).WillOnce(Return(0));
+
+    compiler.init();
+    compiler.setBlock(block);
+    MotionBlocks::compilePointInDirection(&compiler);
+    compiler.end();
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT }));
+    ASSERT_EQ(compiler.constValues().size(), 1);
+    ASSERT_EQ(compiler.constValues()[0].toDouble(), -60.5);
+}
+
+TEST_F(MotionBlocksTest, PointInDirectionImpl)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &MotionBlocks::pointInDirection };
+    static Value constValues[] = { -60.5 };
+
+    Sprite sprite;
+    sprite.setDirection(50.02);
+
+    VirtualMachine vm(&sprite, nullptr, nullptr);
+    vm.setBytecode(bytecode);
+    vm.setFunctions(functions);
+    vm.setConstValues(constValues);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(sprite.direction(), -60.5);
+}

From 5ce128eea02a01cc172923e6ee4dc29a4a40b974 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 22 Sep 2023 23:20:01 +0200
Subject: [PATCH 17/49] Store mouse pointer position in Engine

---
 include/scratchcpp/iengine.h   | 12 ++++++++++++
 src/engine/internal/engine.cpp | 20 ++++++++++++++++++++
 src/engine/internal/engine.h   |  8 ++++++++
 test/engine/engine_test.cpp    | 18 ++++++++++++++++++
 test/mocks/enginemock.h        |  6 ++++++
 5 files changed, 64 insertions(+)

diff --git a/include/scratchcpp/iengine.h b/include/scratchcpp/iengine.h
index ac4b7ce8..f9c8851f 100644
--- a/include/scratchcpp/iengine.h
+++ b/include/scratchcpp/iengine.h
@@ -91,6 +91,18 @@ class LIBSCRATCHCPP_EXPORT IEngine
         /*! Sets the framerate of the project. */
         virtual void setFps(double fps) = 0;
 
+        /*! Returns the X coordinate of the mouse pointer. */
+        virtual double mouseX() const = 0;
+
+        /*! Sets the X coordinate of the mouse pointer. */
+        virtual void setMouseX(double x) = 0;
+
+        /*! Returns the Y coordinate of the mouse pointer. */
+        virtual double mouseY() const = 0;
+
+        /*! Sets the Y coordinate of the mouse pointer. */
+        virtual void setMouseY(double y) = 0;
+
         /*! Returns true if there are any running script of the broadcast with the given index. */
         virtual bool broadcastRunning(unsigned int index, VirtualMachine *sourceScript) = 0;
 
diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp
index 79551a2e..8f322eb2 100644
--- a/src/engine/internal/engine.cpp
+++ b/src/engine/internal/engine.cpp
@@ -364,6 +364,26 @@ void Engine::setFps(double fps)
     updateFrameDuration();
 }
 
+double Engine::mouseX() const
+{
+    return m_mouseX;
+}
+
+void Engine::setMouseX(double x)
+{
+    m_mouseX = x;
+}
+
+double Engine::mouseY() const
+{
+    return m_mouseY;
+}
+
+void Engine::setMouseY(double y)
+{
+    m_mouseY = y;
+}
+
 bool Engine::broadcastRunning(unsigned int index, VirtualMachine *sourceScript)
 {
     const auto &scripts = m_runningBroadcastMap[index];
diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h
index 4a492b34..fef0fbf7 100644
--- a/src/engine/internal/engine.h
+++ b/src/engine/internal/engine.h
@@ -39,6 +39,12 @@ class Engine : public IEngine
         double fps() const override;
         void setFps(double fps) override;
 
+        double mouseX() const override;
+        void setMouseX(double x) override;
+
+        double mouseY() const override;
+        void setMouseY(double y) override;
+
         bool broadcastRunning(unsigned int index, VirtualMachine *sourceScript) override;
 
         void breakFrame() override;
@@ -114,6 +120,8 @@ class Engine : public IEngine
         ITimer *m_timer = nullptr;
         double m_fps = 30;                         // default FPS
         std::chrono::milliseconds m_frameDuration; // will be computed in run()
+        double m_mouseX = 0;
+        double m_mouseY = 0;
 
         bool m_running = false;
         bool m_breakFrame = false;
diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp
index 055e2f07..b9653b18 100644
--- a/test/engine/engine_test.cpp
+++ b/test/engine/engine_test.cpp
@@ -43,6 +43,24 @@ TEST(EngineTest, Fps)
     ASSERT_EQ(engine.fps(), 60.25);
 }
 
+TEST(EngineTest, MouseX)
+{
+    Engine engine;
+    ASSERT_EQ(engine.mouseX(), 0);
+
+    engine.setMouseX(-128.038);
+    ASSERT_EQ(engine.mouseX(), -128.038);
+}
+
+TEST(EngineTest, MouseY)
+{
+    Engine engine;
+    ASSERT_EQ(engine.mouseY(), 0);
+
+    engine.setMouseY(179.9258);
+    ASSERT_EQ(engine.mouseY(), 179.9258);
+}
+
 TEST(EngineTest, BreakFrame)
 {
     Engine engine;
diff --git a/test/mocks/enginemock.h b/test/mocks/enginemock.h
index 2d874d79..636a3aae 100644
--- a/test/mocks/enginemock.h
+++ b/test/mocks/enginemock.h
@@ -27,6 +27,12 @@ class EngineMock : public IEngine
         MOCK_METHOD(double, fps, (), (const, override));
         MOCK_METHOD(void, setFps, (double fps), (override));
 
+        MOCK_METHOD(double, mouseX, (), (const, override));
+        MOCK_METHOD(void, setMouseX, (double x), (override));
+
+        MOCK_METHOD(double, mouseY, (), (const, override));
+        MOCK_METHOD(void, setMouseY, (double y), (override));
+
         MOCK_METHOD(bool, broadcastRunning, (unsigned int, VirtualMachine *), (override));
 
         MOCK_METHOD(void, breakFrame, (), (override));

From 39c1380e0ce88c2d755310dfe44966ce69aac76f Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 23 Sep 2023 13:33:31 +0200
Subject: [PATCH 18/49] fix #225: Fix sprite direction range

---
 src/scratch/sprite.cpp               |  8 +++++++-
 test/scratch_classes/sprite_test.cpp | 24 ++++++++++++++++++++++++
 2 files changed, 31 insertions(+), 1 deletion(-)

diff --git a/src/scratch/sprite.cpp b/src/scratch/sprite.cpp
index dc7bc5a6..787d36f8 100644
--- a/src/scratch/sprite.cpp
+++ b/src/scratch/sprite.cpp
@@ -197,7 +197,13 @@ double Sprite::direction() const
 /*! Sets the direction. */
 void Sprite::setDirection(double newDirection)
 {
-    impl->direction = newDirection;
+    if (newDirection >= -180 && newDirection <= 180)
+        impl->direction = newDirection;
+    else if (newDirection < -180)
+        impl->direction = std::fmod(newDirection - 180, 360) + 180;
+    else
+        impl->direction = std::fmod(newDirection + 180, 360) - 180;
+
     if (impl->iface)
         impl->iface->onDirectionChanged(impl->direction);
 }
diff --git a/test/scratch_classes/sprite_test.cpp b/test/scratch_classes/sprite_test.cpp
index 8abe2426..e5b60f70 100644
--- a/test/scratch_classes/sprite_test.cpp
+++ b/test/scratch_classes/sprite_test.cpp
@@ -225,6 +225,30 @@ TEST(SpriteTest, Direction)
 
     sprite.setDirection(145.2);
     ASSERT_EQ(sprite.direction(), 145.2);
+
+    sprite.setDirection(182.5);
+    ASSERT_EQ(sprite.direction(), -177.5);
+
+    sprite.setDirection(270);
+    ASSERT_EQ(sprite.direction(), -90);
+
+    sprite.setDirection(360);
+    ASSERT_EQ(sprite.direction(), 0);
+
+    sprite.setDirection(500);
+    ASSERT_EQ(sprite.direction(), 140);
+
+    sprite.setDirection(-182.5);
+    ASSERT_EQ(sprite.direction(), 177.5);
+
+    sprite.setDirection(-270);
+    ASSERT_EQ(sprite.direction(), 90);
+
+    sprite.setDirection(-360);
+    ASSERT_EQ(sprite.direction(), 0);
+
+    sprite.setDirection(-500);
+    ASSERT_EQ(sprite.direction(), -140);
 }
 
 TEST(SpriteTest, Draggable)

From 5afac784d3b34135e2134636e982d5c0c46d010e Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 23 Sep 2023 13:43:06 +0200
Subject: [PATCH 19/49] Use all threads in CI

---
 .github/workflows/build.yml  | 2 +-
 .github/workflows/utests.yml | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 8be119c2..f43a6897 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -27,5 +27,5 @@ jobs:
       run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
 
     - name: Build
-      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
+      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j$(nproc --all)
 
diff --git a/.github/workflows/utests.yml b/.github/workflows/utests.yml
index 9d69d395..9eefb482 100644
--- a/.github/workflows/utests.yml
+++ b/.github/workflows/utests.yml
@@ -27,7 +27,7 @@ jobs:
       run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DLIBSCRATCHCPP_BUILD_UNIT_TESTS=ON
 
     - name: Build
-      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
+      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j$(nproc --all)
 
     - name: Run unit tests
       run: ctest --test-dir build -V

From 734a172521e68c0540b457b4b5b65a8c33ed2733 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 23 Sep 2023 13:58:54 +0200
Subject: [PATCH 20/49] Implement motion_pointtowards block

---
 src/blocks/motionblocks.cpp        | 107 +++++++++++++++
 src/blocks/motionblocks.h          |  24 +++-
 test/blocks/motion_blocks_test.cpp | 214 +++++++++++++++++++++++++++++
 3 files changed, 344 insertions(+), 1 deletion(-)

diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp
index c3da42b6..9b2325f5 100644
--- a/src/blocks/motionblocks.cpp
+++ b/src/blocks/motionblocks.cpp
@@ -3,13 +3,17 @@
 #include <scratchcpp/iengine.h>
 #include <scratchcpp/compiler.h>
 #include <scratchcpp/sprite.h>
+#include <scratchcpp/input.h>
 
 #include "motionblocks.h"
+#include "../engine/internal/randomgenerator.h"
 
 using namespace libscratchcpp;
 
 static const double pi = std::acos(-1); // TODO: Use std::numbers::pi in C++20
 
+IRandomGenerator *MotionBlocks::rng = nullptr;
+
 std::string MotionBlocks::name() const
 {
     return "Motion";
@@ -22,11 +26,13 @@ void MotionBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "motion_turnright", &compileTurnRight);
     engine->addCompileFunction(this, "motion_turnleft", &compileTurnLeft);
     engine->addCompileFunction(this, "motion_pointindirection", &compilePointInDirection);
+    engine->addCompileFunction(this, "motion_pointtowards", &compilePointTowards);
 
     // Inputs
     engine->addInput(this, "STEPS", STEPS);
     engine->addInput(this, "DEGREES", DEGREES);
     engine->addInput(this, "DIRECTION", DIRECTION);
+    engine->addInput(this, "TOWARDS", TOWARDS);
 }
 
 void MotionBlocks::compileMoveSteps(Compiler *compiler)
@@ -53,6 +59,29 @@ void MotionBlocks::compilePointInDirection(Compiler *compiler)
     compiler->addFunctionCall(&pointInDirection);
 }
 
+void MotionBlocks::compilePointTowards(Compiler *compiler)
+{
+    Input *input = compiler->input(TOWARDS);
+
+    if (input->type() != Input::Type::ObscuredShadow) {
+        assert(input->pointsToDropdownMenu());
+        std::string value = input->selectedMenuItem();
+
+        if (value == "_mouse_")
+            compiler->addFunctionCall(&pointTowardsMousePointer);
+        else if (value == "_random_")
+            compiler->addFunctionCall(&pointTowardsRandomPosition);
+        else {
+            int index = compiler->engine()->findTarget(value);
+            compiler->addConstValue(index);
+            compiler->addFunctionCall(&pointTowardsByIndex);
+        }
+    } else {
+        compiler->addInput(input);
+        compiler->addFunctionCall(&pointTowards);
+    }
+}
+
 unsigned int MotionBlocks::moveSteps(VirtualMachine *vm)
 {
     Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
@@ -96,3 +125,81 @@ unsigned int MotionBlocks::pointInDirection(VirtualMachine *vm)
 
     return 1;
 }
+
+void MotionBlocks::pointTowardsPos(Sprite *sprite, double x, double y)
+{
+    if (!sprite)
+        return;
+
+    // https://en.scratch-wiki.info/wiki/Point_Towards_()_(block)#Workaround
+    double deltaX = x - sprite->x();
+    double deltaY = y - sprite->y();
+
+    if (deltaY == 0) {
+        if (deltaX < 0)
+            sprite->setDirection(-90);
+        else
+            sprite->setDirection(90);
+    } else if (deltaY < 0)
+        sprite->setDirection(180 + (180 / pi) * std::atan(deltaX / deltaY));
+    else
+        sprite->setDirection((180 / pi) * std::atan(deltaX / deltaY));
+}
+
+unsigned int MotionBlocks::pointTowards(VirtualMachine *vm)
+{
+    std::string value = vm->getInput(0, 1)->toString();
+    Target *target;
+
+    if (value == "_mouse_")
+        pointTowardsPos(dynamic_cast<Sprite *>(vm->target()), vm->engine()->mouseX(), vm->engine()->mouseY());
+    else if (value == "_random_") {
+        // TODO: Read stage size from engine (#224)
+        static const unsigned int stageWidth = 480;
+        static const unsigned int stageHeight = 360;
+
+        if (!rng)
+            rng = RandomGenerator::instance().get();
+
+        pointTowardsPos(dynamic_cast<Sprite *>(vm->target()), rng->randint(-static_cast<int>(stageWidth / 2), stageWidth / 2), rng->randint(-static_cast<int>(stageHeight / 2), stageHeight / 2));
+    } else {
+        target = vm->engine()->targetAt(vm->engine()->findTarget(value));
+        Sprite *sprite = dynamic_cast<Sprite *>(target);
+
+        if (sprite)
+            pointTowardsPos(dynamic_cast<Sprite *>(vm->target()), sprite->x(), sprite->y());
+    }
+
+    return 1;
+}
+
+unsigned int MotionBlocks::pointTowardsByIndex(VirtualMachine *vm)
+{
+    Target *target = vm->engine()->targetAt(vm->getInput(0, 1)->toInt());
+    Sprite *sprite = dynamic_cast<Sprite *>(target);
+
+    if (sprite)
+        pointTowardsPos(dynamic_cast<Sprite *>(vm->target()), sprite->x(), sprite->y());
+
+    return 1;
+}
+
+unsigned int MotionBlocks::pointTowardsMousePointer(VirtualMachine *vm)
+{
+    pointTowardsPos(dynamic_cast<Sprite *>(vm->target()), vm->engine()->mouseX(), vm->engine()->mouseY());
+    return 0;
+}
+
+unsigned int MotionBlocks::pointTowardsRandomPosition(VirtualMachine *vm)
+{
+    // TODO: Read stage size from engine (#224)
+    static const unsigned int stageWidth = 480;
+    static const unsigned int stageHeight = 360;
+
+    if (!rng)
+        rng = RandomGenerator::instance().get();
+
+    pointTowardsPos(dynamic_cast<Sprite *>(vm->target()), rng->randint(-static_cast<int>(stageWidth / 2), stageWidth / 2), rng->randint(-static_cast<int>(stageHeight / 2), stageHeight / 2));
+
+    return 0;
+}
diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h
index b756a963..2630acf8 100644
--- a/src/blocks/motionblocks.h
+++ b/src/blocks/motionblocks.h
@@ -7,6 +7,9 @@
 namespace libscratchcpp
 {
 
+class Sprite;
+class IRandomGenerator;
+
 /*! \brief The MotionBlocks class contains the implementation of motion blocks. */
 class MotionBlocks : public IBlockSection
 {
@@ -15,7 +18,16 @@ class MotionBlocks : public IBlockSection
         {
             STEPS,
             DEGREES,
-            DIRECTION
+            DIRECTION,
+            TOWARDS
+        };
+
+        enum Fields
+        {
+        };
+
+        enum FieldValues
+        {
         };
 
         std::string name() const override;
@@ -26,11 +38,21 @@ class MotionBlocks : public IBlockSection
         static void compileTurnRight(Compiler *compiler);
         static void compileTurnLeft(Compiler *compiler);
         static void compilePointInDirection(Compiler *compiler);
+        static void compilePointTowards(Compiler *compiler);
 
         static unsigned int moveSteps(VirtualMachine *vm);
         static unsigned int turnRight(VirtualMachine *vm);
         static unsigned int turnLeft(VirtualMachine *vm);
         static unsigned int pointInDirection(VirtualMachine *vm);
+
+        static void pointTowardsPos(Sprite *sprite, double x, double y);
+
+        static unsigned int pointTowards(VirtualMachine *vm);
+        static unsigned int pointTowardsByIndex(VirtualMachine *vm);
+        static unsigned int pointTowardsMousePointer(VirtualMachine *vm);
+        static unsigned int pointTowardsRandomPosition(VirtualMachine *vm);
+
+        static IRandomGenerator *rng;
 };
 
 } // namespace libscratchcpp
diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
index f3771703..47bd7526 100644
--- a/test/blocks/motion_blocks_test.cpp
+++ b/test/blocks/motion_blocks_test.cpp
@@ -1,11 +1,14 @@
 #include <scratchcpp/compiler.h>
 #include <scratchcpp/block.h>
 #include <scratchcpp/input.h>
+#include <scratchcpp/field.h>
 #include <scratchcpp/sprite.h>
 #include <enginemock.h>
+#include <randomgeneratormock.h>
 
 #include "../common.h"
 #include "blocks/motionblocks.h"
+#include "blocks/operatorblocks.h"
 #include "engine/internal/engine.h"
 
 using namespace libscratchcpp;
@@ -33,6 +36,46 @@ class MotionBlocksTest : public testing::Test
             block->updateInputMap();
         }
 
+        void addObscuredInput(std::shared_ptr<Block> block, const std::string &name, MotionBlocks::Inputs id, std::shared_ptr<Block> valueBlock) const
+        {
+            auto input = std::make_shared<Input>(name, Input::Type::ObscuredShadow);
+            input->setValueBlock(valueBlock);
+            input->setInputId(id);
+            block->addInput(input);
+            block->updateInputMap();
+        }
+
+        std::shared_ptr<Input> addNullInput(std::shared_ptr<Block> block, const std::string &name, MotionBlocks::Inputs id) const
+        {
+            auto input = std::make_shared<Input>(name, Input::Type::Shadow);
+            input->setInputId(id);
+            block->addInput(input);
+            block->updateInputMap();
+
+            return input;
+        }
+
+        void addDropdownInput(std::shared_ptr<Block> block, const std::string &name, MotionBlocks::Inputs id, const std::string &selectedValue, std::shared_ptr<Block> valueBlock = nullptr) const
+        {
+            if (valueBlock)
+                addObscuredInput(block, name, id, valueBlock);
+            else {
+                auto input = addNullInput(block, name, id);
+                auto menu = createMotionBlock(block->id() + "_menu", block->opcode() + "_menu");
+                input->setValueBlock(menu);
+                addDropdownField(menu, "CLONE_OPTION", static_cast<MotionBlocks::Fields>(-1), selectedValue, static_cast<MotionBlocks::FieldValues>(-1));
+            }
+        }
+
+        void addDropdownField(std::shared_ptr<Block> block, const std::string &name, MotionBlocks::Fields id, const std::string &value, MotionBlocks::FieldValues valueId) const
+        {
+            auto field = std::make_shared<Field>(name, value);
+            field->setFieldId(id);
+            field->setSpecialValueId(valueId);
+            block->addField(field);
+            block->updateFieldMap();
+        }
+
         std::unique_ptr<IBlockSection> m_section;
         EngineMock m_engineMock;
         Engine m_engine;
@@ -55,11 +98,13 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_turnright", &MotionBlocks::compileTurnRight));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_turnleft", &MotionBlocks::compileTurnLeft));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_pointindirection", &MotionBlocks::compilePointInDirection));
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_pointtowards", &MotionBlocks::compilePointTowards));
 
     // Inputs
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS));
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DEGREES", MotionBlocks::DEGREES));
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DIRECTION", MotionBlocks::DIRECTION));
+    EXPECT_CALL(m_engineMock, addInput(m_section.get(), "TOWARDS", MotionBlocks::TOWARDS));
 
     m_section->registerBlocks(&m_engineMock);
 }
@@ -222,3 +267,172 @@ TEST_F(MotionBlocksTest, PointInDirectionImpl)
     ASSERT_EQ(vm.registerCount(), 0);
     ASSERT_EQ(sprite.direction(), -60.5);
 }
+
+TEST_F(MotionBlocksTest, PointTowards)
+{
+    Compiler compiler(&m_engineMock);
+
+    // point towards (mouse-pointer)
+    auto block1 = std::make_shared<Block>("a", "motion_pointtowards");
+    addDropdownInput(block1, "TOWARDS", MotionBlocks::TOWARDS, "_mouse_");
+
+    // point towards (random position)
+    auto block2 = std::make_shared<Block>("b", "motion_pointtowards");
+    addDropdownInput(block2, "TOWARDS", MotionBlocks::TOWARDS, "_random_");
+
+    // point towards (Sprite2)
+    auto block3 = std::make_shared<Block>("c", "motion_pointtowards");
+    addDropdownInput(block3, "TOWARDS", MotionBlocks::TOWARDS, "Sprite2");
+
+    // point towards (join "" "")
+    auto joinBlock = std::make_shared<Block>("e", "operator_join");
+    joinBlock->setCompileFunction(&OperatorBlocks::compileJoin);
+    auto block4 = std::make_shared<Block>("d", "motion_pointtowards");
+    addDropdownInput(block4, "TOWARDS", MotionBlocks::TOWARDS, "", joinBlock);
+
+    compiler.init();
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::pointTowardsMousePointer)).WillOnce(Return(0));
+    compiler.setBlock(block1);
+    MotionBlocks::compilePointTowards(&compiler);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::pointTowardsRandomPosition)).WillOnce(Return(1));
+    compiler.setBlock(block2);
+    MotionBlocks::compilePointTowards(&compiler);
+
+    EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(5));
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::pointTowardsByIndex)).WillOnce(Return(2));
+    compiler.setBlock(block3);
+    MotionBlocks::compilePointTowards(&compiler);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::pointTowards)).WillOnce(Return(3));
+    compiler.setBlock(block4);
+    MotionBlocks::compilePointTowards(&compiler);
+
+    compiler.end();
+
+    ASSERT_EQ(
+        compiler.bytecode(),
+        std::vector<unsigned int>({ vm::OP_START, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_CONST, 0, vm::OP_EXEC, 2, vm::OP_NULL, vm::OP_NULL, vm::OP_STR_CONCAT, vm::OP_EXEC, 3, vm::OP_HALT }));
+    ASSERT_EQ(compiler.constValues().size(), 1);
+    ASSERT_EQ(compiler.constValues()[0].toDouble(), 5);
+}
+
+TEST_F(MotionBlocksTest, PointTowardsImpl)
+{
+    static unsigned int bytecode1[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT };
+    static unsigned int bytecode2[] = { vm::OP_START, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_HALT };
+    static unsigned int bytecode3[] = { vm::OP_START, vm::OP_CONST, 2, vm::OP_EXEC, 0, vm::OP_HALT };
+    static unsigned int bytecode4[] = { vm::OP_START, vm::OP_CONST, 3, vm::OP_EXEC, 1, vm::OP_HALT };
+    static unsigned int bytecode5[] = { vm::OP_START, vm::OP_EXEC, 2, vm::OP_HALT };
+    static unsigned int bytecode6[] = { vm::OP_START, vm::OP_EXEC, 3, vm::OP_HALT };
+    static BlockFunc functions[] = { &MotionBlocks::pointTowards, &MotionBlocks::pointTowardsByIndex, &MotionBlocks::pointTowardsMousePointer, &MotionBlocks::pointTowardsRandomPosition };
+    static Value constValues[] = { "_mouse_", "_random_", "Sprite2", 3 };
+
+    Sprite sprite, anotherSprite;
+    sprite.setX(70.1);
+    sprite.setY(-100.025);
+
+    VirtualMachine vm(&sprite, &m_engineMock, nullptr);
+    vm.setFunctions(functions);
+    vm.setConstValues(constValues);
+
+    RandomGeneratorMock rng;
+    MotionBlocks::rng = &rng;
+
+    static const std::vector<std::pair<double, double>> positions = { { -45.12, -123.48 }, { 125.23, -3.21 }, { 30.15, -100.025 }, { 70.1, -100.025 }, { 150.9, -100.025 } };
+    static const std::vector<double> results = { -101.51, 29.66, -90, 90, 90 };
+    static const std::vector<double> intPosResults = { -101.31, 29.55, -90, 90, 90 };
+
+    // point towards (join "_mouse_" "")
+    for (int i = 0; i < positions.size(); i++) {
+        EXPECT_CALL(m_engineMock, mouseX()).WillOnce(Return(positions[i].first));
+        EXPECT_CALL(m_engineMock, mouseY()).WillOnce(Return(positions[i].second));
+
+        // TODO: Move setBytecode() out of the loop and use reset() after task #215 is completed
+        vm.setBytecode(bytecode1);
+        vm.run();
+
+        ASSERT_EQ(vm.registerCount(), 0);
+        ASSERT_EQ(std::round(sprite.direction() * 100) / 100, results[i]);
+    }
+
+    // point towards (join "_random_" "")
+    sprite.setX(std::round(sprite.x()));
+    sprite.setY(std::round(sprite.y()));
+
+    for (int i = 0; i < positions.size(); i++) {
+        EXPECT_CALL(rng, randint(-240, 240)).WillOnce(Return(std::round(positions[i].first)));
+        EXPECT_CALL(rng, randint(-180, 180)).WillOnce(Return(std::round(positions[i].second)));
+
+        // TODO: Move setBytecode() out of the loop and use reset() after task #215 is completed
+        vm.setBytecode(bytecode2);
+        vm.run();
+
+        ASSERT_EQ(vm.registerCount(), 0);
+        ASSERT_EQ(std::round(sprite.direction() * 100) / 100, intPosResults[i]);
+    }
+
+    sprite.setX(70.1);
+    sprite.setY(-100.025);
+
+    // point towards (join "Sprite2" "")
+    for (int i = 0; i < positions.size(); i++) {
+        anotherSprite.setX(positions[i].first);
+        anotherSprite.setY(positions[i].second);
+
+        EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(3));
+        EXPECT_CALL(m_engineMock, targetAt(3)).WillOnce(Return(&anotherSprite));
+
+        // TODO: Move setBytecode() out of the loop and use reset() after task #215 is completed
+        vm.setBytecode(bytecode3);
+        vm.run();
+
+        ASSERT_EQ(vm.registerCount(), 0);
+        ASSERT_EQ(std::round(sprite.direction() * 100) / 100, results[i]);
+    }
+
+    // point towards (Sprite2)
+    for (int i = 0; i < positions.size(); i++) {
+        anotherSprite.setX(positions[i].first);
+        anotherSprite.setY(positions[i].second);
+
+        EXPECT_CALL(m_engineMock, targetAt(3)).WillOnce(Return(&anotherSprite));
+
+        // TODO: Move setBytecode() out of the loop and use reset() after task #215 is completed
+        vm.setBytecode(bytecode4);
+        vm.run();
+
+        ASSERT_EQ(vm.registerCount(), 0);
+        ASSERT_EQ(std::round(sprite.direction() * 100) / 100, results[i]);
+    }
+
+    // point towards (mouse-pointer)
+    for (int i = 0; i < positions.size(); i++) {
+        EXPECT_CALL(m_engineMock, mouseX()).WillOnce(Return(positions[i].first));
+        EXPECT_CALL(m_engineMock, mouseY()).WillOnce(Return(positions[i].second));
+
+        // TODO: Move setBytecode() out of the loop and use reset() after task #215 is completed
+        vm.setBytecode(bytecode5);
+        vm.run();
+
+        ASSERT_EQ(vm.registerCount(), 0);
+        ASSERT_EQ(std::round(sprite.direction() * 100) / 100, results[i]);
+    }
+
+    // point towards (random position)
+    sprite.setX(std::round(sprite.x()));
+    sprite.setY(std::round(sprite.y()));
+
+    for (int i = 0; i < positions.size(); i++) {
+        EXPECT_CALL(rng, randint(-240, 240)).WillOnce(Return(std::round(positions[i].first)));
+        EXPECT_CALL(rng, randint(-180, 180)).WillOnce(Return(std::round(positions[i].second)));
+
+        // TODO: Move setBytecode() out of the loop and use reset() after task #215 is completed
+        vm.setBytecode(bytecode6);
+        vm.run();
+
+        ASSERT_EQ(vm.registerCount(), 0);
+        ASSERT_EQ(std::round(sprite.direction() * 100) / 100, intPosResults[i]);
+    }
+}

From 191d617917094ccc01913b9a3317d6c9cec47af9 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 23 Sep 2023 14:21:40 +0200
Subject: [PATCH 21/49] Implement motion_gotoxy block

---
 src/blocks/motionblocks.cpp        | 22 ++++++++++++++++
 src/blocks/motionblocks.h          |  7 ++++-
 test/blocks/motion_blocks_test.cpp | 42 ++++++++++++++++++++++++++++++
 3 files changed, 70 insertions(+), 1 deletion(-)

diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp
index 9b2325f5..82984fad 100644
--- a/src/blocks/motionblocks.cpp
+++ b/src/blocks/motionblocks.cpp
@@ -27,12 +27,15 @@ void MotionBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "motion_turnleft", &compileTurnLeft);
     engine->addCompileFunction(this, "motion_pointindirection", &compilePointInDirection);
     engine->addCompileFunction(this, "motion_pointtowards", &compilePointTowards);
+    engine->addCompileFunction(this, "motion_gotoxy", &compileGoToXY);
 
     // Inputs
     engine->addInput(this, "STEPS", STEPS);
     engine->addInput(this, "DEGREES", DEGREES);
     engine->addInput(this, "DIRECTION", DIRECTION);
     engine->addInput(this, "TOWARDS", TOWARDS);
+    engine->addInput(this, "X", X);
+    engine->addInput(this, "Y", Y);
 }
 
 void MotionBlocks::compileMoveSteps(Compiler *compiler)
@@ -82,6 +85,13 @@ void MotionBlocks::compilePointTowards(Compiler *compiler)
     }
 }
 
+void MotionBlocks::compileGoToXY(Compiler *compiler)
+{
+    compiler->addInput(X);
+    compiler->addInput(Y);
+    compiler->addFunctionCall(&goToXY);
+}
+
 unsigned int MotionBlocks::moveSteps(VirtualMachine *vm)
 {
     Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
@@ -203,3 +213,15 @@ unsigned int MotionBlocks::pointTowardsRandomPosition(VirtualMachine *vm)
 
     return 0;
 }
+
+unsigned int MotionBlocks::goToXY(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (sprite) {
+        sprite->setX(vm->getInput(0, 2)->toDouble());
+        sprite->setY(vm->getInput(1, 2)->toDouble());
+    }
+
+    return 2;
+}
diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h
index 2630acf8..0b880be7 100644
--- a/src/blocks/motionblocks.h
+++ b/src/blocks/motionblocks.h
@@ -19,7 +19,9 @@ class MotionBlocks : public IBlockSection
             STEPS,
             DEGREES,
             DIRECTION,
-            TOWARDS
+            TOWARDS,
+            X,
+            Y
         };
 
         enum Fields
@@ -39,6 +41,7 @@ class MotionBlocks : public IBlockSection
         static void compileTurnLeft(Compiler *compiler);
         static void compilePointInDirection(Compiler *compiler);
         static void compilePointTowards(Compiler *compiler);
+        static void compileGoToXY(Compiler *compiler);
 
         static unsigned int moveSteps(VirtualMachine *vm);
         static unsigned int turnRight(VirtualMachine *vm);
@@ -52,6 +55,8 @@ class MotionBlocks : public IBlockSection
         static unsigned int pointTowardsMousePointer(VirtualMachine *vm);
         static unsigned int pointTowardsRandomPosition(VirtualMachine *vm);
 
+        static unsigned int goToXY(VirtualMachine *vm);
+
         static IRandomGenerator *rng;
 };
 
diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
index 47bd7526..32373dc4 100644
--- a/test/blocks/motion_blocks_test.cpp
+++ b/test/blocks/motion_blocks_test.cpp
@@ -99,12 +99,15 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_turnleft", &MotionBlocks::compileTurnLeft));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_pointindirection", &MotionBlocks::compilePointInDirection));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_pointtowards", &MotionBlocks::compilePointTowards));
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_gotoxy", &MotionBlocks::compileGoToXY));
 
     // Inputs
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS));
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DEGREES", MotionBlocks::DEGREES));
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DIRECTION", MotionBlocks::DIRECTION));
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "TOWARDS", MotionBlocks::TOWARDS));
+    EXPECT_CALL(m_engineMock, addInput(m_section.get(), "X", MotionBlocks::X));
+    EXPECT_CALL(m_engineMock, addInput(m_section.get(), "Y", MotionBlocks::Y));
 
     m_section->registerBlocks(&m_engineMock);
 }
@@ -436,3 +439,42 @@ TEST_F(MotionBlocksTest, PointTowardsImpl)
         ASSERT_EQ(std::round(sprite.direction() * 100) / 100, intPosResults[i]);
     }
 }
+
+TEST_F(MotionBlocksTest, GoToXY)
+{
+    Compiler compiler(&m_engineMock);
+
+    // turn right (12.05) degrees
+    auto block = std::make_shared<Block>("a", "motion_gotoxy");
+    addValueInput(block, "X", MotionBlocks::X, 95.2);
+    addValueInput(block, "Y", MotionBlocks::Y, -175.9);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::goToXY)).WillOnce(Return(0));
+
+    compiler.init();
+    compiler.setBlock(block);
+    MotionBlocks::compileGoToXY(&compiler);
+    compiler.end();
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_CONST, 0, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_HALT }));
+    ASSERT_EQ(compiler.constValues(), std::vector<Value>({ 95.2, -175.9 }));
+}
+
+TEST_F(MotionBlocksTest, GoToXYImpl)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &MotionBlocks::goToXY };
+    static Value constValues[] = { 95.2, -175.9 };
+
+    Sprite sprite;
+
+    VirtualMachine vm(&sprite, nullptr, nullptr);
+    vm.setBytecode(bytecode);
+    vm.setFunctions(functions);
+    vm.setConstValues(constValues);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(sprite.x(), 95.2);
+    ASSERT_EQ(sprite.y(), -175.9);
+}

From d0b16f39c7d99a4984f4dce70767742307253ecc Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 23 Sep 2023 16:21:57 +0200
Subject: [PATCH 22/49] Implement motion_goto block

---
 src/blocks/motionblocks.cpp        | 104 +++++++++++++++++++++
 src/blocks/motionblocks.h          |   8 +-
 test/blocks/motion_blocks_test.cpp | 144 +++++++++++++++++++++++++++++
 3 files changed, 255 insertions(+), 1 deletion(-)

diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp
index 82984fad..f4d4fa6d 100644
--- a/src/blocks/motionblocks.cpp
+++ b/src/blocks/motionblocks.cpp
@@ -28,6 +28,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);
 
     // Inputs
     engine->addInput(this, "STEPS", STEPS);
@@ -36,6 +37,7 @@ void MotionBlocks::registerBlocks(IEngine *engine)
     engine->addInput(this, "TOWARDS", TOWARDS);
     engine->addInput(this, "X", X);
     engine->addInput(this, "Y", Y);
+    engine->addInput(this, "TO", TO);
 }
 
 void MotionBlocks::compileMoveSteps(Compiler *compiler)
@@ -92,6 +94,29 @@ void MotionBlocks::compileGoToXY(Compiler *compiler)
     compiler->addFunctionCall(&goToXY);
 }
 
+void MotionBlocks::compileGoTo(Compiler *compiler)
+{
+    Input *input = compiler->input(TO);
+
+    if (input->type() != Input::Type::ObscuredShadow) {
+        assert(input->pointsToDropdownMenu());
+        std::string value = input->selectedMenuItem();
+
+        if (value == "_mouse_")
+            compiler->addFunctionCall(&goToMousePointer);
+        else if (value == "_random_")
+            compiler->addFunctionCall(&goToRandomPosition);
+        else {
+            int index = compiler->engine()->findTarget(value);
+            compiler->addConstValue(index);
+            compiler->addFunctionCall(&goToByIndex);
+        }
+    } else {
+        compiler->addInput(input);
+        compiler->addFunctionCall(&goTo);
+    }
+}
+
 unsigned int MotionBlocks::moveSteps(VirtualMachine *vm)
 {
     Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
@@ -225,3 +250,82 @@ unsigned int MotionBlocks::goToXY(VirtualMachine *vm)
 
     return 2;
 }
+
+unsigned int MotionBlocks::goTo(VirtualMachine *vm)
+{
+    std::string value = vm->getInput(0, 1)->toString();
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (!sprite)
+        return 1;
+
+    if (value == "_mouse_") {
+        sprite->setX(vm->engine()->mouseX());
+        sprite->setY(vm->engine()->mouseY());
+    } else if (value == "_random_") {
+        // TODO: Read stage size from engine (#224)
+        static const unsigned int stageWidth = 480;
+        static const unsigned int stageHeight = 360;
+
+        if (!rng)
+            rng = RandomGenerator::instance().get();
+
+        sprite->setX(rng->randint(-static_cast<int>(stageWidth / 2), stageWidth / 2));
+        sprite->setY(rng->randint(-static_cast<int>(stageHeight / 2), stageHeight / 2));
+    } else {
+        Target *target = vm->engine()->targetAt(vm->engine()->findTarget(value));
+        Sprite *targetSprite = dynamic_cast<Sprite *>(target);
+
+        if (targetSprite) {
+            sprite->setX(targetSprite->x());
+            sprite->setY(targetSprite->y());
+        }
+    }
+
+    return 1;
+}
+
+unsigned int MotionBlocks::goToByIndex(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+    Target *target = vm->engine()->targetAt(vm->getInput(0, 1)->toInt());
+    Sprite *targetSprite = dynamic_cast<Sprite *>(target);
+
+    if (sprite && targetSprite) {
+        sprite->setX(targetSprite->x());
+        sprite->setY(targetSprite->y());
+    }
+
+    return 1;
+}
+
+unsigned int MotionBlocks::goToMousePointer(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (sprite) {
+        sprite->setX(vm->engine()->mouseX());
+        sprite->setY(vm->engine()->mouseY());
+    }
+
+    return 0;
+}
+
+unsigned int MotionBlocks::goToRandomPosition(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (sprite) {
+        // TODO: Read stage size from engine (#224)
+        static const unsigned int stageWidth = 480;
+        static const unsigned int stageHeight = 360;
+
+        if (!rng)
+            rng = RandomGenerator::instance().get();
+
+        sprite->setX(rng->randint(-static_cast<int>(stageWidth / 2), stageWidth / 2));
+        sprite->setY(rng->randint(-static_cast<int>(stageHeight / 2), stageHeight / 2));
+    }
+
+    return 0;
+}
diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h
index 0b880be7..c65e887a 100644
--- a/src/blocks/motionblocks.h
+++ b/src/blocks/motionblocks.h
@@ -21,7 +21,8 @@ class MotionBlocks : public IBlockSection
             DIRECTION,
             TOWARDS,
             X,
-            Y
+            Y,
+            TO
         };
 
         enum Fields
@@ -42,6 +43,7 @@ class MotionBlocks : public IBlockSection
         static void compilePointInDirection(Compiler *compiler);
         static void compilePointTowards(Compiler *compiler);
         static void compileGoToXY(Compiler *compiler);
+        static void compileGoTo(Compiler *compiler);
 
         static unsigned int moveSteps(VirtualMachine *vm);
         static unsigned int turnRight(VirtualMachine *vm);
@@ -56,6 +58,10 @@ class MotionBlocks : public IBlockSection
         static unsigned int pointTowardsRandomPosition(VirtualMachine *vm);
 
         static unsigned int goToXY(VirtualMachine *vm);
+        static unsigned int goTo(VirtualMachine *vm);
+        static unsigned int goToByIndex(VirtualMachine *vm);
+        static unsigned int goToMousePointer(VirtualMachine *vm);
+        static unsigned int goToRandomPosition(VirtualMachine *vm);
 
         static IRandomGenerator *rng;
 };
diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
index 32373dc4..96791d9d 100644
--- a/test/blocks/motion_blocks_test.cpp
+++ b/test/blocks/motion_blocks_test.cpp
@@ -100,6 +100,7 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_pointindirection", &MotionBlocks::compilePointInDirection));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_pointtowards", &MotionBlocks::compilePointTowards));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_gotoxy", &MotionBlocks::compileGoToXY));
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_goto", &MotionBlocks::compileGoTo));
 
     // Inputs
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS));
@@ -108,6 +109,7 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "TOWARDS", MotionBlocks::TOWARDS));
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "X", MotionBlocks::X));
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "Y", MotionBlocks::Y));
+    EXPECT_CALL(m_engineMock, addInput(m_section.get(), "TO", MotionBlocks::TO));
 
     m_section->registerBlocks(&m_engineMock);
 }
@@ -478,3 +480,145 @@ TEST_F(MotionBlocksTest, GoToXYImpl)
     ASSERT_EQ(sprite.x(), 95.2);
     ASSERT_EQ(sprite.y(), -175.9);
 }
+
+TEST_F(MotionBlocksTest, GoTo)
+{
+    Compiler compiler(&m_engineMock);
+
+    // go to (mouse-pointer)
+    auto block1 = std::make_shared<Block>("a", "motion_goto");
+    addDropdownInput(block1, "TO", MotionBlocks::TO, "_mouse_");
+
+    // go to (random position)
+    auto block2 = std::make_shared<Block>("b", "motion_goto");
+    addDropdownInput(block2, "TO", MotionBlocks::TO, "_random_");
+
+    // go to (Sprite2)
+    auto block3 = std::make_shared<Block>("c", "motion_goto");
+    addDropdownInput(block3, "TO", MotionBlocks::TO, "Sprite2");
+
+    // go to (join "" "")
+    auto joinBlock = std::make_shared<Block>("e", "operator_join");
+    joinBlock->setCompileFunction(&OperatorBlocks::compileJoin);
+    auto block4 = std::make_shared<Block>("d", "motion_goto");
+    addDropdownInput(block4, "TO", MotionBlocks::TO, "", joinBlock);
+
+    compiler.init();
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::goToMousePointer)).WillOnce(Return(0));
+    compiler.setBlock(block1);
+    MotionBlocks::compileGoTo(&compiler);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::goToRandomPosition)).WillOnce(Return(1));
+    compiler.setBlock(block2);
+    MotionBlocks::compileGoTo(&compiler);
+
+    EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(5));
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::goToByIndex)).WillOnce(Return(2));
+    compiler.setBlock(block3);
+    MotionBlocks::compileGoTo(&compiler);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::goTo)).WillOnce(Return(3));
+    compiler.setBlock(block4);
+    MotionBlocks::compileGoTo(&compiler);
+
+    compiler.end();
+
+    ASSERT_EQ(
+        compiler.bytecode(),
+        std::vector<unsigned int>({ vm::OP_START, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_CONST, 0, vm::OP_EXEC, 2, vm::OP_NULL, vm::OP_NULL, vm::OP_STR_CONCAT, vm::OP_EXEC, 3, vm::OP_HALT }));
+    ASSERT_EQ(compiler.constValues().size(), 1);
+    ASSERT_EQ(compiler.constValues()[0].toDouble(), 5);
+}
+
+TEST_F(MotionBlocksTest, GoToImpl)
+{
+    static unsigned int bytecode1[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT };
+    static unsigned int bytecode2[] = { vm::OP_START, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_HALT };
+    static unsigned int bytecode3[] = { vm::OP_START, vm::OP_CONST, 2, vm::OP_EXEC, 0, vm::OP_HALT };
+    static unsigned int bytecode4[] = { vm::OP_START, vm::OP_CONST, 3, vm::OP_EXEC, 1, vm::OP_HALT };
+    static unsigned int bytecode5[] = { vm::OP_START, vm::OP_EXEC, 2, vm::OP_HALT };
+    static unsigned int bytecode6[] = { vm::OP_START, vm::OP_EXEC, 3, vm::OP_HALT };
+    static BlockFunc functions[] = { &MotionBlocks::goTo, &MotionBlocks::goToByIndex, &MotionBlocks::goToMousePointer, &MotionBlocks::goToRandomPosition };
+    static Value constValues[] = { "_mouse_", "_random_", "Sprite2", 3 };
+
+    Sprite sprite;
+    sprite.setX(70.1);
+    sprite.setY(-100.025);
+
+    Sprite anotherSprite;
+    anotherSprite.setX(-195.25);
+    anotherSprite.setY(148.02);
+
+    VirtualMachine vm(&sprite, &m_engineMock, nullptr);
+    vm.setFunctions(functions);
+    vm.setConstValues(constValues);
+
+    RandomGeneratorMock rng;
+    MotionBlocks::rng = &rng;
+
+    // go to (join "_mouse_" "")
+    EXPECT_CALL(m_engineMock, mouseX()).WillOnce(Return(70.56));
+    EXPECT_CALL(m_engineMock, mouseY()).WillOnce(Return(45.2));
+
+    vm.setBytecode(bytecode1);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(sprite.x(), 70.56);
+    ASSERT_EQ(sprite.y(), 45.2);
+
+    // go to (join "_random_" "")
+    EXPECT_CALL(rng, randint(-240, 240)).WillOnce(Return(-158));
+    EXPECT_CALL(rng, randint(-180, 180)).WillOnce(Return(65));
+
+    vm.setBytecode(bytecode2);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(sprite.x(), -158);
+    ASSERT_EQ(sprite.y(), 65);
+
+    // go to (join "Sprite2" "")
+    EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(3));
+    EXPECT_CALL(m_engineMock, targetAt(3)).WillOnce(Return(&anotherSprite));
+
+    vm.setBytecode(bytecode3);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(sprite.x(), anotherSprite.x());
+    ASSERT_EQ(sprite.y(), anotherSprite.y());
+
+    // go to (Sprite2)
+    EXPECT_CALL(m_engineMock, targetAt(3)).WillOnce(Return(&anotherSprite));
+
+    vm.setBytecode(bytecode4);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(sprite.x(), anotherSprite.x());
+    ASSERT_EQ(sprite.y(), anotherSprite.y());
+
+    // go to (mouse-pointer)
+    EXPECT_CALL(m_engineMock, mouseX()).WillOnce(Return(-239.92));
+    EXPECT_CALL(m_engineMock, mouseY()).WillOnce(Return(-170.6));
+
+    vm.setBytecode(bytecode5);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(sprite.x(), -239.92);
+    ASSERT_EQ(sprite.y(), -170.6);
+
+    // go to (random position)
+    EXPECT_CALL(rng, randint(-240, 240)).WillOnce(Return(220));
+    EXPECT_CALL(rng, randint(-180, 180)).WillOnce(Return(-16));
+
+    vm.setBytecode(bytecode6);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(sprite.x(), 220);
+    ASSERT_EQ(sprite.y(), -16);
+}

From bb99a36c3d23e2f08fb88136452cdd3cf02470a8 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 23 Sep 2023 16:29:24 +0200
Subject: [PATCH 23/49] Update compiler test (load_test project)

The load_test project contains the motion_goto block which is now supported.
---
 test/compiler/compiler_test.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/compiler/compiler_test.cpp b/test/compiler/compiler_test.cpp
index 572b5069..fd778ba0 100644
--- a/test/compiler/compiler_test.cpp
+++ b/test/compiler/compiler_test.cpp
@@ -806,7 +806,7 @@ TEST_F(CompilerTest, MultipleTargets)
 
     auto sprite1 = engine.targetAt(engine.findTarget("Sprite1"));
     auto script = scripts.at(sprite1->greenFlagBlocks().at(0));
-    ASSERT_EQ(script->bytecodeVector().size(), 30);
+    ASSERT_EQ(script->bytecodeVector().size(), 32);
     auto vm = script->start();
     ASSERT_EQ(vm->target(), sprite1);
     ASSERT_EQ(vm->engine(), &engine);

From 385d89591f13746d1500f9fbbbbdf200f49a5b4e Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 23 Sep 2023 21:18:14 +0200
Subject: [PATCH 24/49] Remove obsolete target variable from move steps
 implementation

---
 src/blocks/motionblocks.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp
index f4d4fa6d..1bbc44f4 100644
--- a/src/blocks/motionblocks.cpp
+++ b/src/blocks/motionblocks.cpp
@@ -184,7 +184,6 @@ void MotionBlocks::pointTowardsPos(Sprite *sprite, double x, double y)
 unsigned int MotionBlocks::pointTowards(VirtualMachine *vm)
 {
     std::string value = vm->getInput(0, 1)->toString();
-    Target *target;
 
     if (value == "_mouse_")
         pointTowardsPos(dynamic_cast<Sprite *>(vm->target()), vm->engine()->mouseX(), vm->engine()->mouseY());
@@ -198,7 +197,7 @@ unsigned int MotionBlocks::pointTowards(VirtualMachine *vm)
 
         pointTowardsPos(dynamic_cast<Sprite *>(vm->target()), rng->randint(-static_cast<int>(stageWidth / 2), stageWidth / 2), rng->randint(-static_cast<int>(stageHeight / 2), stageHeight / 2));
     } else {
-        target = vm->engine()->targetAt(vm->engine()->findTarget(value));
+        Target *target = vm->engine()->targetAt(vm->engine()->findTarget(value));
         Sprite *sprite = dynamic_cast<Sprite *>(target);
 
         if (sprite)

From f7687edaba21d7956c5c10a2dd12ff58e0434429 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 23 Sep 2023 21:20:10 +0200
Subject: [PATCH 25/49] Fix a typo in GoToXY test comment

---
 test/blocks/motion_blocks_test.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
index 96791d9d..36f742b1 100644
--- a/test/blocks/motion_blocks_test.cpp
+++ b/test/blocks/motion_blocks_test.cpp
@@ -446,7 +446,7 @@ TEST_F(MotionBlocksTest, GoToXY)
 {
     Compiler compiler(&m_engineMock);
 
-    // turn right (12.05) degrees
+    // go to x: (95.2) y: (-175.9)
     auto block = std::make_shared<Block>("a", "motion_gotoxy");
     addValueInput(block, "X", MotionBlocks::X, 95.2);
     addValueInput(block, "Y", MotionBlocks::Y, -175.9);

From 4e2243f7500584cfa2e786a3bbdca93894737e17 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 23 Sep 2023 21:35:10 +0200
Subject: [PATCH 26/49] Reset random generator in motion blocks test

---
 test/blocks/motion_blocks_test.cpp | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
index 36f742b1..a13deb5d 100644
--- a/test/blocks/motion_blocks_test.cpp
+++ b/test/blocks/motion_blocks_test.cpp
@@ -10,6 +10,7 @@
 #include "blocks/motionblocks.h"
 #include "blocks/operatorblocks.h"
 #include "engine/internal/engine.h"
+#include "engine/internal/randomgenerator.h"
 
 using namespace libscratchcpp;
 
@@ -440,6 +441,8 @@ TEST_F(MotionBlocksTest, PointTowardsImpl)
         ASSERT_EQ(vm.registerCount(), 0);
         ASSERT_EQ(std::round(sprite.direction() * 100) / 100, intPosResults[i]);
     }
+
+    MotionBlocks::rng = RandomGenerator::instance().get();
 }
 
 TEST_F(MotionBlocksTest, GoToXY)
@@ -621,4 +624,6 @@ TEST_F(MotionBlocksTest, GoToImpl)
     ASSERT_EQ(vm.registerCount(), 0);
     ASSERT_EQ(sprite.x(), 220);
     ASSERT_EQ(sprite.y(), -16);
+
+    MotionBlocks::rng = RandomGenerator::instance().get();
 }

From 6b8c1cf62531213e81dca079288e033ef6ff5479 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 23 Sep 2023 22:37:17 +0200
Subject: [PATCH 27/49] Implement motion_glidesecstoxy block

---
 src/blocks/motionblocks.cpp        | 91 ++++++++++++++++++++++++++++++
 src/blocks/motionblocks.h          | 18 +++++-
 test/blocks/motion_blocks_test.cpp | 89 +++++++++++++++++++++++++++++
 3 files changed, 197 insertions(+), 1 deletion(-)

diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp
index 1bbc44f4..ff7e7e91 100644
--- a/src/blocks/motionblocks.cpp
+++ b/src/blocks/motionblocks.cpp
@@ -7,12 +7,14 @@
 
 #include "motionblocks.h"
 #include "../engine/internal/randomgenerator.h"
+#include "../engine/internal/clock.h"
 
 using namespace libscratchcpp;
 
 static const double pi = std::acos(-1); // TODO: Use std::numbers::pi in C++20
 
 IRandomGenerator *MotionBlocks::rng = nullptr;
+IClock *MotionBlocks::clock = nullptr;
 
 std::string MotionBlocks::name() const
 {
@@ -29,6 +31,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);
 
     // Inputs
     engine->addInput(this, "STEPS", STEPS);
@@ -38,6 +41,7 @@ void MotionBlocks::registerBlocks(IEngine *engine)
     engine->addInput(this, "X", X);
     engine->addInput(this, "Y", Y);
     engine->addInput(this, "TO", TO);
+    engine->addInput(this, "SECS", SECS);
 }
 
 void MotionBlocks::compileMoveSteps(Compiler *compiler)
@@ -117,6 +121,15 @@ void MotionBlocks::compileGoTo(Compiler *compiler)
     }
 }
 
+void MotionBlocks::compileGlideSecsToXY(Compiler *compiler)
+{
+    compiler->addInput(SECS);
+    compiler->addInput(X);
+    compiler->addInput(Y);
+    compiler->addFunctionCall(&startGlideSecsTo);
+    compiler->addFunctionCall(&glideSecsTo);
+}
+
 unsigned int MotionBlocks::moveSteps(VirtualMachine *vm)
 {
     Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
@@ -328,3 +341,81 @@ unsigned int MotionBlocks::goToRandomPosition(VirtualMachine *vm)
 
     return 0;
 }
+
+void MotionBlocks::startGlidingToPos(VirtualMachine *vm, double x, double y, double secs)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (!sprite)
+        return;
+
+    if (secs <= 0) {
+        if (sprite) {
+            sprite->setX(x);
+            sprite->setY(y);
+        }
+
+        return;
+    }
+
+    if (!clock)
+        clock = Clock::instance().get();
+
+    auto currentTime = clock->currentSteadyTime();
+    m_timeMap[vm] = { currentTime, secs * 1000 };
+    m_glideMap[vm] = { { sprite->x(), sprite->y() }, { x, y } };
+}
+
+void MotionBlocks::continueGliding(VirtualMachine *vm)
+{
+    if (!clock)
+        clock = Clock::instance().get();
+
+    auto currentTime = clock->currentSteadyTime();
+    auto elapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - m_timeMap[vm].first).count();
+    auto maxTime = m_timeMap[vm].second;
+    assert(m_timeMap.count(vm) == 1);
+
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+    double x = m_glideMap[vm].second.first;
+    double y = m_glideMap[vm].second.second;
+
+    if (elapsedTime >= maxTime) {
+        if (sprite) {
+            sprite->setX(x);
+            sprite->setY(y);
+        }
+
+        m_timeMap.erase(vm);
+        m_glideMap.erase(vm);
+    } else {
+        if (sprite) {
+            double startX = m_glideMap[vm].first.first;
+            double startY = m_glideMap[vm].first.second;
+            double factor = elapsedTime / static_cast<double>(maxTime);
+            assert(factor >= 0 && factor < 1);
+
+            sprite->setX(startX + (x - startX) * factor);
+            sprite->setY(startY + (y - startY) * factor);
+        }
+
+        vm->stop(true, true, true);
+        vm->engine()->lockFrame();
+    }
+}
+
+unsigned int MotionBlocks::startGlideSecsTo(VirtualMachine *vm)
+{
+    startGlidingToPos(vm, vm->getInput(1, 3)->toDouble(), vm->getInput(2, 3)->toDouble(), vm->getInput(0, 3)->toDouble());
+    return 3;
+}
+
+unsigned int MotionBlocks::glideSecsTo(VirtualMachine *vm)
+{
+    if (m_timeMap.find(vm) != m_timeMap.cend()) {
+        assert(m_glideMap.find(vm) != m_glideMap.cend());
+        continueGliding(vm);
+    }
+
+    return 0;
+}
diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h
index c65e887a..694ca951 100644
--- a/src/blocks/motionblocks.h
+++ b/src/blocks/motionblocks.h
@@ -2,6 +2,9 @@
 
 #pragma once
 
+#include <unordered_map>
+#include <chrono>
+
 #include <scratchcpp/iblocksection.h>
 
 namespace libscratchcpp
@@ -9,6 +12,7 @@ namespace libscratchcpp
 
 class Sprite;
 class IRandomGenerator;
+class IClock;
 
 /*! \brief The MotionBlocks class contains the implementation of motion blocks. */
 class MotionBlocks : public IBlockSection
@@ -22,7 +26,8 @@ class MotionBlocks : public IBlockSection
             TOWARDS,
             X,
             Y,
-            TO
+            TO,
+            SECS
         };
 
         enum Fields
@@ -44,6 +49,7 @@ class MotionBlocks : public IBlockSection
         static void compilePointTowards(Compiler *compiler);
         static void compileGoToXY(Compiler *compiler);
         static void compileGoTo(Compiler *compiler);
+        static void compileGlideSecsToXY(Compiler *compiler);
 
         static unsigned int moveSteps(VirtualMachine *vm);
         static unsigned int turnRight(VirtualMachine *vm);
@@ -63,7 +69,17 @@ class MotionBlocks : public IBlockSection
         static unsigned int goToMousePointer(VirtualMachine *vm);
         static unsigned int goToRandomPosition(VirtualMachine *vm);
 
+        static void startGlidingToPos(VirtualMachine *vm, double x, double y, double secs);
+        static void continueGliding(VirtualMachine *vm);
+
+        static unsigned int startGlideSecsTo(VirtualMachine *vm);
+        static unsigned int glideSecsTo(VirtualMachine *vm);
+
         static IRandomGenerator *rng;
+        static IClock *clock;
+
+        static inline std::unordered_map<VirtualMachine *, std::pair<std::chrono::steady_clock::time_point, int>> m_timeMap;
+        static inline std::unordered_map<VirtualMachine *, std::pair<std::pair<double, double>, std::pair<double, double>>> m_glideMap; // start pos, end pos
 };
 
 } // namespace libscratchcpp
diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
index a13deb5d..27b1d4fe 100644
--- a/test/blocks/motion_blocks_test.cpp
+++ b/test/blocks/motion_blocks_test.cpp
@@ -5,12 +5,14 @@
 #include <scratchcpp/sprite.h>
 #include <enginemock.h>
 #include <randomgeneratormock.h>
+#include <clockmock.h>
 
 #include "../common.h"
 #include "blocks/motionblocks.h"
 #include "blocks/operatorblocks.h"
 #include "engine/internal/engine.h"
 #include "engine/internal/randomgenerator.h"
+#include "engine/internal/clock.h"
 
 using namespace libscratchcpp;
 
@@ -102,6 +104,7 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_pointtowards", &MotionBlocks::compilePointTowards));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_gotoxy", &MotionBlocks::compileGoToXY));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_goto", &MotionBlocks::compileGoTo));
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_glidesecstoxy", &MotionBlocks::compileGlideSecsToXY));
 
     // Inputs
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS));
@@ -111,6 +114,7 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "X", MotionBlocks::X));
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "Y", MotionBlocks::Y));
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "TO", MotionBlocks::TO));
+    EXPECT_CALL(m_engineMock, addInput(m_section.get(), "SECS", MotionBlocks::SECS));
 
     m_section->registerBlocks(&m_engineMock);
 }
@@ -627,3 +631,88 @@ TEST_F(MotionBlocksTest, GoToImpl)
 
     MotionBlocks::rng = RandomGenerator::instance().get();
 }
+
+TEST_F(MotionBlocksTest, GlideSecsToXY)
+{
+    Compiler compiler(&m_engineMock);
+
+    // glide (2.5) secs to x: (95.2) y: (-175.9)
+    auto block = std::make_shared<Block>("a", "motion_gotoxy");
+    addValueInput(block, "SECS", MotionBlocks::SECS, 2.5);
+    addValueInput(block, "X", MotionBlocks::X, 95.2);
+    addValueInput(block, "Y", MotionBlocks::Y, -175.9);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::startGlideSecsTo)).WillOnce(Return(0));
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::glideSecsTo)).WillOnce(Return(1));
+
+    compiler.init();
+    compiler.setBlock(block);
+    MotionBlocks::compileGlideSecsToXY(&compiler);
+    compiler.end();
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_CONST, 0, vm::OP_CONST, 1, vm::OP_CONST, 2, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_HALT }));
+    ASSERT_EQ(compiler.constValues(), std::vector<Value>({ 2.5, 95.2, -175.9 }));
+}
+
+TEST_F(MotionBlocksTest, GlideSecsToXYImpl)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_CONST, 1, vm::OP_CONST, 2, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_HALT };
+    static BlockFunc functions[] = { &MotionBlocks::startGlideSecsTo, &MotionBlocks::glideSecsTo };
+    static Value constValues[] = { 2.5, 95.2, -175.9 };
+
+    double startX = 100.32;
+    double startY = -50.12;
+
+    Sprite sprite;
+    sprite.setX(startX);
+    sprite.setY(startY);
+
+    VirtualMachine vm(&sprite, &m_engineMock, nullptr);
+    vm.setBytecode(bytecode);
+    vm.setFunctions(functions);
+    vm.setConstValues(constValues);
+
+    ClockMock clock;
+    MotionBlocks::clock = &clock;
+
+    std::chrono::steady_clock::time_point startTime(std::chrono::milliseconds(1000));
+    EXPECT_CALL(clock, currentSteadyTime()).Times(2).WillRepeatedly(Return(startTime));
+    EXPECT_CALL(m_engineMock, lockFrame()).Times(1);
+    EXPECT_CALL(m_engineMock, breakFrame()).Times(1);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_TRUE(MotionBlocks::m_timeMap.find(&vm) != MotionBlocks::m_timeMap.cend());
+    ASSERT_TRUE(MotionBlocks::m_glideMap.find(&vm) != MotionBlocks::m_glideMap.cend());
+    ASSERT_FALSE(vm.atEnd());
+    ASSERT_EQ(sprite.x(), startX);
+    ASSERT_EQ(sprite.y(), startY);
+
+    std::chrono::steady_clock::time_point time1(std::chrono::milliseconds(3456));
+    EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time1));
+    EXPECT_CALL(m_engineMock, lockFrame()).Times(1);
+    EXPECT_CALL(m_engineMock, breakFrame()).Times(1);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_TRUE(MotionBlocks::m_timeMap.find(&vm) != MotionBlocks::m_timeMap.cend());
+    ASSERT_TRUE(MotionBlocks::m_glideMap.find(&vm) != MotionBlocks::m_glideMap.cend());
+    ASSERT_FALSE(vm.atEnd());
+    ASSERT_EQ(std::round(sprite.x() * 100) / 100, 95.29);
+    ASSERT_EQ(std::round(sprite.y() * 100) / 100, -173.69);
+
+    std::chrono::steady_clock::time_point time2(std::chrono::milliseconds(3500));
+    EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time2));
+    EXPECT_CALL(m_engineMock, lockFrame()).Times(0);
+    EXPECT_CALL(m_engineMock, breakFrame()).Times(0);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_TRUE(MotionBlocks::m_timeMap.find(&vm) == MotionBlocks::m_timeMap.cend());
+    ASSERT_TRUE(MotionBlocks::m_glideMap.find(&vm) == MotionBlocks::m_glideMap.cend());
+    ASSERT_TRUE(vm.atEnd());
+    ASSERT_EQ(std::round(sprite.x() * 100) / 100, 95.2);
+    ASSERT_EQ(std::round(sprite.y() * 100) / 100, -175.9);
+
+    MotionBlocks::clock = Clock::instance().get();
+}

From e7890217df29e85ce50c9cbef9707b3fe73d0e08 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 24 Sep 2023 10:39:16 +0200
Subject: [PATCH 28/49] Implement motion_glideto block

---
 src/blocks/motionblocks.cpp        | 102 +++++++++++++-
 src/blocks/motionblocks.h          |   5 +
 test/blocks/motion_blocks_test.cpp | 208 ++++++++++++++++++++++++++++-
 3 files changed, 312 insertions(+), 3 deletions(-)

diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp
index ff7e7e91..aae1204d 100644
--- a/src/blocks/motionblocks.cpp
+++ b/src/blocks/motionblocks.cpp
@@ -32,6 +32,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);
 
     // Inputs
     engine->addInput(this, "STEPS", STEPS);
@@ -130,6 +131,33 @@ void MotionBlocks::compileGlideSecsToXY(Compiler *compiler)
     compiler->addFunctionCall(&glideSecsTo);
 }
 
+void MotionBlocks::compileGlideTo(Compiler *compiler)
+{
+    compiler->addInput(SECS);
+
+    Input *input = compiler->input(TO);
+
+    if (input->type() != Input::Type::ObscuredShadow) {
+        assert(input->pointsToDropdownMenu());
+        std::string value = input->selectedMenuItem();
+
+        if (value == "_mouse_")
+            compiler->addFunctionCall(&startGlideToMousePointer);
+        else if (value == "_random_")
+            compiler->addFunctionCall(&startGlideToRandomPosition);
+        else {
+            int index = compiler->engine()->findTarget(value);
+            compiler->addConstValue(index);
+            compiler->addFunctionCall(&startGlideToByIndex);
+        }
+    } else {
+        compiler->addInput(input);
+        compiler->addFunctionCall(&startGlideTo);
+    }
+
+    compiler->addFunctionCall(&glideSecsTo);
+}
+
 unsigned int MotionBlocks::moveSteps(VirtualMachine *vm)
 {
     Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
@@ -265,12 +293,13 @@ unsigned int MotionBlocks::goToXY(VirtualMachine *vm)
 
 unsigned int MotionBlocks::goTo(VirtualMachine *vm)
 {
-    std::string value = vm->getInput(0, 1)->toString();
     Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
 
     if (!sprite)
         return 1;
 
+    std::string value = vm->getInput(0, 1)->toString();
+
     if (value == "_mouse_") {
         sprite->setX(vm->engine()->mouseX());
         sprite->setY(vm->engine()->mouseY());
@@ -419,3 +448,74 @@ unsigned int MotionBlocks::glideSecsTo(VirtualMachine *vm)
 
     return 0;
 }
+
+unsigned int MotionBlocks::startGlideTo(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (!sprite)
+        return 1;
+
+    std::string value = vm->getInput(1, 2)->toString();
+
+    if (value == "_mouse_")
+        startGlidingToPos(vm, vm->engine()->mouseX(), vm->engine()->mouseY(), vm->getInput(0, 2)->toDouble());
+    else if (value == "_random_") {
+        // TODO: Read stage size from engine (#224)
+        static const unsigned int stageWidth = 480;
+        static const unsigned int stageHeight = 360;
+
+        if (!rng)
+            rng = RandomGenerator::instance().get();
+
+        startGlidingToPos(vm, rng->randint(-static_cast<int>(stageWidth / 2), stageWidth / 2), rng->randint(-static_cast<int>(stageHeight / 2), stageHeight / 2), vm->getInput(0, 2)->toDouble());
+    } else {
+        Target *target = vm->engine()->targetAt(vm->engine()->findTarget(value));
+        Sprite *targetSprite = dynamic_cast<Sprite *>(target);
+
+        if (targetSprite)
+            startGlidingToPos(vm, targetSprite->x(), targetSprite->y(), vm->getInput(0, 2)->toDouble());
+    }
+
+    return 2;
+}
+
+unsigned int MotionBlocks::startGlideToByIndex(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+    Target *target = vm->engine()->targetAt(vm->getInput(1, 2)->toInt());
+    Sprite *targetSprite = dynamic_cast<Sprite *>(target);
+
+    if (sprite && targetSprite)
+        startGlidingToPos(vm, targetSprite->x(), targetSprite->y(), vm->getInput(0, 2)->toDouble());
+
+    return 2;
+}
+
+unsigned int MotionBlocks::startGlideToMousePointer(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (sprite)
+        startGlidingToPos(vm, vm->engine()->mouseX(), vm->engine()->mouseY(), vm->getInput(0, 1)->toDouble());
+
+    return 1;
+}
+
+unsigned int MotionBlocks::startGlideToRandomPosition(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (sprite) {
+        // TODO: Read stage size from engine (#224)
+        static const unsigned int stageWidth = 480;
+        static const unsigned int stageHeight = 360;
+
+        if (!rng)
+            rng = RandomGenerator::instance().get();
+
+        startGlidingToPos(vm, rng->randint(-static_cast<int>(stageWidth / 2), stageWidth / 2), rng->randint(-static_cast<int>(stageHeight / 2), stageHeight / 2), vm->getInput(0, 1)->toDouble());
+    }
+
+    return 1;
+}
diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h
index 694ca951..0aef29bf 100644
--- a/src/blocks/motionblocks.h
+++ b/src/blocks/motionblocks.h
@@ -50,6 +50,7 @@ class MotionBlocks : public IBlockSection
         static void compileGoToXY(Compiler *compiler);
         static void compileGoTo(Compiler *compiler);
         static void compileGlideSecsToXY(Compiler *compiler);
+        static void compileGlideTo(Compiler *compiler);
 
         static unsigned int moveSteps(VirtualMachine *vm);
         static unsigned int turnRight(VirtualMachine *vm);
@@ -74,6 +75,10 @@ class MotionBlocks : public IBlockSection
 
         static unsigned int startGlideSecsTo(VirtualMachine *vm);
         static unsigned int glideSecsTo(VirtualMachine *vm);
+        static unsigned int startGlideTo(VirtualMachine *vm);
+        static unsigned int startGlideToByIndex(VirtualMachine *vm);
+        static unsigned int startGlideToMousePointer(VirtualMachine *vm);
+        static unsigned int startGlideToRandomPosition(VirtualMachine *vm);
 
         static IRandomGenerator *rng;
         static IClock *clock;
diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
index 27b1d4fe..1d5e91fd 100644
--- a/test/blocks/motion_blocks_test.cpp
+++ b/test/blocks/motion_blocks_test.cpp
@@ -105,6 +105,7 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_gotoxy", &MotionBlocks::compileGoToXY));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_goto", &MotionBlocks::compileGoTo));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_glidesecstoxy", &MotionBlocks::compileGlideSecsToXY));
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_glideto", &MotionBlocks::compileGlideTo));
 
     // Inputs
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS));
@@ -660,8 +661,8 @@ TEST_F(MotionBlocksTest, GlideSecsToXYImpl)
     static BlockFunc functions[] = { &MotionBlocks::startGlideSecsTo, &MotionBlocks::glideSecsTo };
     static Value constValues[] = { 2.5, 95.2, -175.9 };
 
-    double startX = 100.32;
-    double startY = -50.12;
+    static const double startX = 100.32;
+    static const double startY = -50.12;
 
     Sprite sprite;
     sprite.setX(startX);
@@ -716,3 +717,206 @@ TEST_F(MotionBlocksTest, GlideSecsToXYImpl)
 
     MotionBlocks::clock = Clock::instance().get();
 }
+
+TEST_F(MotionBlocksTest, GlideTo)
+{
+    Compiler compiler(&m_engineMock);
+
+    // glide (3.25) secs to (mouse-pointer)
+    auto block1 = std::make_shared<Block>("a", "motion_glideto");
+    addValueInput(block1, "SECS", MotionBlocks::SECS, 3.25);
+    addDropdownInput(block1, "TO", MotionBlocks::TO, "_mouse_");
+
+    // glide (2.5) secs to (random position)
+    auto block2 = std::make_shared<Block>("b", "motion_glideto");
+    addValueInput(block2, "SECS", MotionBlocks::SECS, 2.5);
+    addDropdownInput(block2, "TO", MotionBlocks::TO, "_random_");
+
+    // glide (5.01) secs to (Sprite2)
+    auto block3 = std::make_shared<Block>("c", "motion_glideto");
+    addValueInput(block3, "SECS", MotionBlocks::SECS, 3.25);
+    addDropdownInput(block3, "TO", MotionBlocks::TO, "Sprite2");
+
+    // glide (6.5) secs to (join "" "")
+    auto joinBlock = std::make_shared<Block>("e", "operator_join");
+    joinBlock->setCompileFunction(&OperatorBlocks::compileJoin);
+    auto block4 = std::make_shared<Block>("d", "motion_glideto");
+    addValueInput(block4, "SECS", MotionBlocks::SECS, 6.5);
+    addDropdownInput(block4, "TO", MotionBlocks::TO, "", joinBlock);
+
+    compiler.init();
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::startGlideToMousePointer)).WillOnce(Return(0));
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::glideSecsTo)).WillOnce(Return(4));
+    compiler.setBlock(block1);
+    MotionBlocks::compileGlideTo(&compiler);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::startGlideToRandomPosition)).WillOnce(Return(1));
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::glideSecsTo)).WillOnce(Return(4));
+    compiler.setBlock(block2);
+    MotionBlocks::compileGlideTo(&compiler);
+
+    EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(5));
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::startGlideToByIndex)).WillOnce(Return(2));
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::glideSecsTo)).WillOnce(Return(4));
+    compiler.setBlock(block3);
+    MotionBlocks::compileGlideTo(&compiler);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::startGlideTo)).WillOnce(Return(3));
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::glideSecsTo)).WillOnce(Return(4));
+    compiler.setBlock(block4);
+    MotionBlocks::compileGlideTo(&compiler);
+
+    compiler.end();
+
+    ASSERT_EQ(
+        compiler.bytecode(),
+        std::vector<unsigned int>(
+            { vm::OP_START,
+              vm::OP_CONST,
+              0,
+              vm::OP_EXEC,
+              0,
+              vm::OP_EXEC,
+              4,
+              vm::OP_CONST,
+              1,
+              vm::OP_EXEC,
+              1,
+              vm::OP_EXEC,
+              4,
+              vm::OP_CONST,
+              2,
+              vm::OP_CONST,
+              3,
+              vm::OP_EXEC,
+              2,
+              vm::OP_EXEC,
+              4,
+              vm::OP_CONST,
+              4,
+              vm::OP_NULL,
+              vm::OP_NULL,
+              vm::OP_STR_CONCAT,
+              vm::OP_EXEC,
+              3,
+              vm::OP_EXEC,
+              4,
+              vm::OP_HALT }));
+    ASSERT_EQ(compiler.constValues(), std::vector<Value>({ 3.25, 2.5, 3.25, 5, 6.5 }));
+}
+
+TEST_F(MotionBlocksTest, GlideToImpl)
+{
+    static unsigned int bytecode1[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_EXEC, 4, vm::OP_HALT };
+    static unsigned int bytecode2[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_CONST, 2, vm::OP_EXEC, 1, vm::OP_EXEC, 4, vm::OP_HALT };
+    static unsigned int bytecode3[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 2, vm::OP_EXEC, 4, vm::OP_HALT };
+    static unsigned int bytecode4[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 3, vm::OP_EXEC, 4, vm::OP_HALT };
+    static unsigned int *scripts[] = { bytecode1, bytecode2, bytecode3, bytecode4 };
+    static BlockFunc functions
+        [] = { &MotionBlocks::startGlideTo, &MotionBlocks::startGlideToByIndex, &MotionBlocks::startGlideToMousePointer, &MotionBlocks::startGlideToRandomPosition, &MotionBlocks::glideSecsTo };
+    static Value constValues[] = { 2.5, "Sprite2", 3 };
+
+    static const double startX = 100.32;
+    static const double startY = -50.12;
+    static const double endX = 95.2;
+    static const double endY = -175.9;
+
+    Sprite sprite;
+    Sprite anotherSprite;
+    anotherSprite.setX(endX);
+    anotherSprite.setY(endY);
+
+    VirtualMachine vm(&sprite, &m_engineMock, nullptr);
+    vm.setFunctions(functions);
+    vm.setConstValues(constValues);
+
+    ClockMock clock;
+    RandomGeneratorMock rng;
+    MotionBlocks::clock = &clock;
+    MotionBlocks::rng = &rng;
+    int i = 0;
+
+    for (auto script : scripts) {
+        sprite.setX(startX);
+        sprite.setY(startY);
+
+        switch (i) {
+            case 0:
+                EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(3));
+                EXPECT_CALL(m_engineMock, targetAt(3)).WillOnce(Return(&anotherSprite));
+                break;
+
+            case 1:
+                EXPECT_CALL(m_engineMock, targetAt(3)).WillOnce(Return(&anotherSprite));
+                break;
+
+            case 2:
+                EXPECT_CALL(m_engineMock, mouseX()).WillOnce(Return(endX));
+                EXPECT_CALL(m_engineMock, mouseY()).WillOnce(Return(endY));
+                break;
+            case 3:
+                EXPECT_CALL(rng, randint(-240, 240)).WillOnce(Return(std::round(endX)));
+                EXPECT_CALL(rng, randint(-180, 180)).WillOnce(Return(std::round(endY)));
+            default:
+                break;
+        }
+
+        vm.setBytecode(script);
+        std::chrono::steady_clock::time_point startTime(std::chrono::milliseconds(1000));
+        EXPECT_CALL(clock, currentSteadyTime()).Times(2).WillRepeatedly(Return(startTime));
+        EXPECT_CALL(m_engineMock, lockFrame()).Times(1);
+        EXPECT_CALL(m_engineMock, breakFrame()).Times(1);
+        vm.run();
+
+        ASSERT_EQ(vm.registerCount(), 0);
+        ASSERT_TRUE(MotionBlocks::m_timeMap.find(&vm) != MotionBlocks::m_timeMap.cend());
+        ASSERT_TRUE(MotionBlocks::m_glideMap.find(&vm) != MotionBlocks::m_glideMap.cend());
+        ASSERT_FALSE(vm.atEnd());
+        ASSERT_EQ(sprite.x(), startX);
+        ASSERT_EQ(sprite.y(), startY);
+
+        std::chrono::steady_clock::time_point time1(std::chrono::milliseconds(3456));
+        EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time1));
+        EXPECT_CALL(m_engineMock, lockFrame()).Times(1);
+        EXPECT_CALL(m_engineMock, breakFrame()).Times(1);
+        vm.run();
+
+        ASSERT_EQ(vm.registerCount(), 0);
+        ASSERT_TRUE(MotionBlocks::m_timeMap.find(&vm) != MotionBlocks::m_timeMap.cend());
+        ASSERT_TRUE(MotionBlocks::m_glideMap.find(&vm) != MotionBlocks::m_glideMap.cend());
+        ASSERT_FALSE(vm.atEnd());
+
+        if (i == 3) {
+            ASSERT_EQ(std::round(sprite.x() * 100) / 100, 95.09);
+            ASSERT_EQ(std::round(sprite.y() * 100) / 100, -173.78);
+        } else {
+            ASSERT_EQ(std::round(sprite.x() * 100) / 100, 95.29);
+            ASSERT_EQ(std::round(sprite.y() * 100) / 100, -173.69);
+        }
+
+        std::chrono::steady_clock::time_point time2(std::chrono::milliseconds(3500));
+        EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time2));
+        EXPECT_CALL(m_engineMock, lockFrame()).Times(0);
+        EXPECT_CALL(m_engineMock, breakFrame()).Times(0);
+        vm.run();
+
+        ASSERT_EQ(vm.registerCount(), 0);
+        ASSERT_TRUE(MotionBlocks::m_timeMap.find(&vm) == MotionBlocks::m_timeMap.cend());
+        ASSERT_TRUE(MotionBlocks::m_glideMap.find(&vm) == MotionBlocks::m_glideMap.cend());
+        ASSERT_TRUE(vm.atEnd());
+
+        if (i == 3) {
+            ASSERT_EQ(sprite.x(), std::round(endX));
+            ASSERT_EQ(sprite.y(), std::round(endY));
+        } else {
+            ASSERT_EQ(std::round(sprite.x() * 100) / 100, endX);
+            ASSERT_EQ(std::round(sprite.y() * 100) / 100, endY);
+        }
+
+        i++;
+    }
+
+    MotionBlocks::clock = Clock::instance().get();
+    MotionBlocks::rng = RandomGenerator::instance().get();
+}

From 1c5425ec00d65b13d817e5b308cbe276a69d41ad Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 24 Sep 2023 11:04:04 +0200
Subject: [PATCH 29/49] Implement motion_changexby block

---
 src/blocks/motionblocks.cpp        | 18 +++++++++++++
 src/blocks/motionblocks.h          |  6 ++++-
 test/blocks/motion_blocks_test.cpp | 41 ++++++++++++++++++++++++++++++
 3 files changed, 64 insertions(+), 1 deletion(-)

diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp
index aae1204d..d4cd0c06 100644
--- a/src/blocks/motionblocks.cpp
+++ b/src/blocks/motionblocks.cpp
@@ -33,6 +33,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);
 
     // Inputs
     engine->addInput(this, "STEPS", STEPS);
@@ -43,6 +44,7 @@ void MotionBlocks::registerBlocks(IEngine *engine)
     engine->addInput(this, "Y", Y);
     engine->addInput(this, "TO", TO);
     engine->addInput(this, "SECS", SECS);
+    engine->addInput(this, "DX", DX);
 }
 
 void MotionBlocks::compileMoveSteps(Compiler *compiler)
@@ -158,6 +160,12 @@ void MotionBlocks::compileGlideTo(Compiler *compiler)
     compiler->addFunctionCall(&glideSecsTo);
 }
 
+void MotionBlocks::compileChangeXBy(Compiler *compiler)
+{
+    compiler->addInput(DX);
+    compiler->addFunctionCall(&changeXBy);
+}
+
 unsigned int MotionBlocks::moveSteps(VirtualMachine *vm)
 {
     Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
@@ -519,3 +527,13 @@ unsigned int MotionBlocks::startGlideToRandomPosition(VirtualMachine *vm)
 
     return 1;
 }
+
+unsigned int MotionBlocks::changeXBy(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (sprite)
+        sprite->setX(sprite->x() + vm->getInput(0, 1)->toDouble());
+
+    return 1;
+}
diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h
index 0aef29bf..e6604996 100644
--- a/src/blocks/motionblocks.h
+++ b/src/blocks/motionblocks.h
@@ -27,7 +27,8 @@ class MotionBlocks : public IBlockSection
             X,
             Y,
             TO,
-            SECS
+            SECS,
+            DX
         };
 
         enum Fields
@@ -51,6 +52,7 @@ class MotionBlocks : public IBlockSection
         static void compileGoTo(Compiler *compiler);
         static void compileGlideSecsToXY(Compiler *compiler);
         static void compileGlideTo(Compiler *compiler);
+        static void compileChangeXBy(Compiler *compiler);
 
         static unsigned int moveSteps(VirtualMachine *vm);
         static unsigned int turnRight(VirtualMachine *vm);
@@ -80,6 +82,8 @@ class MotionBlocks : public IBlockSection
         static unsigned int startGlideToMousePointer(VirtualMachine *vm);
         static unsigned int startGlideToRandomPosition(VirtualMachine *vm);
 
+        static unsigned int changeXBy(VirtualMachine *vm);
+
         static IRandomGenerator *rng;
         static IClock *clock;
 
diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
index 1d5e91fd..e4662d15 100644
--- a/test/blocks/motion_blocks_test.cpp
+++ b/test/blocks/motion_blocks_test.cpp
@@ -106,6 +106,7 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_goto", &MotionBlocks::compileGoTo));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_glidesecstoxy", &MotionBlocks::compileGlideSecsToXY));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_glideto", &MotionBlocks::compileGlideTo));
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_changexby", &MotionBlocks::compileChangeXBy));
 
     // Inputs
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS));
@@ -116,6 +117,7 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "Y", MotionBlocks::Y));
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "TO", MotionBlocks::TO));
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "SECS", MotionBlocks::SECS));
+    EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DX", MotionBlocks::DX));
 
     m_section->registerBlocks(&m_engineMock);
 }
@@ -920,3 +922,42 @@ TEST_F(MotionBlocksTest, GlideToImpl)
     MotionBlocks::clock = Clock::instance().get();
     MotionBlocks::rng = RandomGenerator::instance().get();
 }
+
+TEST_F(MotionBlocksTest, ChangeXBy)
+{
+    Compiler compiler(&m_engineMock);
+
+    // change x by (56.54)
+    auto block = std::make_shared<Block>("a", "motion_changexby");
+    addValueInput(block, "DX", MotionBlocks::DX, 56.54);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::changeXBy)).WillOnce(Return(0));
+
+    compiler.init();
+    compiler.setBlock(block);
+    MotionBlocks::compileChangeXBy(&compiler);
+    compiler.end();
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT }));
+    ASSERT_EQ(compiler.constValues().size(), 1);
+    ASSERT_EQ(compiler.constValues()[0].toDouble(), 56.54);
+}
+
+TEST_F(MotionBlocksTest, ChangeXByImpl)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &MotionBlocks::changeXBy };
+    static Value constValues[] = { 56.54 };
+
+    Sprite sprite;
+    sprite.setX(-23.4);
+
+    VirtualMachine vm(&sprite, nullptr, nullptr);
+    vm.setBytecode(bytecode);
+    vm.setFunctions(functions);
+    vm.setConstValues(constValues);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(sprite.x(), 33.14);
+}

From 4e03238144476af112279ab28d4a4df10076237f Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 24 Sep 2023 11:29:54 +0200
Subject: [PATCH 30/49] fix #188: Join digits without spaces in
 List::toString()

---
 src/scratch/list.cpp               | 32 +++++++++++++++++++++++++-----
 test/scratch_classes/list_test.cpp | 18 +++++++++++++++++
 2 files changed, 45 insertions(+), 5 deletions(-)

diff --git a/src/scratch/list.cpp b/src/scratch/list.cpp
index 1f2109f1..db958547 100644
--- a/src/scratch/list.cpp
+++ b/src/scratch/list.cpp
@@ -43,14 +43,36 @@ bool List::contains(const Value &value) const
     return (indexOf(value) != -1);
 }
 
-/*! Joins the list items with spaces. */
+/*! Joins the list items with spaces or without any separator if there are only digits. */
 std::string List::toString() const
 {
     std::string ret;
-    for (int i = 0; i < size(); i++) {
-        ret.append(at(i).toString());
-        if (i + 1 < size())
-            ret.push_back(' ');
+    bool digits = true;
+
+    for (const auto &item : *this) {
+        if (item.type() == Value::Type::Integer) {
+            long num = item.toLong();
+
+            if (num < 0 || num >= 10) {
+                digits = false;
+                break;
+            }
+        } else {
+            digits = false;
+            break;
+        }
     }
+
+    if (digits) {
+        for (const auto &item : *this)
+            ret.append(item.toString());
+    } else {
+        for (int i = 0; i < size(); i++) {
+            ret.append(at(i).toString());
+            if (i + 1 < size())
+                ret.push_back(' ');
+        }
+    }
+
     return ret;
 }
diff --git a/test/scratch_classes/list_test.cpp b/test/scratch_classes/list_test.cpp
index c42240c9..4464d666 100644
--- a/test/scratch_classes/list_test.cpp
+++ b/test/scratch_classes/list_test.cpp
@@ -151,4 +151,22 @@ TEST(ListTest, ToString)
     list.push_back("áä");
     list.push_back("ľ š");
     ASSERT_EQ(list.toString(), "áä ľ š");
+
+    list.clear();
+    list.push_back(-2);
+    list.push_back(5);
+    list.push_back(8);
+    ASSERT_EQ(list.toString(), "-2 5 8");
+
+    list.clear();
+    list.push_back(2);
+    list.push_back(10);
+    list.push_back(8);
+    ASSERT_EQ(list.toString(), "2 10 8");
+
+    list.clear();
+    list.push_back(0);
+    list.push_back(9);
+    list.push_back(8);
+    ASSERT_EQ(list.toString(), "098");
 }

From 38f7b17d4766a069b5553f54d91daeef1e147a75 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 24 Sep 2023 11:32:43 +0200
Subject: [PATCH 31/49] Update engine test after fixing #188

---
 test/engine/engine_test.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp
index b9653b18..cd97cd4a 100644
--- a/test/engine/engine_test.cpp
+++ b/test/engine/engine_test.cpp
@@ -422,7 +422,7 @@ TEST(EngineTest, Clones)
         if (i < 10)
             ASSERT_EQ((*list)[i].toInt(), 1);
         else
-            ASSERT_EQ((*list)[i].toString(), "1 2"); // TODO: Change this to "12" after #188 is fixed
+            ASSERT_EQ((*list)[i].toString(), "12");
     }
 }
 

From 2760f1fd7836f82d58557d4d6e32ee8c526ff1eb Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 24 Sep 2023 12:39:24 +0200
Subject: [PATCH 32/49] fix: #176: Use stringstream to convert double to string

---
 include/scratchcpp/value.h          | 20 ++++++++++++++++----
 test/scratch_classes/value_test.cpp | 24 ++++++++++++++++++++++++
 2 files changed, 40 insertions(+), 4 deletions(-)

diff --git a/include/scratchcpp/value.h b/include/scratchcpp/value.h
index d72b2f9e..ff1cbced 100644
--- a/include/scratchcpp/value.h
+++ b/include/scratchcpp/value.h
@@ -257,11 +257,23 @@ class LIBSCRATCHCPP_EXPORT Value
                     case Type::Integer:
                         return std::to_string(m_intValue);
                     case Type::Double: {
-                        std::string s = std::to_string(m_doubleValue);
-                        s.erase(s.find_last_not_of('0') + 1, std::string::npos);
-                        if (s.back() == '.') {
-                            s.pop_back();
+                        std::stringstream stream;
+                        stream << m_doubleValue;
+                        std::string s = stream.str();
+                        std::size_t index;
+
+                        for (int i = 0; i < 2; i++) {
+                            if (i == 0)
+                                index = s.find("e+");
+                            else
+                                index = s.find("e-");
+
+                            if (index != std::string::npos) {
+                                while ((s.size() >= index + 3) && (s[index + 2] == '0'))
+                                    s.erase(index + 2, 1);
+                            }
                         }
+
                         return s;
                     }
                     case Type::Bool:
diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp
index 54bfa2d6..3657d799 100644
--- a/test/scratch_classes/value_test.cpp
+++ b/test/scratch_classes/value_test.cpp
@@ -824,6 +824,16 @@ TEST(ValueTest, ToDouble)
     v = "-255.625";
     ASSERT_EQ(v.toDouble(), -255.625);
 
+    v = "9432.4e-12";
+    ASSERT_EQ(v.toDouble(), 9.4324e-9);
+    v = "-9432.4e-12";
+    ASSERT_EQ(v.toDouble(), -9.4324e-9);
+
+    v = "9432.4e+6";
+    ASSERT_EQ(v.toDouble(), 9.4324e+9);
+    v = "-9432.4e+6";
+    ASSERT_EQ(v.toDouble(), -9.4324e+9);
+
     v = "false";
     ASSERT_EQ(v.toDouble(), 0.0);
     v = "true";
@@ -953,6 +963,20 @@ TEST(ValueTest, ToString)
     ASSERT_EQ(v.toString(), "-255.625");
     ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
 
+    v = "9432.4e-12";
+    ASSERT_EQ(v.toString(), "9.4324e-9");
+    ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
+    v = "-9432.4e-12";
+    ASSERT_EQ(v.toString(), "-9.4324e-9");
+    ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
+
+    v = "9432.4e+6";
+    ASSERT_EQ(v.toString(), "9.4324e+9");
+    ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
+    v = "-9432.4e+6";
+    ASSERT_EQ(v.toString(), "-9.4324e+9");
+    ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
+
     v = "false";
     ASSERT_EQ(v.toString(), "false");
     ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());

From 2cb41f42f936b53411ecb6acbd7f7b1fdf31cd25 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 25 Sep 2023 09:04:20 +0200
Subject: [PATCH 33/49] Implement motion_setx block

---
 src/blocks/motionblocks.cpp        | 17 +++++++++++++
 src/blocks/motionblocks.h          |  2 ++
 test/blocks/motion_blocks_test.cpp | 40 ++++++++++++++++++++++++++++++
 3 files changed, 59 insertions(+)

diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp
index d4cd0c06..40443dd9 100644
--- a/src/blocks/motionblocks.cpp
+++ b/src/blocks/motionblocks.cpp
@@ -34,6 +34,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);
 
     // Inputs
     engine->addInput(this, "STEPS", STEPS);
@@ -166,6 +167,12 @@ void MotionBlocks::compileChangeXBy(Compiler *compiler)
     compiler->addFunctionCall(&changeXBy);
 }
 
+void MotionBlocks::compileSetX(Compiler *compiler)
+{
+    compiler->addInput(X);
+    compiler->addFunctionCall(&setX);
+}
+
 unsigned int MotionBlocks::moveSteps(VirtualMachine *vm)
 {
     Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
@@ -537,3 +544,13 @@ unsigned int MotionBlocks::changeXBy(VirtualMachine *vm)
 
     return 1;
 }
+
+unsigned int MotionBlocks::setX(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (sprite)
+        sprite->setX(vm->getInput(0, 1)->toDouble());
+
+    return 1;
+}
diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h
index e6604996..1cc8bd60 100644
--- a/src/blocks/motionblocks.h
+++ b/src/blocks/motionblocks.h
@@ -53,6 +53,7 @@ class MotionBlocks : public IBlockSection
         static void compileGlideSecsToXY(Compiler *compiler);
         static void compileGlideTo(Compiler *compiler);
         static void compileChangeXBy(Compiler *compiler);
+        static void compileSetX(Compiler *compiler);
 
         static unsigned int moveSteps(VirtualMachine *vm);
         static unsigned int turnRight(VirtualMachine *vm);
@@ -83,6 +84,7 @@ class MotionBlocks : public IBlockSection
         static unsigned int startGlideToRandomPosition(VirtualMachine *vm);
 
         static unsigned int changeXBy(VirtualMachine *vm);
+        static unsigned int setX(VirtualMachine *vm);
 
         static IRandomGenerator *rng;
         static IClock *clock;
diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
index e4662d15..265a0707 100644
--- a/test/blocks/motion_blocks_test.cpp
+++ b/test/blocks/motion_blocks_test.cpp
@@ -107,6 +107,7 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_glidesecstoxy", &MotionBlocks::compileGlideSecsToXY));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_glideto", &MotionBlocks::compileGlideTo));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_changexby", &MotionBlocks::compileChangeXBy));
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_setx", &MotionBlocks::compileSetX));
 
     // Inputs
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS));
@@ -961,3 +962,42 @@ TEST_F(MotionBlocksTest, ChangeXByImpl)
     ASSERT_EQ(vm.registerCount(), 0);
     ASSERT_EQ(sprite.x(), 33.14);
 }
+
+TEST_F(MotionBlocksTest, SetX)
+{
+    Compiler compiler(&m_engineMock);
+
+    // set x to (-63.4)
+    auto block = std::make_shared<Block>("a", "motion_setx");
+    addValueInput(block, "X", MotionBlocks::X, -63.4);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::setX)).WillOnce(Return(0));
+
+    compiler.init();
+    compiler.setBlock(block);
+    MotionBlocks::compileSetX(&compiler);
+    compiler.end();
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT }));
+    ASSERT_EQ(compiler.constValues().size(), 1);
+    ASSERT_EQ(compiler.constValues()[0].toDouble(), -63.4);
+}
+
+TEST_F(MotionBlocksTest, SetXImpl)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &MotionBlocks::setX };
+    static Value constValues[] = { -63.4 };
+
+    Sprite sprite;
+    sprite.setX(239.98);
+
+    VirtualMachine vm(&sprite, nullptr, nullptr);
+    vm.setBytecode(bytecode);
+    vm.setFunctions(functions);
+    vm.setConstValues(constValues);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(sprite.x(), -63.4);
+}

From 7ccafe3f3041bcc77ea22b3b5e922501c95d0b9e Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 25 Sep 2023 09:24:51 +0200
Subject: [PATCH 34/49] Implement motion_changeyby block

---
 src/blocks/motionblocks.cpp        | 18 +++++++++++++
 src/blocks/motionblocks.h          |  5 +++-
 test/blocks/motion_blocks_test.cpp | 41 ++++++++++++++++++++++++++++++
 3 files changed, 63 insertions(+), 1 deletion(-)

diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp
index 40443dd9..8492c014 100644
--- a/src/blocks/motionblocks.cpp
+++ b/src/blocks/motionblocks.cpp
@@ -35,6 +35,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);
 
     // Inputs
     engine->addInput(this, "STEPS", STEPS);
@@ -46,6 +47,7 @@ void MotionBlocks::registerBlocks(IEngine *engine)
     engine->addInput(this, "TO", TO);
     engine->addInput(this, "SECS", SECS);
     engine->addInput(this, "DX", DX);
+    engine->addInput(this, "DY", DY);
 }
 
 void MotionBlocks::compileMoveSteps(Compiler *compiler)
@@ -173,6 +175,12 @@ void MotionBlocks::compileSetX(Compiler *compiler)
     compiler->addFunctionCall(&setX);
 }
 
+void MotionBlocks::compileChangeYBy(Compiler *compiler)
+{
+    compiler->addInput(DY);
+    compiler->addFunctionCall(&changeYBy);
+}
+
 unsigned int MotionBlocks::moveSteps(VirtualMachine *vm)
 {
     Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
@@ -554,3 +562,13 @@ unsigned int MotionBlocks::setX(VirtualMachine *vm)
 
     return 1;
 }
+
+unsigned int MotionBlocks::changeYBy(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (sprite)
+        sprite->setY(sprite->y() + vm->getInput(0, 1)->toDouble());
+
+    return 1;
+}
diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h
index 1cc8bd60..c5a07bb0 100644
--- a/src/blocks/motionblocks.h
+++ b/src/blocks/motionblocks.h
@@ -28,7 +28,8 @@ class MotionBlocks : public IBlockSection
             Y,
             TO,
             SECS,
-            DX
+            DX,
+            DY
         };
 
         enum Fields
@@ -54,6 +55,7 @@ class MotionBlocks : public IBlockSection
         static void compileGlideTo(Compiler *compiler);
         static void compileChangeXBy(Compiler *compiler);
         static void compileSetX(Compiler *compiler);
+        static void compileChangeYBy(Compiler *compiler);
 
         static unsigned int moveSteps(VirtualMachine *vm);
         static unsigned int turnRight(VirtualMachine *vm);
@@ -85,6 +87,7 @@ class MotionBlocks : public IBlockSection
 
         static unsigned int changeXBy(VirtualMachine *vm);
         static unsigned int setX(VirtualMachine *vm);
+        static unsigned int changeYBy(VirtualMachine *vm);
 
         static IRandomGenerator *rng;
         static IClock *clock;
diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
index 265a0707..0f98f4ff 100644
--- a/test/blocks/motion_blocks_test.cpp
+++ b/test/blocks/motion_blocks_test.cpp
@@ -108,6 +108,7 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_glideto", &MotionBlocks::compileGlideTo));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_changexby", &MotionBlocks::compileChangeXBy));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_setx", &MotionBlocks::compileSetX));
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_changeyby", &MotionBlocks::compileChangeYBy));
 
     // Inputs
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS));
@@ -119,6 +120,7 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "TO", MotionBlocks::TO));
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "SECS", MotionBlocks::SECS));
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DX", MotionBlocks::DX));
+    EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DY", MotionBlocks::DY));
 
     m_section->registerBlocks(&m_engineMock);
 }
@@ -1001,3 +1003,42 @@ TEST_F(MotionBlocksTest, SetXImpl)
     ASSERT_EQ(vm.registerCount(), 0);
     ASSERT_EQ(sprite.x(), -63.4);
 }
+
+TEST_F(MotionBlocksTest, ChangeYBy)
+{
+    Compiler compiler(&m_engineMock);
+
+    // change y by (135.2)
+    auto block = std::make_shared<Block>("a", "motion_changeyby");
+    addValueInput(block, "DY", MotionBlocks::DY, 135.2);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::changeYBy)).WillOnce(Return(0));
+
+    compiler.init();
+    compiler.setBlock(block);
+    MotionBlocks::compileChangeYBy(&compiler);
+    compiler.end();
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT }));
+    ASSERT_EQ(compiler.constValues().size(), 1);
+    ASSERT_EQ(compiler.constValues()[0].toDouble(), 135.2);
+}
+
+TEST_F(MotionBlocksTest, ChangeYByImpl)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &MotionBlocks::changeYBy };
+    static Value constValues[] = { 135.2 };
+
+    Sprite sprite;
+    sprite.setY(200.657);
+
+    VirtualMachine vm(&sprite, nullptr, nullptr);
+    vm.setBytecode(bytecode);
+    vm.setFunctions(functions);
+    vm.setConstValues(constValues);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(std::round(sprite.y() * 1000) / 1000, 335.857);
+}

From 0ddbf97a36661dd98c5f633c275040094063b3cc Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 25 Sep 2023 09:39:38 +0200
Subject: [PATCH 35/49] fix #196: Fix comparison of special values

---
 include/scratchcpp/value.h          | 5 +----
 test/scratch_classes/value_test.cpp | 5 ++---
 2 files changed, 3 insertions(+), 7 deletions(-)

diff --git a/include/scratchcpp/value.h b/include/scratchcpp/value.h
index ff1cbced..45f3a24a 100644
--- a/include/scratchcpp/value.h
+++ b/include/scratchcpp/value.h
@@ -525,10 +525,7 @@ class LIBSCRATCHCPP_EXPORT Value
                         return stringsEqual(v1.toUtf16(), v2.toUtf16());
                     default:
                         if ((static_cast<int>(v1.m_type) < 0) && (static_cast<int>(v2.m_type) < 0)) {
-                            if (v1.m_type == Type::NaN || v2.m_type == Type::NaN)
-                                return false;
-                            else
-                                return ((v1.m_type == Type::Infinity && v2.m_type == Type::Infinity) || (v1.m_type == Type::NegativeInfinity && v2.m_type == Type::NegativeInfinity));
+                            return v1.m_type == v2.m_type;
                         }
                 }
             } else {
diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp
index 3657d799..38387534 100644
--- a/test/scratch_classes/value_test.cpp
+++ b/test/scratch_classes/value_test.cpp
@@ -1740,9 +1740,8 @@ TEST(ValueTest, EqualityOperators)
         ASSERT_TRUE(v3 == v4);
         ASSERT_FALSE(v3 != v4);
 
-        // TODO: Enable this after #196 is fixed
-        /*ASSERT_TRUE(v5 == v6);
-        ASSERT_FALSE(v5 != v6);*/
+        ASSERT_TRUE(v5 == v6);
+        ASSERT_FALSE(v5 != v6);
 
         ASSERT_FALSE(v1 == v3);
         ASSERT_TRUE(v1 != v3);

From 5c12d1057d8598a0ba634e52cf966e838666cf0a Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 26 Sep 2023 18:50:07 +0200
Subject: [PATCH 36/49] Implement motion_sety block

---
 src/blocks/motionblocks.cpp        | 17 +++++++++++++
 src/blocks/motionblocks.h          |  2 ++
 test/blocks/motion_blocks_test.cpp | 40 ++++++++++++++++++++++++++++++
 3 files changed, 59 insertions(+)

diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp
index 8492c014..7d05a799 100644
--- a/src/blocks/motionblocks.cpp
+++ b/src/blocks/motionblocks.cpp
@@ -36,6 +36,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);
 
     // Inputs
     engine->addInput(this, "STEPS", STEPS);
@@ -181,6 +182,12 @@ void MotionBlocks::compileChangeYBy(Compiler *compiler)
     compiler->addFunctionCall(&changeYBy);
 }
 
+void MotionBlocks::compileSetY(Compiler *compiler)
+{
+    compiler->addInput(Y);
+    compiler->addFunctionCall(&setY);
+}
+
 unsigned int MotionBlocks::moveSteps(VirtualMachine *vm)
 {
     Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
@@ -572,3 +579,13 @@ unsigned int MotionBlocks::changeYBy(VirtualMachine *vm)
 
     return 1;
 }
+
+unsigned int MotionBlocks::setY(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (sprite)
+        sprite->setY(vm->getInput(0, 1)->toDouble());
+
+    return 1;
+}
diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h
index c5a07bb0..2596f37b 100644
--- a/src/blocks/motionblocks.h
+++ b/src/blocks/motionblocks.h
@@ -56,6 +56,7 @@ class MotionBlocks : public IBlockSection
         static void compileChangeXBy(Compiler *compiler);
         static void compileSetX(Compiler *compiler);
         static void compileChangeYBy(Compiler *compiler);
+        static void compileSetY(Compiler *compiler);
 
         static unsigned int moveSteps(VirtualMachine *vm);
         static unsigned int turnRight(VirtualMachine *vm);
@@ -88,6 +89,7 @@ class MotionBlocks : public IBlockSection
         static unsigned int changeXBy(VirtualMachine *vm);
         static unsigned int setX(VirtualMachine *vm);
         static unsigned int changeYBy(VirtualMachine *vm);
+        static unsigned int setY(VirtualMachine *vm);
 
         static IRandomGenerator *rng;
         static IClock *clock;
diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
index 0f98f4ff..24bc61a3 100644
--- a/test/blocks/motion_blocks_test.cpp
+++ b/test/blocks/motion_blocks_test.cpp
@@ -109,6 +109,7 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_changexby", &MotionBlocks::compileChangeXBy));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_setx", &MotionBlocks::compileSetX));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_changeyby", &MotionBlocks::compileChangeYBy));
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_sety", &MotionBlocks::compileSetY));
 
     // Inputs
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS));
@@ -1042,3 +1043,42 @@ TEST_F(MotionBlocksTest, ChangeYByImpl)
     ASSERT_EQ(vm.registerCount(), 0);
     ASSERT_EQ(std::round(sprite.y() * 1000) / 1000, 335.857);
 }
+
+TEST_F(MotionBlocksTest, SetY)
+{
+    Compiler compiler(&m_engineMock);
+
+    // set y to (189.42)
+    auto block = std::make_shared<Block>("a", "motion_sety");
+    addValueInput(block, "Y", MotionBlocks::Y, 189.42);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::setY)).WillOnce(Return(0));
+
+    compiler.init();
+    compiler.setBlock(block);
+    MotionBlocks::compileSetY(&compiler);
+    compiler.end();
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT }));
+    ASSERT_EQ(compiler.constValues().size(), 1);
+    ASSERT_EQ(compiler.constValues()[0].toDouble(), 189.42);
+}
+
+TEST_F(MotionBlocksTest, SetYImpl)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &MotionBlocks::setY };
+    static Value constValues[] = { 189.42 };
+
+    Sprite sprite;
+    sprite.setY(-15.056);
+
+    VirtualMachine vm(&sprite, nullptr, nullptr);
+    vm.setBytecode(bytecode);
+    vm.setFunctions(functions);
+    vm.setConstValues(constValues);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(sprite.y(), 189.42);
+}

From 64fe2dab38f2d3b0245a91fd94ba17d9e2c3a64d Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 28 Sep 2023 17:03:27 +0200
Subject: [PATCH 37/49] Implement motion_setrotationstyle block

---
 src/blocks/motionblocks.cpp        | 57 ++++++++++++++++++++++++
 src/blocks/motionblocks.h          |  8 ++++
 test/blocks/motion_blocks_test.cpp | 71 ++++++++++++++++++++++++++++++
 3 files changed, 136 insertions(+)

diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp
index 7d05a799..741b8572 100644
--- a/src/blocks/motionblocks.cpp
+++ b/src/blocks/motionblocks.cpp
@@ -4,6 +4,7 @@
 #include <scratchcpp/compiler.h>
 #include <scratchcpp/sprite.h>
 #include <scratchcpp/input.h>
+#include <scratchcpp/field.h>
 
 #include "motionblocks.h"
 #include "../engine/internal/randomgenerator.h"
@@ -37,6 +38,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_setrotationstyle", &compileSetRotationStyle);
 
     // Inputs
     engine->addInput(this, "STEPS", STEPS);
@@ -49,6 +51,9 @@ void MotionBlocks::registerBlocks(IEngine *engine)
     engine->addInput(this, "SECS", SECS);
     engine->addInput(this, "DX", DX);
     engine->addInput(this, "DY", DY);
+
+    // Fields
+    engine->addField(this, "STYLE", STYLE);
 }
 
 void MotionBlocks::compileMoveSteps(Compiler *compiler)
@@ -188,6 +193,28 @@ void MotionBlocks::compileSetY(Compiler *compiler)
     compiler->addFunctionCall(&setY);
 }
 
+void MotionBlocks::compileSetRotationStyle(Compiler *compiler)
+{
+    int option = compiler->field(STYLE)->specialValueId();
+
+    switch (option) {
+        case LeftRight:
+            compiler->addFunctionCall(&setLeftRightRotationStyle);
+            break;
+
+        case DoNotRotate:
+            compiler->addFunctionCall(&setDoNotRotateRotationStyle);
+            break;
+
+        case AllAround:
+            compiler->addFunctionCall(&setAllAroundRotationStyle);
+            break;
+
+        default:
+            break;
+    }
+}
+
 unsigned int MotionBlocks::moveSteps(VirtualMachine *vm)
 {
     Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
@@ -589,3 +616,33 @@ unsigned int MotionBlocks::setY(VirtualMachine *vm)
 
     return 1;
 }
+
+unsigned int MotionBlocks::setLeftRightRotationStyle(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (sprite)
+        sprite->setRotationStyle(Sprite::RotationStyle::LeftRight);
+
+    return 0;
+}
+
+unsigned int MotionBlocks::setDoNotRotateRotationStyle(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (sprite)
+        sprite->setRotationStyle(Sprite::RotationStyle::DoNotRotate);
+
+    return 0;
+}
+
+unsigned int MotionBlocks::setAllAroundRotationStyle(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (sprite)
+        sprite->setRotationStyle(Sprite::RotationStyle::AllAround);
+
+    return 0;
+}
diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h
index 2596f37b..051b27f7 100644
--- a/src/blocks/motionblocks.h
+++ b/src/blocks/motionblocks.h
@@ -34,10 +34,14 @@ class MotionBlocks : public IBlockSection
 
         enum Fields
         {
+            STYLE
         };
 
         enum FieldValues
         {
+            LeftRight,
+            AllAround,
+            DoNotRotate
         };
 
         std::string name() const override;
@@ -57,6 +61,7 @@ class MotionBlocks : public IBlockSection
         static void compileSetX(Compiler *compiler);
         static void compileChangeYBy(Compiler *compiler);
         static void compileSetY(Compiler *compiler);
+        static void compileSetRotationStyle(Compiler *compiler);
 
         static unsigned int moveSteps(VirtualMachine *vm);
         static unsigned int turnRight(VirtualMachine *vm);
@@ -90,6 +95,9 @@ class MotionBlocks : public IBlockSection
         static unsigned int setX(VirtualMachine *vm);
         static unsigned int changeYBy(VirtualMachine *vm);
         static unsigned int setY(VirtualMachine *vm);
+        static unsigned int setLeftRightRotationStyle(VirtualMachine *vm);
+        static unsigned int setDoNotRotateRotationStyle(VirtualMachine *vm);
+        static unsigned int setAllAroundRotationStyle(VirtualMachine *vm);
 
         static IRandomGenerator *rng;
         static IClock *clock;
diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
index 24bc61a3..2ff8ed79 100644
--- a/test/blocks/motion_blocks_test.cpp
+++ b/test/blocks/motion_blocks_test.cpp
@@ -110,6 +110,7 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_setx", &MotionBlocks::compileSetX));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_changeyby", &MotionBlocks::compileChangeYBy));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_sety", &MotionBlocks::compileSetY));
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_setrotationstyle", &MotionBlocks::compileSetRotationStyle));
 
     // Inputs
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS));
@@ -123,6 +124,9 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DX", MotionBlocks::DX));
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DY", MotionBlocks::DY));
 
+    // Fields
+    EXPECT_CALL(m_engineMock, addField(m_section.get(), "STYLE", MotionBlocks::STYLE));
+
     m_section->registerBlocks(&m_engineMock);
 }
 
@@ -1082,3 +1086,70 @@ TEST_F(MotionBlocksTest, SetYImpl)
     ASSERT_EQ(vm.registerCount(), 0);
     ASSERT_EQ(sprite.y(), 189.42);
 }
+
+TEST_F(MotionBlocksTest, SetRotationStyle)
+{
+    Compiler compiler(&m_engineMock);
+
+    // set rotation style [left-right]
+    auto block1 = std::make_shared<Block>("a", "motion_setrotationstyle");
+    addDropdownField(block1, "STYLE", MotionBlocks::STYLE, "left-right", MotionBlocks::LeftRight);
+
+    // set rotation style [don't rotate]
+    auto block2 = std::make_shared<Block>("b", "motion_setrotationstyle");
+    addDropdownField(block2, "STYLE", MotionBlocks::STYLE, "don't rotate", MotionBlocks::DoNotRotate);
+
+    // set rotation style [all around]
+    auto block3 = std::make_shared<Block>("c", "motion_setrotationstyle");
+    addDropdownField(block3, "STYLE", MotionBlocks::STYLE, "all around", MotionBlocks::AllAround);
+
+    compiler.init();
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::setLeftRightRotationStyle)).WillOnce(Return(0));
+    compiler.setBlock(block1);
+    MotionBlocks::compileSetRotationStyle(&compiler);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::setDoNotRotateRotationStyle)).WillOnce(Return(1));
+    compiler.setBlock(block2);
+    MotionBlocks::compileSetRotationStyle(&compiler);
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::setAllAroundRotationStyle)).WillOnce(Return(2));
+    compiler.setBlock(block3);
+    MotionBlocks::compileSetRotationStyle(&compiler);
+
+    compiler.end();
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_EXEC, 2, vm::OP_HALT }));
+    ASSERT_TRUE(compiler.constValues().empty());
+}
+
+TEST_F(MotionBlocksTest, SetRotationStyleImpl)
+{
+    static unsigned int bytecode1[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT };
+    static unsigned int bytecode2[] = { vm::OP_START, vm::OP_EXEC, 1, vm::OP_HALT };
+    static unsigned int bytecode3[] = { vm::OP_START, vm::OP_EXEC, 2, vm::OP_HALT };
+    static BlockFunc functions[] = { &MotionBlocks::setLeftRightRotationStyle, &MotionBlocks::setDoNotRotateRotationStyle, &MotionBlocks::setAllAroundRotationStyle };
+
+    Sprite sprite;
+
+    VirtualMachine vm(&sprite, nullptr, nullptr);
+    vm.setFunctions(functions);
+
+    vm.setBytecode(bytecode1);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(sprite.rotationStyle(), Sprite::RotationStyle::LeftRight);
+
+    vm.setBytecode(bytecode2);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(sprite.rotationStyle(), Sprite::RotationStyle::DoNotRotate);
+
+    vm.setBytecode(bytecode3);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(sprite.rotationStyle(), Sprite::RotationStyle::AllAround);
+}

From afdc329ce11a8cadf5750b7813c63edc081c8508 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 28 Sep 2023 17:21:50 +0200
Subject: [PATCH 38/49] Implement motion_xposition block

---
 src/blocks/motionblocks.cpp        | 18 +++++++++++++++
 src/blocks/motionblocks.h          |  2 ++
 test/blocks/motion_blocks_test.cpp | 35 ++++++++++++++++++++++++++++++
 3 files changed, 55 insertions(+)

diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp
index 741b8572..9e6b175e 100644
--- a/src/blocks/motionblocks.cpp
+++ b/src/blocks/motionblocks.cpp
@@ -39,6 +39,7 @@ void MotionBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "motion_changeyby", &compileChangeYBy);
     engine->addCompileFunction(this, "motion_sety", &compileSetY);
     engine->addCompileFunction(this, "motion_setrotationstyle", &compileSetRotationStyle);
+    engine->addCompileFunction(this, "motion_xposition", &compileXPosition);
 
     // Inputs
     engine->addInput(this, "STEPS", STEPS);
@@ -215,6 +216,11 @@ void MotionBlocks::compileSetRotationStyle(Compiler *compiler)
     }
 }
 
+void MotionBlocks::compileXPosition(Compiler *compiler)
+{
+    compiler->addFunctionCall(&xPosition);
+}
+
 unsigned int MotionBlocks::moveSteps(VirtualMachine *vm)
 {
     Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
@@ -646,3 +652,15 @@ unsigned int MotionBlocks::setAllAroundRotationStyle(VirtualMachine *vm)
 
     return 0;
 }
+
+unsigned int MotionBlocks::xPosition(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (sprite)
+        vm->addReturnValue(sprite->x());
+    else
+        vm->addReturnValue(0);
+
+    return 0;
+}
diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h
index 051b27f7..62503600 100644
--- a/src/blocks/motionblocks.h
+++ b/src/blocks/motionblocks.h
@@ -62,6 +62,7 @@ class MotionBlocks : public IBlockSection
         static void compileChangeYBy(Compiler *compiler);
         static void compileSetY(Compiler *compiler);
         static void compileSetRotationStyle(Compiler *compiler);
+        static void compileXPosition(Compiler *compiler);
 
         static unsigned int moveSteps(VirtualMachine *vm);
         static unsigned int turnRight(VirtualMachine *vm);
@@ -98,6 +99,7 @@ class MotionBlocks : public IBlockSection
         static unsigned int setLeftRightRotationStyle(VirtualMachine *vm);
         static unsigned int setDoNotRotateRotationStyle(VirtualMachine *vm);
         static unsigned int setAllAroundRotationStyle(VirtualMachine *vm);
+        static unsigned int xPosition(VirtualMachine *vm);
 
         static IRandomGenerator *rng;
         static IClock *clock;
diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
index 2ff8ed79..4405c287 100644
--- a/test/blocks/motion_blocks_test.cpp
+++ b/test/blocks/motion_blocks_test.cpp
@@ -111,6 +111,7 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_changeyby", &MotionBlocks::compileChangeYBy));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_sety", &MotionBlocks::compileSetY));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_setrotationstyle", &MotionBlocks::compileSetRotationStyle));
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_xposition", &MotionBlocks::compileXPosition));
 
     // Inputs
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS));
@@ -1153,3 +1154,37 @@ TEST_F(MotionBlocksTest, SetRotationStyleImpl)
     ASSERT_EQ(vm.registerCount(), 0);
     ASSERT_EQ(sprite.rotationStyle(), Sprite::RotationStyle::AllAround);
 }
+
+TEST_F(MotionBlocksTest, XPosition)
+{
+    Compiler compiler(&m_engineMock);
+
+    auto block = std::make_shared<Block>("a", "motion_xposition");
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::xPosition)).WillOnce(Return(0));
+
+    compiler.init();
+    compiler.setBlock(block);
+    MotionBlocks::compileXPosition(&compiler);
+    compiler.end();
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT }));
+    ASSERT_TRUE(compiler.constValues().empty());
+}
+
+TEST_F(MotionBlocksTest, XPositionImpl)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &MotionBlocks::xPosition };
+
+    Sprite sprite;
+    sprite.setX(198.0874);
+
+    VirtualMachine vm(&sprite, nullptr, nullptr);
+    vm.setBytecode(bytecode);
+    vm.setFunctions(functions);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 1);
+    ASSERT_EQ(vm.getInput(0, 1)->toDouble(), 198.0874);
+}

From 718e67b5c26516fd71298e9ebe511c27c13a41c5 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 28 Sep 2023 19:48:44 +0200
Subject: [PATCH 39/49] fix #195: Fix string to double conversion (space before
 number)

---
 include/scratchcpp/value.h          | 134 +++++++++++++++++++++-------
 test/scratch_classes/value_test.cpp |  38 ++++++--
 2 files changed, 133 insertions(+), 39 deletions(-)

diff --git a/include/scratchcpp/value.h b/include/scratchcpp/value.h
index ff1cbced..b9b0d39e 100644
--- a/include/scratchcpp/value.h
+++ b/include/scratchcpp/value.h
@@ -257,24 +257,7 @@ class LIBSCRATCHCPP_EXPORT Value
                     case Type::Integer:
                         return std::to_string(m_intValue);
                     case Type::Double: {
-                        std::stringstream stream;
-                        stream << m_doubleValue;
-                        std::string s = stream.str();
-                        std::size_t index;
-
-                        for (int i = 0; i < 2; i++) {
-                            if (i == 0)
-                                index = s.find("e+");
-                            else
-                                index = s.find("e-");
-
-                            if (index != std::string::npos) {
-                                while ((s.size() >= index + 3) && (s[index + 2] == '0'))
-                                    s.erase(index + 2, 1);
-                            }
-                        }
-
-                        return s;
+                        return doubleToString(m_doubleValue);
                     }
                     case Type::Bool:
                         return m_boolValue ? "true" : "false";
@@ -471,6 +454,26 @@ class LIBSCRATCHCPP_EXPORT Value
                 std::string m_stringValue;
         };
 
+        // -1 - error
+        // 0 - is string
+        // 1 - is long
+        // 2 - is double
+        static int checkString(const std::string &str, long *longValue, double *doubleValue)
+        {
+            if (!longValue || !doubleValue)
+                return -1;
+
+            bool ok;
+
+            if ((str.find_first_of('.') == std::string::npos) && (str.find_first_of('e') == std::string::npos) && (str.find_first_of('E') == std::string::npos)) {
+                *longValue = stringToLong(str, &ok);
+                return ok ? 1 : 0;
+            } else {
+                *doubleValue = stringToDouble(str, &ok);
+                return ok ? 2 : 0;
+            }
+        }
+
         void initString(const std::string &str)
         {
             if (str.empty())
@@ -486,23 +489,21 @@ class LIBSCRATCHCPP_EXPORT Value
                 return;
             }
 
-            bool ok;
-            bool isLong = false;
             long l;
             double d;
-            if ((str.find_first_of('.') == std::string::npos) && (str.find_first_of('e') == std::string::npos) && (str.find_first_of('E') == std::string::npos)) {
-                l = stringToLong(str, &ok);
-                isLong = true;
-            } else
-                d = stringToDouble(str, &ok);
-            if (ok) {
-                if (isLong) {
+            int type = checkString(str, &l, &d);
+
+            switch (type) {
+                case 1:
                     *this = l;
                     m_type = Type::Integer;
-                } else {
+                    break;
+                case 2:
                     *this = d;
                     m_type = Type::Double;
-                }
+                    break;
+                default:
+                    break;
             }
         }
 
@@ -532,9 +533,30 @@ class LIBSCRATCHCPP_EXPORT Value
                         }
                 }
             } else {
-                if (v1.isString() || v2.isString())
-                    return stringsEqual(v1.toUtf16(), v2.toUtf16());
-                else if (v1.isNumber() || v2.isNumber())
+                if (v1.isString() || v2.isString()) {
+                    if (static_cast<int>(v1.m_type) < 0 || static_cast<int>(v2.m_type) < 0)
+                        return stringsEqual(v1.toUtf16(), v2.toUtf16());
+
+                    long l1, l2;
+                    double d1, d2;
+                    int type1 = checkString(v1.toString(), &l1, &d1);
+                    int type2 = checkString(v2.toString(), &l2, &d2);
+
+                    if (type1 == 1)
+                        d1 = l1;
+
+                    if (type2 == 1)
+                        d2 = l2;
+
+                    if (type1 > 0 && type2 > 0)
+                        return d1 == d2;
+                    else if (type2 > 0)
+                        return v1.toString() == doubleToString(d2);
+                    else if (type1 > 0)
+                        return doubleToString(d1) == v2.toString();
+                    else
+                        return stringsEqual(v1.toUtf16(), v2.toUtf16());
+                } else if (v1.isNumber() || v2.isNumber())
                     return v1.toDouble() == v2.toDouble();
                 else if (v1.isBool() || v2.isBool())
                     return ((v1.m_type != Type::NaN && v2.m_type != Type::NaN) && (v1.toBool() == v2.toBool()));
@@ -711,15 +733,37 @@ class LIBSCRATCHCPP_EXPORT Value
             }
 
             static const std::string digits = "0123456789.eE+-";
-            for (char c : s) {
+            const std::string *stringPtr = &s;
+            bool customStr = false;
+
+            if (!s.empty() && ((s[0] == ' ') || (s.back() == ' '))) {
+                std::string *localPtr = new std::string(s);
+                stringPtr = localPtr;
+                customStr = true;
+
+                while (!localPtr->empty() && (localPtr->at(0) == ' '))
+                    localPtr->erase(0, 1);
+
+                while (!localPtr->empty() && (localPtr->back() == ' '))
+                    localPtr->pop_back();
+            }
+
+            for (char c : *stringPtr) {
                 if (digits.find(c) == std::string::npos) {
                     return 0;
                 }
             }
+
             try {
                 if (ok)
                     *ok = true;
-                return std::stod(s);
+
+                double ret = std::stod(*stringPtr);
+
+                if (customStr)
+                    delete stringPtr;
+
+                return ret;
             } catch (...) {
                 if (ok)
                     *ok = false;
@@ -764,6 +808,28 @@ class LIBSCRATCHCPP_EXPORT Value
                 return 0;
             }
         }
+
+        static std::string doubleToString(double v)
+        {
+            std::stringstream stream;
+            stream << v;
+            std::string s = stream.str();
+            std::size_t index;
+
+            for (int i = 0; i < 2; i++) {
+                if (i == 0)
+                    index = s.find("e+");
+                else
+                    index = s.find("e-");
+
+                if (index != std::string::npos) {
+                    while ((s.size() >= index + 3) && (s[index + 2] == '0'))
+                        s.erase(index + 2, 1);
+                }
+            }
+
+            return s;
+        }
 };
 
 } // namespace libscratchcpp
diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp
index 3657d799..e522e760 100644
--- a/test/scratch_classes/value_test.cpp
+++ b/test/scratch_classes/value_test.cpp
@@ -1787,16 +1787,44 @@ TEST(ValueTest, EqualityOperators)
     {
         Value v1 = 5.25;
         Value v2 = "5.25";
-        Value v3 = "5.26";
+        Value v3 = " 5.25";
+        Value v4 = "5.25 ";
+        Value v5 = " 5.25 ";
+        Value v6 = "  5.25  ";
+        Value v7 = "5.26";
 
         ASSERT_TRUE(v1 == v2);
         ASSERT_FALSE(v1 != v2);
 
-        ASSERT_FALSE(v1 == v3);
-        ASSERT_TRUE(v1 != v3);
+        ASSERT_TRUE(v1 == v3);
+        ASSERT_FALSE(v1 != v3);
 
-        ASSERT_FALSE(v2 == v3);
-        ASSERT_TRUE(v2 != v3);
+        ASSERT_TRUE(v1 == v4);
+        ASSERT_FALSE(v1 != v4);
+
+        ASSERT_TRUE(v1 == v5);
+        ASSERT_FALSE(v1 != v5);
+
+        ASSERT_TRUE(v1 == v6);
+        ASSERT_FALSE(v1 != v6);
+
+        ASSERT_FALSE(v1 == v7);
+        ASSERT_TRUE(v1 != v7);
+
+        ASSERT_FALSE(v2 == v7);
+        ASSERT_TRUE(v2 != v7);
+
+        ASSERT_FALSE(v3 == v7);
+        ASSERT_TRUE(v3 != v7);
+
+        ASSERT_FALSE(v4 == v7);
+        ASSERT_TRUE(v4 != v7);
+
+        ASSERT_FALSE(v5 == v7);
+        ASSERT_TRUE(v5 != v7);
+
+        ASSERT_FALSE(v6 == v7);
+        ASSERT_TRUE(v6 != v7);
     }
 
     {

From 653236e3ae18f0a52250a702909a4083738ac01b Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 29 Sep 2023 14:46:07 +0200
Subject: [PATCH 40/49] Add test for #197

---
 test/scratch_classes/value_test.cpp | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp
index 9436ebc3..06e18f89 100644
--- a/test/scratch_classes/value_test.cpp
+++ b/test/scratch_classes/value_test.cpp
@@ -1983,9 +1983,8 @@ TEST(ValueTest, EqualityOperators)
         ASSERT_FALSE(v5 == v8);
         ASSERT_TRUE(v5 != v8);
 
-        // TODO: Enable this after #197 is fixed
-        /*ASSERT_TRUE(v5 == v9);
-        ASSERT_FALSE(v5 != v9);*/
+        ASSERT_TRUE(v5 == v9);
+        ASSERT_FALSE(v5 != v9);
 
         // nan
         ASSERT_FALSE(v6 == v7);

From 46e011aeefa93d1c78f64873967903f383846dd4 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 29 Sep 2023 14:58:52 +0200
Subject: [PATCH 41/49] fix #198: Fix comparison of 0 with NaN

---
 include/scratchcpp/value.h          |  9 ++++++---
 test/scratch_classes/value_test.cpp | 10 ++++------
 2 files changed, 10 insertions(+), 9 deletions(-)

diff --git a/include/scratchcpp/value.h b/include/scratchcpp/value.h
index b1292afc..601b39fc 100644
--- a/include/scratchcpp/value.h
+++ b/include/scratchcpp/value.h
@@ -553,9 +553,12 @@ class LIBSCRATCHCPP_EXPORT Value
                         return doubleToString(d1) == v2.toString();
                     else
                         return stringsEqual(v1.toUtf16(), v2.toUtf16());
-                } else if (v1.isNumber() || v2.isNumber())
-                    return v1.toDouble() == v2.toDouble();
-                else if (v1.isBool() || v2.isBool())
+                } else if (v1.isNumber() || v2.isNumber()) {
+                    if (static_cast<int>(v1.m_type) < 0 || static_cast<int>(v2.m_type) < 0)
+                        return false;
+                    else
+                        return v1.toDouble() == v2.toDouble();
+                } else if (v1.isBool() || v2.isBool())
                     return ((v1.m_type != Type::NaN && v2.m_type != Type::NaN) && (v1.toBool() == v2.toBool()));
                 else
                     return false;
diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp
index 06e18f89..65f0e9e8 100644
--- a/test/scratch_classes/value_test.cpp
+++ b/test/scratch_classes/value_test.cpp
@@ -1873,9 +1873,8 @@ TEST(ValueTest, EqualityOperators)
         ASSERT_FALSE(v1 == v5);
         ASSERT_TRUE(v1 != v5);
 
-        // TODO: Enable this after #198 is fixed
-        /*ASSERT_FALSE(v2 == v5);
-        ASSERT_TRUE(v2 != v5);*/
+        ASSERT_FALSE(v2 == v5);
+        ASSERT_TRUE(v2 != v5);
     }
 
     {
@@ -2042,8 +2041,7 @@ TEST(ValueTest, EqualityOperators)
         ASSERT_FALSE(v3 == v7);
         ASSERT_TRUE(v3 != v7);
 
-        // TODO: Enable this after #198 is fixed
-        /*ASSERT_FALSE(v4 == v7);
-        ASSERT_TRUE(v4 != v7);*/
+        ASSERT_FALSE(v4 == v7);
+        ASSERT_TRUE(v4 != v7);
     }
 }

From cb4b2fe408cca9e28aac36a739543df31e95dd42 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 29 Sep 2023 16:10:09 +0200
Subject: [PATCH 42/49] Update math op block after fixing #198

---
 src/blocks/operatorblocks.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/blocks/operatorblocks.cpp b/src/blocks/operatorblocks.cpp
index 9c4adb83..58851344 100644
--- a/src/blocks/operatorblocks.cpp
+++ b/src/blocks/operatorblocks.cpp
@@ -257,7 +257,7 @@ unsigned int OperatorBlocks::op_ln(VirtualMachine *vm)
     const Value &v = *vm->getInput(0, 1);
     if (v < 0)
         vm->replaceReturnValue(Value(Value::SpecialValue::NaN), 1);
-    else if (v == 0)
+    else if (v == 0 || v.isNaN())
         vm->replaceReturnValue(Value(Value::SpecialValue::NegativeInfinity), 1);
     else if (!v.isInfinity())
         vm->replaceReturnValue(std::log(v.toDouble()), 1);
@@ -269,7 +269,7 @@ unsigned int OperatorBlocks::op_log(VirtualMachine *vm)
     const Value &v = *vm->getInput(0, 1);
     if (v < 0)
         vm->replaceReturnValue(Value(Value::SpecialValue::NaN), 1);
-    else if (v == 0)
+    else if (v == 0 || v.isNaN())
         vm->replaceReturnValue(Value(Value::SpecialValue::NegativeInfinity), 1);
     else if (!v.isInfinity())
         vm->replaceReturnValue(std::log10(v.toDouble()), 1);

From d230416b6db13c817b3b0f71dccaf7b22047c709 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 29 Sep 2023 20:58:21 +0200
Subject: [PATCH 43/49] Implement motion_yposition block

---
 src/blocks/motionblocks.cpp        | 18 +++++++++++++++
 src/blocks/motionblocks.h          |  2 ++
 test/blocks/motion_blocks_test.cpp | 35 ++++++++++++++++++++++++++++++
 3 files changed, 55 insertions(+)

diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp
index 9e6b175e..c22ae095 100644
--- a/src/blocks/motionblocks.cpp
+++ b/src/blocks/motionblocks.cpp
@@ -40,6 +40,7 @@ void MotionBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "motion_sety", &compileSetY);
     engine->addCompileFunction(this, "motion_setrotationstyle", &compileSetRotationStyle);
     engine->addCompileFunction(this, "motion_xposition", &compileXPosition);
+    engine->addCompileFunction(this, "motion_yposition", &compileYPosition);
 
     // Inputs
     engine->addInput(this, "STEPS", STEPS);
@@ -221,6 +222,11 @@ void MotionBlocks::compileXPosition(Compiler *compiler)
     compiler->addFunctionCall(&xPosition);
 }
 
+void MotionBlocks::compileYPosition(Compiler *compiler)
+{
+    compiler->addFunctionCall(&yPosition);
+}
+
 unsigned int MotionBlocks::moveSteps(VirtualMachine *vm)
 {
     Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
@@ -664,3 +670,15 @@ unsigned int MotionBlocks::xPosition(VirtualMachine *vm)
 
     return 0;
 }
+
+unsigned int MotionBlocks::yPosition(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (sprite)
+        vm->addReturnValue(sprite->y());
+    else
+        vm->addReturnValue(0);
+
+    return 0;
+}
diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h
index 62503600..985549d0 100644
--- a/src/blocks/motionblocks.h
+++ b/src/blocks/motionblocks.h
@@ -63,6 +63,7 @@ class MotionBlocks : public IBlockSection
         static void compileSetY(Compiler *compiler);
         static void compileSetRotationStyle(Compiler *compiler);
         static void compileXPosition(Compiler *compiler);
+        static void compileYPosition(Compiler *compiler);
 
         static unsigned int moveSteps(VirtualMachine *vm);
         static unsigned int turnRight(VirtualMachine *vm);
@@ -100,6 +101,7 @@ class MotionBlocks : public IBlockSection
         static unsigned int setDoNotRotateRotationStyle(VirtualMachine *vm);
         static unsigned int setAllAroundRotationStyle(VirtualMachine *vm);
         static unsigned int xPosition(VirtualMachine *vm);
+        static unsigned int yPosition(VirtualMachine *vm);
 
         static IRandomGenerator *rng;
         static IClock *clock;
diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
index 4405c287..54a4a171 100644
--- a/test/blocks/motion_blocks_test.cpp
+++ b/test/blocks/motion_blocks_test.cpp
@@ -112,6 +112,7 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_sety", &MotionBlocks::compileSetY));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_setrotationstyle", &MotionBlocks::compileSetRotationStyle));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_xposition", &MotionBlocks::compileXPosition));
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_yposition", &MotionBlocks::compileYPosition));
 
     // Inputs
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS));
@@ -1188,3 +1189,37 @@ TEST_F(MotionBlocksTest, XPositionImpl)
     ASSERT_EQ(vm.registerCount(), 1);
     ASSERT_EQ(vm.getInput(0, 1)->toDouble(), 198.0874);
 }
+
+TEST_F(MotionBlocksTest, YPosition)
+{
+    Compiler compiler(&m_engineMock);
+
+    auto block = std::make_shared<Block>("a", "motion_yposition");
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::yPosition)).WillOnce(Return(0));
+
+    compiler.init();
+    compiler.setBlock(block);
+    MotionBlocks::compileYPosition(&compiler);
+    compiler.end();
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT }));
+    ASSERT_TRUE(compiler.constValues().empty());
+}
+
+TEST_F(MotionBlocksTest, YPositionImpl)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &MotionBlocks::yPosition };
+
+    Sprite sprite;
+    sprite.setY(-68.5408);
+
+    VirtualMachine vm(&sprite, nullptr, nullptr);
+    vm.setBytecode(bytecode);
+    vm.setFunctions(functions);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 1);
+    ASSERT_EQ(vm.getInput(0, 1)->toDouble(), -68.5408);
+}

From ec69adbd45dd223d51107d7bdd7d2435fe4b0265 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 29 Sep 2023 21:05:45 +0200
Subject: [PATCH 44/49] Implement motion_direction block

---
 src/blocks/motionblocks.cpp        | 18 +++++++++++++++
 src/blocks/motionblocks.h          |  2 ++
 test/blocks/motion_blocks_test.cpp | 35 ++++++++++++++++++++++++++++++
 3 files changed, 55 insertions(+)

diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp
index c22ae095..91317605 100644
--- a/src/blocks/motionblocks.cpp
+++ b/src/blocks/motionblocks.cpp
@@ -41,6 +41,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);
 
     // Inputs
     engine->addInput(this, "STEPS", STEPS);
@@ -227,6 +228,11 @@ void MotionBlocks::compileYPosition(Compiler *compiler)
     compiler->addFunctionCall(&yPosition);
 }
 
+void MotionBlocks::compileDirection(Compiler *compiler)
+{
+    compiler->addFunctionCall(&direction);
+}
+
 unsigned int MotionBlocks::moveSteps(VirtualMachine *vm)
 {
     Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
@@ -682,3 +688,15 @@ unsigned int MotionBlocks::yPosition(VirtualMachine *vm)
 
     return 0;
 }
+
+unsigned int MotionBlocks::direction(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (sprite)
+        vm->addReturnValue(sprite->direction());
+    else
+        vm->addReturnValue(0);
+
+    return 0;
+}
diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h
index 985549d0..6fd4c464 100644
--- a/src/blocks/motionblocks.h
+++ b/src/blocks/motionblocks.h
@@ -64,6 +64,7 @@ class MotionBlocks : public IBlockSection
         static void compileSetRotationStyle(Compiler *compiler);
         static void compileXPosition(Compiler *compiler);
         static void compileYPosition(Compiler *compiler);
+        static void compileDirection(Compiler *compiler);
 
         static unsigned int moveSteps(VirtualMachine *vm);
         static unsigned int turnRight(VirtualMachine *vm);
@@ -102,6 +103,7 @@ class MotionBlocks : public IBlockSection
         static unsigned int setAllAroundRotationStyle(VirtualMachine *vm);
         static unsigned int xPosition(VirtualMachine *vm);
         static unsigned int yPosition(VirtualMachine *vm);
+        static unsigned int direction(VirtualMachine *vm);
 
         static IRandomGenerator *rng;
         static IClock *clock;
diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
index 54a4a171..bbd312f5 100644
--- a/test/blocks/motion_blocks_test.cpp
+++ b/test/blocks/motion_blocks_test.cpp
@@ -113,6 +113,7 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_setrotationstyle", &MotionBlocks::compileSetRotationStyle));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_xposition", &MotionBlocks::compileXPosition));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_yposition", &MotionBlocks::compileYPosition));
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_direction", &MotionBlocks::compileDirection));
 
     // Inputs
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS));
@@ -1223,3 +1224,37 @@ TEST_F(MotionBlocksTest, YPositionImpl)
     ASSERT_EQ(vm.registerCount(), 1);
     ASSERT_EQ(vm.getInput(0, 1)->toDouble(), -68.5408);
 }
+
+TEST_F(MotionBlocksTest, Direction)
+{
+    Compiler compiler(&m_engineMock);
+
+    auto block = std::make_shared<Block>("a", "motion_direction");
+
+    EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::direction)).WillOnce(Return(0));
+
+    compiler.init();
+    compiler.setBlock(block);
+    MotionBlocks::compileDirection(&compiler);
+    compiler.end();
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT }));
+    ASSERT_TRUE(compiler.constValues().empty());
+}
+
+TEST_F(MotionBlocksTest, DirectionImpl)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &MotionBlocks::direction };
+
+    Sprite sprite;
+    sprite.setDirection(179.605);
+
+    VirtualMachine vm(&sprite, nullptr, nullptr);
+    vm.setBytecode(bytecode);
+    vm.setFunctions(functions);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 1);
+    ASSERT_EQ(vm.getInput(0, 1)->toDouble(), 179.605);
+}

From 3c20340f74a546c1ec459cf788dc4e5a9cd06954 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 29 Sep 2023 22:29:58 +0200
Subject: [PATCH 45/49] Add stage height properties to Engine

---
 include/scratchcpp/iengine.h   | 12 ++++++++++++
 src/engine/internal/engine.cpp | 20 ++++++++++++++++++++
 src/engine/internal/engine.h   |  8 ++++++++
 test/engine/engine_test.cpp    | 18 ++++++++++++++++++
 test/mocks/enginemock.h        |  6 ++++++
 5 files changed, 64 insertions(+)

diff --git a/include/scratchcpp/iengine.h b/include/scratchcpp/iengine.h
index f9c8851f..ae653f35 100644
--- a/include/scratchcpp/iengine.h
+++ b/include/scratchcpp/iengine.h
@@ -103,6 +103,18 @@ class LIBSCRATCHCPP_EXPORT IEngine
         /*! Sets the Y coordinate of the mouse pointer. */
         virtual void setMouseY(double y) = 0;
 
+        /*! Returns the stage width. */
+        virtual unsigned int stageWidth() const = 0;
+
+        /*! Sets the stage width. */
+        virtual void setStageWidth(unsigned int width) = 0;
+
+        /*! Returns the stage height. */
+        virtual unsigned int stageHeight() const = 0;
+
+        /*! Sets the stage height. */
+        virtual void setStageHeight(unsigned int height) = 0;
+
         /*! Returns true if there are any running script of the broadcast with the given index. */
         virtual bool broadcastRunning(unsigned int index, VirtualMachine *sourceScript) = 0;
 
diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp
index f504d5d5..85cc4f2a 100644
--- a/src/engine/internal/engine.cpp
+++ b/src/engine/internal/engine.cpp
@@ -383,6 +383,26 @@ void Engine::setMouseY(double y)
     m_mouseY = y;
 }
 
+unsigned int Engine::stageWidth() const
+{
+    return m_stageWidth;
+}
+
+void Engine::setStageWidth(unsigned int width)
+{
+    m_stageWidth = width;
+}
+
+unsigned int Engine::stageHeight() const
+{
+    return m_stageHeight;
+}
+
+void Engine::setStageHeight(unsigned int height)
+{
+    m_stageHeight = height;
+}
+
 bool Engine::broadcastRunning(unsigned int index, VirtualMachine *sourceScript)
 {
     const auto &scripts = m_runningBroadcastMap[index];
diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h
index fef0fbf7..ea92eb45 100644
--- a/src/engine/internal/engine.h
+++ b/src/engine/internal/engine.h
@@ -45,6 +45,12 @@ class Engine : public IEngine
         double mouseY() const override;
         void setMouseY(double y) override;
 
+        unsigned int stageWidth() const override;
+        void setStageWidth(unsigned int width) override;
+
+        unsigned int stageHeight() const override;
+        void setStageHeight(unsigned int height) override;
+
         bool broadcastRunning(unsigned int index, VirtualMachine *sourceScript) override;
 
         void breakFrame() override;
@@ -122,6 +128,8 @@ class Engine : public IEngine
         std::chrono::milliseconds m_frameDuration; // will be computed in run()
         double m_mouseX = 0;
         double m_mouseY = 0;
+        unsigned int m_stageWidth = 480;
+        unsigned int m_stageHeight = 360;
 
         bool m_running = false;
         bool m_breakFrame = false;
diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp
index cd97cd4a..3a83c981 100644
--- a/test/engine/engine_test.cpp
+++ b/test/engine/engine_test.cpp
@@ -61,6 +61,24 @@ TEST(EngineTest, MouseY)
     ASSERT_EQ(engine.mouseY(), 179.9258);
 }
 
+TEST(EngineTest, StageWidth)
+{
+    Engine engine;
+    ASSERT_EQ(engine.stageWidth(), 480);
+
+    engine.setStageWidth(640);
+    ASSERT_EQ(engine.stageWidth(), 640);
+}
+
+TEST(EngineTest, StageHeight)
+{
+    Engine engine;
+    ASSERT_EQ(engine.stageHeight(), 360);
+
+    engine.setStageHeight(515);
+    ASSERT_EQ(engine.stageHeight(), 515);
+}
+
 TEST(EngineTest, BreakFrame)
 {
     Engine engine;
diff --git a/test/mocks/enginemock.h b/test/mocks/enginemock.h
index 636a3aae..7e56c489 100644
--- a/test/mocks/enginemock.h
+++ b/test/mocks/enginemock.h
@@ -33,6 +33,12 @@ class EngineMock : public IEngine
         MOCK_METHOD(double, mouseY, (), (const, override));
         MOCK_METHOD(void, setMouseY, (double y), (override));
 
+        MOCK_METHOD(unsigned int, stageWidth, (), (const, override));
+        MOCK_METHOD(void, setStageWidth, (unsigned int), (override));
+
+        MOCK_METHOD(unsigned int, stageHeight, (), (const, override));
+        MOCK_METHOD(void, setStageHeight, (unsigned int), (override));
+
         MOCK_METHOD(bool, broadcastRunning, (unsigned int, VirtualMachine *), (override));
 
         MOCK_METHOD(void, breakFrame, (), (override));

From 6d2ff6d10983c52085deed199d65248fed35f5f5 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 30 Sep 2023 11:18:52 +0200
Subject: [PATCH 46/49] Read stage size from engine in motion blocks

---
 src/blocks/motionblocks.cpp        | 30 ++++++++++++------------------
 test/blocks/motion_blocks_test.cpp | 30 ++++++++++++++++++++----------
 2 files changed, 32 insertions(+), 28 deletions(-)

diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp
index 91317605..cbf8c69f 100644
--- a/src/blocks/motionblocks.cpp
+++ b/src/blocks/motionblocks.cpp
@@ -304,9 +304,8 @@ unsigned int MotionBlocks::pointTowards(VirtualMachine *vm)
     if (value == "_mouse_")
         pointTowardsPos(dynamic_cast<Sprite *>(vm->target()), vm->engine()->mouseX(), vm->engine()->mouseY());
     else if (value == "_random_") {
-        // TODO: Read stage size from engine (#224)
-        static const unsigned int stageWidth = 480;
-        static const unsigned int stageHeight = 360;
+        const unsigned int stageWidth = vm->engine()->stageWidth();
+        const unsigned int stageHeight = vm->engine()->stageHeight();
 
         if (!rng)
             rng = RandomGenerator::instance().get();
@@ -342,9 +341,8 @@ unsigned int MotionBlocks::pointTowardsMousePointer(VirtualMachine *vm)
 
 unsigned int MotionBlocks::pointTowardsRandomPosition(VirtualMachine *vm)
 {
-    // TODO: Read stage size from engine (#224)
-    static const unsigned int stageWidth = 480;
-    static const unsigned int stageHeight = 360;
+    const unsigned int stageWidth = vm->engine()->stageWidth();
+    const unsigned int stageHeight = vm->engine()->stageHeight();
 
     if (!rng)
         rng = RandomGenerator::instance().get();
@@ -379,9 +377,8 @@ unsigned int MotionBlocks::goTo(VirtualMachine *vm)
         sprite->setX(vm->engine()->mouseX());
         sprite->setY(vm->engine()->mouseY());
     } else if (value == "_random_") {
-        // TODO: Read stage size from engine (#224)
-        static const unsigned int stageWidth = 480;
-        static const unsigned int stageHeight = 360;
+        const unsigned int stageWidth = vm->engine()->stageWidth();
+        const unsigned int stageHeight = vm->engine()->stageHeight();
 
         if (!rng)
             rng = RandomGenerator::instance().get();
@@ -432,9 +429,8 @@ unsigned int MotionBlocks::goToRandomPosition(VirtualMachine *vm)
     Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
 
     if (sprite) {
-        // TODO: Read stage size from engine (#224)
-        static const unsigned int stageWidth = 480;
-        static const unsigned int stageHeight = 360;
+        const unsigned int stageWidth = vm->engine()->stageWidth();
+        const unsigned int stageHeight = vm->engine()->stageHeight();
 
         if (!rng)
             rng = RandomGenerator::instance().get();
@@ -536,9 +532,8 @@ unsigned int MotionBlocks::startGlideTo(VirtualMachine *vm)
     if (value == "_mouse_")
         startGlidingToPos(vm, vm->engine()->mouseX(), vm->engine()->mouseY(), vm->getInput(0, 2)->toDouble());
     else if (value == "_random_") {
-        // TODO: Read stage size from engine (#224)
-        static const unsigned int stageWidth = 480;
-        static const unsigned int stageHeight = 360;
+        const unsigned int stageWidth = vm->engine()->stageWidth();
+        const unsigned int stageHeight = vm->engine()->stageHeight();
 
         if (!rng)
             rng = RandomGenerator::instance().get();
@@ -582,9 +577,8 @@ unsigned int MotionBlocks::startGlideToRandomPosition(VirtualMachine *vm)
     Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
 
     if (sprite) {
-        // TODO: Read stage size from engine (#224)
-        static const unsigned int stageWidth = 480;
-        static const unsigned int stageHeight = 360;
+        const unsigned int stageWidth = vm->engine()->stageWidth();
+        const unsigned int stageHeight = vm->engine()->stageHeight();
 
         if (!rng)
             rng = RandomGenerator::instance().get();
diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp
index bbd312f5..10723adc 100644
--- a/test/blocks/motion_blocks_test.cpp
+++ b/test/blocks/motion_blocks_test.cpp
@@ -386,8 +386,10 @@ TEST_F(MotionBlocksTest, PointTowardsImpl)
     sprite.setY(std::round(sprite.y()));
 
     for (int i = 0; i < positions.size(); i++) {
-        EXPECT_CALL(rng, randint(-240, 240)).WillOnce(Return(std::round(positions[i].first)));
-        EXPECT_CALL(rng, randint(-180, 180)).WillOnce(Return(std::round(positions[i].second)));
+        EXPECT_CALL(m_engineMock, stageWidth()).WillOnce(Return(640));
+        EXPECT_CALL(m_engineMock, stageHeight()).WillOnce(Return(500));
+        EXPECT_CALL(rng, randint(-320, 320)).WillOnce(Return(std::round(positions[i].first)));
+        EXPECT_CALL(rng, randint(-250, 250)).WillOnce(Return(std::round(positions[i].second)));
 
         // TODO: Move setBytecode() out of the loop and use reset() after task #215 is completed
         vm.setBytecode(bytecode2);
@@ -449,8 +451,10 @@ TEST_F(MotionBlocksTest, PointTowardsImpl)
     sprite.setY(std::round(sprite.y()));
 
     for (int i = 0; i < positions.size(); i++) {
-        EXPECT_CALL(rng, randint(-240, 240)).WillOnce(Return(std::round(positions[i].first)));
-        EXPECT_CALL(rng, randint(-180, 180)).WillOnce(Return(std::round(positions[i].second)));
+        EXPECT_CALL(m_engineMock, stageWidth()).WillOnce(Return(640));
+        EXPECT_CALL(m_engineMock, stageHeight()).WillOnce(Return(500));
+        EXPECT_CALL(rng, randint(-320, 320)).WillOnce(Return(std::round(positions[i].first)));
+        EXPECT_CALL(rng, randint(-250, 250)).WillOnce(Return(std::round(positions[i].second)));
 
         // TODO: Move setBytecode() out of the loop and use reset() after task #215 is completed
         vm.setBytecode(bytecode6);
@@ -590,8 +594,10 @@ TEST_F(MotionBlocksTest, GoToImpl)
     ASSERT_EQ(sprite.y(), 45.2);
 
     // go to (join "_random_" "")
-    EXPECT_CALL(rng, randint(-240, 240)).WillOnce(Return(-158));
-    EXPECT_CALL(rng, randint(-180, 180)).WillOnce(Return(65));
+    EXPECT_CALL(m_engineMock, stageWidth()).WillOnce(Return(640));
+    EXPECT_CALL(m_engineMock, stageHeight()).WillOnce(Return(500));
+    EXPECT_CALL(rng, randint(-320, 320)).WillOnce(Return(-158));
+    EXPECT_CALL(rng, randint(-250, 250)).WillOnce(Return(65));
 
     vm.setBytecode(bytecode2);
     vm.run();
@@ -633,8 +639,10 @@ TEST_F(MotionBlocksTest, GoToImpl)
     ASSERT_EQ(sprite.y(), -170.6);
 
     // go to (random position)
-    EXPECT_CALL(rng, randint(-240, 240)).WillOnce(Return(220));
-    EXPECT_CALL(rng, randint(-180, 180)).WillOnce(Return(-16));
+    EXPECT_CALL(m_engineMock, stageWidth()).WillOnce(Return(640));
+    EXPECT_CALL(m_engineMock, stageHeight()).WillOnce(Return(500));
+    EXPECT_CALL(rng, randint(-320, 320)).WillOnce(Return(220));
+    EXPECT_CALL(rng, randint(-250, 250)).WillOnce(Return(-16));
 
     vm.setBytecode(bytecode6);
     vm.run();
@@ -869,8 +877,10 @@ TEST_F(MotionBlocksTest, GlideToImpl)
                 EXPECT_CALL(m_engineMock, mouseY()).WillOnce(Return(endY));
                 break;
             case 3:
-                EXPECT_CALL(rng, randint(-240, 240)).WillOnce(Return(std::round(endX)));
-                EXPECT_CALL(rng, randint(-180, 180)).WillOnce(Return(std::round(endY)));
+                EXPECT_CALL(m_engineMock, stageWidth()).WillOnce(Return(640));
+                EXPECT_CALL(m_engineMock, stageHeight()).WillOnce(Return(500));
+                EXPECT_CALL(rng, randint(-320, 320)).WillOnce(Return(std::round(endX)));
+                EXPECT_CALL(rng, randint(-250, 250)).WillOnce(Return(std::round(endY)));
             default:
                 break;
         }

From 1098d08fd81c7d18ce1c9c2deb4483a59f92d727 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 30 Sep 2023 14:16:01 +0200
Subject: [PATCH 47/49] fix #222: Add support for random decimals

---
 src/engine/virtualmachine_p.cpp               |  5 +-
 test/virtual_machine/virtual_machine_test.cpp | 51 +++++++++++++++++++
 2 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/src/engine/virtualmachine_p.cpp b/src/engine/virtualmachine_p.cpp
index f28f76fc..2c747c30 100644
--- a/src/engine/virtualmachine_p.cpp
+++ b/src/engine/virtualmachine_p.cpp
@@ -365,7 +365,10 @@ do_loop_end : {
     DISPATCH();
 
 do_random:
-    REPLACE_RET_VALUE(rng->randint(READ_REG(0, 2)->toDouble(), READ_REG(1, 2)->toDouble()), 2);
+    if ((READ_REG(0, 2)->type() == Value::Type::Integer) && (READ_REG(1, 2)->type() == Value::Type::Integer))
+        REPLACE_RET_VALUE(rng->randint(READ_REG(0, 2)->toInt(), READ_REG(1, 2)->toInt()), 2);
+    else
+        REPLACE_RET_VALUE(rng->randintDouble(READ_REG(0, 2)->toDouble(), READ_REG(1, 2)->toDouble()), 2);
     FREE_REGS(1);
     DISPATCH();
 
diff --git a/test/virtual_machine/virtual_machine_test.cpp b/test/virtual_machine/virtual_machine_test.cpp
index 7b4b1a19..2f4010a4 100644
--- a/test/virtual_machine/virtual_machine_test.cpp
+++ b/test/virtual_machine/virtual_machine_test.cpp
@@ -362,6 +362,57 @@ TEST(VirtualMachineTest, OP_MOD)
     ASSERT_EQ(vm.getInput(0, 1)->toDouble(), 1.5);
 }
 
+TEST(VirtualMachineTest, OP_RANDOM)
+{
+    static unsigned int bytecode1[] = { OP_START, OP_CONST, 0, OP_CONST, 1, OP_RANDOM, OP_HALT };
+    static unsigned int bytecode2[] = { OP_START, OP_CONST, 1, OP_CONST, 2, OP_RANDOM, OP_HALT };
+    static unsigned int bytecode3[] = { OP_START, OP_CONST, 3, OP_CONST, 0, OP_RANDOM, OP_HALT };
+    static unsigned int bytecode4[] = { OP_START, OP_CONST, 2, OP_CONST, 3, OP_RANDOM, OP_HALT };
+    static Value constValues[] = { -45, 12, 6.05, -78.686 };
+
+    VirtualMachinePrivate vm(nullptr, nullptr, nullptr, nullptr);
+    vm.constValues = constValues;
+
+    RandomGeneratorMock rng;
+    vm.rng = &rng;
+
+    EXPECT_CALL(rng, randint(-45, 12)).WillOnce(Return(-18));
+    vm.bytecode = bytecode1;
+    vm.pos = bytecode1;
+    vm.regCount = 0;
+    vm.run(vm.pos);
+
+    ASSERT_EQ(vm.regCount, 1);
+    ASSERT_EQ(vm.regs[vm.regCount - 1]->toDouble(), -18);
+
+    EXPECT_CALL(rng, randintDouble(12, 6.05)).WillOnce(Return(3.486789));
+    vm.bytecode = bytecode2;
+    vm.pos = bytecode2;
+    vm.regCount = 0;
+    vm.run(vm.pos);
+
+    ASSERT_EQ(vm.regCount, 1);
+    ASSERT_EQ(vm.regs[vm.regCount - 1]->toDouble(), 3.486789);
+
+    EXPECT_CALL(rng, randintDouble(-78.686, -45)).WillOnce(Return(-59.468873));
+    vm.bytecode = bytecode3;
+    vm.pos = bytecode3;
+    vm.regCount = 0;
+    vm.run(vm.pos);
+
+    ASSERT_EQ(vm.regCount, 1);
+    ASSERT_EQ(vm.regs[vm.regCount - 1]->toDouble(), -59.468873);
+
+    EXPECT_CALL(rng, randintDouble(6.05, -78.686)).WillOnce(Return(-28.648764));
+    vm.bytecode = bytecode4;
+    vm.pos = bytecode4;
+    vm.regCount = 0;
+    vm.run(vm.pos);
+
+    ASSERT_EQ(vm.regCount, 1);
+    ASSERT_EQ(vm.regs[vm.regCount - 1]->toDouble(), -28.648764);
+}
+
 TEST(VirtualMachineTest, OP_ROUND)
 {
     static unsigned int bytecode[] = {

From e7dbd2855abe9bd01e5f7cdb40ae8d76bba6dced Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 1 Oct 2023 15:20:18 +0200
Subject: [PATCH 48/49] fix #235: Value: Fix memory leaks in assignment
 operators

---
 include/scratchcpp/value.h | 57 ++++++++++++++++++++++++++++++++------
 1 file changed, 49 insertions(+), 8 deletions(-)

diff --git a/include/scratchcpp/value.h b/include/scratchcpp/value.h
index 601b39fc..3dd86335 100644
--- a/include/scratchcpp/value.h
+++ b/include/scratchcpp/value.h
@@ -372,6 +372,9 @@ class LIBSCRATCHCPP_EXPORT Value
 
         const Value &operator=(float v)
         {
+            if (m_type == Type::String)
+                m_stringValue.~basic_string();
+
             m_type = Type::Double;
             m_doubleValue = v;
             return *this;
@@ -379,6 +382,9 @@ class LIBSCRATCHCPP_EXPORT Value
 
         const Value &operator=(double v)
         {
+            if (m_type == Type::String)
+                m_stringValue.~basic_string();
+
             m_type = Type::Double;
             m_doubleValue = v;
             return *this;
@@ -386,6 +392,9 @@ class LIBSCRATCHCPP_EXPORT Value
 
         const Value &operator=(int v)
         {
+            if (m_type == Type::String)
+                m_stringValue.~basic_string();
+
             m_type = Type::Integer;
             m_intValue = v;
             return *this;
@@ -393,6 +402,9 @@ class LIBSCRATCHCPP_EXPORT Value
 
         const Value &operator=(long v)
         {
+            if (m_type == Type::String)
+                m_stringValue.~basic_string();
+
             m_type = Type::Integer;
             m_intValue = v;
             return *this;
@@ -400,6 +412,9 @@ class LIBSCRATCHCPP_EXPORT Value
 
         const Value &operator=(bool v)
         {
+            if (m_type == Type::String)
+                m_stringValue.~basic_string();
+
             m_type = Type::Bool;
             m_boolValue = v;
             return *this;
@@ -407,41 +422,67 @@ class LIBSCRATCHCPP_EXPORT Value
 
         const Value &operator=(const std::string &v)
         {
-            m_type = Type::String;
-            new (&m_stringValue) std::string(v);
+            if (m_type == Type::String)
+                m_stringValue = v;
+            else {
+                new (&m_stringValue) std::string(v);
+                m_type = Type::String;
+            }
+
             initString(v);
             return *this;
         }
 
         const Value &operator=(const char *v)
         {
-            m_type = Type::String;
-            new (&m_stringValue) std::string(v);
+            if (m_type == Type::String)
+                m_stringValue = v;
+            else {
+                new (&m_stringValue) std::string(v);
+                m_type = Type::String;
+            }
+
             initString(v);
             return *this;
         }
 
         const Value &operator=(const Value &v)
         {
-            m_type = v.m_type;
-
-            switch (m_type) {
+            switch (v.m_type) {
                 case Type::Integer:
+                    if (m_type == Type::String)
+                        m_stringValue.~basic_string();
+
                     m_intValue = v.m_intValue;
                     break;
+
                 case Type::Double:
+                    if (m_type == Type::String)
+                        m_stringValue.~basic_string();
+
                     m_doubleValue = v.m_doubleValue;
                     break;
+
                 case Type::Bool:
+                    if (m_type == Type::String)
+                        m_stringValue.~basic_string();
+
                     m_boolValue = v.m_boolValue;
                     break;
+
                 case Type::String:
-                    new (&m_stringValue) std::string(v.m_stringValue);
+                    if (m_type == Type::String)
+                        m_stringValue = v.m_stringValue;
+                    else
+                        new (&m_stringValue) std::string(v.m_stringValue);
                     break;
+
                 default:
                     break;
             }
 
+            m_type = v.m_type;
+
             return *this;
         }
 

From 7130ccfe54d8ec847eefe4de1a3559e8270e134c Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 1 Oct 2023 15:46:52 +0200
Subject: [PATCH 49/49] Add isRunning method to Engine

---
 include/scratchcpp/iengine.h   |  3 +++
 src/engine/internal/engine.cpp |  5 +++++
 src/engine/internal/engine.h   |  2 ++
 test/engine/engine_test.cpp    | 16 ++++++++++++++++
 test/mocks/enginemock.h        |  2 ++
 5 files changed, 28 insertions(+)

diff --git a/include/scratchcpp/iengine.h b/include/scratchcpp/iengine.h
index ae653f35..6a0c6ef3 100644
--- a/include/scratchcpp/iengine.h
+++ b/include/scratchcpp/iengine.h
@@ -85,6 +85,9 @@ class LIBSCRATCHCPP_EXPORT IEngine
          */
         virtual void run() = 0;
 
+        /*! Returns true if the project is currently running. */
+        virtual bool isRunning() const = 0;
+
         /*! Returns the framerate of the project. */
         virtual double fps() const = 0;
 
diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp
index 85cc4f2a..2abe31d5 100644
--- a/src/engine/internal/engine.cpp
+++ b/src/engine/internal/engine.cpp
@@ -352,6 +352,11 @@ void Engine::run()
     stop();
 }
 
+bool Engine::isRunning() const
+{
+    return m_running;
+}
+
 double Engine::fps() const
 {
     return m_fps;
diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h
index ea92eb45..aaa260eb 100644
--- a/src/engine/internal/engine.h
+++ b/src/engine/internal/engine.h
@@ -36,6 +36,8 @@ class Engine : public IEngine
         void initClone(libscratchcpp::Sprite *clone) override;
         void run() override;
 
+        bool isRunning() const override;
+
         double fps() const override;
         void setFps(double fps) override;
 
diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp
index 3a83c981..d5636969 100644
--- a/test/engine/engine_test.cpp
+++ b/test/engine/engine_test.cpp
@@ -34,6 +34,22 @@ TEST(EngineTest, Clear)
     ASSERT_TRUE(engine.registeredSections().empty());
 }
 
+TEST(EngineTest, IsRunning)
+{
+    Engine engine;
+    ASSERT_FALSE(engine.isRunning());
+
+    engine.start();
+    ASSERT_TRUE(engine.isRunning());
+
+    engine.stop();
+    ASSERT_FALSE(engine.isRunning());
+
+    engine.start();
+    engine.run();
+    ASSERT_FALSE(engine.isRunning());
+}
+
 TEST(EngineTest, Fps)
 {
     Engine engine;
diff --git a/test/mocks/enginemock.h b/test/mocks/enginemock.h
index 7e56c489..4c147e72 100644
--- a/test/mocks/enginemock.h
+++ b/test/mocks/enginemock.h
@@ -24,6 +24,8 @@ class EngineMock : public IEngine
         MOCK_METHOD(void, initClone, (Sprite *), (override));
         MOCK_METHOD(void, run, (), (override));
 
+        MOCK_METHOD(bool, isRunning, (), (const, override));
+
         MOCK_METHOD(double, fps, (), (const, override));
         MOCK_METHOD(void, setFps, (double fps), (override));
 
