Skip to content

Commit 6b8c1cf

Browse files
committed
Implement motion_glidesecstoxy block
1 parent 4e2243f commit 6b8c1cf

File tree

3 files changed

+197
-1
lines changed

3 files changed

+197
-1
lines changed

src/blocks/motionblocks.cpp

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77

88
#include "motionblocks.h"
99
#include "../engine/internal/randomgenerator.h"
10+
#include "../engine/internal/clock.h"
1011

1112
using namespace libscratchcpp;
1213

1314
static const double pi = std::acos(-1); // TODO: Use std::numbers::pi in C++20
1415

1516
IRandomGenerator *MotionBlocks::rng = nullptr;
17+
IClock *MotionBlocks::clock = nullptr;
1618

1719
std::string MotionBlocks::name() const
1820
{
@@ -29,6 +31,7 @@ void MotionBlocks::registerBlocks(IEngine *engine)
2931
engine->addCompileFunction(this, "motion_pointtowards", &compilePointTowards);
3032
engine->addCompileFunction(this, "motion_gotoxy", &compileGoToXY);
3133
engine->addCompileFunction(this, "motion_goto", &compileGoTo);
34+
engine->addCompileFunction(this, "motion_glidesecstoxy", &compileGlideSecsToXY);
3235

3336
// Inputs
3437
engine->addInput(this, "STEPS", STEPS);
@@ -38,6 +41,7 @@ void MotionBlocks::registerBlocks(IEngine *engine)
3841
engine->addInput(this, "X", X);
3942
engine->addInput(this, "Y", Y);
4043
engine->addInput(this, "TO", TO);
44+
engine->addInput(this, "SECS", SECS);
4145
}
4246

4347
void MotionBlocks::compileMoveSteps(Compiler *compiler)
@@ -117,6 +121,15 @@ void MotionBlocks::compileGoTo(Compiler *compiler)
117121
}
118122
}
119123

124+
void MotionBlocks::compileGlideSecsToXY(Compiler *compiler)
125+
{
126+
compiler->addInput(SECS);
127+
compiler->addInput(X);
128+
compiler->addInput(Y);
129+
compiler->addFunctionCall(&startGlideSecsTo);
130+
compiler->addFunctionCall(&glideSecsTo);
131+
}
132+
120133
unsigned int MotionBlocks::moveSteps(VirtualMachine *vm)
121134
{
122135
Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
@@ -328,3 +341,81 @@ unsigned int MotionBlocks::goToRandomPosition(VirtualMachine *vm)
328341

329342
return 0;
330343
}
344+
345+
void MotionBlocks::startGlidingToPos(VirtualMachine *vm, double x, double y, double secs)
346+
{
347+
Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
348+
349+
if (!sprite)
350+
return;
351+
352+
if (secs <= 0) {
353+
if (sprite) {
354+
sprite->setX(x);
355+
sprite->setY(y);
356+
}
357+
358+
return;
359+
}
360+
361+
if (!clock)
362+
clock = Clock::instance().get();
363+
364+
auto currentTime = clock->currentSteadyTime();
365+
m_timeMap[vm] = { currentTime, secs * 1000 };
366+
m_glideMap[vm] = { { sprite->x(), sprite->y() }, { x, y } };
367+
}
368+
369+
void MotionBlocks::continueGliding(VirtualMachine *vm)
370+
{
371+
if (!clock)
372+
clock = Clock::instance().get();
373+
374+
auto currentTime = clock->currentSteadyTime();
375+
auto elapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - m_timeMap[vm].first).count();
376+
auto maxTime = m_timeMap[vm].second;
377+
assert(m_timeMap.count(vm) == 1);
378+
379+
Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
380+
double x = m_glideMap[vm].second.first;
381+
double y = m_glideMap[vm].second.second;
382+
383+
if (elapsedTime >= maxTime) {
384+
if (sprite) {
385+
sprite->setX(x);
386+
sprite->setY(y);
387+
}
388+
389+
m_timeMap.erase(vm);
390+
m_glideMap.erase(vm);
391+
} else {
392+
if (sprite) {
393+
double startX = m_glideMap[vm].first.first;
394+
double startY = m_glideMap[vm].first.second;
395+
double factor = elapsedTime / static_cast<double>(maxTime);
396+
assert(factor >= 0 && factor < 1);
397+
398+
sprite->setX(startX + (x - startX) * factor);
399+
sprite->setY(startY + (y - startY) * factor);
400+
}
401+
402+
vm->stop(true, true, true);
403+
vm->engine()->lockFrame();
404+
}
405+
}
406+
407+
unsigned int MotionBlocks::startGlideSecsTo(VirtualMachine *vm)
408+
{
409+
startGlidingToPos(vm, vm->getInput(1, 3)->toDouble(), vm->getInput(2, 3)->toDouble(), vm->getInput(0, 3)->toDouble());
410+
return 3;
411+
}
412+
413+
unsigned int MotionBlocks::glideSecsTo(VirtualMachine *vm)
414+
{
415+
if (m_timeMap.find(vm) != m_timeMap.cend()) {
416+
assert(m_glideMap.find(vm) != m_glideMap.cend());
417+
continueGliding(vm);
418+
}
419+
420+
return 0;
421+
}

