This guide gives an overview of SmileBASIC's language design.
When I started writing this guide, I set out to write something that would help people with previous programming experience get up to speed with SmileBASIC 4 as a language quickly. At the same time, I tried to put in enough details that someone just starting out would have a chance at following along. In the end, I believe I've ended up with something that will please neither group! Unfortunately, I'm out of time, and SmileBASIC 4 is already out worldwide, so this is the best you're gonna get for now. It's at least a step up from reading random parts of the reference.
Because this guide is focused on language design and syntax, it leaves out all of the fun stuff. No controllers, no graphics, and no audio. All this guide covers is text input and output. Sorry about that.
Also, even for the subjects I do cover, I won't be covering every single minute detail about them. This is an overview, not a reference.
For the rest, you'll want to check out other sources. There's
the official reference, of course, and
my guide on the differences between SB3 and SB4 delves into all the cool new features, although it's written with users of the 3DS version of SmileBASIC in mind, and may be a bit outdated in spots.
If you've got any questions, comments, or corrections, feel free to post them below.
Finally, I just want to say to anyone reading this who has never programmed before: don't be discouraged if you can't understand this guide! This guide just isn't designed very well for beginners, and better guides will show up before too long. In the meantime, there's a lot of games that other SB4 users have made, so go check them out!
Comments
Anything written after
' is a line comment.
'SmileBASIC ignores all comments, so use them as notes to yourself
'and other people to make your code easier to read and understand!
There's another way to write comments called REM.
REM This is also a comment!
REM exists because older kinds of BASIC used it instead of
', but nowadays you should just use
'.
This guide will use comments beginning with
'=> to show what's printed to the screen. For example,
PRINT 100 prints
100 to the screen, so this guide will show that like this:
PRINT 100 '=> 100
If it's obvious what will be printed or otherwise isn't relevant, the output may not be shown, however.
Printing to the screen
The
PRINT command lets you display text on the screen.
PRINT "Hello, world!" '=> Hello, world!
The quotes around
"Hello, world!" mark it as a type of value called a string. String is just a programming term that means a text value.
You can also print numbers!
PRINT 123 '=> 123
PRINT 5+5 '=> 10
To print a blank line, you can just use
PRINT without any value.
PRINT
You can print multiple values on one line by putting
, between each value. This puts gaps between them, so if you don't want that, you can use
; instead.
PRINT 1,2,3 '=> 1 2 3
PRINT 1;2;3 '=> 123
Code is executed in order from top to bottom, so this code prints the line
Hello, then the line
World.
PRINT "Hello"
PRINT "World"
You can also put multiple commands on a single line by using
:.
PRINT "Hello":PRINT "World"
PRINT automatically advances to the next line on the screen after printing, which is why
Hello and
World are printed on two separate lines. However, sometimes you don't want it to do that. You can stop it from advancing to the next line by putting a
; at the very end. This code prints one line:
HelloWorld.
PRINT "Hello";
PRINT "World"
PRINT is used a lot, so there's a shorter way to write it. These two lines do the same thing.
PRINT "Hello, world!"
?"Hello, world!"
More advanced text formatting can be done using the
FORMAT$ function. This uses similar syntax to
printf in C.
?FORMAT$("The number is %04D.",1) '=> The number is 0001.
If you want to clear all the text off the screen, you can use
CLS.
CLS stands for "CLear Screen".
CLS
You can also pick a specific place on the screen to print with
LOCATE.
LOCATE 5,5
?"Hello!"
Types
There are seven types of values in SmileBASIC 4.
There are no user-defined types or structs in SmileBASIC.
You can check the type of a value with
TYPEOF.
?TYPEOF(5) '=> 1
?TYPEOF(1.234) '=> 2
?TYPEOF("Hi!") '=> 3
There's also a handy command called
INSPECT that lets us view a value in detail. It's helpful when debugging programs.
INSPECT 5 '=> INT: 5
INSPECT 1.234 '=> REAL: 1.23400000
INSPECT "Hi!" '=> STRING: (3)"Hi!"
Like
PRINT, there's a shorter way to write this command.
??5 '=> INT: 5
It's possible to convert between these types as well.
INT converts from a real number to an integer.
??INT(2.5) '=> INT: 2
FLOAT converts from an integer to a real number.
??FLOAT(5) '=> REAL: 5.00000000
STR$ converts from either number type to a string.
??STR$(1.234) '=> STRING: (5)"1.234"
VAL converts from a string to an integer, if possible. Otherwise, it converts to a real number. If neither are possible, it returns 0.
??VAL("123") '=> INT: 123
??VAL("1.23") '=> REAL: 1.23000000
??VAL("56hi") '=> INT: 0
SmileBASIC's typing is not very strict when it comes to numbers. If the result of some operation doesn't fit into an integer, it will become a real number automatically.
Arithmetic
Arithmetic in SmileBASIC works pretty much as you'd expect.
+ means add,
- means subtract,
* means multiply, and
/ means divide.
All of these lines set
X to 12.
X=8+4
X=15-3
X=2*(3+3)
X=24/2
+ and
* can also be used with strings.
?"Good"+"Job" '=> GoodJob
?"Ha"*5 '=> HaHaHaHaHa
There's also integer division and remainder, using
DIV and
MOD.
Using normal division, 19 divided by 8 is 2โ
.
Using integer division, 19 divided by 8 is 2 with a remainder of 3.
DIV gives you the first number, and
MOD gives you the second number.
A=19 DIV 8
B=19 MOD 8
?A,B '=> 2 3
Exponents can be calculated using the
POW function. This code calculates 2 to the power of 8.
?POW(2,8) '=> 256
Bitwise operators also exist, like
AND,
OR,
XOR, and
NOT. These are helpful in dealing with binary numbers, but don't worry if you don't know how to use them.
?1 OR 3 '=> 3
?(1 XOR 6) AND NOT 3 '=> 4
The other bitwise operators are various kinds of bit shifts.
- << and >> are arithmetic (signed) shift.
- <<< and >>> are logical (unsigned) shift.
- <<+ and >>+ are rotate left and right.
?1<<4 '=> 16
?-1>>>30 '=> 3
?5>>+1 '=> -2147483646
When using bitwise operators, it's helpful to be able to write numbers in hexadecimal or binary. In BASIC, these are done with the
&H and
&B prefixes.
?&HFF '=> 255
?&B1000 '=> 8
Variables
Values can be given names, called variables. To set a variable to a specific value, you can use
=. This is called assigning a value. This code sets the variable
SALLY to 123.
SALLY=123
Once a variable is set, you can use it with commands like
PRINT.
?SALLY '=> 123
You can set a variable as many times as you want, and it will use the latest value you've set it to.
SALLY=150
SALLY=SALLY+1
?SALLY '=> 151
To increase or decrease a value by some number, you can also use the
INC and
DEC commands.
SALLY=100
SALLY=SALLY+10
??SALLY '=> INT: 110
SALLY=100
INC SALLY,10
??SALLY '=> INT: 110
Variables don't care about what type of value is stored in them, so this code is okay.
FRED=100
FRED="Hello!"
However, it's worth noting that previous versions of SmileBASIC were stricter about variables, marking the type with a suffix on the name.
FRED=100.0
FRED%=100
FRED#=100.0
FRED$="Hello!"
These suffixes still matter in certain circumstances. For example, if you try to read a variable that hasn't been assigned a value yet, it will use a default value depending on the variable name.
??UNUSED '=> REAL: 0.00000000
??UNUSED% '=> INT: 0
??UNUSED# '=> REAL: 0.00000000
??UNUSED$ '=> STRING: (0)""
Speaking of that, some people don't like being able to read unassigned variables, because if you make a typo, like writing
SALY instead of
SALLY, you'll just get 0.0 instead of the program giving an error message. To change this behavior, you can put this at the top of the file:
OPTION STRICT
Once
OPTION STRICT is used, variables need to be declared with the
VAR command before you can use them. Once they're declared, you can use them like normal.
VAR X=123
X=456
?X '=> 456
You can use
VAR without a value, in which case it uses the default for that type, as determined by the suffix.
VAR Y%
??Y '=> INT: 0
You can even declare multiple variables at once.
VAR A=10,B=20,C
?A,B,C '=> 10 20 0
Instead of
VAR, you can write
DIM, which does the same thing.
DIM BOB=12345
?BOB '=> 12345
VAR has a function form that lets you access variables by passing their name in as a string.
VAR is special in that it can be used before
=.
?BOB '=> 12345
?VAR("BOB") '=> 12345
PHIL=500
VAR("PHIL")=500
It's also possible to declare constants in SmileBASIC, using
CONST. A constant is a variable that never changes. Their names always start with
#.
CONST #WIDTH=1280
It's a good idea to use constants whenever you need to give a name to a value that won't ever change while the code is running.
Text input
Programs aren't very useful without any input from the user, so let's go over that now.
LINPUT prints a prompt to the screen and waits for the user to type in some text and press Enter. This code prints
What is X? , reads a line, and puts it into the
X variable.
LINPUT "What is X? ";X
If you want, you can leave off the prompt entirely.
LINPUT X
INPUT is similar to
LINPUT, but it reads a series of values separated by commas, instead of taking the line as is. It decides whether to read a string or convert it into a number based on the type of the variable used.
INPUT "Name and age";NAME$,AGE%
?NAME$,AGE%
Many other ways to get input exist, but they aren't covered in this guide.
Comparisons
Not only is arithmetic possible, but it's also possible to check if some condition is true or not.
In SmileBASIC, 1 is used to mean "true", and 0 is used to mean "false". To make things easier to read, you can write
#TRUE or
#FALSE instead.
?#TRUE '=> 1
?#FALSE '=> 0
To check if a number is equal to another, you can use
==. The reason SmileBASIC uses
== instead of
= is because
= is used for assigning values.
?6==6 '=> 1
?9==3 '=> 0
To check if a number is NOT equal to another, use
!=.
?4!=5 '=> 1
?5!=5 '=> 0
To check if a number is less than another, use
<.
To check if a number is greater than another, use
>.
?3<7 '=> 1
?3>7 '=> 0
To check if a number is less than or equal to another, use
<=.
To check if a number is greater than or equal to another, use
>=.
?3<=7 '=> 1
?3<=3 '=> 1
?7>=3 '=> 1
?7>=7 '=> 1
If you want to check multiple conditions, you can use
&& or
||.
A
&& B is true only when A and B are both true.
A
|| B is true if either A or B are true.
?10<20 && 5==5 '=> 1
?50>3 || 2==7 '=> 1
To invert a condition, so that true becomes false and false becomes true, use
!. For instance, these two lines are equivalent.
?!(2<3) '=> 0
?2>=3 '=> 0
Branching
To do something only if a condition is true, use
IF...
THEN. This code prints
Hello! if
X is equal to 5, or nothing if it's not equal.
IF X==5 THEN ?"Hello!"
If you want to do something else if the condition is false, use
ELSE. This code prints
Hello! if
X is equal to 5, or
Bye! if it's not equal.
IF X==5 THEN ?"Hello!" ELSE ?"Bye!"
If you want to have multiple commands in an
IF statement, there is a multi-line version.
ENDIF tells SmileBASIC where the
IF statement ends.
IF X==5 THEN
?"Hello!"
?"X is equal to 5!"
ENDIF
ELSE can also be used with this form.
IF X==5 THEN
?"X is equal to 5!"
ELSE
?"X is not equal to 5!"
ENDIF
If you want to check a list of conditions, and do the first one that's true, you can use
ELSEIF. You can have as many
ELSEIFs as you want.
IF X==5 THEN
?"X is equal to 5!"
ELSEIF X==6 THEN
?"X wasn't equal to 5, but it is equal to 6!"
ELSE
?"X wasn't equal to 5 or 6."
ENDIF
If you want to check if a variable is equal to a bunch of different values, you can use
CASE. If you've used languages like C or JavaScript, keep in mind that there is no fallthrough in
CASE.
CASE X
WHEN 0: ?"X is 0!"
WHEN 1: ?"X is 1!"
WHEN 2: ?"X is 2!"
OTHERWISE: ?"X is something else!"
ENDCASE
Looping
LOOP...
ENDLOOP is an infinite loop. This code prints
Hello, world! over and over, forever.
LOOP
?"Hello, world!"
ENDLOOP
You can also loop while a condition is true by using
WHILE...
WEND.
X=10
WHILE X<100
?"X is ";X
INC X,5
WEND
You can loop over a range of numbers with
FOR...
NEXT.
FOR X=1 TO 10
?"X is ";X
NEXT
You can use
STEP in
FOR loops to increment by a specified number instead of 1. This code is equivalent to the
WHILE loop from earlier.
FOR X=10 TO 100 STEP 5
?"X is ";X
NEXT
BREAK lets you end a loop early. This loop ends when
X is 5 because of the
BREAK.
FOR X=1 TO 10
?"X is ";X
IF X==5 THEN BREAK
NEXT
CONTINUE lets you skip to the next iteration of the loop. This loop doesn't print
X is 7 because
CONTINUE skips past it.
FOR X=1 TO 10
IF X==7 THEN CONTINUE
?"X is ";X
NEXT
END and STOP
END ends the program. You usually don't need to write it, since the program will end anyway once it reaches the end of your code, but sometimes you want to end it early.
END
STOP ends the program with an error message.
STOP "Number must be between 0 and 100"
A very simple game
Believe it or not, it's possible to make a simple game just from the stuff we've seen so far.
Here is a small game where you try to guess a random number:
'This line sets N to a random number between 1 and 100.
N=RND(100)+1
?"I'm thinking of a number between 1 and 100."
?"Try to guess which number it is!"
LOOP
INPUT "Guess";GUESS%
IF GUESS%>N THEN
?"Too high, try again!"
ELSEIF GUESS%<N THEN
?"Too low, try again!"
ELSE
?"You did it!"
BREAK
ENDIF
ENDLOOP
Labels, GOTO, and GOSUB
These are features that come from old versions of BASIC, so they're not really recommended, but they're included here because you're likely to run into them when reading other people's code.
Labels are used to give a name to a specific spot in your code. They don't do anything by themselves, but
GOTO,
GOSUB, and a few other commands use them.
@NAME
GOTO jumps to a specific label. For instance, this code prints
1 and
3, skipping
2.
?1
GOTO @DENNIS
?2
@DENNIS
?3
Note that when using a label as a value, it is actually a string. In other words, these lines are equivalent:
S$="@TEST"
S$=@TEST
It's also possible to jump backwards, making a loop. It's preferred to write this with
LOOP...
ENDLOOP instead, though.
@HELLO
?"Hello, world!"
GOTO @HELLO
GOSUB is similar to
GOTO, but you can
RETURN after jumping to a label, which brings you back to right after the
GOSUB. This code prints
Hello!, then
Yay!, then
Goodbye!. Notice how we use
END here to keep SmileBASIC from running the code under
@YAY a second time.
?"Hello!"
GOSUB @YAY
?"Goodbye!"
END
@YAY
?"Yay!"
RETURN
If you're already experienced with programming, you might recognize
GOSUB as a subroutine call. However, SmileBASIC offers proper functions, which are covered later, so there isn't much use for
GOSUB.
ON isn't necessary now that
CASE exists, but for reference, here's the equivalent of the
CASE example from earlier.
ON can also use
GOSUB instead of
GOTO.
ON X GOTO @0,@1,@2
?"X is something else!":GOTO @E
@0:?"X is 0!":GOTO @E
@1:?"X is 1!":GOTO @E
@2:?"X is 2!":GOTO @E
@E
As you may notice, these
GOTO and
GOSUB examples are often hard to read, which is why they aren't really recommended. There are usually better ways to accomplish the same things.
Functions and the empty type
Sometimes code gets repetitive, and you want to give a certain set of actions a name, so that you don't have to write it all out every time.
For example, consider this code:
LOOP
INPUT "First number";A
IF A<0 || A>9 THEN ?"Number must be a single digit" ELSE BREAK
ENDLOOP
LOOP
INPUT "Second number";B
IF B<0 || B>9 THEN ?"Number must be a single digit" ELSE BREAK
ENDLOOP
LOOP
INPUT "Third number";C
IF C<0 || C>9 THEN ?"Number must be a single digit" ELSE BREAK
ENDLOOP
?"The sum is ";A+B+C
This code works, but it's very repetitive, which can lead to mistakes if you have to change it in the future.
Instead, using
DEF, we can turn the repetitive code into a function.
DEF DIGIT(PROMPT)
LOOP
INPUT PROMPT;N
IF N<0 || N>9 THEN ?"Number must be a single digit" ELSE BREAK
ENDLOOP
RETURN N
END
A=DIGIT("First number")
B=DIGIT("Second number")
C=DIGIT("Third number")
?"The sum is ";A+B+C
Functions give part of our code a name, and allow us to reuse it in multiple places within our code. It doesn't matter whether
DEF comes before or after the place the function is used. Notice that the code within
DEF doesn't execute until the function is actually used.
Also, note that the
END here is used to mark the end of a
DEF. If there wasn't a
DEF, it would end the program instead.
The syntax for defining a function is somewhat complicated. A function has a set of arguments (the inputs of the function) and a set of return values (the outputs of the function). The basic syntax for defining a function is:
DEF name arg1,arg2,arg3... OUT ret1,ret2,ret3...
...
END
For example, here is a function with two inputs and two outputs.
DEF DIVMOD X,Y OUT D,M
D=X DIV Y
M=X MOD Y
END
DIVMOD 11,7 OUT A,B
?A,B '=> 1 4
If there are no outputs, the
OUT part can be omitted.
DEF HELLO
?"Hello, world!"
END
HELLO '=> Hello, world!
And if there's only one output, there's another special form.
DEF AVG(X,Y)
RETURN (X+Y)/2
END
A=AVG(10,20)
?A '=> 15
This is the same as writing the following code.
DEF AVG X,Y OUT A
A=(X+Y)/2
END
AVG 10,20 OUT A
?A '=> 15
The advantage of the
AVG(10,20) form is that it can be used in math expressions, and as an input to other functions.
?AVG(10,20)
X=10+AVG(5,8)
IF X>AVG(4,11) THEN ?"Yay!"
Y=AVG(4,AVG(15,25))
Any return values that are left unset become the empty type. This could be useful if you only want to return a value sometimes.
DEF NULL()
END
??NULL() '=> EMPTY
You can check for this type with
TYPEOF, as usual.
?TYPEOF(NULL()) '=> 0
It's also possible to omit arguments when calling a function, in which case they will be the empty type.
DEF TEST A,B
??A
??B
END
TEST 100,200 '=> INT: 100
'=> INT: 200
TEST 100, '=> INT: 100
'=> EMPTY
TEST ,200 '=> EMPTY
'=> INT: 200
TEST , '=> EMPTY
'=> EMPTY
There's also a way to have a function that can take any number of arguments, and/or return any number of return values. That will be covered later, in the "Variadic functions" section.
Arrays
Now, let's say we want to read three lines from input, and then print them in reverse order. That's pretty simple.
LINPUT A$
LINPUT B$
LINPUT C$
?C$
?B$
?A$
This worked fine, since we're only reversing three lines. But what if we wanted to reverse a hundred lines? Of course we could keep going, and add
D$,
E$,
F$,
G$,
H$... but it's best to avoid repetitive code as much as possible.
What we really need now is a list of strings. In SmileBASIC, lists of values are called "arrays".
To create an array, you can declare it with
VAR (or
DIM). The type of the array is determined by the name suffix, and the number within the brackets specifies how many values should be in the array.
VAR NUMBERS%[4]
You can check the length of an array (or string) with
LEN.
?LEN(NUMBERS%) '=> 4
At first, they will all be the default value for that type.
??NUMBERS% '=> INT[4]:
'=> [0]: 0
'=> [1]: 0
'=> [2]: 0
'=> [3]: 0
The values in the array are numbered starting from 0. These position numbers are called an "index".
To set the value at index 2 within the array, you can do this:
NUMBERS%[2]=123
??NUMBERS% '=> INT[4]:
'=> [0]: 0
'=> [1]: 0
'=> [2]: 123
'=> [3]: 0
You can access a specific value in the array the same way.
??NUMBERS%[2] '=> INT: 123
Unlike some other languages, arrays in SmileBASIC can only store one type. It's not possible to have an array that contains both a string and an integer, for instance.
However, arrays are resizable in SmileBASIC. For example,
PUSH adds a value to the end of an array.
VAR TEST#[2]
??TEST# '=> REAL[2]:
'=> [0]: 0.00000000
'=> [1]: 0.00000000
PUSH TEST#,567.89
??TEST# '=> REAL[3]:
'=> [0]: 0.00000000
'=> [1]: 0.00000000
'=> [2]: 567.89000000
You can also resize an array with
RESIZE.
RESIZE TEST#,5
??TEST# '=> REAL[5]:
'=> [0]: 0.00000000
'=> [1]: 0.00000000
'=> [2]: 567.89000000
'=> [3]: 0.00000000
'=> [4]: 0.00000000
RESIZE TEST#,1
??TEST# '=> REAL[1]:
'=> [0]: 0.00000000
So, let's go back to our previous example again. With arrays, we can write something like this:
VAR LINES$[100]
FOR I=0 TO 99
LINPUT L$
LINES$[I]=L$
NEXT
FOR I=99 TO 0 STEP -1
?LINES$[I]
NEXT
Note how we write
FOR I=0 TO 99. The first index is 0, so the last item has an index of 99, not 100. Rather than writing out
99, though, it would be better to use
LEN instead.
FOR I=0 TO LEN(LINES$)-1
LINPUT L$
LINES$[I]=L$
NEXT
LEN(...)-1 is so common in SmileBASIC that there's an even shorter way to write it. Note that
LAST(LINES$) is the
index of the last value in
LINES$, not the last value itself.
FOR I=0 TO LAST(LINES$)
LINPUT L$
LINES$[I]=L$
NEXT
But we can do more with this. What if, instead of reading a set number of lines, we keep reading until the user enters the line
END?
As mentioned earlier,
PUSH adds a value to the end of a list, so if we start with an empty array, we can do this:
VAR LINES$[0]
?"Enter some lines, then 'END' once you're done:"
LOOP
LINPUT L$
IF L$=="END" THEN BREAK ELSE PUSH LINES$,L$
ENDLOOP
Then, to print them in reverse order, we can take lines off the end of the array with
POP.
WHILE LEN(LINES$)>0
?POP(LINES$)
WEND
Now let's talk about making arrays with pre-defined data. We could write something like this:
VAR NUMBERS%[4]
NUMBERS%[0]=100
NUMBERS%[1]=1234
NUMBERS%[2]=5
NUMBERS%[3]=256
But it's kind of annoying to write it like that. There's special syntax that makes this easier.
VAR NUMBERS%[4]=[100,1234,5,256]
Because it knows from the list of numbers we give that the length should be 4, we can leave it out.
VAR NUMBERS%[]=[100,1234,5,256]
If you're coming from another language, note that the number list is part of the
VAR syntax, and you can't use it on its own.
?[1,2,3] 'DOES NOT WORK
Still, although this syntax is nice, it has some shortcomings when we're dealing with complex data. For that, we'll have to look at the appropriately-named
DATA command in the next section.
DATA
DATA lets you define data within your program. For example, say we're making an RPG, and we want data for a bunch of enemy names and their HP. We can write something like this:
DATA "Slime",10
DATA "Goblin",50
DATA "Werewolf",200
Now, by itself, this data doesn't do anything. To use it, we have to use
READ. Each
READ will read one value from the defined data in the program, starting from the very first
DATA.
READ X
?X '=> Slime
READ X
?X '=> 10
READ X
?X '=> Goblin
READ X
?X '=> 50
We can also read multiple values at once.
READ NAME$,HP%
?NAME$ '=> Werewolf
?HP% '=> 200
We run into a problem if we want to
READ the data again after we've already read it. To do that, we can use
RESTORE. First, we need to put a label above our
DATA.
@ENEMIES
DATA "Slime",10
DATA "Goblin",50
DATA "Werewolf",200
Then we can use
RESTORE to change where the next
READ will read from. The next
READ will use the first data after the label given to
RESTORE, and so on.
RESTORE @ENEMIES
READ NAME$,HP%
?NAME$,HP% '=> Slime 10
READ NAME$,HP%
?NAME$,HP% '=> Goblin 50
RESTORE @ENEMIES
READ NAME$,HP%
?NAME$,HP% '=> Slime 10
Where this really becomes useful is when we're dealing with arrays. For example, let's define two arrays for enemy names and HP.
VAR ENEMY_NAME$[0]
VAR ENEMY_HP%[0]
Then we can
READ the data into these arrays.
RESTORE @ENEMIES
FOR I=1 TO 3
READ NAME$,HP%
PUSH ENEMY_NAME$,NAME$
PUSH ENEMY_HP%,HP%
NEXT
??ENEMY_NAME$ '=> STRING[3]:
'=> [0]: (5)"Slime"
'=> [1]: (6)"Goblin"
'=> [2]: (8)"Werewolf"
??ENEMY_HP% '=> INT[3]:
'=> [0]: 10
'=> [1]: 50
'=> [2]: 200
We used
FOR I=1 TO 3 to make sure we wouldn't run out of data, but we could check for a special ending value instead.
@ENEMIES
DATA "Slime",10
DATA "Goblin",50
DATA "Werewolf",200
DATA ""
VAR ENEMY_NAME$[0]
VAR ENEMY_HP%[0]
RESTORE @ENEMIES
LOOP
READ NAME$
IF NAME$="" THEN BREAK
READ HP%
PUSH ENEMY_NAME$,NAME$
PUSH ENEMY_HP%,HP%
ENDLOOP
Finally, if we're only dealing with a single array, we can use
COPY to read a bunch of data into an array automatically. Note that in this case, we need to set the array size to the amount of data we want to read.
VAR NUMBERS%[10]
COPY NUMBERS%,@NUMBERS
@NUMBERS
DATA 0,1,1,2,3
DATA 5,8,13,21,34
Multi-dimensional arrays
Although we've only looked at arrays that are one-dimensional lists so far, it's also possible to make multi-dimensional arrays.
VAR BOARD%[3,2]=[10,20,30,40,50,60]
??BOARD% '=> INT[3,2]:
'=> [0,0]: 10
'=> [0,1]: 20
'=> [1,0]: 30
'=> [1,1]: 40
'=> [2,0]: 50
'=> [2,1]: 60
?BOARD%[1,0] '=> 30
Multi-dimensional arrays can still be indexed as if they were one-dimensional.
?BOARD%[2] '=> 30
Some commands like
COPY and
RESIZE still work with these arrays, but others like
PUSH and
POP only work for one-dimensional arrays.
LEN will give you the total number of values in the array.
?LEN(BOARD%) '=> 9
To find the size of each dimension, you can use the
DIM function. This is different from using
DIM as a command, which is the same as
VAR.
DIM with one argument gives you the number of dimensions in the array.
?DIM(BOARD%) '=> 2
Adding a second argument lets you see the size of each dimension.
?DIM(BOARD%,0) '=> 3
?DIM(BOARD%,1) '=> 2
Strings in-depth
We've used strings a lot already, but in fact, strings have some similarities to arrays. Strings are made up of characters, after all.
For example, strings have a length too.
?LEN("Hello!") '=> 6
You can even use an index with them! Doing so gives you a new string containing the character at that index.
HELLO$="Hello!"
?HELLO$[0] '=> H
?HELLO$[1] '=> e
HELLO$[1]="a"
?HELLO$ '=> Hallo!
Each character has a numeric code. For example,
A is 65, and
e is 101. You can get this code with the
ASC function.
?ASC("A") '=> 65
You can turn a code into a character with the
CHR$ function.
?CHR$(65) '=> A
These character codes come from a standard called Unicode. For those who are familiar with Unicode, strings use the UCS-2 encoding (UTF-16 without surrogate pairs). In other words, you are limited to characters within the Basic Multilingual Plane, which are characters 0 through 65535.
In some languages, there are escape sequences to put special characters in strings, but in SmileBASIC, you must use
CHR$. For example, to put a newline character in a string, use
CHR$(10).
?"Hello,"+CHR$(10)+"world!" '=> Hello,
'=> world!
There are a few functions used to work with strings.
LEFT$ and
RIGHT$ allow you to get a number of characters from the left or right side of a string.
?LEFT$("Hello, world!",6) '=> Hello,
?RIGHT$("Hello, world!",6) '=> world!
MID$ allows you to do the same from the middle of a string.
?MID$("Hello, world!",2,4) '=> llo,
INSTR finds a string within another string, returning an index.
?INSTR("Hello, world!","el") '=> 1
?INSTR("Hello, world!","world") '=> 7
?INSTR("Hello, world!","Hi!") '=> -1
SUBST$ replaces part of a string with another string.
?SUBST$("Hello!",2,2,"ww") '=> Hewwo!
INSTR and
SUBST$ can be combined to make a find-and-replace function.
DEF REPLACE$(TEXT$,BEFORE$,AFTER$)
I=INSTR(TEXT$,BEFORE$)
IF I<0 RETURN TEXT$
RETURN SUBST$(TEXT$,I,LEN(BEFORE$),AFTER$)
END
Files
Variables are great, but they don't stick around between runs of a program, so you may want to save them to a file so that you can load them later.
For example, if you're making a game, you may want to let people save their progress, If you're making an image editor, you'll want people to be able to save their artwork.
In SmileBASIC, there are six commands used for loading and saving, depending on what you want to load or save.
LOAD loads an editor slot from a text file.
LOAD "MY_PROGRAM",3
SAVE saves an editor slot to a text file.
SAVE "MY_PROGRAM",3
LOADG loads a graphics page from a graphics file.
LOADG "MY_SPRITES",2
SAVEG saves a graphics page to a graphics file.
SAVEG "MY_SPRITES",2
LOADV loads a string or array from a file.
MY_TEXT$=LOADV("TXT:MY_TEXT")
MY_DATA%=LOADV("DAT:MY_DATA")
SAVEV saves a string or array to a file.
SAVEV "TXT:MY_TEXT",MY_TEXT$
SAVEV "DAT:MY_DATA",MY_DATA%
If we're making a save file for a game,
SAVEV is what we'll want to use. Ideally we want just one file, since SmileBASIC asks the user if they want to save a file, and we don't want to bother them with multiple confirmation dialogs in a row. That said, games have a lot of different pieces of data, and you can only save one string or array in a file, so sometimes it takes a little creativity.
One easy way to do it is by creating a string array, and converting everything to a string. For example, if we have this data we want to save:
PLAYER_NAME$="Doc"
PLAYER_LEVEL%=30
PLAYER_MAX_HP%=250
We could have load and save code like this:
DEF SAVE_GAME
VAR SAVE$[3]
SAVE$[0]=PLAYER_NAME$
SAVE$[1]=STR$(PLAYER_LEVEL%)
SAVE$[2]=STR$(PLAYER_MAX_HP%)
SAVEV "DAT:SAVE",SAVE$
END
DEF LOAD_GAME
SAVE$=LOADV("DAT:SAVE")
PLAYER_NAME$=SAVE$[0]
PLAYER_LEVEL%=VAL(SAVE$[1])
PLAYER_MAX_HP%=VAL(SAVE$[2])
END
Variadic functions
Earlier, we covered functions with a set number of arguments or return values, but SmileBASIC 4 supports variadic functions as well.
To define a variadic function, we use
* instead of having a list of arguments or return values.
DEF SUM * OUT N
'code goes here
END
To access variadic arguments and return values, there are a few functions we can use.
- DEFARGC gives you the number of arguments.
- DEFARG gets a specific argument by index.
- DEFOUTC gives you the number of return values.
- DEFOUT sets a specific return value by index.
So, if we wanted to write a variadic function that takes any number of arguments and returns the sum, we can do this:
DEF SUM * OUT N
N=0
FOR I=0 TO DEFARGC()-1
N=N+DEFARG(I)
NEXT
END
?SUM(1,2,3) '=> 6
If we wanted to write a function that gives any number of return values, we can do something like this:
DEF FIBONACCI OUT *
A=0
B=1
FOR I=0 TO DEFOUTC()-1
DEFOUT I,A
SWAP A,B
B=A+B
NEXT
END
FIBONACCI OUT A,B,C,D,E,F,G
?A,B,C,D,E,F,G '=> 0 1 1 2 3 5 8
And of course, both arguments and return values can be variadic.
DEF DOUBLE * OUT *
IF DEFARGC()!=DEFOUTC() THEN
STOP "Must have same number of arguments and return values"
ENDIF
FOR I=0 TO DEFARGC()-1
DEFOUT I,2*DEFARG(I)
NEXT
END
DOUBLE 2,7,6 OUT A,B,C
?A,B,C '=> 4 14 12
EXEC and COMMON DEF
SmileBASIC allows for six code files to be loaded at once, in the editor slots 0 through 5.
To have one code file load and run another, you can use
EXEC. This loads the text file
MYLIBRARY into slot 1 and runs it.
EXEC "MYLIBRARY",1
In fact, this is short for:
LOAD "MYLIBRARY",1
EXEC 1
When one slot executes another,
END will return back to after the
EXEC that executed the slot it's in, rather than ending the program. For example, if slot 0 uses
EXEC to run slot 1, and slot 1 reaches
END, execution will return back to slot 0, immediately after the
EXEC.
Put another way,
EXEC allows you to call another slot as a subroutine, which returns using
END.
It's possible to access variables from other slots using
VAR with a slot number.
?VAR("2:FOO")
VAR("0:BAR")=123
It's also possible to specify labels in other slots.
COPY ARR%,"0:@ARR"
Normally, a function can't be accessed outside of the slot it's in. However,
COMMON DEF will export that function so that it's available in any slot.
COMMON DEF TEST A,B
?A+B
END
The basic structure of a library in SmileBASIC ends up looking like this:
'globals and initialization code...
END
'DEF functions that are internal to the library...
'COMMON DEF functions that represent the library interface...