Skip to content

Commit 2a4876a

Browse files
committed
Implement event_whentouchingobject block
1 parent a663b7b commit 2a4876a

File tree

3 files changed

+223
-0
lines changed

3 files changed

+223
-0
lines changed

src/blocks/eventblocks.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <scratchcpp/input.h>
77
#include <scratchcpp/inputvalue.h>
88
#include <scratchcpp/field.h>
9+
#include <scratchcpp/sprite.h>
910
#include <scratchcpp/stage.h>
1011
#include <scratchcpp/costume.h>
1112
#include <scratchcpp/block.h>
@@ -27,6 +28,7 @@ std::string EventBlocks::name() const
2728
void EventBlocks::registerBlocks(IEngine *engine)
2829
{
2930
// Blocks
31+
engine->addCompileFunction(this, "event_whentouchingobject", &compileWhenTouchingObject);
3032
engine->addCompileFunction(this, "event_whenflagclicked", &compileWhenFlagClicked);
3133
engine->addCompileFunction(this, "event_whenthisspriteclicked", &compileWhenThisSpriteClicked);
3234
engine->addCompileFunction(this, "event_whenstageclicked", &compileWhenStageClicked);
@@ -38,9 +40,11 @@ void EventBlocks::registerBlocks(IEngine *engine)
3840
engine->addCompileFunction(this, "event_whenkeypressed", &compileWhenKeyPressed);
3941

4042
// Hat predicates
43+
engine->addHatPredicateCompileFunction(this, "event_whentouchingobject", &compileWhenTouchingObjectPredicate);
4144
engine->addHatPredicateCompileFunction(this, "event_whengreaterthan", &compileWhenGreaterThanPredicate);
4245

4346
// Inputs
47+
engine->addInput(this, "TOUCHINGOBJECTMENU", TOUCHINGOBJECTMENU);
4448
engine->addInput(this, "BROADCAST_INPUT", BROADCAST_INPUT);
4549
engine->addInput(this, "VALUE", VALUE);
4650

@@ -55,6 +59,27 @@ void EventBlocks::registerBlocks(IEngine *engine)
5559
engine->addFieldValue(this, "TIMER", Timer);
5660
}
5761

62+
void EventBlocks::compileWhenTouchingObjectPredicate(Compiler *compiler)
63+
{
64+
Input *input = compiler->input(TOUCHINGOBJECTMENU);
65+
66+
if (input->type() != Input::Type::ObscuredShadow) {
67+
assert(input->pointsToDropdownMenu());
68+
std::string value = input->selectedMenuItem();
69+
70+
compiler->addConstValue(value);
71+
compiler->addFunctionCall(&whenTouchingObjectPredicate);
72+
} else {
73+
compiler->addInput(input);
74+
compiler->addFunctionCall(&whenTouchingObjectPredicate);
75+
}
76+
}
77+
78+
void EventBlocks::compileWhenTouchingObject(Compiler *compiler)
79+
{
80+
compiler->engine()->addWhenTouchingObjectScript(compiler->block());
81+
}
82+
5883
void EventBlocks::compileWhenFlagClicked(Compiler *compiler)
5984
{
6085
compiler->engine()->addGreenFlagScript(compiler->block());
@@ -151,6 +176,26 @@ void EventBlocks::compileWhenKeyPressed(Compiler *compiler)
151176
compiler->engine()->addKeyPressScript(compiler->block(), KEY_OPTION);
152177
}
153178

