01Start

Welcome to basicFusion

basicFusion is a modern QBasic-style BASIC interpreter that runs in the browser. It supports both classic text programs and 2D graphics with animation on an HTML5 canvas.

The environment is compatible with QBasic/QuickBASIC syntax. You can write programs with line numbers (classic style) or without them, using text labels instead. The built-in graphics panel lets you draw shapes, animations, and demoscene effects.

How to Run a Program

Type your code in the editor and press Ctrl+F9 or click the Run button. The terminal and graphics canvas will appear on the right.

Text Mode

Default mode. PRINT writes to the terminal. INPUT reads data from the user.

Graphics Mode

Activated by SCREEN. An HTML5 canvas with full 2D support: lines, circles, fills, text, animation.

💡
No line numbers needed! In basicFusion you don't have to write 10, 20, 30... You can write code like a normal language, using readable text labels instead of numbers.
02Basics

Program Structure

Line Numbers vs. Labels

You can use line numbers (classic style) or text labels (modern style). Both approaches work simultaneously.

' Classic style with numbers
10 PRINT "Hello!"
20 GOTO 10
' Modern style with labels
START:
  PRINT "Hello!"
  GOTO START  ' jumps to label START

Comments

REM This is a full-line comment
PRINT "Code" ' This is also a comment (apostrophe)

Program Header => REM metadata (Program / Author / Version / Description)

You can put a small header of REM comments at the very top of your program. In the Build menu, App Settings lets you edit it, and the export dialog reads it to prefill its fields. Keys are case-insensitive and each line is optional.

REM Program: Franko
REM Author: Terror
REM Version: 1.0
REM Description: A retro action game made with basicFusion

The header must sit in the leading comment block (before the first line of real code). It is made of ordinary comments, so it has no effect on how the program runs. The apostrophe form works too, e.g. ' Author: Terror.

Multiple Statements on One Line

LET A = 5 : LET B = 10 : PRINT A + B

Ending the Program

END  ' Stops program execution
02bBasics

Compiler Options => OPTION

The OPTION command is used to configure how the basicFusion interpreter behaves, acting like a compiler directive or pragma.

OPTION EXPLICIT

By default, variables in basicFusion do not need to be declared before use. If you misspell a variable name, the interpreter simply creates a new one with a default value of 0 or "". Using OPTION EXPLICIT forces you to explicitly declare all variables using DIM or LOCAL before assigning values to them. This helps prevent bugs caused by typos.

OPTION EXPLICIT

DIM PLAYER_SPEED AS INTEGER
PLAYER_SPEED = 5

PRINT PLAYER_SPEED

OPTION STRICT

While OPTION EXPLICIT forces variable declaration, OPTION STRICT goes a step further by enforcing strict data typing and preventing unsafe implicit type conversions. For example, it stops you from accidentally assigning a decimal number to an integer variable without an explicit conversion, or mixing strings and numbers.

OPTION STRICT

DIM SCORE AS INTEGER
SCORE = 10.5       ' Error: Implicit conversion not allowed
SCORE = CINT(10.5) ' OK: Explicit conversion using CINT()
⚠️
Placement: Directives like OPTION EXPLICIT and OPTION STRICT must be placed at the very top of your program, before any variables are used or assigned.

OPTION STACKSIZE / OPTION VMSPEED

Two options let a program override the IDE Settings for itself: OPTION STACKSIZE n sets how deep recursion and GOSUB may nest before a Stack overflow error (raise it for deep recursion, lower it to catch runaway recursion early), and OPTION VMSPEED n sets how many instructions run per tick (higher = faster/less responsive, lower = smoother). If omitted, the values from Settings are used.

OPTION VMSPEED 500000   ' run faster than the default
OPTION STACKSIZE 20000  ' allow deeper recursion
03Basics

Variables and Data Types

Variable Types

BASIC distinguishes between numeric and text (string) variables. String variables end with the $ character.

TypeExample nameExample valueDescription
NumericA, X, SCORE, i42, 3.14, -7Integer or floating-point number
Text (String)A$, NAME$, S$"Hello", "Jan"Character sequence, name ends with dollar sign

Assigning Values

The LET keyword is optional, you can omit it.

LET X = 10          ' with LET
Y = 3.14              ' without LET (also works)
NAME$ = "Anna"       ' string variable
RESULT = X + Y      ' expression
PRINT NAME$; " is "; X; " years old"

Compound Assignment => += -= *= /= ^=

Shorthand for updating a variable from its own value. X += 5 means X = X + (5). Works on array elements and object fields too. Since strings join with +, S$ += "!" appends text (there is no & operator).

SCORE = 0
SCORE += 100       ' 100
SCORE *= 2         ' 200
MSG$ = "Hi"
MSG$ += " there"   ' "Hi there"

INC / DEC => Increment & Decrement

INC x adds 1, DEC x subtracts 1. An optional second argument is a custom step. Works on array elements and object fields as well.

LIVES = 3
DEC LIVES          ' 2
INC SCORE, 50     ' SCORE = SCORE + 50

SWAP => Swapping Values

A = 10 : B = 20
SWAP A, B
PRINT A, B     ' prints: 20   10
⚠️
Note: Variables do not need to be declared in advance. An unused numeric variable defaults to 0, a string variable defaults to the empty string "".
03bBasics

Constants => CONST

Constants are named values that are set once and never change during program execution. Using constants makes code more readable and eliminates "magic numbers".

Declaring Constants

Use CONST followed by the name, an equals sign, and the value. Constants can be numeric or string.

CONST MAX_LIVES = 3
CONST SCREEN_W = 640
CONST SCREEN_H = 480
CONST GRAVITY  = 0.5
CONST TITLE$   = "My Game"

Using Constants

After declaration, use a constant just like any variable, but you cannot assign a new value to it.

CONST PLAYER_SPEED = 5
CONST BALL_R = 20

SCREEN 12
PX = 320

LOOP:
  PAINT "black"
  IF KEY("ARROWRIGHT") THEN PX = PX + PLAYER_SPEED
  IF KEY("ARROWLEFT")  THEN PX = PX - PLAYER_SPEED
  CIRCLE (PX, 240), BALL_R, "lime"
  SYNC
GOTO LOOP
⚠️
Note: Attempting to assign a new value to a constant (e.g. MAX_LIVES = 5 after declaring it with CONST) will cause a runtime error. Declare all constants at the top of your program.
💡
Convention: Constants are typically written in ALL_CAPS to visually distinguish them from regular variables.
04Basics

Operators

Arithmetic

OperatorMeaningExampleResult
+Addition3 + 47
-Subtraction10 - 37
*Multiplication4 * 520
/Division10 / 42.5
^Exponentiation2 ^ 8256
MODRemainder (modulo)17 MOD 52

Comparisons (return -1 = true, 0 = false)

OperatorMeaning
= or ==Equal
<>Not equal
< , >Less than / greater than
<= , >=Less than or equal / greater than or equal

Logical

OperatorMeaningExample
ANDLogical AND (both must be true)X > 0 AND X < 10
ORLogical OR (at least one must be true)X < 0 OR X > 100
NOTLogical NOT (negation)NOT (X = 5)

String Concatenation

A$ = "Hello" + " World"
B$ = "Age: " + STR$(25)
PRINT A$    ' Hello World
06Control Flow

Conditions => IF / ELSE / END IF

Single-line Form

IF X > 0 THEN PRINT "Positive"
IF X > 0 THEN PRINT "Positive" ELSE PRINT "Non-positive"

Block Form (multi-line)

IF SCORE >= 90 THEN
  PRINT "Grade: A"
ELSE IF SCORE >= 70 THEN
  PRINT "Grade: B"
ELSE IF SCORE >= 50 THEN
  PRINT "Grade: C"
ELSE
  PRINT "Grade: F"
END IF
💡
Important: The block form requires END IF at the end. The single-line form does not need END IF.

Nested Conditions

IF A > 0 THEN
  IF B > 0 THEN
    PRINT "A and B are positive"
  END IF
END IF
07Control Flow

Loops => FOR / WHILE / DO

💡
Note: In BASIC, loops don't use "END" keywords. FOR is closed by NEXT, DO by LOOP, and WHILE by WEND.

FOR ... NEXT => Counter Loop

FOR I = 1 TO 10
  PRINT "Step:"; I
NEXT I

WHILE ... WEND => Simple Condition

WHILE X < 10
  X = X + 1
WEND

DO ... LOOP => Flexible Loop

DO
  INPUT "Again? (y/n)"; A$
LOOP UNTIL A$ = "n"

EXIT => Early Loop Termination

Use EXIT FOR or EXIT DO to immediately break out of a loop. Note that there is no "EXIT WHILE".

FOR I = 1 TO 100
  IF KEY("ESCAPE") THEN EXIT FOR
NEXT I

CONTINUE => Skip to Next Iteration

Use CONTINUE to jump straight to the next pass of the loop, skipping the rest of the body. It works in FOR, WHILE and DO loops (in FOR the counter is still advanced). Optionally name the loop type: CONTINUE FOR.

FOR I = 1 TO 6
  IF I MOD 2 = 0 THEN CONTINUE
  PRINT I   ' prints 1, 3, 5
NEXT I
08Control Flow

GOTO, GOSUB and ON

GOTO => Unconditional Jump

GOTO DONE
PRINT "This will not run"
DONE:
PRINT "End"

GOSUB / RETURN => Subroutines with Return

GOSUB DRAW_HEADER
PRINT "Content"
GOSUB DRAW_FOOTER
END

DRAW_HEADER:
  PRINT STRING$(40, "=")
  PRINT "  PROGRAM TITLE"
  PRINT STRING$(40, "=")
RETURN

DRAW_FOOTER:
  PRINT STRING$(40, "-")
RETURN

ON X GOTO / ON X GOSUB => Jump by Index

INPUT "Choose 1-3: "; CHOICE
ON CHOICE GOTO OPC1, OPC2, OPC3
GOTO ERR
OPC1: PRINT "You chose 1" : GOTO DONE
OPC2: PRINT "You chose 2" : GOTO DONE
OPC3: PRINT "You chose 3" : GOTO DONE
ERR:  PRINT "Invalid choice"
DONE:
09Control Flow

SELECT CASE

An elegant alternative to long IF/ELSE IF chains when checking a single variable.

INPUT "Day of week (1-7): "; D
SELECT CASE D
  CASE 1
    PRINT "Monday"
  CASE 2
    PRINT "Tuesday"
  CASE 6, 7
    PRINT "Weekend!"
  CASE ELSE
    PRINT "Midweek"
END SELECT
💡
CASE ELSE is the default branch, it runs when no other CASE matches.

Ranges => CASE lo TO hi

A single CASE can match a whole range of values with TO. The bounds can be numbers, variables, or expressions, and it works on strings too (compared alphabetically).

SELECT CASE SCORE
  CASE 90 TO 100
    PRINT "A"
  CASE 80 TO 89
    PRINT "B"
  CASE ELSE
    PRINT "lower"
END SELECT

' String range - matches "A" through "M"
SELECT CASE LETTER$
  CASE "A" TO "M"
    PRINT "first half"
  CASE "N" TO "Z"
    PRINT "second half"
END SELECT

Comparisons => CASE IS

Use CASE IS with a comparison operator (<, <=, >, >=, =, <>) to match everything above, below, or different from a value.

SELECT CASE N
  CASE IS < 0
    PRINT "negative"
  CASE 0
    PRINT "zero"
  CASE IS >= 100
    PRINT "big"
  CASE ELSE
    PRINT "in between"
END SELECT

You can mix all of these in one CASE, separated by commas:

CASE 1, 5 TO 9, IS > 100    ' 1, or 5..9, or anything over 100
💡
Ranges and CASE IS also work inside FUNCTIONs and SUBs, and you can nest a SELECT inside another CASE branch.
10Data

Arrays => DIM

Arrays are variables that store multiple values indexed by a number. Indices start at 0.

One-dimensional Array

DIM SCORES(9)       ' 10 elements: SCORES(0) to SCORES(9)

FOR I = 0 TO 9
  SCORES(I) = I * 10
NEXT I

PRINT SCORES(5)     ' prints: 50

Two-dimensional Array (Matrix)

DIM MAP(9, 9)       ' 10x10 matrix

FOR R = 0 TO 9
  FOR C = 0 TO 9
    MAP(R, C) = R * 10 + C
  NEXT C
NEXT R

Multiple Arrays in One DIM

DIM X(99), Y(99), ACTIVE(99)

Custom Index Bounds => DIM with TO

Instead of always starting at 0, you can declare arrays with any lower bound using the TO keyword. This is very useful when indices represent real-world values like years, coordinates, or months.

' DIM name(lower TO upper)
DIM YEAR_DATA(2000 TO 2030)    ' index 2000..2030 (31 elements)
DIM BOARD(1 TO 8, 1 TO 8)      ' chess board, rows/cols 1..8
DIM GRID(-5 TO 5)               ' negative lower bound is allowed

