You all know about scratchblocks, it is a way to generate images of scratch scripts from text. Currently scratchblocks is being used on the forum and the wiki. It does have some Snap! support, but not a whole lot. There are grey rings, variadic input arrows, and the list icon (although the forum hasn't gotten that update yet).
[scratchblocks] (map ((join @addInput :: operators) ((#1) :: grey) @delInput @addInput :: grey ring) over @list :: list) [/scratchblocks]
However there are still many issues with the current scratchblocks. Empty rings do not work, the snap "define" block cannot be made because scratchblocks has special formatting for the scratch define block (and there's no way to override it), most Snap! icons are missing, multiline blocks are impossible to make, and more.
Seeing as how there is still very little Snap! support in scratchblocks, I decided to create a fork of scratchblocks, calling it snapblocks. Snapblocks aims to make scratchblocks for Snap! as well as fixing many issues. Here are the highlights. If you want to mess around with snapblocks, here's the link to the homepage, although every snapblocks image will be a link to the block on the snapblocks homepage.
First and foremost, I added a new Snap! style, which actually looks like Snap! (Scratch 2.0 looks similar, but it's still very clear that it's not Snap! especially when it comes to hat blocks).
The scratch define block now has more strict formatting
define {block}
This will make sure we can create blocks with the define label, with no Scratch define block formatting (yes, I know it will break existing scratchblocks text, but since snapblocks is for Snap! and is seperate from scratchblocks, I feel like this is fine. Plus, I'm not sure how many people ever used it on the forum).
define ((block)) [] (() @addInput :: ring)
As you can see, upvars now have automatic formatting. The ring around the variable is automatically set to the category of the parent.
motion ((var 1)) :: motion
looks ((var 2)) ::: looks
pen ((var 3)) :: pen stack
control ((var 4)) :: control
variables ((var 5)) :: variables
list ((var 6)) :: list
other ((var 7)) :: other
Now, on the topic of custom blocks and upvars, I have also added Snap! custom block support.
{custom block ((input)) ((number #)) ((bool ?)) :: motion} :: define
custom block [text] (5) <>
But Snap! usually shows a + between each block fragment in the block prototype, unless the setting (plain prototype labels) is turned on. Snapblocks does allow the + to be ignored in the block prototype.
{+ say + costume + ((costume = costume 1)) + :: looks} :: define
say costume [costume 1 V]
Unfortunately I was unable to make it automatically set the shape (without making it look bad anyway).
( + sound + ((sound # #)) + :: sound) :: define
report (item (sound #) of (my [sounds V]))
sound (5)
(sound (5))
Now, if you've seen, there are many blocks in Snap! libraries that have multiple lines. Scratchblocks does not have the ability to make multiline blocks, but snapblocks does.
(zip (() @addInput) inputs:
[] leaf-rank (0)
[] leaf-rank (0) :: control)
If you put a newline inside a reporter or predicate (oh, by the way, you can now specify the predicate
shape, not just boolean
), it will have a newline. This is not the only way, as this does not work in command (stack) blocks. Due to this, you can also use \n
, as well as putting a backslash at the end of a line right before a newline.
first line \n second line \
third line \
fourth line
On the topic of multiline blocks, I think it's a good idea to mention multiline inputs.
(multiline [first line
second line
third line] :: operators)
Snap! also has the ability to put c-shapes in reporters / predicates, which scratchblocks does not have support for, so I added it in snapblocks.
<else if <> then {
} :: control>
Unfortunately I have not added a way to add inputs on mew lines in the middle of a block, specifically there's no way to accurately recreate variadic ring inputs. The only reason I have not added this, is because there's not a really good way of determining if the input should go on the next line at the beginning of the block, or right under the previous input on the same line. If you don't know what I'm talking about, look at the pipe block.
I have also added a bunch of Snap! icons (started with the most commonly used icons).
@greenFlag @stopSign @turnRight @turnLeft @list @pointRight @turtle @turtleOutline @pause @cloud @cloudOutline @flash @arrowRight @arrowRightOutline @arrowRightThin @delInput @verticalEllipsis @addInput◂⋮▸ :: grey
note: there are arrow icons in every direction. This is not all the icons in snapblocks, but all the new Snap! icons have the same name as in Snap!, so if you want to use an icon in snapblocks from snap, just try the name used in snap.
Given that you can change the color and size of icons in Snap! I have also added a way to do that. It's pretty much the same way you do it in Snap!.
@greenFlag-scale-r-g-b
@greenFlag-2-0-0-255
You can also change the scale and color of text, using the exact same syntax that you would in Snap!.
$text-2-0-255-0
I have also revamped the block detection system to work with variadic inputs, and blocks that can change their text.
if <> {
} @addInput
if <> {
} else if <> {
} else if <> {
} else if <> {
} @delInput @addInput
all @verticalEllipsis @addInput
all (var)
<> and <> and <> and <> @delInput @verticalEllipsis @addInput
// Yes, all of these are detected as the same block
broadcast [message V] @addInput and wait
broadcast [message V] to [all V] @delInput @addInput and wait
broadcast [message V] to [sprite V] with data [data] @delInput and wait
This new system may be slower than the old method, but I think it's worth it because it allows for easier block creation, and it doesn't actually have to be real time, since most of the time, the image will not change.
Block detection explenation
I just kind of want to explain how this new block detection system works, as I'm kind of proud of coming up with the idea.
Let's start by explaining how scratchblocks detects blocks.
We start out with the block information
{
id: "id",
spec: "broadcast %1",
inputs: ["%m.messages"],
shape: "stack",
category: "control",
}
Note: this is very much simplified, and doesn't contain all the information scratchblocks uses.
When you type in a broadcast block
broadcast [message v]
It creates a block hash from this. A block hash replaces inputs with _
(weirdly similar to how programmatically naming blocks works in snap...)
broadcast _
Then it converts the block spec from the block information into this format
broadcast %1 --> broadcast _
Then it checks if the two hashes are the same. In this case, they are, so it knows to set the category and shape.
This works for scratch, but when you want to add snap primitives into the mix, things get a little messy. For some blocks, I can create aliases, other specs that a block can be compared to.
{
spec: "broadcast %1",
aliases: [
"broadcast %1 @addInput",
"broadcast %1 to %2 @delInput @addInput",
"broadcast %1 to %2 with data %3 @delInput",
],
...
}
Which will then match these using the same rules, just also factoring in the aliases
broadcast [message v]
broadcast [message v] @addInput
broadcast [message v] to [all v] @delInput @addInput
broadcast [message v] to [all v] with data [data] @delInput
This works for some blocks, but when it comes to variadic inputs that have an infinite amount of inputs, you can see why this wouldn't work. That is why I came up with a more dynamic solution.
This method has a base spec, then some spec defs, which replace specific parts in a spec.
{
spec: "broadcast %1 {receiver}",
specDefs: {
"receiver": [
"",
"@addInput",
"to %2 {data}",
],
"data": [
"@delInput @addInput",
"with data %3 @delInput",
],
}
}
This works in a very different way from the old method. Instead of checking if the block specs are equal, it instead creates the block spec based off of what you typed, by stepping through the block spec one word at a time.
It starts off with the first word
broadcast
This is compared with the first word of the spec
broadcast (_to _ @delInput)
broadcast (_ {reciever})
As these both have the same thing in the current position, this block is kept, if they're not equal, the block will be dropped from the list of blocks to check.
It repeats this process until it comes to a word that is surrounded by curly braces {}
, this is a spec def.
It then fills this position with it's spec defs.
broadcast _ {reciever}
broadcast _
broadcast _ @addInput
broadcast _ to _ {data}
There is only one that can compare to the current block, so the rest are dropped
broadcast _ to _ (@delInput @addInput)
broadcast _ to _ ({data})
Next it checks for the {data}
def
broadcast _ to _ {data}
broadcast _ to _ @delInput @addInput
broadcast _ to _ with data _ @delInput
Again, only one of these is the same as the current block spec
broadcast _ to _ @delInput @addInput
In fact, since they're equal, I don't need to continue, I can just keep this block spec, because I know it's correct.
This example didn't have an infinite variadic input, so here's an example.
{
spec: "{if}",
specDefs: {
"if": [
"if %1 %2 {else}",
],
"else": [
"@addInput",
"@delInput @addInput",
"else {if}",
],
}
}
Let's compare it to this
if <> {
} else if <> {
} else if <> {
} @delInput @addInput
Which will turn into this hash
if _ _ else if _ _ else if _ _ @delInput @addInput
The if spec starts with a def, so let's fill that in
{if}
if _ _ {else}
We know it is equal to the first 3 inputs, so let's keep it. Next we need to fill in the {else}
def.
if _ _ {else}
if _ _ @addInput
if _ _ @delInput @addInput
if _ _ else {if}
The last one is the only one that matches out current hash
if _ _ else (if _ _ else if _ _ @delInput @addInput)
if _ _ else {if}
After we fill in that def, we get this
if _ _ else if _ _ {else}
And then if we keep repeating this process, a couple more times, we end up with
if _ _ else if _ _ else if _ _ ({else})
if _ _ else if _ _ else if _ _ (@delInput @addInput)
Let's fill in the last {else}
and see what we get
if _ _ else if _ _ else if _ _ {else}
if _ _ else if _ _ else if _ _ @addInput
if _ _ else if _ _ else if _ _ @delInput @addInput
if _ _ else if _ _ else if _ _ else {if}
only the third one matches, so let's use that
if _ _ else if _ _ else if _ _ @delInput @addInput
And since that one is equal, we know our block is indeed an if block. Again, if at any point, there is something that doesn't match, it will not continue with that block, and will also not be matched. For example, this
if <> {
} else {
}
Will not be matched, because these are not equal
if _ _ else _
if _ _ else {if}
if _ _ else if _ _ {else}
If you want to understand how it's able to match all
as well as <> and <> and <> @delInput @verticalEllipsis @addInput
, here's the "and" block
{
spec: "{all}",
specDefs: {
"all": [
"all @addInput",
"all @verticalEllipsis @addInput",
"all %1",
"{and}"
],
"and": [
"%1 and {and}",
"%2 @delInput @addInput",
"%2 @delInput @verticalEllipsis @addInput",
],
},
}
There are still a bit more that snapblocks does, but this is the basics.
As you can tell, I am very proud of coming up with this solution. In fact, I actually came up with it one night, then spent the whole next day implementing it, because I knew it would work.
I do really want to make plugins for mediawiki and discourse, specifically fore easier support. Currently, all you really need to do is swap out the scratchblocks files (including the translations-all.js
(if you use it)) with the snapblocks files (you may also want to change the reference to use the snapblocks name, everything else can stay using scratchblocks).
Since snapblocks is open source, you can contribute to it if you want.
""
I also noticed this when creating my post,
I guess I'm that person who finally created a fork of scratchblocks, and made it for snap.
Here are some things that I want to add in the future
Zebra coloring
Block wrapping
Multiline comments
Maybe a snapblocks to snap xml?
(fix the translations system)
It would also be great if someone could make a logo for it, as I'm not very good at art.