src/blocks/motionblocks.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
22

33
#pragma once
44

5+
#include <unordered_map>
6+
#include <chrono>
7+
58
#include <scratchcpp/iblocksection.h>
69

710
namespace libscratchcpp
811
{
912

1013
class Sprite;
1114
class IRandomGenerator;
15+
class IClock;
1216

1317
/*! \brief The MotionBlocks class contains the implementation of motion blocks. */
1418
class MotionBlocks : public IBlockSection
@@ -22,7 +26,8 @@ class MotionBlocks : public IBlockSection
2226
TOWARDS,
2327
X,
2428
Y,
25-
TO
29+
TO,
30+
SECS
2631
};
2732

2833
enum Fields
@@ -44,6 +49,7 @@ class MotionBlocks : public IBlockSection
4449
static void compilePointTowards(Compiler *compiler);
4550
static void compileGoToXY(Compiler *compiler);
4651
static void compileGoTo(Compiler *compiler);
52+
static void compileGlideSecsToXY(Compiler *compiler);
4753

4854
static unsigned int moveSteps(VirtualMachine *vm);
4955
static unsigned int turnRight(VirtualMachine *vm);
@@ -63,7 +69,17 @@ class MotionBlocks : public IBlockSection
6369
static unsigned int goToMousePointer(VirtualMachine *vm);
6470
static unsigned int goToRandomPosition(VirtualMachine *vm);
6571

72+
static void startGlidingToPos(VirtualMachine *vm, double x, double y, double secs);
73+
static void continueGliding(VirtualMachine *vm);
74+
75+
static unsigned int startGlideSecsTo(VirtualMachine *vm);
76+
static unsigned int glideSecsTo(VirtualMachine *vm);
77+
6678
static IRandomGenerator *rng;
79+
static IClock *clock;
80+
81+
static inline std::unordered_map<VirtualMachine *, std::pair<std::chrono::steady_clock::time_point, int>> m_timeMap;
82+
static inline std::unordered_map<VirtualMachine *, std::pair<std::pair<double, double>, std::pair<double, double>>> m_glideMap; // start pos, end pos
6783
};
6884

6985
} // namespace libscratchcpp

test/blocks/motion_blocks_test.cpp

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
#include <scratchcpp/sprite.h>
66
#include <enginemock.h>
77
#include <randomgeneratormock.h>
8+
#include <clockmock.h>
89

910
#include "../common.h"
1011
#include "blocks/motionblocks.h"
1112
#include "blocks/operatorblocks.h"
1213
#include "engine/internal/engine.h"
1314
#include "engine/internal/randomgenerator.h"
15+
#include "engine/internal/clock.h"
1416

1517
using namespace libscratchcpp;
1618

@@ -102,6 +104,7 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
102104
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_pointtowards", &MotionBlocks::compilePointTowards));
103105
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_gotoxy", &MotionBlocks::compileGoToXY));
104106
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_goto", &MotionBlocks::compileGoTo));
107+
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_glidesecstoxy", &MotionBlocks::compileGlideSecsToXY));
105108

106109
// Inputs
107110
EXPECT_CALL(m_engineMock, addInput(m_section.get(), "STEPS", MotionBlocks::STEPS));
@@ -111,6 +114,7 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
111114
EXPECT_CALL(m_engineMock, addInput(m_section.get(), "X", MotionBlocks::X));
112115
EXPECT_CALL(m_engineMock, addInput(m_section.get(), "Y", MotionBlocks::Y));
113116
EXPECT_CALL(m_engineMock, addInput(m_section.get(), "TO", MotionBlocks::TO));
117+
EXPECT_CALL(m_engineMock, addInput(m_section.get(), "SECS", MotionBlocks::SECS));
114118

115119
m_section->registerBlocks(&m_engineMock);
116120
}
@@ -627,3 +631,88 @@ TEST_F(MotionBlocksTest, GoToImpl)
627631

