Skip to content

Commit 87dc27e

Browse files
committed
Implement motion_ifonedgebounce block
1 parent 44184aa commit 87dc27e

File tree

3 files changed

+246
-0
lines changed

3 files changed

+246
-0
lines changed

src/blocks/motionblocks.cpp

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <scratchcpp/sprite.h>
66
#include <scratchcpp/input.h>
77
#include <scratchcpp/field.h>
8+
#include <scratchcpp/rect.h>
89

910
#include "motionblocks.h"
1011
#include "../engine/internal/randomgenerator.h"
@@ -38,6 +39,7 @@ void MotionBlocks::registerBlocks(IEngine *engine)
3839
engine->addCompileFunction(this, "motion_setx", &compileSetX);
3940
engine->addCompileFunction(this, "motion_changeyby", &compileChangeYBy);
4041
engine->addCompileFunction(this, "motion_sety", &compileSetY);
42+
engine->addCompileFunction(this, "motion_ifonedgebounce", &compileIfOnEdgeBounce);
4143
engine->addCompileFunction(this, "motion_setrotationstyle", &compileSetRotationStyle);
4244
engine->addCompileFunction(this, "motion_xposition", &compileXPosition);
4345
engine->addCompileFunction(this, "motion_yposition", &compileYPosition);
@@ -196,6 +198,11 @@ void MotionBlocks::compileSetY(Compiler *compiler)
196198
compiler->addFunctionCall(&setY);
197199
}
198200

201+
void MotionBlocks::compileIfOnEdgeBounce(Compiler *compiler)
202+
{
203+
compiler->addFunctionCall(&ifOnEdgeBounce);
204+
}
205+
199206
void MotionBlocks::compileSetRotationStyle(Compiler *compiler)
200207
{
201208
int option = compiler->field(STYLE)->specialValueId();
@@ -628,6 +635,99 @@ unsigned int MotionBlocks::setY(VirtualMachine *vm)
628635
return 1;
629636
}
630637

638+
unsigned int MotionBlocks::ifOnEdgeBounce(VirtualMachine *vm)
639+
{
640+
// See https://github.com/scratchfoundation/scratch-vm/blob/c37745e97e6d8a77ad1dc31a943ea728dd17ba78/src/blocks/scratch3_motion.js#L186-L240
641+
Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
642+
IEngine *engine = vm->engine();
643+
644+
if (!sprite || !engine)
645+
return 0;
646+
647+
Rect bounds = sprite->boundingRect();
648+
649+
// Measure distance to edges
650+
// Values are zero when the sprite is beyond
651+
unsigned int stageWidth = engine->stageWidth();
652+
unsigned int stageHeight = engine->stageHeight();
653+
double distLeft = std::max(0.0, (stageWidth / 2.0) + bounds.left());
654+
double distTop = std::max(0.0, (stageHeight / 2.0) - bounds.top());
655+
double distRight = std::max(0.0, (stageWidth / 2.0) - bounds.right());
656+
double distBottom = std::max(0.0, (stageHeight / 2.0) + bounds.bottom());
657+
658+
// Find the nearest edge
659+
// 1 - left
660+
// 2 - top
661+
// 3 - right
662+
// 4 - bottom
663+
unsigned short nearestEdge = 0;
664+
double minDist = std::numeric_limits<double>::infinity();
665+
666+
if (distLeft < minDist) {
667+
minDist = distLeft;
668+
nearestEdge = 1;
669+
}
670+
671+
if (distTop < minDist) {
672+
minDist = distTop;
673+
nearestEdge = 2;
674+
}
675+
676+
if (distRight < minDist) {
677+
minDist = distRight;
678+
nearestEdge = 3;
679+
}
680+
681+
if (distBottom < minDist) {
682+
minDist = distBottom;
683+
nearestEdge = 4;
684+
}
685+
686+
if (minDist > 0) {
687+
return 0; // Not touching any edge
688+
}
689+
690+
assert(nearestEdge != 0);
691+
692+
// Point away from the nearest edge
693+
double radians = (90 - sprite->direction()) * pi / 180;
694+
double dx = std::cos(radians);
695+
double dy = -std::sin(radians);
696+
697+
switch (nearestEdge) {
698+
case 1:
699+
// Left
700+
dx = std::max(0.2, std::abs(dx));
701+
break;
702+
703+
case 2:
704+
// Top
705+
dy = std::max(0.2, std::abs(dy));
706+
break;
707+
708+
case 3:
709+
// Right
710+
dx = 0 - std::max(0.2, std::abs(dx));
711+
break;
712+
713+
case 4:
714+
// Bottom
715+
dy = 0 - std::max(0.2, std::abs(dy));
716+
break;
717+
}
718+
719+
double newDirection = (180 / pi) * (std::atan2(dy, dx)) + 90;
720+
sprite->setDirection(newDirection);
721+
722+
// Keep within the stage
723+
double fencedX, fencedY;
724+
sprite->keepInFence(sprite->x(), sprite->y(), &fencedX, &fencedY);
725+
sprite->setX(fencedX);
726+
sprite->setY(fencedY);
727+
728+
return 0;
729+
}
730+
631731
unsigned int MotionBlocks::setLeftRightRotationStyle(VirtualMachine *vm)
632732
{
633733
Sprite *sprite = dynamic_cast<Sprite *>(vm->target());

src/blocks/motionblocks.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class MotionBlocks : public IBlockSection
6161
static void compileSetX(Compiler *compiler);
6262
static void compileChangeYBy(Compiler *compiler);
6363
static void compileSetY(Compiler *compiler);
64+
static void compileIfOnEdgeBounce(Compiler *compiler);
6465
static void compileSetRotationStyle(Compiler *compiler);
6566
static void compileXPosition(Compiler *compiler);
6667
static void compileYPosition(Compiler *compiler);
@@ -98,9 +99,13 @@ class MotionBlocks : public IBlockSection
9899
static unsigned int setX(VirtualMachine *vm);
99100
static unsigned int changeYBy(VirtualMachine *vm);
100101
static unsigned int setY(VirtualMachine *vm);
102+
103+
static unsigned int ifOnEdgeBounce(VirtualMachine *vm);
104+
101105
static unsigned int setLeftRightRotationStyle(VirtualMachine *vm);
102106
static unsigned int setDoNotRotateRotationStyle(VirtualMachine *vm);
103107
static unsigned int setAllAroundRotationStyle(VirtualMachine *vm);
108+
104109
static unsigned int xPosition(VirtualMachine *vm);
105110
static unsigned int yPosition(VirtualMachine *vm);
106111
static unsigned int direction(VirtualMachine *vm);

test/blocks/motion_blocks_test.cpp

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
#include <scratchcpp/input.h>
44
#include <scratchcpp/field.h>
55
#include <scratchcpp/sprite.h>
6+
#include <scratchcpp/costume.h>
7+
#include <scratchcpp/scratchconfiguration.h>
68
#include <enginemock.h>
79
#include <randomgeneratormock.h>
810
#include <clockmock.h>
11+
#include <imageformatfactorymock.h>
12+
#include <imageformatmock.h>
913

1014
#include "../common.h"
1115
#include "blocks/motionblocks.h"
@@ -115,6 +119,7 @@ TEST_F(MotionBlocksTest, RegisterBlocks)
115119
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_setx", &MotionBlocks::compileSetX));
116120
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_changeyby", &MotionBlocks::compileChangeYBy));
117121
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_sety", &MotionBlocks::compileSetY));
122+
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_ifonedgebounce", &MotionBlocks::compileIfOnEdgeBounce));
118123
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_setrotationstyle", &MotionBlocks::compileSetRotationStyle));
119124
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_xposition", &MotionBlocks::compileXPosition));
120125
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "motion_yposition", &MotionBlocks::compileYPosition));
@@ -1085,6 +1090,142 @@ TEST_F(MotionBlocksTest, SetYImpl)
10851090
ASSERT_EQ(sprite.y(), 189.42);
10861091
}
10871092

