Skip to content

Commit a587483

Browse files
committed
Implement sensing_distanceto block
1 parent 0d6fd71 commit a587483

File tree

3 files changed

+332
-0
lines changed

3 files changed

+332
-0
lines changed

src/blocks/sensingblocks.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ void SensingBlocks::registerBlocks(IEngine *engine)
3434
engine->addCompileFunction(this, "sensing_touchingobject", &compileTouchingObject);
3535
engine->addCompileFunction(this, "sensing_touchingcolor", &compileTouchingColor);
3636
engine->addCompileFunction(this, "sensing_coloristouchingcolor", &compileColorIsTouchingColor);
37+
engine->addCompileFunction(this, "sensing_distanceto", &compileDistanceTo);
3738
}
3839

3940
CompilerValue *SensingBlocks::compileTouchingObject(Compiler *compiler)
@@ -77,6 +78,35 @@ CompilerValue *SensingBlocks::compileColorIsTouchingColor(Compiler *compiler)
7778
return compiler->addTargetFunctionCall("sensing_coloristouchingcolor", Compiler::StaticType::Bool, { Compiler::StaticType::Unknown, Compiler::StaticType::Unknown }, { color, color2 });
7879
}
7980

81+
CompilerValue *SensingBlocks::compileDistanceTo(Compiler *compiler)
82+
{
83+
if (compiler->target()->isStage())
84+
return compiler->addConstValue(10000.0);
85+
86+
IEngine *engine = compiler->engine();
87+
Input *input = compiler->input("DISTANCETOMENU");
88+
89+
if (input->pointsToDropdownMenu()) {
90+
std::string value = input->selectedMenuItem();
91+
92+
if (value == "_mouse_")
93+
return compiler->addTargetFunctionCall("sensing_distance_to_mouse", Compiler::StaticType::Number);
94+
else if (value != "_stage_") {
95+
Target *target = engine->targetAt(engine->findTarget(value));
96+
97+
if (target) {
98+
CompilerValue *targetPtr = compiler->addConstValue(target);
99+
return compiler->addTargetFunctionCall("sensing_distance_to_sprite", Compiler::StaticType::Number, { Compiler::StaticType::Pointer }, { targetPtr });
100+
}
101+
}
102+
} else {
103+
CompilerValue *object = compiler->addInput(input);
104+
return compiler->addTargetFunctionCall("sensing_distanceto", Compiler::StaticType::Number, { Compiler::StaticType::String }, { object });
105+
}
106+
107+
return compiler->addConstValue(10000.0);
108+
}
109+
80110
extern "C" bool sensing_touching_mouse(Target *target)
81111
{
82112
IEngine *engine = target->engine();
@@ -125,3 +155,39 @@ extern "C" bool sensing_coloristouchingcolor(Target *target, const ValueData *co
125155
{
126156
return target->touchingColor(value_toRgba(color), value_toRgba(color2));
127157
}
158+
159+
static inline double sensing_distance(double x0, double y0, double x1, double y1)
160+
{
161+
return std::sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0));
162+
}
163+
164+
extern "C" double sensing_distance_to_mouse(Sprite *sprite)
165+
{
166+
IEngine *engine = sprite->engine();
167+
return sensing_distance(sprite->x(), sprite->y(), engine->mouseX(), engine->mouseY());
168+
}
169+
170+
extern "C" double sensing_distance_to_sprite(Sprite *sprite, Sprite *targetSprite)
171+
{
172+
return sensing_distance(sprite->x(), sprite->y(), targetSprite->x(), targetSprite->y());
173+
}
174+
175+
extern "C" double sensing_distanceto(Sprite *sprite, const StringPtr *object)
176+
{
177+
static const StringPtr MOUSE_STR("_mouse_");
178+
static const StringPtr STAGE_STR("_stage_");
179+
180+
if (string_compare_case_sensitive(object, &MOUSE_STR) == 0)
181+
return sensing_distance_to_mouse(sprite);
182+
else if (string_compare_case_sensitive(object, &STAGE_STR) != 0) {
183+
IEngine *engine = sprite->engine();
184+
// TODO: Use UTF-16 in engine
185+
std::string u8name = utf8::utf16to8(std::u16string(object->data));
186+
Target *objTarget = engine->targetAt(engine->findTarget(u8name));
187+
188+
if (objTarget)
189+
return sensing_distance_to_sprite(sprite, static_cast<Sprite *>(objTarget));
190+
}
191+
192+
return 10000.0;
193+
}

