Subscratch

From Esolang
Jump to navigation Jump to search

subscratch is an OISC language invented by User:Zapcircuit. its main purpose is for codegolfing games in scratch. its most interesting feature is its scratch implementation, which uses very few scratch blocks.

implementation

to the right is an image of the subscratch interpreter in scratch.

since scratch is proprietary, this screenshot has been replaced by a hand drawn version

note: if there are any discrepancies between the behavior of this scratch program and the info described below, the scratch program should be considered the legitimate one.

overview

in order to run a subscratch program on the subscratch interpreter, one needs to provide the following:

  • the p(program) list. this is a text file with the formatting being base 10 integers separated by newlines.
  • the m(memory) list. same as above.
  • the pc(program counter) register.
  • the sub(subtrahend) register.
  • the send constant.
  • the i0(interface 0) register.
  • graphics assets. a collection of images that the scratch ide accepts.
  • audio assets. same as above but with audio files.

explanation

  • p list: this long list of numbers form half of the code. they are not modifiable during runtime. note scratch lists are one indexed, meaning the first item is item 1.
  • m list: this list forms the other half of the code. it also holds all the variables and special registers. this is modifiable during code execution. a memory cell from the m list is also referred to as a memory address. so "address 10000" would be the same as "10000th cell of the m list".
  • pc: this is a register, meaning an arbitrarily chosen cell in the m list. this register holds the line number the program is executing. the interpreter executes lines of code one by one by adding 1 to pc, executing that line of code, then adding 1 to pc again etc. if the code sets pc to a value, the code will jump to that location then continue execution from there.
  • sub: this is another register. we will get to what it does soon, but it is just a cell in the m list and you can modify it just like any other memory cell.
  • send: this is just a number. whenever the pc equals this number(jumped to this location), a new frame of graphics is pushed.
  • i0: this register is related to i/o. the memory cell right after this is i1, then i2... all the way to i302. these 303 registers should be placed at the end of the m list, as any cell after i302 will not be preserved between frames.

execution

the subscratch language has only one instruction(command) that takes 2 operands(fields). since there is only one instruction the operation name can be omitted. here is an example of a line of code:

15000   15001

what this does is: set the contents of address 15000 to the contents of address 15001 minus the contents of the sub register. for example: if address 15000 holds the value 12345, address 15001 holds the value 70, and sub holds the value 40, after the code is executed address 15000 will now hold the value 30(which is 70-40).

each time a new line of code needs to be run, pc is incremented by 1 and the operands of the instruction is fetched from the p list and the m list. the pc-th item of the p list will be the left operand and the pc-th item of the m list will be the right operand. for example, if pc is currently 12000, item 12001 of p list is 23000, item 12001 of m list is 25000, then the next instruction would be

23000   25000

notice this m list is the same m list we are modifying the data from. the program exists in the same space as the variables. it is important to not mix up the location of a code(pc needs to be here to execute this code) and the operand of the code(which cell the code actually manipulates).

examples

here are some code snippets to demonstrate how to accomplish some common tasks. note these code are not optimized.

set sub to 0

if sub is at address 14567:

14567   14567

this sets address 14567 to address 14567 - address 14567, which, regardless of the initial state of address 14567, sets address 14567 to 0.

for the ease of viewing we can replace addresses with names. the above code becomes

sub     sub

set value of a to value of b

sub     sub     (clears sub first)
a       b       (value of a = value of b-0)

set value of a to 4

there are no constants in subscratch. the only way is to have a variable have an initial value and never change it.

sub     sub
a       "4"

where "4" is a variable that holds the initial value of 4.

from here on, a number inside quotes in a line of code is to be interpreted as a variable that holds the number that never changes(aka "constant"). additionally, a variable name in quotes means a "constant" that holds the value of the address of the variable. so if x is at address 23145, then

a       "x"

is the same as

a       "21345"

jump to line 11111

sub     sub
pc      "11111" (pc = 11111, code continues to execute from line 11112)

subtract b from a

sub     sub     (clear sub)
sub     b       (set sub to b)
a       a       (a = a-b)

add b to a

sub     sub     (clear sub)
sub     b       (set sub to b)
sub     "0"     (set sub to 0-sub which is -b)
a       a       (set a to a-(-b) which is a+b)

indexed access to memory

we know the code(or at least half of it) and variables both exist within the m list. to the code there is no difference between the two, so it can also change the right operand of instructions like variables. this means subscratch is self modifying. this enables indexed access to memory.

since this part relies on self modifying code, the line number will be included to the left of the instructions.

lets say this code snippet starts on line 10400.

10400:  sub     sub     (clear sub)
10401:  10402   b       (set value of address 10402 to b, address 10402 happens to be the right operand of the following instruction)
10402:  a       0       (the 0 doesnt matter as it will be replaced by the contents of b.)

now, the value of a will be set to the value of the address thats equal to the number b holds.(not the address of b, rather the contents of b.) for example, if b is at address 20304 and holds the value 25000, and address 25000 holds the value 30, a will be set to 30.

for brevity, we will now use relative addresses to replace direct addresses in code. all instances of a "+" or "-" sign followed by a number should be treated as a relative address, with its absolute value being the line number plus the relative value. for example:

12345:  -7      x