1093+
TEST_F(MotionBlocksTest, IfOnEdgeBounce)
1094+
{
1095+
Compiler compiler(&m_engineMock);
1096+
1097+
auto block = std::make_shared<Block>("a", "motion_ifonedgebounce");
1098+
1099+
EXPECT_CALL(m_engineMock, functionIndex(&MotionBlocks::ifOnEdgeBounce)).WillOnce(Return(0));
1100+
1101+
compiler.init();
1102+
compiler.setBlock(block);
1103+
MotionBlocks::compileIfOnEdgeBounce(&compiler);
1104+
compiler.end();
1105+
1106+
ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT }));
1107+
ASSERT_TRUE(compiler.constValues().empty());
1108+
}
1109+
1110+
TEST_F(MotionBlocksTest, IfOnEdgeBounceImpl)
1111+
{
1112+
static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT };
1113+
static BlockFunc functions[] = { &MotionBlocks::ifOnEdgeBounce };
1114+
1115+
auto imageFormatFactory = std::make_shared<ImageFormatFactoryMock>();
1116+
auto imageFormat = std::make_shared<ImageFormatMock>();
1117+
1118+
ScratchConfiguration::registerImageFormat("test", imageFormatFactory);
1119+
EXPECT_CALL(*imageFormatFactory, createInstance()).WillOnce(Return(imageFormat));
1120+
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(0));
1121+
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(0));
1122+
auto costume = std::make_shared<Costume>("costume1", "a", "test");
1123+
1124+
Sprite sprite;
1125+
sprite.addCostume(costume);
1126+
sprite.setCostumeIndex(0);
1127+
1128+
static char data[5] = "abcd";
1129+
EXPECT_CALL(*imageFormat, setData(5, data));
1130+
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4));
1131+
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3));
1132+
1133+
EXPECT_CALL(*imageFormat, colorAt(0, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
1134+
EXPECT_CALL(*imageFormat, colorAt(1, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
1135+
EXPECT_CALL(*imageFormat, colorAt(2, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 255)));
1136+
EXPECT_CALL(*imageFormat, colorAt(3, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
1137+
1138+
EXPECT_CALL(*imageFormat, colorAt(0, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
1139+
EXPECT_CALL(*imageFormat, colorAt(1, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 255)));
1140+
EXPECT_CALL(*imageFormat, colorAt(2, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
1141+
EXPECT_CALL(*imageFormat, colorAt(3, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 255)));
1142+
1143+
EXPECT_CALL(*imageFormat, colorAt(0, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 255)));
1144+
EXPECT_CALL(*imageFormat, colorAt(1, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
1145+
EXPECT_CALL(*imageFormat, colorAt(2, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
1146+
EXPECT_CALL(*imageFormat, colorAt(3, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
1147+
costume->setData(5, data);
1148+
1149+
sprite.setEngine(&m_engineMock);
1150+
1151+
VirtualMachine vm(&sprite, &m_engineMock, nullptr);
1152+
vm.setBytecode(bytecode);
1153+
vm.setFunctions(functions);
1154+
1155+
EXPECT_CALL(*imageFormat, width()).Times(9).WillRepeatedly(Return(4));
1156+
EXPECT_CALL(*imageFormat, height()).Times(9).WillRepeatedly(Return(3));
1157+
EXPECT_CALL(m_engineMock, stageWidth()).Times(9).WillRepeatedly(Return(480));
1158+
EXPECT_CALL(m_engineMock, stageHeight()).Times(9).WillRepeatedly(Return(360));
1159+
1160+
// No edge
1161+
EXPECT_CALL(m_engineMock, requestRedraw()).Times(3);
1162+
EXPECT_CALL(m_engineMock, spriteFencingEnabled()).Times(2).WillRepeatedly(Return(false));
1163+
sprite.setX(100);
1164+
sprite.setY(60);
1165+
sprite.setDirection(-45);
1166+
vm.run();
1167+
1168+
ASSERT_EQ(vm.registerCount(), 0);
1169+
ASSERT_EQ(sprite.x(), 100);
1170+
ASSERT_EQ(sprite.y(), 60);
1171+
ASSERT_EQ(sprite.direction(), -45);
1172+
1173+
// Left edge
1174+
EXPECT_CALL(m_engineMock, requestRedraw()).Times(5);
1175+
EXPECT_CALL(m_engineMock, spriteFencingEnabled()).Times(4).WillRepeatedly(Return(false));
1176+
sprite.setX(-240);
1177+
sprite.setY(60);
1178+
vm.reset();
1179+
vm.run();
1180+
1181+
ASSERT_EQ(vm.registerCount(), 0);
1182+
ASSERT_EQ(std::round(sprite.x() * 100) / 100, -238.23);
1183+
ASSERT_EQ(sprite.y(), 60);
1184+
ASSERT_EQ(std::round(sprite.direction() * 100) / 100, 45);
1185+
1186+
// Top edge
1187+
EXPECT_CALL(m_engineMock, requestRedraw()).Times(6);
1188+
EXPECT_CALL(m_engineMock, spriteFencingEnabled()).Times(4).WillRepeatedly(Return(false));
1189+
sprite.setX(100);
1190+
sprite.setY(180);
1191+
sprite.setDirection(45);
1192+
vm.reset();
1193+
vm.run();
1194+
1195+
ASSERT_EQ(vm.registerCount(), 0);
1196+
ASSERT_EQ(sprite.x(), 100);
1197+
ASSERT_EQ(std::round(sprite.y() * 100) / 100, 178.23);
1198+
ASSERT_EQ(sprite.direction(), 135);
1199+
1200+
// Right edge
1201+
EXPECT_CALL(m_engineMock, requestRedraw()).Times(5);
1202+
EXPECT_CALL(m_engineMock, spriteFencingEnabled()).Times(4).WillRepeatedly(Return(false));
1203+
sprite.setX(240);
1204+
sprite.setY(60);
1205+
vm.reset();
1206+
vm.run();
1207+
1208+
ASSERT_EQ(vm.registerCount(), 0);
1209+
ASSERT_EQ(std::round(sprite.x() * 100) / 100, 238.23);
1210+
ASSERT_EQ(sprite.y(), 60);
1211+
ASSERT_EQ(sprite.direction(), -135);
1212+
1213+
// Bottom edge
1214+
EXPECT_CALL(m_engineMock, requestRedraw()).Times(5);
1215+
EXPECT_CALL(m_engineMock, spriteFencingEnabled()).Times(4).WillRepeatedly(Return(false));
1216+
sprite.setX(-100);
1217+
sprite.setY(-180);
1218+
vm.reset();
1219+
vm.run();
1220+
1221+
ASSERT_EQ(vm.registerCount(), 0);
1222+
ASSERT_EQ(sprite.x(), -100);
1223+
ASSERT_EQ(std::round(sprite.y() * 100) / 100, -178.23);
1224+
ASSERT_EQ(std::round(sprite.direction() * 100) / 100, -45);
1225+
1226+
ScratchConfiguration::removeImageFormat("test");
1227+
}
1228+
10881229
TEST_F(MotionBlocksTest, SetRotationStyle)
10891230
{
10901231
Compiler compiler(&m_engineMock);

0 commit comments

Comments
 (0)