Skip to content

Commit 1cd4016

Browse files
committed
Implement control_create_clone_of
1 parent 6482b86 commit 1cd4016

File tree

4 files changed

+351
-0
lines changed

4 files changed

+351
-0
lines changed

src/dev/blocks/controlblocks.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <scratchcpp/thread.h>
1111
#include <scratchcpp/istacktimer.h>
1212
#include <scratchcpp/variable.h>
13+
#include <scratchcpp/sprite.h>
1314

1415
#include "controlblocks.h"
1516

@@ -38,6 +39,7 @@ void ControlBlocks::registerBlocks(IEngine *engine)
3839
engine->addCompileFunction(this, "control_while", &compileWhile);
3940
engine->addCompileFunction(this, "control_for_each", &compileForEach);
4041
engine->addCompileFunction(this, "control_start_as_clone", &compileStartAsClone);
42+
engine->addCompileFunction(this, "control_create_clone_of", &compileCreateCloneOf);
4143
}
4244

4345
CompilerValue *ControlBlocks::compileForever(Compiler *compiler)
@@ -143,6 +145,28 @@ CompilerValue *ControlBlocks::compileStartAsClone(Compiler *compiler)
143145
return nullptr;
144146
}
145147

148+
CompilerValue *ControlBlocks::compileCreateCloneOf(Compiler *compiler)
149+
{
150+
Input *input = compiler->input("CLONE_OPTION");
151+
152+
if (input->pointsToDropdownMenu()) {
153+
std::string spriteName = input->selectedMenuItem();
154+
155+
if (spriteName == "_myself_")
156+
compiler->addTargetFunctionCall("control_create_clone_of_myself");
157+
else {
158+
auto index = compiler->engine()->findTarget(spriteName);
159+
CompilerValue *arg = compiler->addConstValue(index);
160+
compiler->addFunctionCallWithCtx("control_create_clone_by_index", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { arg });
161+
}
162+
} else {
163+
CompilerValue *arg = compiler->addInput("CLONE_OPTION");
164+
compiler->addFunctionCallWithCtx("control_create_clone", Compiler::StaticType::Void, { Compiler::StaticType::String }, { arg });
165+
}
166+
167+
return nullptr;
168+
}
169+
146170
extern "C" void control_stop_all(ExecutionContext *ctx)
147171
{
148172
ctx->engine()->stop();
@@ -164,3 +188,31 @@ extern "C" bool control_stack_timer_elapsed(ExecutionContext *ctx)
164188
{
165189
return ctx->stackTimer()->elapsed();
166190
}
191+
192+
extern "C" void control_create_clone_of_myself(Target *target)
193+
{
194+
if (!target->isStage())
195+
static_cast<Sprite *>(target)->clone();
196+
}
197+
198+
extern "C" void control_create_clone_by_index(ExecutionContext *ctx, double index)
199+
{
200+
Target *target = ctx->engine()->targetAt(index);
201+
202+
if (!target->isStage())
203+
static_cast<Sprite *>(target)->clone();
204+
}
205+
206+
extern "C" void control_create_clone(ExecutionContext *ctx, const char *spriteName)
207+
{
208+
if (strcmp(spriteName, "_myself_") == 0)
209+
control_create_clone_of_myself(ctx->thread()->target());
210+
else {
211+
IEngine *engine = ctx->engine();
212+
auto index = engine->findTarget(spriteName);
213+
Target *target = engine->targetAt(index);
214+
215+
if (!target->isStage())
216+
static_cast<Sprite *>(target)->clone();
217+
}
218+
}

src/dev/blocks/controlblocks.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class ControlBlocks : public IExtension
2727
static CompilerValue *compileWhile(Compiler *compiler);
2828
static CompilerValue *compileForEach(Compiler *compiler);
2929
static CompilerValue *compileStartAsClone(Compiler *compiler);
30+
static CompilerValue *compileCreateCloneOf(Compiler *compiler);
3031
};
3132

3233
} // namespace libscratchcpp

test/dev/blocks/control_blocks_test.cpp

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <scratchcpp/dev/compiler.h>
33
#include <scratchcpp/dev/test/scriptbuilder.h>
44
#include <scratchcpp/sprite.h>
5+
#include <scratchcpp/stage.h>
56
#include <scratchcpp/block.h>
67
#include <scratchcpp/input.h>
78
#include <scratchcpp/field.h>
@@ -22,6 +23,8 @@ using namespace libscratchcpp;
2223
using namespace libscratchcpp::test;
2324

