BazzBasic

BazzBasic is a BASIC interpreter built to work with the .NET10 Framework.

It supports many of the features of BASIC interpreters from the 80s, but also offers something modern.

Development

So far, EkBass has been responsible for the development of BazzBasic.

BazzBasic is released under the open source MIT license.

Its source code is available and visible to everyone in the project’s GitHub repository.

Currently, the development work is done in the Windows 11 operating system, but with quite a bit of effort it can also be translated to Linux or MacOS.

Main functionalities

Most familiar BASIC features work either completely or almost completely as users of traditional BASIC languages ​​are used to using them.

User-Defined Functions

With or without recursion.

DEF FN factorial$(n$) 
    IF n$ <= 1 THEN 
        RETURN 1 
    END IF 
    RETURN n$ * FN factorial$(n$ - 1)
END DEF

PRINT FN factorial$(5) ' Output: 120
PRINT FN factorial$(10) ' Output: 3628800

SDL2 Graphics

BazzBasic offers a reasonable sampling of SDL2 features.

If your program uses graphic features, SDL2.dll must be in the same directory. This does not apply to console-only programs.

See Graphics Commands

Sounds

BazzBasic includes a sound system built on SDL2_mixer, supporting audio playback with both background and blocking modes.

See Sound Commands

Source Control

With the INCLUDE function, you can split the source code into different files and folders or generate tokenized libraries.

See Preprocessor or Generating libraries

Data types

Unlike many traditional BASIC interpreters, which required strong typing and often separated different data types with suffixes such as $ or %, BazzBasic copes smoothly with untyped data.

Typeless Variables and Constants

Variables automatically hold either numbers or strings:

LET num$ = 42            ' Number
LET text$ = "Hello"      ' String
LET mixed$ = "123"       ' String (quoted)

See Variables & Constants

Arrays

BazzBasic arrays are fully dynamic and support numeric, string, or mixed indexing.

DIM MyArray$
MyArray$("name") = "John Smith"
MyArray$("age") = 42

See Arrays

Getting Started

More Resources

BazzBasic size

Currently, BazzBasic requires about 70 megabytes + SDL2.dll

PublishTrimmed=true would reduce its size, but thorough testing is needed first.

BazzBasic includes .NET 10 assemblies during compilation, which affects the file size.

.NET 10, although a bit bulky, still offers compatibility far into the future.,

Contact

Author of BazzBasic is Kristian Virtanen.

Email: krisu.virtanen@gmail.com
Url: https://github.com/EkBass/BazzBasic

License

BazzBasic is released under MIT license:

MIT License

Copyright (c) 2026 Kristian Virtanen

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Installation

Download binary

Firewall issues

Since BazzBasic is built around .NET 10 and it is still a fairly recent project, Windows Firewall or third-party security programs may give a warning about it the first time you use it.

However, by switching to the GitHub release and providing the correct metadata during the compilation phase, these warnings can be reduced in the future.

The truth is, however, that open source projects that use .NET platforms will always suffer from this problem to some extent.

Run BazzBasic IDE

In order you to run your BazzBasic code with built-in IDE, you must save your file first and then press F5 to run it.

Use external IDE

Use with terminal

Source

IDE Usage

BazzBasic includes a simple integrated development environment (IDE).

Starting the IDE

Run BazzBasic.exe without any arguments to open the IDE.

Editor Features

Keyboard Shortcuts & Menu Map

Menu Shortcut Action CLI option
File Ctrl+N New file BazzBasic.exe
File Ctrl+O Open file none
File Ctrl+S Save file none
File Ctrl+Shift+S Save As none
File Ctrl+W Close tab none
File Alt + F4 Exit none
Edit Ctrl+F Find none
Edit Ctrl+H Replace none
Run F5 Run Program BazzBasic.exe file.bas
Run none Compile as Exe BazzBasic.exe -exe file.bas
Run none Compile as Library (.bb) BazzBasic.exe -lib file.bas
Help none About BazzBasic.exe -v
Help none Beginner’s Guide[1] BazzBasic.exe -guide
Help none Check For Updates BazzBasic.exe -checkupdates

[1] Opens BazzBasic-Beginners-Guide/releases in default www-browser.

Running Programs

Press F5 to run the current program. A console window will open showing the output.

If there are errors, they will be displayed with line numbers.

Creating Standalone Executables

BazzBasic can package your BASIC program into a standalone .exe file.

Usage

bazzbasic.exe -exe yourprogram.bas

This creates yourprogram.exe in the same folder as the .bas file.

Required Files for Distribution

The standalone exe needs only one additional file:

File Required Purpose
yourprogram.exe Yes Your packaged program
SDL2.dll Yes Graphics and input
SDL2_mixer.dll Yes Audio

Your assets:

File Required Purpose
.png, .bmp If used Image files
.wav, .mp3 If used Sound files

Minimal Distribution

mygame/
├── yourprogram.exe
└── SDL2.dll

Distribution with Assets recommendation

mygame/
├── yourprogram.exe
├── SDL2.dll
├── images/
│   ├── player.png
│   └── enemy.png
└── sounds/
    ├── shoot.wav
    └── music.mp3

Where to Find SDL2.dll

Binary download:

If you downloaded the binary file, bazzbasic.exe the required SDL2.dll in in the same folder.

If you build from the source:

After building BazzBasic, SDL2.dll is in:

BazzBasic\bin\Debug\net10.0-windows\

Or after publishing:

BazzBasic\bin\Release\net10.0-windows\publish\

Copy SDL2.dll alongside your packaged exe.

Console Window

The standalone exe runs with a console window visible. This is intentional for showing error messages.
A feature with what to hide console is coming on version 1.0

Notes

.NET Runtime Requirement

The packaged exe inherits the same runtime requirements as the BazzBasic.exe used to create it:

To create a fully self-contained BazzBasic for packaging:

dotnet publish -c Release -r win-x64 --self-contained true

Then use that published BazzBasic.exe for packaging your programs.

Creating Libraries

BazzBasic supports pre-compiled libraries (.bb files) for code reuse. Libraries contain tokenized functions that load faster than source files and use automatic namespace prefixes to prevent naming conflicts.

What is a Library?

A library is a collection of user-defined functions compiled into a binary format. When you create a library: - Source code is tokenized (converted to internal format) - Function names get a prefix based on the filename - The result is saved as a .bb file

Creating a Library

Step 1: Write your library source

Libraries can only contain DEF FN functions. No variables, constants, or executable code outside functions.

REM MathLib.bas - A simple math library

DEF FN add$(x$, y$)
  RETURN x$ + y$
END DEF

DEF FN multiply$(x$, y$)
  RETURN x$ * y$
END DEF

Step 2: Compile the library

bazzbasic.exe -lib MathLib.bas

Output:

Created library: MathLib.bb
Library name: MATHLIB
Size: <size> bytes

The library name is derived from the filename (uppercase, without extension).

Using a Library

Include the library and call library functions with the FN keyword using the library prefix:

INCLUDE "MathLib.bb"

LET a$ = 5
LET b$ = 3

PRINT "5 + 3 = "; FN MATHLIB_add$(a$, b$)
PRINT "5 * 3 = "; FN MATHLIB_multiply$(a$, b$)

Output:

5 + 3 = 8
5 * 3 = 15

Naming Convention

Source file Library name Function prefix
MathLib.bas MATHLIB MATHLIB_
StringUtils.bas STRINGUTILS STRINGUTILS_
MyGame.bas MYGAME MYGAME_

Original function add$() becomes MATHLIB_add$() when compiled from MathLib.bas.

Accessing Main Program Constants

Libraries cannot define constants, but library functions can access constants defined in the main program:

REM Main program
LET APP_NAME# = "MyApp"
LET APP_VERSION# = "1.0"

INCLUDE "Utils.bb"
PRINT FN UTILS_getAppInfo$()   ' Can use APP_NAME# and APP_VERSION#
REM Utils.bas - Library source
DEF FN getAppInfo$()
  RETURN APP_NAME# + " v" + APP_VERSION#
END DEF

Library Rules

Error Handling

Invalid library content:

bazzbasic.exe -lib BadLib.bas
Library validation failed: Line 5: Libraries can only contain DEF FN functions. Found: TOK_LET

Library not found:

Error: Line 1: Library not found: missing.bb

Benefits of Libraries

Note: For small files with only a few functions, the .bas file may be smaller than the tokenized .bb file due to metadata overhead. The size advantage shifts to the tokenized format as file size grows.

Distributing Libraries

When distributing your program with libraries:

MyGame/
├── MyGame.exe  (or MyGame.bas)
├── MathLib.bb
├── StringLib.bb
└── SDL2.dll

Libraries must be in the same directory as the main program, or in a path relative to it.

More Command Line Features

bazzbasic.exe -guide

or

bazzbasic.exe -help

Get Version of Your Current BazzBasic

BazzBasic.exe -version

or

BazzBasic.exe -v

Check For New Version Release

BazzBasic.exe -checkupdate

AI assisted learning

I downloaded the current documentation for Claude.AI, ChatGPT and Mistrall Le Chat to review and asked them to develop a guide for the BazzBasic language that works precisely as an aid to artificial intelligence.

This is the end result that all three “were” satisfied with.

In the AI service you prefer, upload it directly as a file, add it as a project file or agent guide.

With this guide, any AI can help you learn or use BazzBasic effectively.

BazzBasic Guide for AI

Rosetta Code

Rosetta Code is a programming chrestomathy site — a collection of common programming tasks solved in as many languages as possible. It’s a great way to see how a language thinks and compare it to others you already know.

BazzBasic has its own category on Rosetta Code:

Rosetta Code — BazzBasic

The same examples are also available in the BazzBasic GitHub repository:

GitHub — Examples

BazzBasic Beginner’s Guide

A beginner’s guide to BazzBasic is now available here: BazzBasic_Beginners_Guide.pdf
Markdown version here

This guide covers everything you need to get started: variables, loops, conditions, functions, file handling, JSON, networking, graphics, sound, and more. Each chapter builds on the last, with working example programs throughout.

This is not a complete reference.
It is a starting point.

The full BazzBasic manual covers everything else.


Author: Kristian Virtanen (EkBass)
License: CC BY-ND 4.0 — free to share with attribution, translations permitted, modifications are not permitted.

Variables & Constants

Declaration

Variables

' Basic init for variables
LET a$ = "Foo"
PRINT a$ ' Output: "Foo"
LET b$ = 1
PRINT b$ ' Output: 1
LET c$ = 1 + 2
PRINT c$ ' Output: 3

Constants

' Basic init for constants'
LET PI# = 3.14159
LET MAX_PLAYERS# = 4
LET GAME_TITLE# = "Space Invaders"

LET only when init

' Once variable is initialized, you do not need LET anymore
LET a$ = 1
a$ = 3
PRINT a$ ' Output: 3

Unsigned data type

' Variable stores either a number or a text.
' It is ok to change the type of data.
LET a$ = 1
PRINT a$ ' Output: 1
a$ = "Foo"
PRINT a$ ' Output: Foo

Versatile usage

' Math with variables and multiple inits in single line
LET a$ = 1, b$ = 2
LET c$ = a$ + b$
LET MyConst# = c$ * b$

Error situation examples

' Some errors
a$ = 1 ' error: a$ not initialized

LET b# = 1
b# = b# + 3 ' error: Constant b# had value 1 when initialized, not allowed to change anymore

Init with out a value

' If you want just to init var and not give value yet
LET a$ ' declares variable
a$ = 1 ' inits value 1 to variable

LET b# ' works, but is a bit stupid since now b# is constant with value of nothing

Concatenated values

LET x$ = 10
x$ += 5     ' 15
x$ -= 3     ' 12
x$ *= 2     ' 24
x$ /= 4     ' 6
PRINT x$    ' 6
LET s$ = "Hello"
s$ += " World"
PRINT s$    ' Hello World

Important: Not allowed with constants “#”

Exceptions

FOR and INPUT

When a variable is introduced with a FOR...NEXT or INPUT command, it does not need to be initialized with LET:

REM Ok to use without prior LET
INPUT "What is your name? ", name$

FOR i$ = 1 TO 10
    PRINT i$
NEXT

Comparing variables

A comparison is true if: - two string variables are equal - two number variables are equal - the value of the number in the string variable is the same as the number variable

LET a$, b$
a$ = "123"              ' a$ has now value "123"
b$ = 123                ' b$ has now value 123
LET c$ = "321"          ' c$ is now "321"

' Output of this IF...THEN: Same
IF a$ = b$ then
    print "Same"
ELSE
    print "Different"
ENDIF

' Output of this IF...THEN: Different
IF a$ = c$ then
    print "Same"
ELSE
    print "Different"
ENDIF

c$ = 123
' Output of this IF...THEN: Same
IF c$ = b$ then
    print "Same"
ELSE
    print "Different"
ENDIF

Note: Although 123 and “123” produce TRUE in an IF…THEN comparison, it is best to keep numeric variables as numeric and string variables as string.

A comparison between a numeric and string variable is only made if the data type of the variable contents is not the same. This slows down the comparison process if it is done significantly.

Built-in Constants

BazzBasic provides a full range of automatically initialized constants

System: | Constant | Description | |———-|————-| | PRG_ROOT# | Full path to the program’s base directory | | BBVER# | BazzBasic version string (e.g. "1.1d") |

PRINT "Version: "; BBVER#     ' Output: 1.1d
PRINT "Root:    "; PRG_ROOT#  ' Output: C:\path\to\program\

Keyboard:

Arrow Keys

KEY_UP# KEY_DOWN# KEY_LEFT#
KEY_RIGHT#

Special Keys

KEY_ESC# KEY_TAB# KEY_BACKSPACE#
KEY_ENTER# KEY_SPACE# KEY_INSERT#
KEY_DELETE# KEY_HOME# KEY_END#
KEY_PGUP# KEY_PGDN#

Modifier Keys

KEY_LSHIFT# KEY_RSHIFT# KEY_LCTRL#
KEY_RCTRL# KEY_LALT# KEY_RALT#
KEY_LWIN# KEY_RWIN#

Function Keys

KEY_F1# KEY_F2# KEY_F3#
KEY_F4# KEY_F5# KEY_F6#
KEY_F7# KEY_F8# KEY_F9#
KEY_F10# KEY_F11# KEY_F12#

Numpad Keys

KEY_NUMPAD0# KEY_NUMPAD1# KEY_NUMPAD2#
KEY_NUMPAD3# KEY_NUMPAD4# KEY_NUMPAD5#
KEY_NUMPAD6# KEY_NUMPAD7# KEY_NUMPAD8#
KEY_NUMPAD9#

Punctuation Keys

KEY_COMMA# KEY_DOT# KEY_MINUS#
KEY_EQUALS# KEY_SLASH# KEY_BACKSLASH#
KEY_SEP# KEY_GRAVE# KEY_LBRACKET#
KEY_RBRACKET#

Alphabet Keys

KEY_A# KEY_B# KEY_C#
KEY_D# KEY_E# KEY_F#
KEY_G# KEY_H# KEY_I#
KEY_J# KEY_K# KEY_L#
KEY_M# KEY_N# KEY_O#
KEY_P# KEY_Q# KEY_R#
KEY_S# KEY_T# KEY_U#
KEY_V# KEY_W# KEY_X#
KEY_Y# KEY_Z#

Number Keys

KEY_0# KEY_1# KEY_2#
KEY_3# KEY_4# KEY_5#
KEY_6# KEY_7# KEY_8#
KEY_9#

