Skip to content

Commit 9b33a2a

Browse files
authored
Merge pull request #259 from scratchcpp/sensing_distanceto_block
Implement distance to block
2 parents 53414c3 + 798f7bf commit 9b33a2a

File tree

3 files changed

+275
-4
lines changed

3 files changed

+275
-4
lines changed

src/blocks/sensingblocks.cpp

+79
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
#include <scratchcpp/compiler.h>
55
#include <scratchcpp/iengine.h>
66
#include <scratchcpp/itimer.h>
7+
#include <scratchcpp/input.h>
78
#include <scratchcpp/field.h>
9+
#include <scratchcpp/sprite.h>
810
#include "sensingblocks.h"
911

1012
#include "../engine/internal/clock.h"
@@ -21,11 +23,15 @@ std::string SensingBlocks::name() const
2123
void SensingBlocks::registerBlocks(IEngine *engine)
2224
{
2325
// Blocks
26+
engine->addCompileFunction(this, "sensing_distanceto", &compileDistanceTo);
2427
engine->addCompileFunction(this, "sensing_timer", &compileTimer);
2528
engine->addCompileFunction(this, "sensing_resettimer", &compileResetTimer);
2629
engine->addCompileFunction(this, "sensing_current", &compileCurrent);
2730
engine->addCompileFunction(this, "sensing_dayssince2000", &compileDaysSince2000);
2831

32+
// Inputs
33+
engine->addInput(this, "DISTANCETOMENU", DISTANCETOMENU);
34+
2935
// Fields
3036
engine->addField(this, "CURRENTMENU", CURRENTMENU);
3137

@@ -39,6 +45,27 @@ void SensingBlocks::registerBlocks(IEngine *engine)
3945
engine->addFieldValue(this, "SECOND", SECOND);
4046
}
4147

48+
void SensingBlocks::compileDistanceTo(Compiler *compiler)
49+
{
50+
Input *input = compiler->input(DISTANCETOMENU);
51+
52+
if (input->type() != Input::Type::ObscuredShadow) {
53+
assert(input->pointsToDropdownMenu());
54+
std::string value = input->selectedMenuItem();
55+
56+
if (value == "_mouse_")
57+
compiler->addFunctionCall(&distanceToMousePointer);
58+
else {
59+
int index = compiler->engine()->findTarget(value);
60+
compiler->addConstValue(index);
61+
compiler->addFunctionCall(&distanceToByIndex);
62+
}
63+
} else {
64+
compiler->addInput(input);
65+
compiler->addFunctionCall(&distanceTo);
66+
}
67+
}
68+
4269
void SensingBlocks::compileTimer(Compiler *compiler)
4370
{
4471
compiler->addFunctionCall(&timer);
@@ -92,6 +119,58 @@ void SensingBlocks::compileDaysSince2000(Compiler *compiler)
92119
compiler->addFunctionCall(&daysSince2000);
93120
}
94121