2425
using ::testing::Return;
26+
using ::testing::SaveArg;
27+
using ::testing::_;
2528

2629
class ControlBlocksTest : public testing::Test
2730
{
@@ -767,3 +770,296 @@ TEST_F(ControlBlocksTest, StartAsClone)
767770
EXPECT_CALL(m_engineMock, addCloneInitScript(block));
768771
compiler.compile(block);
769772
}
773+
774+
TEST_F(ControlBlocksTest, CreateCloneOfSprite)
775+
{
776+
EXPECT_CALL(m_engineMock, cloneLimit()).WillRepeatedly(Return(-1));
777+
EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return());
778+
auto target = std::make_shared<Sprite>();
779+
target->setEngine(&m_engineMock);
780+
781+
// create clone of [Sprite1]
782+
{
783+
ScriptBuilder builder(m_extension.get(), m_engine, target);
784+
785+
builder.addBlock("control_create_clone_of");
786+
builder.addDropdownInput("CLONE_OPTION", "Sprite1");
787+
auto block = builder.currentBlock();
788+
789+
EXPECT_CALL(m_engineMock, findTarget("Sprite1")).WillOnce(Return(4));
790+
Compiler compiler(&m_engineMock, target.get());
791+
auto code = compiler.compile(block);
792+
Script script(target.get(), block, &m_engineMock);
793+
script.setCode(code);
794+
Thread thread(target.get(), &m_engineMock, &script);
795+
796+
Sprite sprite;
797+
sprite.setEngine(&m_engineMock);
798+
std::shared_ptr<Sprite> clone;
799+
EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&sprite));
800+
EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone));
801+
EXPECT_CALL(m_engineMock, moveDrawableBehindOther(_, &sprite));
802+
thread.run();
803+
ASSERT_TRUE(clone);
804+
ASSERT_EQ(clone->cloneSprite(), &sprite);
805+
}
806+
807+
m_engine->clear();
808+
target = std::make_shared<Sprite>();
809+
target->setEngine(&m_engineMock);
810+
811+
// create clone of [myself]
812+
{
813+
ScriptBuilder builder(m_extension.get(), m_engine, target);
814+
815+
builder.addBlock("control_create_clone_of");
816+
builder.addDropdownInput("CLONE_OPTION", "_myself_");
817+
auto block = builder.currentBlock();
818+
819+
EXPECT_CALL(m_engineMock, findTarget).Times(0);
820+
Compiler compiler(&m_engineMock, target.get());
821+
auto code = compiler.compile(block);
822+
Script script(target.get(), block, &m_engineMock);
823+
script.setCode(code);
824+
Thread thread(target.get(), &m_engineMock, &script);
825+
826+
std::shared_ptr<Sprite> clone;
827+
EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone));
828+
EXPECT_CALL(m_engineMock, moveDrawableBehindOther(_, target.get()));
829+
thread.run();
830+
ASSERT_TRUE(clone);
831+
ASSERT_EQ(clone->cloneSprite(), target.get());
832+
}
833+
834+
m_engine->clear();
835+
target = std::make_shared<Sprite>();
836+
target->setEngine(&m_engineMock);
837+
838+
// create clone of ["_mYself_"]
839+
{
840+
ScriptBuilder builder(m_extension.get(), m_engine, target);
841+
842+
builder.addBlock("control_create_clone_of");
843+
builder.addDropdownInput("CLONE_OPTION", "_mYself_");
844+
auto block = builder.currentBlock();
845+
846+
EXPECT_CALL(m_engineMock, findTarget("_mYself_")).WillOnce(Return(4));
847+
Compiler compiler(&m_engineMock, target.get());
848+
auto code = compiler.compile(block);
849+
Script script(target.get(), block, &m_engineMock);
850+
script.setCode(code);
851+
Thread thread(target.get(), &m_engineMock, &script);
852+
853+
Sprite sprite;
854+
sprite.setEngine(&m_engineMock);
855+
std::shared_ptr<Sprite> clone;
856+
EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&sprite));
857+
EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone));
858+
EXPECT_CALL(m_engineMock, moveDrawableBehindOther(_, &sprite));
859+
thread.run();
860+
ASSERT_TRUE(clone);
861+
ASSERT_EQ(clone->cloneSprite(), &sprite);
862+
}
863+
864+
m_engine->clear();
865+
target = std::make_shared<Sprite>();
866+
target->setEngine(&m_engineMock);
867+
868+
// create clone of (null block)
869+
{
870+
ScriptBuilder builder(m_extension.get(), m_engine, target);
871+
872+
builder.addBlock("control_create_clone_of");
873+
builder.addNullObscuredInput("CLONE_OPTION");
874+
auto block = builder.currentBlock();
875+
876+
EXPECT_CALL(m_engineMock, findTarget).Times(0);
877+
Compiler compiler(&m_engineMock, target.get());
878+
auto code = compiler.compile(block);
879+
Script script(target.get(), block, &m_engineMock);
880+
script.setCode(code);
881+
Thread thread(target.get(), &m_engineMock, &script);
882+
883+
Sprite sprite;
884+
sprite.setEngine(&m_engineMock);
885+
std::shared_ptr<Sprite> clone;
886+
EXPECT_CALL(m_engineMock, findTarget("0")).WillOnce(Return(2));
887+
EXPECT_CALL(m_engineMock, targetAt(2)).WillOnce(Return(&sprite));
888+
EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone));
889+
EXPECT_CALL(m_engineMock, moveDrawableBehindOther(_, &sprite));
890+
thread.run();
891+
ASSERT_TRUE(clone);
892+
ASSERT_EQ(clone->cloneSprite(), &sprite);
893+
}
894+
895+
m_engine->clear();
896+
target = std::make_shared<Sprite>();
897+
target->setEngine(&m_engineMock);
898+
899+
// create clone of ("_myself_")
900+
{
901+
ScriptBuilder builder(m_extension.get(), m_engine, target);
902+
903+
builder.addBlock("control_create_clone_of");
904+
auto valueBlock = std::make_shared<Block>("", "test_input");
905+
auto input = std::make_shared<Input>("INPUT", Input::Type::Shadow);
906+
input->setPrimaryValue("_myself_");
907+
valueBlock->addInput(input);
908+
builder.addObscuredInput("CLONE_OPTION", valueBlock);
909+
auto block = builder.currentBlock();
910+
911+
EXPECT_CALL(m_engineMock, findTarget).Times(0);
912+
Compiler compiler(&m_engineMock, target.get());
913+
auto code = compiler.compile(block);
914+
Script script(target.get(), block, &m_engineMock);
915+
script.setCode(code);
916+
Thread thread(target.get(), &m_engineMock, &script);
917+
918+
std::shared_ptr<Sprite> clone;
919+
EXPECT_CALL(m_engineMock, findTarget).Times(0);
920+
EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone));
921+
EXPECT_CALL(m_engineMock, moveDrawableBehindOther(_, target.get()));
922+
thread.run();
923+
ASSERT_TRUE(clone);
924+
ASSERT_EQ(clone->cloneSprite(), target.get());
925+
}
926+
927+
// create clone of ("_mYself_")
928+
{
929+
ScriptBuilder builder(m_extension.get(), m_engine, target);
930+
931+
builder.addBlock("control_create_clone_of");
932+
auto valueBlock = std::make_shared<Block>("", "test_input");
933+
auto input = std::make_shared<Input>("INPUT", Input::Type::Shadow);
934+
input->setPrimaryValue("_mYself_");
935+
valueBlock->addInput(input);
936+
builder.addObscuredInput("CLONE_OPTION", valueBlock);
937+
auto block = builder.currentBlock();
938+
939+
EXPECT_CALL(m_engineMock, findTarget).Times(0);
940+
Compiler compiler(&m_engineMock, target.get());
941+
auto code = compiler.compile(block);
942+
Script script(target.get(), block, &m_engineMock);
943+
script.setCode(code);
944+
Thread thread(target.get(), &m_engineMock, &script);
945+
946+
Sprite sprite;
947+
sprite.setEngine(&m_engineMock);
948+
std::shared_ptr<Sprite> clone;
949+
EXPECT_CALL(m_engineMock, findTarget("_mYself_")).WillOnce(Return(2));
950+
EXPECT_CALL(m_engineMock, targetAt(2)).WillOnce(Return(&sprite));
951+
EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone));
952+
EXPECT_CALL(m_engineMock, moveDrawableBehindOther(_, &sprite));
953+
thread.run();
954+
ASSERT_TRUE(clone);
955+
ASSERT_EQ(clone->cloneSprite(), &sprite);
956+
}
957+
}
958+
959+
TEST_F(ControlBlocksTest, CreateCloneOfStage)
960+
{
961+
EXPECT_CALL(m_engineMock, cloneLimit()).WillRepeatedly(Return(-1));
962+
EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return());
963+
auto target = std::make_shared<Stage>();
964+
target->setEngine(&m_engineMock);
965+
966+
// create clone of [Stage]
967+
{
968+
ScriptBuilder builder(m_extension.get(), m_engine, target);
969+
970+
builder.addBlock("control_create_clone_of");
971+
builder.addDropdownInput("CLONE_OPTION", "_stage_");
972+
auto block = builder.currentBlock();
973+
974+
EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillOnce(Return(8));
975+
Compiler compiler(&m_engineMock, target.get());
976+
auto code = compiler.compile(block);
977+
Script script(target.get(), block, &m_engineMock);
978+
script.setCode(code);
979+
Thread thread(target.get(), &m_engineMock, &script);
980+
981+
Stage stage;
982+
stage.setEngine(&m_engineMock);
983+
EXPECT_CALL(m_engineMock, targetAt(8)).WillOnce(Return(&stage));
984+
EXPECT_CALL(m_engineMock, initClone).Times(0);
985+
thread.run();
986+
}
987+
988+
m_engine->clear();
989+
target = std::make_shared<Stage>();
990+
target->setEngine(&m_engineMock);
991+
992+
// create clone of [myself]
993+
{
994+
ScriptBuilder builder(m_extension.get(), m_engine, target);
995+
996+
builder.addBlock("control_create_clone_of");
997+
builder.addDropdownInput("CLONE_OPTION", "_myself_");
998+
auto block = builder.currentBlock();
999+
1000+
EXPECT_CALL(m_engineMock, findTarget).Times(0);
1001+
Compiler compiler(&m_engineMock, target.get());
1002+
auto code = compiler.compile(block);
1003+
Script script(target.get(), block, &m_engineMock);
1004+
script.setCode(code);
1005+
Thread thread(target.get(), &m_engineMock, &script);
1006+
1007+
EXPECT_CALL(m_engineMock, initClone).Times(0);
1008+
thread.run();
1009+
}
1010+
1011+
m_engine->clear();
1012+
target = std::make_shared<Stage>();
1013+
target->setEngine(&m_engineMock);
1014+
1015+
// create clone of (null block)
1016+
{
1017+
ScriptBuilder builder(m_extension.get(), m_engine, target);
1018+
1019+
builder.addBlock("control_create_clone_of");
1020+
builder.addNullObscuredInput("CLONE_OPTION");
1021+
auto block = builder.currentBlock();
1022+
1023+
EXPECT_CALL(m_engineMock, findTarget).Times(0);
1024+
Compiler compiler(&m_engineMock, target.get());
1025+
auto code = compiler.compile(block);
1026+
Script script(target.get(), block, &m_engineMock);
1027+
script.setCode(code);
1028+
Thread thread(target.get(), &m_engineMock, &script);
1029+
1030+
Stage stage;
1031+
stage.setEngine(&m_engineMock);
1032+
EXPECT_CALL(m_engineMock, findTarget("0")).WillOnce(Return(2));
1033+
EXPECT_CALL(m_engineMock, targetAt(2)).WillOnce(Return(&stage));
1034+
EXPECT_CALL(m_engineMock, initClone).Times(0);
1035+
thread.run();
1036+
}
1037+
1038+
m_engine->clear();
1039+
target = std::make_shared<Stage>();
1040+
target->setEngine(&m_engineMock);
1041+
1042+
// create clone of ("_myself_")
1043+
{
1044+
ScriptBuilder builder(m_extension.get(), m_engine, target);
1045+
1046+
builder.addBlock("control_create_clone_of");
1047+
auto valueBlock = std::make_shared<Block>("", "test_input");
1048+
auto input = std::make_shared<Input>("INPUT", Input::Type::Shadow);
1049+
input->setPrimaryValue("_myself_");
1050+
valueBlock->addInput(input);
1051+
builder.addObscuredInput("CLONE_OPTION", valueBlock);
1052+
auto block = builder.currentBlock();
1053+
1054+
EXPECT_CALL(m_engineMock, findTarget).Times(0);
1055+
Compiler compiler(&m_engineMock, target.get());
1056+
auto code = compiler.compile(block);
1057+
Script script(target.get(), block, &m_engineMock);
1058+
script.setCode(code);
1059+
Thread thread(target.get(), &m_engineMock, &script);
1060+
1061+
EXPECT_CALL(m_engineMock, findTarget).Times(0);
1062+
EXPECT_CALL(m_engineMock, initClone).Times(0);
1063+
thread.run();
1064+
}
1065+
}

0 commit comments

Comments
 (0)