Skip to content

Commit 1e656e2

Browse files
committed
Implement control_create_clone_of block
1 parent 1405c37 commit 1e656e2

File tree

3 files changed

+188
-2
lines changed

3 files changed

+188
-2
lines changed

src/blocks/controlblocks.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
#include <scratchcpp/iengine.h>
44
#include <scratchcpp/compiler.h>
5+
#include <scratchcpp/sprite.h>
6+
#include <scratchcpp/input.h>
57
#include <scratchcpp/field.h>
8+
#include <scratchcpp/block.h>
69
#include <cassert>
710

811
#include "controlblocks.h"
@@ -27,6 +30,7 @@ void ControlBlocks::registerBlocks(IEngine *engine)
2730
engine->addCompileFunction(this, "control_stop", &compileStop);
2831
engine->addCompileFunction(this, "control_wait", &compileWait);
2932
engine->addCompileFunction(this, "control_wait_until", &compileWaitUntil);
33+
engine->addCompileFunction(this, "control_create_clone_of", &compileCreateClone);
3034

3135
// Inputs
3236
engine->addInput(this, "SUBSTACK", SUBSTACK);
@@ -35,6 +39,7 @@ void ControlBlocks::registerBlocks(IEngine *engine)
3539
engine->addInput(this, "CONDITION", CONDITION);
3640
engine->addInput(this, "DURATION", DURATION);
3741
engine->addInput(this, "VALUE", VALUE);
42+
engine->addInput(this, "CLONE_OPTION", CLONE_OPTION);
3843

3944
// Fields
4045
engine->addField(this, "STOP_OPTION", STOP_OPTION);
@@ -155,6 +160,27 @@ void ControlBlocks::compileWaitUntil(Compiler *compiler)
155160
compiler->addFunctionCall(&waitUntil);
156161
}
157162

163+
void ControlBlocks::compileCreateClone(Compiler *compiler)
164+
{
165+
Input *input = compiler->input(CLONE_OPTION);
166+
167+
if (input->type() != Input::Type::ObscuredShadow) {
168+
assert(input->pointsToDropdownMenu());
169+
std::string spriteName = input->selectedMenuItem();
170+
171+
if (spriteName == "_myself_")
172+
compiler->addFunctionCall(&createCloneOfMyself);
173+
else {
174+
int index = compiler->engine()->findTarget(spriteName);
175+
compiler->addConstValue(index);
176+
compiler->addFunctionCall(&createCloneByIndex);
177+
}
178+
} else {
179+
compiler->addInput(input);
180+
compiler->addFunctionCall(&createClone);
181+
}
182+
}
183+
158184
unsigned int ControlBlocks::stopAll(VirtualMachine *vm)
159185
{
160186
vm->engine()->stop();
@@ -196,3 +222,42 @@ unsigned int ControlBlocks::waitUntil(VirtualMachine *vm)
196222
}
197223
return 1;
198224
}
225+
226+
unsigned int ControlBlocks::createClone(VirtualMachine *vm)
227+
{
228+
std::string spriteName = vm->getInput(0, 1)->toString();
229+
Target *target;
230+
231+
if (spriteName == "_myself_")
232+
target = vm->target();
233+
else
234+
target = vm->engine()->targetAt(vm->engine()->findTarget(spriteName));
235+
236+
Sprite *sprite = dynamic_cast<Sprite *>(target);
237+
238+
if (sprite)
239+
sprite->clone();
240+
241+
return 1;
242+
}
243+
244+
unsigned int ControlBlocks::createCloneByIndex(VirtualMachine *vm)
245+
{
246+
Target *target = vm->engine()->targetAt(vm->getInput(0, 1)->toInt());
247+
Sprite *sprite = dynamic_cast<Sprite *>(target);
248+
249+
if (sprite)
250+
sprite->clone();
251+
252+
return 1;
253+
}
254+
255+
unsigned int ControlBlocks::createCloneOfMyself(VirtualMachine *vm)
256+
{
257+
Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
258+
259+
if (sprite)
260+
sprite->clone();
261+
262+
return 0;
263+
}

src/blocks/controlblocks.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ class ControlBlocks : public IBlockSection
2323
TIMES,
2424
CONDITION,
2525
DURATION,
26-
VALUE
26+
VALUE,
27+
CLONE_OPTION
2728
};
2829

2930
enum Fields
@@ -53,12 +54,16 @@ class ControlBlocks : public IBlockSection
5354
static void compileStop(Compiler *compiler);
5455
static void compileWait(Compiler *compiler);
5556
static void compileWaitUntil(Compiler *compiler);
57+
static void compileCreateClone(Compiler *compiler);
5658