YEAR_DATA(2024) = 999
PRINT YEAR_DATA(2024)            ' 999

FOR R = 1 TO 8
  FOR C = 1 TO 8
    BOARD(R, C) = R * 10 + C
  NEXT C
NEXT R

LBOUND / UBOUND => Query Array Bounds

LBOUND returns the lowest valid index and UBOUND returns the highest. They accept an optional second argument for the dimension (default: 1). This lets you write loops that work correctly regardless of how the array was declared.

FunctionSyntaxDescription
LBOUNDLBOUND(array [, dim])Returns the lower bound of the given dimension (default dim=1)
UBOUNDUBOUND(array [, dim])Returns the upper bound of the given dimension (default dim=1)
DIM PRICES(5 TO 15)
PRINT LBOUND(PRICES)    ' 5
PRINT UBOUND(PRICES)    ' 15

FOR I = LBOUND(PRICES) TO UBOUND(PRICES)
  PRICES(I) = I * 2.5
NEXT I
' Multi-dimensional, pass dimension number as second arg
DIM MAP(1 TO 10, 3 TO 7)
PRINT LBOUND(MAP, 1)   ' 1
PRINT UBOUND(MAP, 1)   ' 10
PRINT LBOUND(MAP, 2)   ' 3
PRINT UBOUND(MAP, 2)   ' 7
💡
Good practice: Always use LBOUND/UBOUND in your loops instead of hard-coded numbers. If you ever change the array size, your loops automatically adapt.
⚠️
Note: You must declare an array with DIM before using it, otherwise you will get a "missing array" error.

REDIM => Resize an Array

REDIM changes the size of an array. Plain REDIM gives you a fresh array at the new size, so all elements are reset to 0 (or "" for strings). It also works on an array that was never DIM'd, and the bounds can be expressions.

DIM GRID(9)          ' 10 elements
' ... later, need more room ...
REDIM GRID(99)       ' now 100 elements, all reset to 0

' Size from a variable, and 2D works too
N = 50
REDIM MAP(N, N)
⚠️
Heads up: plain REDIM clears the contents. REDIM PRESERVE (keeping the old values) is not supported yet, it raises a clear error for now. Inside a SUB, REDIM targets the local array, like DIM.

ERASE => Free Arrays From Memory

ERASE removes one or more arrays from memory and frees the space they used. After erasing, the array no longer exists, using it again without a fresh DIM raises a "not an array" error. Use it to reclaim memory from large temporary arrays, or to rebuild an array at a different size.

' ERASE array [, array...]
DIM BUFFER(9999)
' ... fill and use the buffer ...
ERASE BUFFER          ' frees the memory it held

' Re-DIM at a new size afterwards
DIM BUFFER(99)      ' fresh, smaller array
' Erase several arrays in one statement
DIM X(99), Y(99), SCORES(50)
ERASE X, Y, SCORES
⚠️
Note: ERASE takes bare array names, no parentheses or indices. Inside a SUB it erases the local array, leaving any global array of the same name untouched. After erasing you must DIM the array again before reading or writing its elements.
10cData

Dynamic Expression Evaluation => EVAL

EVAL(string) takes a string and evaluates it as a BASIC expression at runtime, returning the result. This lets you build and execute expressions dynamically, useful for formula interpreters, calculators, scripted logic, and data-driven programs.

Basic Usage

EXPR$ = "3 + 4 * 2"
PRINT EVAL(EXPR$)        ' 11

X = 10
PRINT EVAL("X * X + 1")   ' 101, can reference current variables

Example => Simple Formula Calculator

INPUT "Enter formula: "; F$
RESULT = EVAL(F$)
PRINT "= "; RESULT

Example => Building Expressions from Data

FOR N = 1 TO 5
  E$ = STR$(N) + " * " + STR$(N) + " + 1"
  PRINT N; "^2 + 1 = "; EVAL(E$)
NEXT N
⚠️
EVAL evaluates expressions only, it cannot run full statements like IF, FOR, or GOTO. Use it for numeric and string expressions such as math formulas and function calls.
💡
Variables are in scope: EVAL has access to all variables defined in the current program, so expressions like EVAL("SCORE * MULTIPLIER") work as expected.
10bData

Custom Structures => TYPE / END TYPE

TYPE lets you group multiple related fields into a single named structure, similar to a record or struct. Instead of keeping separate arrays for each property, you store everything together under one variable.

Defining a TYPE

Declare the structure before the program starts. Fields use AS with a type: STRING * N (fixed-length string), INTEGER, SINGLE, or DOUBLE.

TYPE Player
    name   AS STRING * 20   ' fixed 20-char string
    score  AS INTEGER
    health AS SINGLE
END TYPE

Creating Variables and Arrays of a TYPE

Use DIM with AS TypeName to create a single variable or an array of that type.

DIM p AS Player          ' single record
DIM team(4) AS Player    ' array of 5 players (0..4)

Accessing Fields => Dot Notation

Access fields with a dot: variable.field

p.name   = "Hero"
p.score  = 100
p.health = 99.5

PRINT p.name; ", score: "; p.score   ' Hero, score: 100

Using an Array of Structures

Loop through records just like a normal array, use dot notation on each element.

TYPE Employee
    nm     AS STRING * 40
    role   AS STRING * 40
    salary AS SINGLE
END TYPE

DIM emp(4) AS Employee

emp(0).nm     = "Anna Kowalski"
emp(0).role   = "Developer"
emp(0).salary = 8500

FOR i = 0 TO 4
    PRINT RTRIM$(emp(i).nm); ", "; emp(i).salary
NEXT i
💡
STRING * N fields: Fixed-length strings are padded with spaces to fill the declared length. Always use RTRIM$() when displaying or comparing them to strip the trailing spaces.
⚠️
TYPE before DIM: The TYPE block must appear before any DIM that uses it. Place all TYPE definitions at the very top of your program.
10dData

Object-Oriented Programming => OBJECT

basicFusion supports fully featured Object-Oriented Programming (OOP). An OBJECT can encapsulate both fields (variables) and methods (subroutines or functions with executable code).

Methods can have their executable code written in two ways: directly inside the object block, or forward-declared and implemented outside.

1. Inline Methods (Code inside the Object)

You can write the complete executable code of a method directly inside the OBJECT block. Fields belonging to the object are implicitly available to these methods.

OBJECT IntStack
    DIM buf(16) AS INTEGER
    DIM sp AS INTEGER
    
    SUB Push(v)
        sp = sp + 1
        buf(sp) = v
    END SUB
    
    FUNCTION Pop() AS INTEGER
        Pop = buf(sp)
        sp = sp - 1
    END FUNCTION
END OBJECT

2. Declared Methods (Code outside the Object)

Alternatively, you can keep the object definition clean by forward-declaring headers with DECLARE, and providing the executable code outside the block using dot notation (ClassName.MethodName).

OBJECT Vec
    DIM x AS SINGLE
    DIM y AS SINGLE
    DECLARE FUNCTION Dot(o) AS SINGLE
END OBJECT

FUNCTION Vec.Dot(o)
    Dot = x * o.x + y * o.y
END FUNCTION

The THIS Keyword

While object fields are bound implicitly, you can use the special THIS keyword inside any method to explicitly reference the current object instance. This is required when a method needs to call another method on itself or pass its context along.

OBJECT Mob
    DIM hp AS INTEGER
    SUB Hurt(d)
        hp = hp - d
        IF hp < 0 THEN hp = 0
    END SUB
    SUB HurtTwice(d)
        THIS.Hurt(d) ' Calls its own Hurt method twice
        THIS.Hurt(d)
    END SUB
END OBJECT
💡
Deep Instantiation: basicFusion automatically handles nested objects! If you declare an object field inside another object (e.g., DIM pos AS Vec inside a player class), the runtime engine recursively instantiates the sub-object safely in memory.

Usage Example => Arrays of Objects

Objects can be instantiated into regular variables or stored inside standard arrays initialized via DIM.

DIM party(3) AS Mob
FOR i = 1 TO 3
    party(i).hp = i * 10
NEXT i
party(2).HurtTwice(4)
PRINT "Mob 2 HP: "; party(2).hp
11Data

Subroutines => SUB / CALL

Subroutines are code blocks with parameters. They can be called repeatedly using CALL.

DECLARE SUB GREET(NAME$)
DECLARE SUB SQUARE(N)

CALL GREET("Anna")
CALL SQUARE(5)
CALL SQUARE(12)
END

SUB GREET(NAME$)
  PRINT "Hello, "; NAME$; "!"
END SUB

SUB SQUARE(N)
  PRINT N; " squared = "; N ^ 2
END SUB
💡
DECLARE SUB at the top tells the interpreter that the subroutine exists. The SUB ... END SUB definition can be placed at the end of the file.
11bData

Variable Scope => GLOBAL / LOCAL / STATIC / SHARED

Inside a SUB or FUNCTION a variable can behave in several different ways. Choosing the right one prevents procedures from accidentally overwriting each other's data.

KindHow to declareLifetime & visibility
Globaljust use the nameShared by the whole program. A plain variable used in a SUB refers to the global of that name.
LocalLOCAL XPrivate to the current call. Created fresh on entry, discarded on exit. Never touches a global of the same name.
StaticSTATIC XPrivate like LOCAL, but its value survives between calls. The procedure "remembers" it.
SharedSHARED X  /  DIM SHARED XExplicitly ties a name to the global of the same name, so a procedure reads and writes the shared copy.

Global by Default

If you do not declare a variable, it is global. The parameters of a SUB/FUNCTION are always local, but any other plain name reaches out to the shared global space.

SCORE = 0
CALL ADD_POINT
CALL ADD_POINT
PRINT SCORE        ' prints 2, the global was updated
END

SUB ADD_POINT()
  SCORE = SCORE + 1   ' no LOCAL → this is the global SCORE
END SUB

LOCAL => Private and Fresh Each Call

Use LOCAL for a scratch variable that should not leak out of the procedure. It starts empty (0 or "") every time the procedure runs, even if a global of the same name exists.

N = 100
CALL DEMO
PRINT N            ' still 100, the global was untouched
END

SUB DEMO()
  LOCAL N = 5      ' a separate, private N
  N = N + 1
  PRINT "inside: "; N   ' inside: 6
END SUB

STATIC => Remembers Between Calls

A STATIC variable is local, but it keeps its value from one call to the next. The optional = value initializer runs only on the first call, perfect for counters and state.

CALL COUNTER
CALL COUNTER
CALL COUNTER       ' prints 1, then 2, then 3
END

SUB COUNTER()
  STATIC TIMES = 0   ' = 0 runs once, on the first call
  TIMES = TIMES + 1
  PRINT TIMES
END SUB

Local Arrays

To make an array local to a procedure, declare it with LOCAL DIM. A static array uses STATIC DIM and is built only on the first call.

SUB SCRATCH()
  LOCAL DIM BUFFER(10)   ' private array, fresh each call
  BUFFER(0) = 42
END SUB

SHARED => Reaching the Global on Purpose

There are two ways to make a global explicitly available to procedures. Use DIM SHARED at the top level to declare a variable that every SUB and FUNCTION sees automatically:

DIM SHARED LIVES
LIVES = 3

CALL HURT
PRINT LIVES        ' prints 2, the shared global changed
END

SUB HURT()
  LIVES = LIVES - 1   ' same LIVES as the main program
END SUB

Or use SHARED inside a single procedure to tie one name to the global, just for that procedure:

TOTAL = 0
CALL ADD_TEN
PRINT TOTAL        ' prints 10
END

SUB ADD_TEN()
  SHARED TOTAL      ' TOTAL here means the global TOTAL
  TOTAL = TOTAL + 10
END SUB
💡
Two ways to share: DIM SHARED X at the top level shares X with every procedure at once. SHARED X written inside a single SUB shares it only within that SUB. Both make the procedure use the one global copy instead of a private one.
⚠️
Watch out: a global and a LOCAL/STATIC of the same name are different variables. If a SUB declares LOCAL SCORE, changing it will not affect the global SCORE outside the SUB.
12bData

Multi-line Functions => FUNCTION

FUNCTION block allows for complex, multi-line operations. You return a value from a function by assigning it directly to the function's name.

💡
Usage: Functions are used directly in expressions (like in a PRINT statement or variable assignment), not with the CALL keyword.
PRINT "Result: "; ADD_AND_MULTIPLY(5, 10)

FINAL_SCORE = ADD_AND_MULTIPLY(3, 4)
PRINT "Final Score: "; FINAL_SCORE
END

FUNCTION ADD_AND_MULTIPLY(A, B)
  SUM = A + B
  ' Return value by assigning to the function name
  ADD_AND_MULTIPLY = SUM * 2
END FUNCTION

Example: Factorial (Recursion)

PRINT "Factorial of 5 is: "; FACTORIAL(5)
END

