Dang, man. This is really good. Thanks!
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 '=> 100If 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 '=> 10To print a blank line, you can just use PRINT without any value.
PRINT 1,2,3 '=> 1 2 3 PRINT 1;2;3 '=> 123Code 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".
CLSYou 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.- Empty: This is the type used when there's no value at all. SmileBASIC also refers to this as the "default type". This type doesn't come up very often, so it will be covered later in the "Functions and empty type" section.
- Integer: This is a number without a decimal point. 32-bit.
?5 ?-100
- Real number: This is a number with a decimal point, also called a "floating-point number" or "float" for short. 64-bit.
?5.0 ?-123.456789
- String: This stores some text using the UCS-2 character encoding.
?"Hello, world!" ?"ใใใซใกใฏ๏ผ"
- Integer, real number, and string arrays: These store a list of values inside of them. There's a lot to talk about with these, so they'll be covered later in the "Arrays" section.
?TYPEOF(5) '=> 1 ?TYPEOF(1.234) '=> 2 ?TYPEOF("Hi!") '=> 3There'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: 5It's possible to convert between these types as well. INT converts from a real number to an integer.
??INT(2.5) '=> INT: 2FLOAT converts from an integer to a real number.
??FLOAT(5) '=> REAL: 5.00000000STR$ 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: 0SmileBASIC'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 '=> HaHaHaHaHaThere'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 3Exponents can be calculated using the POW function. This code calculates 2 to the power of 8.
?POW(2,8) '=> 256Bitwise 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 '=> 4The 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 '=> -2147483646When 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=123Once a variable is set, you can use it with commands like PRINT.
?SALLY '=> 123You 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 '=> 151To 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: 110Variables 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 STRICTOnce 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 '=> 456You 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: 0You can even declare multiple variables at once.
VAR A=10,B=20,C ?A,B,C '=> 10 20 0Instead of VAR, you can write DIM, which does the same thing.
DIM BOB=12345 ?BOB '=> 12345VAR 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")=500It'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=1280It'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? ";XIf you want, you can leave off the prompt entirely.
LINPUT XINPUT 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 '=> 0To 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 '=> 0To check if a number is NOT equal to another, use !=.
?4!=5 '=> 1 ?5!=5 '=> 0To check if a number is less than another, use <. To check if a number is greater than another, use >.
?3<7 '=> 1 ?3>7 '=> 0To 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 '=> 1If 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 '=> 1To 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!" ENDIFELSE can also be used with this form.
IF X==5 THEN ?"X is equal to 5!" ELSE ?"X is not equal to 5!" ENDIFIf 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." ENDIFIf 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!" ENDLOOPYou can also loop while a condition is true by using WHILE...WEND.
X=10 WHILE X<100 ?"X is ";X INC X,5 WENDYou can loop over a range of numbers with FOR...NEXT.
FOR X=1 TO 10 ?"X is ";X NEXTYou 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 NEXTBREAK 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 NEXTCONTINUE 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.ENDSTOP 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.@NAMEGOTO jumps to a specific label. For instance, this code prints 1 and 3, skipping 2.
?1 GOTO @DENNIS ?2 @DENNIS ?3Note that when using a label as a value, it is actually a string. In other words, these lines are equivalent:
S$="@TEST" S$=@TESTIt's also possible to jump backwards, making a loop. It's preferred to write this with LOOP...ENDLOOP instead, though.
@HELLO ?"Hello, world!" GOTO @HELLOGOSUB 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!" RETURNIf 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 @EAs 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+CThis 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+CFunctions 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... ... ENDFor 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 4If 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 '=> 15This 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 '=> 15The 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() '=> EMPTYYou can check for this type with TYPEOF, as usual.
?TYPEOF(NULL()) '=> 0It'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 '=> EMPTYThere'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%) '=> 4At first, they will all be the default value for that type.
??NUMBERS% '=> INT[4]: '=> [0]: 0 '=> [1]: 0 '=> [2]: 0 '=> [3]: 0The 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]: 0You can access a specific value in the array the same way.
??NUMBERS%[2] '=> INT: 123Unlike 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.89000000You 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.00000000So, 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] NEXTNote 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$ NEXTLEN(...)-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$ NEXTBut 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$ ENDLOOPThen, to print them in reverse order, we can take lines off the end of the array with POP.
WHILE LEN(LINES$)>0 ?POP(LINES$) WENDNow 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]=256But 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 WORKStill, 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",200Now, 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 '=> 50We can also read multiple values at once.
READ NAME$,HP% ?NAME$ '=> Werewolf ?HP% '=> 200We 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",200Then 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 10Where 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]: 200We 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% ENDLOOPFinally, 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] '=> 30Multi-dimensional arrays can still be indexed as if they were one-dimensional.
?BOARD%[2] '=> 30Some 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%) '=> 9To 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%) '=> 2Adding 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!") '=> 6You 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") '=> 65You can turn a code into a character with the CHR$ function.
?CHR$(65) '=> AThese 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!") '=> -1SUBST$ 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",3SAVE saves an editor slot to a text file.
SAVE "MY_PROGRAM",3LOADG loads a graphics page from a graphics file.
LOADG "MY_SPRITES",2SAVEG saves a graphics page to a graphics file.
SAVEG "MY_SPRITES",2LOADV 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%=250We 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 ENDTo 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.
DEF SUM * OUT N N=0 FOR I=0 TO DEFARGC()-1 N=N+DEFARG(I) NEXT END ?SUM(1,2,3) '=> 6If 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 8And 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",1In fact, this is short for:
LOAD "MYLIBRARY",1 EXEC 1When 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")=123It'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 ENDThe 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...