5759
static unsigned int stopAll(VirtualMachine *vm);
5860
static unsigned int stopOtherScriptsInSprite(VirtualMachine *vm);
5961
static unsigned int startWait(VirtualMachine *vm);
6062
static unsigned int wait(VirtualMachine *vm);
6163
static unsigned int waitUntil(VirtualMachine *vm);
64+
static unsigned int createClone(VirtualMachine *vm);
65+
static unsigned int createCloneByIndex(VirtualMachine *vm);
66+
static unsigned int createCloneOfMyself(VirtualMachine *vm);
6267

6368
static inline std::unordered_map<VirtualMachine *, std::pair<std::chrono::steady_clock::time_point, int>> m_timeMap;
6469
};

test/blocks/control_blocks_test.cpp

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
#include <scratchcpp/input.h>
44
#include <scratchcpp/field.h>
55
#include <scratchcpp/variable.h>
6+
#include <scratchcpp/sprite.h>
67
#include <enginemock.h>
78

89
#include "../common.h"
910
#include "blocks/controlblocks.h"
11+
#include "blocks/operatorblocks.h"
1012
#include "engine/internal/engine.h"
1113

1214
using namespace libscratchcpp;
@@ -25,6 +27,23 @@ class ControlBlocksTest : public testing::Test
2527
// For any control block
2628
std::shared_ptr<Block> createControlBlock(const std::string &id, const std::string &opcode) const { return std::make_shared<Block>(id, opcode); }
2729

30+
// For control_create_clone_of
31+
std::shared_ptr<Block> createCloneBlock(const std::string &id, const std::string &spriteName, std::shared_ptr<Block> valueBlock = nullptr)
32+
{
33+
auto block = createControlBlock(id, "control_create_clone_of");
34+
35+
if (valueBlock)
36+
addObscuredInput(block, "CLONE_OPTION", ControlBlocks::CLONE_OPTION, valueBlock);
37+
else {
38+
auto input = addNullInput(block, "CLONE_OPTION", ControlBlocks::CLONE_OPTION);
39+
auto menu = createControlBlock(id + "_menu", "control_create_clone_of_menu");
40+
input->setValueBlock(menu);
41+
addDropdownField(menu, "CLONE_OPTION", static_cast<ControlBlocks::Fields>(-1), spriteName, static_cast<ControlBlocks::FieldValues>(-1));
42+
}
43+
44+
return block;
45+
}
46+
2847
void addSubstackInput(std::shared_ptr<Block> block, const std::string &name, ControlBlocks::Inputs id, std::shared_ptr<Block> valueBlock) const
2948
{
3049
auto input = std::make_shared<Input>(name, Input::Type::NoShadow);
@@ -52,12 +71,14 @@ class ControlBlocksTest : public testing::Test
5271
block->updateInputMap();
5372
}
5473

55-
void addNullInput(std::shared_ptr<Block> block, const std::string &name, ControlBlocks::Inputs id) const
74+
std::shared_ptr<Input> addNullInput(std::shared_ptr<Block> block, const std::string &name, ControlBlocks::Inputs id) const
5675
{
5776
auto input = std::make_shared<Input>(name, Input::Type::Shadow);
5877
input->setInputId(id);
5978
block->addInput(input);
6079
block->updateInputMap();
80+
81+
return input;
6182
}
6283

6384
void addDropdownField(std::shared_ptr<Block> block, const std::string &name, ControlBlocks::Fields id, const std::string &value, ControlBlocks::FieldValues valueId) const
@@ -130,6 +151,7 @@ TEST_F(ControlBlocksTest, RegisterBlocks)
130151
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "control_stop", &ControlBlocks::compileStop)).Times(1);
131152
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "control_wait", &ControlBlocks::compileWait)).Times(1);
132153
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "control_wait_until", &ControlBlocks::compileWaitUntil)).Times(1);
154+
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "control_create_clone_of", &ControlBlocks::compileCreateClone)).Times(1);
133155

134156
// Inputs
135157
EXPECT_CALL(m_engineMock, addInput(m_section.get(), "SUBSTACK", ControlBlocks::SUBSTACK));
@@ -138,6 +160,7 @@ TEST_F(ControlBlocksTest, RegisterBlocks)
138160
EXPECT_CALL(m_engineMock, addInput(m_section.get(), "CONDITION", ControlBlocks::CONDITION));
139161
EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DURATION", ControlBlocks::DURATION));
140162
EXPECT_CALL(m_engineMock, addInput(m_section.get(), "VALUE", ControlBlocks::VALUE));
163+
EXPECT_CALL(m_engineMock, addInput(m_section.get(), "CLONE_OPTION", ControlBlocks::CLONE_OPTION));
141164