src/blocks/sensingblocks.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class SensingBlocks : public IExtension
2020
static CompilerValue *compileTouchingObject(Compiler *compiler);
2121
static CompilerValue *compileTouchingColor(Compiler *compiler);
2222
static CompilerValue *compileColorIsTouchingColor(Compiler *compiler);
23+
static CompilerValue *compileDistanceTo(Compiler *compiler);
2324
};
2425

2526
} // namespace libscratchcpp

test/blocks/sensing_blocks_test.cpp

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,3 +477,268 @@ TEST_F(SensingBlocksTest, ColorIsTouchingColor_NumberString)
477477
ASSERT_FALSE(value_toBool(&value));
478478
value_free(&value);
479479
}
480+
481+
TEST_F(SensingBlocksTest, DistanceTo_Sprite_CompileTime)
482+
{
483+
auto sprite = std::make_shared<Sprite>();
484+
sprite->setEngine(&m_engineMock);
485+
486+
Sprite targetSprite;
487+
488+
ScriptBuilder builder(m_extension.get(), m_engine, sprite);
489+
builder.addBlock("sensing_distanceto");
490+
builder.addDropdownInput("DISTANCETOMENU", "Sprite2");
491+
Block *block = builder.currentBlock();
492+
493+
EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(3));
494+
EXPECT_CALL(m_engineMock, targetAt(3)).WillOnce(Return(&targetSprite));
495+
Compiler compiler(&m_engineMock, sprite.get());
496+
auto code = compiler.compile(block, Compiler::CodeType::Reporter);
497+
Script script(sprite.get(), block, &m_engineMock);
498+
script.setCode(code);
499+
Thread thread(sprite.get(), &m_engineMock, &script);
500+
501+
sprite->setX(-50.35);
502+
sprite->setY(33.04);
503+
504+
targetSprite.setX(108.564);
505+
targetSprite.setY(-168.452);
506+
507+
ValueData value = thread.runReporter();
508+
ASSERT_EQ(std::round(value_toDouble(&value) * 10000) / 10000, 256.6178);
509+
value_free(&value);
510+
}
511+
512+
TEST_F(SensingBlocksTest, DistanceTo_Sprite_Runtime)
513+
{
514+
auto sprite = std::make_shared<Sprite>();
515+
sprite->setEngine(&m_engineMock);
516+
517+
Sprite targetSprite;
518+
519+
ScriptBuilder builder(m_extension.get(), m_engine, sprite);
520+
521+
builder.addBlock("test_const_string");
522+
builder.addValueInput("STRING", "Sprite2");
523+
auto valueBlock = builder.takeBlock();
524+
525+
builder.addBlock("sensing_distanceto");
526+
builder.addObscuredInput("DISTANCETOMENU", valueBlock);
527+
Block *block = builder.currentBlock();
528+
529+
Compiler compiler(&m_engineMock, sprite.get());
530+
auto code = compiler.compile(block, Compiler::CodeType::Reporter);
531+
Script script(sprite.get(), block, &m_engineMock);
532+
script.setCode(code);
533+
Thread thread(sprite.get(), &m_engineMock, &script);
534+
535+
sprite->setX(-50.35);
536+
sprite->setY(33.04);
537+
538+
targetSprite.setX(108.564);
539+
targetSprite.setY(-168.452);
540+
541+
EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillRepeatedly(Return(5));
542+
EXPECT_CALL(m_engineMock, targetAt(5)).WillRepeatedly(Return(&targetSprite));
543+
544+
ValueData value = thread.runReporter();
545+
ASSERT_EQ(std::round(value_toDouble(&value) * 10000) / 10000, 256.6178);
546+
value_free(&value);
547+
}
548+
549+
TEST_F(SensingBlocksTest, DistanceTo_Sprite_FromStage)
550+
{
551+
auto stage = std::make_shared<Stage>();
552+
stage->setEngine(&m_engineMock);
553+
554+
Sprite targetSprite;
555+
556+
ScriptBuilder builder(m_extension.get(), m_engine, stage);
557+
builder.addBlock("sensing_distanceto");
558+
builder.addDropdownInput("DISTANCETOMENU", "Sprite2");
559+
Block *block = builder.currentBlock();
560+
561+
EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillRepeatedly(Return(3));
562+
EXPECT_CALL(m_engineMock, targetAt(3)).WillRepeatedly(Return(&targetSprite));
563+
Compiler compiler(&m_engineMock, stage.get());
564+
auto code = compiler.compile(block, Compiler::CodeType::Reporter);
565+
Script script(stage.get(), block, &m_engineMock);
566+
script.setCode(code);
567+
Thread thread(stage.get(), &m_engineMock, &script);
568+
569+
targetSprite.setX(108.564);
570+
targetSprite.setY(-168.452);
571+
572+
ValueData value = thread.runReporter();
573+
ASSERT_EQ(value_toDouble(&value), 10000.0);
574+
value_free(&value);
575+
}
576+
577+
TEST_F(SensingBlocksTest, DistanceTo_Stage_CompileTime)
578+
{
579+
auto sprite = std::make_shared<Sprite>();
580+
sprite->setEngine(&m_engineMock);
581+
582+
Stage stage;
583+
584+
ScriptBuilder builder(m_extension.get(), m_engine, sprite);
585+
builder.addBlock("sensing_distanceto");
586+
builder.addDropdownInput("DISTANCETOMENU", "_stage_");
587+
Block *block = builder.currentBlock();
588+
589+
EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillRepeatedly(Return(3));
590+
EXPECT_CALL(m_engineMock, targetAt(3)).WillRepeatedly(Return(&stage));
591+
Compiler compiler(&m_engineMock, sprite.get());
592+
auto code = compiler.compile(block, Compiler::CodeType::Reporter);
593+
Script script(sprite.get(), block, &m_engineMock);
594+
script.setCode(code);
595+
Thread thread(sprite.get(), &m_engineMock, &script);
596+
597+
ValueData value = thread.runReporter();
598+
ASSERT_EQ(value_toDouble(&value), 10000.0);
599+
value_free(&value);
600+
}
601+
602+
TEST_F(SensingBlocksTest, DistanceTo_Stage_Runtime)
603+
{
604+
auto sprite = std::make_shared<Sprite>();
605+
sprite->setEngine(&m_engineMock);
606+
607+
Stage stage;
608+
609+
ScriptBuilder builder(m_extension.get(), m_engine, sprite);
610+
611+
builder.addBlock("test_const_string");
612+
builder.addValueInput("STRING", "_stage_");
613+
auto valueBlock = builder.takeBlock();
614+
615+
builder.addBlock("sensing_distanceto");
616+
builder.addObscuredInput("DISTANCETOMENU", valueBlock);
617+
Block *block = builder.currentBlock();
618+
619+
Compiler compiler(&m_engineMock, sprite.get());
620+
auto code = compiler.compile(block, Compiler::CodeType::Reporter);
621+
Script script(sprite.get(), block, &m_engineMock);
622+
script.setCode(code);
623+
Thread thread(sprite.get(), &m_engineMock, &script);
624+
625+
EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillRepeatedly(Return(5));
626+
EXPECT_CALL(m_engineMock, targetAt(5)).WillRepeatedly(Return(&stage));
627+
628+
ValueData value = thread.runReporter();
629+
ASSERT_EQ(value_toDouble(&value), 10000.0);
630+
value_free(&value);
631+
}
632+
633+
TEST_F(SensingBlocksTest, DistanceTo_Mouse_CompileTime)
634+
{
635+
auto sprite = std::make_shared<Sprite>();
636+
sprite->setEngine(&m_engineMock);
637+
638+
ScriptBuilder builder(m_extension.get(), m_engine, sprite);
639+
builder.addBlock("sensing_distanceto");
640+
builder.addDropdownInput("DISTANCETOMENU", "_mouse_");
641+
Block *block = builder.currentBlock();
642+
643+
EXPECT_CALL(m_engineMock, findTarget).Times(0);
644+
EXPECT_CALL(m_engineMock, targetAt).Times(0);
645+
Compiler compiler(&m_engineMock, sprite.get());
646+
auto code = compiler.compile(block, Compiler::CodeType::Reporter);
647+
Script script(sprite.get(), block, &m_engineMock);
648+
script.setCode(code);
649+
Thread thread(sprite.get(), &m_engineMock, &script);
650+
651+
sprite->setX(-50.35);
652+
sprite->setY(33.04);
653+
654+
EXPECT_CALL(m_engineMock, mouseX).WillOnce(Return(-239.98));
655+
EXPECT_CALL(m_engineMock, mouseY).WillOnce(Return(-86.188));
656+
ValueData value = thread.runReporter();
657+
ASSERT_EQ(std::round(value_toDouble(&value) * 10000) / 10000, 223.9974);
658+
value_free(&value);
659+
}
660+
661+
TEST_F(SensingBlocksTest, DistanceTo_Mouse_Runtime)
662+
{
663+
auto sprite = std::make_shared<Sprite>();
664+
sprite->setEngine(&m_engineMock);
665+
666+
ScriptBuilder builder(m_extension.get(), m_engine, sprite);
667+
668+
builder.addBlock("test_const_string");
669+
builder.addValueInput("STRING", "_mouse_");
670+
auto valueBlock = builder.takeBlock();
671+
672+
builder.addBlock("sensing_distanceto");
673+
builder.addObscuredInput("DISTANCETOMENU", valueBlock);
674+
Block *block = builder.currentBlock();
675+
676+
Compiler compiler(&m_engineMock, sprite.get());
677+
auto code = compiler.compile(block, Compiler::CodeType::Reporter);
678+
Script script(sprite.get(), block, &m_engineMock);
679+
script.setCode(code);
680+
Thread thread(sprite.get(), &m_engineMock, &script);
681+
682+
EXPECT_CALL(m_engineMock, findTarget).Times(0);
683+
EXPECT_CALL(m_engineMock, targetAt).Times(0);
684+
685+
sprite->setX(-50.35);
686+
sprite->setY(33.04);
687+
688+
EXPECT_CALL(m_engineMock, mouseX).WillOnce(Return(-239.98));
689+
EXPECT_CALL(m_engineMock, mouseY).WillOnce(Return(-86.188));
690+
ValueData value = thread.runReporter();
691+
ASSERT_EQ(std::round(value_toDouble(&value) * 10000) / 10000, 223.9974);
692+
value_free(&value);
693+
}
694+
695+
TEST_F(SensingBlocksTest, DistanceTo_Invalid_CompileTime)
696+
{
697+
auto sprite = std::make_shared<Sprite>();
698+
sprite->setEngine(&m_engineMock);
699+
700+
ScriptBuilder builder(m_extension.get(), m_engine, sprite);
701+
builder.addBlock("sensing_distanceto");
702+
builder.addDropdownInput("DISTANCETOMENU", "test");
703+
Block *block = builder.currentBlock();
704+
705+
EXPECT_CALL(m_engineMock, findTarget("test")).WillOnce(Return(-1));
706+
EXPECT_CALL(m_engineMock, targetAt).WillRepeatedly(Return(nullptr));
707+
Compiler compiler(&m_engineMock, sprite.get());
708+
auto code = compiler.compile(block, Compiler::CodeType::Reporter);
709+
Script script(sprite.get(), block, &m_engineMock);
710+
script.setCode(code);
711+
Thread thread(sprite.get(), &m_engineMock, &script);
712+
713+
ValueData value = thread.runReporter();
714+
ASSERT_EQ(value_toDouble(&value), 10000.0);
715+
value_free(&value);
716+
}
717+
718+
TEST_F(SensingBlocksTest, DistanceTo_Invalid_Runtime)
719+
{
720+
auto sprite = std::make_shared<Sprite>();
721+
sprite->setEngine(&m_engineMock);
722+
723+
ScriptBuilder builder(m_extension.get(), m_engine, sprite);
724+
725+
builder.addBlock("test_const_string");
726+
builder.addValueInput("STRING", "test");
727+
auto valueBlock = builder.takeBlock();
728+
729+
builder.addBlock("sensing_distanceto");
730+
builder.addObscuredInput("DISTANCETOMENU", valueBlock);
731+
Block *block = builder.currentBlock();
732+
733+
Compiler compiler(&m_engineMock, sprite.get());
734+
auto code = compiler.compile(block, Compiler::CodeType::Reporter);
735+
Script script(sprite.get(), block, &m_engineMock);
736+
script.setCode(code);
737+
Thread thread(sprite.get(), &m_engineMock, &script);
738+
739+
EXPECT_CALL(m_engineMock, findTarget("test")).WillRepeatedly(Return(-1));
740+
EXPECT_CALL(m_engineMock, targetAt).WillRepeatedly(Return(nullptr));
741+
ValueData value = thread.runReporter();
742+
ASSERT_EQ(value_toDouble(&value), 10000.0);
743+
value_free(&value);
744+
}

0 commit comments

Comments
 (0)