is the same as

12345:  12338   x

and

15015:  +3      x

is the same as

15015:  15018   x

thus, the above code can be written as:

sub     sub
+1      b
a       0

set x to a[b]

with indexed access to memory, it is possible to implement arrays. here, an array means a finite list of variables whose addresses are continuous in memory. the first one should be called arrayname[0], next one is arrayname[1], then arrayname[2]... in this example, the array name is a.

sub     sub     (clear sub)
sub     b       (sub = b)
sub     "0"     (sub = -b)
+2      "a[0]"  (+2 is now address(not value) of a[0] + b, which is address of a[b])
sub     sub     (clear sub)
x       0       (set value of address x to value of address a[b], which means x = a[b])

switch case

here, switch case means jumping to different positions according to the value of a variable. for example, if a=0 jump to 20143, if a=1 jump to 20261, if a=2 jump to 20345, etc.

sub     sub
sub     a
sub     "0"
+2      "jumptable[0]"
sub     sub
pc      0

where jumptable[0] has the value 20143, jumptable[1] has the value 20261, jumptable[2] has the value 20345, etc.

note: a[-1] will return the value of the address 1 before a[0], so its also possible to have arrays with negative indexes, and also negative "cases".

a>0

unfortunately there is no arbitrary comparison in subscratch. the only way to accomplish this is to have an infinitely long array, where array[1], array[2], array[3]... all holds the value 1, and array[0], array[-1], array[-2]... all holds the value 0, then a>0 will be equal to array[a]. however, arrays have finite sizes. to approximate this, we can fill the first 10000 addresses with the value "1". when reading a negative item of a list, scratch automatically returns "" which is equivalent to "0", so the negative part is taken care of. now we can test if a>0 by reading array[a], as long as a holds an integer below 10000.

sub     sub
sub     a
sub     "0"
+2      1       (this is the actual address 1, which is array[0]. it is not a "constant" with the value 1. in the actual program this will be the number "1". the constant "1", on the other hand, will not be represented by a 1 in the code.)
sub     sub
result  0

a>b

a>b is just a-b>0. this can be done by chaining a-b and a>0.

sub     sub
sub     b
temp0   a       (temp0 = a-b)
sub     sub
sub     temp0
sub     "0"
+2      1
sub     sub
result  0       (result = temp0>0)

if a>b else

this can be done by chaining a>b and switch case together.

sub     sub
sub     b
temp0   a
sub     sub
sub     temp0
sub     "0"
+2      1
sub     sub
temp1   0       (temp1 = a>b)
sub     sub
sub     temp1
sub     "0"
+2      "jumptable[0]"
sub     sub
pc      0       (switch temp1)

since temp1 can be only 0 or 1, the jump table only needs 2 values. this can also be used to do conditional loops by jumping to a previous location if a condition is not met.

i/o

scratch itself has 301 hardware sprites. a sprite has a costume, which is one of the images from the graphical assets, a x and y position, and can start a sound from the audio assets every frame.

when sprites overlap graphically, sprites with a higher id will be placed on top. sprite 301 is at the top of all sprites.

scratch sprites also has many other features, but they are not supported by subscratch.

everytime pc is set to the send constant, a new graphics frame will be generated made of the sprites, which will do the following:

  • sprite 0 will set costume to the i0-th, start the i0-th sound, and go to the x position of register i1, y position of i2. sprite 1 will set costume to i1, and play sound i1, and go to x: i2, y: i3. similarly sprite 2 will be i2, i3, and i4... sprite 300 will set costume to i300, play sound i300, and go to x: i301, y: i302.
  • then, the sprites will read input. sprite 0 will check the keyboard key with the same name as its costume name, and put it into i0. the value will be true(equivalent to 1) if the key is pressed, and false(equivalent to 0) if not. for example, if i0 was 500, sprite 0 will now switch to costume number 500. if the name of costume 500 is "up arrow", i0 will then check for the up arrow key on the keyboard. if it is pressed, then i0 will be set to true, which behaves like the number 1 and can be computed as such. the same goes for sprite 1 and i1, sprite 2 and i2 etc.

it is obvious the i/o of subscratch is convoluted and requires great care to not produce garbage output. here are some observations to be mindful of.

  • the costume output and the audio output of a sprite shares an index. in most cases, one does not want the displaying of an image to be linked to the playing of a sound. a way of solving this problem is to make sure the sound file with the same index as an useful image file is silent, and the image file with the same index as an useful sound file transparent.
  • different sprites use the same registers for different purposes. for example, the register i2 is used simultaneously as the y position of sprite 0, the x position of sprite 1, and the costume number of sprite 2. it is highly unlikely for the required data for 3 sprites to be the same number, so the only option is to not use sprite 1 and sprite 2, and assign the register solely for sprite 0's use. this also means the number of usable sprites is cut down to 101.
  • however, there is no way to simply disable a sprite in subscratch. this means the unused sprites will take the parameters of other unrelated sprites as its position, costume and sound, messing up the graphics and audio. to avoid this, add a few hundred of blank images and audio files to the start and end of the list of costumes and audio. this is so when the position parameters of other sprites get incorrectly used as graphics and audio information, the sprites will be blank and play no sound, avoiding messing up the output.