179+
unsigned int EventBlocks::whenTouchingObjectPredicate(VirtualMachine *vm)
180+
{
181+
std::string value = vm->getInput(0, 1)->toString();
182+
183+
if (value == "_mouse_")
184+
vm->replaceReturnValue(vm->target()->touchingPoint(vm->engine()->mouseX(), vm->engine()->mouseY()), 1);
185+
else if (value == "_edge_")
186+
vm->replaceReturnValue(vm->target()->touchingEdge(), 1);
187+
else {
188+
Target *target = vm->engine()->targetAt(vm->engine()->findTarget(value));
189+
190+
if (target && !target->isStage())
191+
vm->replaceReturnValue(vm->target()->touchingSprite(static_cast<Sprite *>(target)), 1);
192+
else
193+
vm->replaceReturnValue(false, 1);
194+
}
195+
196+
return 0;
197+
}
198+
154199
unsigned int EventBlocks::broadcast(VirtualMachine *vm)
155200
{
156201
vm->engine()->broadcast(vm->engine()->findBroadcast(vm->getInput(0, 1)->toString()));

src/blocks/eventblocks.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class EventBlocks : public IBlockSection
1717
public:
1818
enum Inputs
1919
{
20+
TOUCHINGOBJECTMENU,
2021
BROADCAST_INPUT,
2122
VALUE
2223
};
@@ -39,6 +40,8 @@ class EventBlocks : public IBlockSection
3940

4041
void registerBlocks(IEngine *engine) override;
4142

43+
static void compileWhenTouchingObjectPredicate(Compiler *compiler);
44+
static void compileWhenTouchingObject(Compiler *compiler);
4245
static void compileWhenFlagClicked(Compiler *compiler);
4346
static void compileWhenThisSpriteClicked(Compiler *compiler);
4447
static void compileWhenStageClicked(Compiler *compiler);
@@ -50,6 +53,8 @@ class EventBlocks : public IBlockSection
5053
static void compileWhenGreaterThan(Compiler *compiler);
5154
static void compileWhenKeyPressed(Compiler *compiler);
5255

56+
static unsigned int whenTouchingObjectPredicate(VirtualMachine *vm);
57+
5358
static unsigned int broadcast(VirtualMachine *vm);
5459
static unsigned int broadcastByIndex(VirtualMachine *vm);
5560
static unsigned int broadcastAndWait(VirtualMachine *vm);

test/blocks/event_blocks_test.cpp

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
#include <scratchcpp/inputvalue.h>
55
#include <scratchcpp/field.h>
66
#include <scratchcpp/broadcast.h>
7+
#include <scratchcpp/sprite.h>
78
#include <scratchcpp/stage.h>
89
#include <scratchcpp/costume.h>
910
#include <enginemock.h>
1011
#include <audioinputmock.h>
1112
#include <audioloudnessmock.h>
1213
#include <timermock.h>
14+
#include <targetmock.h>
1315

1416
#include "../common.h"
1517
#include "blocks/eventblocks.h"
@@ -35,6 +37,15 @@ class EventBlocksTest : public testing::Test
3537
// For any event block
3638
std::shared_ptr<Block> createEventBlock(const std::string &id, const std::string &opcode) const { return std::make_shared<Block>(id, opcode); }
3739

40+
std::shared_ptr<Block> createNullBlock(const std::string &id)
41+
{
42+
std::shared_ptr<Block> block = std::make_shared<Block>(id, "");
43+
BlockComp func = [](Compiler *compiler) { compiler->addInstruction(vm::OP_NULL); };
44+
block->setCompileFunction(func);
45+
46+
return block;
47+
}
48+
3849
void addBroadcastInput(std::shared_ptr<Block> block, const std::string &name, EventBlocks::Inputs id, std::shared_ptr<Broadcast> broadcast) const
3950
{
4051
auto input = std::make_shared<Input>(name, Input::Type::Shadow);
@@ -45,6 +56,15 @@ class EventBlocksTest : public testing::Test
4556
block->addInput(input);
4657
}
4758

59+
std::shared_ptr<Input> addNullInput(std::shared_ptr<Block> block, const std::string &name, EventBlocks::Inputs id) const
60+
{
61+
auto input = std::make_shared<Input>(name, Input::Type::Shadow);
62+
input->setInputId(id);
63+
block->addInput(input);
64+
65+
return input;
66+
}
67+
4868
void addValueInput(std::shared_ptr<Block> block, const std::string &name, EventBlocks::Inputs id, const Value &value) const
4969
{
5070
auto input = std::make_shared<Input>(name, Input::Type::Shadow);
@@ -61,6 +81,26 @@ class EventBlocksTest : public testing::Test
6181
block->addInput(input);
6282
}
6383

84+
void addDropdownInput(std::shared_ptr<Block> block, const std::string &name, EventBlocks::Inputs id, const std::string &selectedValue, std::shared_ptr<Block> valueBlock = nullptr) const
85+
{
86+
if (valueBlock)
87+
addObscuredInput(block, name, id, valueBlock);
88+
else {
89+
auto input = addNullInput(block, name, id);
90+
auto menu = std::make_shared<Block>(block->id() + "_menu", block->opcode() + "_menu");
91+
input->setValueBlock(menu);
92+
addDropdownField(menu, name, static_cast<EventBlocks::Fields>(-1), selectedValue, static_cast<EventBlocks::FieldValues>(-1));
93+
}
94+
}
95+
96+
void addDropdownField(std::shared_ptr<Block> block, const std::string &name, EventBlocks::Fields id, const std::string &value, EventBlocks::FieldValues valueId) const
97+
{
98+
auto field = std::make_shared<Field>(name, value);
99+
field->setFieldId(id);
100+
field->setSpecialValueId(valueId);
101+
block->addField(field);
102+
}
103+
64104
void addValueField(std::shared_ptr<Block> block, const std::string &name, EventBlocks::Fields id, const std::string &value, int valueId = -1) const
65105
{
66106
auto field = std::make_shared<Field>(name, value);
@@ -95,6 +135,7 @@ TEST_F(EventBlocksTest, CategoryVisible)
95135
TEST_F(EventBlocksTest, RegisterBlocks)
96136
{
97137
// Blocks
138+
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "event_whentouchingobject", &EventBlocks::compileWhenTouchingObject));
98139
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "event_whenflagclicked", &EventBlocks::compileWhenFlagClicked));
99140
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "event_whenthisspriteclicked", &EventBlocks::compileWhenThisSpriteClicked));
100141
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "event_whenstageclicked", &EventBlocks::compileWhenStageClicked));
@@ -106,9 +147,11 @@ TEST_F(EventBlocksTest, RegisterBlocks)
106147
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "event_whenkeypressed", &EventBlocks::compileWhenKeyPressed));
107148