Naming Rules

  1. Must end with $ (variable) or # (constant)
  2. Can contain letters, numbers, and underscores
  3. Cannot start with a number
  4. Case-insensitive

Valid names:

LET score$
LET player1_name$
LET MAX_VALUE#
LET x$

Invalid names:

LET score       ' Missing suffix
LET 1player$    ' Starts with number

Errors

In case of error, BazzBasic stops with proper error message.

LET a$ = 100 ' ok
b$ = 200     ' Error at line 2: Undefined variable: B$ (use LET for first assignment)

Scope

Variables initialized within the main code belong to the same scope.

IF 1 = 1 THEN
    LET x$ = 10
END IF
PRINT x$                 ' Output: 10 (x$ is still accessible)
LET a$ = 1
LET b$ = 100              ' Global variable
LET C# = "Foo"            ' Global constant

DEF FN test$(a$)           ' Local parameter a$ is initialized here
    LET b$ = 2             ' Local b$ (separate from global b$)
    PRINT b$               ' Output: 2
    PRINT C#               ' Output: Foo
    a$ = a$ * b$           ' Uses local a$ (100) and local b$ (2)
    RETURN a$              ' Returns 200
END DEF

PRINT FN test$(b$)         ' Output: 200
PRINT a$                   ' Output: 1
PRINT b$                   ' Output: 100

Arrays & JSON

Arrays store collections of values. BazzBasic arrays are fully dynamic and support numeric, string, or mixed indexing.

Initialization

DIM scores$
DIM names$
DIM matrix$

Multiple declarations:

DIM a$, b$, c$

Numeric Indexing

Use numbers as indices (0-based):

DIM scores$
scores$(0) = 95
scores$(1) = 87
scores$(2) = 92

PRINT scores$(0)         ' Output: 95

String Indexing (Associative Arrays)

Use strings as keys:

DIM player$
player$("name") = "Alice"
player$("score") = 1500
player$("level") = 3

PRINT player$("name")    ' Output: Alice

Multi-dimensional Arrays

Use multiple indices separated by commas:

DIM matrix$
matrix$(0, 0) = "A1"
matrix$(0, 1) = "A2"
matrix$(1, 0) = "B1"
matrix$(1, 1) = "B2"
PRINT matrix$(1, 0)      ' Output: B1

Mixed Indexing

Combine numeric and string indices:

DIM data$
data$(1, "header") = "Name"
data$(1, "value") = "Alice"
data$(2, "header") = "Age"
data$(2, "value") = 30

PRINT data$(1, "value")  ' Output: Alice

Array Functions

DELARRAY

Removes the entire array and all its elements:

DIM arr$
arr$("name") = "Test"
arr$(0) = "Zero"

PRINT LEN(arr$())             ' Output: 2

DELARRAY arr$

' Array no longer exists, can be re-declared
DIM arr$
PRINT LEN(arr$())             ' Output: 0

DELKEY

Removes an element from the array:

DIM cache$
cache$("temp") = "value"
PRINT HASKEY(cache$("temp"))  ' Output: 1

DELKEY cache$("temp")
PRINT HASKEY(cache$("temp"))  ' Output: 0

HASKEY

Returns 1 if the key exists, 0 otherwise:

DIM config$
config$("debug") = 1

IF HASKEY(config$("debug")) THEN
    PRINT "Debug mode is set"
END IF

IF HASKEY(config$("verbose")) = 0 THEN
    PRINT "Verbose not set"
END IF

JOIN

Merges two source arrays into a destination array. If both arrays share the same key, src2$ overwrites src1$.

DIM a$
DIM b$
DIM c$

a$("name") = "Alice"
a$("score") = 100

b$("score") = 999   ' Same key as a$ — this wins
b$("level") = 5

JOIN c$, a$, b$

PRINT c$("name")    ' Output: Alice  (from a$)
PRINT c$("score")   ' Output: 999    (b$ overwrote a$)
PRINT c$("level")   ' Output: 5      (from b$)

LEN and ROWCOUNT

LEN(arr$()) | Total element count across all dimensions (note empty parens) |
ROWCOUNT(arr$()) | Count of first-dimension keys only — use for FOR loops over multi-dim arrays |
DIM sounds$
    sounds$("guns", "shotgun_shoot")    = "shoot_shotgun.wav"
    sounds$("guns", "shotgun_reload")   = "reload_shotgun.wav"
    sounds$("guns", "ak47_shoot")       = "shoot_ak47.wav"
    sounds$("guns", "ak47_reload")      = "reload_ak47.wav"
    sounds$("food", "ham_eat")          = "eat_ham.wav"

PRINT LEN(sounds$())        ' 5 as full size of arrayas there is total of 4 values in array
PRINT ROWCOUNT(sounds$())   ' 2, "guns" & "food"

Practical Examples

Pass values to user-defined functions

Arrays cannot be passed directly to functions. Instead, pass individual elements as values or whole array as JSON string.

As single values

DIM a$
a$("name") = "Foo"
a$("age") = 19
a$(1) = 1

DEF FN func$(a$, b$, c$)
    PRINT a$
    PRINT b$
    PRINT c$
    RETURN 0
END DEF

LET b$ = FN func$(a$("name"), a$("age"), a$(1))
' Output:
' Foo
' 19
' 1

As JSON

' ==================================================
' Example how to pass array to user-defined function
' Starting from ver. 1.2
' EkBass, public domain
' ==================================================

 ' User-defined functions
DEF FN ArrayAsParam$(data$)
    DIM array$

    LET count$= ASARRAY(array$, data$)
    LET daString$
        daString$ = array$("name") + "\n"
        daString$ = daString$ + array$("score") + "\n"
        daString$ = daString$ + array$("address,city") + "\n"
        daString$ = daString$ + array$("skills,0") + "\n"
        daString$ = daString$ + array$("skills,1") + "\n"

    RETURN daString$
END DEF

[inits]
    DIM player$
        player$("name") = "Alice"
        player$("score") = 9999
        player$("address,city") = "New York"
        player$("skills,0") = "JavaScript"
        player$("skills,1") = "Python"

    LET json$

[main]
    json$ = ASJSON(player$)
    ' json$ now: {"name":"Alice","score":9999,"address":{"city":"New York"},"skills":["JavaScript","Python"]}

[output]
    PRINT FN ArrayAsParam$(json$)
END

' Output:
' Alice
' 9999
' New York
' JavaScript
' Python

Simple List

DIM fruits$
LET count$ = 0

fruits$(count$) = "Apple"
count$ = count$ + 1
fruits$(count$) = "Banana"
count$ = count$ + 1
fruits$(count$) = "Cherry"
count$ = count$ + 1

FOR i$ = 0 TO count$ - 1
    PRINT fruits$(i$)
NEXT

Dictionary / Map

DIM translations$
translations$("hello") = "hei"
translations$("goodbye") = "nakemiin"
translations$("thanks") = "kiitos"

INPUT "English word: ", word$
IF HASKEY(translations$(word$)) THEN
    PRINT "Finnish: "; translations$(word$)
ELSE
    PRINT "Translation not found"
END IF

2D Grid

DIM grid$

' Initialize 3x3 grid
FOR row$ = 0 TO 2
    FOR col$ = 0 TO 2
        grid$(row$, col$) = "."
    NEXT
NEXT

' Place some markers
grid$(0, 0) = "X"
grid$(1, 1) = "O"
grid$(2, 2) = "X"

' Print grid
FOR row$ = 0 TO 2
    FOR col$ = 0 TO 2
        PRINT grid$(row$, col$); " ";
    NEXT
    PRINT ""
NEXT

Output:

X . .
. O .
. . X

Error Handling

Array Not Declared

scores$(0) = 95
' ERROR: Array not declared, use DIM first: scores$

Element Not Initialized

DIM data$
PRINT data$(0)
' ERROR: Array element data$(0) not initialized

Always check with HASKEY or initialize elements before reading.


Arrays and JSON

BazzBasic arrays map naturally to JSON. Nested JSON objects and arrays become multi-dimensional keys using comma-separated indices.

ASJSON

Converts a BazzBasic array to a JSON string:

DIM player$
player$("name") = "Alice"
player$("score") = 9999
player$("address,city") = "New York"
player$("skills,0") = "JavaScript"
player$("skills,1") = "Python"

LET json$ = ASJSON(player$)
PRINT json$
' Output: {"name":"Alice","score":9999,"address":{"city":"New York"},"skills":["JavaScript","Python"]}

ASARRAY

Converts a JSON string into a BazzBasic array. Returns number of elements loaded:

DIM data$
LET count$ = ASARRAY(data$, "{""name"":""Bob"",""score"":42}")

PRINT data$("name")    ' Output: Bob
PRINT data$("score")   ' Output: 42
PRINT count$           ' Output: 2

Nested JSON becomes comma-separated keys:

DIM data$
LET json$ = "{""player"":{""name"":""Alice"",""hp"":100},""skills"":[""fire"",""ice""]}"
ASARRAY data$, json$

PRINT data$("player,name")   ' Output: Alice
PRINT data$("player,hp")     ' Output: 100
PRINT data$("skills,0")      ' Output: fire
PRINT data$("skills,1")      ' Output: ice

LOADJSON

Loads a JSON file directly into an array:

DIM scores$
LOADJSON scores$, "highscores.json"

PRINT scores$("first,name")   ' Output: depends on file contents

SAVEJSON

Saves an array as a formatted JSON file:

DIM save$
save$("level") = 3
save$("hp") = 80
save$("position,x") = 100
save$("position,y") = 200

SAVEJSON save$, "savegame.json"

The resulting savegame.json:

{
  "level": 3,
  "hp": 80,
  "position": {
    "x": 100,
    "y": 200
  }
}

Practical example: HTTP API + JSON

DIM response$
LET raw$ = HTTPGET("https://api.example.com/user/1")
ASARRAY response$, raw$

PRINT "Name: "; response$("name")
PRINT "Email: "; response$("email")

HTTP Headers with Arrays

HTTPGET and HTTPPOST accept an optional headers$ array as the last parameter. This enables authenticated API calls, custom content types, and any other HTTP header needs.

DIM headers$
headers$("Authorization") = "Bearer mytoken123"
headers$("Content-Type") = "application/json"

LET raw$ = HTTPGET("https://api.example.com/data", headers$)
DIM headers$
headers$("Authorization") = "Bearer mytoken123"
headers$("Content-Type") = "application/json"

DIM body$
body$("model") = "gpt-4.1-mini"
body$("messages,0,role") = "user"
body$("messages,0,content") = "Hello"
body$("max_tokens") = 100

LET raw$ = HTTPPOST("https://api.openai.com/v1/chat/completions", ASJSON(body$), headers$)

DIM result$
LET count$ = ASARRAY(result$, raw$)
PRINT result$("choices,0,message,content")

Loading key=value Files into Arrays

FileRead can populate an array directly from a key=value formatted text file. If the target variable is a DIM’d array, BazzBasic automatically parses the file contents line by line into array elements.

DIM config$
LET config$ = FileRead("settings.txt")

PRINT config$("width")    ' Output: 800
PRINT config$("height")   ' Output: 600

Where settings.txt contains:

width=800
height=600
title=My Game

Lines beginning with # are treated as comments and ignored:

# Game settings
width=800
height=600

# Display
fullscreen=0

.env files

This makes .env files work naturally for storing API keys and configuration outside of source code:

IF FileExists(".env") = 0 THEN
    PRINT "Error: .env file not found"
    END
END IF

DIM env$
LET env$ = FileRead(".env")

LET ApiKey# = env$("OPENAI_API_KEY")

Where .env contains:

OPENAI_API_KEY=sk-proj-...
ANTHROPIC_API_KEY=sk-ant-...

Important: Add .env to your .gitignore to keep API keys out of version control.

Operators

Logical Values

TRUE, FALSE

Boolean constants.

LET gameOver$ = FALSE

WHILE NOT gameOver$
    ' game loop
    IF lives$ = 0 THEN gameOver$ = TRUE
WEND

Arithmetic Operators

Operator Description Example
+ Addition 5 + 38
- Subtraction 5 - 32
* Multiplication 5 * 315
/ Division 10 / 33.333...

Comparison Operators

Operator Description Example
= Equal to x = 5
<> Not equal to x <> 5
< Less than x < 10
> Greater than x > 0
<= Less than or equal x <= 100
>= Greater than or equal x >= 1

Logical Operators

Operator Description Example
AND Logical AND x > 0 AND x < 10
OR Logical OR x < 0 OR x > 100
NOT Logical NOT NOT finished

String Operators

Operator Description Example
+ Concatenation "Hello" + " " + "World""Hello World"

Operator Precedence

From highest to lowest:

  1. () - Parentheses
  2. NOT - Logical NOT
  3. *, / - Multiplication, Division
  4. +, - - Addition, Subtraction
  5. =, <>, <, >, <=, >= - Comparison
  6. AND - Logical AND
  7. OR - Logical OR

Comments

BazzBasic supports two comment styles.

REM

Traditional BASIC keyword comment.

REM This is a comment
REM Initialize player position
LET x$ = 100

Single Quote

Shorter alternative, works inline too.

' This is a comment
LET x$ = 100    ' Inline comment

Both styles are completely identical in behavior — use whichever you prefer. Single quote ' is generally preferred for its brevity.

' ============================================
' player.bas - Player movement logic
' BazzBasic: https://github.com/EkBass/BazzBasic
' ============================================

[inits]
    LET x$ = 320    ' Start at center X
    LET y$ = 240    ' Start at center Y

User Defined Functions

Basic Syntax

Note: Name of user-defined function must have ‘$’ as suffix

DEF FN functionName$(param1$, param2$, ...)
    ' Function body
    RETURN value
END DEF

Important to remember

A user-defined function must always return a value, and BazzBasic always expects that value to be used — assigned to a variable or passed to a command like PRINT.

DEF FN double$(x$)
    RETURN x$ * 2
END DEF

PRINT FN double$(21)        ' ✓ OK - return value goes to PRINT
LET foo$ = FN double$(5)    ' ✓ OK - return value stored in foo$
FN double$(21)              ' ✗ ERROR - return value has nowhere to go

Simple Function

DEF FN double$(x$)
    RETURN x$ * 2
END DEF

PRINT FN double$(21)    ' Output: 42
PRINT FN double$(5)     ' Output: 10

Multiple Parameters

DEF FN add$(a$, b$)
    RETURN a$ + b$
END DEF

DEF FN multiply$(a$, b$)
    RETURN a$ * b$
END DEF

PRINT FN add$(3, 4)         ' Output: 7
PRINT FN multiply$(3, 4)    ' Output: 12

Array data as parameter

Arrays cannot be passed directly to functions. Instead, pass individual elements as values or whole array as JSON string.

As single values

DIM a$
a$("name") = "Foo"
a$("age") = 19
a$(1) = 1

DEF FN func$(a$, b$, c$)
    PRINT a$
    PRINT b$
    PRINT c$
    RETURN 0
END DEF

LET b$ = FN func$(a$("name"), a$("age"), a$(1))
' Output:
' Foo
' 19
' 1

As JSON

' ==================================================
' Example how to pass array to user-defined function
' Starting from ver. 1.2
' EkBass, public domain
' ==================================================

 ' User-defined functions
