Skip to content

Implement the ask and wait and answer blocks #480

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions include/scratchcpp/iengine.h
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,18 @@ class LIBSCRATCHCPP_EXPORT IEngine
/*! Sets the function which is called when a monitor is removed. */
virtual void setRemoveMonitorHandler(const std::function<void(Monitor *, IMonitorHandler *)> &handler) = 0;

/*! Returns the function which is called when a question is asked, for example using the 'ask and wait' block. */
virtual const std::function<void(const std::string &)> &questionAsked() const = 0;

/*! Sets the function which is called when a question is asked, for example using the 'ask and wait' block. */
virtual void setQuestionAsked(const std::function<void(const std::string &)> &f) = 0;

/*! Returns the function which should be called when a question is answered. */
virtual const std::function<void(const std::string &)> &questionAnswered() const = 0;

/*! Sets the function which should be called when a question is answered. */
virtual void setQuestionAnswered(const std::function<void(const std::string &)> &f) = 0;

/*! Returns the list of extension names. */
virtual const std::vector<std::string> &extensions() const = 0;

Expand Down
96 changes: 96 additions & 0 deletions src/blocks/sensingblocks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ void SensingBlocks::registerBlocks(IEngine *engine)
{
// Blocks
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);
Expand Down Expand Up @@ -77,6 +79,9 @@ void SensingBlocks::registerBlocks(IEngine *engine)
engine->addFieldValue(this, "background #", BackdropNumber); // Scratch 1.4 support
engine->addFieldValue(this, "backdrop #", BackdropNumber);
engine->addFieldValue(this, "backdrop name", BackdropName);

// Callbacks
engine->setQuestionAnswered(&onAnswer);
}

void SensingBlocks::compileDistanceTo(Compiler *compiler)
Expand All @@ -100,6 +105,17 @@ void SensingBlocks::compileDistanceTo(Compiler *compiler)
}
}

void SensingBlocks::compileAskAndWait(Compiler *compiler)
{
compiler->addInput(QUESTION);
compiler->addFunctionCall(&askAndWait);
}

void SensingBlocks::compileAnswer(Compiler *compiler)
{
compiler->addFunctionCall(&answer);
}

void SensingBlocks::compileKeyPressed(Compiler *compiler)
{
compiler->addInput(KEY_OPTION);
Expand Down Expand Up @@ -488,6 +504,45 @@ unsigned int SensingBlocks::distanceToMousePointer(VirtualMachine *vm)
return 0;
}

void SensingBlocks::onAnswer(const std::string &answer)
{
// https://github.com/scratchfoundation/scratch-vm/blob/6055823f203a696165084b873e661713806583ec/src/blocks/scratch3_sensing.js#L99-L115
m_answer = answer;

if (!m_questionList.empty()) {
Question *question = m_questionList.front().get();
VirtualMachine *vm = question->vm;
assert(vm);
assert(vm->target());

// If the target was visible when asked, hide the say bubble unless the target was the stage
if (question->wasVisible && !question->wasStage)
vm->target()->setBubbleText("");

m_questionList.erase(m_questionList.begin());
vm->resolvePromise();
askNextQuestion();
}
}

unsigned int SensingBlocks::askAndWait(VirtualMachine *vm)
{
const bool isQuestionAsked = !m_questionList.empty();
enqueueAsk(vm->getInput(0, 1)->toString(), vm);

if (!isQuestionAsked)
askNextQuestion();

vm->promise();
return 1;
}

unsigned int SensingBlocks::answer(VirtualMachine *vm)
{
vm->addReturnValue(m_answer);
return 0;
}

unsigned int SensingBlocks::timer(VirtualMachine *vm)
{
vm->addReturnValue(vm->engine()->timer()->value());
Expand Down Expand Up @@ -822,3 +877,44 @@ unsigned int SensingBlocks::daysSince2000(VirtualMachine *vm)

return 0;
}

void SensingBlocks::enqueueAsk(const std::string &question, VirtualMachine *vm)
{
// https://github.com/scratchfoundation/scratch-vm/blob/6055823f203a696165084b873e661713806583ec/src/blocks/scratch3_sensing.js#L117-L119
assert(vm);
Target *target = vm->target();
assert(target);
bool visible = true;
bool isStage = target->isStage();

if (!isStage) {
Sprite *sprite = static_cast<Sprite *>(target);
visible = sprite->visible();
}

m_questionList.push_back(std::make_unique<Question>(question, vm, visible, isStage));
}

void SensingBlocks::askNextQuestion()
{
// https://github.com/scratchfoundation/scratch-vm/blob/6055823f203a696165084b873e661713806583ec/src/blocks/scratch3_sensing.js#L121-L133
if (m_questionList.empty())
return;

Question *question = m_questionList.front().get();
Target *target = question->vm->target();
auto ask = question->vm->engine()->questionAsked();

// 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->setBubbleType(Target::BubbleType::Say);
target->setBubbleText(question->question);

if (ask)
ask("");
} else {
if (ask)
ask(question->question);
}
}
35 changes: 35 additions & 0 deletions src/blocks/sensingblocks.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@
#pragma once