142165
// Fields
143166
EXPECT_CALL(m_engineMock, addField(m_section.get(), "STOP_OPTION", ControlBlocks::STOP_OPTION));
@@ -810,3 +833,96 @@ TEST_F(ControlBlocksTest, WaitUntilImpl)
810833
ASSERT_EQ(vm.registerCount(), 0);
811834
ASSERT_TRUE(vm.atEnd());
812835
}
836+
837+
TEST_F(ControlBlocksTest, CreateCloneOf)
838+
{
839+
Compiler compiler(&m_engineMock);
840+
841+
// create clone of [Sprite1]
842+
auto block1 = createCloneBlock("a", "Sprite1");
843+
844+
// create clone of [myself]
845+
auto block2 = createCloneBlock("b", "_myself_");
846+
847+
// create clone of (join "" "")
848+
auto joinBlock = std::make_shared<Block>("d", "operator_join");
849+
joinBlock->setCompileFunction(&OperatorBlocks::compileJoin);
850+
auto block3 = createCloneBlock("c", "", joinBlock);
851+
852+
EXPECT_CALL(m_engineMock, findTarget("Sprite1")).WillOnce(Return(4));
853+
EXPECT_CALL(m_engineMock, functionIndex(&ControlBlocks::createCloneByIndex)).WillOnce(Return(0));
854+
EXPECT_CALL(m_engineMock, functionIndex(&ControlBlocks::createCloneOfMyself)).WillOnce(Return(1));
855+
EXPECT_CALL(m_engineMock, functionIndex(&ControlBlocks::createClone)).WillOnce(Return(2));
856+
857+
compiler.init();
858+
compiler.setBlock(block1);
859+
ControlBlocks::compileCreateClone(&compiler);
860+
compiler.setBlock(block2);
861+
ControlBlocks::compileCreateClone(&compiler);
862+
compiler.setBlock(block3);
863+
ControlBlocks::compileCreateClone(&compiler);
864+
compiler.end();
865+
866+
ASSERT_EQ(
867+
compiler.bytecode(),
868+
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 }));
869+
ASSERT_EQ(compiler.constValues().size(), 1);
870+
ASSERT_EQ(compiler.constValues()[0].toDouble(), 4);
871+
ASSERT_TRUE(compiler.variables().empty());
872+
ASSERT_TRUE(compiler.lists().empty());
873+
}
874+
875+
TEST_F(ControlBlocksTest, CreateCloneOfImpl)
876+
{
877+
static unsigned int bytecode1[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT };
878+
static unsigned int bytecode2[] = { vm::OP_START, vm::OP_EXEC, 1, vm::OP_HALT };
879+
static unsigned int bytecode3[] = { vm::OP_START, vm::OP_CONST, 1, vm::OP_EXEC, 2, vm::OP_HALT };
880+
static unsigned int bytecode4[] = { vm::OP_START, vm::OP_CONST, 2, vm::OP_EXEC, 2, vm::OP_HALT };
881+
static BlockFunc functions[] = { &ControlBlocks::createCloneByIndex, &ControlBlocks::createCloneOfMyself, &ControlBlocks::createClone };
882+
static Value constValues[] = { 4, "Sprite1", "_myself_" };
883+
884+
Sprite sprite;
885+
sprite.setEngine(&m_engineMock);
886+
887+
VirtualMachine vm(&sprite, &m_engineMock, nullptr);
888+
vm.setFunctions(functions);
889+
vm.setConstValues(constValues);
890+
891+
EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&sprite));
892+
EXPECT_CALL(m_engineMock, initClone).Times(1);
893+
894+
vm.setBytecode(bytecode1);
895+
vm.run();
896+
897+
ASSERT_EQ(vm.registerCount(), 0);
898+
ASSERT_EQ(sprite.allChildren().size(), 1);
899+
900+
EXPECT_CALL(m_engineMock, initClone).Times(1);
901+
902+
vm.setBytecode(bytecode2);
903+
vm.run();
904+
905+
ASSERT_EQ(vm.registerCount(), 0);
906+
ASSERT_EQ(sprite.allChildren().size(), 2);
907+
ASSERT_EQ(sprite.children(), sprite.allChildren());
908+
909+
EXPECT_CALL(m_engineMock, findTarget).WillOnce(Return(4));
910+
EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&sprite));
911+
EXPECT_CALL(m_engineMock, initClone).Times(1);
912+
913+
vm.setBytecode(bytecode3);
914+
vm.run();
915+
916+
ASSERT_EQ(vm.registerCount(), 0);
917+
ASSERT_EQ(sprite.allChildren().size(), 3);
918+
ASSERT_EQ(sprite.children(), sprite.allChildren());
919+
920+
EXPECT_CALL(m_engineMock, initClone).Times(1);
921+
922+
vm.setBytecode(bytecode4);
923+
vm.run();
924+
925+
ASSERT_EQ(vm.registerCount(), 0);
926+
ASSERT_EQ(sprite.allChildren().size(), 4);
927+
ASSERT_EQ(sprite.children(), sprite.allChildren());
928+
}

0 commit comments

Comments
 (0)