108149
// Hat predicates
150+
EXPECT_CALL(m_engineMock, addHatPredicateCompileFunction(m_section.get(), "event_whentouchingobject", &EventBlocks::compileWhenTouchingObjectPredicate));
109151
EXPECT_CALL(m_engineMock, addHatPredicateCompileFunction(m_section.get(), "event_whengreaterthan", &EventBlocks::compileWhenGreaterThanPredicate));
110152

111153
// Inputs
154+
EXPECT_CALL(m_engineMock, addInput(m_section.get(), "TOUCHINGOBJECTMENU", EventBlocks::TOUCHINGOBJECTMENU));
112155
EXPECT_CALL(m_engineMock, addInput(m_section.get(), "BROADCAST_INPUT", EventBlocks::BROADCAST_INPUT));
113156
EXPECT_CALL(m_engineMock, addInput(m_section.get(), "VALUE", EventBlocks::VALUE));
114157

@@ -125,6 +168,136 @@ TEST_F(EventBlocksTest, RegisterBlocks)
125168
m_section->registerBlocks(&m_engineMock);
126169
}
127170

171+
TEST_F(EventBlocksTest, WhenTouchingObjectPredicate)
172+
{
173+
Compiler compiler(&m_engineMock);
174+
175+
// when touching (Sprite1)
176+
auto block1 = createEventBlock("a", "event_whentouchingobject");
177+
addDropdownInput(block1, "TOUCHINGOBJECTMENU", EventBlocks::TOUCHINGOBJECTMENU, "Sprite1");
178+
179+
// when touching (null block)
180+
auto block2 = createEventBlock("b", "event_whentouchingobject");
181+
addDropdownInput(block2, "TOUCHINGOBJECTMENU", EventBlocks::TOUCHINGOBJECTMENU, "", createNullBlock("e"));
182+
183+
compiler.init();
184+
185+
EXPECT_CALL(m_engineMock, functionIndex(&EventBlocks::whenTouchingObjectPredicate)).WillOnce(Return(0));
186+
compiler.setBlock(block1);
187+
EventBlocks::compileWhenTouchingObjectPredicate(&compiler);
188+
189+
EXPECT_CALL(m_engineMock, functionIndex(&EventBlocks::whenTouchingObjectPredicate)).WillOnce(Return(0));
190+
compiler.setBlock(block2);
191+
EventBlocks::compileWhenTouchingObjectPredicate(&compiler);
192+
193+
compiler.end();
194+
195+
ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_NULL, vm::OP_EXEC, 0, vm::OP_HALT }));
196+
ASSERT_EQ(compiler.constValues().size(), 1);
197+
ASSERT_EQ(compiler.constValues()[0], "Sprite1");
198+
ASSERT_TRUE(compiler.variables().empty());
199+
ASSERT_TRUE(compiler.lists().empty());
200+
}
201+
202+
TEST_F(EventBlocksTest, WhenTouchingObjectPredicateImpl)
203+
{
204+
static unsigned int bytecode1[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT };
205+
static unsigned int bytecode2[] = { vm::OP_START, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_HALT };
206+
static unsigned int bytecode3[] = { vm::OP_START, vm::OP_CONST, 2, vm::OP_EXEC, 0, vm::OP_HALT };
207+
static unsigned int bytecode4[] = { vm::OP_START, vm::OP_CONST, 3, vm::OP_EXEC, 0, vm::OP_HALT };
208+
static BlockFunc functions[] = { &EventBlocks::whenTouchingObjectPredicate };
209+
static Value constValues[] = { "Sprite2", "_mouse_", "_edge_", "", 1, -1, 2 };
210+
211+
TargetMock target;
212+
target.setEngine(&m_engineMock);
213+
Sprite sprite;
214+
VirtualMachine vm(&target, &m_engineMock, nullptr);
215+
vm.setFunctions(functions);
216+
vm.setConstValues(constValues);
217+
218+
// touching "Sprite2"
219+
EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(3));
220+
EXPECT_CALL(m_engineMock, targetAt(3)).WillOnce(Return(&sprite));
221+
EXPECT_CALL(target, touchingClones).WillOnce(Return(false));
222+
vm.setBytecode(bytecode1);
223+
vm.run();
224+
225+
ASSERT_EQ(vm.registerCount(), 1);
226+
ASSERT_FALSE(vm.getInput(0, 1)->toBool());
227+
228+
EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(3));
229+
EXPECT_CALL(m_engineMock, targetAt(3)).WillOnce(Return(&sprite));
230+
EXPECT_CALL(target, touchingClones).WillOnce(Return(true));
231+
vm.reset();
232+
vm.run();
233+
234+
ASSERT_EQ(vm.registerCount(), 1);
235+
ASSERT_TRUE(vm.getInput(0, 1)->toBool());
236+
237+
// touching "_mouse_"
238+
EXPECT_CALL(m_engineMock, mouseX()).WillOnce(Return(24.5));
239+
EXPECT_CALL(m_engineMock, mouseY()).WillOnce(Return(-16.04));
240+
EXPECT_CALL(target, touchingPoint(24.5, -16.04)).WillOnce(Return(true));
241+
vm.setBytecode(bytecode2);
242+
vm.reset();
243+
vm.run();
244+
245+
ASSERT_EQ(vm.registerCount(), 1);
246+
ASSERT_TRUE(vm.getInput(0, 1)->toBool());
247+
248+
// touching "_edge_"
249+
EXPECT_CALL(m_engineMock, stageWidth()).WillOnce(Return(0));
250+
EXPECT_CALL(m_engineMock, stageHeight()).WillOnce(Return(0));
251+
EXPECT_CALL(target, boundingRect()).WillOnce(Return(Rect(-5, 5, 5, -5)));
252+
vm.setBytecode(bytecode3);
253+
vm.reset();
254+
vm.run();
255+
256+
ASSERT_EQ(vm.registerCount(), 1);
257+
ASSERT_TRUE(vm.getInput(0, 1)->toBool());
258+
259+
EXPECT_CALL(m_engineMock, stageWidth()).WillOnce(Return(10));
260+
EXPECT_CALL(m_engineMock, stageHeight()).WillOnce(Return(10));
261+
EXPECT_CALL(target, boundingRect()).WillOnce(Return(Rect(-5, 5, 5, -5)));
262+
vm.reset();
263+
vm.run();
264+
265+
ASSERT_EQ(vm.registerCount(), 1);
266+
ASSERT_FALSE(vm.getInput(0, 1)->toBool());
267+
268+
// touching ""
269+
EXPECT_CALL(m_engineMock, findTarget("")).WillOnce(Return(-1));
270+
EXPECT_CALL(m_engineMock, targetAt(-1)).WillOnce(Return(nullptr));
271+
vm.setBytecode(bytecode4);
272+
vm.reset();
273+
vm.run();
274+
275+
ASSERT_EQ(vm.registerCount(), 1);
276+
ASSERT_FALSE(vm.getInput(0, 1)->toBool());
277+
}
278+
279+
TEST_F(EventBlocksTest, WhenTouchingObject)
280+
{
281+
Compiler compiler(&m_engineMock);
282+
283+
// when touching (Sprite1)
284+
auto block = createEventBlock("a", "event_whentouchingobject");
285+
addDropdownInput(block, "TOUCHINGOBJECTMENU", EventBlocks::TOUCHINGOBJECTMENU, "Sprite1");
286+
287+
compiler.init();
288+
289+
EXPECT_CALL(m_engineMock, addWhenTouchingObjectScript(block));
290+
compiler.setBlock(block);
291+
EventBlocks::compileWhenTouchingObject(&compiler);
292+
293+
compiler.end();
294+
295+
ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_HALT }));
296+
ASSERT_TRUE(compiler.constValues().empty());
297+
ASSERT_TRUE(compiler.variables().empty());
298+
ASSERT_TRUE(compiler.lists().empty());
299+
}
300+
128301
TEST_F(EventBlocksTest, WhenFlagClicked)
129302
{
130303
Compiler compiler(&m_engineMock);

0 commit comments

Comments
 (0)