FUNCTION FACTORIAL(N)
  IF N <= 1 THEN
    FACTORIAL = 1
  ELSE
    FACTORIAL = N * FACTORIAL(N - 1)
  END IF
END FUNCTION
13Data

DATA and READ => Embedded Data

Allows you to embed data directly in the program code and read it sequentially.

DATA 10, 20, 30, 40, 50
DATA "Ala", "Bob", "Celina"

READ A, B, C         ' A=10, B=20, C=30
PRINT A, B, C

FOR I = 1 TO 3
  READ NAME$
  PRINT "Name: "; NAME$
NEXT I

RESTORE              ' reset pointer to start of DATA
READ FIRST          ' 10 again
14Data

Math Functions

FunctionDescriptionExampleResult
SIN(x)Sine (radians)SIN(PI/2)1
COS(x)Cosine (radians)COS(0)1
TAN(x)TangentTAN(PI/4)1
ATN(x)ArctangentATN(1)*4PI
SQR(x)Square rootSQR(16)4
ABS(x)Absolute valueABS(-7)7
INT(x)Round down (floor)INT(3.9)3
FIX(x)Truncate fractionFIX(-3.7)-3
CINT(x)Round to nearest integerCINT(3.6)4
SGN(x)Sign of number (-1, 0, 1)SGN(-5)-1
LOG(x)Natural logarithmLOG(EXP(1))1
EXP(x)e raised to the power xEXP(1)2.718...
RND()Random number 0.0, 1.0INT(RND()*6)+11-6
CLAMP(v,lo,hi)Constrains v to [lo, hi]CLAMP(120, 0, 100)100
LERP(a,b,t)Linear blend: a+(b-a)*tLERP(0, 100, 0.25)25
PIConstant π (3.14159...)2*PI*Rcircumference
' Random dice roll
ROLL = INT(RND() * 6) + 1
PRINT "Rolled:"; ROLL
15Data

String Operations

FunctionDescriptionExampleResult
LEN(s$)String lengthLEN("Hello")5
LEFT$(s$,n)First n charactersLEFT$("BASIC",3)"BAS"
RIGHT$(s$,n)Last n charactersRIGHT$("BASIC",3)"SIC"
MID$(s$,p,n)Substring from position p, n charactersMID$("BASIC",2,3)"ASI"
INSTR(s$,s2$)Position of s2$ in s$ (0 = not found)INSTR("Hello","ll")3
UCASE$(s$)Convert to uppercaseUCASE$("abc")"ABC"
LCASE$(s$)Convert to lowercaseLCASE$("ABC")"abc"
LTRIM$(s$)Remove leading spacesLTRIM$(" ok")"ok"
RTRIM$(s$)Remove trailing spacesRTRIM$("ok ")"ok"
STR$(n)Number to stringSTR$(42)" 42"
VAL(s$)String to numberVAL("3.14")3.14
CHR$(n)Character from ASCII codeCHR$(65)"A"
ASC(s$)ASCII code of first characterASC("A")65
STRING$(n,s$)Repeat character n timesSTRING$(5,"*")"*****"
SPACE$(n)n spacesSPACE$(3)" "
HEX$(n)Hexadecimal representationHEX$(255)"FF"
OCT$(n)Octal representationOCT$(8)"10"
16Sound

8-bit Music and Sound

basicFusion has built-in simple sound generators, ideal for creating retro effects and 8-bit melodies (chiptune).

PLAY => Playing Notes

' PLAY "note", duration_ms, [wave_type], [volume]
PLAY "C4", 200
PLAY "E4", 200, 1, 0.5