DEF FN ArrayAsParam$(data$)
    DIM array$

    LET count$= ASARRAY(array$, data$)
    LET daString$
        daString$ = array$("name") + "\n"
        daString$ = daString$ + array$("score") + "\n"
        daString$ = daString$ + array$("address,city") + "\n"
        daString$ = daString$ + array$("skills,0") + "\n"
        daString$ = daString$ + array$("skills,1") + "\n"

    RETURN daString$
END DEF

[inits]
    DIM player$
        player$("name") = "Alice"
        player$("score") = 9999
        player$("address,city") = "New York"
        player$("skills,0") = "JavaScript"
        player$("skills,1") = "Python"

    LET json$

[main]
    json$ = ASJSON(player$)
    ' json$ now: {"name":"Alice","score":9999,"address":{"city":"New York"},"skills":["JavaScript","Python"]}

[output]
    PRINT FN ArrayAsParam$(json$)
END

' Output:
' Alice
' 9999
' New York
' JavaScript
' Python

String Functions

DEF FN greet$(name$)
    RETURN "Hello, " + name$ + "!"
END DEF

PRINT FN greet$("Alice")    ' Output: Hello, Alice!

Functions with Logic

DEF FN max$(a$, b$)
    IF a$ > b$ THEN
        RETURN a$
    ELSE
        RETURN b$
    END IF
END DEF

DEF FN min$(a$, b$)
    IF a$ < b$ THEN
        RETURN a$
    ELSE
        RETURN b$
    END IF
END DEF

PRINT FN max$(10, 25)    ' Output: 25
PRINT FN min$(10, 25)    ' Output: 10

Recursive Functions

Functions can call themselves:

DEF FN factorial$(n$)
    IF n$ <= 1 THEN
        RETURN 1
    END IF
    RETURN n$ * FN factorial$(n$ - 1)
END DEF

PRINT FN factorial$(5)    ' Output: 120
PRINT FN factorial$(10)   ' Output: 3628800

Scope

User-defined functions have their own local scope that is completely isolated from the main program:

LET b$ = 100              ' Global variable
LET C# = "Foo"            ' Global constant
DEF FN test$(a$)          ' Parameter a$ is local
    LET b$ = 2            ' Local b$ (separate from global b$)
    PRINT C#              ' Global constants are available
    a$ = a$ * b$          ' Uses local a$ (100) and local b$ (2)
    RETURN a$             ' Returns 200
END DEF

In this example: - The global b$ has value 100 - When calling FN test(b$), the value 100 is passed to parameter a$ - Inside the function, a new local variable b$ is created with value 2 - The local b$ is completely separate from the global b$ - The function calculates a$ * b$ = 100 × 2 = 200 - The global b$ remains unchanged at 100

Labels Inside Functions

Functions can have local labels for GOTO/GOSUB:

DEF FN countdown$(n$)
    [loop]
    IF n$ <= 0 THEN
        RETURN "Done!"
    END IF
    PRINT n$
    n$ = n$ - 1
    GOTO [loop]
END DEF

PRINT FN countdown$(5)

Output:

5
4
3
2
1
Done!

Function Isolation

GOTO and GOSUB inside a function cannot jump outside the function:

[outside_label]
PRINT "Outside"

DEF FN broken$(x$)
    GOTO [outside_label]    ' ERROR! Label is outside of function
    RETURN x$
END DEF

This produces: GOTO cannot jump outside function: outside_label

This restriction ensures functions are self-contained and predictable.

Practical Examples

Is Even/Odd

DEF FN isEven$(n$)
    RETURN MOD(n$, 2) = 0
END DEF

DEF FN isOdd$(n$)
    RETURN MOD(n$, 2) = 1
END DEF

FOR i$ = 1 TO 5
    IF FN isEven$(i$) THEN
        PRINT i$; " is even"
    ELSE
        PRINT i$; " is odd"
    END IF
NEXT

Order of code

Before code can call a user-created function, it must be loaded from the source code.

Wrong