628632
MotionBlocks::rng = RandomGenerator::instance().get();
629633
}
634+
635+
TEST_F(MotionBlocksTest, GlideSecsToXY)
636+
{
637+
Compiler compiler(&m_engineMock);
638+
639+
// glide (2.5) secs to x: (95.2) y: (-175.9)
640+
auto block = std::make_shared<Block>("a", "motion_gotoxy");
641+
addValueInput(block, "SECS", MotionBlocks::SECS, 2.5);
642+
addValueInput(block, "X", MotionBlocks::X, 95.2);
643+
addValueInput(block, "Y", MotionBlocks::Y, -175.9);
644+
645+
EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::startGlideSecsTo)).WillOnce(Return(0));
646+
EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::glideSecsTo)).WillOnce(Return(1));
647+
648+
compiler.init();
649+
compiler.setBlock(block);
650+
MotionBlocks::compileGlideSecsToXY(&compiler);
651+
compiler.end();
652+
653+
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 }));
654+
ASSERT_EQ(compiler.constValues(), std::vector<Value>({ 2.5, 95.2, -175.9 }));
655+
}
656+
657+
TEST_F(MotionBlocksTest, GlideSecsToXYImpl)
658+
{
659+
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 };
660+
static BlockFunc functions[] = { &MotionBlocks::startGlideSecsTo, &MotionBlocks::glideSecsTo };
661+
static Value constValues[] = { 2.5, 95.2, -175.9 };
662+
663+
double startX = 100.32;
664+
double startY = -50.12;
665+
666+
Sprite sprite;
667+
sprite.setX(startX);
668+
sprite.setY(startY);
669+
670+
VirtualMachine vm(&sprite, &m_engineMock, nullptr);
671+
vm.setBytecode(bytecode);
672+
vm.setFunctions(functions);
673+
vm.setConstValues(constValues);
674+
675+
ClockMock clock;
676+
MotionBlocks::clock = &clock;
677+
678+
std::chrono::steady_clock::time_point startTime(std::chrono::milliseconds(1000));
679+
EXPECT_CALL(clock, currentSteadyTime()).Times(2).WillRepeatedly(Return(startTime));
680+
EXPECT_CALL(m_engineMock, lockFrame()).Times(1);
681+
EXPECT_CALL(m_engineMock, breakFrame()).Times(1);
682+
vm.run();
683+
684+
ASSERT_EQ(vm.registerCount(), 0);
685+
ASSERT_TRUE(MotionBlocks::m_timeMap.find(&vm) != MotionBlocks::m_timeMap.cend());
686+
ASSERT_TRUE(MotionBlocks::m_glideMap.find(&vm) != MotionBlocks::m_glideMap.cend());
687+
ASSERT_FALSE(vm.atEnd());
688+
ASSERT_EQ(sprite.x(), startX);
689+
ASSERT_EQ(sprite.y(), startY);
690+
691+
std::chrono::steady_clock::time_point time1(std::chrono::milliseconds(3456));
692+
EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time1));
693+
EXPECT_CALL(m_engineMock, lockFrame()).Times(1);
694+
EXPECT_CALL(m_engineMock, breakFrame()).Times(1);
695+
vm.run();
696+
697+
ASSERT_EQ(vm.registerCount(), 0);
698+
ASSERT_TRUE(MotionBlocks::m_timeMap.find(&vm) != MotionBlocks::m_timeMap.cend());
699+
ASSERT_TRUE(MotionBlocks::m_glideMap.find(&vm) != MotionBlocks::m_glideMap.cend());
700+
ASSERT_FALSE(vm.atEnd());
701+
ASSERT_EQ(std::round(sprite.x() * 100) / 100, 95.29);
702+
ASSERT_EQ(std::round(sprite.y() * 100) / 100, -173.69);
703+
704+
std::chrono::steady_clock::time_point time2(std::chrono::milliseconds(3500));
705+
EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time2));
706+
EXPECT_CALL(m_engineMock, lockFrame()).Times(0);
707+
EXPECT_CALL(m_engineMock, breakFrame()).Times(0);
708+
vm.run();
709+
710+
ASSERT_EQ(vm.registerCount(), 0);
711+
ASSERT_TRUE(MotionBlocks::m_timeMap.find(&vm) == MotionBlocks::m_timeMap.cend());
712+
ASSERT_TRUE(MotionBlocks::m_glideMap.find(&vm) == MotionBlocks::m_glideMap.cend());
713+
ASSERT_TRUE(vm.atEnd());
714+
ASSERT_EQ(std::round(sprite.x() * 100) / 100, 95.2);
715+
ASSERT_EQ(std::round(sprite.y() * 100) / 100, -175.9);
716+
717+
MotionBlocks::clock = Clock::instance().get();
718+
}

0 commit comments

Comments
 (0)