#include <scratchcpp/iblocksection.h>
#include <scratchcpp/value.h>
#include <vector>

#include "../engine/internal/clock.h"

namespace libscratchcpp
{

class Target;

/*! \brief The SensingBlocks class contains the implementation of sensing blocks. */
class SensingBlocks : public IBlockSection
{
public:
enum Inputs
{
DISTANCETOMENU,
QUESTION,
KEY_OPTION,
OBJECT
};
Expand Down Expand Up @@ -53,6 +59,8 @@ class SensingBlocks : public IBlockSection
void registerBlocks(IEngine *engine) override;

static void compileDistanceTo(Compiler *compiler);
static void compileAskAndWait(Compiler *compiler);
static void compileAnswer(Compiler *compiler);
static void compileKeyPressed(Compiler *compiler);
static void compileMouseDown(Compiler *compiler);
static void compileMouseX(Compiler *compiler);
Expand Down Expand Up @@ -83,6 +91,10 @@ class SensingBlocks : public IBlockSection
static unsigned int distanceToByIndex(VirtualMachine *vm);
static unsigned int distanceToMousePointer(VirtualMachine *vm);

static void onAnswer(const std::string &answer);
static unsigned int askAndWait(VirtualMachine *vm);
static unsigned int answer(VirtualMachine *vm);

static unsigned int timer(VirtualMachine *vm);
static unsigned int resetTimer(VirtualMachine *vm);

Expand Down Expand Up @@ -116,6 +128,29 @@ class SensingBlocks : public IBlockSection
static unsigned int daysSince2000(VirtualMachine *vm);

static IClock *clock;

private:
struct Question
{
Question(const std::string &question, VirtualMachine *vm, bool wasVisible, bool wasStage) :
question(question),
vm(vm),
wasVisible(wasVisible),
wasStage(wasStage)
{
}

std::string question;
VirtualMachine *vm = nullptr;
bool wasVisible = false;
bool wasStage = false;
};

static void enqueueAsk(const std::string &question, VirtualMachine *vm);
static void askNextQuestion();

static inline std::vector<std::unique_ptr<Question>> m_questionList;
static inline Value m_answer;
};

} // namespace libscratchcpp
20 changes: 20 additions & 0 deletions src/engine/internal/engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,26 @@ void Engine::setRemoveMonitorHandler(const std::function<void(Monitor *, IMonito
m_removeMonitorHandler = handler;
}

const std::function<void(const std::string &)> &Engine::questionAsked() const
{
return m_questionAsked;
}

void Engine::setQuestionAsked(const std::function<void(const std::string &)> &f)
{
m_questionAsked = f;
}

const std::function<void(const std::string &)> &Engine::questionAnswered() const
{
return m_questionAnswered;
}

void Engine::setQuestionAnswered(const std::function<void(const std::string &)> &f)
{
m_questionAnswered = f;
}

const std::vector<std::string> &Engine::extensions() const
{
return m_extensions;
Expand Down
8 changes: 8 additions & 0 deletions src/engine/internal/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,12 @@ class Engine : public IEngine
void setAddMonitorHandler(const std::function<void(Monitor *)> &handler) override;
void setRemoveMonitorHandler(const std::function<void(Monitor *, IMonitorHandler *)> &handler) override;

const std::function<void(const std::string &)> &questionAsked() const override;
void setQuestionAsked(const std::function<void(const std::string &)> &f) override;

const std::function<void(const std::string &)> &questionAnswered() const override;
void setQuestionAnswered(const std::function<void(const std::string &)> &f) override;

const std::vector<std::string> &extensions() const override;
void setExtensions(const std::vector<std::string> &newExtensions) override;

Expand Down Expand Up @@ -255,6 +261,8 @@ class Engine : public IEngine

std::function<void(Monitor *)> m_addMonitorHandler = nullptr;
std::function<void(Monitor *, IMonitorHandler *)> m_removeMonitorHandler = nullptr;
std::function<void(const std::string &)> m_questionAsked = nullptr;
std::function<void(const std::string &)> m_questionAnswered = nullptr;
};

} // namespace libscratchcpp
4 changes: 2 additions & 2 deletions src/scratch/sprite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ void Sprite::setBubbleType(BubbleType type)
/*! Overrides Target#setBubbleText(). */
void Sprite::setBubbleText(const std::string &text)
{
Target::setBubbleText(text);
Target::setBubbleText(impl->visible ? text : "");

if (impl->visible && !text.empty()) {
IEngine *eng = engine();
Expand All @@ -451,7 +451,7 @@ void Sprite::setBubbleText(const std::string &text)
}

if (impl->iface)
impl->iface->onBubbleTextChanged(text);
impl->iface->onBubbleTextChanged(impl->visible ? text : "");
}

Target *Sprite::dataSource() const
Expand Down
Loading