Notes: A, B, C, D, E, F, G (plus sharps # and flats b, e.g. Bb4). The digit sets the octave (default 4). This command does not pause code execution!

Wave Types (Sound Shape)

ValueTypeSound character
0SineSmooth, flute or clean whistle
1SquareClassic 8-bit "Nintendo" sound, default, best for melodies
2SawtoothSharp, buzzing, strings or techno bass
3TriangleSoft, sub-bass, NES style

SOUND => Raw Frequency

' SOUND frequency_Hz, duration_ms, [wave_type], [volume]
SOUND 440, 500       ' 440 Hz (note A) for 0.5 seconds
SOUND 120, 1000, 2   ' Low buzz (Sawtooth) for 1 second

NOISE => Noise (Drums / Explosions)

' NOISE duration_ms, [volume]
NOISE 50, 0.4        ' Short click, hi-hat drum
NOISE 300, 0.8       ' Longer, louder noise, explosion

SLEEP => Blocking Pause

SLEEP ms pauses program execution for the given number of milliseconds. Use it to time notes when playing melodies.

DELAY => Non-blocking Audio Delay

DELAY ms advances the internal audio scheduler without pausing the program. Sounds play in the background, useful alongside animations.

PLAY "C4", 200
SLEEP 220            ' wait 220 ms (200 for note + 20 ms gap)
PLAY "D4", 200
SLEEP 220
PLAY "E4", 400
17Graphics

Graphics Mode => SCREEN

The SCREEN command sets the screen mode. SCREEN 0 returns to text mode (the 80×25 terminal); the other modes open the graphics canvas at a fixed resolution.

ModeResolutionUse case
SCREEN 0textText mode, back to the 80×25 terminal
SCREEN 1320 × 200Classic CGA, retro effects, pixel art
SCREEN 2640 × 200Horizontal text graphics
SCREEN 7320 × 200EGA compatible
SCREEN 9640 × 350EGA, more room
SCREEN 12640 × 480VGA, full resolution, recommended
SCREEN 12           ' 640x480, black background
PAINT "#001133"     ' set background color
CIRCLE (320,240), 100, "white"
END
💡
After calling SCREEN the canvas is black. Color it with PAINT "color" or BOX (0,0)-(640,480), "color". The CLS command clears both the terminal and the canvas.

GRAPHICS => Custom Resolution

Instead of the predefined SCREEN modes, you can use the GRAPHICS command to create a canvas of any exact size.

' GRAPHICS width, height
GRAPHICS 800, 600     ' Custom 800x600 resolution
PAINT "#112233"
END
18Graphics

Drawing Shapes

PSET => Pixel

PSET (100,150), "red"

POINT => Get Pixel Color

Pobiera kolor punktu z podanej pozycji płótna (canvas). Funkcja jest inteligentna i zwraca wynik w formacie zależnym od zmiennej, do której przypisujesz wartość:

' Pobranie jako tekst (Hex)
C$ = POINT(100, 150)
PRINT "Kolor HEX: "; C$

' Pobranie jako liczba (Integer)
C = POINT(100, 150)
PRINT "Wartosc liczbowa: "; C

LINE => Straight Line

LINE (10,10)-(300,200), "cyan"
💡
Flat form: the two-point shapes (LINE, BOX, RECT, GRADIENT) also accept all coordinates in one parenthesis, e.g. LINE (10, 10, 300, 200, "cyan"). Both styles are equivalent.

BOX / BOXFRAME => Rectangle

BOX (50,50)-(200,150), "blue"        ' filled
BOXFRAME (50,50)-(200,150), "white"    ' outline only

CIRCLE => Circle

CIRCLE (320,240), 80, "yellow"    ' center (320,240), radius 80

ELLIPSE => Ellipse

ELLIPSE (320,240), 120, 60, "lime", 0   ' outline
ELLIPSE (320,240), 120, 60, "lime", 1   ' filled

ARC => Arc

' ARC (cx,cy), radius, start_angle, end_angle, color
' Angles in radians: 0=right, PI/2=down, PI=left
ARC (200,200), 80, 0, 3.14, "orange"

TRI => Filled Triangle

TRI (160,50)-(50,200)-(270,200), "red"
💡
Flat form: TRI also accepts all six coordinates in one parenthesis: TRI (160, 50, 50, 200, 270, 200, "red").

LINEWIDTH => Line Thickness

LINEWIDTH 4
CIRCLE (200,200), 50, "white"
LINEWIDTH 1   ' restore default

Transforms => TRANSLATE / ROTATE / SCALE

These change the coordinate system for everything drawn afterwards, the whole canvas. Great for rotozoomers, spinning sprites, screen shake, and zoom effects. TRANSLATE moves the origin, ROTATE turns it (radians, so PI = 180°), SCALE zooms it (one value = both axes; negatives flip). Wrap a shape in PUSHMATRIX / POPMATRIX to isolate its transform, and use RESETTRANSFORM to go back to normal. Transforms stay active until you reset them (a good habit is to reset or push/pop every frame). Note: they affect vector/image/sprite drawing, but not direct POKEBUF pixel writes.

' spin a square around its own centre
PUSHMATRIX
  TRANSLATE 160, 100      ' move origin to centre
  ROTATE T               ' T grows each frame (radians)
  SCALE 2                ' twice as big
  BOX (-20, -20, 20, 20, "cyan")   ' centred on the origin
POPMATRIX          ' back to normal for the HUD, etc.

Glow & Fades => BLENDMODE / ALPHA / FADE

Three tiny commands unlock a lot of demoscene/intro flair. BLENDMODE chooses how new pixels combine with the screen: "add" brightens where shapes overlap (that classic neon glow), "screen" is a softer glow, "multiply" darkens, and "normal" restores. ALPHA a sets a global opacity (0-1) for everything you draw next. FADE amount lays a translucent black rectangle over the whole screen, do it a little every frame and moving shapes leave glowing trails (feedback); do FADE 1 to clear. All three stick until you change them (they reset when a new program starts).

' additive glow: two circles brighten where they overlap
BLENDMODE "add"
CIRCLE (140,100), 60, RGB(255,0,0)
CIRCLE (190,100), 60, RGB(0,0,255)   ' overlap -> bright magenta/white
BLENDMODE "normal"

' motion trails: fade the screen a bit each frame instead of CLS
DO
  FADE 0.08                 ' darken by 8% (leaves trails)
  PSET (160 + 100*SIN(t), 100 + 60*COS(t*1.7)), "cyan"
  t = t + 0.05
  SYNC
LOOP
💡
These affect vector/image/sprite drawing (they go through the canvas), but not direct POKEBUF pixel writes. Combine BLENDMODE "add" with many translucent shapes for cheap bloom.
19Graphics

Colors, Fills, and Gradients

Colors can be specified as CSS color names or hex values. The full set of CSS colors is supported.

white
black
red
lime
blue
yellow
magenta
cyan
orange
purple
hotpink
turquoise
orangered
dodgerblue
chartreuse
gold

Color Formats => String, Hex, CSS Functions

Wherever a color parameter is accepted (LINE, CIRCLE, BOX, TEXT, COLOR, PAINT, etc.) you can use any of these formats interchangeably:

' Named CSS color
CIRCLE (100,100), 40, "red"

' Hex color
CIRCLE (200,100), 40, "#ff6600"

' HSL, great for cycling through rainbow colors
FOR I = 0 TO 359
  HUE$ = "hsl(" + STR$(I) + ",100%,60%)"
  PSET (I,240), HUE$
NEXT I

' RGBA with transparency
BOX (50,50)-(200,150), "rgba(255,0,0,0.5)"

Integer Colors => QBasic Palette (0-15)

For compatibility with classic QBasic code, you can pass a color as an integer from 0 to 15. Each number maps to a fixed color from the original CGA/EGA palette. Any graphics command that accepts a color string also accepts these integers.

ValueColorValueColor
0black8gray
1blue9lightblue
2green10lightgreen
3cyan11lightcyan
4red12pink (light red)
5magenta13lightmagenta
6brown14yellow
7lightgray15white

These constants are also available by name: BLACK, BLUE, GREEN, CYAN, RED, MAGENTA, BROWN, LIGHTGRAY, GRAY, LIGHTBLUE, LIGHTGREEN, LIGHTCYAN, YELLOW, WHITE.

' Integer colors work anywhere a color string is expected
LINE (0,0)-(640,480), 4          ' red (4)
CIRCLE (320,240), 100, 14         ' yellow (14)

' Using named constants, same result
LINE (0,0)-(640,480), RED
CIRCLE (320,240), 100, YELLOW

' Cycling through palette colors (0..15)
FOR I = 0 TO 15
  BOX (I*40,50)-(I*40+38,100), I
NEXT I

PAINT => Background

PAINT "#0a0a1a"     ' fill entire canvas with color

FILL => Flood Fill

SCREEN 12
CIRCLE (200,200), 80, "white"
FILL (200,200), "cyan"
⚠️
FILL works pixel-by-pixel. For large areas it can be slow. Prefer BOX, filled ELLIPSE, or GRADIENT instead.

GRADIENT => Linear Gradient

GRADIENT (0,0)-(640,480), "#001133", "#330011"
GRADIENT (100,50)-(100,200), "red", "blue"       ' vertical gradient
19bGraphics

COLOR Command

The COLOR command sets the default foreground color for subsequent PRINT output in text mode, and optionally the background color of the terminal.

Syntax

' COLOR foreground [, background]
COLOR "lime"                  ' set text color only
COLOR "yellow", "#1a1a2e"    ' set text + background color
COLOR "white"                 ' reset to white

After calling COLOR, all following PRINT statements use that color until COLOR is called again. Any CSS color name or hex value is accepted.

Example => Colored Terminal Output

COLOR "cyan"
PRINT "=== MENU ==="
COLOR "white"
PRINT "1. New Game"
PRINT "2. Load Game"
COLOR "red"
PRINT "3. Quit"
COLOR "white"          ' always reset at the end
INPUT "Your choice: "; CH

Example => Error and Success Messages

INPUT "Enter PIN: "; PIN$
IF PIN$ = "1234" THEN
  COLOR "lime"
  PRINT "Access granted!"
ELSE
  COLOR "red"
  PRINT "Wrong PIN!"
END IF
COLOR "white"
💡
Good practice: Always reset to COLOR "white" (or your preferred default) after colored output so subsequent PRINT statements aren't accidentally colored.
⚠️
Note: COLOR affects only PRINT in text/terminal mode. To draw colored text on the graphics canvas use TEXT (x,y), "...", "color" instead.
20Graphics

Text on the Graphics Canvas

TEXT => Drawing Text

' TEXT (x,y), "text", "color"
SCREEN 12
TEXT (100,100), "Hello World!", "white"
TEXT (100,130), "Score: " + STR$(SCORE), "yellow"

FONTSIZE => Font Size

FONTSIZE 36
TEXT (160,200), "GAME OVER", "red"
FONTSIZE 16
TEXT (220,250), "Press space", "gray"

Font Handling (SETFONT)

By default, the TEXT command draws text using the Courier New font. However, you can change this to any font installed on your operating system using the SETFONT command (or its alias LOADFONT).

You control the text size separately using FONTSIZE. Both commands retain their settings until you change them again or restart the program.

Example usage:

SCREEN 12
CLS

' Setting a large Impact font
SETFONT "Impact"
FONTSIZE 40
TEXT (50, 60), "HEADER IN IMPACT"

' Changing the font to Comic Sans
SETFONT "Comic Sans MS"
FONTSIZE 20
TEXT (50, 120), "And this is standard text in Comic Sans :)", 14

' You can also use standard sans-serif fonts
SETFONT "Arial"
FONTSIZE 16
TEXT (50, 160), "Text written in Arial font.", 15
💡
The default font is bold 18px Courier New. FONTSIZE changes only the size. The Y position refers to the text baseline, not the top.
20bGraphics

Custom Bitmap Fonts

TEXT together with SETFONT draws using whatever fonts are installed on the operating system, great for clean labels, but it never looks truly retro. When you want full control over every pixel (chunky demoscene headers, a game logo, a console font that looks identical on every machine) you can design your own bitmap font and draw it with PRINTFONT.

A custom font is just a grid of pixels per character. You declare the cell size once, paint each character row by row using "1" (solid) and "0" (empty), pick a color, then print any string with it.

DEFFONT => Create a Font

Reserves a custom font in memory under an id, with every character being w pixels wide and h pixels tall. An optional 4th argument names the font: DEFFONT id, w, h, "name" (the Font Editor fills this in).

' DEFFONT id, width, height [, "name"]
DEFFONT 0, 5, 5

FONTDATA => Paint a Character

Fills in the pixels for a single character. charCode is the ASCII code of the character, use ASC("A") for readability, or pass the number directly. After it come h row strings, each exactly w characters long, where 1 is a solid pixel and 0 is empty.

' FONTDATA id, charCode, "row0", "row1", ...   (h rows of w chars)
' Letter H
FONTDATA 0, ASC("H"), "10001", "10001", "11111", "10001", "10001"
' Letter I
FONTDATA 0, ASC("I"), "11111", "00100", "00100", "00100", "11111"
' Exclamation mark
FONTDATA 0, ASC("!"), "00100", "00100", "00100", "00000", "00100"

FONTCOLOR => Set the Color

Sets the color used by every following PRINTFONT call. Accepts CSS color names, hex strings, or the 0-15 QBasic color numbers, exactly like the other drawing commands.

FONTCOLOR "lime"
FONTCOLOR "#FF004D"
FONTCOLOR 14   ' yellow

PRINTFONT => Draw the Text

Draws a string at (X, Y) using the bitmap font with the given id. Two optional parameters let you blow it up and space it out: scale multiplies every pixel (default 1), and spacing adds extra empty pixels between characters.

' PRINTFONT id, X, Y, "text" [, scale [, spacing]]
PRINTFONT 0, 40, 40, "HI!"
PRINTFONT 0, 40, 100, "HI!", 6, 2   ' 6x size, +2px spacing

Full Example

Defines a tiny 5×5 font, then prints the same word small and huge:

SCREEN 12
CLS

' --- Design a 5x5 bitmap font (id 0) ---
DEFFONT 0, 5, 5
FONTDATA 0, ASC("H"), "10001", "10001", "11111", "10001", "10001"
FONTDATA 0, ASC("I"), "11111", "00100", "00100", "00100", "11111"
FONTDATA 0, ASC("!"), "00100", "00100", "00100", "00000", "00100"

' --- Draw it ---
FONTCOLOR "lime"
PRINTFONT 0, 40, 40, "HI!"

FONTCOLOR "#FF004D"
PRINTFONT 0, 40, 100, "HI!", 8, 2
💡
Every row string in FONTDATA must match the font's width, and you need exactly as many rows as the height you declared in DEFFONT. A 5×5 font means five strings of five characters each. Only the codes you define get drawn, any undefined character in a PRINTFONT string simply renders blank.
🎨
You can keep several fonts loaded at once under different ids (e.g. DEFFONT 0 for the title font, DEFFONT 1 for the HUD) and switch between them just by changing the id you pass to PRINTFONT. Design them quickly in the IDE's sprite/font tooling instead of typing rows by hand.
21bGraphics

Direct Pixel Buffer (Demoscene Speed)

For extreme performance (e.g., when writing custom 3D engines, demoscene effects like plasma or fire), bypass standard drawing commands. Use FASTGRAPHICS and manipulate screen memory directly with POKEBUF and MEMSET.

To extract maximum speed, pass colors to the buffer as packed integers using the RGB(r, g, b) function.

' Hypnotic demoscene circles
FASTGRAPHICS : GRAPHICS 320, 240

t = 0
DO
  FOR y = 0 TO 239
    FOR x = 0 TO 319
      ' Magic math for the pattern
      c = CINT((x * x + y * y) / 128 + t) MOD 256
      
      ' POKEBUF writes directly to memory
      POKEBUF x, y, RGB(c, 50, 255 - c)
    NEXT x
  NEXT y
  
  SYNC
  t = t + 5
LOOP
21Graphics

Animation and SYNC

SYNC pauses execution until the next browser render frame (requestAnimationFrame), guaranteeing smooth animations at ~60 FPS.

FASTGRAPHICS => Double Buffering

The FASTGRAPHICS command turns on double-buffering. All drawing commands are performed in background memory rather than directly on the screen, which completely eliminates flickering during complex frame rendering.

⚠️
Crucial: When FASTGRAPHICS is enabled, the screen will NOT update automatically. You MUST use the SYNC command to swap the buffer to the screen. If you forget SYNC, your canvas will remain completely blank!
💡
HINT: FASTGRAPHICS can also be used in text mode for smooth screen double buffering. To swap the buffer to the screen, you can use either the SYNC or sleep command, although SYNC is the better solution.
GRAPHICS 640, 480
FASTGRAPHICS         ' Enable drawing in the background

LOOP:
  CLS
  CIRCLE (MOUSEX, MOUSEY), 20, "lime"
  SYNC               ' REQUIRED to actually display the frame!
GOTO LOOP

Basic Game Loop

SCREEN 12
X = 0

LOOP:
  CLS
  CIRCLE (X,240), 20, "yellow"
  X = X + 3
  IF X > 640 THEN X = 0
  SYNC
GOTO LOOP

Bouncing Ball

SCREEN 12
PX = 320 : PY = 240
VX = 4   : VY = 3

BOUNCE:
  PAINT "black"
  PX = PX + VX
  PY = PY + VY
  IF PX >= 620 OR PX <= 20 THEN VX = -VX
  IF PY >= 460 OR PY <= 20 THEN VY = -VY
  CIRCLE (PX,PY), 20, "orange"
  SYNC
GOTO BOUNCE
22Graphics

Keyboard Input => KEY()

KEY("name") returns 1 when a key is currently held down, or 0 if not. Works in real time inside the animation loop.

KeyName in KEY()
Left arrowKEY("ARROWLEFT")
Right arrowKEY("ARROWRIGHT")
Arrow upKEY("ARROWUP")
Arrow downKEY("ARROWDOWN")
SpaceKEY("SPACE") or KEY(" ")
EnterKEY("ENTER")
Letters A-ZKEY("A"), KEY("W"), KEY("S"), KEY("D")
Digits 0-9KEY("0") ... KEY("9")
EscapeKEY("ESCAPE")

Example: Player Movement

SCREEN 12
PX = 320 : PY = 240

GAMELOOP:
  PAINT "#0a0a1a"
  IF KEY("ARROWLEFT")  THEN PX = PX - 5
  IF KEY("ARROWRIGHT") THEN PX = PX + 5
  IF KEY("ARROWUP")    THEN PY = PY - 5
  IF KEY("ARROWDOWN")  THEN PY = PY + 5
  IF PX < 15 THEN PX = 15
  IF PX > 625 THEN PX = 625
  IF PY < 15 THEN PY = 15
  IF PY > 465 THEN PY = 465
  BOX (PX-15,PY-15)-(PX+15,PY+15), "lime"
  SYNC
GOTO GAMELOOP
22cGraphics

Keyboard Input => INKEY$

INKEY$ reads a single character from the keyboard buffer and returns it as a string. If no key has been typed, it returns "" (empty string). Unlike INPUT, it does not pause the program, the loop keeps running while waiting.

KEY() vs INKEY$

FeatureKEY("name")INKEY$
Blocking?NoNo
Detects held keys?Yes => returns 1 while heldNo => fires once per press
Returns value?0 or 1Character string or ""
Best forGame movement, held-down actionsMenus, text input, single-press confirm

Basic Usage => Reading a Single Keypress

WaitKey:
  k$ = INKEY$
  IF k$ = "" THEN GOTO WaitKey   ' loop until a key is pressed
PRINT "You pressed: "; k$

Example: Yes / No Confirmation

A classic pattern in text-mode programs, wait for a specific key without blocking.

PRINT "Delete record? [Y / N]"

ConfirmLoop:
  c$ = UCASE$(INKEY$)
  IF c$ = ""   THEN GOTO ConfirmLoop
  IF c$ = "N"  THEN GOTO Cancelled
  IF c$ <> "Y" THEN GOTO ConfirmLoop  ' ignore other keys

PRINT "Deleted!" : GOTO Done

Cancelled:
PRINT "Cancelled."

Done:
END

Example: Build a String Character by Character

typed$ = ""
PRINT "Type something (Enter to finish):"

TypeLoop:
  ch$ = INKEY$
  IF ch$ = "" THEN GOTO TypeLoop
  IF ASC(ch$) = 13 THEN GOTO Done    ' Enter = ASCII 13
  typed$ = typed$ + ch$
  PRINT ch$;                           ' echo character
GOTO TypeLoop

Done:
PRINT
PRINT "You typed: "; typed$
💡
Flushing the buffer: Before opening a menu that waits on INKEY$, clear any leftover keys with a flush loop:

SLEEP 80
FlushLoop: IF INKEY$ <> "" THEN GOTO FlushLoop
⚠️
Case sensitivity: INKEY$ returns the character as typed, lowercase comes back lowercase. Wrap it in UCASE$() to handle both Y and y with one check.
22bGraphics

Mouse Input => MOUSEX, MOUSEY, MOUSECLICK, HIDEMOUSE, SHOWMOUSE

basicFusion allows you to read the mouse position and button clicks on the graphics canvas in real time.

FunctionDescription
MOUSEXReturns the current X coordinate of the mouse pointer relative to the canvas.
MOUSEYReturns the current Y coordinate of the mouse pointer relative to the canvas.
MOUSECLICKReturns 1 if the left button is clicked, 2 for the right button, and 0 if no button is pressed.
HIDEMOUSEHides the mouse cursor.
SHOWMOUSE [sprite_id_num]Displays the default mouse cursor, or a custom one if a sprite ID is provided.

Example: Simple Drawing App

SCREEN 12
PAINT "#111111"
FONTSIZE 18

DRAW_LOOP:
  X = MOUSEX
  Y = MOUSEY
  BTN = MOUSECLICK

  IF BTN = 1 THEN
    CIRCLE (X,Y), 5, "lime"
  ELSE IF BTN = 2 THEN
    CIRCLE (X,Y), 5, "red"
  END IF

  ' UI Overlay
  BOX (0,0)-(200,30), "black"
  TEXT (10,20), "X:" + STR$(X) + " Y:" + STR$(Y), "white"

  SYNC
GOTO DRAW_LOOP
23Graphics

Sprites

basicFusion supports pixel-art sprites defined directly in code using color grids. Sprites can have multiple animation frames and be drawn at any position, scale, angle, or stretched to a target size.

DEFSPRITE => Define a Sprite

' DEFSPRITE id, width, height [, "name"]
DEFSPRITE 1, 4, 4   ' sprite ID 1, 4x4 pixels (optional name as 4th arg)

SPRITEDATA => Set a Pixel Row

Defines one row of pixels for a sprite frame. Colors are comma-separated CSS color strings. An empty string means transparent.

' SPRITEDATA id, frame, row, "col,col,col,..."
DEFSPRITE 1, 4, 4
SPRITEDATA 1, 0, 0, ",red,red,"
SPRITEDATA 1, 0, 1, "red,red,red,red"
SPRITEDATA 1, 0, 2, "red,red,red,red"
SPRITEDATA 1, 0, 3, ",red,red,"
⚠️
SPRITEROW is the old name for this command. It is deprecated, it still works and you can keep using it, but SPRITEDATA is the recommended form. The syntax is identical.
💡
Transparency: An empty entry in the row string leaves that pixel transparent. Example: ",red,," draws only the second pixel red.

DRAWSPRITE => Draw a Sprite

' DRAWSPRITE id, x, y [, frame [, scale [, angle_deg]]]
DRAWSPRITE 1, 100, 100                   ' frame 0, scale 1
DRAWSPRITE 1, 200, 100, 0, 8             ' scale x8
DRAWSPRITE 1, 300, 100, 0, 4, 45          ' rotated 45°

DRAWSPRITESTRETCHED => Draw Stretched to Size

' DRAWSPRITESTRETCHED id, x, y, dest_width, dest_height [, frame]
DRAWSPRITESTRETCHED 1, 50, 50, 64, 64
DRAWSPRITESTRETCHED 1, 200, 50, 128, 32

DRAWSPRITETRI => Texture-mapped Triangle

' DRAWSPRITETRI id, x1,y1,u1,v1, x2,y2,u2,v2, x3,y3,u3,v3 [, frame]
DRAWSPRITETRI 1, 160,50, 0,0, 320,50, 4,0, 240,200, 2,4

Full Example => Animated Sprite

SCREEN 12
DEFSPRITE 1, 4, 4
' Frame 0
SPRITEROW 1,0,0, ",lime,lime,"
SPRITEROW 1,0,1, "lime,lime,lime,lime"
SPRITEROW 1,0,2, "lime,,lime,lime"
SPRITEROW 1,0,3, ",lime,,lime"
' Frame 1
SPRITEROW 1,1,0, ",cyan,cyan,"
SPRITEROW 1,1,1, "cyan,cyan,cyan,cyan"
SPRITEROW 1,1,2, "cyan,,cyan,cyan"
SPRITEROW 1,1,3, ",cyan,,cyan"

F = 0 : TICK = 0

LOOP:
  PAINT "#0a0a1a"
  TICK = TICK + 1
  IF TICK MOD 15 = 0 THEN F = 1 - F
  DRAWSPRITE 1, 300, 220, F, 10
  SYNC
GOTO LOOP
23bGraphics

Sprite Collision => SPRITECOL()

Tests whether the bounding boxes of two sprites overlap. Returns -1 (True) if they collide, or 0 (False) if they don't.

Syntax

' SPRITECOL(id1, x1, y1, id2, x2, y2)
'   id1, id2, sprite IDs (defined with DEFSPRITE)
'   x1, y1  , current draw position of sprite 1
'   x2, y2  , current draw position of sprite 2
'   Returns -1 (True) if bounding boxes overlap, 0 otherwise

IF SPRITECOL(1, PX, PY, 2, EX, EY) THEN
  PRINT "Collision!"
END IF
💡
Bounding box means the full rectangular area of the sprite (width × height as defined in DEFSPRITE), placed at the given (x, y) position. Collision is detected if these rectangles overlap, this is the fastest collision method and sufficient for most games.

Full Example => Player vs Enemy

SCREEN 12

' Player sprite (ID 1), 4x4
DEFSPRITE 1, 4, 4
SPRITEROW 1,0,0, ",lime,lime,"
SPRITEROW 1,0,1, "lime,lime,lime,lime"
SPRITEROW 1,0,2, "lime,lime,lime,lime"
SPRITEROW 1,0,3, ",lime,lime,"

' Enemy sprite (ID 2), 4x4
DEFSPRITE 2, 4, 4
SPRITEROW 2,0,0, ",red,red,"
SPRITEROW 2,0,1, "red,red,red,red"
SPRITEROW 2,0,2, "red,red,red,red"
SPRITEROW 2,0,3, ",red,red,"

PX = 100 : PY = 220   ' player position
EX = 400 : EY = 220   ' enemy position
EVX = -2               ' enemy moves left
HIT = 0

GL:
  PAINT "#0a0a1a"

  ' Move player
  IF KEY("ARROWLEFT")  THEN PX = PX - 4
  IF KEY("ARROWRIGHT") THEN PX = PX + 4
  IF KEY("ARROWUP")    THEN PY = PY - 4
  IF KEY("ARROWDOWN")  THEN PY = PY + 4

  ' Move enemy
  EX = EX + EVX
  IF EX < 0 THEN EX = 640

  ' Draw sprites (scale 8)
  DRAWSPRITE 1, PX, PY, 0, 8
  DRAWSPRITE 2, EX, EY, 0, 8

  ' Collision check
  IF SPRITECOL(1, PX, PY, 2, EX, EY) THEN
    FONTSIZE 28
    TEXT (220,50), "HIT!", "red"
  END IF

  FONTSIZE 16
  TEXT (10,22), "Arrows = move | avoid the red enemy", "gray"
  SYNC
GOTO GL
⚠️
Scale matters: SPRITECOL uses the sprite's base pixel dimensions (from DEFSPRITE) multiplied by the scale you pass to DRAWSPRITE. Make sure the x, y positions passed to SPRITECOL match exactly the positions you passed to DRAWSPRITE for accurate results.
23dGraphics

Tilemaps => DRAWTILEMAP & GETTILE

A tilemap is just a regular 2D array you draw as a grid of tiles. Each cell holds a frame index of a tileset sprite. This is exactly the array the built-in Map Editor produces, so you design a level visually, export it as a DATA_LEVEL_n block, load it with RESTORE/READ, and draw the whole thing with one DRAWTILEMAP. GETTILE reads the tile at a world position, which is all you need for map collision.

DRAWTILEMAP => Draw a Tile Grid

' DRAWTILEMAP map(), tilesetId, tileW [, tileH]
'   map()     , a 2D array (Y, X); each cell = tileset frame index
'   tilesetId , a sprite (DEFSPRITE) whose frames are your tiles
'   tileW/tileH, size each tile is drawn at (sprite is scaled to fit)
DRAWTILEMAP Map(), 1, 32, 32

GETTILE => Read a Tile (Collision)

' GETTILE(map(), worldX, worldY, tileW [, tileH]) -> frame index, or -1 outside the map
IF GETTILE(Map(), PX, PY, 32) > 0 THEN
  ' a solid tile is here -> block the move
END IF
🗺️
Made for the Map Editor. When you export a map, the editor writes a block like this into your code:
DATA_LEVEL_1:
DATA 1, 15, 10, 1      ' levelId, width, height, layer
DATA 0, 0, 0, ...    ' one DATA line per row
Load it once with RESTORE DATA_LEVEL_1 + a READ loop into a DIM array, then draw it with DRAWTILEMAP. You can re-open the block in the editor and re-export any time, your loader and draw code never change.
💡
The array is indexed (Y, X), row first, then column, matching the editor's row-by-row DATA output. Pass it to the commands as Map() or just Map; both hand over the whole array.
⚠️
0 means empty. The Map Editor leaves blank cells as 0, so make frame 0 of your tileset transparent and start real tiles at frame 1. Then test solidity with GETTILE(...) > 0. (For maps you build by hand instead, you can use -1 for empty, DRAWTILEMAP skips it entirely.)

Full Example => Load a Level from the Map Editor

The level lives in the DATA_LEVEL_1 block at the bottom, the exact format the Map Editor exports. The loader reads the header (levelId, width, height, layer), fills a DIM array, and the game loop just draws it and uses GETTILE for gravity and the wall. Edit the DATA visually in the editor and the rest still works untouched.

SCREEN 12 : FASTGRAPHICS
TS = 32

' Tileset (sprite 1): frame 0 = empty, 1 = grass, 2 = brick.
' The map editor uses 0 for empty cells, so frame 0 stays transparent.
DEFSPRITE 1, 8, 8
FOR R = 0 TO 7
    SPRITEROW 1, 0, R, ",,,,,,,"
    SPRITEROW 1, 1, R, "green,limegreen,green,green,green,limegreen,green,green"
    SPRITEROW 1, 2, R, "firebrick,red,firebrick,red,firebrick,red,firebrick,red"
NEXT R

' Player (sprite 2)
DEFSPRITE 2, 8, 8
FOR R = 0 TO 7
    SPRITEROW 2, 0, R, "yellow,gold,yellow,yellow,yellow,gold,yellow,yellow"
NEXT R

' --- Load the level straight from the map editor's DATA block ---
RESTORE DATA_LEVEL_1
READ LevelID : READ MapW : READ MapH : READ LayerNo
DIM Map(MapH - 1, MapW - 1)
FOR Y = 0 TO MapH - 1
    FOR X = 0 TO MapW - 1
        READ Tile
        Map(Y, X) = Tile
    NEXT X
NEXT Y

PX = 64 : PY = 100

DO
    PAINT "#101820"

    DX = 0
    IF KEY("ARROWLEFT")  THEN DX = -3
    IF KEY("ARROWRIGHT") THEN DX =  3
    ' move only if the target cell is not solid (0 = empty, -1 = off map)
    IF GETTILE(Map(), PX + DX + 16, PY + 16, TS) <= 0 THEN PX = PX + DX

    ' gravity + land on solid tiles
    PY = PY + 4
    IF GETTILE(Map(), PX + 16, PY + TS, TS) > 0 THEN PY = INT((PY + TS) / TS) * TS - TS

    DRAWTILEMAP Map(), 1, TS, TS
    DRAWSPRITE 2, PX, PY, 0, 4

    SYNC(60)
    IF KEY("ESCAPE") THEN END
LOOP

' ============================================================
'  This block is produced by the Map Editor. You can re-open it
'  there, edit visually, and re-export, the loader above stays
'  the same. Header = levelId, width, height, layer.
' ============================================================
DATA_LEVEL_1:
DATA 1, 15, 10, 1
DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
DATA 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0
DATA 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0
DATA 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
23eGraphics

Camera => CAMERA, CAMERAZOOM & CAMERASHAKE

The camera sets a global world-to-screen offset. After CAMERA, everything you draw, sprites, images, shapes, text, tilemaps, is in world-space, so you never subtract the scroll position by hand again. CAMERA 0, 0 snaps back to screen-space for your HUD and background.

CAMERA => Set the World Offset

' CAMERA [x, y]  , shifts the whole world by (-x, -y)
CAMERA CamX, 0      ' world scrolls; draw at world coords
CAMERA 0, 0         ' reset, screen-space again (HUD, background)

CAMERAZOOM & CAMERASHAKE

' CAMERAZOOM scale  , 1 = normal, 2 = 2x zoom (anchored at world origin)
CAMERAZOOM 2
CAMERAZOOM 1           ' back to normal

' CAMERASHAKE amount, adds shake "trauma" (0..1) that fades on its own
CAMERASHAKE 0.6       ' call once on an explosion / hit
💡
The frame recipe: clear the screen at CAMERA 0, 0 → set CAMERA CamX, CamY → draw the world (tilemap, sprites) → CAMERA 0, 0 again → draw the HUD. PAINT and CLS always clear the full screen, so they're safe either way.
⚠️
Shake fades automatically over about half a second, so just call CAMERASHAKE once on impact, don't call it every frame or it never settles. It works wherever the camera is (even at 0, 0), and the whole screen, HUD included, jitters briefly. The bigger the trauma, the bigger the shake (it grows with the square of the value, so 0.6 is much stronger than 0.3).

Full Example => Side-scrolling Camera

The camera follows the player and is clamped to the level edges. The world tiles scroll; the "world X" label is drawn at CAMERA 0, 0 so it stays fixed on screen.

SCREEN 12 : FASTGRAPHICS
TS = 32
MAPW = 60
DEFSPRITE 1, 8, 8
FOR R = 0 TO 7
    SPRITEROW 1, 0, R, "steelblue,lightblue,steelblue,steelblue,steelblue,lightblue,steelblue,steelblue"
NEXT R
DEFSPRITE 2, 8, 8
FOR R = 0 TO 7
    SPRITEROW 2, 0, R, "orange,gold,orange,orange,orange,gold,orange,orange"
NEXT R
DIM Map(9, MAPW)
FOR Y = 0 TO 9
    FOR X = 0 TO MAPW
        Map(Y, X) = -1
    NEXT X
NEXT Y
FOR X = 0 TO MAPW
    Map(9, X) = 0
NEXT X
PX = 64
DO
    PAINT "#0a0a1a"
    IF KEY("ARROWLEFT")  THEN PX = PX - 4
    IF KEY("ARROWRIGHT") THEN PX = PX + 4
    CamX = PX - 320
    IF CamX < 0 THEN CamX = 0
    MaxCam = (MAPW + 1) * TS - 640
    IF CamX > MaxCam THEN CamX = MaxCam
    CAMERA CamX, 0
    DRAWTILEMAP Map(), 1, TS, TS
    DRAWSPRITE 2, PX, 240, 0, 4
    CAMERA 0, 0
    TEXT (10, 14), "world X = " + STR$(INT(PX)), "white"
    SYNC(60)
    IF KEY("ESCAPE") THEN END
LOOP
⚠️
Heads up, this map is generated in code. The flat floor above is built with a couple of FOR loops just to keep the camera demo short. That's fine for a quick test, but for a real game you shouldn't hand-build levels like this. Design them visually in the Map Editor, export a DATA_LEVEL_n block, and load it with RESTORE/READ (see the Tilemaps section). The loader is always the same:
' --- Standard way: load a level designed in the Map Editor ---
RESTORE DATA_LEVEL_1
READ LevelID : READ MapW : READ MapH : READ LayerNo   ' header line
DIM Map(MapH - 1, MapW - 1)
FOR Y = 0 TO MapH - 1
    FOR X = 0 TO MapW - 1
        READ Tile
        Map(Y, X) = Tile
    NEXT X
NEXT Y

' ...then in your game loop just draw it:
'   CAMERA CamX, 0
'   DRAWTILEMAP Map(), 1, TS, TS
23fData

Saving Data => SAVEDATA & LOADDATA$

These two commands give you persistent storage that survives closing and reopening the program, perfect for high scores, settings, or save slots. The same code works in the browser, the desktop app, and the Android APK.

SAVEDATA & LOADDATA$

' SAVEDATA key$, value  , stores a number or string under a key
SAVEDATA "hiscore", 1500
SAVEDATA "player", "TERROR"

' LOADDATA$(key$) , returns the stored STRING, or "" if nothing was saved
Name$ = LOADDATA$("player")
' for numbers, wrap it in VAL():
Best = VAL(LOADDATA$("hiscore"))
💡
LOADDATA$ always returns a string (that's why it ends with $). On the very first run nothing is saved yet, so it returns "", and VAL("") is 0, which makes a perfect starting high score.
⚠️
Android note: data is stored in the WebView's local storage. It persists normally, but can be wiped if the user clears the app's data. For high scores and settings that's fine; for anything critical, keep a backup.

Full Example => Persistent High Score

Loads the best score on start, counts up while you hold SPACE, and saves a new record the moment you beat it. Close the program and run it again, your best is still there.

SCREEN 12
HiScore = VAL(LOADDATA$("demo_best"))
Score = 0
DO
    PAINT "#101030"
    IF KEY("SPACE") THEN Score = Score + 1
    IF Score > HiScore THEN
        HiScore = Score
        SAVEDATA "demo_best", HiScore
    END IF
    FONTSIZE 24
    TEXT (40, 80),  "Score: " + STR$(Score), "yellow"
    TEXT (40, 130), "Best:  " + STR$(INT(HiScore)), "lime"
    FONTSIZE 16
    TEXT (40, 200), "Hold SPACE to score. Best is saved across runs.", "gray"
    SYNC(30)
    IF KEY("ESCAPE") THEN END
LOOP
23cGraphics

Images => LOADIMAGE, PUTIMAGE, IMGSAVE & more

basicFusion lets you load external images (PNG, JPG, GIF, etc.) onto the canvas, transform them (stretch, rotate, mirror, slice spritesheets, map onto 3D quads), read individual pixels, and save the canvas as a PNG file. These commands require a running instance of the standalone basicFusion app, see the compatibility note below.

LOADIMAGE => Load an Image from URL or File Path

LOADIMAGE is a function, it takes a URL or local file path as a string and returns an image object. Assign the result to a variable and then draw it with PUTIMAGE.

' IMG = LOADIMAGE("url_or_path")
IMG = LOADIMAGE("https://example.com/sprite.png")

' In standalone version, local file path
IMG = LOADIMAGE("images/background.png")

PUTIMAGE => Draw an Image on the Canvas

PUTIMAGE (x,y), imageVar draws the loaded image at the given top-left coordinates.

SCREEN 12
BG = LOADIMAGE("background.png")
HERO = LOADIMAGE("hero.png")

GAMELOOP:
  PUTIMAGE (0,0), BG          ' draw background
  PUTIMAGE (PX,PY), HERO     ' draw player at (PX, PY)
  SYNC
GOTO GAMELOOP

IMAGEALPHA => Set Image Opacity

IMAGEALPHA imgVar, alpha sets how transparent an image is, from 0.0 (invisible) to 1.0 (fully opaque). The value sticks to the image, so every PUTIMAGE (and the stretched / rotated / mirrored / part variants) draws it with that opacity until you change it again.

GHOST = LOADIMAGE("ghost.png")
IMAGEALPHA GHOST, 0.5      ' half transparent

LOOP:
  CLS
  PUTIMAGE (X,Y), GHOST
  SYNC
GOTO LOOP

Set the alpha back to 1 to make the image solid again: IMAGEALPHA GHOST, 1. Stepping the value each frame fades an image in or out.

IMAGETINT => Color an Image

IMAGETINT imgVar, COLOR multiplies an image by a color, so a white sprite takes on that color and a colored one is shaded toward it. COLOR works like everywhere else, a named color, a variable, or RGB(r,g,b).

HERO = LOADIMAGE("hero.png")

IMAGETINT HERO, RED            ' damage flash
IMAGETINT HERO, RGB(80,80,255)   ' frozen / blue
IMAGETINT HERO, -1             ' clear tint, back to normal

The tint is baked once when you call IMAGETINT, not every frame, so drawing a tinted image stays as fast as a normal one. Pass -1 as the color to remove the tint and draw the original pixels again.

SETTRANSPARENT => Color Key (make a color transparent)

SETTRANSPARENT imgVar, COLOR erases one color from an image, every pixel matching COLOR becomes fully transparent. This is the classic trick for images saved without an alpha channel: paint the background a flag color (often magenta) and key it out after loading. The optional third argument is a tolerance (0-255) that also removes near-matching shades, which helps with anti-aliased edges.

HERO = LOADIMAGE("hero.png")
SETTRANSPARENT HERO, RGB(255,0,255)      ' magenta -> transparent
SETTRANSPARENT HERO, "black", 16       ' black + close shades
PUTIMAGE (100, 80), HERO
💡
It edits the image's pixels in place (once), so later PUTIMAGE calls are just as fast. The parenthesized form works too: SETTRANSPARENT(HERO, RGB(255,0,255)).

IMGSAVE => Save the Canvas as a PNG

IMGSAVE saves the current graphics canvas as a PNG file. If you omit the filename, it defaults to canvas.png.

' IMGSAVE [filename]
IMGSAVE "screenshot.png"   ' save with a name
IMGSAVE                      ' save as canvas.png

IMAGEWIDTH / IMAGEHEIGHT => Measure an Image

IMAGEWIDTH imgVar and IMAGEHEIGHT imgVar return the width and height of a loaded image object in pixels. Use them to center, scale, or tile images without hard-coding sizes.

IMG = LOADIMAGE("hero.png")
W = IMAGEWIDTH IMG
H = IMAGEHEIGHT IMG

' center the image on a 640x480 screen
PUTIMAGE (320 - W / 2, 240 - H / 2), IMG

PUTIMAGESTRETCHED => Draw a Scaled Image

PUTIMAGESTRETCHED (x,y), W, H, imgVar draws a loaded image scaled to the width W and height H you specify, starting at the top-left corner (x,y).

IMG = LOADIMAGE("logo.png")

' draw it at double size in the corner
PUTIMAGESTRETCHED (10,10), 256, 256, IMG

' stretch to fill the whole screen
PUTIMAGESTRETCHED (0,0), 640, 480, IMG

PUTIMAGEROTATED => Draw a Rotated Image

PUTIMAGEROTATED (x,y), W, H, [angle,] imgVar draws a scaled image rotated by angle degrees around its center point. Increase the angle each frame to spin the image.

SCREEN 12
WHEEL = LOADIMAGE("wheel.png")
A = 0

SPIN:
  PAINT "black"
  ' 128x128, rotated by A degrees around its center
  PUTIMAGEROTATED (320,240), 128, 128, A, WHEEL
  A = A + 3
  SYNC
GOTO SPIN

PUTIMAGEMIRRORED => Draw a Flipped Image

PUTIMAGEMIRRORED (x,y), flipH, flipV, imgVar draws an image, mirrored horizontally when flipH is 1 and/or vertically when flipV is 1. Use 0 for no flip. This is perfect for facing a sprite left or right.

HERO = LOADIMAGE("hero.png")

' flipH, flipV
PUTIMAGEMIRRORED (100,100), 0, 0, HERO   ' normal
PUTIMAGEMIRRORED (200,100), 1, 0, HERO   ' face the other way
PUTIMAGEMIRRORED (300,100), 0, 1, HERO   ' upside down

PUTIMAGEPART => Draw a Cutout from a Spritesheet

PUTIMAGEPART (x,y), srcX, srcY, W, H, imgVar draws only a rectangular region of an image, taken from (srcX, srcY) with size W×H. This is how you pull a single frame out of a spritesheet for animation.

SHEET = LOADIMAGE("explosion_sheet.png")
' each frame is 64x64, laid out in a row
FRAME = 2

' draw frame number FRAME at screen (100,100)
PUTIMAGEPART (100,100), FRAME * 64, 0, 64, 64, SHEET

EXTRACTIMAGE => Cut a Region into a New Image

EXTRACTIMAGE (imgVar, x, y, W, H) is a function, it cuts a rectangular region out of an existing image and returns it as a brand-new, independent image object. Unlike PUTIMAGEPART (which only draws), this gives you a reusable image you can draw, transform, or save on its own.

SHEET = LOADIMAGE("tiles.png")

' grab the 32x32 tile at column 3, row 1
GRASS = EXTRACTIMAGE(SHEET, 96, 32, 32, 32)

' now reuse it like any other image
PUTIMAGE (0,0), GRASS
PUTIMAGESTRETCHED (64,0), 128, 128, GRASS

IMAGEFLIPH / IMAGEFLIPV => Lossless Flipped Copies

IMAGEFLIPH(imgVar) and IMAGEFLIPV(imgVar) are functions that return a brand-new image flipped horizontally or vertically. The flip is a pure pixel mirror, so there is no quality loss whatsoever, ideal for turning a right-facing sprite into a left-facing one without drawing or loading a second image.

HERO = LOADIMAGE("hero_right.png")

' build the mirrored copies ONCE, before the game loop
HEROLEFT = IMAGEFLIPH(HERO)
HEROFLIP = IMAGEFLIPV(HERO)

' draw whichever way the player faces
IF DIR = -1 THEN PUTIMAGE (X,Y), HEROLEFT ELSE PUTIMAGE (X,Y), HERO
💡
Flip once, not per frame: the returned image is fully independent (its own canvas), so build it outside the loop and reuse it. The copy starts fresh, it does not carry over IMAGEALPHA or IMAGETINT from the source.

IMAGEPIXEL => Read a Pixel from Image Memory

IMAGEPIXEL (imgVar, x, y) is a function that reads the color of a pixel directly from an image's memory and returns it as an integer RGB value. It is much faster than reading from the canvas, which makes it ideal for collision masks and per-pixel tests.

MASK = LOADIMAGE("level_mask.png")

' read the pixel under the player
C = IMAGEPIXEL(MASK, PX, PY)

' black pixel = wall => block movement
IF C = 0 THEN PRINT "Hit a wall!"

PUTIMAGEQUAD => Map onto a 4-Point Polygon

PUTIMAGEQUAD (x1,y1)-(x2,y2)-(x3,y3)-(x4,y4), img warps the image so its corners land on four arbitrary points. Give the points in order (top-left, top-right, bottom-right, bottom-left) to create Mode 7 / pseudo-3D floor and skew effects.

FLOOR = LOADIMAGE("floor.png")

' a trapezoid: narrow at the top, wide at the bottom
PUTIMAGEQUAD (260,200)-(380,200)-(600,460)-(40,460), FLOOR

PUTIMAGEQUAD3D => Perspective-Correct 3D Quad

PUTIMAGEQUAD3D X1,Y1,Z1, X2,Y2,Z2, X3,Y3,Z3, X4,Y4,Z4, img maps an image onto a quad defined by four 3D points, applying perspective correction. Use it for textured 3D surfaces where the flat PUTIMAGEQUAD would look distorted.

TEX = LOADIMAGE("brick.png")

' X,Y,Z for each of the 4 corners (top-L, top-R, bottom-R, bottom-L)
PUTIMAGEQUAD3D -1,1,2, 1,1,2, 1,-1,4, -1,-1,4, TEX
⚠️
Browser compatibility: LOADIMAGE with local file paths and IMGSAVE work reliably only in the standalone basicFusion desktop application. In a browser, loading local images may be blocked by the browser's security policy (CORS). Remote URLs (https://...) may also be restricted depending on the server's CORS headers. If an image fails to load in the browser, run your program from the standalone app instead.
💡
Load once, draw many times: Call LOADIMAGE before the game loop, not inside it. Loading an image on every frame is very slow, store the result in a variable and reuse it.
23gGraphics

3D Rendering

basicFusion features a built-in software 3D renderer that supports loading Wavefront .obj models, UV texture mapping, and automatic Z-Buffer depth sorting.

LOAD3DOBJECT & DRAW3DOBJECT => Solid Color Models

Used to load and draw basic 3D objects with a solid color, without texture mapping.

ParameterDescription
objThe 3D object loaded via LOAD3DOBJECT.
x, yScreen position (center of the object).
zDepth position (Z-axis offset).
scaleSize multiplier for the model.
rx, ry, rzRotation angles in degrees around the X, Y, and Z axes.
colorA CSS color string or integer (e.g., "lime", "#FF0000", 14).

Example 1: Basic Solid Model

SCREEN 12
FASTGRAPHICS

MODEL = LOAD3DOBJECT("https://basicfusion.org/cloud/assets/3d/skull.obj")
A = 0

SOLID_LOOP:
  CLS
  DRAW3DOBJECT MODEL, 0, 0, 0, 3.5, 0, A * 1.5, 0, 2
  A = A + 1
  SYNC
GOTO SOLID_LOOP

LOAD3DUVOBJECT, SET3DTEXTURE & DRAW3DUVOBJECT => Textured Models

Loads an .obj file containing UV mapping. SET3DTEXTURE assigns a loaded image to a specific material slot (starting from 0).

ParameterDescription
objThe 3D object loaded via LOAD3DUVOBJECT.
x, yScreen position (center of the object).
zDepth position (Z-axis offset). Normally 0.
scaleSize multiplier for the model.
rx, ry, rzRotation angles in degrees.

Example 2: Textured 3D Object

SCREEN 12
FASTGRAPHICS

BOX_OBJ = LOAD3DUVOBJECT("https://basicfusion.org/cloud/assets/3d/cube.obj")
BOX_TEX = LOADIMAGE("https://basicfusion.org/cloud/assets/3d/texture.jpg")

SET3DTEXTURE BOX_OBJ, 0, BOX_TEX

A = 0

SPIN_LOOP:
  CLS
  DRAW3DUVOBJECT BOX_OBJ, 0, 0, 0, 2, A, A * 1.5, A * 0.5
  A = A + 1
  SYNC
GOTO SPIN_LOOP
⚠️
Model Preparation: Ensure your .obj models are exported with UV mapping enabled (the file must contain vt tags). Also, keep the polygon count reasonable (low-poly), as basicFusion uses a software renderer.
💡
Z-Buffer is Automatic: You don't need to manually sort your models! The built-in Z-Buffer handles depth automatically, so you can draw multiple intersecting 3D objects in any order. The Z-Buffer is cleared every time you call CLS.
21Files

File I/O => FOPEN, FREAD$, FWRITE, FCLOSE

Open a file (or a URL) with FOPEN, it returns a numeric handle, or 0 if it failed. A path starting with http:// / https:// is downloaded (read-only); any other name is a local file kept in the browser. The optional second argument is the mode: "r" read (default), "w" write (truncate), "a" append.

fp = FOPEN("save.txt", "w")
IF fp = 0 THEN PRINT "cannot open" : END
FWRITELINE(fp, "hello")
FWRITE(fp, "score=") : FWRITE(fp, score)
FCLOSE(fp)   ' local files are saved on close

Read the whole thing with FREAD$(fp), or line by line with FREADLINE$(fp) until FEOF(fp). Also available: FSEEK(fp,pos), FTELL(fp), FSIZE(fp), FEXISTS(name$).

fp = FOPEN("https://example.com/data.txt")
IF fp = 0 THEN PRINT "load error" : END
WHILE FEOF(fp) = 0
  line$ = FREADLINE$(fp)
  PRINT line$
WEND
FCLOSE(fp)
💡
Good for save games, high-score tables and loading level or config data. String readers end in $ (FREAD$, FREADLINE$); the rest return numbers. See the ready-made File / config parser example.

Where do files live?

Three kinds of path are understood:

FDIALOG => Let the user pick a file

In the browser you cannot read arbitrary disk paths, so FDIALOG() opens the system "Open file" dialog, reads the chosen file as text and returns a read handle (0 if cancelled). Then use FREAD$ / FREADLINE$ as usual. An optional filter narrows the file types.

fp = FDIALOG(".txt")      ' user picks a file
IF fp = 0 THEN PRINT "cancelled" : END
PRINT FREAD$(fp)
FCLOSE(fp)
💡
Browsers only open a picker in response to a user action, so call FDIALOG near the start of your program (or right after a keypress), not deep inside a loop.

Example: a simple INI config parser

Reads a file with [sections], key = value lines and # comments, and prints it back:

cfg = FOPEN("https://example.com/config.txt")
IF cfg = 0 THEN PRINT "load error" : END

section$ = "(root)"
WHILE FEOF(cfg) = 0
  line$ = LTRIM$(RTRIM$(FREADLINE$(cfg)))
  IF line$ = "" THEN CONTINUE
  IF LEFT$(line$, 1) = "#" THEN CONTINUE
  IF LEFT$(line$, 1) = "[" THEN
    section$ = MID$(line$, 2, INSTR(line$, "]") - 2)
    PRINT "[" + section$ + "]"
    CONTINUE
  END IF
  eq = INSTR(line$, "=")
  IF eq > 0 THEN
    PRINT "  " + RTRIM$(LEFT$(line$, eq - 1)) + " = " + LTRIM$(MID$(line$, eq + 1))
  END IF
WEND
FCLOSE(cfg)
21Net

Sockets => SOCKETOPEN, SOCKETSEND, SOCKETRECV$ (WebSocket)

For a two-way, real-time connection (chat, multiplayer, live data) basicFusion speaks WebSocket. SOCKETOPEN connects and returns a handle once the socket is open (or 0 on failure); messages arrive asynchronously and queue up for SOCKETRECV$.

sock = SOCKETOPEN("wss://echo.websocket.events")
IF sock = 0 THEN PRINT "connect failed" : END
SOCKETSEND(sock, "hello")

FOR T = 1 TO 30
  WHILE SOCKETCOUNT(sock) > 0
    PRINT "recv: " + SOCKETRECV$(sock)
  WEND
  SLEEP 100
NEXT T
SOCKETCLOSE(sock)

Other calls: SOCKETSTATUS(sock) is 1 while the connection is open, SOCKETCOUNT(sock) tells you how many messages are waiting. SOCKETRECV$ is non-blocking, it returns "" when nothing is queued, so poll it inside your loop.

💡
Browsers can't open raw TCP sockets. To read a web page over HTTP, use FOPEN with an http(s) URL and FREAD$ (see the Files section and the Mini HTML parser example). WebSockets are for live, bidirectional links; cross-site fetches may be limited by the server's CORS policy.
21Net

Bluetooth LE => BTSCAN, BTWRITE, BTNOTIFY

basicFusion can talk to Bluetooth Low Energy devices (micro:bit, ESP32, sensors, wearables) using the browser's Web Bluetooth. BTSCAN opens the system device picker and connects; then read, write and subscribe to notifications on GATT characteristics.

SVC$ = "6e400001-b5a3-f393-e0a9-e50e24dcca9e"   ' Nordic UART service
TX$ = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"    ' notifications from device
RX$ = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"    ' write to device

dev = BTSCAN(SVC$)
IF dev = 0 THEN PRINT "no device" : END
PRINT "connected: " + BTNAME$(dev)
BTNOTIFY(dev, TX$)
BTWRITE(dev, RX$, "hello" + CHR$(10))
FOR T = 1 TO 60
  WHILE BTCOUNT(dev) > 0
    PRINT "recv: " + BTRECV$(dev)
  WEND
  SLEEP 100
NEXT T
BTCLOSE(dev)
💡
Works in Chrome / Edge (desktop and Android) and the desktop app, not Firefox or Safari. Needs HTTPS and a click to open the picker. This is BLE (GATT), not classic Bluetooth (no file transfer / SPP). Values are sent/received as UTF-8 text.
21Net

USB Serial => SERIALOPEN, SERIALWRITE, SERIALRECVLINE$

Talk to an Arduino (or any USB serial device) over the cable. SERIALOPEN shows the port picker and opens it; SERIALWRITE sends, and SERIALRECVLINE$ reads one line at a time, which matches how Serial.println() sends data.

p = SERIALOPEN(9600)
IF p = 0 THEN PRINT "no port" : END

SERIALWRITE(p, "1" + CHR$(10))    ' e.g. turn an LED on

FOR T = 1 TO 100
  line$ = SERIALRECVLINE$(p)
  WHILE line$ <> ""
    PRINT "arduino: " + line$
    line$ = SERIALRECVLINE$(p)
  WEND
  SLEEP 50
NEXT T
SERIALCLOSE(p)

On the Arduino side it's just Serial.begin(9600) with Serial.println(...) / Serial.read(), the same as the Arduino IDE. Use SERIALRECV$ for raw buffered text, SERIALCOUNT to see how much is waiting.

💡
Web Serial runs in Chrome / Edge / Chromium (including the desktop app), not Firefox/Safari. Only one tab can hold a port, so call SERIALCLOSE when done. On Android you'll need a USB-OTG adapter.
23dSound

Sound Files => LOADSOUND, PLAYSOUND, STOPSOUND, SOUNDVOLUME

basicFusion can load and play real audio files (MP3, WAV, OGG, etc.) from URLs using the LOADSOUND family of commands. This is separate from the synthesised 8-bit SOUND / PLAY / NOISE commands, use these when you need recorded music, voice, or sampled sound effects.

⚠️
Browser note: Loading audio from local file paths is subject to the same CORS restrictions as LOADIMAGE. Remote HTTPS URLs work best in the browser; local files are most reliable from the standalone basicFusion desktop app.

LOADSOUND => Load an Audio File

LOADSOUND is a function, it takes a URL string and returns a sound object. Assign the result to a variable for later use.

' SND = LOADSOUND("url_or_path")
MUSIC = LOADSOUND("https://example.com/theme.mp3")
BOOM  = LOADSOUND("sounds/explosion.wav")

PLAYSOUND => Play a Loaded Sound

Pass the sound variable. The optional second argument controls looping: 1 = loop continuously, 0 (default) = play once.

' play once
PLAYSOUND BOOM

' loop continuously (background music)
PLAYSOUND MUSIC, 1
💡
PLAYSOUND also plays synthesised effects: pass a number instead of a loaded sound to play the DEFSOUND with that id (e.g. PLAYSOUND 0). See the SFX Editor / DEFSOUND section.

STOPSOUND => Stop Playback

STOPSOUND MUSIC

SOUNDVOLUME => Set Volume

Volume ranges from 0.0 (silent) to 1.0 (full). You can call this at any time, before or after starting playback.

SOUNDVOLUME MUSIC, 0.5    ' 50% volume
SOUNDVOLUME BOOM, 1.0     ' full volume

Full Example => Background Music + SFX

SCREEN 12

' Load sounds once before the game loop
MUSIC  = LOADSOUND("https://example.com/bgm.mp3")
BOOM   = LOADSOUND("https://example.com/explosion.wav")
PICKUP = LOADSOUND("https://example.com/coin.wav")

SOUNDVOLUME MUSIC, 0.4     ' quiet background track
PLAYSOUND MUSIC, 1         ' loop it

PX = 100 : PY = 240
CX = INT(RND() * 580) + 30  ' coin X
CY = INT(RND() * 420) + 30  ' coin Y
SCORE = 0

GL:
  PAINT "#0a0a1a"
  IF KEY("ARROWLEFT")  THEN PX = PX - 5
  IF KEY("ARROWRIGHT") THEN PX = PX + 5
  IF KEY("ARROWUP")    THEN PY = PY - 5
  IF KEY("ARROWDOWN")  THEN PY = PY + 5

  IF ABS(PX - CX) < 20 AND ABS(PY - CY) < 20 THEN
    PLAYSOUND PICKUP
    SCORE = SCORE + 1
    CX = INT(RND() * 580) + 30
    CY = INT(RND() * 420) + 30
  END IF

  CIRCLE (PX,PY), 12, "lime"
  CIRCLE (CX,CY), 10, "gold"
  FONTSIZE 18
  TEXT (10,22), "SCORE: " + STR$(SCORE), "white"
  SYNC
GOTO GL
💡
Load once, play many times: Call LOADSOUND before your game loop, not inside it. Loading audio on every frame is very slow. Store the result in a variable and reuse it.
23eSound

Custom Sound Effects => DEFSOUND

DEFSOUND lets you define PICO-8 style sound effects right inside your program: a pattern of up to 32 notes, each with its own pitch, waveform, volume and effect. Unlike LOADSOUND (which streams a recorded file), these are fully synthesised, no external files, and they live in your code as plain text.

🎹
Build them visually: open the SFX Editor from the wizard menu, draw notes on the grid, pick waveform / volume / effect, set the speed, then hit Insert Code. It writes the DEFSOUND / SOUNDDATA lines for you and can re-load every sound it finds in your code.

Defining a Sound

A sound is two lines that share the same id (a number or a constant): a DEFSOUND header and a SOUNDDATA note pattern.

' DEFSOUND id, speed [, loopStart, loopEnd]
' SOUNDDATA id, "pitch:wave:vol:fx, ..."  (up to 32 notes)
DEFSOUND 0, 8
SOUNDDATA 0, "24:3:6:0, 28:3:6:0, 31:3:5:0, 36:3:5:0"

PLAYSOUND 0     ' play it by id

speed is how many ticks each note lasts (lower = faster). The id can be a constant, which keeps your code readable:

CONST SFX_LASER = 1
DEFSOUND SFX_LASER, 5
SOUNDDATA SFX_LASER, "60:2:5:3, 52:2:5:3, 44:2:4:3, 36:2:4:3"
PLAYSOUND SFX_LASER

The Note Format => pitch:wave:vol:fx

Each note in SOUNDDATA is four numbers joined by colons, and notes are separated by commas. A note with vol = 0 is a rest (silence).

FieldRangeMeaning
pitch0-63Semitone, low to high (0 ≈ C2). Each +12 is one octave up.
wave0-7Waveform / instrument (see table below).
vol0-7Volume. 0 = rest (no sound for that step).
fx0-7Per-note effect (see table below).

Waveforms (wave)

ValueTypeCharacter
0TriangleSoft, mellow, leads and bass
1Tilted sawSlightly buzzy, warmer than a full saw
2SawSharp, buzzy, basses and zaps
3SquareClassic 8-bit melody tone
4PulseThin, nasal pulse wave
5OrganLayered harmonics, fuller tone
6NoiseWhite noise, drums, explosions, hits
7PhaserDetuned, swirling saw

Effects (fx)

ValueEffectWhat it does
0NonePlain note
1SlideGlides in pitch from the previous note
2VibratoWobbles the pitch up and down
3DropPitch falls during the note
4Fade inVolume ramps up
5Fade outVolume ramps down
6Arp fastFast arpeggio across the group of 4 notes
7Arp slowSlower arpeggio across the group of 4 notes

Speed and Looping

The optional loopStart, loopEnd on DEFSOUND mark a region (by note index) that repeats. Omit them or use 0, 0 for a one-shot effect. Looping kicks in only when you ask for it with PLAYSOUND id, vol, 1.

' a 32-step pattern that loops steps 4..12 forever
DEFSOUND 2, 10, 4, 12
SOUNDDATA 2, "24:3:6:0, 24:3:5:0, 31:3:5:0, 28:3:5:0, 24:3:6:2, 27:3:5:2, 31:3:5:2, 28:3:5:2"
PLAYSOUND 2, 0.8, 1    ' vol 0.8, loop on

Full Example => Coin Pickup

SCREEN 12

' two quick rising blips, a classic coin sound
DEFSOUND 0, 7
SOUNDDATA 0, "43:0:6:0, 50:0:6:5"

PX = 100 : PY = 240
CX = 400 : CY = 240

GL:
  PAINT "#0a0a1a"
  IF KEY("ARROWLEFT")  THEN PX = PX - 5
  IF KEY("ARROWRIGHT") THEN PX = PX + 5

  IF ABS(PX - CX) < 20 THEN
    PLAYSOUND 0      ' play the DEFSOUND coin effect
    CX = INT(RND() * 580) + 30
  END IF

  CIRCLE (PX,PY), 12, "lime"
  CIRCLE (CX,CY), 10, "gold"
  SYNC
GOTO GL
⚠️
Define a sound before you play it. DEFSOUND / SOUNDDATA register the pattern when they run, so put them above your game loop, PLAYSOUND id on an undefined id is simply silent.
23fSound

Music Tracker => DEFMUSIC

DEFMUSIC lets you compose multi-channel chiptune music directly in your program. Each pattern has up to 4 channels (lead, bass, chord, percussion) and any number of steps. Unlike DEFSOUND which works with raw pitch numbers, the tracker uses standard note names like C4, A#3, F#5, much easier to write and read.

🎛️
Build them visually: open the Music Tracker from the wizard menu, place notes on the step grid, pick waveform / volume / duration / effects per step, then hit Insert Code. It writes all DEFMUSIC / MUSICDATA lines automatically and can re-load any pattern it finds in your code.

Defining a Pattern

A music pattern is one DEFMUSIC header line plus one MUSICDATA line per active channel, all sharing the same id.

' DEFMUSIC id, steps
' MUSICDATA id, channel, "step, step, ..."
DEFMUSIC 0, 16
MUSICDATA 0, 0, "C4:1:80:150:0:0,E4:1:80:150:0:0,-,G4:1:80:150:0:0,-,-,A4:1:70:150:0:0,-,-,-,-,-,-,-,-,-"
MUSICDATA 0, 1, "C3:2:60:300:0:0,-,-,-,C3:2:60:300:0:0,-,-,-,G2:2:60:300:0:0,-,-,-,-,-,-,-"

PLAYMUSIC 0    ' start looping, plays in the background

steps sets the loop length (16 and 32 are most common). The id can be a constant to keep your code readable:

CONST MUS_THEME = 0
DEFMUSIC MUS_THEME, 16
MUSICDATA MUS_THEME, 0, "C4:1:80:150:0:0,E4:1:80:150:0:0,G4:1:80:150:0:0,-,..."
PLAYMUSIC MUS_THEME

' later, in a game over screen:
STOPMUSIC MUS_THEME

The Step Format => note:wave:vol:dur:fx:fxVal

Each step in MUSICDATA is six fields joined by colons. Use - (a single dash) for a rest (silent step).

FieldExampleMeaning
noteC4, A#3, F#5Note name + octave. Standard notation: A-G, optional # for sharp.
wave0-7Waveform / instrument (0=sine, 1=square, 2=saw, 3=triangle, ...).
vol0-100Volume in percent. 0 is silent.
dur150Note duration in milliseconds.
fxARP, POR, VIB, 0Per-step effect name, or 0 for none.
fxVal7Numeric parameter for the effect (semitones for ARP, glide ms for POR, depth for VIB).

Effects (fx)

NameWhat it does
0 / NONEPlain note, no effect.
ARPArpeggio, rapidly alternates between the base note and base + fxVal semitones.
PORPortamento (glide), pitch slides up from fxVal semitones below to the target note over fxVal ms.
VIBVibrato, wobbles the pitch at ~8 Hz with a depth proportional to fxVal.

PLAYMUSIC Options

PLAYMUSIC takes optional arguments for loop, tempo and volume. Multiple patterns can play simultaneously on separate slots.

PLAYMUSIC 0                   ' loop=1 (default), bpm=120, vol=1.0
PLAYMUSIC 0, 1, 140           ' loop, 140 bpm
PLAYMUSIC 0, 0                ' play once (no loop)
PLAYMUSIC 0, 1, 120, 0.6     ' loop, 120 bpm, volume 60%

STOPMUSIC 0                   ' stop one pattern
STOPMUSIC                     ' stop all patterns

Full Example => Background Music in a Game

SCREEN 12

' --- DEFINED MUSIC ID: 0 (BPM: 130, STEPS: 16) ---
CONST MUS_GAME = 0
DEFMUSIC MUS_GAME, 16
MUSICDATA MUS_GAME, 0, "C4:1:80:150:0:0,-,E4:1:80:150:0:0,-,G4:1:80:150:0:0,-,E4:1:70:150:0:0,-,D4:1:80:150:0:0,-,F4:1:80:150:0:0,-,E4:1:75:150:0:0,-,-,-,-,-"
MUSICDATA MUS_GAME, 1, "C3:2:55:300:0:0,-,-,-,G2:2:55:300:0:0,-,-,-,F2:2:55:300:0:0,-,-,-,G2:2:55:300:0:0,-,-,-"

PX = 320 : PY = 240
PLAYMUSIC MUS_GAME, 1, 130   ' start music, loop, 130 BPM

GL:
  PAINT "#080820"
  IF KEY("ARROWLEFT")  THEN PX = PX - 4
  IF KEY("ARROWRIGHT") THEN PX = PX + 4
  IF KEY("ARROWUP")    THEN PY = PY - 4
  IF KEY("ARROWDOWN")  THEN PY = PY + 4
  CIRCLE (PX,PY), 14, "cyan"
  FONTSIZE 14
  TEXT (10,22), "arrows to move | music plays in background", "white"
  SYNC
GOTO GL
⚠️
Put all DEFMUSIC / MUSICDATA lines before PLAYMUSIC, they register the pattern when they run, so calling PLAYMUSIC on an undefined id is silent. Also remember: STOPMUSIC without an argument stops all active patterns at once.
24IDE

IDE Keyboard Shortcuts

Running

Ctrl+F9
Run program

Editor

Ctrl+Z
Undo
Ctrl+Y
Redo
Ctrl+S
Save code to file
Ctrl+G
Go to line

Search

Ctrl+F
Search in code
F3
Find next occurrence
Ctrl+H
Find and replace

Help

F1
Open / close help window
Esc
Close dialog
25IDE

Ready-made Examples

1. BMI Calculator

CLS
COLOR "cyan"
PRINT STRING$(40, "=")
PRINT "       BMI CALCULATOR"
PRINT STRING$(40, "=")
COLOR "white"
INPUT "Weight (kg): "; WAGA
INPUT "Height (m): "; WZROST
BMI = WAGA / (WZROST * WZROST)
PRINT "Your BMI:"; INT(BMI * 10) / 10
IF BMI < 18.5 THEN COLOR "yellow" : PRINT "Underweight"
IF BMI >= 18.5 AND BMI < 25 THEN COLOR "lime" : PRINT "Normal weight"
IF BMI >= 25 THEN COLOR "red" : PRINT "Overweight"
COLOR "white"

2. Multiplication Table

FOR I = 1 TO 10
  FOR J = 1 TO 10
    PRINT RIGHT$("   " + STR$(I * J), 4);
  NEXT J
  PRINT
NEXT I

3. Pythagorean Spiral

FASTGRAPHICS : SCREEN 12
CX = 320 : CY = 240
DO
    CLS
    FOR I = 1 TO 200
        T = I * 0.2 + ANGLE
        R = I * 0.9
        X = CX + COS(T) * R
        Y = CY + SIN(T) * R
        C = INT(I * 1.27) MOD 360
        PSET (X, Y), "hsl(" + STR$(C) + ",100%,60%)"
    NEXT I
    ANGLE = ANGLE + 0.001
    SYNC
LOOP UNTIL INKEY$ <> ""
END

4. Mini Game => Collecting Points

SCREEN 12
CONST SPD = 4
PX = 320 : PY = 400
GX = INT(RND() * 600) + 20
GY = INT(RND() * 400) + 20
SCORE = 0

GL:
  PAINT "#0a0010"
  IF KEY("ARROWLEFT")  THEN PX = PX - SPD
  IF KEY("ARROWRIGHT") THEN PX = PX + SPD
  IF KEY("ARROWUP")    THEN PY = PY - SPD
  IF KEY("ARROWDOWN")  THEN PY = PY + SPD
  IF ABS(PX-GX) < 20 AND ABS(PY-GY) < 20 THEN
    SCORE = SCORE + 10
    GX = INT(RND() * 600) + 20
    GY = INT(RND() * 400) + 20
  END IF
  BOX (PX-12,PY-12)-(PX+12,PY+12), "lime"
  CIRCLE (GX,GY), 10, "gold"
  FONTSIZE 18
  TEXT (10,22), "SCORE: " + STR$(SCORE), "white"
  SYNC
GOTO GL