122+
unsigned int SensingBlocks::distanceTo(VirtualMachine *vm)
123+
{
124+
Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
125+
126+
if (!sprite) {
127+
vm->replaceReturnValue(10000, 1);
128+
return 0;
129+
}
130+
131+
std::string value = vm->getInput(0, 1)->toString();
132+
133+
if (value == "_mouse_")
134+
vm->replaceReturnValue(std::sqrt(std::pow(sprite->x() - vm->engine()->mouseX(), 2) + std::pow(sprite->y() - vm->engine()->mouseY(), 2)), 1);
135+
else {
136+
Target *target = vm->engine()->targetAt(vm->engine()->findTarget(value));
137+
Sprite *targetSprite = dynamic_cast<Sprite *>(target);
138+
139+
if (targetSprite)
140+
vm->replaceReturnValue(std::sqrt(std::pow(sprite->x() - targetSprite->x(), 2) + std::pow(sprite->y() - targetSprite->y(), 2)), 1);
141+
else
142+
vm->replaceReturnValue(10000, 1);
143+
}
144+
145+
return 0;
146+
}
147+
148+
unsigned int SensingBlocks::distanceToByIndex(VirtualMachine *vm)
149+
{
150+
Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
151+
Target *target = vm->engine()->targetAt(vm->getInput(0, 1)->toInt());
152+
Sprite *targetSprite = dynamic_cast<Sprite *>(target);
153+
154+
if (sprite && targetSprite)
155+
vm->replaceReturnValue(std::sqrt(std::pow(sprite->x() - targetSprite->x(), 2) + std::pow(sprite->y() - targetSprite->y(), 2)), 1);
156+
else
157+
vm->replaceReturnValue(10000, 1);
158+
159+
return 0;
160+
}
161+
162+
unsigned int SensingBlocks::distanceToMousePointer(VirtualMachine *vm)
163+
{
164+
Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
165+
166+
if (sprite)
167+
vm->addReturnValue(std::sqrt(std::pow(sprite->x() - vm->engine()->mouseX(), 2) + std::pow(sprite->y() - vm->engine()->mouseY(), 2)));
168+
else
169+
vm->addReturnValue(10000);
170+
171+
return 0;
172+
}
173+
95174
unsigned int SensingBlocks::timer(VirtualMachine *vm)
96175
{
97176
vm->addReturnValue(vm->engine()->timer()->value());

src/blocks/sensingblocks.h

+10
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ namespace libscratchcpp
1212
class SensingBlocks : public IBlockSection
1313
{
1414
public:
15+
enum Inputs
16+
{
17+
DISTANCETOMENU
18+
};
19+
1520
enum Fields
1621
{
1722
CURRENTMENU
@@ -32,11 +37,16 @@ class SensingBlocks : public IBlockSection
3237

3338
void registerBlocks(IEngine *engine) override;
3439

40+
static void compileDistanceTo(Compiler *compiler);
3541
static void compileTimer(Compiler *compiler);
3642
static void compileResetTimer(Compiler *compiler);
3743
static void compileCurrent(Compiler *compiler);
3844
static void compileDaysSince2000(Compiler *compiler);
3945

46+
static unsigned int distanceTo(VirtualMachine *vm);
47+
static unsigned int distanceToByIndex(VirtualMachine *vm);
48+
static unsigned int distanceToMousePointer(VirtualMachine *vm);
49+
4050
static unsigned int timer(VirtualMachine *vm);
4151
static unsigned int resetTimer(VirtualMachine *vm);
4252
static unsigned int currentYear(VirtualMachine *vm);

test/blocks/sensing_blocks_test.cpp

+186-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
#include <scratchcpp/compiler.h>
22
#include <scratchcpp/block.h>
3+
#include <scratchcpp/input.h>
34
#include <scratchcpp/field.h>
5+
#include <scratchcpp/sprite.h>
46
#include <enginemock.h>
57
#include <timermock.h>
68
#include <clockmock.h>
79

810
#include "../common.h"
911
#include "blocks/sensingblocks.h"
12+
#include "blocks/operatorblocks.h"
1013
#include "engine/internal/engine.h"
1114

1215
using namespace libscratchcpp;
@@ -34,6 +37,46 @@ class SensingBlocksTest : public testing::Test
3437
return block;
3538
}
3639

40+
void addObscuredInput(std::shared_ptr<Block> block, const std::string &name, SensingBlocks::Inputs id, std::shared_ptr<Block> valueBlock) const
41+
{
42+
auto input = std::make_shared<Input>(name, Input::Type::ObscuredShadow);
43+
input->setValueBlock(valueBlock);
44+
input->setInputId(id);
45+
block->addInput(input);
46+
block->updateInputMap();
47+
}
48+
49+
std::shared_ptr<Input> addNullInput(std::shared_ptr<Block> block, const std::string &name, SensingBlocks::Inputs id) const
50+
{
51+
auto input = std::make_shared<Input>(name, Input::Type::Shadow);
52+
input->setInputId(id);
53+
block->addInput(input);
54+
block->updateInputMap();
55+
56+
return input;
57+
}
58+
59+
void addDropdownInput(std::shared_ptr<Block> block, const std::string &name, SensingBlocks::Inputs id, const std::string &selectedValue, std::shared_ptr<Block> valueBlock = nullptr) const
60+
{
61+
if (valueBlock)
62+
addObscuredInput(block, name, id, valueBlock);
63+
else {
64+
auto input = addNullInput(block, name, id);
65+
auto menu = std::make_shared<Block>(block->id() + "_menu", block->opcode() + "_menu");
66+
input->setValueBlock(menu);
67+
addDropdownField(menu, name, static_cast<SensingBlocks::Fields>(-1), selectedValue, static_cast<SensingBlocks::FieldValues>(-1));
68+
}
69+
}
70+
71+
void addDropdownField(std::shared_ptr<Block> block, const std::string &name, SensingBlocks::Fields id, const std::string &value, SensingBlocks::FieldValues valueId) const
72+
{
73+
auto field = std::make_shared<Field>(name, value);
74+
field->setFieldId(id);
75+
field->setSpecialValueId(valueId);
76+
block->addField(field);
77+
block->updateFieldMap();
78+
}
79+
3780
std::unique_ptr<IBlockSection> m_section;
3881
EngineMock m_engineMock;
3982
Engine m_engine;
@@ -54,10 +97,14 @@ TEST_F(SensingBlocksTest, CategoryVisible)
5497
TEST_F(SensingBlocksTest, RegisterBlocks)
5598
{
5699
// Blocks
57-
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_timer", &SensingBlocks::compileTimer)).Times(1);
58-
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_resettimer", &SensingBlocks::compileResetTimer)).Times(1);
59-
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_current", &SensingBlocks::compileCurrent)).Times(1);
60-
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_dayssince2000", &SensingBlocks::compileDaysSince2000)).Times(1);
100+
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_distanceto", &SensingBlocks::compileDistanceTo));
101+
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_timer", &SensingBlocks::compileTimer));
102+
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_resettimer", &SensingBlocks::compileResetTimer));
103+
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_current", &SensingBlocks::compileCurrent));
104+
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_dayssince2000", &SensingBlocks::compileDaysSince2000));
105+
106+
// Inputs
107+
EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DISTANCETOMENU", SensingBlocks::DISTANCETOMENU));
61108

62109
// Fields
63110
EXPECT_CALL(m_engineMock, addField(m_section.get(), "CURRENTMENU", SensingBlocks::CURRENTMENU));
@@ -74,6 +121,141 @@ TEST_F(SensingBlocksTest, RegisterBlocks)
74121
m_section->registerBlocks(&m_engineMock);
75122
}
76123

124+
TEST_F(SensingBlocksTest, DistanceTo)
125+
{
126+
Compiler compiler(&m_engineMock);
127+
128+
// distance to (Sprite2)
129+
auto block1 = std::make_shared<Block>("a", "sensing_distanceto");
130+
addDropdownInput(block1, "DISTANCETOMENU", SensingBlocks::DISTANCETOMENU, "Sprite2");
131+
132+
// distance to (mouse-pointer)
133+
auto block2 = std::make_shared<Block>("b", "sensing_distanceto");
134+
addDropdownInput(block2, "DISTANCETOMENU", SensingBlocks::DISTANCETOMENU, "_mouse_");
135+
136+
// distance to (join "" "")
137+
auto joinBlock = std::make_shared<Block>("d", "operator_join");
138+
joinBlock->setCompileFunction(&OperatorBlocks::compileJoin);
139+
auto block3 = std::make_shared<Block>("c", "sensing_distanceto");
140+
addDropdownInput(block3, "DISTANCETOMENU", SensingBlocks::DISTANCETOMENU, "", joinBlock);
141+
142+
compiler.init();
143+
144+
EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(5));
145+
EXPECT_CALL(m_engineMock, functionIndex(&SensingBlocks::distanceToByIndex)).WillOnce(Return(0));
146+
compiler.setBlock(block1);
147+
SensingBlocks::compileDistanceTo(&compiler);
148+
149+
EXPECT_CALL(m_engineMock, functionIndex(&SensingBlocks::distanceToMousePointer)).WillOnce(Return(1));
150+
compiler.setBlock(block2);
151+
SensingBlocks::compileDistanceTo(&compiler);
152+
153+
EXPECT_CALL(m_engineMock, functionIndex(&SensingBlocks::distanceTo)).WillOnce(Return(2));
154+
compiler.setBlock(block3);
155+
SensingBlocks::compileDistanceTo(&compiler);
156+
157+
compiler.end();
158+
159+
ASSERT_EQ(
160+
compiler.bytecode(),
161+
std::vector<unsigned int>({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_NULL, vm::OP_NULL, vm::OP_STR_CONCAT, vm::OP_EXEC, 2, vm::OP_HALT }));
162+
ASSERT_EQ(compiler.constValues().size(), 1);
163+
ASSERT_EQ(compiler.constValues()[0].toDouble(), 5);
164+
}
165+
166+
TEST_F(SensingBlocksTest, DistanceToImpl)
167+
{
168+
static unsigned int bytecode1[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT };
169+
static unsigned int bytecode2[] = { vm::OP_START, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_HALT };
170+
static unsigned int bytecode3[] = { vm::OP_START, vm::OP_CONST, 2, vm::OP_EXEC, 0, vm::OP_HALT };
171+
static unsigned int bytecode4[] = { vm::OP_START, vm::OP_CONST, 3, vm::OP_EXEC, 1, vm::OP_HALT };
172+
static unsigned int bytecode5[] = { vm::OP_START, vm::OP_CONST, 4, vm::OP_EXEC, 1, vm::OP_HALT };
173+
static unsigned int bytecode6[] = { vm::OP_START, vm::OP_CONST, 5, vm::OP_EXEC, 1, vm::OP_HALT };
174+
static unsigned int bytecode7[] = { vm::OP_START, vm::OP_CONST, 6, vm::OP_EXEC, 1, vm::OP_HALT };
175+
static unsigned int bytecode8[] = { vm::OP_START, vm::OP_EXEC, 2, vm::OP_HALT };
176+
static BlockFunc functions[] = { &SensingBlocks::distanceTo, &SensingBlocks::distanceToByIndex, &SensingBlocks::distanceToMousePointer };
177+
static Value constValues[] = { "Sprite2", "_mouse_", "", 0, 1, -1, 2 };
178+
179+
Sprite sprite1;
180+
sprite1.setX(-50.35);
181+
sprite1.setY(33.04);
182+
183+
Sprite sprite2;
184+
sprite2.setX(108.564);
185+
sprite2.setY(-168.452);
186+
187+
VirtualMachine vm(&sprite1, &m_engineMock, nullptr);
188+
vm.setFunctions(functions);
189+
vm.setConstValues(constValues);
190+
191+
EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(3));
192+
EXPECT_CALL(m_engineMock, targetAt(3)).WillOnce(Return(&sprite2));
193+
vm.setBytecode(bytecode1);
194+
vm.run();
195+
196+
ASSERT_EQ(vm.registerCount(), 1);
197+
ASSERT_EQ(std::round(vm.getInput(0, 1)->toDouble() * 10000) / 10000, 256.6178);
198+
199+
EXPECT_CALL(m_engineMock, mouseX()).WillOnce(Return(-239.98));
200+
EXPECT_CALL(m_engineMock, mouseY()).WillOnce(Return(-86.188));
201+
vm.setBytecode(bytecode2);
202+
vm.reset();
203+
vm.run();
204+
205+
ASSERT_EQ(vm.registerCount(), 1);
206+
ASSERT_EQ(std::round(vm.getInput(0, 1)->toDouble() * 10000) / 10000, 223.9974);
207+
208+
EXPECT_CALL(m_engineMock, findTarget("")).WillOnce(Return(-1));
209+
EXPECT_CALL(m_engineMock, targetAt(-1)).WillOnce(Return(nullptr));
210+
vm.setBytecode(bytecode3);
211+
vm.reset();
212+
vm.run();
213+
214+
ASSERT_EQ(vm.registerCount(), 1);
215+
ASSERT_EQ(vm.getInput(0, 1)->toDouble(), 10000);
216+
217+
EXPECT_CALL(m_engineMock, targetAt(0)).WillOnce(Return(&sprite1));
218+
vm.setBytecode(bytecode4);
219+
vm.reset();
220+
vm.run();
221+
222+
ASSERT_EQ(vm.registerCount(), 1);
223+
ASSERT_EQ(vm.getInput(0, 1)->toDouble(), 0);
224+
225+
EXPECT_CALL(m_engineMock, targetAt(1)).WillOnce(Return(&sprite2));
226+
vm.setBytecode(bytecode5);
227+
vm.reset();
228+
vm.run();
229+
230+
ASSERT_EQ(vm.registerCount(), 1);
231+
ASSERT_EQ(std::round(vm.getInput(0, 1)->toDouble() * 10000) / 10000, 256.6178);
232+
233+
EXPECT_CALL(m_engineMock, targetAt(-1)).WillOnce(Return(nullptr));
234+
vm.setBytecode(bytecode6);
235+
vm.reset();
236+
vm.run();
237+
238+
ASSERT_EQ(vm.registerCount(), 1);
239+
ASSERT_EQ(vm.getInput(0, 1)->toDouble(), 10000);
240+
241+
EXPECT_CALL(m_engineMock, targetAt(2)).WillOnce(Return(nullptr));
242+
vm.setBytecode(bytecode7);
243+
vm.reset();
244+
vm.run();
245+
246+
ASSERT_EQ(vm.registerCount(), 1);
247+
ASSERT_EQ(vm.getInput(0, 1)->toDouble(), 10000);
248+
249+
EXPECT_CALL(m_engineMock, mouseX()).WillOnce(Return(168.087));
250+
EXPECT_CALL(m_engineMock, mouseY()).WillOnce(Return(175.908));
251+
vm.setBytecode(bytecode8);
252+
vm.reset();
253+
vm.run();
254+
255+
ASSERT_EQ(vm.registerCount(), 1);
256+
ASSERT_EQ(std::round(vm.getInput(0, 1)->toDouble() * 10000) / 10000, 261.0096);
257+
}
258+
77259
TEST_F(SensingBlocksTest, Timer)
78260
{
79261
Compiler compiler(&m_engineMock);

0 commit comments

Comments
 (0)