Code below causes error: Error: Line 1: ‘ADD’ is not a valid variable name (must end with $ or #) or keyword

' incorrect way
PRINT FN ADD$(1, 2)

DEF FN ADD($a$, b$)
    RETURN a$ + b$
END DEF

Code below is valid

' correct way
DEF FN ADD$(a$, b$)
    RETURN a$ + b$
END DEF

PRINT FN ADD$(1, 2)

Tip

If you have several of your own functions, write them in their own file and include them in the code using INCLUDE right at the beginning of your program.

Control Flow

IF Statements

Block IF

Multi-line conditional with THEN on its own:

IF score$ >= 90 THEN
    PRINT "Grade: A"
    PRINT "Excellent!"
END IF

IF/ELSE

IF age$ >= 18 THEN
    PRINT "Adult"
ELSE
    PRINT "Minor"
END IF

IF/ELSEIF/ELSE

IF score$ >= 90 THEN
    PRINT "A"
ELSEIF score$ >= 80 THEN
    PRINT "B"
ELSEIF score$ >= 70 THEN
    PRINT "C"
ELSEIF score$ >= 60 THEN
    PRINT "D"
ELSE
    PRINT "F"
END IF

One-line IF

Jump to label based on condition:

IF lives$ = 0 THEN GOTO [game_over]
IF key$ = KEY_ESC# THEN GOTO [menu] ELSE GOTO [continue]
IF ready$ = 1 THEN GOSUB [start_game]

FOR Loops

Note: FOR auto-declares the loop variable — no LET needed.

Basic FOR

FOR i$ = 1 TO 10
    PRINT i$
NEXT

FOR with STEP

FOR i$ = 0 TO 100 STEP 10
    PRINT i$
NEXT

Counting Down

FOR i$ = 10 TO 1 STEP -1
    PRINT i$
NEXT
PRINT "Liftoff!"

Nested FOR

FOR row$ = 1 TO 3
    FOR col$ = 1 TO 3
        PRINT row$; ","; col$; " ";
    NEXT
    PRINT ""
NEXT

WHILE Loops

Repeat while condition is true:

LET count$ = 0
WHILE count$ < 5
    PRINT count$
    count$ = count$ + 1
WEND

Infinite Loop with Exit Condition

WHILE 1
    LET key$ = INKEY
    IF key$ = KEY_ESC# THEN GOTO [exit]
    ' Game logic here
WEND

[exit]
PRINT "Goodbye"

Labels and GOTO

Labels are marked with square brackets:

[start]
    PRINT "Starting..."
    GOTO [main]

[main]
    PRINT "Main section"
    GOTO [end]

[end]
    PRINT "Done"
    END

GOSUB and RETURN

Call a subroutine and return:

PRINT "Before subroutine"
GOSUB [greet]
PRINT "After subroutine"
END

[greet]
    PRINT "Hello!"
    RETURN

Nested GOSUB

GOSUB [level1]
END

[level1]
    PRINT "Level 1"
    GOSUB [level2]
    PRINT "Back to Level 1"
    RETURN

[level2]
    PRINT "Level 2"
    RETURN

Variables as GOTO/GOSUB targets

A variable or constant containing a label string can be used as a jump target:

LET foo$ = "[start]"
LET bar# = "[jump]"
GOTO foo$

[start]
PRINT "[start]"
GOSUB bar#
PRINT "Ending line"
END

[jump]
PRINT "[jump]"
RETURN

Function Scope and Labels

Inside user-defined functions, GOTO and GOSUB can only jump to labels within the same function:

DEF FN test$(x$)
    [local_label]
    IF x$ < 10 THEN
        x$ = x$ + 1
        GOTO [local_label]    ' OK - within function
    END IF
    RETURN x$
END DEF

[outside]
' GOTO [outside] from inside the function above would cause an error
PRINT "Outside"

END

Terminates program execution.

IF error$ THEN
    PRINT "Error occurred"
    END
END IF
PRINT "This runs if no error"

SLEEP

Pause execution for specified milliseconds.

PRINT "Wait 2 seconds..."
SLEEP 2000
PRINT "Done!"

Line Continuation

Long expressions and statements can be split across multiple lines. BazzBasic figures this out automatically — there is no special continuation character to remember (no \, no _, no &).

A newline is treated as a continuation (instead of ending the statement) when either:

  1. The previous token cannot legally end an expression — a binary operator, comma, compound-assign, or open paren.
  2. You are inside an open ( ... ) pair.

Operator-driven continuation

Any line ending in an “incomplete” token continues on the next line. Empty lines and trailing comments between the parts are tolerated.

LET total$ = price$ * count$ -
             discount$

LET msg$ = "Hello, " +
           name$ +
           "!"

IF score$ >= 0 AND
   score$ <= 100 THEN
    PRINT "Valid score"
END IF

LET counter$ +=
    step$

The full list of operators that trigger continuation: +, -, *, /, %, =, <>, <, <=, >, >=, AND, OR, +=, -=, *=, /=, and ,.

Note: MOD is a function (MOD(a, b)), not an operator, so it does not trigger continuation. The symbol % does.

Paren-driven continuation

Anything inside an unmatched ( keeps reading until the matching ), regardless of newlines:

LET dist$ = DISTANCE(
    x1$, y1$,
    x2$, y2$
)

LET val$ = MAX(
    MIN(100, score$),
    0
)

DIM grid$
    grid$(0,
          0) = "X"

Both mechanisms can be combined freely:

LET total$ = (
    base$ +
    tax$ -
    discount$
)

Watch out

Forgetting to close a ( makes the lexer keep eating lines until it finds a matching ) (or runs out of source). The eventual error message can be far from the actual mistake — count your parentheses if a long file suddenly stops parsing the way you expect.

See Also

Statements A-Z

Action commands that perform operations. For flow control (IF, FOR, WHILE, GOTO, GOSUB) see Control Flow.

CLS

Clear the screen.

CLS

COLOR

Set text foreground and background colors (0-15 palette).

COLOR 14, 1    ' Yellow text on blue background
PRINT "Colorful!"

See also: Graphics Commands

INPUT and LINE INPUT

BazzBasic provides two commands for reading user input from the console.

INPUT

Reads user input, splits on whitespace or comma.

INPUT "prompt", variable$
INPUT "prompt", var1$, var2$, var3$
INPUT variable$              ' Uses "? " as default prompt
LET firstName$, lastName$
INPUT "Enter your name: ", firstName$, lastName$
' User types: Kristian Virtanen
PRINT firstName$ + " " + lastName$
' Output: Kristian Virtanen

LINE INPUT

Reads the entire line including spaces.

LINE INPUT "Enter your name: ", name$
' User types: Kristian Virtanen
PRINT name$
' Output: Kristian Virtanen

Comparison

Feature INPUT LINE INPUT
Reads spaces No (splits) Yes
Multiple variables Yes No
Default prompt “?” None

LOCATE

Move cursor to specified position (1-based row and column).

LOCATE 10, 20
PRINT "Positioned text"

PRINT

Output text and values to the screen.

PRINT "Hello, World!"
PRINT 42
PRINT "Value: "; x$
PRINT "A", "B", "C"    ' Tab-separated
PRINT "X="; 10;        ' No newline at end

Escape Characters

Escape Result
\" Quote
\n New line
\t Tab
\\ Backslash
PRINT "He said, \"Hello\""  ' Output: He said, "Hello"
PRINT "Line1\nLine2"

SHELL

Sends a command to the system command interpreter.
Optional timeout parameter in milliseconds (default: 5000).

LET a$ = SHELL("dir *.txt")
PRINT a$

' Custom timeout (2 seconds)
LET a$ = SHELL("dir *.txt", 2000)
PRINT a$

SLEEP

Pause execution for specified milliseconds.

PRINT "Wait..."
SLEEP 2000
PRINT "Done!"

See Also

Functions

BazzBasic’s built-in functions are organized into separate pages:

Math Functions

ABS(n)

Returns the absolute value of n.

PRINT ABS(1.5)  ' 1.5
PRINT ABS(-1.5) ' 1.5

ATAN(n)

Returns the arc tangent of a n

PRINT ATAN(1.5) ' 0.982793723247329
PRINT ATAN(-1.5)    ' -0.982793723247329

ATAN2(n, n2)

Returns the angle, in radians, between the positive x-axis and a vector to the point with the given (x, y) coordinates in the Cartesian plane.

PRINT ATAN2(5, 10) ' 0.4636476090008061

BETWEEN(n, min, max)

Returns true if n is equal or between min and max

BETWEEN(1, 1, 10) ' true
BETWEEN(2, 1, 10) ' true
INBETWEEN(1, 1, 10) ' false
INBETWEEN(2, 1, 10) ' true

Unlike INBETWEEN which returns true if n is not equal and between min and max, BETWEEN returns true if n is equal and between min and max

CINT(n)

The Cint function converts an expression to type Integer.

PRINT CINT(3.7)      ' Output: 4
PRINT CINT(3.3)     ' Output: 3
PRINT CINT(-3.7)     ' Output: -4

CEIL(n)

Returns the smallest integer that is greater than or equal to a given number

PRINT CEIL(3.7)      ' Output: 4
PRINT CEIL(3.3)     ' Output: 4
PRINT CEIL(-3.7)     ' Output: -3

COS(n)

Returns the cosine of an angle

PRINT COS(0)    ' Output: 1
PRINT COS(45)   ' Output 0.5253219888177297
PRINT COS(90)   ' Output: -0.4480736161291701

CLAMP(n, min, max)

If the given parameter n falls below or exceeds the given limit values min or max, it is returned within the limits

LET brightness$ = CLAMP(value$, 20, 255)
' Replaces lines
' IF brightness$ < 20 THEN brightness$ = 20
' IF brightness$ > 255 THEN brightness$ = 255

DEG(radians)

Converts radians to degrees.

PRINT DEG(PI#)       ' Output: 180
PRINT DEG(HPI#)      ' Output: 90
PRINT DEG(2 * PI#)   ' Output: 360

' Convert result back to degrees
LET angle_rad$ = 1.5707963267948966
PRINT DEG(angle_rad$)  ' Output: 90

DISTANCE(x1, y1, x2, y2) or DISTANCE(x1, y1, z1, x2, y2, z2)

Returns the Euclidean distance between two points. Supports both 2D and 3D.

2D mode (4 parameters):

PRINT DISTANCE(0, 0, 3, 4)        ' Output: 5 (3-4-5 triangle)
PRINT DISTANCE(10, 20, 40, 60)    ' Output: 50

3D mode (6 parameters):

PRINT DISTANCE(0, 0, 0, 1, 1, 1)  ' Output: 1.732... (sqrt of 3)
PRINT DISTANCE(0, 0, 0, 0, 0, 5)  ' Output: 5

Practical example:

LET playerX$ = 100, playerY$ = 200
LET enemyX$ = 250, enemyY$ = 350
LET dist$ = DISTANCE(playerX$, playerY$, enemyX$, enemyY$)

IF dist$ < 50 THEN
    PRINT "Enemy is close!"
END IF

EULER

Returns the Euler’s constant, 2.718281828459045

PRINT EULER# ' Output: 2.718281828459045

EXP(n)

Exponential (e^n).

PRINT EXP(1)        ' Output: 2.718...
PRINT EXP(0)        ' Output: 1

FLOOR(n)

Rounds down and returns the largest integer

PRINT FLOOR(1.1)      ' Output: 1
PRINT FLOOR(1.95)       ' Output: 1
PRINT FLOOR(300)       ' Output: 300

HPI

Returns half of π (pi/2) ≈ 1.5707963267948966. Equivalent to 90 degrees in radians.

Useful in graphics and game programming where 90-degree angles are common.

PRINT HPI#                   ' Output: 1.5707963267948966
PRINT DEG(HPI#)              ' Output: 90

' Common angles in radians using built-in constants
LET angle_0$ = 0            ' 0 degrees
LET angle_45$ = QPI#         ' 45 degrees
LET angle_90$ = HPI#         ' 90 degrees
LET angle_180$ = PI#         ' 180 degrees
LET angle_270$ = PI# + HPI#   ' 270 degrees
LET angle_360$ = TAU#        ' 360 degrees

INBETWEEN(n, min, max)

Returns true, if n is between min and max and not equal to them

INBETWEEN(1, 1, 10) ' false
INBETWEEN(2, 1, 10) ' true
BETWEEN(1, 1, 10) ' true
BETWEEN(2, 1, 10) ' true

Unlike BETWEEN which returns true if n is equal or between min and max, INBETWEEN returns true if n is not equal and between min and max

INT(n)

Returns the integer part (truncates toward zero).

PRINT INT(3.7)     ' Output: 3
PRINT INT(-3.7)    ' Output: -3

LERP(start, end, t)

Linear interpolation between two values. Returns a value between start and end based on parameter t (0.0 to 1.0).

Parameters: - start - Starting value (when t = 0) - end - Ending value (when t = 1) - t - Interpolation factor (0.0 to 1.0) - t = 0.0 returns start - t = 0.5 returns halfway between - t = 1.0 returns end

Formula: result = start + (end - start) × t

Basic examples:

PRINT LERP(0, 100, 0)      ' Output: 0   (0% of the way)
PRINT LERP(0, 100, 0.5)    ' Output: 50  (50% of the way)
PRINT LERP(0, 100, 1)      ' Output: 100 (100% of the way)

PRINT LERP(10, 20, 0.25)   ' Output: 12.5
PRINT LERP(10, 20, 0.75)   ' Output: 17.5

Practical examples:

Distance-based brightness (raycasting):

LET maxDist# = 10
LET t$ = CLAMP(distance$ / maxDist#, 0, 1)
LET brightness$ = LERP(255, 20, t$)  ' Bright nearby, dark far away

Smooth camera movement:

LET t$ = 0.1  ' 10% closer each frame
cameraX$ = LERP(cameraX$, targetX$, t$)
cameraY$ = LERP(cameraY$, targetY$, t$)

LOG(n)

Natural logarithm (base e).

PRINT LOG(2.718)    ' Output: 0.999896315728952

MAX(n, n)

Return higher one from two numbers

LET A# = 1
LET A$ = 9.8
PRINT MAX(A#, A$)    ' Output: 9.8

MIN(n, n)

Return smaller one from two numbers

LET A# = 1
LET A$ = 9.8
PRINT MIN(A#, A$)    ' Output: 1

MOD(n, n)

Return modulus (remainder) of the two numbers

PRINT MOD(10, 3)    ' Output: 1
PRINT MOD(100, 20)  ' Output: 0

PI

Returns the mathematical constant π (pi) ≈ 3.14159265358979.

PRINT PI#                    ' Output: 3.14159265358979
LET circumference$ = 2 * PI# * radius$
LET area$ = PI# * radius$ * radius$

POW(n, n)

Return a number raised to the power of another number

PRINT POW(3, 3)     ' Output: 27
PRINT POW(2, 2)     ' Output: 4

QPI

Returns a quarter of π (pi/4) ≈ 0.7853981633974475. Equivalent to 45 degrees in radians.

PRINT QPI#                   ' Output: 0.7853981633974475
PRINT DEG(QPI#)              ' Output: 45

LET dx$ = COS(QPI#)          ' ~0.707
LET dy$ = SIN(QPI#)          ' ~0.707

RAD(degrees)

Converts degrees to radians.

PRINT RAD(90)       ' Output: 1.5707963267948966 (HPI#)
PRINT RAD(180)      ' Output: 3.141592653589793 (PI#)
PRINT RAD(360)      ' Output: 6.283185307179586 (TAU#)

PRINT SIN(RAD(90))  ' Output: 1
PRINT COS(RAD(180)) ' Output: -1

RND(n)

Returns a random integer from 0 to n-1 or a floating point between 0 and 1.

PRINT RND(10)       ' Output: 0-9
PRINT RND(100)      ' Output: 0-99
RND(0)          ' Float between 0.0 and 1.0 (IE: 0.5841907423666761)
' Dice roll
LET dice$ = RND(6) + 1

ROUND(n)

Rounds a number according to standard rules

PRINT ROUND(1.1)    ' Output: 1
PRINT ROUND(1.5)    ' Output: 2
PRINT ROUND(1.9)    ' Output: 2

SGN(n)

Returns the sign: -1, 0, or 1.

PRINT SGN(-5)      ' Output: -1
PRINT SGN(0)       ' Output: 0
PRINT SGN(5)       ' Output: 1

SIN(n)

Returns the sine of a number

PRINT SIN(-5)      ' Output: 0.9589242746631385
PRINT SIN(0)       ' Output: 0
PRINT SIN(5)       ' Output: -0.9589242746631385

SQR(n)

Returns the square root.

PRINT SQR(0)      ' Output: 0
PRINT SQR(5)      ' Output: 2.23606797749979
PRINT SQR(9)      ' Output: 3

TAN(n)

Returns the tangent of the given angle

PRINT TAN(0)      ' Output: 0
PRINT TAN(5)      ' Output: -3.380515006246586
PRINT TAN(9)      ' Output: -0.45231565944180985

TAU

Returns τ (tau) = 2π ≈ 6.28318530717958. Equivalent to 360 degrees in radians (a full circle).

PRINT TAU#                   ' Output: 6.28318530717958

' Full circle loop
FOR angle$ = 0 TO 359
    LET rad$ = (angle$ / 360) * TAU#
    LET x$ = COS(rad$) * radius$
    LET y$ = SIN(rad$) * radius$
NEXT

' All BazzBasic circle constants:
' QPI#  = π/4  = 45°
' HPI#  = π/2  = 90°
' PI#   = π    = 180°
' TAU#  = 2π   = 360°

String Functions

ASC(s$)

Returns ASCII code of first character.

PRINT ASC("A")      ' Output: 65
PRINT ASC("Hello")  ' Output: 72 (H)

BASE64DECODE(s$) & BASE64ENCODE(s$)

Decodes and encodes Base64

LET encoded$ = BASE64ENCODE("Hello, World!")
PRINT encoded$              ' SGVsbG8sIFdvcmxkIQ==

LET decoded$ = BASE64DECODE(encoded$)
PRINT decoded$              ' Hello, World!

CHR(n)

Returns character for ASCII code.

PRINT CHR(65)      ' Output: A
PRINT CHR(10)      ' Output: (newline)

FSTRING(template$)

String interpolation. Substitutes {{-name-}} placeholders inside the template with the value of variables, constants, or array elements. Whitespace inside placeholders is trimmed (so {{- name$ -}} works the same as {{-name$-}}). Numbers are auto-converted to strings.

Placeholder forms: - {{-var$-}} — variable - {{-CONST#-}} — constant - {{-arr$(0)-}} — array element with numeric index - {{-arr$(i$)-}} or {{-arr$(KEY#)-}} — array element where the index comes from a variable / constant (must end in $ or #) - {{-arr$(name)-}} — array element with literal string key (matches what was set via arr$("name") = ...) - {{-arr$(0, i$)-}} — multidimensional, mixed literal and variable indices

Notes: - No expressions, arithmetic, or function calls are allowed inside placeholders. Pre-compute into a variable first. - A bare identifier without $ / # suffix inside an array index is treated as a literal string key. - Unknown variable, missing -}}, or empty placeholder halts execution with a line-numbered error. - The literal sequence {{-...-}} is reserved — FSTRING will always try to resolve it.

LET name$ = "Krisu"
LET LEVEL# = 5
PRINT FSTRING("Hello {{-name$-}}, level {{-LEVEL#-}}")
' Output: Hello Krisu, level 5

DIM player$
    player$("name")  = "Krisu"
    player$("level") = 99
PRINT FSTRING("{{-player$(name)-}} is level {{-player$(level)-}}")
' Output: Krisu is level 99

DIM grid$
    grid$(0, 0) = "X"
    grid$(0, 1) = "O"
LET row$ = 0
PRINT FSTRING("[{{-grid$(row$, 0)-}}{{-grid$(row$, 1)-}}]")
' Output: [XO]

Finds position of substring (1-based, 0 if not found).

' In default mode, INSTR is case-sensitive
 PRINT INSTR("Hello World", "World") ' returns 7
 PRINT INSTR("Hello World", "HELLO") ' returns 0
 
' Case-insensitive mode.
PRINT INSTR("Hello World", "world", 0) ' returns 7

' Case-sensitive mode, same as default mode
PRINT INSTR("Hello World", "world", 1) ' returns 0

INVERT(s$)

Inverts a string

PRINT INVERT("Hello World")      ' Output: dlroW olleH

LCASE(s$)

Converts to lowercase.

PRINT LCASE("Hello")  ' Output: hello

LEFT(s$, n)

Returns first n characters.

PRINT LEFT("Hello World", 5)  ' Output: Hello

LEN(s$)

Returns string length.

PRINT LEN("Hello")  ' Output: 5
PRINT LEN("")       ' Output: 0

LTRIM(s$)

Removes blank characters from the left of the text

PRINT LTRIM("        Hello World") ' Output: Hello World

MID(s$, start) or MID(s$, start, length)

Returns substring starting at position (1-based).

PRINT MID("Hello World", 7)     ' Output: World
PRINT MID("Hello World", 7, 3)  ' Output: Wor
PRINT MID("Hello World", 1, 5)  ' Output: Hello

REPEAT(s$, n)

Repeats the text requested times

LET a$ = REPEAT("Foo", 10)
PRINT a$ ' Output: FooFooFooFooFooFooFooFooFooFoo

REPLACE(s$, a$, b$)

Replaces a$ with b$ from s$

LET text$ = "Hello World"
LET result$ = REPLACE(text$, "World", "BazzBasic")
PRINT result$  ' "Hello BazzBasic"

RIGHT(s$, n)

Returns last n characters.

PRINT RIGHT("Hello World", 5) ' Output: World

RTRIM(s$)

Removes blank characters from the right of the text

LET a$ = "Foo     "
LET b$ = "Bar"
a$ = RTRIM(a$)
PRINT a$ + b$ ' Output: FooBar

SHA256(s$)

Creates SHA256 hash from a string

LET hash$ = SHA256("password123")
PRINT hash$                 ' ef92b778... (64-char lowercase hex)

SPLIT(s$, a$, b$)

Splits string into array according to separator

DIM parts$
LET count$ = SPLIT(parts$, "apple,banana,orange", ",")
PRINT "Parts: "; count$
PRINT parts$(0)  ' "apple"
PRINT parts$(1)  ' "banana"
PRINT parts$(2)  ' "orange"

SRAND(n)

Returns random string length of n from allowed chars.
Allowed chars: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456780_

PRINT SRAND(10)  ' Output example: noAR1S-Qw1

STR(n)

Converts number to string.

LET s$ = STR(42)
PRINT "Value: " + s$  ' Output: Value: 42

TRIM(s$)

Removes blank characters from start and end of text

LET a$ = "    Foo     "
LET b$ = "    Bar     "
a$ = TRIM(a$)
b$ = TRIM(b$)
PRINT a$ + b$ ' Output: FooBar

UCASE(s$)

Converts to uppercase.

PRINT UCASE("Hello")  ' Output: HELLO

VAL(s$)

Converts string to number.

LET n$ = VAL("42")
PRINT n$ + 8        ' Output: 50

Input Functions

INKEY

Returns current key press (non-blocking). Returns 0 if no key pressed.

[loop]
    LET k$ = INKEY
    IF k$ = 0 THEN GOTO [loop]
    IF k$ = KEY_ESC# THEN END
    PRINT "Key code: "; k$
    GOTO [loop]

Special keys return values > 256: - Arrow keys: KEY_UP#, KEY_DOWN#, KEY_LEFT#, KEY_RIGHT# - Function keys: KEY_F1# through KEY_F12#

KEYDOWN

Read key states via key constants
Note: KEYDOWN availale only when graphics screen used. Not via console

REM KEYDOWN Test
SCREEN 12

LET x$ = 320
LET y$ = 240

WHILE INKEY <> KEY_ESC#
    SCREENLOCK ON
    LINE (0,0)-(640,480), 0, BF
    
    IF KEYDOWN(KEY_W#) THEN y$ = y$ - 2
    IF KEYDOWN(KEY_S#) THEN y$ = y$ + 2
    IF KEYDOWN(KEY_A#) THEN x$ = x$ - 2
    IF KEYDOWN(KEY_D#) THEN x$ = x$ + 2
    
    IF KEYDOWN(KEY_LSHIFT#) THEN
        CIRCLE (x$, y$), 20, RGB(255, 0, 0), 1
    ELSE
        CIRCLE (x$, y$), 10, RGB(0, 255, 0), 1
    END IF
    
    LOCATE 1, 1
    COLOR 15, 0
    PRINT "WASD=Move  SHIFT=Big  ESC=Quit"
    LOCATE 2, 1
    PRINT "X:"; x$; " Y:"; y$; "   "
    
    SCREENLOCK OFF
    SLEEP 16
WEND
END

WAITKEY

Halts execution until one of the specified keys is pressed. Returns the key value.

' Wait for ENTER only
PRINT "Press ENTER to continue"
LET foo$ = WAITKEY(KEY_ENTER#)

' Wait for any of several keys, capture result
PRINT "Press A, B or ESC"
LET k$ = WAITKEY(KEY_A#, KEY_B#, KEY_ESC#)
PRINT "You pressed: "; k$

' Wait for any key
PRINT "Press any key..."
LET k$ = WAITKEY()
PRINT "Key value: "; k$
Feature INKEY KEYDOWN WAITKEY
Blocking No No Yes
Returns Key value or 0 TRUE/FALSE Key value
Use case Game loops Held keys Menus, pauses

CURPOS

Returns the current cursor position. Useful for reading where the cursor is after PRINT or LOCATE.

LOCATE 5, 10
PRINT "Hello"

PRINT CURPOS("row")   ' Output: 5
PRINT CURPOS("col")   ' Output: 15  (10 + 5 chars of "Hello")
Parameter Returns
"row" Current cursor row (1-based)
"col" Current cursor column (1-based)

MOUSEX, MOUSEY

Note: Only available when graphics screen is open.

Returns mouse cursor position.

SCREEN 12
[loop]
    LOCATE 1, 1
    PRINT "X:"; MOUSEX; " Y:"; MOUSEY; "   "
    GOTO [loop]

MOUSELEFT, MOUSERIGHT, MOUSEMIDDLE

Note: Only available when graphics screen is open.

Returns 1 if the specified mouse button is currently pressed, 0 otherwise.

IF MOUSELEFT   THEN PRINT "Left clicked"
IF MOUSERIGHT  THEN PRINT "Right clicked"
IF MOUSEMIDDLE THEN PRINT "Middle clicked"

MOUSEHIDE & MOUSESHOW

In graphics screen, MOUSEHIDE hides the mouse cursor while MOUSESHOW will bring it back.

MOUSEHIDE ' hides mouse
SLEEP 5000
MOUSESHOW ' brings it back

Fast Trigonometry (Lookup Tables)

For graphics-intensive applications (games, raycasting, animations), BazzBasic provides fast trigonometric functions using pre-calculated lookup tables. These are significantly faster than standard SIN/COS functions but have 1-degree precision.

Performance: ~20x faster than SIN(RAD(x)) for integer degree values.

Memory: Uses ~5.6 KB when enabled (360 values × 2 tables × 8 bytes).

FastTrig(enable)

Enables or disables fast trigonometry lookup tables.

Parameters: - TRUE (or any non-zero value) - Creates lookup tables - FALSE (or 0) - Destroys lookup tables and frees memory

Important: Must call FastTrig(TRUE) before using FastSin, FastCos, or FastRad.

FastTrig(TRUE)

LET x$ = FastCos(45)
LET y$ = FastSin(45)

FastTrig(FALSE)

FastSin(angle)

Returns the sine of an angle (in degrees) using a lookup table. Angle is automatically normalized to 0-359. Precision: 1 degree.

FastTrig(TRUE)

PRINT FastSin(0)    ' Output: 0
PRINT FastSin(90)   ' Output: 1
PRINT FastSin(180)  ' Output: 0
PRINT FastSin(270)  ' Output: -1

' Angles are automatically wrapped
PRINT FastSin(450)  ' Same as FastSin(90) = 1
PRINT FastSin(-90)  ' Same as FastSin(270) = -1

FastTrig(FALSE)

FastCos(angle)

Returns the cosine of an angle (in degrees) using a lookup table. Angle is automatically normalized to 0-359. Precision: 1 degree.

FastTrig(TRUE)

PRINT FastCos(0)    ' Output: 1
PRINT FastCos(90)   ' Output: 0
PRINT FastCos(180)  ' Output: -1
PRINT FastCos(270)  ' Output: 0

FOR angle$ = 0 TO 359
    LET dx$ = FastCos(angle$)
    LET dy$ = FastSin(angle$)
    ' Cast ray in direction (dx, dy)
NEXT

FastTrig(FALSE)

FastRad(angle)

Converts degrees to radians using an optimized formula. Angle is automatically normalized to 0-359.

Note: Does not require FastTrig(TRUE).

PRINT FastRad(90)   ' Output: 1.5707963267948966 (HPI)
PRINT FastRad(180)  ' Output: 3.141592653589793 (PI)
PRINT FastRad(360)  ' Output: 6.283185307179586 (2*PI)

When to Use Fast Trig

Use FastTrig when: - Rendering graphics at high frame rates (60+ FPS) - Raycasting or ray tracing - Rotating sprites or shapes - Particle systems with many particles - Any loop that calls SIN/COS hundreds of times per frame

Use regular SIN/COS when: - Scientific calculations requiring high precision - One-time calculations - Angles are not in integer degrees

Color Functions

RGB(r, g, b)

Creates a color value from red, green, and blue components (0-255 each).

LET red$ = RGB(255, 0, 0)
LET green$ = RGB(0, 255, 0)
LET blue$ = RGB(0, 0, 255)
LET white$ = RGB(255, 255, 255)
LET black$ = RGB(0, 0, 0)
LET purple$ = RGB(128, 0, 128)

PSET 100, 100, RGB(255, 128, 0)          ' Orange pixel
LINE (0, 0)-(100, 100), RGB(0, 255, 0)   ' Green line
CIRCLE 200, 200, 50, RGB(255, 0, 0)      ' Red circle

Tip: Store frequently used colors in constants for better performance:

LET COLOR_PLAYER# = RGB(0, 128, 255)
LET COLOR_ENEMY# = RGB(255, 64, 64)
LET COLOR_BG# = RGB(32, 32, 48)

Time Functions

TIME(format$)

Returns current date/time as a formatted string. Uses .NET DateTime format strings.

PRINT TIME()                ' Default: "16:30:45"
PRINT TIME("HH:mm:ss")      ' "16:30:45"
PRINT TIME("dd.MM.yyyy")    ' "09.01.2026"
PRINT TIME("yyyy-MM-dd")    ' "2026-01-09"
PRINT TIME("dddd")          ' "Friday"
PRINT TIME("MMMM")          ' "January"
PRINT TIME("dd MMMM yyyy")  ' "09 January 2026"
PRINT TIME("HH:mm")         ' "16:30"

Common format codes: | Code | Description | Example | |——|————-|———| | HH | Hour (00-23) | 16 | | mm | Minutes | 30 | | ss | Seconds | 45 | | dd | Day | 09 | | MM | Month (number) | 01 | | MMM | Month (short) | Jan | | MMMM | Month (full) | January | | yy | Year (2 digits) | 26 | | yyyy | Year (4 digits) | 2026 | | ddd | Weekday (short) | Fri | | dddd | Weekday (full) | Friday |

TICKS

Returns milliseconds elapsed since program started. Useful for timing, animations, and game loops.

LET start$ = TICKS

FOR i$ = 1 TO 10000
    LET x$ = x$ + 1
NEXT

LET elapsed$ = TICKS - start$
PRINT "Time taken: "; elapsed$; " ms"

Game loop timing example:

SCREEN 12
LET lastFrame$ = TICKS
LET frameTime# = 16  ' ~60 FPS

[gameloop]
    LET now$ = TICKS
    IF now$ - lastFrame$ >= frameTime# THEN
        ' Update game logic here
        lastFrame$ = now$
    END IF

    IF INKEY = KEY_ESC# THEN END
    GOTO [gameloop]

Console Functions

GETCONSOLE(row, col, type)

Reads a character or color from a specific position on the console screen. Console only — not available in graphics mode.

Parameters: - row — row (1-based) - col — column (1-based) - type — 0 = character (as ASCII code), 1 = foreground color, 2 = background color

CLS
COLOR 11, 1
PRINT "abcdefghi"
PRINT "ABCDEFGHI"
COLOR 1, 9
PRINT "987654321"
COLOR 15, 0

PRINT "Row 2, col 5 char is: " + CHR(GETCONSOLE(2, 5, 0))  ' Output: E
PRINT "Row 1, col 2 foreground color: " + GETCONSOLE(1, 2, 1)  ' Output: 11
PRINT "Row 1, col 3 background color: " + GETCONSOLE(1, 3, 2)  ' Output: 1

Command Line Arguments

ARGCOUNT

Return the amount of arguments passed to program.

' BazzBasic.exe test.bas arg1 arg2
PRINT ARGCOUNT ' 2

ARGS()

Returns certain argument, if passed.

' BazzBasic.exe test.bas arg1 arg2
PRINT ARGS(0)
PRINT ARGS(1)
' Output:
' arg1
' arg2

Important

Variable Functions

ISSET(name)

Returns 1 if the named variable ($) or constant (#) is declared, 0 otherwise. Useful for guarding optional parameters, checking whether LET has run on a code path, or branching on configuration values.

ISSET only sees scalar variables and constants. Arrays and array elements are stored in a separate namespace and always return 0 — use the array-specific functions if you need to check those.

LET a$ = "hello"
LET b$                  ' declared with no value
LET MAX# = 100

PRINT ISSET(a$)         ' 1
PRINT ISSET(b$)         ' 1  (LET declares it even without a value)
PRINT ISSET(MAX#)       ' 1
PRINT ISSET(undef$)     ' 0
PRINT ISSET(UNDEF#)     ' 0

Suffix matters. a$ and A# are two different names — $ and # are part of the identifier:

LET a$ = "foo"
PRINT ISSET(a$)  ' 1
PRINT ISSET(A#)  ' 0   - the constant A# does not exist

Arrays return 0. The array name is not a scalar variable, and array elements are not scalars either:

DIM arr$
    arr$(0) = "zero"
PRINT ISSET(arr$)        ' 0
PRINT ISSET(arr$(0))     ' 0
PRINT ISSET(arr$(99))    ' 0

Typical use — guarding optional setup:

IF NOT ISSET(playerName$) THEN
    LET playerName$ = "Anonymous"
END IF

Important

File handling

BazzBasic includes a simple but powerful file system for reading and writing text files, enabling games to save scores, settings, and game state.

Overview

The file system allows you to: - Read text file contents as strings - Write or overwrite files - Append content to existing files - Check if files exist - Delete files - Work with both relative and absolute paths - Automatically create directories as needed

Supported File Operations

File Functions

FileRead(filepath$)

Reads the entire contents of a text file and returns it as a string or array.

Returns: String - Complete file contents, or empty string if file doesn’t exist or error occurs

Syntax:

content$ = FileRead("path/to/file.txt")

Example with variable:

LET config$
config$ = FileRead("settings.txt")
IF LEN(config$) > 0 THEN
    PRINT "Config loaded: "; config$
ELSE
    PRINT "Config file not found"
ENDIF

Example with array:

LET FILENAME# = "array.txt"
DIM a$
a$("first") = 1
a$("second") = 2
a$("third") = 3
a$("fourth") = "Four"
a$("fourth", "temp") = "Temp"
FileWrite FILENAME#, a$
DIM b$ = FileRead(FILENAME#)
PRINT b$("fourth", "temp")

Notes: - Returns empty string on error (no exception thrown) - Handles various text encodings automatically - Best for small to medium-sized text files - Line breaks are preserved in the returned string


FileExists(filepath$)

Checks whether a file exists at the specified path.

Returns: Number - 1 if file exists, 0 if not

Syntax:

LET exists# = FileExists("path/to/file.txt")

Example:

IF FileExists("highscore.txt") = 1 THEN
    PRINT "Loading existing scores..."
    LET scores$ = FileRead("highscore.txt")
ELSE
    PRINT "No saved scores found"
    LET scores$ = "0"
ENDIF

Notes: - Safe to call on any path - Returns 0 if path doesn’t exist or access is denied - Use before FileRead to avoid empty string results - Useful for conditional file operations


File Commands

FileWrite filepath$, content$

Writes content to a file, creating it if it doesn’t exist or overwriting it completely if it does.

Syntax:

FileWrite filepath$, content$

Example with string:

LET score$ = 12345
LET playerName$ = "Alice"
LET saveData$ = playerName$ + "\n" + STR(score$)
FileWrite "savegame.txt", saveData$
PRINT "Game saved!"

Example with array:

LET FILENAME# = "array.txt"
DIM a$
a$("first") = 1
a$("second") = 2
a$("third") = 3
a$("fourth") = "Four"
a$("fourth", "temp") = "Temp"
FileWrite FILENAME#, a$
DIM b$ = FileRead(FILENAME#)
PRINT b$("fourth", "temp") ' Outputs: Temp

Notes: - Creates parent directories automatically if they don’t exist - Completely replaces existing file contents - Silent operation - no return value - Use \n for line breaks in content - Escape backslashes in paths: "data\\save.txt" or use forward slash: "data/save.txt"


FileAppend filepath$, content$

Appends content to the end of an existing file, or creates the file if it doesn’t exist.

Syntax:

FileAppend filepath$, content$

Example:

LET timestamp$ = "2025-01-04 15:30:00"
LET logEntry$ = timestamp$ + " - Game started\n"
FileAppend "gamelog.txt", logEntry$

Notes: - Creates file if it doesn’t exist - Adds content to end without removing existing data - Perfect for logging and incremental data - Creates directories automatically if needed


FileDelete filepath$

Deletes a file from the file system.

Syntax:

FileDelete filepath$

Example:

REM Clean up temporary files
IF FileExists("temp.dat") = 1 THEN
    FileDelete "temp.dat"
    PRINT "Temporary file removed"
ENDIF

Notes: - Silent operation if file doesn’t exist - Irreversible operation - use with caution - Useful for cleanup and reset operations


Loading key=value Files into Arrays (.env support)

When FileRead assigns to a DIM’d array, BazzBasic automatically parses the file contents line by line into array elements — using the key=value format that .env files use.

DIM config$
LET config$ = FileRead("settings.txt")

PRINT config$("width")      ' Output: 800
PRINT config$("height")     ' Output: 600
PRINT config$("title")      ' Output: My Game

Where settings.txt contains:

width=800
height=600
title=My Game

Lines beginning with # are treated as comments and ignored:

# Game settings
width=800
height=600

# Display options
fullscreen=0

Using .env files for API keys

This makes standard .env files work directly for storing sensitive values such as API keys outside of source code:

IF FileExists(".env") = 0 THEN
    PRINT "Error: .env file not found"
    END
END IF

DIM env$
LET env$ = FileRead(".env")

LET ApiKey# = env$("OPENAI_API_KEY")
LET OtherKey# = env$("OTHER_SERVICE_KEY")

Where .env contains:

OPENAI_API_KEY=sk-proj-...
OTHER_SERVICE_KEY=abc123...

Important: Add .env to your .gitignore to keep API keys out of version control:

.env

Note: This parsing only happens when assigning FileRead to a DIM’d array. Assigning to a regular variable (LET data$) always returns the raw file contents as a plain string.


Path Handling

Relative vs Absolute Paths

Relative Paths (recommended for game files):

FileWrite "highscore.txt", "1000"           REM Same directory as program
FileWrite "data/settings.txt", "sound=on"   REM Subdirectory

Absolute Paths (for system-wide access):

FileWrite "C:/Users/Player/Documents/save.txt", "data"

Note: Use forward slashes / or escaped backslashes \\ to avoid escape sequence issues:

REM CORRECT:
FileWrite "data/file.txt", "content"
FileWrite "data\\file.txt", "content"

REM WRONG (escape sequences):
FileWrite "data\file.txt", "content"  REM \f becomes form feed!

PRG_ROOT# Constant

BazzBasic provides a PRG_ROOT# constant containing the program’s base directory path.

Example:

PRINT "Program root: "; PRG_ROOT#

REM Build absolute paths
LET savePath# = PRG_ROOT# + "/saves/game1.txt"
FileWrite savePath#, "Player data"

Use Cases: - Ensuring consistent file locations - Building absolute paths from relative ones - Debugging path issues


Complete Example: Highscore System

REM ============================================
REM Highscore Save/Load System
REM ============================================

CLS
PRINT "=== Highscore Demo ==="
PRINT ""

LET scoreFile$ = "highscore.txt"
LET currentScore$

REM Load existing highscore
IF FileExists(scoreFile$) = 1 THEN
    LET scoreData$ = FileRead(scoreFile$)
    LET highScore# = VAL(scoreData$)
    PRINT "Current highscore: "; highScore$
ELSE
    PRINT "No previous highscore found"
ENDIF

PRINT ""
PRINT "Play the game..."
REM Simulate gameplay
currentScore$ = RND(10000)

PRINT "Your score: "; currentScore$
PRINT ""

REM Check if new highscore
IF currentScore$ > highScore# THEN
    PRINT "NEW HIGHSCORE!"
    FileWrite scoreFile$, STR(currentScore$)
    PRINT "Highscore saved!"
ELSE
    PRINT "Better luck next time!"
ENDIF

END

Complete Example: Game Settings

REM ============================================
REM Game Settings Manager
REM ============================================

LET settingsFile$ = "settings.txt"
LET soundEnabled$ = "1"
LET musicVolume$ = "80"
LET difficulty$ = "medium"

REM Load settings if they exist
IF FileExists(settingsFile$) = 1 THEN
    PRINT "Loading settings..."
    LET settings$ = FileRead(settingsFile$)
    
    REM Parse settings (simple format: one per line)
    REM In real game, you'd parse the string
    PRINT "Settings loaded"
ELSE
    PRINT "Creating default settings..."
    
    REM Build settings string
    LET settings$ = ""
    settings$ = settings$ + "sound=" + soundEnabled$ + "\n"
    settings$ = settings$ + "volume=" + musicVolume$ + "\n"
    settings$ = settings$ + "difficulty=" + difficulty$ + "\n"
    
    FileWrite settingsFile$, settings$
    PRINT "Default settings saved"
ENDIF

REM Display settings
PRINT ""
PRINT "Current Settings:"
PRINT settings$

END

Complete Example: Game Event Log

REM ============================================
REM Event Logging System
REM ============================================

LET logFile$ = "gamelog.txt"

REM Initialize log file
IF FileExists(logFile$) = 0 THEN
    FileWrite logFile$, "=== Game Log ===\n"
ENDIF

REM Function to log an event
REM (In real code, use DEF FN)
LET event$ = "Game started"
FileAppend logFile$, event$ + "\n"

REM Simulate game events
SLEEP 1000
event$ = "Player spawned at (100, 200)"
FileAppend logFile$, event$ + "\n"

SLEEP 1000  
event$ = "Enemy encountered"
FileAppend logFile$, event$ + "\n"

SLEEP 1000
event$ = "Player health: 75"
FileAppend logFile$, event$ + "\n"

SLEEP 1000
event$ = "Game ended"
FileAppend logFile$, event$ + "\n"

PRINT "Events logged!"
PRINT ""
PRINT "Log contents:"
PRINT FileRead(logFile$)

END

Complete Example: Save Game System

REM ============================================
REM Simple Save Game System
REM ============================================

LET saveFile$ = "savegame.dat"

REM Game state variables
LET playerName$ = "Hero"
LET playerLevel$ = 5
LET playerHealth$ = 85
LET playerGold$ = 1250
LET currentMap$ = "dungeon_2"

REM === SAVE GAME ===
PRINT "Saving game..."

REM Build save data string (simple format)
LET saveData$ = ""
saveData$ = saveData$ + playerName$ + "\n"
saveData$ = saveData$ + STR(playerLevel$) + "\n"
saveData$ = saveData$ + STR(playerHealth$) + "\n"
saveData$ = saveData$ + STR(playerGold$) + "\n"
saveData$ = saveData$ + currentMap$ + "\n"

FileWrite saveFile$, saveData$
PRINT "Game saved!"
PRINT ""

REM === LOAD GAME ===
PRINT "Loading game..."

IF FileExists(saveFile$) = 1 THEN
    LET loadedData$ = FileRead(saveFile$)
    PRINT "Save file loaded:"
    PRINT loadedData$
    
    REM In real game, parse the string and restore variables
    PRINT "Game loaded successfully!"
ELSE
    PRINT "No save file found!"
ENDIF

END

Technical Details

Automatic Directory Creation

Error Handling

Character Encoding

Path Resolution

Thread Safety


Best Practices

  1. Check File Existence Before Reading

    IF FileExists("data.txt") = TRUE THEN
        LET data$ = FileRead("data.txt")
    ELSE
        LET data$ = ""  REM Default value
    ENDIF
  2. Use Relative Paths for Game Files

    REM Keep game files organized
    FileWrite "saves/slot1.dat", saveData$
    FileWrite "config/settings.txt", config$
  3. Structure Your Save Data

    REM Use clear format (one value per line)
    LET data$ = ""
    data$ = data$ + "PlayerName=" + name$ + "\n"
    data$ = data$ + "Score=" + STR(score#) + "\n"
    FileWrite "save.txt", data$
  4. Use FileAppend for Logs

    REM Accumulate log entries
    FileAppend "debug.log", "Error occurred\n"
  5. Clean Up Temporary Files

    REM At program exit
    IF FileExists("temp.dat") = 1 THEN
        FileDelete "temp.dat"
    ENDIF
  6. Escape Path Separators

    REM CORRECT:
    FileWrite "data/save.txt", content$
    FileWrite "data\\save.txt", content$
    
    REM WRONG:
    FileWrite "data\save.txt", content$  REM \s might be escape!

Common Patterns

Save/Load Pattern

REM Save
FileWrite "game.sav", gameState$

REM Load
IF FileExists("game.sav") = 1 THEN
    gameState$ = FileRead("game.sav")
ENDIF

Config File Pattern

REM Load or create default
IF FileExists("config.txt") = 0 THEN
    FileWrite "config.txt", defaultConfig$
ENDIF
config$ = FileRead("config.txt")

Incremental Log Pattern

REM Add entries over time
FileAppend "log.txt", timestamp$ + ": " + message$ + "\n"

Multiple Save Slots

LET slot$ = 1
LET filename$ = "save" + STR(slot$) + ".dat"
FileWrite filename$, data$

Troubleshooting

Problem: FileRead returns empty string - Check file exists with FileExists first - Verify file path is correct (use forward slashes) - Check file isn’t locked by another program - Try absolute path to debug

Problem: FileWrite doesn’t create file - Check path doesn’t contain invalid characters - Verify disk isn’t full or write-protected - Check permissions on target directory

Problem: Escape sequence in path - Use forward slashes: "data/file.txt" - Or escape backslashes: "data\\file.txt" - Avoid: \n, \t, \r in paths

Problem: Can’t find files after writing - Print PRG_ROOT# to see base directory - Use absolute paths for debugging - Check program’s working directory


Limitations


Future Enhancements

Potential additions to the file system: - Binary file support (read/write bytes) - Directory listing and enumeration - File copy and move operations - File metadata queries (size, modified date) - File locking for concurrent access - Stream-based reading for large files - CSV and JSON parsing helpers - Compression support

Screen Control

SCREEN - Initialize Graphics Mode

Initialize SDL2 graphics window with specified resolution and title.

SCREEN mode                      ' Standard BASIC mode
SCREEN 0, width, height          ' Custom resolution
SCREEN 0, width, height, "Title" ' Custom resolution with window title

Standard BASIC Modes

Mode Resolution Description
0 640×400 Text emulation mode
1 320×200 Low resolution
2 640×350 Medium resolution
7 320×200 VGA low-res
9 640×350 EGA enhanced
12 640×480 VGA standard (recommended)
13 320×200 MCGA mode

Examples

SCREEN 12                        ' 640×480 VGA mode
SCREEN 0, 800, 600               ' 800×600 custom size
SCREEN 0, 1024, 768, "My Game"   ' 1024×768 with custom title

CLS

Clear entire screen to background color.
Note: See performance tips for graphics

CLS                 ' Clear screen
COLOR 0, 1          ' Set blue background
CLS                 ' Clear to blue

FULLSCREEN

Toggle fullscreen mode (borderless window). Note: Fullscreen is only supported in graphics modes (not console mode).

SCREEN 640, 480, "My title"
FULLSCREEN TRUE   ' borderless fullscreen on
' ...
FULLSCREEN FALSE  ' Windowed mode

LOCATE

Move text cursor to specified row and column (1-based).

LOCATE row, column

LOCATE 10, 20       ' Row 10, Column 20
PRINT "Centered"

LOCATE 1, 1         ' Top-left corner
PRINT "Header"

Note: - In graphics mode, character positions are based on 8×8 pixel font cells. - While PRINT works, it is recommended to use DRAWSTRING since PRINT can make graphic screen to blink if FPS rate is high.

COLOR

Set foreground (text) and background colors.

COLOR foreground, background

COLOR 14, 1         ' Yellow text on blue background
PRINT "Warning!"

COLOR 15, 0         ' White on black (default)

Color Palette (0-15)

Value Color Value Color
0 Black 8 Dark Gray
1 Blue 9 Light Blue
2 Green 10 Light Green
3 Cyan 11 Light Cyan
4 Red 12 Light Red
5 Magenta 13 Light Magenta
6 Brown 14 Yellow
7 Light Gray 15 White

SCREENLOCK

Control when graphics are displayed to prevent flickering.

SCREENLOCK ON       ' Lock screen (start buffering)
SCREENLOCK OFF      ' Unlock and display (present buffer)
SCREENLOCK          ' Same as SCREENLOCK ON

Why use SCREENLOCK? Without screen locking, each graphics command immediately updates the display, causing visible flickering during animation. SCREENLOCK buffers all drawing commands and displays them at once.

Example - Smooth Animation

[inits]
    ' declarations and initialisations before loops etc.
    LET angle$ = 0
    SCREEN 12

[main]
    WHILE INKEY <> 27

        ' keep logic and math out of SCREENLOCK ON/OFF
        angle$ = angle$ + 5
        
        ' Start buffering. Indents helps with SCREENLOCK ON/OFF
        SCREENLOCK ON
            ' clear with LINE...BF
             LINE (0, 0)-(640, 480), 0, BF

            ' Draw multiple shapes
            LINE (100,100)-(200,200), 15
            CIRCLE (320, 240), 50, 12
        SCREENLOCK OFF          ' Display all at once
        SLEEP 16                ' ~60 FPS
    WEND
END

VSYNC

Control vertical synchronization (VSync) to limit or maximize frame rate.

VSYNC(TRUE)         ' Enable VSync (default)
VSYNC(FALSE)        ' Disable VSync

Parameters: - TRUE or any non-zero value - Enable VSync - FALSE or 0 - Disable VSync

What is VSync?

VSync synchronizes frame rendering with your monitor’s refresh rate (typically 60Hz = 60 FPS). This prevents screen tearing (horizontal lines during fast motion) but limits maximum frame rate.

When to use: - VSYNC(TRUE) - Normal gameplay (smoother, no tearing) - VSYNC(FALSE) - Benchmarking, performance testing, FPS measurement

Important: - VSync is enabled by default when you call SCREEN - Call VSYNC after the SCREEN command - If rendering takes longer than 16.67ms (1/60s), VSync will lock to 30 FPS instead of 60 FPS

Performance Impact

' With VSync enabled (default):
' - FPS capped at monitor refresh rate (usually 60)
' - Smooth rendering, no screen tearing
' - If frame takes > 16.67ms → drops to 30 FPS

' With VSync disabled:
' - Unlimited FPS (can exceed 100+ FPS)
' - May show screen tearing
' - Better for performance benchmarking

Example - FPS Testing

' BazzBasic version 1.3
' https://ekbass.github.io/BazzBasic/

[inits]
    LET frameCount$     = 0
    LET lastTime$       = TICKS
    LET frameCount$     = 0
    LET currentTime$    = 0
    LET fps$

    SCREEN 12
    VSYNC(FALSE)        ' Disable for true FPS measurement
    LET MY_COLOR# = RGB(255, 255, 255)

[main]
    WHILE INKEY <> KEY_ESC#

        ' Calculate FPS
        frameCount$ = frameCount$ + 1
        currentTime$ = TICKS
        
        IF currentTime$ - lastTime$ >= 1000 THEN
            fps$ = frameCount$ / ((currentTime$ - lastTime$) / 1000)
            frameCount$ = 0
            lastTime$ = currentTime$
        ENDIF

        SCREENLOCK ON
            ' clear
            LINE (0, 0)-(640, 480), 0, BF
            ' filled circle
            CIRCLE (320, 240), 50, 12, 1
            ' fps
            DRAWSTRING "FPS: " + STR(INT(fps$)), 0, 0, MY_COLOR#
        SCREENLOCK OFF
    WEND

    VSYNC(TRUE)         ' Restore default before exit
END

Example - Toggle VSync at Runtime

' BazzBasic version 1.3
' https://ekbass.github.io/BazzBasic/

[inits]
    SCREEN 12
    LET vSyncStatus$ = TRUE
    VSYNC(vSyncStatus$)
    LET vColor$ = RGB(255, 255, 255)
    LET vString$
    LET key$
    LET running$ = TRUE
[main]
    WHILE running$
        
        IF KEYDOWN(KEY_ESC#) THEN running$ = FALSE
        IF KEYDOWN(KEY_SPACE#) THEN
            vSyncStatus$ = NOT vSyncStatus$
            VSYNC(vSyncStatus$)
            ' Wait until space is released or it gets buffered and in problems
            WHILE KEYDOWN(KEY_SPACE#) : WEND
            IF vSyncStatus$ = TRUE THEN
                vString$ = "VSync: ON "
            ELSE
                vString$ = "VSync: OFF "
            END IF
        END IF
        
        SCREENLOCK ON
            LINE (0,0)-(639,479), 0, BF
            DRAWSTRING vString$, 0, 16, vColor$
        SCREENLOCK OFF
        SLEEP 16
    WEND
END

Troubleshooting:

If your game runs at 30 FPS instead of 60 FPS with VSync enabled: 1. Your rendering takes > 16.67ms per frame 2. VSync forces wait for next refresh (60 FPS ÷ 2 = 30 FPS) 3. Solutions: - Reduce number of rays in raycaster - Lower screen resolution - Optimize rendering code - Use VSYNC(FALSE) to test maximum achievable FPS

Basic Graphics Primitives

PSET - Draw Pixel

Draw a single pixel at coordinates (x, y).

PSET (x, y), color

PSET (100, 100), 15     ' White pixel
PSET (320, 240), 12     ' Red pixel at center

LINE - Draw Lines and Boxes

Draw lines or filled/unfilled rectangles.

LINE (x1, y1)-(x2, y2), color           ' Line
LINE (x1, y1)-(x2, y2), color, B        ' Box (outline)
LINE (x1, y1)-(x2, y2), color, BF       ' Box Filled

Examples

LINE (10, 10)-(100, 50), 15             ' White line
LINE (50, 50)-(150, 150), 12, B         ' Red box outline
LINE (200, 100)-(300, 200), 9, BF       ' Blue filled box

CIRCLE - Draw Circle

Draw circle or filled circle at center point.

CIRCLE (centerX, centerY), radius, color
CIRCLE (centerX, centerY), radius, color, filled

CIRCLE (320, 240), 50, 14               ' Yellow circle outline
CIRCLE (100, 100), 30, 12, 1            ' Red filled circle

PAINT - Fill Area

Fill an enclosed area with color (flood fill).

PAINT (x, y), fillColor, borderColor

' Draw and fill a box
LINE (100, 100)-(200, 200), 15, B       ' White border
PAINT (150, 150), 12, 15                ' Fill red inside white border

RGB - Create Color Value

Create custom RGB color (0-255 per channel).

LET color$ = RGB(red, green, blue)

LET purple$ = RGB(128, 0, 128)
PSET (100, 100), purple$

LET orange$ = RGB(255, 165, 0)
CIRCLE (320, 240), 50, orange$

POINT - Read pixel color from screen

Returns a RGP color value from certain point of the screen.

LET c$ = POINT(100, 100)

DRAWSTRING & LOADFONT

Render text directly to the SDL2 graphics surface. Requires SDL2_ttf.dll in the same directory as the interpreter.

' Default font (Arial, size 20)
DRAWSTRING "Hello!", 100, 200, RGB(255, 255, 255)

' Load alternative font — becomes the new active font
LOADFONT "myfont.ttf", 24
DRAWSTRING "Hello!", 100, 200, RGB(255, 255, 255)

' Reset to default (Arial, size 20)
LOADFONT

DRAWSTRING positions text by its top-left corner. Prefer it over PRINT in graphics mode — PRINT bypasses the SDL2 rendering pipeline and causes flickering at higher frame rates.

Fonts

SDL2_ttf loads any standard .ttf or .otf font file. BazzBasic defaults to Arial (size 20). To use a different font, call LOADFONT with a font filename and point size. The loaded font stays active until you call LOADFONT again or reset it.

Bundling fonts with your program (recommended)

Copy the .ttf file into the same folder as your .bas file and reference it by filename:

LOADFONT "PressStart2P.ttf", 16
DRAWSTRING "GAME OVER", 200, 200, RGB(255, 0, 0)

This makes your program portable and not dependent on the fonts installed on the user’s system. Free fonts are widely available — Google Fonts offers hundreds under the OFL open-source licence.

Using Windows system fonts

You can also reference fonts already installed on Windows by full path:

LOADFONT "C:\\Windows\\Fonts\\consola.ttf", 14   ' Consolas (monospace)
LOADFONT "C:\\Windows\\Fonts\\times.ttf", 18     ' Times New Roman

Note: this works only on Windows and only if the font is installed — not recommended for distributed programs.

Font sizes

The size parameter is a point size — any positive integer is valid:

Use Size
Small UI / HUD text 12–14
Normal text 16–20
Subheadings 24–32
Titles / headings 48–64
Splash screens 72–96

Shape System (Sprites)

LOADSHAPE - Create Shape

Create or load a new shape and return its ID.

id$ = LOADSHAPE(type$, width, height, color)

Shape types: - "RECTANGLE" - Rectangle shape - "CIRCLE" - Circle shape
- "TRIANGLE" - Triangle shape

Examples

LET SQUARE# = LOADSHAPE("RECTANGLE", 50, 50, RGB(255, 0, 0))
LET BALL#   = LOADSHAPE("CIRCLE", 40, 40, RGB(0, 255, 0))
LET ARROW#  = LOADSHAPE("TRIANGLE", 30, 40, RGB(0, 0, 255))

LOADSHEET - Load Sprite Sheet

Reads an image file and splits it into sprites according to the specification. The original image size must be divisible by the given size parameters

' BazzBasic version 1.3
' https://ekbass.github.io/BazzBasic/

' ============================================
' LOADSHEET demo: countdown 9 -> 0
' sheet_numbers.png: 640x256, 128x128 sprites
' ============================================

[inits]
    ' Center position for a 128x128 sprite on 640x480 screen
    LET x# = 256
    LET y# = 176
    DIM sprites$

    SCREEN 0, 640, 480, "Countdown!"
    LOADSHEET sprites$, 128, 128, "Examples/images/sheet_numbers.png"

[main]
    ' Count down from 9 to 0
    FOR i$ = ROWCOUNT(sprites$()) - 1 TO 0 STEP -1
        SCREENLOCK ON
            LINE (0, 0)-(640, 480), 0, BF
            MOVESHAPE sprites$(i$), x#, y#
            DRAWSHAPE sprites$(i$)
        SCREENLOCK OFF
        SLEEP 500
    NEXT
END

MOVESHAPE - Position Shape

Move shape to absolute screen position.

MOVESHAPE ID#, x, y

ROTATESHAPE - Rotate Shape

Rotate shape to specified angle in degrees.

ROTATESHAPE id$, angle

ROTATESHAPE SQUARE#, 45         ' 45 degrees
ROTATESHAPE ARROW#, 180         ' Upside down
ROTATESHAPE ARROW#, 270         ' 90 degrees counter-clockwise

Note: Rotation is absolute, not cumulative. To spin continuously:

DIM angle$
LET angle$ = 0

WHILE 1
    angle$ = angle$ + 5
    IF angle$ >= 360 THEN
        angle$ = 0
    ENDIF
    ROTATESHAPE SQUARE#, angle$
    SLEEP 16
WEND

SCALESHAPE - Scale Shape Size

Scale shape by multiplier (1.0 = original size).

SCALESHAPE id$, scale

SCALESHAPE BALL#, 0.5           ' Half size
SCALESHAPE SQUARE#, 2.0         ' Double size
SCALESHAPE ARROW#, 1.5          ' 150% size

Pulsing effect:

DIM scale$
DIM direction$
LET scale$ = 1
LET direction$ = 0.01

WHILE 1
    scale$ = scale$ + direction$
    
    IF scale$ >= 1.5 OR scale$ <= 0.5 THEN
        direction$ = direction$ * -1
    ENDIF
    
    SCALESHAPE BALL#, scale$
    SLEEP 16
WEND

DRAWSHAPE - Render Shape

Draw shape to screen at current position/rotation/scale.

DRAWSHAPE SQUARE#

SCREENLOCK ON
    LINE (0, 0)-(640, 480), 0, BF
    DRAWSHAPE SQUARE#
    DRAWSHAPE BALL#
    DRAWSHAPE ARROW#
SCREENLOCK OFF

Important: Use with SCREENLOCK for smooth rendering!

SHOWSHAPE / HIDESHAPE - Toggle Visibility

Show or hide shape without removing it.

SHOWSHAPE id$
HIDESHAPE id$

HIDESHAPE ENEMY#                ' Temporarily hide
' ... do something ...
SHOWSHAPE ENEMY#                ' Show again

REMOVESHAPE - Delete Shape

Permanently remove shape from memory.

REMOVESHAPE ID#

REMOVESHAPE SQUARE#             ' Delete and free memory

Note: Always remove shapes when done to free memory!

LOADIMAGE

Note: Images can be handled just as shapes

[inits]
    LET SPRITE# = LOADIMAGE("temp.bmp")
    SCREEN 12

    MOVESHAPE SPRITE#, 320, 240
    ROTATESHAPE SPRITE#, 45      ' Rotate 45
    SCALESHAPE SPRITE#, 2.0      ' Scale by 2

[main]
    SCREENLOCK ON
        DRAWSHAPE SPRITE#
    SCREENLOCK OFF
    SLEEP 3000
    REMOVESHAPE SPRITE#          ' Free mem
END

LOADIMAGE with url

LET SPRITE# = LOADIMAGE("https://example.com/sprite.png")
' → downloads "sprite.png" to root of your program
' → sprite$ works just as with normal file load

' to remove or move the downloaded file
SHELL("move sprite.png images\sprite.png")   ' move to subfolder
' or
FILEDELETE "sprite.png"                       ' just delete it

Drawing on Screen

Complete Examples

Example 1: Rotating Shapes

RotatingShapesDemo.bas

Example 2: Bouncing Ball

BouncingBall.bas

Performance Tips

  1. Always use SCREENLOCK for animation:

    WHILE 1
     ' Math and logic here
    
         SCREENLOCK ON
         ' All drawing here
         SCREENLOCK OFF
    
         ' Delay here
        SLEEP 16
    WEND
  2. Clear only what you need:

    ' Clear only draw area, not text
    COLOR 0, 0
    LINE (0, 50)-(640, 480), 0, BF
  3. LINE… BF is much faster than CLS

     ' Filling screen with LINE... BF is faster than CLS
     ' CLS works fine with console
     COLOR 0, 0
     LINE (0, 80)-(640, 480), 0, BF
  4. Remove unused shapes:

    REMOVESHAPE OLD_SHAPE#  ' Free memory
  5. Use RGB() once, store value:

    LET MY_COLOR# = RGB(128, 64, 200)
    ' Use MY_COLOR# multiple times
  6. Target 60 FPS with SLEEP 16:

    SLEEP 16  ' ~60 FPS (1000ms / 60 ≈ 16ms)

Image Support

LOADIMAGE Function

Load images as shapes that can be moved, rotated, and scaled.

Supported Formats

Format Transparency Notes
PNG Full alpha (0-255) Recommended for sprites
BMP None Legacy support

Usage

LET ID# = LOADIMAGE("filepath")

Parameters: - filepath - Path to image file (PNG or BMP)

Returns: - Shape ID string (use with MOVESHAPE, ROTATESHAPE, etc.)

Example

[inits]
    SCREEN 12

    REM Load PNG image with transparency
    LET SPRITE# = LOADIMAGE("player.png")

    REM Position and transform
    MOVESHAPE SPRITE#, 320, 240
    ROTATESHAPE SPRITE#, 45
    SCALESHAPE SPRITE#, 2.0

[main]
    REM Draw
    SCREENLOCK ON
        DRAWSHAPE SPRITE#
    SCREENLOCK OFF

    SLEEP 3000

    REM Cleanup when done
    REMOVESHAPE SPRITE#
END

PNG Transparency (Alpha Channel)

PNG images support full alpha transparency. Each pixel can have an alpha value from 0 to 255:

Alpha Value Result
255 Fully opaque (solid)
128 50% transparent
0 Fully transparent

Transparency is read directly from the PNG file - no color key needed.

Image Transformations

Once loaded, images work exactly like other shapes:

LET IMG# = LOADIMAGE("logo.png")

MOVESHAPE IMG#, x, y           ' Position (center point)
ROTATESHAPE IMG#, angle        ' Rotate (degrees)
SCALESHAPE IMG#, scale         ' Scale (1.0 = original size)
SHOWSHAPE IMG#                 ' Make visible
HIDESHAPE IMG#                 ' Make invisible
DRAWSHAPE IMG#                 ' Render to screen
REMOVESHAPE IMG#               ' Delete and free memory

Notes

Why Constants, Not Variables?

Sound IDs returned by LOADIMAGE and are handles assigned by SDL2. BazzBasic uses them only to point to the correct image resource — they never change during program execution.

Storing them in constants (# suffix) communicates this intent clearly:

LET PISTOL# = LOADIMAGE("pistol.png")
LET GAME_OVER# = LOADIMAGE("gameOver.png")

Using a mutable variable ($ suffix) would work, but it implies the value could change — which is misleading and leaves the door open for accidental reassignment bugs.

The rule of thumb: if a value is set once and never modified, it belongs in a constant.


When to Use an Array Instead

If your program loads many images at once, individual constants become verbose. In that case, a named array is cleaner and self-documenting:

DIM images$
    images$("shoot")        = LOADIMAGE("shoot.png")
    images$("explosion")    = LOADIMAGE("explosion.png")
    images$("pickup")       = LOADIMAGE("pickup.png")

For larger projects with categorized images, multidimensional string keys scale even better:

DIM images$
    images$("weapons", "shotgun")   = LOADIMAGE("shotgun.png")
    images$("weapons", "ak47")      = LOADIMAGE("ak47.png")
    images$("explosions", "small")  = LOADIMAGE("explosion_small.png")
    images$("explosions", "large")  = LOADIMAGE("explosion_large.png")

This keeps image assets organized by category and type — easy to extend without renaming anything.

Sound System

BazzBasic includes a comprehensive sound system built on SDL2_mixer, supporting audio playback with both background and blocking modes.

Overview

The sound system allows you to: - Load sound files in various formats (WAV, MP3, etc.) - Play sounds once or in a continuous loop - Control playback (stop individual sounds or all at once) - Choose between background playback or wait-for-completion modes

Supported Audio Formats

Sound Commands

LOADSOUND(filepath$)

Loads a sound file from disk and returns a unique sound ID string.

Returns: String - Unique identifier for the loaded sound

Syntax:

LET SOUND_ID# = LOADSOUND("path/to/sound.wav")
' or
LET SOUND_ID# = LOADSOUND("path\\to\\sound.wav")

Note: If you use “", you must use it as double”\” since it is also a escape character

Example:

LET EXPLOSION#
EXPLOSION# = LOADSOUND("C:\\sounds\\explosion.wav")
PRINT "Sound loaded with ID: "; EXPLOSION#

Notes: - The file must exist or an error will occur - Each call to LOADSOUND creates a new independent sound instance - The same file can be loaded multiple times for overlapping playback


SOUNDONCE(SOUND_ID#)

Plays a loaded sound once in the background. Program execution continues immediately.

Syntax:

SOUNDONCE(SOUND_ID#)

Example:

LET BEEP#
BEEP# = LOADSOUND("beep.wav")
SOUNDONCE(BEEP#)
PRINT "Sound playing in background..."
SLEEP 1000
PRINT "Continuing while sound plays..."

Use Cases: - Sound effects in games - UI feedback sounds - Background audio that shouldn’t block program flow


SOUNDONCEWAIT(SOUND_ID#)

Plays a sound once and waits for playback to complete before continuing program execution.

Syntax:

SOUNDONCEWAIT(SOUND_ID#)

Example:

LET INTRO#
INTRO# = LOADSOUND("intro_music.wav")
PRINT "Playing intro..."
SOUNDONCEWAIT(INTRO#)
PRINT "Intro finished, starting game!"

Use Cases: - Intro music or narration - Sequential audio playback - When timing with program flow is critical


SOUNDREPEAT(SOUND_ID#)

Plays a sound in a continuous loop in the background.

Syntax:

SOUNDREPEAT(SOUND_ID#)

Example:

LET BG_MUSIC#
BG_MUSIC# = LOADSOUND("background_music.wav")
SOUNDREPEAT(BG_MUSIC#)
PRINT "Music looping in background..."
REM Game loop runs here

Notes: - Sound loops seamlessly when it reaches the end - Use SOUNDSTOP to stop the loop - Multiple sounds can be looping simultaneously

Use Cases: - Background music - Ambient sounds (rain, wind, etc.) - Continuous sound effects


SOUNDSTOP(SOUND_ID#)

Stops playback of a specific sound.

Syntax:

SOUNDSTOP(SOUND_ID#)

Example:

LET ALARM#
ALARM# = LOADSOUND("alarm.wav")
SOUNDREPEAT(ALARM#)
PRINT "Alarm ringing..."
SLEEP 5000
SOUNDSTOP(ALARM#)
PRINT "Alarm stopped"

Notes: - Safe to call even if sound is not currently playing - Stops both looping and one-time playback


SOUNDSTOPALL

Stops all currently playing sounds at once.

Syntax:

SOUNDSTOPALL

Example:

REM Emergency stop all audio
SOUNDSTOPALL
PRINT "All sounds stopped"

Use Cases: - Game pause functionality - Switching between game states - Emergency audio cutoff - Cleanup before program exit


Complete Example: Game with Sound

REM ============================================
REM Simple Game with Sound Effects
REM ============================================

CLS
PRINT "Loading sounds..."

REM Load all game sounds
LET JUMP#, COIN#, DEATH#, BG_MUSIC#
JUMP# = LOADSOUND("C:\\game_sounds\\jump.wav")
COIN# = LOADSOUND("C:\\game_sounds\\coin.wav")
DEATH# = LOADSOUND("C:\\game_sounds\\death.wav")
BG_MUSIC# = LOADSOUND("C:\\game_sounds\\music.wav")

REM Start background music
SOUNDREPEAT(BG_MUSIC#)

REM Game variables
LET score$ = 0
LET playing$ = 1
LET coins_collected$ = 0

PRINT "Game started! Press SPACE to jump, ESC to quit"

REM Main game loop
WHILE playing$
    LET key$ = INKEY
    
    IF key$ = KEY_ESC# THEN
        playing$ = 0
    ENDIF
    
    IF key$ = KEY_SPACE# THEN
        SOUNDONCE(JUMP#)
        PRINT "Jump!"
    ENDIF
    
    REM Simulate collecting a coin
    IF RND(100) < 1 THEN
        coins_collected$ = coins_collected$ + 1
        score$ = score$ + 10
        SOUNDONCE(COIN#)
        PRINT "Coin collected! Score: "; score$
    ENDIF
    
    REM Simulate game over condition
    IF coins_collected$ >= 10 THEN
        playing$ = 0
    ENDIF
    
    SLEEP 50
WEND

REM Game over sequence
SOUNDSTOPALL
PRINT ""
PRINT "Game Over!"
PRINT "Final Score: "; score$
SOUNDONCEWAIT(DEATH#)
PRINT "Thanks for playing!"

END

Example: Multiple Background Sounds

REM Layer multiple ambient sounds

LET RAIN#, WIND#, THUNDER#
RAIN# = LOADSOUND("rain.wav")
WIND# = LOADSOUND("wind.wav")  
THUNDER# = LOADSOUND("thunder.wav")

REM Start ambient background
SOUNDREPEAT(RAIN#)
SOUNDREPEAT(WIND#)

PRINT "Storm ambience playing..."
SLEEP 5000

REM Add thunder effect
SOUNDONCE(THUNDER#)
SLEEP 3000

REM Stop all ambience
SOUNDSTOPALL
PRINT "Storm ended"

END

Example: Sequential Audio Narration

REM Play audio files in sequence

LET PART1#, PART2#, PART3#
PART1# = LOADSOUND("narration_part1.wav")
PART2# = LOADSOUND("narration_part2.wav")
PART3# = LOADSOUND("narration_part3.wav")

PRINT "Starting narration..."
SOUNDONCEWAIT(PART1#)
PRINT "Part 1 complete"

SOUNDONCEWAIT(PART2#)
PRINT "Part 2 complete"

SOUNDONCEWAIT(PART3#)
PRINT "Narration finished!"

END

Technical Details

Thread Safety

Memory Management

Performance Considerations

Looping Behavior

Error Handling

Best Practices

  1. Organize Your Sounds

    REM Load all sounds at startup
    LET SFX_JUMP#, SFX_COIN#, MUSIC_BG#
    SFX_JUMP# = LOADSOUND("sfx\\jump.wav")
    SFX_COIN# = LOADSOUND("sfx\\coin.wav")
    MUSIC_BG# = LOADSOUND("music\\background.wav")
  2. Use Appropriate Playback Modes

    • SOUNDONCE - Short sound effects
    • SOUNDREPEAT - Background music, ambient loops
    • SOUNDONCEWAIT - Sequential audio, narration
  3. Clean Up Audio

    REM Before program exit
    SOUNDSTOPALL
    END
  4. Handle Game States

    REM When pausing game
    SOUNDSTOPALL
    
    REM When resuming
    SOUNDREPEAT(BG_MUSIC#)

Why Constants, Not Variables?

Sound IDs returned by LOADSOUND are handles assigned by SDL2. BazzBasic uses them only to point to the correct audio resource — they never change during program execution.

Storing them in constants (# suffix) communicates this intent clearly:

LET SHOOT_SND# = LOADSOUND("shoot.wav")

Using a mutable variable ($ suffix) would work, but it implies the value could change — which is misleading and leaves the door open for accidental reassignment bugs.

The rule of thumb: if a value is set once and never modified, it belongs in a constant.


When to Use an Array Instead

If your program loads many sounds at once, individual constants become verbose. In that case, a named array is cleaner and self-documenting:

DIM sounds$
    sounds$("shoot")        = LOADSOUND("shoot.wav")
    sounds$("explosion")    = LOADSOUND("explosion.wav")
    sounds$("pickup")       = LOADSOUND("pickup.wav")

SOUNDOCE(sounds$("shoot"))

For larger projects with categorized sounds, multidimensional string keys scale even better:

DIM sounds$
    sounds$("shoot", "shotgun")     = LOADSOUND("shoot_shotgun.wav")
    sounds$("shoot", "ak47")        = LOADSOUND("shoot_ak47.wav")
    sounds$("explosion", "small")   = LOADSOUND("explosion_small.wav")
    sounds$("explosion", "large")   = LOADSOUND("explosion_large.wav")

SOUNDONCE(sounds$("shoot", "ak47"))

This keeps sound assets organized by category and type — easy to extend without renaming anything.

Network

Note: httpbin.org is a good test address — it returns JSON showing what you sent. HTTPPOST sends Content-Type: application/json by default.

HTTPGET

Allows you to send HTTP GET requests to a specified URL and retrieve the response as a string.

LET response$ = HTTPGET("https://httpbin.org/get")
PRINT response$

Output:

{
  "args": {},
  "headers": {
    "Host": "httpbin.org",
    "X-Amzn-Trace-Id": "Root=1-6999cff3-34d8f03c0661810224fe62db"
  },
  "origin": "84.231.65.52",
  "url": "https://httpbin.org/get"
}

HTTPPOST

Allows you to send HTTP POST requests to a specified URL and retrieve the response as a string.

LET postResult$ = HTTPPOST("https://httpbin.org/post", "{""key"":""value""}")
PRINT postResult$

Output;

{
  "args": {},
  "data": "{\"key\":\"value\"}",
  "files": {},
  "form": {},
  "headers": {
    "Content-Length": "15",
    "Content-Type": "application/json; charset=utf-8",
    "Host": "httpbin.org",
    "X-Amzn-Trace-Id": "Root=1-6999d03a-3b47996d66ac7d8e65200a1e"
  },
  "json": {
    "key": "value"
  },
  "origin": "84.231.65.52",
  "url": "https://httpbin.org/post"
}

HTTP Server (LISTEN)

BazzBasic can act as a small local HTTP server to receive POST requests from a browser-side HTML page or other tooling. The intended use is local-only glue: a static HTML page on the user’s machine talks to BazzBasic via fetch().

The server is bound to 127.0.0.1 only — no admin rights are needed on Windows, and the listener is never exposed to the network.

Commands

Command Description
STARTLISTEN port Open the given port. GETREQUEST() will block forever until a request arrives.
STARTLISTEN port, timeout_ms Same, but GETREQUEST() returns an empty string if no request arrives within the timeout.
GETREQUEST() Block until a request arrives, then return the POST/PUT/PATCH body or the GET query string (without the leading ?). Returns "" on timeout.
SENDRESPONSE str$ Send 200 OK with the given body, Content-Type: text/plain; charset=utf-8, and CORS headers. Must be called after each GETREQUEST() so the browser does not hang.
STOPLISTEN Close the port. Silent no-op if no listener is active.

Behaviour

Example: receive a JSON form post

STARTLISTEN 8080
PRINT "Listening on http://127.0.0.1:8080 ..."

LET json$ = GETREQUEST()
PRINT "Received: " + json$

' Parse the JSON into an array
DIM data$
LET data$ = ASARRAY(json$)
PRINT "Name: " + data$("name")

SENDRESPONSE "{""status"":""ok""}"
STOPLISTEN
END

Matching browser-side JavaScript:

fetch("http://127.0.0.1:8080", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ name: "Krisu", level: 99 })
})
.then(r => r.json())
.then(data => console.log(data));

Example: poll loop with timeout

STARTLISTEN 8080, 2000

LET running$ = 1
WHILE running$
    LET req$ = GETREQUEST()
    IF LEN(req$) = 0 THEN
        PRINT "Idle..."
    ELSE
        PRINT "Got: " + req$
        SENDRESPONSE "ok"
        IF req$ = "quit" THEN
            LET running$ = 0
        END IF
    END IF
WEND

STOPLISTEN
END

Notes & limitations

Preprocessor & Source Control

BazzBasic supports splitting your program across multiple files. This helps keep large programs organized and allows reusable code libraries.

INCLUDE

Include another BazzBasic source file. The contents of the included file are inserted at the exact point of the INCLUDE statement — as if you had written the code there directly.

INCLUDE "utils.bas"
INCLUDE "graphics_helpers.bas"
INCLUDE "MathLib.bb"    ' Compiled library

Usage Notes

Example

main.bas:

INCLUDE "helpers.bas"

LET result$ = FN Square$(5)
PRINT result$    ' Output: 25

helpers.bas:

DEF FN Square$(n$)
    RETURN n$ * n$
END DEF

BazzBasic reads these as a single program:

DEF FN Square$(n$)
    RETURN n$ * n$
END DEF

LET result$ = FN Square$(5)
PRINT result$    ' Output: 25

Compiled Libraries (.bb)

Library files contain only DEF FN functions and are compiled to a tokenized .bb format.

' Compile:  bazzbasic.exe -lib MathLib.bas  →  MathLib.bb
INCLUDE "MathLib.bb"

' Library functions are prefixed with FILENAME_
PRINT FN MATHLIB_Square$(5)    ' Output: 25

For larger programs, split code into focused files and INCLUDE them:

' main.bas
INCLUDE "constants.bas"
INCLUDE "functions.bas"
INCLUDE "inits.bas"

[main]
    GOSUB [sub:update]
    GOSUB [sub:draw]
    SLEEP 16
    GOTO [main]
END

INCLUDE "subs.bas"

See Also

BazzBasic Reserved Words

Complete list of all reserved keywords. All are case-insensitive.
Variables must end with $, constants with # — they cannot share names with keywords.


Control Flow

Keyword Description
DEF Begin function definition (DEF FN name$(...))
DIM Declare array
ELSE Alternative branch in IF block
ELSEIF Chained condition in IF block
END Terminate program; also END IF, END DEF
ENDIF Close IF block (alias for END IF)
FN Call user-defined function (FN name$(...))
FOR Begin counted loop
GOSUB Call subroutine at label
GOTO Jump to label
IF Conditional branch
INCLUDE Insert source file or library
LET Declare variable or constant
NEXT End FOR loop
PRINT Output to screen
REM Comment (also ')
RETURN Return from GOSUB or DEF FN
STEP Loop increment in FOR
THEN Required after IF condition
TO Range separator in FOR
WEND End WHILE loop
WHILE Begin conditional loop

I/O

Keyword Description
ARGCOUNT Returns amount or args. passed to program
ARGS Returns specific arg. passed to program
CLS Clear screen
COLOR Set text foreground/background color
CURPOS Read cursor row or col: CURPOS("row") / CURPOS("col")
GETCONSOLE Read character or color from console position
INPUT Read user input
LINE INPUT Read full line including spaces
LOCATE Move text cursor
SLEEP Pause execution (milliseconds)

Graphics

Keyword Description
CIRCLE Draw circle or filled circle
DRAWSHAPE Render a shape/image to screen
DRAWSTRING Render a text to screen
FULLSCREEN Toggle borderless fullscreen
HIDESHAPE Hide shape without removing
LINE Draw line, box outline, or filled box
LOADFONT Load font from file or reset to Arial
LOADIMAGE Load PNG/BMP (or URL) as shape
LOADSHEET Load sprite sheet into array
LOADSHAPE Create geometric shape
MOVESHAPE Set shape position
PAINT Flood fill
POINT Read pixel color
PSET Draw single pixel
REMOVESHAPE Delete shape and free memory
RGB Create color value from R, G, B components
ROTATESHAPE Rotate shape (absolute degrees)
SCALESHAPE Scale shape
SCREEN Initialize graphics window
SCREENLOCK Buffer drawing (ON) / present frame (OFF)
SHOWSHAPE Make hidden shape visible
VSYNC Enable/disable vertical sync

Sound

Keyword Description
LOADSOUND Load sound file, returns ID
SOUNDONCE Play sound once (non-blocking)
SOUNDONCEWAIT Play sound once (blocking)
SOUNDREPEAT Loop sound continuously
SOUNDSTOP Stop specific sound
SOUNDSTOPALL Stop all sounds

File

Keyword Description
FILEAPPEND Append string to file
FILEDELETE Delete a file
FILEEXISTS Returns 1 if file exists
FILEREAD Read file as string or key=value array
FILEWRITE Write string or array to file
SHELL Run system command, returns output

Network & Data

Keyword Description
ASARRAY Parse JSON string into array
ASJSON Convert array to JSON string
BASE64DECODE Decode Base64 string
BASE64ENCODE Encode string to Base64
HTTPGET HTTP GET request
HTTPPOST HTTP POST request
LOADJSON Load JSON file into array
SAVEJSON Save array as JSON file
SHA256 SHA256 hash (64-char hex)

Math Functions

Keyword Description
ABS Absolute value
ATAN Arc tangent
BETWEEN TRUE if value is within range
CEIL Round up
CINT Round to nearest integer
CLAMP Constrain value to range
COS Cosine (radians)
DEG Radians to degrees
DISTANCE 2D or 3D Euclidean distance
EULER Euler’s number (e = 2.71828…)
EXP e raised to power
FASTCOS Fast cosine (degrees, lookup table)
FASTRAD Fast degrees-to-radians
FASTSIN Fast sine (degrees, lookup table)
FASTTRIG Enable/disable fast trig lookup tables
FLOOR Round down
HPI π/2 (90°)
INT Truncate toward zero
LERP Linear interpolation
LOG Natural logarithm
MAX Higher of two values
MIN Lower of two values
MOD Remainder
PI π (3.14159…)
POW Power
QPI π/4 (45°)
RAD Degrees to radians
RND Random integer 0 to n-1
ROUND Standard rounding
SGN Sign (-1, 0, 1)
SIN Sine (radians)
SQR Square root
TAN Tangent (radians)
TAU 2π (360°)

String Functions

Keyword Description
ASC Character to ASCII code
CHR ASCII code to character
INSTR Find substring position
INVERT Reverse string
LCASE Convert to lowercase
LEFT First n characters
LEN String length (also array element count)
LTRIM Strip leading whitespace
MID Substring
REPEAT Repeat string n times
REPLACE Replace all occurrences
RIGHT Last n characters
RTRIM Strip trailing whitespace
SPLIT Split string into array
SRAND Random alphanumeric string
STR Number to string
TRIM Strip leading and trailing whitespace
UCASE Convert to uppercase
VAL String to number

Input & Mouse

Keyword Description
INKEY Non-blocking key check
KEYDOWN TRUE if key is held (graphics mode only)
MOUSEMIDDLE 1 if middle button pressed (graphics mode)
MOUSELEFT 1 if left button pressed (graphics mode)
MOUSERIGHT 1 if right button pressed (graphics mode)
MOUSEX Mouse cursor X position (graphics mode)
MOUSEY Mouse cursor Y position (graphics mode)
WAITKEY Block until key pressed

Arrays

Keyword Description
DELARRAY Remove entire array
DELKEY Remove one array element
HASKEY 1 if array element exists
JOIN Merge two arrays; src2$ overwrites src1$ on conflict
ROWCOUNT Return the size of array in first dimension

Time

Keyword Description
TICKS Milliseconds since program start
TIME Current date/time as formatted string

Logic

Keyword Description
AND Logical AND
FALSE 0
NOT Logical NOT
OFF Alias for FALSE (used with SCREENLOCK, VSYNC)
ON Alias for TRUE (used with SCREENLOCK, VSYNC)
OR Logical OR
TRUE 1

Built-in Constants

These are auto-initialized at startup — no LET required.

Constant Value
BBVER# BazzBasic version string (e.g. "1.1d")
PRG_ROOT# Program base directory path
TRUE 1
FALSE 0
PI# 3.14159265358979
HPI# 1.5707963267929895 (π/2)
QPI# 0.7853981633974483 (π/4)
TAU# 6.283185307179586 (2π)
EULER# 2.718281828459045 (e)

Keyboard constants

KEY_UP# KEY_DOWN# KEY_LEFT# KEY_RIGHT#
KEY_ESC# KEY_ENTER# KEY_SPACE# KEY_TAB# KEY_BACKSPACE#
KEY_INSERT# KEY_DELETE# KEY_HOME# KEY_END# KEY_PGUP# KEY_PGDN#
KEY_F1#KEY_F12#
KEY_A#KEY_Z#
KEY_0#KEY_9#
KEY_NUMPAD0#KEY_NUMPAD9#
KEY_LSHIFT# KEY_RSHIFT# KEY_LCTRL# KEY_RCTRL# KEY_LALT# KEY_RALT# KEY_LWIN# KEY_RWIN#
KEY_COMMA# KEY_DOT# KEY_MINUS# KEY_EQUALS# KEY_SLASH# KEY_BACKSLASH# KEY_GRAVE# KEY_LBRACKET# KEY_RBRACKET# KEY_SEP#