diff --git a/include/scratchcpp/iengine.h b/include/scratchcpp/iengine.h index bf21ddf4..821615f4 100644 --- a/include/scratchcpp/iengine.h +++ b/include/scratchcpp/iengine.h @@ -24,6 +24,7 @@ class Stage; class Variable; class List; class Script; +class StringPtr; class Thread; class ITimer; class KeyEvent; @@ -182,6 +183,12 @@ class LIBSCRATCHCPP_EXPORT IEngine /*! Call this when a target is clicked. */ virtual void clickTarget(Target *target) = 0; + /*! + * Returns the answer received from the user. + * \see questionAnswered() + */ + virtual const StringPtr *answer() const = 0; + /*! Returns the stage width. */ virtual unsigned int stageWidth() const = 0; diff --git a/src/blocks/sensingblocks.cpp b/src/blocks/sensingblocks.cpp index 8e3b18ca..0238889b 100644 --- a/src/blocks/sensingblocks.cpp +++ b/src/blocks/sensingblocks.cpp @@ -1,6 +1,29 @@ // SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "sensingblocks.h" +#include "audio/audioinput.h" +#include "audio/iaudioloudness.h" +#include "engine/internal/clock.h" using namespace libscratchcpp; @@ -21,4 +44,750 @@ Rgb SensingBlocks::color() const void SensingBlocks::registerBlocks(IEngine *engine) { + engine->addCompileFunction(this, "sensing_touchingobject", &compileTouchingObject); + engine->addCompileFunction(this, "sensing_touchingcolor", &compileTouchingColor); + engine->addCompileFunction(this, "sensing_coloristouchingcolor", &compileColorIsTouchingColor); + engine->addCompileFunction(this, "sensing_distanceto", &compileDistanceTo); + engine->addCompileFunction(this, "sensing_askandwait", &compileAskAndWait); + engine->addCompileFunction(this, "sensing_answer", &compileAnswer); + engine->addCompileFunction(this, "sensing_keypressed", &compileKeyPressed); + engine->addCompileFunction(this, "sensing_mousedown", &compileMouseDown); + engine->addCompileFunction(this, "sensing_mousex", &compileMouseX); + engine->addCompileFunction(this, "sensing_mousey", &compileMouseY); + engine->addCompileFunction(this, "sensing_setdragmode", &compileSetDragMode); + engine->addCompileFunction(this, "sensing_loudness", &compileLoudness); + engine->addCompileFunction(this, "sensing_loud", &compileLoud); + engine->addCompileFunction(this, "sensing_timer", &compileTimer); + engine->addCompileFunction(this, "sensing_resettimer", &compileResetTimer); + engine->addCompileFunction(this, "sensing_of", &compileOf); + engine->addCompileFunction(this, "sensing_current", &compileCurrent); + engine->addCompileFunction(this, "sensing_dayssince2000", &compileDaysSince2000); +} + +void SensingBlocks::onInit(IEngine *engine) +{ + engine->questionAnswered().connect(&onAnswer); + + engine->threadAboutToStop().connect([engine](Thread *thread) { + if (!m_questions.empty()) { + // Abort the question of this thread if it's currently being displayed + if (m_questions.front()->thread == thread) { + thread->target()->bubble()->setText(""); + engine->questionAborted()(); + } + + m_questions.erase(std::remove_if(m_questions.begin(), m_questions.end(), [thread](const std::unique_ptr &question) { return question->thread == thread; }), m_questions.end()); + } + }); +} + +void SensingBlocks::clearQuestions() +{ + m_questions.clear(); +} + +void SensingBlocks::askQuestion(ExecutionContext *ctx, const StringPtr *question) +{ + const bool isQuestionAsked = !m_questions.empty(); + // TODO: Use UTF-16 in engine (and TextBubble?) + std::string u8str = utf8::utf16to8(std::u16string(question->data)); + enqueueAsk(u8str, ctx->thread()); + + if (!isQuestionAsked) + askNextQuestion(); +} + +CompilerValue *SensingBlocks::compileTouchingObject(Compiler *compiler) +{ + IEngine *engine = compiler->engine(); + Input *input = compiler->input("TOUCHINGOBJECTMENU"); + + if (input->pointsToDropdownMenu()) { + std::string value = input->selectedMenuItem(); + + if (value == "_mouse_") + return compiler->addTargetFunctionCall("sensing_touching_mouse", Compiler::StaticType::Bool); + else if (value == "_edge_") + return compiler->addTargetFunctionCall("sensing_touching_edge", Compiler::StaticType::Bool); + else if (value != "_stage_") { + Target *target = engine->targetAt(engine->findTarget(value)); + + if (target) { + CompilerValue *targetPtr = compiler->addConstValue(target); + return compiler->addTargetFunctionCall("sensing_touching_sprite", Compiler::StaticType::Bool, { Compiler::StaticType::Pointer }, { targetPtr }); + } + } + } else { + CompilerValue *object = compiler->addInput(input); + return compiler->addTargetFunctionCall("sensing_touchingobject", Compiler::StaticType::Bool, { Compiler::StaticType::String }, { object }); + } + + return compiler->addConstValue(false); +} + +CompilerValue *SensingBlocks::compileTouchingColor(Compiler *compiler) +{ + CompilerValue *color = compiler->addInput("COLOR"); + return compiler->addTargetFunctionCall("sensing_touchingcolor", Compiler::StaticType::Bool, { Compiler::StaticType::Unknown }, { color }); +} + +CompilerValue *SensingBlocks::compileColorIsTouchingColor(Compiler *compiler) +{ + CompilerValue *color = compiler->addInput("COLOR"); + CompilerValue *color2 = compiler->addInput("COLOR2"); + return compiler->addTargetFunctionCall("sensing_coloristouchingcolor", Compiler::StaticType::Bool, { Compiler::StaticType::Unknown, Compiler::StaticType::Unknown }, { color, color2 }); +} + +CompilerValue *SensingBlocks::compileDistanceTo(Compiler *compiler) +{ + if (compiler->target()->isStage()) + return compiler->addConstValue(10000.0); + + IEngine *engine = compiler->engine(); + Input *input = compiler->input("DISTANCETOMENU"); + + if (input->pointsToDropdownMenu()) { + std::string value = input->selectedMenuItem(); + + if (value == "_mouse_") + return compiler->addTargetFunctionCall("sensing_distance_to_mouse", Compiler::StaticType::Number); + else if (value != "_stage_") { + Target *target = engine->targetAt(engine->findTarget(value)); + + if (target) { + CompilerValue *targetPtr = compiler->addConstValue(target); + return compiler->addTargetFunctionCall("sensing_distance_to_sprite", Compiler::StaticType::Number, { Compiler::StaticType::Pointer }, { targetPtr }); + } + } + } else { + CompilerValue *object = compiler->addInput(input); + return compiler->addTargetFunctionCall("sensing_distanceto", Compiler::StaticType::Number, { Compiler::StaticType::String }, { object }); + } + + return compiler->addConstValue(10000.0); +} + +CompilerValue *SensingBlocks::compileAskAndWait(Compiler *compiler) +{ + CompilerValue *question = compiler->addInput("QUESTION"); + compiler->addFunctionCallWithCtx("sensing_askandwait", Compiler::StaticType::Void, { Compiler::StaticType::String }, { question }); + compiler->createYield(); + return nullptr; +} + +CompilerValue *SensingBlocks::compileAnswer(Compiler *compiler) +{ + return compiler->addFunctionCallWithCtx("sensing_answer", Compiler::StaticType::String); +} + +CompilerValue *SensingBlocks::compileKeyPressed(Compiler *compiler) +{ + CompilerValue *key = compiler->addInput("KEY_OPTION"); + return compiler->addFunctionCallWithCtx("sensing_keypressed", Compiler::StaticType::Bool, { Compiler::StaticType::String }, { key }); +} + +CompilerValue *SensingBlocks::compileMouseDown(Compiler *compiler) +{ + return compiler->addFunctionCallWithCtx("sensing_mousedown", Compiler::StaticType::Bool); +} + +CompilerValue *SensingBlocks::compileMouseX(Compiler *compiler) +{ + return compiler->addFunctionCallWithCtx("sensing_mousex", Compiler::StaticType::Number); +} + +CompilerValue *SensingBlocks::compileMouseY(Compiler *compiler) +{ + return compiler->addFunctionCallWithCtx("sensing_mousey", Compiler::StaticType::Number); +} + +CompilerValue *SensingBlocks::compileSetDragMode(Compiler *compiler) +{ + if (compiler->target()->isStage()) + return nullptr; + + Field *field = compiler->field("DRAG_MODE"); + assert(field); + + std::string mode = field->value().toString(); + CompilerValue *draggable = nullptr; + + if (mode == "draggable") + draggable = compiler->addConstValue(true); + else if (mode == "not draggable") + draggable = compiler->addConstValue(false); + else + return nullptr; + + compiler->addTargetFunctionCall("sensing_setdragmode", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { draggable }); + return nullptr; +} + +CompilerValue *SensingBlocks::compileLoudness(Compiler *compiler) +{ + return compiler->addFunctionCall("sensing_loudness", Compiler::StaticType::Number); +} + +CompilerValue *SensingBlocks::compileLoud(Compiler *compiler) +{ + CompilerValue *treshold = compiler->addConstValue(10); + CompilerValue *loudness = compiler->addFunctionCall("sensing_loudness", Compiler::StaticType::Number); + return compiler->createCmpGT(loudness, treshold); +} + +CompilerValue *SensingBlocks::compileTimer(Compiler *compiler) +{ + ITimer *timer = compiler->engine()->timer(); + CompilerValue *timerPtr = compiler->addConstValue(timer); + return compiler->addFunctionCall("sensing_timer", Compiler::StaticType::Number, { Compiler::StaticType::Pointer }, { timerPtr }); +} + +CompilerValue *SensingBlocks::compileResetTimer(Compiler *compiler) +{ + ITimer *timer = compiler->engine()->timer(); + CompilerValue *timerPtr = compiler->addConstValue(timer); + compiler->addFunctionCall("sensing_resettimer", Compiler::StaticType::Void, { Compiler::StaticType::Pointer }, { timerPtr }); + return nullptr; +} + +CompilerValue *SensingBlocks::compileOf(Compiler *compiler) +{ + IEngine *engine = compiler->engine(); + Input *input = compiler->input("OBJECT"); + Field *field = compiler->field("PROPERTY"); + assert(input); + assert(field); + + std::string property = field->value().toString(); + + if (input->pointsToDropdownMenu()) { + // Compile time + std::string value = input->selectedMenuItem(); + Target *target = nullptr; + CompilerValue *targetPtr = nullptr; + + if (value == "_stage_") { + // Stage properties + target = engine->stage(); + targetPtr = compiler->addConstValue(target); + + if (property == "backdrop #") + return compiler->addFunctionCall("sensing_costume_number_of_target", Compiler::StaticType::Number, { Compiler::StaticType::Pointer }, { targetPtr }); + else if (property == "backdrop name") + return compiler->addFunctionCall("sensing_costume_name_of_target", Compiler::StaticType::String, { Compiler::StaticType::Pointer }, { targetPtr }); + } else { + // Sprite properties + target = engine->targetAt(engine->findTarget(value)); + + if (target) { + targetPtr = compiler->addConstValue(target); + + if (property == "x position") + return compiler->addFunctionCall("sensing_x_position_of_sprite", Compiler::StaticType::Number, { Compiler::StaticType::Pointer }, { targetPtr }); + else if (property == "y position") + return compiler->addFunctionCall("sensing_y_position_of_sprite", Compiler::StaticType::Number, { Compiler::StaticType::Pointer }, { targetPtr }); + else if (property == "direction") + return compiler->addFunctionCall("sensing_direction_of_sprite", Compiler::StaticType::Number, { Compiler::StaticType::Pointer }, { targetPtr }); + else if (property == "costume #") + return compiler->addFunctionCall("sensing_costume_number_of_target", Compiler::StaticType::Number, { Compiler::StaticType::Pointer }, { targetPtr }); + else if (property == "costume name") + return compiler->addFunctionCall("sensing_costume_name_of_target", Compiler::StaticType::String, { Compiler::StaticType::Pointer }, { targetPtr }); + else if (property == "size") + return compiler->addFunctionCall("sensing_size_of_sprite", Compiler::StaticType::Number, { Compiler::StaticType::Pointer }, { targetPtr }); + } + } + + // Common properties + if (target && targetPtr) { + if (property == "volume") + return compiler->addFunctionCall("sensing_volume_of_target", Compiler::StaticType::Number, { Compiler::StaticType::Pointer }, { targetPtr }); + else { + auto var = target->variableAt(target->findVariable(property)); + + if (var) + return compiler->addVariableValue(var.get()); + } + } + } else { + // Runtime + CompilerValue *targetName = compiler->addInput(input); + CompilerValue *targetPtr = compiler->addFunctionCallWithCtx("sensing_get_target", Compiler::StaticType::Pointer, { Compiler::StaticType::String }, { targetName }); + + // Stage properties + if (property == "backdrop #") { + return compiler->addFunctionCall("sensing_backdrop_number_of_stage_with_check", Compiler::StaticType::Number, { Compiler::StaticType::Pointer }, { targetPtr }); + } else if (property == "backdrop name") + return compiler->addFunctionCall("sensing_backdrop_name_of_stage_with_check", Compiler::StaticType::String, { Compiler::StaticType::Pointer }, { targetPtr }); + + // Sprite properties + if (property == "x position") + return compiler->addFunctionCall("sensing_x_position_of_sprite_with_check", Compiler::StaticType::Number, { Compiler::StaticType::Pointer }, { targetPtr }); + else if (property == "y position") + return compiler->addFunctionCall("sensing_y_position_of_sprite_with_check", Compiler::StaticType::Number, { Compiler::StaticType::Pointer }, { targetPtr }); + else if (property == "direction") + return compiler->addFunctionCall("sensing_direction_of_sprite_with_check", Compiler::StaticType::Number, { Compiler::StaticType::Pointer }, { targetPtr }); + else if (property == "costume #") + return compiler->addFunctionCall("sensing_costume_number_of_sprite_with_check", Compiler::StaticType::Number, { Compiler::StaticType::Pointer }, { targetPtr }); + else if (property == "costume name") + return compiler->addFunctionCall("sensing_costume_name_of_sprite_with_check", Compiler::StaticType::String, { Compiler::StaticType::Pointer }, { targetPtr }); + else if (property == "size") + return compiler->addFunctionCall("sensing_size_of_sprite_with_check", Compiler::StaticType::Number, { Compiler::StaticType::Pointer }, { targetPtr }); + + // Common properties + if (property == "volume") + return compiler->addFunctionCall("sensing_volume_of_target_with_check", Compiler::StaticType::Number, { Compiler::StaticType::Pointer }, { targetPtr }); + else { + CompilerValue *varName = compiler->addConstValue(property); + return compiler->addFunctionCall("sensing_variable_of_target", Compiler::StaticType::Unknown, { Compiler::StaticType::Pointer, Compiler::StaticType::String }, { targetPtr, varName }); + } + } + + return compiler->addConstValue(0.0); +} + +CompilerValue *SensingBlocks::compileCurrent(Compiler *compiler) +{ + Field *field = compiler->field("CURRENTMENU"); + assert(field); + std::string option = field->value().toString(); + + if (option == "YEAR") + return compiler->addFunctionCall("sensing_current_year", Compiler::StaticType::Number); + else if (option == "MONTH") + return compiler->addFunctionCall("sensing_current_month", Compiler::StaticType::Number); + else if (option == "DATE") + return compiler->addFunctionCall("sensing_current_date", Compiler::StaticType::Number); + else if (option == "DAYOFWEEK") + return compiler->addFunctionCall("sensing_current_day_of_week", Compiler::StaticType::Number); + else if (option == "HOUR") + return compiler->addFunctionCall("sensing_current_hour", Compiler::StaticType::Number); + else if (option == "MINUTE") + return compiler->addFunctionCall("sensing_current_minute", Compiler::StaticType::Number); + else if (option == "SECOND") + return compiler->addFunctionCall("sensing_current_second", Compiler::StaticType::Number); + else + return compiler->addConstValue(Value()); +} + +CompilerValue *SensingBlocks::compileDaysSince2000(Compiler *compiler) +{ + return compiler->addFunctionCall("sensing_dayssince2000", Compiler::StaticType::Number); +} + +void SensingBlocks::onAnswer(const std::string &answer) +{ + // https://github.com/scratchfoundation/scratch-vm/blob/6055823f203a696165084b873e661713806583ec/src/blocks/scratch3_sensing.js#L99-L115 + if (!m_questions.empty()) { + Question *question = m_questions.front().get(); + Thread *thread = question->thread; + assert(thread); + assert(thread->target()); + + // If the target was visible when asked, hide the say bubble unless the target was the stage + if (question->wasVisible && !question->wasStage) + thread->target()->bubble()->setText(""); + + m_questions.erase(m_questions.begin()); + thread->promise()->resolve(); + askNextQuestion(); + } +} + +void SensingBlocks::enqueueAsk(const std::string &question, Thread *thread) +{ + // https://github.com/scratchfoundation/scratch-vm/blob/6055823f203a696165084b873e661713806583ec/src/blocks/scratch3_sensing.js#L117-L119 + assert(thread); + Target *target = thread->target(); + assert(target); + bool visible = true; + bool isStage = target->isStage(); + + if (!isStage) { + Sprite *sprite = static_cast(target); + visible = sprite->visible(); + } + + m_questions.push_back(std::make_unique(question, thread, visible, isStage)); +} + +void SensingBlocks::askNextQuestion() +{ + // https://github.com/scratchfoundation/scratch-vm/blob/6055823f203a696165084b873e661713806583ec/src/blocks/scratch3_sensing.js#L121-L133 + if (m_questions.empty()) + return; + + Question *question = m_questions.front().get(); + Target *target = question->thread->target(); + IEngine *engine = question->thread->engine(); + + // If the target is visible, emit a blank question and show + // a bubble unless the target was the stage + if (question->wasVisible && !question->wasStage) { + target->bubble()->setType(TextBubble::Type::Say); + target->bubble()->setText(question->question); + + engine->questionAsked()(""); + } else + engine->questionAsked()(question->question); +} + +extern "C" bool sensing_touching_mouse(Target *target) +{ + IEngine *engine = target->engine(); + return target->touchingPoint(engine->mouseX(), engine->mouseY()); +} + +extern "C" bool sensing_touching_edge(Target *target) +{ + return target->touchingEdge(); +} + +extern "C" bool sensing_touching_sprite(Target *target, Sprite *sprite) +{ + return target->touchingSprite(sprite); +} + +extern "C" bool sensing_touchingobject(Target *target, const StringPtr *object) +{ + static const StringPtr MOUSE_STR("_mouse_"); + static const StringPtr EDGE_STR("_edge_"); + static const StringPtr STAGE_STR("_stage_"); + + if (string_compare_case_sensitive(object, &MOUSE_STR) == 0) + return sensing_touching_mouse(target); + else if (string_compare_case_sensitive(object, &EDGE_STR) == 0) + return sensing_touching_edge(target); + else if (string_compare_case_sensitive(object, &STAGE_STR) != 0) { + IEngine *engine = target->engine(); + // TODO: Use UTF-16 in engine + std::string u8name = utf8::utf16to8(std::u16string(object->data)); + Target *objTarget = engine->targetAt(engine->findTarget(u8name)); + + if (objTarget) + return sensing_touching_sprite(target, static_cast(objTarget)); + } + + return false; +} + +extern "C" bool sensing_touchingcolor(Target *target, const ValueData *color) +{ + return target->touchingColor(value_toRgba(color)); +} + +extern "C" bool sensing_coloristouchingcolor(Target *target, const ValueData *color, const ValueData *color2) +{ + return target->touchingColor(value_toRgba(color), value_toRgba(color2)); +} + +static inline double sensing_distance(double x0, double y0, double x1, double y1) +{ + return std::sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)); +} + +extern "C" double sensing_distance_to_mouse(Sprite *sprite) +{ + IEngine *engine = sprite->engine(); + return sensing_distance(sprite->x(), sprite->y(), engine->mouseX(), engine->mouseY()); +} + +extern "C" double sensing_distance_to_sprite(Sprite *sprite, Sprite *targetSprite) +{ + return sensing_distance(sprite->x(), sprite->y(), targetSprite->x(), targetSprite->y()); +} + +extern "C" double sensing_distanceto(Sprite *sprite, const StringPtr *object) +{ + static const StringPtr MOUSE_STR("_mouse_"); + static const StringPtr STAGE_STR("_stage_"); + + if (string_compare_case_sensitive(object, &MOUSE_STR) == 0) + return sensing_distance_to_mouse(sprite); + else if (string_compare_case_sensitive(object, &STAGE_STR) != 0) { + IEngine *engine = sprite->engine(); + // TODO: Use UTF-16 in engine + std::string u8name = utf8::utf16to8(std::u16string(object->data)); + Target *objTarget = engine->targetAt(engine->findTarget(u8name)); + + if (objTarget) + return sensing_distance_to_sprite(sprite, static_cast(objTarget)); + } + + return 10000.0; +} + +extern "C" void sensing_askandwait(ExecutionContext *ctx, const StringPtr *question) +{ + SensingBlocks::askQuestion(ctx, question); + ctx->thread()->setPromise(std::make_shared()); +} + +extern "C" StringPtr *sensing_answer(ExecutionContext *ctx) +{ + StringPtr *ret = string_pool_new(); + string_assign(ret, ctx->engine()->answer()); + return ret; +} + +extern "C" bool sensing_keypressed(ExecutionContext *ctx, const StringPtr *key) +{ + // TODO: Use UTF-16 in engine + std::string u8name = utf8::utf16to8(std::u16string(key->data)); + return ctx->engine()->keyPressed(u8name); +} + +extern "C" bool sensing_mousedown(ExecutionContext *ctx) +{ + return ctx->engine()->mousePressed(); +} + +extern "C" double sensing_mousex(ExecutionContext *ctx) +{ + return ctx->engine()->mouseX(); +} + +extern "C" double sensing_mousey(ExecutionContext *ctx) +{ + return ctx->engine()->mouseY(); +} + +extern "C" void sensing_setdragmode(Sprite *sprite, bool draggable) +{ + sprite->setDraggable(draggable); +} + +extern "C" double sensing_loudness() +{ + if (!SensingBlocks::audioInput) + SensingBlocks::audioInput = AudioInput::instance().get(); + + auto audioLoudness = SensingBlocks::audioInput->audioLoudness(); + return audioLoudness->getLoudness(); +} + +extern "C" double sensing_timer(ITimer *timer) +{ + return timer->value(); +} + +extern "C" void sensing_resettimer(ITimer *timer) +{ + timer->reset(); +} + +extern "C" double sensing_x_position_of_sprite(Sprite *sprite) +{ + return sprite->x(); +} + +extern "C" double sensing_y_position_of_sprite(Sprite *sprite) +{ + return sprite->y(); +} + +extern "C" double sensing_direction_of_sprite(Sprite *sprite) +{ + return sprite->direction(); +} + +extern "C" double sensing_costume_number_of_target(Target *target) +{ + return target->costumeIndex() + 1; +} + +extern "C" StringPtr *sensing_costume_name_of_target(Target *target) +{ + const std::string &name = target->currentCostume()->name(); + StringPtr *ret = string_pool_new(); + string_assign_cstring(ret, name.c_str()); + return ret; +} + +extern "C" double sensing_size_of_sprite(Sprite *sprite) +{ + return sprite->size(); +} + +extern "C" double sensing_volume_of_target(Target *target) +{ + return target->volume(); +} + +extern "C" Target *sensing_get_target(ExecutionContext *ctx, const StringPtr *name) +{ + // TODO: Use UTF-16 in engine + std::string u8name = utf8::utf16to8(std::u16string(name->data)); + IEngine *engine = ctx->engine(); + return engine->targetAt(engine->findTarget(u8name)); +} + +extern "C" double sensing_x_position_of_sprite_with_check(Target *target) +{ + if (target && !target->isStage()) { + Sprite *sprite = static_cast(target); + return sprite->x(); + } + + return 0.0; +} + +extern "C" double sensing_y_position_of_sprite_with_check(Target *target) +{ + if (target && !target->isStage()) { + Sprite *sprite = static_cast(target); + return sprite->y(); + } + + return 0.0; +} + +extern "C" double sensing_direction_of_sprite_with_check(Target *target) +{ + if (target && !target->isStage()) { + Sprite *sprite = static_cast(target); + return sprite->direction(); + } + + return 0.0; +} + +extern "C" double sensing_costume_number_of_sprite_with_check(Target *target) +{ + if (target && !target->isStage()) + return target->costumeIndex() + 1; + + return 0.0; +} + +extern "C" StringPtr *sensing_costume_name_of_sprite_with_check(Target *target) +{ + StringPtr *ret = string_pool_new(); + + if (target && !target->isStage()) { + const std::string &name = target->currentCostume()->name(); + string_assign_cstring(ret, name.c_str()); + } else + string_assign_cstring(ret, "0"); + + return ret; +} + +extern "C" double sensing_size_of_sprite_with_check(Target *target) +{ + if (target && !target->isStage()) { + Sprite *sprite = static_cast(target); + return sprite->size(); + } + + return 0.0; +} + +extern "C" double sensing_backdrop_number_of_stage_with_check(Target *target) +{ + if (target && target->isStage()) + return target->costumeIndex() + 1; + + return 0.0; +} + +extern "C" StringPtr *sensing_backdrop_name_of_stage_with_check(Target *target) +{ + StringPtr *ret = string_pool_new(); + + if (target && target->isStage()) { + const std::string &name = target->currentCostume()->name(); + string_assign_cstring(ret, name.c_str()); + } else + string_assign_cstring(ret, ""); + + return ret; +} + +extern "C" double sensing_volume_of_target_with_check(Target *target) +{ + if (target) + return target->volume(); + + return 0.0; +} + +extern "C" ValueData sensing_variable_of_target(Target *target, const StringPtr *varName) +{ + if (target) { + // TODO: Use UTF-16 in... Target? + std::string u8name = utf8::utf16to8(std::u16string(varName->data)); + int varIndex = target->findVariable(u8name); + if (varIndex >= 0) { + auto var = target->variableAt(varIndex); + + if (var) { + ValueData ret; + value_init(&ret); + value_assign_copy(&ret, &var->value().data()); + return ret; + } + } + } + + ValueData ret; + value_init(&ret); + return ret; +} + +extern "C" double sensing_current_year() +{ + time_t now = time(0); + tm *ltm = localtime(&now); + return ltm->tm_year + 1900; +} + +extern "C" double sensing_current_month() +{ + time_t now = time(0); + tm *ltm = localtime(&now); + return ltm->tm_mon + 1; +} + +extern "C" double sensing_current_date() +{ + time_t now = time(0); + tm *ltm = localtime(&now); + return ltm->tm_mday; +} + +extern "C" double sensing_current_day_of_week() +{ + time_t now = time(0); + tm *ltm = localtime(&now); + return ltm->tm_wday + 1; +} + +extern "C" double sensing_current_hour() +{ + time_t now = time(0); + tm *ltm = localtime(&now); + return ltm->tm_hour; +} + +extern "C" double sensing_current_minute() +{ + time_t now = time(0); + tm *ltm = localtime(&now); + return ltm->tm_min; +} + +extern "C" double sensing_current_second() +{ + time_t now = time(0); + tm *ltm = localtime(&now); + return ltm->tm_sec; +} + +extern "C" double sensing_dayssince2000() +{ + if (!SensingBlocks::clock) + SensingBlocks::clock = Clock::instance().get(); + + auto ms = std::chrono::duration_cast(SensingBlocks::clock->currentSystemTime().time_since_epoch()).count(); + return ms / 86400000.0 - 10957.0; } diff --git a/src/blocks/sensingblocks.h b/src/blocks/sensingblocks.h index 9a0f367f..a1d274a2 100644 --- a/src/blocks/sensingblocks.h +++ b/src/blocks/sensingblocks.h @@ -3,10 +3,17 @@ #pragma once #include +#include +#include namespace libscratchcpp { +class IAudioInput; +class IClock; +class ExecutionContext; +class Thread; + class SensingBlocks : public IExtension { public: @@ -15,6 +22,55 @@ class SensingBlocks : public IExtension Rgb color() const override; void registerBlocks(IEngine *engine) override; + void onInit(IEngine *engine) override; + + static void clearQuestions(); + static void askQuestion(ExecutionContext *ctx, const StringPtr *question); + + static inline IAudioInput *audioInput = nullptr; + static inline IClock *clock = nullptr; + + private: + struct Question + { + Question(const std::string &question, Thread *thread, bool wasVisible, bool wasStage) : + question(question), + thread(thread), + wasVisible(wasVisible), + wasStage(wasStage) + { + } + + std::string question; + Thread *thread = nullptr; + bool wasVisible = false; + bool wasStage = false; + }; + + static CompilerValue *compileTouchingObject(Compiler *compiler); + static CompilerValue *compileTouchingColor(Compiler *compiler); + static CompilerValue *compileColorIsTouchingColor(Compiler *compiler); + static CompilerValue *compileDistanceTo(Compiler *compiler); + static CompilerValue *compileAskAndWait(Compiler *compiler); + static CompilerValue *compileAnswer(Compiler *compiler); + static CompilerValue *compileKeyPressed(Compiler *compiler); + static CompilerValue *compileMouseDown(Compiler *compiler); + static CompilerValue *compileMouseX(Compiler *compiler); + static CompilerValue *compileMouseY(Compiler *compiler); + static CompilerValue *compileSetDragMode(Compiler *compiler); + static CompilerValue *compileLoudness(Compiler *compiler); + static CompilerValue *compileLoud(Compiler *compiler); + static CompilerValue *compileTimer(Compiler *compiler); + static CompilerValue *compileResetTimer(Compiler *compiler); + static CompilerValue *compileOf(Compiler *compiler); + static CompilerValue *compileCurrent(Compiler *compiler); + static CompilerValue *compileDaysSince2000(Compiler *compiler); + + static void onAnswer(const std::string &answer); + static void enqueueAsk(const std::string &question, Thread *thread); + static void askNextQuestion(); + + static inline std::vector> m_questions; }; } // namespace libscratchcpp diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index 176e6b66..72dbbb3b 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -50,12 +50,20 @@ Engine::Engine() : m_clock(Clock::instance().get()), m_audioEngine(IAudioEngine::instance()) { + m_answer = string_pool_new(); + string_assign_cstring(m_answer, ""); + + m_questionAnswered.connect([this](const std::string &answer) { + // Update answer + string_assign_cstring(m_answer, answer.c_str()); + }); } Engine::~Engine() { m_clones.clear(); m_sortedDrawables.clear(); + string_pool_free(m_answer); } void Engine::clear() @@ -848,6 +856,11 @@ void Engine::clickTarget(Target *target) } } +const StringPtr *Engine::answer() const +{ + return m_answer; +} + unsigned int Engine::stageWidth() const { return m_stageWidth; diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h index e2e1018b..19e9809d 100644 --- a/src/engine/internal/engine.h +++ b/src/engine/internal/engine.h @@ -84,6 +84,8 @@ class Engine : public IEngine void clickTarget(Target *target) override; + const StringPtr *answer() const override; + unsigned int stageWidth() const override; void setStageWidth(unsigned int width) override; @@ -273,6 +275,7 @@ class Engine : public IEngine double m_mouseX = 0; double m_mouseY = 0; bool m_mousePressed = false; + StringPtr *m_answer = nullptr; unsigned int m_stageWidth = 480; unsigned int m_stageHeight = 360; int m_cloneLimit = 300; diff --git a/src/engine/internal/llvm/instructions/functions.cpp b/src/engine/internal/llvm/instructions/functions.cpp index cc5c9679..1bdb379b 100644 --- a/src/engine/internal/llvm/instructions/functions.cpp +++ b/src/engine/internal/llvm/instructions/functions.cpp @@ -46,15 +46,19 @@ LLVMInstruction *Functions::buildFunctionCall(LLVMInstruction *ins) // Args for (auto &arg : ins->args) { - types.push_back(m_utils.getType(arg.first)); + types.push_back(m_utils.getType(arg.first, false)); args.push_back(m_utils.castValue(arg.second, arg.first)); } - llvm::Type *retType = m_utils.getType(ins->functionReturnReg ? ins->functionReturnReg->type() : Compiler::StaticType::Void); + llvm::Type *retType = m_utils.getType(ins->functionReturnReg ? ins->functionReturnReg->type() : Compiler::StaticType::Void, true); llvm::Value *ret = m_builder.CreateCall(m_utils.functions().resolveFunction(ins->functionName, llvm::FunctionType::get(retType, types, false)), args); if (ins->functionReturnReg) { - ins->functionReturnReg->value = ret; + if (ins->functionReturnReg->type() == Compiler::StaticType::Unknown) { + ins->functionReturnReg->value = m_utils.addAlloca(retType); + m_builder.CreateStore(ret, ins->functionReturnReg->value); + } else + ins->functionReturnReg->value = ret; if (ins->functionReturnReg->type() == Compiler::StaticType::String) m_utils.freeStringLater(ins->functionReturnReg->value); diff --git a/src/engine/internal/llvm/llvmbuildutils.cpp b/src/engine/internal/llvm/llvmbuildutils.cpp index d1b78ece..771bb665 100644 --- a/src/engine/internal/llvm/llvmbuildutils.cpp +++ b/src/engine/internal/llvm/llvmbuildutils.cpp @@ -496,7 +496,7 @@ llvm::Value *LLVMBuildUtils::castValue(LLVMRegister *reg, Compiler::StaticType t } } -llvm::Type *LLVMBuildUtils::getType(Compiler::StaticType type) +llvm::Type *LLVMBuildUtils::getType(Compiler::StaticType type, bool isReturnType) { switch (type) { case Compiler::StaticType::Void: @@ -515,7 +515,10 @@ llvm::Type *LLVMBuildUtils::getType(Compiler::StaticType type) return m_builder.getVoidTy()->getPointerTo(); case Compiler::StaticType::Unknown: - return m_valueDataType->getPointerTo(); + if (isReturnType) + return m_valueDataType; + else + return m_valueDataType->getPointerTo(); default: assert(false); diff --git a/src/engine/internal/llvm/llvmbuildutils.h b/src/engine/internal/llvm/llvmbuildutils.h index 9ca28a1e..6bfeed3c 100644 --- a/src/engine/internal/llvm/llvmbuildutils.h +++ b/src/engine/internal/llvm/llvmbuildutils.h @@ -80,7 +80,7 @@ class LLVMBuildUtils llvm::Value *addAlloca(llvm::Type *type); llvm::Value *castValue(LLVMRegister *reg, Compiler::StaticType targetType); - llvm::Type *getType(Compiler::StaticType type); + llvm::Type *getType(Compiler::StaticType type, bool isReturnType); llvm::Value *isNaN(llvm::Value *num); llvm::Value *removeNaN(llvm::Value *num); diff --git a/src/engine/internal/llvm/llvmcodebuilder.cpp b/src/engine/internal/llvm/llvmcodebuilder.cpp index e5cc10a1..b189905f 100644 --- a/src/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/engine/internal/llvm/llvmcodebuilder.cpp @@ -100,7 +100,10 @@ CompilerValue *LLVMCodeBuilder::addFunctionCall(const std::string &functionName, if (returnType != Compiler::StaticType::Void) { auto reg = std::make_shared(returnType); - reg->isRawValue = true; + + if (returnType != Compiler::StaticType::Unknown) + reg->isRawValue = true; + ins->functionReturnReg = reg.get(); return addReg(reg, ins); } diff --git a/test/blocks/CMakeLists.txt b/test/blocks/CMakeLists.txt index e8694da9..2a8bd380 100644 --- a/test/blocks/CMakeLists.txt +++ b/test/blocks/CMakeLists.txt @@ -114,6 +114,7 @@ if (LIBSCRATCHCPP_ENABLE_SENSING_BLOCKS) GTest::gmock_main scratchcpp scratchcpp_mocks + block_test_deps ) gtest_discover_tests(sensing_blocks_test) diff --git a/test/blocks/sensing_blocks_test.cpp b/test/blocks/sensing_blocks_test.cpp index d1730f9c..356b94ac 100644 --- a/test/blocks/sensing_blocks_test.cpp +++ b/test/blocks/sensing_blocks_test.cpp @@ -1,15 +1,2774 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include "../common.h" #include "blocks/sensingblocks.h" +#include "util.h" using namespace libscratchcpp; +using namespace libscratchcpp::test; + +using ::testing::Return; +using ::testing::ReturnRef; class SensingBlocksTest : public testing::Test { public: - void SetUp() override { m_extension = std::make_unique(); } + struct QuestionSpy + { + MOCK_METHOD(void, asked, (const std::string &), ()); + MOCK_METHOD(void, aborted, (), ()); + }; + + void SetUp() override + { + m_extension = std::make_unique(); + m_engine = m_project.engine().get(); + m_extension->registerBlocks(m_engine); + m_extension->onInit(m_engine); + registerBlocks(m_engine, m_extension.get()); + + m_audioLoudness = std::make_shared(); + SensingBlocks::audioInput = &m_audioInput; + EXPECT_CALL(m_audioInput, audioLoudness()).WillRepeatedly(Return(m_audioLoudness)); + + SensingBlocks::clock = &m_clock; + } + + void TearDown() override + { + SensingBlocks::clearQuestions(); + SensingBlocks::audioInput = nullptr; + SensingBlocks::clock = nullptr; + } std::unique_ptr m_extension; + Project m_project; + IEngine *m_engine = nullptr; EngineMock m_engineMock; + std::shared_ptr m_audioLoudness; + ClockMock m_clock; + + private: + AudioInputMock m_audioInput; }; + +TEST_F(SensingBlocksTest, TouchingObject_Sprite_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite sprite; + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_touchingobject"); + builder.addDropdownInput("TOUCHINGOBJECTMENU", "Sprite2"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(3)); + EXPECT_CALL(m_engineMock, targetAt(3)).WillOnce(Return(&sprite)); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(*targetMock, touchingClones).WillOnce(Return(true)); + ValueData value = thread.runReporter(); + ASSERT_TRUE(value_toBool(&value)); + value_free(&value); + + EXPECT_CALL(*targetMock, touchingClones).WillOnce(Return(false)); + value = thread.runReporter(); + ASSERT_FALSE(value_toBool(&value)); + value_free(&value); +} + +TEST_F(SensingBlocksTest, TouchingObject_Sprite_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite sprite; + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "Sprite2"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_touchingobject"); + builder.addObscuredInput("TOUCHINGOBJECTMENU", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillRepeatedly(Return(5)); + EXPECT_CALL(m_engineMock, targetAt(5)).WillRepeatedly(Return(&sprite)); + + EXPECT_CALL(*targetMock, touchingClones).WillOnce(Return(true)); + ValueData value = thread.runReporter(); + ASSERT_TRUE(value_toBool(&value)); + value_free(&value); + + EXPECT_CALL(*targetMock, touchingClones).WillOnce(Return(false)); + value = thread.runReporter(); + ASSERT_FALSE(value_toBool(&value)); + value_free(&value); +} + +TEST_F(SensingBlocksTest, TouchingObject_Stage_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Stage stage; + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_touchingobject"); + builder.addDropdownInput("TOUCHINGOBJECTMENU", "_stage_"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillRepeatedly(Return(3)); + EXPECT_CALL(m_engineMock, targetAt(3)).WillRepeatedly(Return(&stage)); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(*targetMock, touchingClones).Times(0); + ValueData value = thread.runReporter(); + ASSERT_FALSE(value_toBool(&value)); + value_free(&value); +} + +TEST_F(SensingBlocksTest, TouchingObject_Stage_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Stage stage; + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "_stage_"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_touchingobject"); + builder.addObscuredInput("TOUCHINGOBJECTMENU", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillRepeatedly(Return(5)); + EXPECT_CALL(m_engineMock, targetAt(5)).WillRepeatedly(Return(&stage)); + + EXPECT_CALL(*targetMock, touchingClones).Times(0); + ValueData value = thread.runReporter(); + ASSERT_FALSE(value_toBool(&value)); + value_free(&value); +} + +TEST_F(SensingBlocksTest, TouchingObject_Mouse_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite sprite; + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_touchingobject"); + builder.addDropdownInput("TOUCHINGOBJECTMENU", "_mouse_"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget).Times(0); + EXPECT_CALL(m_engineMock, targetAt).Times(0); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, mouseX).WillOnce(Return(56.2)); + EXPECT_CALL(m_engineMock, mouseY).WillOnce(Return(-89.5)); + EXPECT_CALL(*targetMock, touchingPoint(56.2, -89.5)).WillOnce(Return(true)); + ValueData value = thread.runReporter(); + ASSERT_TRUE(value_toBool(&value)); + value_free(&value); + + EXPECT_CALL(m_engineMock, mouseX).WillOnce(Return(-12.7)); + EXPECT_CALL(m_engineMock, mouseY).WillOnce(Return(2.2)); + EXPECT_CALL(*targetMock, touchingPoint(-12.7, 2.2)).WillOnce(Return(false)); + value = thread.runReporter(); + ASSERT_FALSE(value_toBool(&value)); + value_free(&value); +} + +TEST_F(SensingBlocksTest, TouchingObject_Mouse_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite sprite; + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "_mouse_"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_touchingobject"); + builder.addObscuredInput("TOUCHINGOBJECTMENU", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget).Times(0); + EXPECT_CALL(m_engineMock, targetAt).Times(0); + + EXPECT_CALL(m_engineMock, mouseX).WillOnce(Return(56.2)); + EXPECT_CALL(m_engineMock, mouseY).WillOnce(Return(-89.5)); + EXPECT_CALL(*targetMock, touchingPoint(56.2, -89.5)).WillOnce(Return(true)); + ValueData value = thread.runReporter(); + ASSERT_TRUE(value_toBool(&value)); + value_free(&value); + + EXPECT_CALL(m_engineMock, mouseX).WillOnce(Return(-12.7)); + EXPECT_CALL(m_engineMock, mouseY).WillOnce(Return(2.2)); + EXPECT_CALL(*targetMock, touchingPoint(-12.7, 2.2)).WillOnce(Return(false)); + value = thread.runReporter(); + ASSERT_FALSE(value_toBool(&value)); + value_free(&value); +} + +TEST_F(SensingBlocksTest, TouchingObject_Edge_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite sprite; + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_touchingobject"); + builder.addDropdownInput("TOUCHINGOBJECTMENU", "_edge_"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget).Times(0); + EXPECT_CALL(m_engineMock, targetAt).Times(0); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, stageWidth()).WillOnce(Return(2)); + EXPECT_CALL(m_engineMock, stageHeight()).WillOnce(Return(2)); + EXPECT_CALL(*targetMock, boundingRect()).WillOnce(Return(Rect(-5, 5, 5, -5))); + ValueData value = thread.runReporter(); + ASSERT_TRUE(value_toBool(&value)); + value_free(&value); + + EXPECT_CALL(m_engineMock, stageWidth()).WillOnce(Return(10)); + EXPECT_CALL(m_engineMock, stageHeight()).WillOnce(Return(10)); + EXPECT_CALL(*targetMock, boundingRect()).WillOnce(Return(Rect(-5, 5, 5, -5))); + value = thread.runReporter(); + ASSERT_FALSE(value_toBool(&value)); + value_free(&value); +} + +TEST_F(SensingBlocksTest, TouchingObject_Edge_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite sprite; + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "_edge_"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_touchingobject"); + builder.addObscuredInput("TOUCHINGOBJECTMENU", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget).Times(0); + EXPECT_CALL(m_engineMock, targetAt).Times(0); + + EXPECT_CALL(m_engineMock, stageWidth()).WillOnce(Return(2)); + EXPECT_CALL(m_engineMock, stageHeight()).WillOnce(Return(2)); + EXPECT_CALL(*targetMock, boundingRect()).WillOnce(Return(Rect(-5, 5, 5, -5))); + ValueData value = thread.runReporter(); + ASSERT_TRUE(value_toBool(&value)); + value_free(&value); + + EXPECT_CALL(m_engineMock, stageWidth()).WillOnce(Return(10)); + EXPECT_CALL(m_engineMock, stageHeight()).WillOnce(Return(10)); + EXPECT_CALL(*targetMock, boundingRect()).WillOnce(Return(Rect(-5, 5, 5, -5))); + value = thread.runReporter(); + ASSERT_FALSE(value_toBool(&value)); + value_free(&value); +} + +TEST_F(SensingBlocksTest, TouchingObject_Invalid_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite sprite; + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_touchingobject"); + builder.addDropdownInput("TOUCHINGOBJECTMENU", "test"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("test")).WillOnce(Return(-1)); + EXPECT_CALL(m_engineMock, targetAt).WillRepeatedly(Return(nullptr)); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + ASSERT_FALSE(value_toBool(&value)); + value_free(&value); +} + +TEST_F(SensingBlocksTest, TouchingObject_Invalid_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite sprite; + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "test"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_touchingobject"); + builder.addObscuredInput("TOUCHINGOBJECTMENU", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("test")).WillRepeatedly(Return(-1)); + EXPECT_CALL(m_engineMock, targetAt).WillRepeatedly(Return(nullptr)); + ValueData value = thread.runReporter(); + ASSERT_FALSE(value_toBool(&value)); + value_free(&value); +} + +TEST_F(SensingBlocksTest, TouchingColor_String) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_touchingcolor"); + builder.addValueInput("COLOR", "#00ffff"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(*targetMock, touchingColor(rgb(0, 255, 255))).WillOnce(Return(true)); + ValueData value = thread.runReporter(); + ASSERT_TRUE(value_toBool(&value)); + value_free(&value); + + EXPECT_CALL(*targetMock, touchingColor(rgb(0, 255, 255))).WillOnce(Return(false)); + value = thread.runReporter(); + ASSERT_FALSE(value_toBool(&value)); + value_free(&value); +} + +TEST_F(SensingBlocksTest, TouchingColor_Number) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_touchingcolor"); + builder.addValueInput("COLOR", rgb(255, 54, 23)); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(*targetMock, touchingColor(rgb(255, 54, 23))).WillOnce(Return(true)); + ValueData value = thread.runReporter(); + ASSERT_TRUE(value_toBool(&value)); + value_free(&value); + + EXPECT_CALL(*targetMock, touchingColor(rgb(255, 54, 23))).WillOnce(Return(false)); + value = thread.runReporter(); + ASSERT_FALSE(value_toBool(&value)); + value_free(&value); +} + +TEST_F(SensingBlocksTest, ColorIsTouchingColor_StringNumber) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_coloristouchingcolor"); + builder.addValueInput("COLOR", "#00ffff"); + builder.addValueInput("COLOR2", rgb(255, 54, 23)); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(*targetMock, touchingColor(rgb(0, 255, 255), rgb(255, 54, 23))).WillOnce(Return(true)); + ValueData value = thread.runReporter(); + ASSERT_TRUE(value_toBool(&value)); + value_free(&value); + + EXPECT_CALL(*targetMock, touchingColor(rgb(0, 255, 255), rgb(255, 54, 23))).WillOnce(Return(false)); + value = thread.runReporter(); + ASSERT_FALSE(value_toBool(&value)); + value_free(&value); +} + +TEST_F(SensingBlocksTest, ColorIsTouchingColor_NumberString) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_coloristouchingcolor"); + builder.addValueInput("COLOR", rgb(255, 54, 23)); + builder.addValueInput("COLOR2", "#00ffff"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(*targetMock, touchingColor(rgb(255, 54, 23), rgb(0, 255, 255))).WillOnce(Return(true)); + ValueData value = thread.runReporter(); + ASSERT_TRUE(value_toBool(&value)); + value_free(&value); + + EXPECT_CALL(*targetMock, touchingColor(rgb(255, 54, 23), rgb(0, 255, 255))).WillOnce(Return(false)); + value = thread.runReporter(); + ASSERT_FALSE(value_toBool(&value)); + value_free(&value); +} + +TEST_F(SensingBlocksTest, DistanceTo_Sprite_CompileTime) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + Sprite targetSprite; + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sensing_distanceto"); + builder.addDropdownInput("DISTANCETOMENU", "Sprite2"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(3)); + EXPECT_CALL(m_engineMock, targetAt(3)).WillOnce(Return(&targetSprite)); + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + sprite->setX(-50.35); + sprite->setY(33.04); + + targetSprite.setX(108.564); + targetSprite.setY(-168.452); + + ValueData value = thread.runReporter(); + ASSERT_EQ(std::round(value_toDouble(&value) * 10000) / 10000, 256.6178); + value_free(&value); +} + +TEST_F(SensingBlocksTest, DistanceTo_Sprite_Runtime) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + Sprite targetSprite; + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "Sprite2"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_distanceto"); + builder.addObscuredInput("DISTANCETOMENU", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + sprite->setX(-50.35); + sprite->setY(33.04); + + targetSprite.setX(108.564); + targetSprite.setY(-168.452); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillRepeatedly(Return(5)); + EXPECT_CALL(m_engineMock, targetAt(5)).WillRepeatedly(Return(&targetSprite)); + + ValueData value = thread.runReporter(); + ASSERT_EQ(std::round(value_toDouble(&value) * 10000) / 10000, 256.6178); + value_free(&value); +} + +TEST_F(SensingBlocksTest, DistanceTo_Sprite_FromStage) +{ + auto stage = std::make_shared(); + stage->setEngine(&m_engineMock); + + Sprite targetSprite; + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + builder.addBlock("sensing_distanceto"); + builder.addDropdownInput("DISTANCETOMENU", "Sprite2"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillRepeatedly(Return(3)); + EXPECT_CALL(m_engineMock, targetAt(3)).WillRepeatedly(Return(&targetSprite)); + Compiler compiler(&m_engineMock, stage.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(stage.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(stage.get(), &m_engineMock, &script); + + targetSprite.setX(108.564); + targetSprite.setY(-168.452); + + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 10000.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, DistanceTo_Stage_CompileTime) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + Stage stage; + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sensing_distanceto"); + builder.addDropdownInput("DISTANCETOMENU", "_stage_"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillRepeatedly(Return(3)); + EXPECT_CALL(m_engineMock, targetAt(3)).WillRepeatedly(Return(&stage)); + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 10000.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, DistanceTo_Stage_Runtime) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + Stage stage; + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "_stage_"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_distanceto"); + builder.addObscuredInput("DISTANCETOMENU", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillRepeatedly(Return(5)); + EXPECT_CALL(m_engineMock, targetAt(5)).WillRepeatedly(Return(&stage)); + + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 10000.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, DistanceTo_Mouse_CompileTime) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sensing_distanceto"); + builder.addDropdownInput("DISTANCETOMENU", "_mouse_"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget).Times(0); + EXPECT_CALL(m_engineMock, targetAt).Times(0); + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + sprite->setX(-50.35); + sprite->setY(33.04); + + EXPECT_CALL(m_engineMock, mouseX).WillOnce(Return(-239.98)); + EXPECT_CALL(m_engineMock, mouseY).WillOnce(Return(-86.188)); + ValueData value = thread.runReporter(); + ASSERT_EQ(std::round(value_toDouble(&value) * 10000) / 10000, 223.9974); + value_free(&value); +} + +TEST_F(SensingBlocksTest, DistanceTo_Mouse_Runtime) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "_mouse_"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_distanceto"); + builder.addObscuredInput("DISTANCETOMENU", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget).Times(0); + EXPECT_CALL(m_engineMock, targetAt).Times(0); + + sprite->setX(-50.35); + sprite->setY(33.04); + + EXPECT_CALL(m_engineMock, mouseX).WillOnce(Return(-239.98)); + EXPECT_CALL(m_engineMock, mouseY).WillOnce(Return(-86.188)); + ValueData value = thread.runReporter(); + ASSERT_EQ(std::round(value_toDouble(&value) * 10000) / 10000, 223.9974); + value_free(&value); +} + +TEST_F(SensingBlocksTest, DistanceTo_Invalid_CompileTime) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sensing_distanceto"); + builder.addDropdownInput("DISTANCETOMENU", "test"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("test")).WillOnce(Return(-1)); + EXPECT_CALL(m_engineMock, targetAt).WillRepeatedly(Return(nullptr)); + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 10000.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, DistanceTo_Invalid_Runtime) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "test"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_distanceto"); + builder.addObscuredInput("DISTANCETOMENU", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("test")).WillRepeatedly(Return(-1)); + EXPECT_CALL(m_engineMock, targetAt).WillRepeatedly(Return(nullptr)); + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 10000.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, AskAndWait_VisibleSprite) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + // Build + ScriptBuilder builder1(m_extension.get(), m_engine, sprite); + builder1.addBlock("sensing_askandwait"); + builder1.addValueInput("QUESTION", "test"); + Block *block1 = builder1.currentBlock(); + + Compiler compiler1(&m_engineMock, sprite.get()); + auto code1 = compiler1.compile(block1); + Script script1(sprite.get(), block1, &m_engineMock); + script1.setCode(code1); + Thread thread1(sprite.get(), &m_engineMock, &script1); + + ScriptBuilder builder2(m_extension.get(), m_engine, sprite); + builder2.addBlock("sensing_answer"); + Block *block2 = builder2.currentBlock(); + + Compiler compiler2(m_engine, sprite.get()); + auto code2 = compiler2.compile(block2, Compiler::CodeType::Reporter); + Script script2(sprite.get(), block2, m_engine); + script2.setCode(code2); + Thread thread2(sprite.get(), m_engine, &script2); + + // Run + QuestionSpy spy; + auto asked = std::bind(&QuestionSpy::asked, &spy, std::placeholders::_1); + sigslot::signal askedSignal; + askedSignal.connect(asked); + + sprite->bubble()->setType(TextBubble::Type::Think); + sprite->setVisible(true); + + EXPECT_CALL(m_engineMock, questionAsked()).WillOnce(ReturnRef(askedSignal)); + EXPECT_CALL(spy, asked("")); // visible => empty question + thread1.run(); + ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Say); + ASSERT_EQ(sprite->bubble()->text(), "test"); + + ValueData value = thread2.runReporter(); + std::string str; + value_toString(&value, &str); + value_free(&value); + ASSERT_EQ(str, ""); // not answered yet + + ASSERT_FALSE(thread1.isFinished()); + + // Answer + m_engine->questionAnswered()("test answer"); + ASSERT_TRUE(sprite->bubble()->text().empty()); + + value = thread2.runReporter(); + value_toString(&value, &str); + value_free(&value); + ASSERT_EQ(str, "test answer"); + + thread1.run(); + ASSERT_TRUE(thread1.isFinished()); +} + +TEST_F(SensingBlocksTest, AskAndWait_VisibleSpriteBefore) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + // Build + ScriptBuilder builder1(m_extension.get(), m_engine, sprite); + builder1.addBlock("sensing_askandwait"); + builder1.addValueInput("QUESTION", "test"); + Block *block1 = builder1.currentBlock(); + + Compiler compiler1(&m_engineMock, sprite.get()); + auto code1 = compiler1.compile(block1); + Script script1(sprite.get(), block1, &m_engineMock); + script1.setCode(code1); + Thread thread1(sprite.get(), &m_engineMock, &script1); + + ScriptBuilder builder2(m_extension.get(), m_engine, sprite); + builder2.addBlock("sensing_answer"); + Block *block2 = builder2.currentBlock(); + + Compiler compiler2(m_engine, sprite.get()); + auto code2 = compiler2.compile(block2, Compiler::CodeType::Reporter); + Script script2(sprite.get(), block2, m_engine); + script2.setCode(code2); + Thread thread2(sprite.get(), m_engine, &script2); + + // Run + QuestionSpy spy; + auto asked = std::bind(&QuestionSpy::asked, &spy, std::placeholders::_1); + sigslot::signal askedSignal; + askedSignal.connect(asked); + + sprite->bubble()->setType(TextBubble::Type::Think); + sprite->setVisible(true); + + EXPECT_CALL(m_engineMock, questionAsked()).WillOnce(ReturnRef(askedSignal)); + EXPECT_CALL(spy, asked("")); // visible => empty question + thread1.run(); + ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Say); + ASSERT_EQ(sprite->bubble()->text(), "test"); + + ASSERT_FALSE(thread1.isFinished()); + + // Answer + sprite->setVisible(false); + m_engine->questionAnswered()("test answer"); + ASSERT_TRUE(sprite->bubble()->text().empty()); // sprite was visible when asked + + ValueData value = thread2.runReporter(); + std::string str; + value_toString(&value, &str); + value_free(&value); + ASSERT_EQ(str, "test answer"); + + thread1.run(); + ASSERT_TRUE(thread1.isFinished()); +} + +TEST_F(SensingBlocksTest, AskAndWait_InvisibleSprite) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + // Build + ScriptBuilder builder1(m_extension.get(), m_engine, sprite); + builder1.addBlock("sensing_askandwait"); + builder1.addValueInput("QUESTION", "test"); + Block *block1 = builder1.currentBlock(); + + Compiler compiler1(&m_engineMock, sprite.get()); + auto code1 = compiler1.compile(block1); + Script script1(sprite.get(), block1, &m_engineMock); + script1.setCode(code1); + Thread thread1(sprite.get(), &m_engineMock, &script1); + + ScriptBuilder builder2(m_extension.get(), m_engine, sprite); + builder2.addBlock("sensing_answer"); + Block *block2 = builder2.currentBlock(); + + Compiler compiler2(m_engine, sprite.get()); + auto code2 = compiler2.compile(block2, Compiler::CodeType::Reporter); + Script script2(sprite.get(), block2, m_engine); + script2.setCode(code2); + Thread thread2(sprite.get(), m_engine, &script2); + + // Run + QuestionSpy spy; + auto asked = std::bind(&QuestionSpy::asked, &spy, std::placeholders::_1); + sigslot::signal askedSignal; + askedSignal.connect(asked); + + sprite->bubble()->setType(TextBubble::Type::Think); + sprite->bubble()->setText("hello"); + sprite->setVisible(false); + + EXPECT_CALL(m_engineMock, questionAsked()).WillOnce(ReturnRef(askedSignal)); + EXPECT_CALL(spy, asked("test")); + thread1.run(); + ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Think); + ASSERT_EQ(sprite->bubble()->text(), "hello"); + + ASSERT_FALSE(thread1.isFinished()); + + // Answer + m_engine->questionAnswered()("test answer"); + ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Think); + ASSERT_EQ(sprite->bubble()->text(), "hello"); + + ValueData value = thread2.runReporter(); + std::string str; + value_toString(&value, &str); + value_free(&value); + ASSERT_EQ(str, "test answer"); + + thread1.run(); + ASSERT_TRUE(thread1.isFinished()); +} + +TEST_F(SensingBlocksTest, AskAndWait_InvisibleSpriteBefore) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + // Build + ScriptBuilder builder1(m_extension.get(), m_engine, sprite); + builder1.addBlock("sensing_askandwait"); + builder1.addValueInput("QUESTION", "test"); + Block *block1 = builder1.currentBlock(); + + Compiler compiler1(&m_engineMock, sprite.get()); + auto code1 = compiler1.compile(block1); + Script script1(sprite.get(), block1, &m_engineMock); + script1.setCode(code1); + Thread thread1(sprite.get(), &m_engineMock, &script1); + + ScriptBuilder builder2(m_extension.get(), m_engine, sprite); + builder2.addBlock("sensing_answer"); + Block *block2 = builder2.currentBlock(); + + Compiler compiler2(m_engine, sprite.get()); + auto code2 = compiler2.compile(block2, Compiler::CodeType::Reporter); + Script script2(sprite.get(), block2, m_engine); + script2.setCode(code2); + Thread thread2(sprite.get(), m_engine, &script2); + + // Run + QuestionSpy spy; + auto asked = std::bind(&QuestionSpy::asked, &spy, std::placeholders::_1); + sigslot::signal askedSignal; + askedSignal.connect(asked); + + sprite->bubble()->setType(TextBubble::Type::Think); + sprite->bubble()->setText("hello"); + sprite->setVisible(false); + + EXPECT_CALL(m_engineMock, questionAsked()).WillOnce(ReturnRef(askedSignal)); + EXPECT_CALL(spy, asked("test")); + thread1.run(); + ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Think); + ASSERT_EQ(sprite->bubble()->text(), "hello"); + + ASSERT_FALSE(thread1.isFinished()); + + // Answer + sprite->setVisible(true); + m_engine->questionAnswered()("test answer"); + ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Think); + ASSERT_EQ(sprite->bubble()->text(), "hello"); // sprite was invisible when asked + + ValueData value = thread2.runReporter(); + std::string str; + value_toString(&value, &str); + ASSERT_EQ(str, "test answer"); + value_free(&value); + + thread1.run(); + ASSERT_TRUE(thread1.isFinished()); +} + +TEST_F(SensingBlocksTest, AskAndWait_Stage) +{ + auto stage = std::make_shared(); + stage->setEngine(&m_engineMock); + + // Build + ScriptBuilder builder1(m_extension.get(), m_engine, stage); + builder1.addBlock("sensing_askandwait"); + builder1.addValueInput("QUESTION", "test"); + Block *block1 = builder1.currentBlock(); + + Compiler compiler1(&m_engineMock, stage.get()); + auto code1 = compiler1.compile(block1); + Script script1(stage.get(), block1, &m_engineMock); + script1.setCode(code1); + Thread thread1(stage.get(), &m_engineMock, &script1); + + ScriptBuilder builder2(m_extension.get(), m_engine, stage); + builder2.addBlock("sensing_answer"); + Block *block2 = builder2.currentBlock(); + + Compiler compiler2(m_engine, stage.get()); + auto code2 = compiler2.compile(block2, Compiler::CodeType::Reporter); + Script script2(stage.get(), block2, m_engine); + script2.setCode(code2); + Thread thread2(stage.get(), m_engine, &script2); + + ASSERT_FALSE(thread1.isFinished()); + + // Run + QuestionSpy spy; + auto asked = std::bind(&QuestionSpy::asked, &spy, std::placeholders::_1); + sigslot::signal askedSignal; + askedSignal.connect(asked); + + stage->bubble()->setType(TextBubble::Type::Think); + stage->bubble()->setText("hello"); + + EXPECT_CALL(m_engineMock, questionAsked()).WillOnce(ReturnRef(askedSignal)); + EXPECT_CALL(spy, asked("test")); + thread1.run(); + ASSERT_EQ(stage->bubble()->type(), TextBubble::Type::Think); + ASSERT_EQ(stage->bubble()->text(), "hello"); + + // Answer + m_engine->questionAnswered()("test answer"); + ASSERT_EQ(stage->bubble()->type(), TextBubble::Type::Think); + ASSERT_EQ(stage->bubble()->text(), "hello"); + + ValueData value = thread2.runReporter(); + std::string str; + value_toString(&value, &str); + value_free(&value); + ASSERT_EQ(str, "test answer"); + + thread1.run(); + ASSERT_TRUE(thread1.isFinished()); +} + +TEST_F(SensingBlocksTest, AskAndWait_MultipleQuestions) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + // Build + ScriptBuilder builder1(m_extension.get(), m_engine, sprite); + + builder1.addBlock("sensing_askandwait"); + builder1.addValueInput("QUESTION", "test1"); + Block *block1 = builder1.currentBlock(); + + builder1.addBlock("sensing_askandwait"); + builder1.addValueInput("QUESTION", "test2"); + builder1.currentBlock(); // force build + + Compiler compiler1(&m_engineMock, sprite.get()); + auto code1 = compiler1.compile(block1); + Script script1(sprite.get(), block1, &m_engineMock); + script1.setCode(code1); + Thread thread1(sprite.get(), &m_engineMock, &script1); + + ScriptBuilder builder2(m_extension.get(), m_engine, sprite); + builder2.addBlock("sensing_answer"); + Block *block2 = builder2.currentBlock(); + + Compiler compiler2(m_engine, sprite.get()); + auto code2 = compiler2.compile(block2, Compiler::CodeType::Reporter); + Script script2(sprite.get(), block2, m_engine); + script2.setCode(code2); + Thread thread2(sprite.get(), m_engine, &script2); + + // Run + QuestionSpy spy; + auto asked = std::bind(&QuestionSpy::asked, &spy, std::placeholders::_1); + sigslot::signal askedSignal; + askedSignal.connect(asked); + + sprite->setVisible(false); + + // Ask (1/2) + EXPECT_CALL(m_engineMock, questionAsked()).WillOnce(ReturnRef(askedSignal)); + EXPECT_CALL(spy, asked("test1")); + thread1.run(); + ASSERT_FALSE(thread1.isFinished()); + + // Answer (1/2) + m_engine->questionAnswered()("test answer 1"); + + ValueData value = thread2.runReporter(); + std::string str; + value_toString(&value, &str); + value_free(&value); + ASSERT_EQ(str, "test answer 1"); + + // Ask (2/2) + EXPECT_CALL(m_engineMock, questionAsked()).WillOnce(ReturnRef(askedSignal)); + EXPECT_CALL(spy, asked("test2")); + thread1.run(); + ASSERT_FALSE(thread1.isFinished()); + + // Answer (2/2) + m_engine->questionAnswered()("test answer 2"); + + value = thread2.runReporter(); + value_toString(&value, &str); + value_free(&value); + ASSERT_EQ(str, "test answer 2"); + + EXPECT_CALL(m_engineMock, questionAsked).Times(0); + thread1.run(); + ASSERT_TRUE(thread1.isFinished()); +} + +TEST_F(SensingBlocksTest, AskAndWait_KillThread) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + // Build + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sensing_askandwait"); + builder.addValueInput("QUESTION", "test"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread1(sprite.get(), &m_engineMock, &script); + + // Run + QuestionSpy spy; + auto asked = std::bind(&QuestionSpy::asked, &spy, std::placeholders::_1); + sigslot::signal askedSignal; + askedSignal.connect(asked); + + sprite->bubble()->setText("hello"); + sprite->setVisible(false); + + EXPECT_CALL(m_engineMock, questionAsked()).WillOnce(ReturnRef(askedSignal)); + EXPECT_CALL(spy, asked); + thread1.run(); + + // Kill + auto aborted = std::bind(&QuestionSpy::aborted, &spy); + m_engine->questionAborted().connect(aborted); + + EXPECT_CALL(spy, aborted()); + m_engine->threadAboutToStop()(&thread1); + thread1.kill(); + ASSERT_TRUE(sprite->bubble()->text().empty()); + + // Running the script again should work because the question has been removed + EXPECT_CALL(m_engineMock, questionAsked()).WillOnce(ReturnRef(askedSignal)); + EXPECT_CALL(spy, asked); + Thread thread2(sprite.get(), &m_engineMock, &script); + thread2.run(); +} + +TEST_F(SensingBlocksTest, KeyPressed_Space) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_keypressed"); + builder.addDropdownInput("KEY_OPTION", "space"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, keyPressed("space")).WillOnce(Return(true)); + ValueData value = thread.runReporter(); + ASSERT_TRUE(value_toBool(&value)); + value_free(&value); + + EXPECT_CALL(m_engineMock, keyPressed("space")).WillOnce(Return(false)); + value = thread.runReporter(); + ASSERT_FALSE(value_toBool(&value)); + value_free(&value); +} + +TEST_F(SensingBlocksTest, KeyPressed_M) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_keypressed"); + builder.addDropdownInput("KEY_OPTION", "m"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, keyPressed("m")).WillOnce(Return(true)); + ValueData value = thread.runReporter(); + ASSERT_TRUE(value_toBool(&value)); + value_free(&value); + + EXPECT_CALL(m_engineMock, keyPressed("m")).WillOnce(Return(false)); + value = thread.runReporter(); + ASSERT_FALSE(value_toBool(&value)); + value_free(&value); +} + +TEST_F(SensingBlocksTest, MouseDown) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_mousedown"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, mousePressed()).WillOnce(Return(true)); + ValueData value = thread.runReporter(); + ASSERT_TRUE(value_toBool(&value)); + value_free(&value); + + EXPECT_CALL(m_engineMock, mousePressed()).WillOnce(Return(false)); + value = thread.runReporter(); + ASSERT_FALSE(value_toBool(&value)); + value_free(&value); +} + +TEST_F(SensingBlocksTest, MouseX) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_mousex"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, mouseX()).WillOnce(Return(53.7)); + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 53.7); + value_free(&value); +} + +TEST_F(SensingBlocksTest, MouseY) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_mousey"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, mouseY()).WillOnce(Return(-78.21)); + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), -78.21); + value_free(&value); +} + +TEST_F(SensingBlocksTest, SetDragMode_Draggable) +{ + auto sprite = std::make_shared(); + sprite->setDraggable(false); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sensing_setdragmode"); + builder.addDropdownField("DRAG_MODE", "draggable"); + builder.build(); + + builder.run(); + ASSERT_TRUE(sprite->draggable()); + + builder.run(); + ASSERT_TRUE(sprite->draggable()); +} + +TEST_F(SensingBlocksTest, SetDragMode_NotDraggable) +{ + auto sprite = std::make_shared(); + sprite->setDraggable(true); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sensing_setdragmode"); + builder.addDropdownField("DRAG_MODE", "not draggable"); + builder.build(); + + builder.run(); + ASSERT_FALSE(sprite->draggable()); + + builder.run(); + ASSERT_FALSE(sprite->draggable()); +} + +TEST_F(SensingBlocksTest, SetDragMode_Invalid) +{ + auto sprite = std::make_shared(); + sprite->setDraggable(true); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sensing_setdragmode"); + builder.addDropdownField("DRAG_MODE", "lorem ipsum"); + builder.build(); + + builder.run(); + ASSERT_TRUE(sprite->draggable()); + + sprite->setDraggable(false); + + builder.run(); + ASSERT_FALSE(sprite->draggable()); +} + +TEST_F(SensingBlocksTest, SetDragMode_Stage) +{ + auto stage = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + builder.addBlock("sensing_setdragmode"); + builder.addDropdownField("DRAG_MODE", "draggable"); + builder.build(); + + builder.run(); +} + +TEST_F(SensingBlocksTest, Loudness) +{ + auto targetMock = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_loudness"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(*m_audioLoudness, getLoudness()).WillOnce(Return(62)); + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 62); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Loud_Below) +{ + auto targetMock = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_loud"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(*m_audioLoudness, getLoudness()).WillOnce(Return(9)); + ValueData value = thread.runReporter(); + ASSERT_FALSE(value_toBool(&value)); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Loud_Equal) +{ + auto targetMock = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_loud"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(*m_audioLoudness, getLoudness()).WillOnce(Return(10)); + ValueData value = thread.runReporter(); + ASSERT_FALSE(value_toBool(&value)); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Loud_Above) +{ + auto targetMock = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_loud"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(*m_audioLoudness, getLoudness()).WillOnce(Return(11)); + ValueData value = thread.runReporter(); + ASSERT_TRUE(value_toBool(&value)); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Timer) +{ + auto targetMock = std::make_shared(); + + TimerMock timer; + EXPECT_CALL(m_engineMock, timer()).WillOnce(Return(&timer)); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_timer"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(timer, value()).WillOnce(Return(23.4)); + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 23.4); + value_free(&value); +} + +TEST_F(SensingBlocksTest, ResetTimer) +{ + auto targetMock = std::make_shared(); + + TimerMock timer; + EXPECT_CALL(m_engineMock, timer()).WillOnce(Return(&timer)); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_resettimer"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(timer, reset()); + thread.run(); +} + +TEST_F(SensingBlocksTest, Of_XPositionOfSprite_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite targetSprite; + targetSprite.setX(65.41); + targetSprite.setY(-56.28); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "x position"); + builder.addDropdownInput("OBJECT", "Sprite2"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&targetSprite)); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 65.41); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_XPositionOfSprite_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite targetSprite; + targetSprite.setX(65.41); + targetSprite.setY(-56.28); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "Sprite2"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "x position"); + builder.addObscuredInput("OBJECT", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&targetSprite)); + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 65.41); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_YPositionOfSprite_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite targetSprite; + targetSprite.setX(65.41); + targetSprite.setY(-56.28); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "y position"); + builder.addDropdownInput("OBJECT", "Sprite2"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&targetSprite)); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), -56.28); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_YPositionOfSprite_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite targetSprite; + targetSprite.setX(65.41); + targetSprite.setY(-56.28); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "Sprite2"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "y position"); + builder.addObscuredInput("OBJECT", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&targetSprite)); + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), -56.28); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_DirectionOfSprite_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite targetSprite; + targetSprite.setDirection(73.8); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "direction"); + builder.addDropdownInput("OBJECT", "Sprite2"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&targetSprite)); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 73.8); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_DirectionOfSprite_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite targetSprite; + targetSprite.setDirection(73.8); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "Sprite2"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "direction"); + builder.addObscuredInput("OBJECT", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&targetSprite)); + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 73.8); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_CostumeNumberOfSprite_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite targetSprite; + targetSprite.addCostume(std::make_shared("costume1", "a", "png")); + targetSprite.addCostume(std::make_shared("costume2", "b", "svg")); + targetSprite.setCostumeIndex(1); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "costume #"); + builder.addDropdownInput("OBJECT", "Sprite2"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&targetSprite)); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 2.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_CostumeNumberOfSprite_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite targetSprite; + targetSprite.addCostume(std::make_shared("costume1", "a", "png")); + targetSprite.addCostume(std::make_shared("costume2", "b", "svg")); + targetSprite.setCostumeIndex(1); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "Sprite2"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "costume #"); + builder.addObscuredInput("OBJECT", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&targetSprite)); + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 2.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_CostumeNameOfSprite_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite targetSprite; + targetSprite.addCostume(std::make_shared("costume1", "a", "png")); + targetSprite.addCostume(std::make_shared("costume2", "b", "svg")); + targetSprite.setCostumeIndex(1); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "costume name"); + builder.addDropdownInput("OBJECT", "Sprite2"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&targetSprite)); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + std::string str; + value_toString(&value, &str); + value_free(&value); + ASSERT_EQ(str, "costume2"); +} + +TEST_F(SensingBlocksTest, Of_CostumeNameOfSprite_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite targetSprite; + targetSprite.addCostume(std::make_shared("costume1", "a", "png")); + targetSprite.addCostume(std::make_shared("costume2", "b", "svg")); + targetSprite.setCostumeIndex(1); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "Sprite2"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "costume name"); + builder.addObscuredInput("OBJECT", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&targetSprite)); + ValueData value = thread.runReporter(); + std::string str; + value_toString(&value, &str); + value_free(&value); + ASSERT_EQ(str, "costume2"); +} + +TEST_F(SensingBlocksTest, Of_SizeOfSprite_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite targetSprite; + targetSprite.setSize(47.32); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "size"); + builder.addDropdownInput("OBJECT", "Sprite2"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&targetSprite)); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 47.32); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_SizeOfSprite_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite targetSprite; + targetSprite.setSize(47.32); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "Sprite2"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "size"); + builder.addObscuredInput("OBJECT", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&targetSprite)); + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 47.32); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_VolumeOfTarget_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + TargetMock target; + target.setVolume(89.46); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "volume"); + builder.addDropdownInput("OBJECT", "test"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("test")).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&target)); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 89.46); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_VolumeOfTarget_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + TargetMock target; + target.setVolume(89.46); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "test"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "volume"); + builder.addObscuredInput("OBJECT", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("test")).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&target)); + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 89.46); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_VariableOfTarget_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + TargetMock target; + auto v1 = std::make_shared("v1", "var1", 64.13); + auto v2 = std::make_shared("v2", "var2", 98); + auto v3 = std::make_shared("v3", "var3", -0.85); + target.addVariable(v1); + target.addVariable(v2); + target.addVariable(v3); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "var2"); + builder.addDropdownInput("OBJECT", "test"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("test")).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&target)); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 98.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_VariableOfTarget_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + TargetMock target; + auto v1 = std::make_shared("v1", "var1", 64.13); + auto v2 = std::make_shared("v2", "var2", 98); + auto v3 = std::make_shared("v3", "var3", -0.85); + target.addVariable(v1); + target.addVariable(v2); + target.addVariable(v3); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "test"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "var2"); + builder.addObscuredInput("OBJECT", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("test")).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&target)); + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 98.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_BackdropNumberOfStage_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Stage stage; + stage.addCostume(std::make_shared("backdrop1", "a", "png")); + stage.addCostume(std::make_shared("backdrop2", "b", "svg")); + stage.addCostume(std::make_shared("backdrop3", "b", "svg")); + stage.setCostumeIndex(2); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "backdrop #"); + builder.addDropdownInput("OBJECT", "_stage_"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillRepeatedly(Return(3)); + EXPECT_CALL(m_engineMock, targetAt(3)).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(&stage)); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 3.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_BackdropNumberOfStage_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Stage stage; + stage.addCostume(std::make_shared("backdrop1", "a", "png")); + stage.addCostume(std::make_shared("backdrop2", "b", "svg")); + stage.addCostume(std::make_shared("backdrop3", "b", "svg")); + stage.setCostumeIndex(2); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "_stage_"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "backdrop #"); + builder.addObscuredInput("OBJECT", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillRepeatedly(Return(3)); + EXPECT_CALL(m_engineMock, targetAt(3)).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(&stage)); + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 3.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_BackdropNameOfStage_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Stage stage; + stage.addCostume(std::make_shared("backdrop1", "a", "png")); + stage.addCostume(std::make_shared("backdrop2", "b", "svg")); + stage.addCostume(std::make_shared("backdrop3", "b", "svg")); + stage.setCostumeIndex(2); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "backdrop name"); + builder.addDropdownInput("OBJECT", "_stage_"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillRepeatedly(Return(3)); + EXPECT_CALL(m_engineMock, targetAt(3)).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(&stage)); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + std::string str; + value_toString(&value, &str); + value_free(&value); + ASSERT_EQ(str, "backdrop3"); +} + +TEST_F(SensingBlocksTest, Of_BackdropNameOfStage_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Stage stage; + stage.addCostume(std::make_shared("backdrop1", "a", "png")); + stage.addCostume(std::make_shared("backdrop2", "b", "svg")); + stage.addCostume(std::make_shared("backdrop3", "b", "svg")); + stage.setCostumeIndex(2); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "_stage_"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "backdrop name"); + builder.addObscuredInput("OBJECT", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillRepeatedly(Return(3)); + EXPECT_CALL(m_engineMock, targetAt(3)).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(&stage)); + ValueData value = thread.runReporter(); + std::string str; + value_toString(&value, &str); + value_free(&value); + ASSERT_EQ(str, "backdrop3"); +} + +TEST_F(SensingBlocksTest, Of_InvalidTarget_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "volume"); + builder.addDropdownInput("OBJECT", "test"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("test")).WillOnce(Return(-1)); + EXPECT_CALL(m_engineMock, targetAt(-1)).WillRepeatedly(Return(nullptr)); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 0.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_InvalidTarget_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "test"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "volume"); + builder.addObscuredInput("OBJECT", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("test")).WillOnce(Return(-1)); + EXPECT_CALL(m_engineMock, targetAt(-1)).WillRepeatedly(Return(nullptr)); + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 0.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_InvalidProperty_Sprite_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite targetSprite; + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "test"); + builder.addDropdownInput("OBJECT", "Sprite2"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillRepeatedly(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillRepeatedly(Return(&targetSprite)); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 0.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_InvalidProperty_Sprite_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite targetSprite; + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "Sprite2"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "test"); + builder.addObscuredInput("OBJECT", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillRepeatedly(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillRepeatedly(Return(&targetSprite)); + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 0.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_InvalidProperty_Stage_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Stage stage; + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "test"); + builder.addDropdownInput("OBJECT", "Sprite2"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillRepeatedly(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillRepeatedly(Return(&stage)); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 0.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_InvalidProperty_Stage_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Stage stage; + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "Sprite2"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "test"); + builder.addObscuredInput("OBJECT", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillRepeatedly(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillRepeatedly(Return(&stage)); + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 0.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_Invalid_XPositionOfStage_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Stage stage; + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "x position"); + builder.addDropdownInput("OBJECT", "_stage_"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillRepeatedly(Return(3)); + EXPECT_CALL(m_engineMock, targetAt(3)).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(&stage)); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 0.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_Invalid_DirectionOfStage_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Stage stage; + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "_stage_"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "direction"); + builder.addObscuredInput("OBJECT", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillRepeatedly(Return(3)); + EXPECT_CALL(m_engineMock, targetAt(3)).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(&stage)); + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 0.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_Invalid_CostumeNumberOfStage_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Stage stage; + stage.addCostume(std::make_shared("backdrop1", "a", "png")); + stage.addCostume(std::make_shared("backdrop2", "b", "svg")); + stage.addCostume(std::make_shared("backdrop3", "b", "svg")); + stage.setCostumeIndex(2); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "costume #"); + builder.addDropdownInput("OBJECT", "_stage_"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillRepeatedly(Return(3)); + EXPECT_CALL(m_engineMock, targetAt(3)).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(&stage)); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 0.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_Invalid_CostumeNameOfStage_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Stage stage; + stage.addCostume(std::make_shared("backdrop1", "a", "png")); + stage.addCostume(std::make_shared("backdrop2", "b", "svg")); + stage.addCostume(std::make_shared("backdrop3", "b", "svg")); + stage.setCostumeIndex(2); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "_stage_"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "costume name"); + builder.addObscuredInput("OBJECT", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillRepeatedly(Return(3)); + EXPECT_CALL(m_engineMock, targetAt(3)).WillRepeatedly(Return(&stage)); + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(&stage)); + ValueData value = thread.runReporter(); + std::string str; + value_toString(&value, &str); + value_free(&value); + ASSERT_EQ(str, "0"); +} + +TEST_F(SensingBlocksTest, Of_Invalid_BackdropNameOfSprite_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite targetSprite; + targetSprite.addCostume(std::make_shared("costume1", "a", "png")); + targetSprite.addCostume(std::make_shared("costume2", "b", "svg")); + targetSprite.setCostumeIndex(1); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "backdrop name"); + builder.addDropdownInput("OBJECT", "Sprite2"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&targetSprite)); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + std::string str; + value_toString(&value, &str); + value_free(&value); + ASSERT_EQ(str, "0"); +} + +TEST_F(SensingBlocksTest, Of_Invalid_BackdropNumberOfSprite_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite targetSprite; + targetSprite.addCostume(std::make_shared("costume1", "a", "png")); + targetSprite.addCostume(std::make_shared("costume2", "b", "svg")); + targetSprite.setCostumeIndex(1); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "Sprite2"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "backdrop #"); + builder.addObscuredInput("OBJECT", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("Sprite2")).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&targetSprite)); + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 0.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_PreferPropertiesOverVariables_CompileTime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite sprite; + auto v1 = std::make_shared("v1", "var1", 64.13); + auto v2 = std::make_shared("v2", "var2", 98); + auto v3 = std::make_shared("v3", "x position", -0.85); + sprite.addVariable(v1); + sprite.addVariable(v2); + sprite.addVariable(v3); + + sprite.setX(-78.25); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "x position"); + builder.addDropdownInput("OBJECT", "test"); + Block *block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("test")).WillOnce(Return(7)); + EXPECT_CALL(m_engineMock, targetAt(7)).WillOnce(Return(&sprite)); + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), -78.25); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Of_PreferPropertiesOverVariables_Runtime) +{ + auto targetMock = std::make_shared(); + targetMock->setEngine(&m_engineMock); + + Sprite sprite; + auto v1 = std::make_shared("v1", "var1", 64.13); + auto v2 = std::make_shared("v2", "var2", 98); + auto v3 = std::make_shared("v3", "x position", -0.85); + sprite.addVariable(v1); + sprite.addVariable(v2); + sprite.addVariable(v3); + + sprite.setX(-78.25); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", "test"); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("sensing_of"); + builder.addDropdownField("PROPERTY", "x position"); + builder.addObscuredInput("OBJECT", valueBlock); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findTarget("test")).WillOnce(Return(4)); + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&sprite)); + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), -78.25); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Current_Year) +{ + auto targetMock = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_current"); + builder.addDropdownField("CURRENTMENU", "YEAR"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + time_t now = time(0); + tm *ltm = localtime(&now); + ASSERT_EQ(value_toDouble(&value), ltm->tm_year + 1900); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Current_Month) +{ + auto targetMock = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_current"); + builder.addDropdownField("CURRENTMENU", "MONTH"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + time_t now = time(0); + tm *ltm = localtime(&now); + ASSERT_EQ(value_toDouble(&value), ltm->tm_mon + 1); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Current_Date) +{ + auto targetMock = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_current"); + builder.addDropdownField("CURRENTMENU", "DATE"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + time_t now = time(0); + tm *ltm = localtime(&now); + ASSERT_EQ(value_toDouble(&value), ltm->tm_mday); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Current_DayOfWeek) +{ + auto targetMock = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_current"); + builder.addDropdownField("CURRENTMENU", "DAYOFWEEK"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + time_t now = time(0); + tm *ltm = localtime(&now); + ASSERT_EQ(value_toDouble(&value), ltm->tm_wday + 1); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Current_Hour) +{ + auto targetMock = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_current"); + builder.addDropdownField("CURRENTMENU", "HOUR"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + time_t now = time(0); + tm *ltm = localtime(&now); + ASSERT_EQ(value_toDouble(&value), ltm->tm_hour); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Current_Minute) +{ + auto targetMock = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_current"); + builder.addDropdownField("CURRENTMENU", "MINUTE"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + time_t now = time(0); + tm *ltm = localtime(&now); + ASSERT_EQ(value_toDouble(&value), ltm->tm_min); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Current_Second) +{ + auto targetMock = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_current"); + builder.addDropdownField("CURRENTMENU", "SECOND"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + time_t now = time(0); + tm *ltm = localtime(&now); + ASSERT_EQ(value_toDouble(&value), ltm->tm_sec); + value_free(&value); +} + +TEST_F(SensingBlocksTest, Current_Invalid) +{ + auto targetMock = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_current"); + builder.addDropdownField("CURRENTMENU", "TEST"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 0.0); + value_free(&value); +} + +TEST_F(SensingBlocksTest, DaysSince2000) +{ + auto targetMock = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, targetMock); + builder.addBlock("sensing_dayssince2000"); + Block *block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, targetMock.get()); + auto code = compiler.compile(block, Compiler::CodeType::Reporter); + Script script(targetMock.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(targetMock.get(), &m_engineMock, &script); + + std::chrono::system_clock::time_point time(std::chrono::milliseconds(1011243120562)); // Jan 17 2002 04:52:00 + EXPECT_CALL(m_clock, currentSystemTime()).WillOnce(Return(time)); + + ValueData value = thread.runReporter(); + ASSERT_EQ(value_toDouble(&value), 747.20278428240817); + value_free(&value); +} diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp index 9b97a191..174ac50a 100644 --- a/test/engine/engine_test.cpp +++ b/test/engine/engine_test.cpp @@ -1694,8 +1694,19 @@ TEST(EngineTest, CreateMissingMonitors) } } -void questionFunction(const std::string &) +TEST(EngineTest, Answer) { + Engine engine; + const StringPtr empty(""); + const StringPtr test("test"); + const StringPtr *answer = engine.answer(); + + ASSERT_TRUE(answer); + ASSERT_EQ(string_compare_case_sensitive(answer, &empty), 0); + + engine.questionAnswered()("test"); + answer = engine.answer(); + ASSERT_EQ(string_compare_case_sensitive(answer, &test), 0); } TEST(EngineTest, Clones) diff --git a/test/llvm/llvmcodebuilder_test.cpp b/test/llvm/llvmcodebuilder_test.cpp index 69a24e34..91e0c475 100644 --- a/test/llvm/llvmcodebuilder_test.cpp +++ b/test/llvm/llvmcodebuilder_test.cpp @@ -3301,3 +3301,28 @@ TEST_F(LLVMCodeBuilderTest, Reporters) ASSERT_EQ(value_toPointer(&ret), &pointee); value_free(&ret); } + +TEST_F(LLVMCodeBuilderTest, UnknownTypeReporter) +{ + Sprite sprite; + auto var = std::make_shared("", ""); + var->setValue("Hello world!"); + sprite.addVariable(var); + + LLVMCodeBuilder *builder = m_utils.createReporterBuilder(&sprite); + + CompilerValue *num = builder->addConstValue(5.2); + builder->addFunctionCall("test_const_unknown", Compiler::StaticType::Unknown, { Compiler::StaticType::Unknown }, { num }); + + auto code = builder->build(); + + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread1(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread1); + + ValueData ret = code->runReporter(ctx.get()); + ASSERT_TRUE(value_isNumber(&ret)); + ASSERT_EQ(value_toDouble(&ret), 5.2); + value_free(&ret); +} diff --git a/test/llvm/testfunctions.cpp b/test/llvm/testfunctions.cpp index 2295d92e..b49e701a 100644 --- a/test/llvm/testfunctions.cpp +++ b/test/llvm/testfunctions.cpp @@ -139,6 +139,14 @@ extern "C" return ret; } + ValueData test_const_unknown(const ValueData *v) + { + ValueData ret; + value_init(&ret); + value_assign_copy(&ret, v); + return ret; + } + const void *test_const_pointer(const void *v) { return v; diff --git a/test/mocks/enginemock.h b/test/mocks/enginemock.h index a57be78d..f7378f51 100644 --- a/test/mocks/enginemock.h +++ b/test/mocks/enginemock.h @@ -66,6 +66,8 @@ class EngineMock : public IEngine MOCK_METHOD(void, clickTarget, (Target * target), (override)); + MOCK_METHOD(const StringPtr *, answer, (), (const, override)); + MOCK_METHOD(unsigned int, stageWidth, (), (const, override)); MOCK_METHOD(void, setStageWidth, (unsigned int), (override));