batari Basic Command Reference
 By Fred "batari" Quimby (adapted by Duane Alan Hahn)

This document is intended as an offline version of the bB 1.0 Command Reference at Duane Alan Hahn's web site. Visit the official batari Basic site for the latest bB news, tutorials, downloads and more.

Things You Should Know

    Memory

    The Atari 2600's microprocessor can only address 4K of ROM at a time, and the console has only has 128 bytes of RAM. Of this RAM, 26 bytes are available for general use in your programs, as variables a-z. It may not sound like much, and it isn't, but many great games have been written despite these limitations.

    Some ways to mitigate the above limitations are to use bank switching and/or Superchip RAM. Bank switching allows programs up to 32K in size, and the Superchip gives us another 128 bytes of RAM. There are drawbacks to both, however.

    See Bank Switching and/or Superchip for more information.

    Timing

    Timing is crucial in batari Basic, in that you only have about 2 milliseconds between successive calls to drawscreen. See drawscreen and vblank for more information.

    White Space

    It is recommended that you include white space in your program. Blank lines can help to create a physical separation of some code, and some space between commands and tokens can help readability. Earlier versions of bB had trouble with white space, but this problem should be resolved now.

    Some of us (myself included) do not leave much, if any, white space between commands, and bB can still parse most code correctly, regardless. For example, the statement:

     for l=1 to 10:t=t+4:next
    

    is acceptable. Also, the following would be parsed the same way:

     for l = 1 to 10 : t = t + 4 : next
    for l =1 to 10:t=t +4: next
    for l=1 to 10 : t= t+4 :next

    The following would not:

     forl=1to10:t=t+4:next
     forl=1 to 10:t=t+4:next
     for l=1 to10 :t=t+4:next
    

    In other words, any keywords or commands must be spaced properly or batari Basic will think they are variables and compilation will fail, but anything else is fair game. As long as there is a recognizable separator, such as +, -, =, :, *, /, &, &&, |, ||, ^ and possibly others, you can space however you want (or not at all).

    DOS Compatibility

    Although batari Basic is a command-line program, you are expected to run it under Windows 95 or later because it requires a DPMI (DOS protected mode interface) and uses long filenames. If you wish to run under pure DOS, however, you can, but you will need to:

    • obtain a DPMI program, such as cwsdpmi.exe, and run this before running batari Basic.
    • rename all filenames to 8.3 format
    • edit includes files to point to renamed files above
    • use the command-line parameter -r to specify an alternate variable redefinition file that conforms to 8.3.
Back to Top

Miscellaneous Commands

    rem (short for Remark)

    The rem statement is used for in-program comments. These comments are very helpful not only to other programmers trying to make sense of your code, but to yourself if your memory is anything like mine :)

    Note that, unlike old interpreted Basics, you can use rem as much as you want and it will not affect the length of your compiled program.

    Example:

     rem *****************************************************
     rem *  Name: Space Nuggets
     rem *  Version: 0001-2007y02m14d
     rem *
     rem *****************************************************
     rem *  Programmer: Scadoobie Floinkenburger
     rem *  Started: 2007y - 02m - 14d
     rem *  Language: batari Basic v1.0
     rem *  System: Atari 2600 VCS
     rem *****************************************************
    
    reboot

    This command will warm boot your game. reboot will clear everything similar to turning the Atari 2600 off and back on again, so do not use it if you need to store things such as the highest level reached, high score, current game selections and so on.

    Example:

     if switchreset then reboot
    
Back to Top

Variables

You have 26 general purpose variables in batari Basic, fixed as a-z. Although they are fixed, you can use the dim command to map an alias to any of these variables.

26 variables is not a lot, so you will use them up quickly. Therefore, it's recommended that you use the bit operations to access single bits when using variables for flags or game state information wherever possible.

If you do run out of variables, you can use four bytes from the playfield if you're not scrolling it. You can also use temp1-temp6 as temporary storage, but these are obliterated when drawscreen is run, and some are used for playfield operations as well, so use these at your own risk.

Although there might be unused bytes in the stack space, it is not recommended that you use these in your program since later versions of batari Basic will probably use these for something else.

    let (optional)

    The let statement is optional, and is used for variable assignment. It was left in because an early unreleased version of batari Basic required it. If you wish to use it, it will not affect program length—it will simply be ignored by the compiler.

    Example:

     let x = x + 1
    
    dim (create descriptive names for variables/assign names to fixed point types)

    Used to create more descriptive names for the 26 variables, or to create a fixed-point type and assign it to some of these variables.

      Using dim to Create More Descriptive Names

      Unlike other Basics, the most common use of the dim statement is not for arrays in batari Basic, but rather for creating an alias, a more descriptive name for each variable than a-z. The statement simply maps a descriptive name to any of the original 26 variables.

      Although dim is typically called at the beginning of the program, for creating a variable alias, it can actually be called at any time, and is applicable to the entire program no matter where it is placed (this is not true for creating fixed point variables). The first character of an alias must be one of the 26 letters of the alphabet.

      Examples:

       dim monsterxpos=a
       dim monsterypos=b
      

      Note that more than one alias may be mapped to the same variable. This is useful for when you will inevitably need to reuse variables in multiple places.

      Using dim to Map Variables to Fixed Point Types

      Although you can use the variables a-z as integers without ever using dim, you cannot use a-z as fixed point variables without using dim. See fixed point variables for more information.

    Bit Operations (squeeze more out of your variables)

    On modern systems, you may not think twice of using an entire byte or even a word for every flag. For example, to determine whether a game is in progress or it is over, often an entire byte is used even though its only value is 0 or 1.

    Since the Atari 2600 only has 128 bytes of RAM, and batari Basic only has 26 bytes available for variables, it is very likely that you will need to use individual bits for game state information. For example, a common flag is to determine whether a game is over or still in progress.

    The bits of a byte are numbered from 0 to 7, with 0 being the least significant bit (LSB) or smallest.

    For example, to access the LSB in a variable or register:

     a{0} = 1
     a{0} = 0
     if !a{0} then gameover
    

    You may also assign one bit to another bit. For example:

     d{3}=r{4}
     f{5}=!f{5}
    

    Accessing the bits of a variable is almost like turning it into 8 separate variables. Instead of having only 26 variables, you potentially have 208. You just have to remember that these itty-bitty variables can only hold 0 or 1.

      Bitwise (Logical) Operators (&, |, ^)

      Batari Basic has three operators for logical operations. They are tokenized as:

      & = AND

      | = OR (Note: the "|" key is usually above the backslash: "\")

      ^ = XOR (exclusive OR)

      These can be used to change the state of individual bits or to mask multiple bits.

      Examples:

       a = a & $0F
       a = b ^ %00110000
       a = a | 1
      
    Fixed Point Variables

    Fixed point variables can be assigned to fractional values, similar to floating point variables in other languages. bB provides two fixed point types:

    • a two-byte variable, with one byte as the integer portion and one as the fractional portion. This is referred to as an 8.8 type. These can range from 0 to 255, with a fractional portion that is accurate to within 1/256 or 0.00390625. This type can also be considered as signed in the same way an integer variable is.

    • a one-byte variable, with four bits as the integer and four bits as the fraction. This is referred to as a 4.4 type. This type ranges from -8 to 7.9375, and is accurate to within 1/16 or 0.0625. In other words, this type is always signed.


    To declare these special types, you must use the dim statement. To declare an 8.8 type, you must specify the integer and fractional portion by the following:

     dim myvar=a.b
     dim monsterx=d.r
    

    The first will use the variable a as the integer and b as the fraction, the second will use d as the integer and r as the fraction. Then any time you use myvar or monsterx in a variable assignment, the compiler will know to use the 8.8 fixed point type.

    To declare a 4.4 type, you use a similar syntax as the above, but you use the same variable name for the integer and fraction:

     dim xvelocity=c.c
     dim yvelocity=d.d
    

    After this dim, using xvelocity will tell the compiler to use the 4.4 type.

    You may use some fixed-point operations without any changes to your program, but some will require you to include the fixed point module. If you need to include the fixed point module, place this somewhere near the beginning of your program:

     include fixed_point_math.asm
    

    Alternatively, you may modify the includes file to include it automatically. See include or includes file for more information.

    In a 2600 game, the 8.8 types are particularly useful for specifying coordinates. The 4.4 types are useful for specifying velocity without using the extra bytes needed in 8.8 types.

    This subpixel movement, or movement of a non-integer number of pixels for each frame, allows more flexibility in gameplay mechanics than ordinary integer variables provide, or at least it simplifies the calculations that would otherwise be required using only integers.

    The 8.8 type can be used interchangeably anywhere an integer would normally be used. If this is done, for example by assigning an 8.8 to player0x, the fractional portion of the number will be ignored, just like the int() function in other BASIC dialects. The 4.4 types, however, cannot be used anywhere — they can only be added, subtracted or assigned to/from themselves, integers or 8.8 types. Well, that's not totally true. If you use a 4.4 type in some way other than an assignment, addition or subtraction, its value will be multiplied by 16.

    Also, note that fixed point types are subject to the same limitations in if-then statements. Although you may compare two 4.4 types in an if-then, if you compare a 4.4 with a number or another type, the 4.4 will be multiplied by 16. If you use an 8.8 type in an if-then, the fractional portion will be ignored.

    If you want to use just the fractional portion in an if-then, this can be done by accessing the variable assigned to the fraction. . . In the examples above this would be b or r. Note that if you access b or r directly, the fractional portion will be multiplied by 256.

    Multiplication and division of fixed point types is subject to the same limitations above.

    Some valid operations using fixed point types, that is, ones that are not subject to limitations are listed below. This is not a complete list. Those denoted with a (*) require that the fixed_point_math.asm module is included, those without a (*) do not.

    Note: assume that my44 is a 4.4 type, myint is an integer, and my88 is an 8.8:

     my88=12.662
     my44=4.67
     my88=-12.662
     my44=-4.67
     my88=myint
     my44=myint
     myint=my44
     myint=my88
     my88=my44 (*)
     my44=my88 (*)
     my88=my88+1.45
     my44=my44+2.55
     my88=my88-1.45 
     my44=my44-2.55
     my88=my44+6.45 (*)
     my44=my88+3.45 (*)
     my88=my44-6.45 (*)
     my44=my88-3.45 (*)
     my88=my88+my88
     my44=my44+my44
     my88=my88-my88
     my44=my44-my44
     my88=my44+my88 (*)
     my44=my88+my44 (*)
     my88=my44-my88 (*)
     my44=my88-my44 (*)
    

    In other words, if you mix 4.4 and 8.8 types in a statement, you need to include the fixed_point_math.asm module.

    Back to Top
    Ephemeral Variables and Registers (short-lived)

    By ephemeral, we mean variables or TIA registers that are routinely obliterated through normal functioning of bB. Note that this section is not yet complete.

    The temp variables, temp1-temp6, are always obliterated by the kernel. Also, some bB internal functions use them, such as those for playfield plotting or scrolling. User functions also pass values by way of these variables. Other than that, the temp variables are safe to use for all of your intermediate calculations.

    Some early versions of bB obliterated certain system variables such as the x and y positions of sprites. Although this is no longer true, this old requirement seems to have rubbed off on newer games, and in turn, programmers are wasting valuable variables to mirror system variables.

    Again, all player, missile and ball x, y and height variables will keep their values unless changed by the programmer.

    In addition, the standard kernel will maintain sprite definitions and so these can be placed outside of the game loop. The multisprite kernel, however, will not maintain definitions for player 0, so this needs to be defined within the game loop.

    TIA registers may be ephemeral, in that the TIA is constantly updated throughout the visible screen so you need to stay on top of most of them. But some TIA registers are persistent, meaning that you can set them once and they will remain in effect throughout your game. Some are obliterated every frame and therefore must be set before each drawscreen. Knowing which are safe to set and once or which need to be redefined every frame will help you write more efficient programs.

    The following are persistent:
    AUDV0
    AUDV1
    AUDC0
    AUDC1
    AUDF0
    AUDF1
    COLUBK (unless background kernel option set)
    COLUPF (unless pfscorecolor variables used and/or pfcolors kernel option set)
    CTRLPF

    The following are obliterated, but values are predictable and don't necessarily need resetting. The TIA register and its value after a drawscreen are noted below:

    TIA reg
    value after drawscreen
    COLUP0 same as scorecolor
    COLUP1 same as scorecolor
    NUSIZ0 0
    NUSIZ1 0
    REFP0 0
    REFP1 0
    PF0 0


    Note that the multisprite kernel uses special variables for its virtual sprites 1-5 that resemble TIA regs, but are not TIA regs themselves (they point to 2600's RAM.) Therefore the values _COLUP1, COLUP2-COLUP5, _NUSIZ1, and NUSIZ2-NUSIZ5 are all persistent. Also note that bit 3 of _NUSIZ1 and NUSIZ2-NUSIZ5 contains the REFP value.

    Constants (variables with a fixed value)

    To declare a constant in batari Basic, use the const command. const declares a constant value for use within a program. This improves readability in a program in the case where a value is used several times but will not change, or you want to try different values in a program but don't want to change your code in several places first.

    For example, you might have the following near the beginning of your program:

     const myconst=200
     const monsterheight=$12
    

    After that, any time myconst or monsterheight is used, the compiler will substitute 200 or $12 respectively.

Back to Top

Labels and Line Numbers

batari Basic supports alphanumeric labels. You may use line numbers if you prefer. Some old-school programmers like line numbers, or at least use them as a matter of comfort since they were necessary in early Basics. In any case, labels and line numbers are optional. Typically you will only need them when you want to specify a goto or gosub target.

Labels, line numbers, and end must not be indented, while all program statements must be. You may use labels with or without program statements after them. A label can have any combination of letters, numbers, or underscores, even as the first character. A label must not match or begin with a known keyword or any labels internal to bB (like end, kernel, and so on). For example, you cannot name a label next or pfpixel and although you could not use 'scorechange' you could use 'changescore'.

Example:

10 pfpixel 2 3 on
20 drawscreen
 player0x=player0x+1:goto mylocation
 player0y=29:goto mylocation2
mylocation
 x=x+1
mylocation2 x=x-1
Back to Top

Jumping Around

    goto

    The goto statement is used to jump to a line number or label anywhere in your program.

    Examples:

     goto 100
     goto mysubroutine
    

    In bank-switched games, if you are jumping to a routine in another bank, you must specify the bank.

    Example:

     goto movemonster bank2
    
    on … goto

    This works similar to a case statement in other languages. It is useful for replacing multiple if-then statements when conditions happen in an ordinal fashion.

    For example:

     on x goto 100 200 300 400
    

    is the same as:

     if x=0 then 100
     if x=1 then 200
     if x=2 then 300
     if x=3 then 400
    

    In bank-switched games, on ... goto can only jump within the current bank.

    Warning: There is no checking to see if your variable's value coincides with a label. In the above example, there is no label for x=4, but the statement will try to find a label for it anyway, which almost always means that your program will crash.

    gosub

    The gosub statement is often used for a subroutine that is called by multiple locations throughout your program.

    Examples:

     gosub 100
     gosub mysubroutine
     if x > 10 then gosub sinkship
    

    To return control back to the main program, issue a return in your subroutine. Example:

    mysubroutine
     a = a - 1
     x = x + 10
     return
    

    Note that each gosub will use two bytes of stack space, which will be recovered after a return. Only 6 bytes of stack space are reserved for this, so do not use too many nested subroutines, especially since this may be changed in later versions.

    In bank-switched games, if you are jumping to a subroutine in another bank, you must specify the bank.

    Example:

     gosub movemonster bank2
    
    return

    The return statement is used in a subroutine to return to the part of the program right after a gosub which called the subroutine.

    Be careful when using return. If a running program encounters one without a gosub that called it, the program will crash or strange things may happen. return can also be used to return a value for a function. See functions for more information.

    If using bank switching, there is some additional overhead with every return statement. Although the standard return by itself will automatically return to whatever bank called the subroutine, it takes up ROM space and cycles to automatically return to the proper bank. Instead, you may use thisbank or otherbank after your return:

      return thisbank
      Returns only to the current bank. Use this whenever possible since it is much faster. The program will crash, however, if you called the subroutine from another bank, so be careful.

      return otherbank
      Can be used anywhere, just like return. However, this one is faster for returning to other banks and slower for returns within the same bank.

      pop

      Cancels the return address from a subroutine. This essentially makes the last gosub command work like it was a goto.

Back to Top

Decision Making — Brains of a Game

    if-then

    Perhaps the most important statement is the if-then statement. These can divert the flow of your program based on a condition.

    The basic syntax is:

    if condition then action

    Action can be a statement, label or line number if you prefer. If the condition is true, then the statement will be executed. Specifying a line number or label will jump there if the condition is true. Put into numerical terms, the result of any comparison that equals a zero is false, with all other numbers being true.

    Note that in specific cases, assembly of if-then statements with a line number or label as the target will fail. If this happens, the assembler will report a "branch out of range." One way to fix this is to change the offending if-then statement to if-then goto, or you can let the compiler fix the problem for you by turning on smart branching.

    To do this, use the following:

     set smartbranching on
    

    Place it near the beginning of your program. Be aware that turning on smartbranching will slightly obfuscate the generated assembly file, so do not use it if you plan to examine the assembly later to see how it works. See smartbranching for more information.

    There are three types of if-then statements. The first is a simple check where the condition is a single statement.

    For example:

     if a then 20
    

    diverts program flow to line 20 if a is anything except zero.

    This type of if-then statement is more often used for checking the state of various read registers. For example, the joysticks, console switches, single bits and hardware collisions are all checked this way.

    For example:

     if joy0up then x = x + 1
    

    That will add 1 to x if the left joystick is pushed up.

     if switchreset then 200
    

    Jumps to line 200 if the reset switch on the console is set.

     if collision(player1,playfield) then t=1
    

    Sets t to 1 if player1 collides with the playfield.

     if !a{3} then a{4}=1
    

    Sets bit 4 of a if bit 3 of a is zero. See Bit Operations for more information about using single bits.

    A second type of statement includes a simple comparison. Valid comparisons are = , < , >, <=, >=, and <>.

    Examples:

     if a < 2 then 50
     if f = g then f = f + 1
     if r <> e then r = e
    

    The third type of if-then is a complex or compound statement, that is, one containing a boolean && (AND) or || (OR) operator. You are allowed one || for each if-then. You can use more than one && in a line, but you cannot mix && and ||.

    For example:

     if x < 10 && x > 2 then b = b - 1
     if !joy0up && gameover = 0 then 200
     if x = 5 || x = 6 then x = x - 4
    

    Warning: Using multiple if-thens in a single line might not work correctly if you are using boolean operators.

    Boolean Operators

    Boolean operators are used as conditions in if-then statements. They are tokenized as:

    && = AND


    || = OR


    ! = NOT

    You may use && or || in a statement, but you cannot mix them. You can use only one || but you may use more than one && if you wish. The NOT ( ! ) operator may only be used with statements that do not include a comparison token (such as =, <, >, or <>).

    For example:

     if a < 31 && a > 0 then 50
     if a = 2 || a = 4 then a = a + 1
     if !joy0up then 200
    

    Warning: If you use || in an if-then statement, the statement must be located at the beginning of a line. At this time, compilation will succeed but your program will probably not work correctly. This issue will probably be fixed in a later version.

    else

    You may use else after an if-then statement. An if-then will check if a condition is true and divert program flow, but an else allows you to also divert program flow in a different way if the condition turns out to be false.

    An else must be on the same line as the if-then that it belongs to. You can include statements separated by colons before the else, but the else must not come after a colon itself.

    For example:

     if r=2 then 20 else 30
     if a>b then r=2:pfpixel 3 5 on: d=d-1 else d=d+1:r=3
     if a>b then 20 else c[4]=12
    

    Warning: The else keyword might not work correctly in a statement containing &&. The else keyword may also not work as expected when there is more than one if-then on a single line.

Back to Top

Loops

A common form of a loop is a for-next loop, but a loop in general is any program that repeats. In this sense, all batari Basic programs must be loops, in that the programs never exit.

In batari Basic, you should limit your use of loops that do not include the drawscreen function somewhere. Too many loops take time which is somewhat limited. See drawscreen for more information.

    for-next

    For-next loops work similar to the way they work in other Basics.

    The syntax is:

    for variable = value1 to value2 [step value3]

    Variable is any variable, and value1, 2, and 3 can be variables or numbers. You may also specify a negative step for value3.

    The step keyword is optional. Omitting it will default the step to 1.

    Examples:

     for x = 1 to 10
     for a = b to c step d
     for l = player0y to 0 step -1
    
    next

    Normally, you would place a variable after the next keyword, but batari Basic ignores the keyword and instead finds the nearest for and jumps back there. Therefore, the usual way to call next is without a variable. If any variable is specified after a next, it will be ignored.

    Example:

     for x = 1 to 10: a[x] = x : next
    

    It is also important to note that the next doesn't care about the program flow — it will instead find the nearest for based on distance.

    For example:

     for x = 1 to 20
     goto 100
     for g = 2 to 49
    100 next
    

    The next above WILL NOT jump back to the first for, instead it will jump to the nearest one, even if this statement has never been executed. Therefore, you should be very careful when using next.

Back to Top

Random Numbers

    rand

    rand is a special variable that will implicitly call an 8-bit random number generator when used.

    The rand function returns a psuedo-random number between 1 and 255 every time it is called. You typically call this function by something like this:

     a = rand
    

    However, you can also use it in an if-then statement:

     if rand < 32 then r = r + 1
    

    You can also assign the value of rand to something else, at least until it is accessed again. The only reason you would ever want to do this is to seed the randomizer. If you do this, pay careful attention to the value you store there, since storing a zero in rand will "break" it such that all subsequent reads will also be zero!

    The standard random number generator should be random enough for most games. However, if it isn't random enough, you can optionally specify a 16-bit random number generator. This requires that you set aside one variable, and you can't use this variable for other purposes. To use the improved routine, place:

     dim rand16=<var>
    

    at the beginning of your program. <var> is one of your 26 variables (a-z.) Then you use rand as you normally would.

    In bank-switched games, rand may slow down your game if you use it often, as it is typically necessary to switch banks every time it is called. You can get around this limitation using one of the optimization options (see optimization under compiler directives.)

Back to Top

Data Statements and Arrays (read-only)

For convenience, you may specify a list of values that will essentially create a read-only array in ROM. You create these lists of values as data tables using the data statement. Although the data statement is similar in its method of operation as in other Basic languages, there are some important differences. Most notably, access to the data does not need to be linear as with the read function in other Basics, and the size is limited to 256 bytes.

If you prefer to use a data statement similar to that in other Basics and don't want to be limited to 256 bytes, see Sequential Data below.

In a regular data statement, any element of the data statement can be accessed at any time. In this vein, it operates like an array. To declare a set of data, use data at the beginning of a line, then include an identifier after the statement. The actual data is included after a linefeed and can continue for a number of lines before being terminated by end. Suppose you declare a data statement as follows, with array name mydata:

 data mydata
 200, 43, 33, 93, 255, 54, 22
end

To access the elements of the data table, simply index it like you would an array in RAM. For example, mydata[0] is 200, mydata[1] is 43, ... and mydata[6] is 22. The maximum size for a data table is 256 elements. Note that there is no checking to see if you have accessed values beyond the table. Doing so will not cause any errors, but the values you get probably won't be very useful.

To help prevent this from happening, a constant is defined with every data statement — this constant contains the length, or the number of elements, in the data. The constant will have the same name as the name of the data statement, but it will have _length appended to it.

For example, if you declare:

 data mydata
 1,2,3,4,5,6,7,8,9 
end

you can then access the length of the data with mydata_length. You can assign this to variables or use anywhere else you would use a number.

Example:

 a = mydata_length

Note again that these data tables are in ROM. Attempting to write values to data tables with commands like mydata[1]=200 will compile but will perform no function.

    Sequential Data (or large data)

    The sdata statement will define a set of data that is accessed more like the data in other Basics. The 256-byte limitation is also removed — the effective length is only limited by the size of each bank (4K). Sequential data is useful for things like music or a large set of scrolled playfield data. There is also no need to specify a pointer into the data.

    One drawback, however, is each sequential data statement requires two adjacent variables from normal 2600's RAM (not Superchip) be set aside.

    To define the set of data, use:

    sdata <name>=<variable>

    <name> is the name you wish to call it when it is read, and <variable> is the first variable name you are setting aside. Although you just specify one variable, the next variable in sequence will also be used.

    Example:

     sdata mymusic=a
     1,2,3,4,5,6,7,255
    end
    

    The above will set up a sequential data table called mymusic that uses variables a and b to remember the element at which it is currently pointing.

    Unlike regular data statements, the program must actually run over the sdata statement for it to be properly initialized to the beginning of the data table. Also, it must typically be defined outside the game loop because each time the program runs over the statement, it will be reinitialized to the beginning of the table.

    To read the data, use the sread function. It works somewhat similar to a regular bB function. Example:

     t = sread(mymusic)
    

    This will get the next value in the data table mymusic and place it in t.

    Note that unlike other Basics, there is no error or other indication when reaching the end of data. Since there is no data length variable defined with sequential data, it's usually necessary to place a terminal value at the end of your data. In the above example, 255 was used. In the above example you would then check t for 255.

    There is no restore function like other basics. However, if you allow your program to run over the sdata statement again, it will be initialized to the beginning of data just like the restore function.

    Note that all data tables, sequential or otherwise, must be located in the same bank where they are read. If you try to access data that lives in another bank, there will be no errors but the data you get will certainly be incorrect.

Back to Top

Colors

The Atari 2600 can display up to 128 unique colors — that is, 16 unique hues and 8 luminosity levels for each hue for NTSC consoles (see tv directive.) PAL consoles can display 104 unique colors (13 hues with 8 luminosity levels.)

Each player object can be colored independently, as can the playfield, background, score, and objects drawn in a minikernel. Also, you can make multicolored objects by using kernel options. The missiles, however, must share their colors with their respective player objects, and the ball will generally be the same color as the playfield.

    COLUBK (Color-Luminosity Background)

    Sets the background color. Example: COLUBK = 112

    COLUPF (Color-Luminosity Playfield, Ball)

    Sets the playfield color and ball color. Example: COLUPF = 154

    COLUP0 (Color-Luminosity Player 0, Missile 0)

    Sets the color for player 0 and missile 0. Example: COLUP0 = 42

    COLUP1 (Color-Luminosity Player 1, Missile 1)

    Sets the color for player 1 and missile 1. Example: COLUP1 = 222

    Atari 2600 NTSC TIA Color Chart (Decimal Numbers)

    Below is a color chart to make it easier to select colors for your games. I included colors from the two most popular emulators. It seems like Stella is closer to the real colors, but you may not agree. If you need PAL colors, check out Glenn Saunder's HTML TIA color chart.

       # Stella z26    # Stella z26    # Stella z26    # Stella z26
    0  64  128  192 
    2  66  130  194 
    4  68  132  196 
    6  70  134  198 
    8  72  136  200 
    10  74  138  202 
    12  76  140  204 
    14  78  142  206 
    16  80  144  208 
    18  82  146  210 
    20  84  148  212 
    22  86  150  214 
    24  88  152  216 
    26  90  154  218 
    28  92  156  220 
    30  94  158  222 
    32  96  160  224 
    34  98  162  226 
    36  100  164  228 
    38  102  166  230 
    40  104  168  232 
    42  106  170  234 
    44  108  172  236 
    46  110  174  238 
    48  112  176  240 
    50  114  178  242 
    52  116  180  244 
    54  118  182  246 
    56  120  184  248 
    58  122  186  250 
    60  124  188  252 
    62  126  190  254 
Back to Top

Objects

    Player Graphics

    The Atari 2600 can display two player sprites, which are 8 pixels wide and any height. You access these sprites by using various reserved values and commands. To define a sprite, you use player0: and player1:

    Example:

     player0:
     %00100010
     %01110111
     %01111111
    end
    

    This will define a player0 sprite which is 3 blocks in height.

    The multisprite kernel allows you to define sprites 2-5 as well. Keep in mind that sprites 2-5 are actually virtual sprites, which are created by repositioning player1 several times during the visible screen.

    Note that the bytes that make up a sprite are upside down in your code. Sprites will be flipped in the game. If you have trouble visualizing, you can try Kirk Israel's Playerpal or the Sprite Editor in attendo's batari Basic IDE.

    To display the objects, you must first set the colors with COLUP0 and COLUP1. They are black by default, which will not display against a black background.

    To set the coordinates, you set player0x, player0y, player1x, or player1y. On the screen, player0x and player1x values between 0 and 159 are useful, as they represent the extreme left and right edges of the screen. You can specify numbers larger than 159 but you may see some jumping at the edges of the screen. Values of player0y and player1y between 0 and about 88 are useful. Others will simply cause the player to move off of the screen.

    Missiles

    Batari Basic can display two missiles on the screen. These are simply vertical lines of a height you specify, and at coordinates you specify. The missiles are the same color as their respective players.

    To access missiles, you can set missile0x, missile0y, and missile0height for missile 0, and missile1x, missile1y, and missile1height for missile 1.

    In the multisprite kernel, the missiles are fixed at one unit high, and the missile height variables do not exist.

    There are more things you can do with missiles by modifying the NUSIZx TIA registers. See TIA Registers for more information.

    Ball

    The ball is one of the objects that the Atari 2600 can display in the screen.

    The ball is the same color as the playfield. It is accessed by ballx, bally, and ballheight, much like accessing the missiles.

    In the multisprite kernel, the ball is fixed at one unit high, and the ballheight variable does not exist.

    You can do a few more things with the ball by changing the CTRLPF TIA register. See TIA Registers for more information.

Back to Top

Collision Detection

    if collision (object,object)

    This function is used for checking if two objects have collided on the screen. Valid arguments are playfield, ball, player0, player1, missile0, missile1. The two objects can be specified in any order.

    The collision() function is only valid when used in an if-then statement.

    Note that the collision detection is hardware-based and is pixel-perfect. The collision detection may or may not be suitable for your game.

    Note that in the multisprite kernel, you can define objects player2 through player5. However, these are not valid arguments in the collision statement because the Atari 2600 cannot actually display 5 completely independent player objects. Player2-5 are virtual sprites, drawn by repositioning player1 several times during the visible screen. Therefore, a collision with player1 in the multisprite kernel means that you have hit one or more virtual players, and you must do further checks, for example, the y-positions, to determine which one.

    Examples:

     if collision(playfield,player0) then a = a + 1
     if !collision(player0,missile1) then 400
    
Back to Top

The Playfield

In the standard kernel, by default, you get a 32 x 11 bitmapped, asymmetric playfield (32 x 12 if you count the hidden row that is only seen if scrolled). The vertical playfield resolution is no longer fixed — it can be varied to some extent. See below for an explanation.

The multisprite kernel gives you a 16-wide playfield, which is mirrored about the center of the screen. Plotting commands such as pfpixel, pfvline and pfhline do not work in this kernel. However, pfread does work if you include a special module. Horizontal scrolling is not possible, but support for vertical scrolling is planned.

The playfield resides in the full vertical length of the screen except for the area reserved for the score. Horizontally, the playfield only uses the center 80% of the screen due to timing constraints. You have only limited access to left or right 10% of the screen, namely you can only draw vertical lines there, and they take the full length of the screen. For example, you can put a vertical border around the drawable portion of the playfield by PF0=128. You can use COLUBK to set the background color and COLUPF to set the playfield color. See the color chart for available colors.

You can specify an entire playfield in one command by using the playfield: command. The syntax of the command is:
 playfield: <off> <on>
 (pixels)
end

<off> and <on> are optional. They allow you to change the off/on symbols for the playfield pixels. Default:. and X example:

 playfield:
 X.X...X..XX..X.XX.X....XX..X.X..
 X.X....XX..X.X..X.X...X..XX..X.X
end

You can specify as many lines high as you want, but some may not display if you specify too many.

In the standard kernel, use 32 characters for each line.

In the multisprite kernel, the above only accepts 16 characters wide, and these 16 are reflected on the right.

Please see pfpixel, pfvline, pfhline, and pfscroll for more information about drawing to the playfield.

The playfield in the standard kernel is no longer fixed at 11+1 pixels high. It can be varied using the pfres setting. The default value for the vertical resolution is 12. If you wish, you can set pfres to values from 3-11. This will shrink the vertical resolution of the playfield and sometimes the visible screen as well. Doing so has advantages, such as freeing up space for more variables and possibly giving more time for each frame in your bB code or minikernels.

You set the value using const. Example:

 const pfres=10

The playfield uses 4 bytes for each row, so setting the height to 10 would free up 8 bytes that could then be used as variables. These variables are available as var0-var7.

Also, note that reducing the number of rows may or may not shrink the size of your screen. bB tries to fill as much of the screen as possible by varying the height of the pixel rows. The default row heights and free variables corresponding to the overall playfield height (pfres) is:

pfres

row height

variables freed

12  8 none
11  8 var0-var3
10  9 var0-var7
 9 10 var0-var11
 8 12 var0-var15
 7 13 var0-var19
 6 16 var0-var23
 5 19 var0-var27
 4 24 var0-var31
 3 32 var0-var35


If you don't like the default row heights, you can specify your own by setting pfrowheight. Note that if pfres*pfrowheight exceeds 96, the screen size will increase and cut into your bB program's time, or in extreme cases, will make the screen jitter, shake or roll.

To set pfrowheight, use const:

 const pfrowheight=7

If you do not want to be stuck using constant row heights, you can specify the height of individual playfield rows by setting the pfheights kernel option. See Compiler Directives for more information.

Lastly, if you are using Superchip RAM, you can use a pfres value up to 32, and all variables from var0-var47 are always free for general use. See Superchip for more information about using Superchip RAM.

    drawscreen

    The drawscreen command displays the screen. Any objects with changed colors, positions or height will be updated. Internally, this command runs the display kernel.

    Normally, drawscreen is called once within the normal game loop, but it can be called anywhere. The drawscreen operation takes about 12 milliseconds to complete, since it needs to render the entire television display, one scanline at a time. Control will be returned to batari Basic once the visible portion of the screen has been rendered.

    It is important to note that the drawscreen command must be run at least 60 times a second. Aside from rendering the visible screen, drawscreen also sends synchronization signals to the television. Failure to run drawscreen quickly enough will result in a display that shakes, jitters, or at worst, rolls.

    Therefore, it is possible that your game loop will take up too much time and cause the television to lose sync. Note that your game loop cannot execute for more than around 2 milliseconds, so you should keep the number of loops and calls to playfield scrolling routines to a minimum. This works out to about 2,700 machine cycles, which can get used up pretty fast if you are doing many complicated operations.

    If your screen rolls, jitters or shakes, the only solution is to simplify your operations or to try and spread your operations across two or more television frames by calling drawscreen at strategic times. Note also that doing so may slow your game down if you do not also move your sprites or other objects between calls to drawscreen.

    Note that emulators are very forgiving about timing, and real hardware will exhibit display problems well before an emulator will. To help troubleshoot timing problems, bB has two debug modes, described elsewhere in this document.

    If you do run out of time, you will need to optimize your code and/or spread it across multiple frames, which could slow down the game.

    One alternative is to place some code in the vertical blank area. See vblank command for more information about placing code here.

      vblank

      Normally, bB code runs in overscan. This is the portion of the television screen, roughly 2 milliseconds long, after the visible screen but before the synchronization signals are sent to the television. After this is an area called vertical blank, where the picture is blanked for a couple of milliseconds before the display kernel renders the visible screen.

      bB runs its kernel setup routines in vertical blank, such as horizontal positioning and setting up the score, and additionally, the multisprite kernel uses this time to determine which virtual sprites to display. With recent improvements in bB's standard kernel, there is now some time available here. How much time depends on a number of factors, but in most cases it should be at least 1000 cycles. You can now run some bB code here if you wish.

      To use, place the vblank keyword in your code. For example, to scroll left in vblank:

       vblank
       pfscroll left
       return
      

      The code in vblank will be run automatically every time a drawscreen is called. You can specify vblank only once, and the code should be physically separate from the rest of your code (in other words, it's inadvisable to allow your regular bB code to 'run into' the vblank code.)

      vblank can be used in bank-switched games, but it must be placed in the last bank (bank 2 for 8K, bank 4 for 16K, and bank 8 for 32K games.)

      Note that running code here means that certain changes won't take effect until the next drawscreen. Particularly, changing x-positions of objects or the score will be delayed.

      Warning: Although some time was freed in the vertical blank area, there still isn't a great deal. In the multisprite kernel, there isn't much time here at all. Although you can still use vblank in the multisprite kernel, its use isn't recommended unless you know what you are doing. Also, at this time there isn't an automatic way to check for the number of cycles left in vblank (as is done using set debug cyclescore.)

    pfpixel (playfield pixel) •

    This draws a single pixel with playfield blocks. Uses 80 processor cycles every frame. The syntax is:

    pfpixel xpos ypos function

    xpos can be 0-31, ypos can be 0-11 (11 is hidden off of the screen and only seen if scrolled.)

    function is any of on, off, or flip. On turns the block on, off turns it off, and flip turns it off if it was on or on if it was off.

    Note that there is no checking if the bounds of the function are exceeded. If you do so, strange things may happen, including crashing your program.

    pfhline (playfield horizontal line) —

    This draws a horizontal line with playfield blocks. Uses 250 to 1500 processor cycles every frame depending on length (Approx 210+42*length). The syntax is:

    pfhline xpos ypos endxpos function

    xpos can be 0-31, ypos can be 0-11 (11 is hidden off of the screen and only seen if scrolled).

    endxpos should be greater than xpos or the command will not work properly, and strange things may happen.

    function is any of on, off, or flip. On turns the block on, off turns it off, and flip turns it off if it was on or on if it was off.

    Note that there is no checking if the bounds of the function are exceeded. If you do so, strange things may happen, including crashing your program.

    pfvline (playfield vertical line) |

    This draws a vertical line with playfield blocks. Uses 230 to 600 processor cycles every frame depending on length (Approx 200+34*length). The syntax is:

    pfhline xpos ypos endypos function

    xpos can be 0-31, ypos can be 0-11 (11 is hidden off of the screen and only seen if scrolled).

    endypos should be greater than ypos or the command will not work properly, and strange things may happen.

    function is any of on, off, or flip. On turns the block on, off turns it off, and flip turns it off if it was on or on if it was off.

    Note that there is no checking if the bounds of the function are exceeded. If you do so, strange things may happen, including crashing your program.

    pfscroll (playfield scroll)

    This command scrolls the playfield. It is useful for a moving background or other purposes.

    Valid values are:
    pfscroll left
    pfscroll right
    pfscroll up
    pfscroll down
    pfscroll upup
    pfscroll downdown


    Using pfscroll left or right will use quite a few processor cycles every frame (500 cycles), so use it sparingly. Using pfscroll up or down uses 650 cycles every 8th time it's called, 30 cycles otherwise.

    When using pfscroll up or down, the hidden blocks at y position 11 are useful. Although these blocks are never seen, they are "scrolled in" to the visible screen by the commands. This invisible area can therefore be used to simulate a changing background, instead of showing the same background over and over again.

    upup and downdown will scroll two lines at a time without the need for calling the routine twice.

    Playfield plotting and scrolling commands do not work with the multisprite kernel. If you try to use them, chances are the program will not compile, or if it does, your program may crash.

    pfread function

    pfread is used to determine whether an existing playfield pixel is on or off. It can only be used in an if-then statement at this time. You may use numbers, variables or arrays as arguments.

    You access it as follows:

     if pfread(10,8) then 20
     if !pfread(a[x], b) then 40
    

    If you are using the multisprite kernel, you can use a special pfread module, called pfread_msk.asm, made just for this kernel. It is not enabled by default. To enable it, you can use the include or inline command in a 2K or 4K game. For a bank-switched game, only the inline command will work, and the command must be placed in the last bank. For example:

     inline pfread_msk.asm
    

    The command will work in a similar manner to the standard kernel except that y-values are reversed. See inline for more information about this command and its proper use. Alternatively, you can create a custom includes file with this module placed such that it will live in the last bank.

    score

    The score keyword is used to change the score. The score is fixed at 6 digits, and it currently resides permanently at the bottom of the screen. Unlike other variables, batari Basic accepts values from 0-999999 when dealing with the score.

    Before the score will appear, you should set its color:

    scorecolor = value

    value should be a number between 0 and 255 in accordance with the color chart.

    To change the score, some examples of valid operations are:

     score = 1000
     score = score + 2000
     score = score - 10
     score = score + a
    

    Be careful when using the last one. It will compile, but upon execution, a must always be a BCD compliant number. If a non-BCD number is in a, part of your score may end up garbled.

    Conditional statements don't work properly with the score. For example, "if score<10" will compile correctly but will not check if the score is less than 10. This is because the score is a special object: a 24-bit BCD number.

    If you plan to check to score, you will need to break out its 6 digits into 3 sets of two digits and check each one. One way to pull out the score digits is:

     dim sc1=score
     dim sc2=score+1
     dim sc3=score+2
    

    sc1 will contain the first two digits, sc2 the 3rd and 4th, and sc3 the last two. Since these are all BCD numbers, you need to place a "$" in front of any values you check. For example, to check if the score is less than 10:

     if sc1=$00 && sc2=$00 && sc3<$10 then ...
    

    To check if the score is greater than 123456:

     if sc1>=$12 && sc2>=$34 && sc3>$56 then ...
    

    Breaking out score digits is also useful for setting the score to display special values or custom graphics. You can modify score_graphics.asm to include definitions for digits "A-F" and set the score digits manually. How to do this is beyond the scope of this document, but it has been discussed in detail in the AtariAge 2600 Basic forum.

      pfscore bars

      The area reserved for the score now also contains two 8-wide bars that can be used to display lives, health or other game information. This feature may be enabled by setting pfscore using const. Example:

       const pfscore=1
      

      The value doesn't matter, as bB will simply look to see if pfscore is defined. This method will be replaced at a later date with the set command, which will give other control over the score. Although the const method will be deprecated then, it will continue to work.

      The 8-wide bars (called pfscore bars because they use playfield registers) have three special variables:

        pfscorecolor
        Color of the bars.

        pfscore1
        Binary value displayed left of score.

        pfscore2
        Binary value displayed right of score, reversed bit order

      For example: Suppose you want to use the left pfscore bar for lives and the right one for health. To initialize, you might do:

       pfscore1=168
       pfscore2=255
      

      The above sets the left bar for 3 separate dots (indicating lives) and the right bar will be full-width.

      To decrement a life:

       pfscore1=pfscore1/4
      

      To decrement the health bar:

       pfscore2=pfscore2/2
      

      When there are no lives left, pfscore1 is zero. Also, when the health bar is empty, pfscore2 is zero.

      Note that you cannot use pfscore bars in conjunction with the HUDs that are currently available without hacking the source files because they use the same memory locations for their variables.

      If you wish to disable the score completely, you may do so by defining noscore. At this time, you can do that by:

       const noscore=1
      

      As with pfscore above, this method will be deprecated later when score options are integrated with the set command.

      What is a BCD Compliant Number?

      BCD stands for Binary-coded decimal. In essence, it is a hexadecimal number represented as a decimal number.

      For instance, $99 is the BCD number for decimal 99. $23 is the BCD number for decimal 23. There is no BCD number for $3E, for instance, since it contains a non-decimal value (the E.) For example, if "a" contained $3E, your score could end up garbled.

    TIA Registers

    There are a few TIA registers that may be useful in batari Basic. This is not a complete list. I'm only mentioning the registers and functions therein that you will most likely find useful. You can learn more by visiting the Stella Programmer's Guide.

      Registers: NUSIZ0, NUSIZ1

      Changes the size and/or other properties of player0/1 and missile0/1.

      value effect
      $0x (x means don't care) missile = 1 pixel wide
      $1x missile = 2 pixels wide
      $2x missile = 4 pixels wide
      $3x missile = 8 pixels wide
      $x0 one copy of player and missile
      $x1 two close copies of player and missile
      $x2 two medium copies of player and missile
      $x3 three close copies of player and missile
      $x4 two wide copies of player and missile
      $x5 double-sized player
      $x6 three medium copies of player and missile
      $x7 quad-sized player


      Note that missile and player properties may be combined in a single write.

      Example: NUSIZ0=$13 will make missile0 8 wide, plus make three close copies of player0 and missile0.

      Register: CTRLPF

      Changes properties of the playfield and/or ball.

      value effect
      $0x (x means don't care) ball = 1 pixel wide
      $1x ball = 2 pixels wide
      $2x ball = 4 pixels wide
      $3x ball = 8 pixels wide
      $x1 none of the below
      $x3 left half of PF gets player0 color, right half gets player1 color
      $x5 players move behind playfield
      $x7 both of the above


      Note that ball and playfield properties may be combined in a single write.

      Registers: REFP0, REFP1

      Reflects player sprites. (Flips them horizontally.)

      value effect
      0 do not reflect
      8 reflect


      This is useful for asymmetric sprites so that they can give the appearance of changing direction without needing to redefine their graphics.

      Registers: PF0

      Set or clear the left and right 10% of the playfield.

      value effect
      $0x through $Fx set vertical lines covering entire height of playfield


      PF0 is useful for creating a border in batari Basic. In other kernels or in assembly, it has other uses.

      Registers: AUDC0, AUDC1, AUDF0, AUDF1, AUDV0, AUDV1

      See sound for more information about these.

Back to Top

Display kernel

The display kernel (usually just "the kernel") is a carefully-written software routine that renders the television display. The Atari 2600's television signal is controlled largely by software because the hardware is so primitive. Although programming a 2600 kernel is quite difficult, the primitive hardware also affords a good deal of flexibility, in that custom kernels can exploit the primitive hardware to suit particular needs.

The original bB kernel was modeled as one-size-fits-all. This allowed for a decent variety of games, but some games were not possible. Furthermore, some of the games that were possible did not look as good as games written in pure assembly.

Therefore, the latest version of bB has not one but actually 28 possible kernels to choose from. 27 of those 28 are based on the standard kernel, optionally adding bells and whistles as needed (sometimes for free, sometimes at a cost). See kernel options under compiler directives for more information about what these options are and how to use them.

    Multisprite Kernel

    This is an entirely new kernel written from scratch that provides significant features that should be suitable for a wide variety of games.

    Instead of two player sprites, you get six. Five of the six sprites are subject to certain limitations, however. These limitations are because the 2600's hardware can only display two sprites on any given horizontal line (or y-value).

    The player0 sprite works much how it did in the standard kernel. Player1-player5 sprites are 'virtual' sprites, and are displayed by repositioning the 2600's second hardware sprite several times during the visible screen.

    One limitation is that the virtual sprites may flicker if one or more of them share the same vertical region or don't have enough vertical separation between them. Most people don't mind the flicker, and on a real television, it isn't always terribly noticeable if used sparingly. If you prefer to write a game with no flicker, just ensure that there is sufficient vertical separation between sprites. The kernel will automatically detect if two or more sprites are overlapping and will automatically flicker them, so you don't need to do this yourself. It should be noted that the flicker algorithm isn't perfected yet. Under certain conditions, some sprites might not display correctly. This is a known bug and it's being looked at.

    The virtual sprites also are given their own virtual color and NUSIZ registers. COLUP2-COLUP5 and NUSIZ2-NUSIZ5 correspond to sprites 2-5. For sprite 1, use _COLUP1 and _NUSIZ1. Although these look like TIA registers, they are not; they point to the 2600's RAM. The actual TIA registers COLUP1 and NUSIZ1 can be set but they will probably have no effect in the multisprite kernel.

    Also, although the multisprite kernel doesn't have special variables for REFPx, you can set the reflection bit for each individual sprite by using bit 3 of _NUSIZ1 or NUSIZ2-NUSIZ5, as this bit is unused by the TIA register NUSIZx.

    Note that missile 1 may be affected by any of sprite 1-5's NUSIZx and COLUPx values depending on where it is located.

      Other Limitations/Considerations
      • playfield is mirrored and located in ROM (see below)
      • no kernel options (yet)
      • missiles and ball are fixed at one unit high
      • Y-values are inverted as compared with the standard kernel (in other words, the bottom of the screen is zero and top is around 88)


      One major difference between the standard kernel and this kernel is that the playfield is mirrored and located in ROM. This has several implications:

      • The right half of the playfield if always the same as the left
      • There are no playfield plotting commands
      • Horizontal scrolling is essentially impossible
      • Vertical scrolling isn't implemented (yet)
      • The only way to draw the playfield is with the playfield: command — this means you must specify the entire playfield at once


      Although the playfield is limited in many ways, the fact that it is stored in ROM is advantageous in one way. Unlike the standard kernel, you are not limited to just 11 rows — you can use up to 44 rows, and you can set the vertical resolution to suit your needs.

      pfheight is a special variable that specifies the height of playfield blocks, minus one. Acceptable values are 31, 15, 7, 3, 1, and 0.

      A pfheight of zero will actually give a vertical resolution of around 88 rows. However, it doesn't work properly when sprites are on the screen, so it's probably only useful for static displays like title screens. The setting that will give the highest resolution for general-purpose use is pfheight=1.

      You can also adjust the height of the screen in the multisprite kernel. You may set screenheight to 80 to shrink the screen by 8 pixels, by:

       const screenheight=80
      

      At this time, the only supported values are 80 and 84, and 80 only works for row heights < 8 while 84 only works for row heights < 4. Other values for screenheight may be allowed in the future. If you try to use other values now, strange things may happen.

    Minikernels/HUDs

    Minikernels are a new feature of bB that allows for further customization of your game. Minikernels are intended for use as a HUD (Heads-up Display), which is the part of the game that shows lives, health, status, time, etc.

    The HUDs go just below the bB screen but above the score. The minikernels that display the HUDs are totally modular, can be used in most any bB program, and will not take anything away from the visible screen (unless you choose to.)

    Two minikernels are included with this release of bB. One contains a "life counter" that can show up to six icons to represent player lives, and the lives have a variety of display configurations. The other minikernel contains a fixed, left-aligned life counter and a 28-unit "status bar" (for lack of a better term, as the bar could be used to indicate health, time, power, speed, etc.)

    Of course you never get something for nothing. Since the minikernels obviously take time to run and render their display, this takes some cycles away from your bB game. A typical minikernel will take around 700 machine cycles each frame. Since this is roughly one-quarter to one-third of the available time, you will need to decide if this is more than your game can handle.

    If you feel that you can't spare the cycles but you want a way to display lives and/or health, there are three remedies.

    The first is to use the pfres and/or pfrowheight constants in the standard kernel or the screenheight constant in the multisprite kernel to shrink the screen height, or the PFheights kernel option with a total height of less than 88. These may reduce the size of the vertical screen and thus may free enough cycles for a HUD.

    A second remedy is to eliminate the score. At the moment, you may do this by placing "const noscore=1" near the beginning of your program.

    Or, if you don't want to limit the height of your playing area or lose the score, another option is the pfscore bars. Built into the score routine are two 8-wide bars on either side that can be used for a variety of purposes. See score for more information.

    More HUDs are planned, and hopefully, their modularity might encourage other programmers to write them. They might also serve as a simpler way to try out 2600 kernel writing without needing to write an entire kernel.

    The two that are available now may be used in your game by including one of the modules as inline asm:

     inline 6lives.asm
    

    or

     inline 6lives_statusbar.asm
    

    With 8K or larger games, you must place the inline command in the last bank since your game will look for the module there. See inline for more information about this command (and note the warning.) In a 4K game, you can use include instead of inline for minikernels.

    The minikernels above require that you define the icon used for the life counter using "lives:". This works similar to a player definition, except that they are currently fixed at 8 units high. For example:

     lives:
     %01000100
     %11111110
     %11111110
     %01111100
     %00111000
     %00010000
     %00010000
     %00010000
    end
    

    If using 6lives.asm, there are two options that can align your lives to the left or center, or can select from compressed or expanded layouts. The compact layout will place the lives close together — they will actually touch if you define them to be 8 pixels wide. The expanded layout puts 8 pixels between each life. Note that 6lives_statusbar.asm is fixed at left-aligned, compact.

    The default is left-aligned, expanded. To select centered and/or compact, place one or both of the following at the beginning of your code:

     dim lives_centered=1
     dim lives_compact=1
    

    There are up to four variables that control 6lives_statusbar, and two in 6lives:
    lives
    lifecolor
    statusbarlength
    statusbarcolor


    The last two aren't used in 6lives. statusbarcolor is optional in 6lives_statusbar.

    The new variables are described below:

      lives
      This is a shared variable. The lower 5 bits are used for the icon graphics pointer, and the upper 3 are used to determine how many lives to display. The upper 3 bits can represent 0-7 lives but no more than 6 will diplay. It might sound difficult to use but it really isn't.

      To assign a number of lives, for example, at the beginning of your game, use the following values:

      #lives

      command

       0 lives=0
       1 lives=32
       2 lives=64
       3 lives=96
       4 lives=128
       5 lives=160
       6 lives=192
       7 lives=224


      When you assign a number of lives, you ALWAYS need to define (or redefine) the life icon using lives: or the icon won't display correctly.

      Adding or subtracting lives is easy. To add a life, do lives=lives+32, to subtract, do lives=lives-32. You do not need to define lives: after adding or subtracting as these operations will not affect the icon graphics pointer.

      To check for zero lives:

       if lives<32 then ....
      

      To check for 7 lives:

       if lives>223 then ...
      

      Subtracting a life from zero will result in seven lives, and adding a life to seven will result in zero lives.

      lifecolor
      Sets the color of the lives.

      statusbarlength
      Sets the length of the status bar. Valid values are the full range 0-255, but numbers greater than 224 will show the bar at full length. The bar is 28 discrete units wide, so the bar only changes in multiples of 8 in the variable. If you know what you are doing, the lower 3 bits of this variable could be used for something else.

      statusbarcolor
      This is optional. If not used, the statusbar will be the same color as the playfield. To use, however, you must reserve one of your 26 user variables using dim.

      For example:

       dim statusbarcolor=t
      
      Writing Your Own Minikernel/HUD

      To write your own minikernel, you will need to use assembly language and follow a few guidelines.

      You do not need any headers, equates or ORG's in your assembly file — just start the code with a label called "minikernel." The regular kernel will conditionally assemble a "jsr minikernel" if the label exists, so it will automatically execute when included with the include or inline command.

      Typically, minikernels will take around 10 scanlines. The interval timer (TIM64T) is set prior to the kernel so you can use more or less if you wish, but using too many can make the visible screen too large and/or limit the amount of time available for the bB code unless certain compromises are made (see minikernels above.)

      You should begin your minikernel with a STA WSYNC so it always starts at the beginning of a line. Also, if you want your code to be flexible, it's important to write it such that the timing will be correct regardless of the physical addresses where the code assembles. This means that you should place checks to ensure that branches or tables don't wrap pages or use WSYNC to delineate a new scanline. Simply placing "align 256" in your code will work but it is probably a waste of space — one solution is to use something like this:

       if ((<*+(code_length-minikernel)) > (<*))
       align 256
       endif
      minikernel
       ... your code ...
      code_length
      

      This will align on a page boundary only if the code would cross a page boundary. Of course there are better ways to do this, but this will work without blatantly wasting space.

      In general, it's inadvisable to use too much time to before you start drawing, so repositioning 5 objects probably isn't a good idea. The minikernel setup should be minimal, otherwise there may be a large gap between the regular screen and the start of your graphics. But some setup is probably needed. Although most TIA registers are cleared before the minikernel, some may or may not be, such as VDELxx.

      4 bytes are set aside for exclusive use in the minikernel — aux3-aux6. These bytes are sequential in RAM so they can be used as pointers if needed. In addition, you may use temp1-temp6 for temporary storage in during the minikernel. If you can ensure that the programmer won't try to call drawscreen while in a subroutine while in a bank-switched game, you might be able to use stack3 and stack4 which are normally reserved for stack space. Beyond that, care is needed to ensure that your variables aren't overwritten by something else.

      Since the minikernel is a subroutine, you just need an rts to return to the regularly scheduled kernel. But before you exit, make sure you clear any TIA registers that your kernel uses.

      If you write a minikernel and you think it might be useful for other games, please post your code to the Atari 2600 Basic forum at Atariage!

Back to Top

Compiler Directives (set command)

The set command tells the compiler or the assembler to build your code a certain way. The syntax is:

set <directive> <option>

Currently, the following directives are supported:
smartbranching
tv
romsize
optimization
kernel
kernel_options
debug
legacy

Typically you will use the directives near the beginning of your program. However, smartbranching and optimization were originally designed to be set, reset, and/or turned on and off throughout your code (though I'm not sure if this actually works :| )

    The Directives
      smartbranching

      The smartbranching directive tells the compiler to generate optimal assembly code when it encounters an if-then statement with a line number or label after the then.

      It is set to "off" by default, because this makes the generated assembly code easier for a human to read and understand. This will sometimes cause the compilation to fail, however. In this case, you will need to use "then goto" instead of just "then" for the if-then statement that caused the problem.

      if you "set smartbranching on" then you just use "then" and the compiler will figure out whether to use "then" or "then goto" for you. The drawback to doing this is that the generated assembly file will be much harder for a human to read. If you do not care to see the assembly language that bB generates, however, you should set smartbranching on.

      You can also use "then goto" all the time and not have to worry about smartbranching at all, but "then goto" uses more code space than just "then" so you should take this into consideration as well.

      tv

      Specifies the tv format. Valid options are ntsc or pal. ntsc is the default and is unnecessary.

      set tv pal will build a game that will run on a PAL television. This format is standard in Europe and Australia, among other places.

      Note that the PAL setting only changes the timing and synchronization signals and not the colors. PAL has a different palette than NTSC and some NTSC colors do not have a PAL counterpart, so you will have to select the colors carefully for each format if you intend to create a game for both formats.

      Recently, it has been discovered that PAL televisions will play NTSC binaries without any problems except different colors. Optionally, you may prefer to create a PAL60 binary, which uses the NTSC timing but PAL colors. To do so, use the NTSC TV format but specify the colors from the PAL palette.

      romsize

      Allows you to specify the size of the generated binary. 4K is the default. Currently you may generate 2K, 4K, 8K, 16K or 32K binaries. Anything 8K or larger will use bank switching, and additional considerations are needed for this. See Bank Switching for more information. Append SC (capitalized) on the end to enable Superchip RAM (valid for 8K or larger binaries only.)

      Examples:

       set romsize 2k
       set romsize 16k
       set romsize 8kSC
      
      optimization

      Tells the compiler to try to generate code that runs faster or has a smaller size. There are five options: speed, size, noinlinedata, inlinerand, and none.

        speed
        May increase speed (particularly, of multiplication and division) at the cost of code size.

        size
        May decrease the size of generated code (particularly, of multiplication and division) at the cost of speed.

        noinlinedata
        Will remove overhead from data tables, saving space. Doing so will limit them to outside of code. That is, you can no longer place data tables inline with code, or you program may crash!

        inlinerand
        Will place calls to the random number generator inline with your code. This is particularly useful for bank-switched games, where a call to the random number generator would normally have to switch banks, so this will speed up your code with a minimal increase in code size. (This setting may become automatic at a later date.)

      Examples:

       set optimization speed
       set optimization inlinerand
      
      kernel

      Determines which kernel to use with your game. Currently there are 28 kernels available. There is a multisprite kernel, a standard kernel, and the remaining 26 of them are technically distinct kernels but are available as options in the standard kernel. The standard kernel is, well, standard, so no directive is needed to use this.

      Multisprite kernel example:

       set kernel multisprite
      

      See multisprite kernel for more information about the multisprite kernel.

      kernel_options

      These are options for customizing the kernel for various needs. Generally, adding options will mean that some objects will not be displayed. Currently the options only apply to the standard kernel.

      Options:


        readpaddle
        Set to read a paddle value — must be used with no_blank_lines.

        player1colors
        Set to use multicolored P1. Cost: loss of missile1 .

        playercolors
        Set to use both multicolored players — must be used with above (player1colors). Cost: loss of missile0.

        no_blank_lines
        No gaps in PF blocks. Cost: loss of missile0.

        pfcolors
        Specify colors of each row of PF blocks. Cost: free

        pfheights
        Specify height of each row. Cost: free

        pfcolors and pfheights together
        Cost: free, but colors and height are fixed.

        background
        Change background color instead of playfield color.


      The syntax is:

      set kernel_options option [option2 option3 ...]

      The order of the options doesn't matter, but there are limitations as to which options can be used alone and/or together:

        Acceptable singles:
        player1colors
        no_blank_lines
        pfcolors
        pfheights

        Invalid singles:
        playercolors
        readpaddle
        background

        Acceptable combinations:
        pfcolors pfheights
        pfcolors pfheights background
        pfcolors no_blank_lines
        pfcolors no_blank_lines background
        player1colors no_blank_lines
        player1colors pfcolors
        player1colors pfheights
        player1colors pfcolors pfheights
        player1colors pfcolors background
        player1colors pfheights background
        player1colors pfcolors pfheights background
        player1colors no_blank_lines readpaddle
        player1colors no_blank_lines pfcolors
        player1colors no_blank_lines pfcolors background
        playercolors player1colors pfcolors
        playercolors player1colors pfheights
        playercolors player1colors pfcolors pfheights
        playercolors player1colors pfcolors background
        playercolors player1colors pfheights background
        playercolors player1colors pfcolors pfheights background
        no_blank_lines readpaddle

        Using kernel_options


          Paddle Reading
          Specify paddle to read in currentpaddle (0-3) then value will be returned in paddle after a call to drawscreen. Value returned should range from about 0-80.

          Multicolored/Variable Height Playfield

            pfheights
            Specifies heights of each playfield row. You must specify 11 values, one to a line, followed by end. The default height is 8. The total should not exceed 88 or you will cut into the time available for your bB program. Specifying less than 88 is fine, but doing so will shrink the size of the visible screen. You can specify pfheights: as many times as you want.

            Warning: At this time, if the first row isn't 8, things might not work quite correctly. This problem is being worked on.

            Example:

            pfheights:
             8
             8
             15
             1
             8
             8
             8
             8
             8
             8
             8
            end
            


            pfcolors
            Specifies colors of each playfield row. Must specify 11 values, one to a line, followed by end. You can specify this as many times as you want.

             pfcolors:
             $f5
             $f5
             $f5
             $43
             $f5
             $f5
             $f5
             $f5
             $f5
             $f5
             $f5
            end
            


            pfcolors and pfheights used together
            When used together in the same game, you can only define pfheights: and pfcolors: one time, and you need to define pfheights first.

            playercolors and player1colors
            Specifies player colors a row at a time.

            playercolors: allows both players to be multicolored, while player1colors: only allows this for player 1.

            Use player0color: to define the colors for the player0, and player1color: for player1.

            For example:

             player0color:
             $f5
             $f5
             $f5
             $43
             $f5
            end
            

            Note that changing player colors also affects the missile colors during the scanlines where the colors change.

      debug

      Used to help find bugs in your bB program. Currently it only helps with determining when too many machine cycles are used in a particular frame.

      Valid options are cycles or cyclescore.


        cycles
        Will flash the background color when the cycle count is exceeded. This has little overhead but its utility is limited.

        cyclescore
        Will display an estimate of the number of actual machine cycles available in the current frame. It should be accurate to about plus or minus 64 cycles. If the score color is white, the number indicated is positive. If it is red, the number is negative (in other words, you have used too many cycles).

        Since cyclescore changes every frame, it may be hard to see the numbers flashing by. You may find it useful to record the minimum number of cycles. To do so, you must define mincycles to be one of the 26 user variables. During debugging you can't use the variable for anything else, but of course you get it back once you're done debugging. To use mincycles, for example:

         dim mincycles = z
         mincycles=255 
        

        The latter statement is needed to set (or reset) mincycles to a large value so it can properly find a minimum.

        cyclescore only measures +/- 2000 cycles. If your deficit is more than 2000 cycles, the score will probably display garbage or your game may crash. But if your code is more than 2000 cycles over the limit, you have bigger problems than this.

      legacy

      Version 1.0 of bB has several new features and has been optimized such that code written for earlier versions may not work quite right. To retain more compatibility with old code, a legacy mode has been implemented that will (hopefully) allow the games to work properly without extensive modifications.

      The most obvious change is for player/missile/ball x positions in the standard kernel — they will likely be shifted 14 pixels to the right. This change was made because the sprite positioning routine runs at least twice as fast, and the values now accurately represent the boundaries of the screen — 0 is the left edge and 159 is the right edge. The legacy mode will use the old positioning.

      It is recommended that you fix your old games to account for this discrepancy. But if that's too daunting, you can use the old positioning by setting the legacy mode to 0.99 or less.

      One way to modify your program for legacy mode:

       set legacy 0.99
      

      You can also modify your 2600basic.h file to include the line legacy = 99 (in other words, version * 100) to always compile programs in 0.99 legacy mode. This approach isn't recommended except for convenience when compiling a number of old programs.

      Setting the legacy mode may also change other things, though most of these are minor.

      Note that while legacy mode may not fix all of the issues that creep up when compiling legacy code, it should fix the most egregious of them.

      An incomplete list of changes that may affect your game, but are not accounted for in legacy mode:

      • A thin playfield line appeared in bB 0.99c (unreleased version) when using the no_blank_lines kernel option. Although this was considered a bug, some programmers exploited it for use as a health bar, etc.

      • The score was moved one pixel to the left as a consequence of eliminating the black HMOVE bar to the left of the score

      • The score was moved one more pixel to the left in order to accommodate the pfscore bars (see score for more details about using pfscore bars.)

      • The rightmost edge of leftmost score digit might not display quite correctly if you redefine the graphics to be 8 pixels wide. But since the score digits are defined to be 6 pixels wide, under normal circumstances, this problem will never surface. It only becomes an issue if the score graphics are redefined to be 8 pixels wide. At the moment, the bug will occur whether or not the pfscore bars are enabled. At a later date, this bug will be fixed when pfscore bars are not enabled. But if they are enabled, there is currently no known solution.
Back to Top

Bank Switching (Up to 32K of ROM for your games!)

The Atari 2600 can only address 4K at a time. Games larger than 4K are possible by additional hardware on the cartridge that can swap in two or more banks of 4K. Since the 2600 can only 'see' 4K at once, the 2-8 banks also can't see each other; that is, one bank cannot access data in another bank. Also, since an entire bank needs to be swapped in all at once, normally this would make programming somewhat more difficult.

Bank switching in bB cannot get past one limitation — One bank still cannot access data from another bank, so data tables can only be accessed from within the same bank in which they are located.

However, the fact that banks must be swapped in all at once doesn't pose any serious technical problems for bB. You can use goto to jump to any routine in another bank, and you can also use gosub for a subroutine, and a return will automatically return to the bank that called the subroutine. This is because bB uses a clever little routine that automatically knows what bank it is in at any time and what bank called a subroutine. This routine does require some overhead in terms of cycles and space, so you should limit bank switching to a few times each frame.

To activate bank switching, you just need to set the size of the binary using "set romsize", with a value of 8K or larger (see compiler directives).

You do not need to specify where graphics go, as they all will be automatically placed in the last bank no matter where you define them. Also, the kernel will always be placed in the last bank. Typically, bB modules and functions will as well. You don't need to do anything differently with drawscreen, and you typically don't with built-in functions or modules either.

In most cases, you must specify where new banks begin. You do not need to specify where the first bank starts, as it must start at the beginning of your code. Strictly speaking, you don't need to specify where additional banks start, but if you don't, you won't be able to put your code in other banks. This might not be a huge problem for an 8K binary (where a good portion of both banks will be used, regardless) but it doesn't make much sense for a 16K or 32K binary.

To specify the beginning of a new bank, use the "bank" keyword, followed by the bank you wish to begin. 8K binaries contain two banks, 16K has four, and 32K has eight.

Examples:

 bank 2
 bank 8

Note: specifying "bank 1" is not necessary.

See goto, gosub and return for more information about jumping from one bank to another.

    Superchip RAM

    The Superchip, also called the SARA chip, provides 128 additional bytes of RAM. Superchip RAM is only used in conjunction with bank switching.

    Since the Superchip is a separate chip, producing standalone cartridges in this format will increase costs slightly. At the moment, one outfit is selling Superchips for $3 each, along with the special boards you need for them. Superchips can also be salvaged from some old games.

    Emulators support Superchip binaries with no special considerations. Furthermore, programmable cartridges, such as the Cuttle Cart, Krokodile Cart and Maxcart all support it. If you do not plan on creating actual cartridges, using this scheme will probably not prevent anyone from playing your games. In order for these games to run properly in an emulator, however, it may be necessary to manually specify the bank switching scheme. This might be a non-issue by the time you read this, however, as the latest versions of some popular emulators should detect Superchip binaries automatically.

    Superchip RAM is enabled automatically with the "set romsize" option — just append SC after the size.

    Example:

     set romsize 8kSC
    

    This would enable Superchip RAM with 8K bank switching. See compiler directives for more information.

    Most existing bB code written for standard bank switching can be adapted to run with Superchip RAM without needing major changes. In doing so, you will use the existing kernels and have the entire 128 bytes available to you as variables. The transition may not be totally seamless because Superchip RAM requires 256 bytes of filler data for every 4K, which will slightly reduce the amount of ROM available.

    It is important to note that the 128 bytes of Superchip RAM requires special treatment. Because of the design of the chip, reads and writes must be done at different addresses. Therefore, 256 variables have been defined — 128 of them are read-only (r000 to r127) and 128 are write-only (w000 to w127). That is, if you write to w000, you must read the value using r000. This means that code must be written very carefully and there are some limitations. As long as you follow certain guidelines, you should be able to do any variable assignment you want. The guidelines are that write registers always go on the left side of an assignment, and read variables (no matter how many there are) always go on the right. When using functions, use the read variables as the arguments and assign the function to a write variables. In an if-then, use read variables for the comparison.

    Examples:

     w010=r010+1
     w000=r001+r002+4
     if r001<4 then 20 
     w004=myfunction(r001,r002)
     COLUP0=r001
     pfpixel r001 r002 on
    

    These special variables cannot be used for fixed-point math, 16-bit multiplication or division, as the counter variable in for-next loops, or any functionality that requires dimming a value to a user variable. There may be other limitations as well. Also, not all variables 000-127 are actually available for general use — this is explained below.

    To ameliorate Superchip variable use, the 48-bytes previously reserved for the playfield has been placed in Superchip RAM. Therefore 48 bytes of standard RAM has been freed and is not subject to the above limitations. These 48 bytes are named var0-var47.

    Optionally, since we have extra RAM for the playfield, it can have greater vertical resolution — up to 31+1 lines instead of 11+1. One drawback of this mode is that horizontal playfield scrolling isn't supported (yet) because it runs too slowly (twice as much data to move!) With the standard-sized playfield, r000-r079 (or w000-w079) are free. With the double-sized playfield, r000-r031 (or w000-w031) are free.

    If you enable Superchip RAM, the above becomes available except the double-height playfield. If you wish to enable the double-height playfield, you need to define the number of rows to display using const. For double-height, use 24. For example:

     const pfres=24
    

    Note that you can set values from 3-32, but if the number doesn't evenly divide 96, the resultant screen might be smaller than normal. The amount of Superchip RAM used is 4 times the height. The largest size, 32, will use all available Superchip RAM.

    See playfield for more information about the pfres setting.

Back to Top

Sound

There are no special functions for accessing sound in batari Basic. Instead, you must access the TIA registers for sound directly. Don't panic, the TIA registers for sound are quite friendly, at least as far as that damn TIA goes.

    Channel 0
      AUDV0

      Channel 0 Volume (valid values are 0 to 15)

      AUDC0

      Channel 0 Tone [Distortion] (valid values are 0 to 15)

      AUDF0

      Channel 0 Frequency (valid values are 0 to 31)

    Channel 1
      AUDV1

      Channel 1 Volume (valid values are 0 to 15)

      AUDC1

      Channel 1 Tone [Distortion] (valid values are 0 to 15)

      AUDF1

      Channel 1 Frequency (valid values are 0 to 31)

Setting the values, for instance, by AUDV0 = 10 : AUDC0 = 12 : AUDF0 = 4 will produce a tone, and it will stay on until you set AUDV0 = 0. Typically, a frame counter is set up that keeps sounds on for a specified number of frames (which occur 60 times a second).

The following is adapted from Atari 2600 VCS Precise Sound Values and Distortion Breakdown by Glenn Saunders:

Tone
What it Sounds Like
     
     
 0
No sound (silent).
     
     
 1
Buzzy tones.
     
     
 2
Carries distortion 1 downward into a rumble.
     
     
 3
Flangy wavering tones, like a UFO.
     
     
 4
Pure tone.
     
     
 5
Same as 4.
     
     
 6
Between pure tone and buzzy tone (Adventure death uses this).
     
     
 7
Reedy tones, much brighter, down to Enduro car rumble.
     
     
 8
White noise/explosions/lightning, jet/spacecraft engine.
     
     
 9
Same as 7.
     
     
10
Same as 6.
     
     
11
Same as 0.
     
     
12
Pure tone, goes much lower in pitch than 4 & 5.
     
     
13
Same as 12.
     
     
14
Electronic tones, mostly lows, extends to rumble.
     
     
15
Electronic tones, mostly lows, extends to rumble.
     
     


Summary:

Unique
Redundant
 0
11
 1  2  3  4
 5
 6
10
 7
 9
 8 12
13
14 15
Back to Top

Joysticks

Joysticks are read by using an if-then statement. There are four directional functions and one fire function for each joystick:

    Left Joystick
      if joy0up

      True if left joystick is pushed up.

      if joy0down

      True if left joystick is pushed down.

      if joy0left

      True if left joystick is pushed left.

      if joy0right

      True if left joystick is pushed right.

      if joy0fire

      True if left joystick's fire button is pushed.

    Right Joystick
      if joy1up

      True if right joystick is pushed up.

      if joy1down

      True if right joystick is pushed down.

      if joy1left

      True if right joystick is pushed left.

      if joy1right

      True if right joystick is pushed right.

      if joy1fire

      True if right joystick's fire button is pushed.

Example:

 if joy0up then x = x + 1

These can also be inverted using the NOT ( ! ) token. For example:

 if !joyup then 230
Back to Top

Console Switches

Reading the console switches is done by using an if-then statement.

    if switchreset

    True if Reset is pressed.

    if switchbw

    True if the COLOR/BW switch is set to BW, false if set to COLOR.

    if switchselect

    True if Select is pressed.

    if switchleftb

    True if left difficulty is set to B (amateur), false if A (pro).

    if switchrightb

    True if right difficulty is set to B (amateur), false if A (pro).

These are accessed by, for example:

 if switchreset then 200

These can all be inverted by the NOT (!) token:

 if !switchreset then 200
Back to Top

Numbers

    Decimal Numbers

    Numbers in batari Basic are assumed to be in decimal unless otherwise specified by either the $ (for hexadecimal) or the % (for binary).

    One exception is signed numbers with the negative bit set, when expressed as a negative. See negative numbers for more information.

    Hexadecimal Numbers

    Often it is handy to express hexadecimal numbers in your Basic program. Simply place the $ before a number to use hexadecimal.

    Binary
    Decimal
      1
    128
      1
     64
      1
     32
      1
     16
      1
      8
      1
      4
      1
      2
      1
      1


    Examples:

     COLUPF = $2E
     a[$12] = $F5
    
    Binary Numbers

    Sometimes it is convenient to express numbers as binary instead of decimal or hexadecimal. To express numbers as binary, place the % before a number. Make sure that you define all 8 bits in the byte. The % operator is particularly useful for accessing certain TIA registers that expect individual bits to be set or cleared, without needing to first convert the numbers to hexadecimal or decimal first. The % operator is also useful for defining player sprites.

    Binary
    Decimal
      1
    128
      1
     64
      1
     32
      1
     16
      1
      8
      1
      4
      1
      2
      1
      1


    Examples:

     CTRLPF = %00100010
     player0:
     %00100010
     %11100111
    end
    
    Negative Numbers

    Negative numbers are somewhat supported. Although variable values can contain 0-255, the numbers will wrap if 255 is exceeded. Therefore, you can think of numbers from 128-255 as being functionally equal to -128 to -1. This is called "two's compliment" form because the high bit is set from 128-255, so this high bit can also be called the "sign."

    In other words, adding 255 to a variable has exactly the same effect as subtracting 1.

    bB also supports assignment to a unary minus, such as a = -a.

    You can also assign a variable to a negative number, such as a = -1.

    Warning: Assignment to negative numbers will not work properly with certain versions of the assembler (DASM). Versions of bB prior to 1.0 were packaged with DASM 2.20.10, which doesn't process negative numbers correctly. Version 2.20.07 of the assembler does work, and so this has been packaged with bB version 1.0.

Back to Top

Math

batari Basic supports full expression evaluation for integer math. It evaluates expressions as efficiently as possible by using the stack when needed to perform intermediate calculations, so no additional space is needed in most cases.

You can only use expression evaluation for variable assignments. You can't (yet) use expressions as arguments in functions, bB commands or comparisons, or with fixed point math.

Expressions can contain addition, subtraction, division, multiplication and/or bitwise operators.

The order of operations is:
() Parentheses
*,/ Multiplication and division (see below)
+,- Addition and subtraction (see below)
&,^,| Bitwise operators

Examples of complex expressions:

 a=e-4*2
 a=(e-4)*2
 a=(e-4)*2*(myarray[4]-(4+c[r]))+2|e

Warning: using complex expressions might result in a localized overflow (that is, an intermediate value that exceeds 255 or is less than zero) if sub-expressions are not well-constructed or variables allowed to get too large (or small, as the case may be.) Also, complex statements, functions, subroutines, and bank switching all use the stack, so using excessively complex statements in a nested function or subroutine may overflow the stack into the variable space, causing weird bugs. However, stack overflows are still considered to be somewhat unlikely.

    Addition

    The + operator is used for addition. You may use any combination of registers, variables, unsigned values from 0-255 or signed values from -128 to 127 (see also negative numbers).

    If the addition causes the result to equal or exceed 256, it will be wrapped around at 0. For instance, 255+1=0, 255+2=1, ... 255+255=254.

    An exception is the score, which can work with values from 0 - 999999.

    Examples:

     a = a + 1
     COLUPF = r + $5F
     player0y = player1y + 6 + temp1
    
    Subtraction

    The - operator is used for subtraction. You may use any combination of registers, variables, unsigned values from 0-255 or signed values from -128 to 127 (see also negative numbers).

    If the subtraction causes the result to be less than 0, it will be wrapped around to 255. For instance, 0-1=255, 1-2=255, ... 0-255=1.

    An exception is the score, which can work with values from 0 - 999999.

    bB also supports a unary minus in a variable assignment (such as a = -a).

    Examples:

     a = a - 1
     COLUPF = r - $5F
     player0y = player1y - 6 - temp1
    
    Multiplication

    bB supports multiplication of two integer numbers. However, some multiplication operations require you to include a module. In particular, multiplying two variables together requires the module, as does multiplication of a variable or by certain constants greater than 10 (see explanation below). Multiplication of a variable to a number 10 or less does not require you to include a module.

    Explanation:
    Multiplication is generally a slow routine requiring lots of precious machine cycles, but bB tries to optimize for speed based on known methods. The concise but technical answer is: If the multiplicand's prime factorization contains only 2, 3, 5, and/or 7, no module is needed, otherwise the module will be needed. If you're not sure or don't want to do figure this out every time, you can try to compile without the module, and if compilation fails, look for "mul8" or "mul16" in the list of unknown symbols.

    If the multiplication causes the result to exceed 255, the result will probably be bogus. There is a way around this — if you use ** as the multiplication operator instead of *, the result will be stored in 16 bits and the value will wrap properly. In this case, the lower byte of the result will be assigned to your variable, and the variable temp1 will contain the upper byte of the result. You should use ** only when you need it, as it takes up additional space and cycles in your program.

    The division and multiplication are packaged as a single module. If you need to include the module, place this line near the beginning of your program:

     include div_mul.asm
    

    If you are using **, however, you should include the 16-bit module, by:

     include div_mul16.asm
    

    Or you may place it in an includes file to include it automatically. See include or includes file for more information.

    Warning: 16-bit multiplication is not fully tested.

    Division

    bB supports division of two integer numbers with some limitations. Some division operations require you to include a module. In particular, dividing two variables requires the module, as does division by any number except a power of two, in other words, dividing by 2, 4, 8, 16, 32, 64, or 128 can be done without the module. You can divide by 1 as well, but I can't imagine why you would want to. If you try to divide by zero, no operation will occur and your program will continue to run.

    The division operation will return an integer result, meaning that any fractional portion or remainder will be lost. If you need the remainder, however, you can use the // operator instead. The remainder will then be stored in temp1.

    The division and multiplication are packaged as a single module. If you need to include the module, place this line near the beginning of your program:

     include div_mul.asm
    

    If you are using //, however, you should include the 16-bit module, by:

     include div_mul16.asm
    

    Or you may place it in an includes file to include it automatically. See include or includes file for more information.

    Warnings: The division modules may not work properly on bank-switched games, and the 16-bit routines have not been fully tested.

      Modulus Operation

      Other languages have a "modulus" operator, typically "%", that will return the remainder from a division operation. Although bB has no such operator, you can still get the remainder by doing a division using the // operator. See division above for more information.

    Functions

    bB provides a simple interface for you to define your own functions. These functions can be defined within your program itself or compiled to their own separate .asm file then included with the include command. Functions can be written in batari Basic or assembly language.

    To call a function, you assign it to a variable. This is currently the only way to call a function. Functions can have up to six input arguments, but they only have one explicit return value (that which is passed to the variable you assigned to the function.) You can have multiple return values but they will be implicit, meaning that the function will modify a variable and then you can access that variable after you call the function.

    A function should have input arguments. In bB, a function can be called with no input arguments if you want, but you might as well use a subroutine instead, as it will save space.

    To declare a function, use the function command, and specify a name for the function. Then place your bB code below and end it by specifying end. To return a value, use the return keyword. Using return without a value will return an unpredictable value.

    Note that in bB, all variables are global, and arguments are passed to the function by use of the temp variables, temp1-temp6. Therefore it is recommended that you use the same temp variables for calculations within your function wherever possible so that the normal variables are not affected.

    Example of declaring a function in bB:

     function sgn
     rem this function returns the sign of a number
     rem if 0 to 127, it returns 1
     rem if -128 to 1 (or 128 to 255), it returns -1 (or 255)
     rem if 0, it returns 0
     if temp1=0 then return 0
     if temp1 <128 then return 1 else return 255
    end
    

    To call the above function, assign it to a variable, as follows:

     a=sgn(f)
    

    Note that there is no checking to see if the number of arguments is correct. If you specify too many, the additional arguments will be ignored. If you specify too few, you will likely get incorrect results.

    To specify more arguments in a function, you can separate them by a comma. Supposing you called a function called max that determined the largest of three values you passed to it:

     function max
     if temp1>temp2 then temp1bigger else temp2bigger 
    temp1bigger if temp1>temp3 then return temp1 else return temp3 
    temp2bigger if temp2>temp3 then return temp2 else return temp3
    end
    

    To call this function, you might do:

     f=max(d, a[3], 7)
    
      Special consideration for functions in assembly language

      To write an asm function for use in bB, many of the considerations are the same — you can pass up to six values to the function and return one. The difference is that the first two arguments are not copied to temp1 and temp2, but instead, the accumulator and the Y register, respectively. Additional arguments are copied to temp3-temp6. To return a value, load it into the accumulator and call an RTS.

      Also, the function is entered with S and Z flags set according to the current value of the accumulator.

      For example, here is the sgn function rewritten in asm:

      sgn
       bpl minus
       lda #$FF
       rts
      minus
       beq zero
       lda #1 
      zero
       rts
      

      To use the above function in your bB program, you can either use inline asm in your bB program, or compile it separately and include its asm file using the include command, then you just call it as normal.

      If you are writing an asm function for use in a bank-switched game, you need to use RETURN instead if rts to return. RETURN is an assembler macro that will automatically return to the calling bank.

      Compiling a Function as a Module

      You can create modules that are written in batari Basic or assembly language. To make an asm module, just write the module and include the file using the include command in bB. To create your own bB module, you must first compile it separately into asm, but not into a full binary. To do this, you can use the following commands. incidentally, the commands in DOS or Unix are the same:

      preprocess < myfile.bas | 2600basic > myfile.asm

      Note that you may need to add a ./ before preprocess or 2600basic in Unix if your path isn't set to look in the current directory.

Back to Top

Assembly Language

    asm

    Use the asm statement to insert inline assembly language into your program. You do not need to preserve any register values when using this feature, except the stack pointer. Mnemonics should be indented by at least one space, and labels should not be indented.

    Example (clears the playfield)

     asm
     ldx #47
     lda #0
    playfieldclear
     sta
     playfield,x
     dex
     bne playfieldclear
    end
    

    You may also access any variables from assembly that are defined in batari Basic. For example, another way to express the statement a=20 is:

     asm
     lda #20
     sta a
    end
    
    include

    Used to include modules in the final binary that are not normally available. You may also include additional modules using the includesfile command, but you may prefer to use include if you just want an extra module or two to be compiled in addition to what is already in the default includes file.

    An example is for fixed point math. Although you do not need to include anything to use many of the fixed point functions, for a few, you will need the fixed_point_math.asm module. You may find it easier to use

     include fixed_point_math.asm
    

    at the beginning of your program instead of (or in addition to) creating a new includes file, and this will allow you to share your source without also needing to attach your includes file as well.

    With the include command, bB will place the module where it sees fit, typically in the first bank. If you want to have more control over where the modules go, you can use an includes file or the inline command.

    Warning: The include command must typically precede all other commands. At this time there is no checking to ensure that you do this. Particularly, if you use an includes file or a different kernel, you need to specify all of your include commands first or they will be ignored.

      includes file

      An includes file contains the filenames of all modules that will be included in the final binary. Modules may be general routines, functions or custom display kernels. The includes file also specifies the order in which they will appear (this is crucial.) The default includes file (default.inc) contains the standard kernel and some of the more commonly used modules.

      To create your own includes file, you can use the default.inc file as a template. The default.inc file itself contains comments that should guide you. Save it to a new name and use the .inc extension. To specify a new includes file in your program, supposing your includes file is called myincludes.inc, use:

       includesfile myincludes.inc
      

      You do not need to use the includesfile command to use the default.inc file.

      One reason you may want to use your own includes file, however, is in the case where you need space in your program more than you need a standard module, such as the one for playfield scrolling.

      Note that if you are using an includes file and you wish to share your Basic code with others, you should also share the includes file so that they can compile your code. Also, if you are using the include command in addition to an includes file, you must specify the include commands first.

    inline

    This command works similar to include, as it is used to include an .asm file in your program. But unlike include, inline allows you to specify exactly where an asm module will go. This is useful when you want to use inline asm code from an external file in your game without copying it into your code. inline is also useful for inserting a minikernel into your game.

    Example:

     inline 6lives.asm
    

    Warning: Using inline will insert the asm code exactly where the statement is, so doing so at the wrong place (such as at the beginning of your program) will probably cause your game to crash.

    Hacking batari Basic's .asm Files

    You are encouraged to hack the .asm files that come with bB, as doing so can extend its capabilities. Also, this is one avenue to learning assembly language programming.

    bB 1.0 will search both the includes directory and the current directory for any .asm files it needs to build your game. But since it will search the current directory first, this means that you can now make a copy of an .asm file from the includes directory and modify your copy while keeping the existing files intact.

    This scenario has the added advantage of allowing you to compile other games without needing to change the files back. Also, you can now distribute modified .asm files with your source code so others can build your game exactly as you intend without requiring them to modify their own .asm files.

    Perhaps the most common file that people modify is score_graphics.asm. This allows you to define your own digits, and you may also modify digits A-F to make custom score displays. Aside from score graphics, this file also contains assembly directives, some of which you need to change if you wish to create definitions for A-F.

    You may also copy and modify includes files (.inc extension) and header files (.h extension) in the same fashion.

Back to Top

Atari 7800

The Atari 7800 is reverse-compatible with nearly 100% of Atari 2600 games. All bB games should run on the 7800. However, games that use the COLOR/BW switch may have problems. On the 7800, the switch is meant to be used as a PAUSE switch. On the 2600 this is a toggle or rocker switch, but on the 7800 it is a momentary pushbutton.

Most games will run exactly the same on either console because most do not use the COLOR/BW switch. But if necessary, bB can detect what console it is running on so you can account for this difference. When your game begins, temp1 will contain zero if the console is a 2600, or 255 if it is a 7800.

Note that you must check temp1 at the beginning of your game (it may be overwritten if you don't check it right away) and set a variable depending on temp1's value. A single bit is all you need.

Back to Top

Troubleshooting

This section does not cover all possible problems, but it does explain some common programming errors and their resolutions.

There are two types of programming errors. One type of error is a compilation error, that is, an error is caught by the compilation process and a proper binary is not produced. Another type is when the compilation process is successful but the binary file doesn't work correctly. This section outlines those two types of errors and tries to help resolve them, or at least the most common of them.

    Compilation Errors

    There are 3 stages in the bB compilation process where errors may be caught — in the preprocessor, the compiler or the assembler.

    Preprocessor Errors

    The preprocessor can only catch the most obvious of errors, such as unrecognized symbols in the code.

    An example of a preprocessor error:

    (34) unrecognized character: "@"

    Compiler Errors

    The compiler can catch a wider range of errors, but quite a few errors will sneak past the compiler and will only be caught by the assembler.

    An example of a compiler error:

    (34) Error: Unknown Keyword: hgoto

    A preprocessor and compiler error, if caught, will point you to the line of code (not linenumber, as linenumbers are not necessary) in the source file where it occurs. In the above examples, the error is found in the 34th line of code in your source file. This should help to pinpoint the cause and fix it easily.

    Note that compiler errors don't always make a lot of sense, but at least they usually point you to the right place.

    Assembler Errors

    Assembler errors are more cryptic and harder to find and fix. This is because they occur only after the Basic file is converted to assembly and linked together to a composite asm file. There are many things that can go wrong at this stage, but there are four common errors that can be caught and fixed without too much trouble.

    The four most common assembly errors are branches out of range, syntax errors, duplicate labels, and unresolved symbols.

      Branches out of Range

      A branch out of range is the easiest to resolve. This usually happens when an if-then statement is located too far away from its target. For example, "if a=1 then 40" will trigger this error if line 40 is far away from the if-then.

      There are two ways to resolve this. One is to use "if a=1 then goto 40." The only problem with this approach is that the assembler does not give you much help as to which line contains the error. You need to search the assembly file as described below, or track down the cause by trial and error or just change all relevant thens to then goto.

      The other approach is to enable smart branching, which will eliminate practically all such errors. To enable smart branching, include the following statement at the beginning of your program:

       set smartbranching on
      

      Smart branching is not enabled by default because it will complicate the assembly. Since the original vision of batari Basic was that the created .asm file could be studied to learn assembly language, complicating the assembly by default would conflict with that vision.

      Syntax Errors

      Often caused by a typographical error in a data statement, player or playfield declaration, inline asm or possibly other places. These are sometimes difficult to spot, and can often only be resolved by searching the created .asm file (see Searching the Assembly File below.)

      Duplicate Labels

      These occur when you use the same label or linenumber for two different lines of code. For example,

      10 a=4
      20 r=4
      10 e=10
      

      will compile but the assembler will claim there is a duplicate label (10.) The resolution is to look in your Basic source and change one of the labels to something else.

      Note that the assembler sometimes reports bogus duplicate labels, but usually assembly is successful anyway. This is considered an assembler bug. Sometimes, the assembler generates literally hundreds of them. For this reason, all but the first one have been suppressed from the output. Ignore duplicate labels if your game builds successfully, or if the game doesn't build but has a non-empty unresolved symbol list (see next topic).

      Unresolved Symbols

      Any time the assembler finds an error, it will list some unresolved symbols. If the list is empty, simply ignore it; the problem lies elsewhere. If there is something on the list, this is likely the cause of your error.

      The most common cause is a goto, if-then, or gosub target to a label that doesn't exist. The following example would cause such an error if line number 300 did not exist:

       goto 300
      

      It can also be caused by a function call to a non-existent function. The following would cause this error if myfunction was not defined:

       a=myfunction(33)
      

      Another cause occurs when a call to a routine in a module or bB internal function is made but the proper module is not included. For example, the statement "a=e/17" will produce "div8" as an unresolved symbol if the module "div_mul.asm" is not included.

      Any cause not listed above will likely only be found by searching the assembly file.

    Searching the Assembly File

    With most assembler errors, the line of code in the composite .asm file will be echoed in parentheses. This line will often be large (sometimes > 10000) so hand-counting the lines is not feasible — you will need to open the file a text editor utility that can jump to this line or at least tell you what line the cursor is on. If you don't have an adequate text editor in Windows, you can use the DOS 'EDIT' program, which will tell you what row of text the cursor is on.

    Once you jump to the offending line of code, look above for a line of bB code that will be inserted as a comment (after a semicolon.) This is usually where the error has occurred in your Basic source file. Note that the error may be subtle, but it is usually there somewhere.

    If you do not see any bB code nearby, the error is probably with a player or playfield definition. In this case, take a look at the data in the graphical object and find the corresponding bB code where you defined this object.

    If none of the above apply, the error could be with some inline asm you have inserted.

    Other Errors

    This section is intended to help find common errors where compilation is successful but the game doesn't work. This section will be expanded at a later date.

      Blank Screen

      Make sure you are calling drawscreen somewhere in your game loop (and that your game runs in a loop!) If you are, you probably didn't set colors, as they are all black by default. You use COLUP0 for player0 and missile0, COLUP1 for player1 and missile1, COLUPF for the playfield and ball, and COLUBK for the background.

      Players Use Score Color

      The score actually uses player objects, so COLUP0 and COLUP1 must be set during every frame or their colors will revert to that of the score.

      Timing problems

      My game jitters, shakes or rolls!

      Your program is spending too much time in the game loop. You have only about 2 milliseconds of time between successive calls to drawscreen, or about 2700 machine cycles. It doesn't seem like much, but with efficient coding you can get quite a bit in here.

      The drawscreen command must run 60 times a second, and it takes about 12 milliseconds to complete, as it renders the television display. Your program must run while the television picture is off of the screen and blanked out.

      The most common cause is probably too many calls to playfield plotting/scrolling routines or too many large loops, though other routines can also use up lots of time.

      The only solution to this problem is to reduce the complexity of your program between calls to drawscreen, either by calling it more than once throughout your program or spreading your calculations across several frames (calls to drawscreen, that is.) Note that calling drawscreen several times without also moving your objects each time will slow down your game.

      See drawscreen for more information about timing problems.

      Games written for earlier versions of bB don't work correctly

      bB 1.0 has many changes that might 'break' your game. Chances are, the problem is that the players are no longer in the correct places (specifically, 14 pixels to the right.)

      There is a new compiler directive (legacy) that can solve some of these problems. See legacy under compiler directives for more information.

      If legacy does not solve your problem and you can't figure out what to do, feel free to post a message to the Atari 2600 Basic forum on AtariAge.

Back to Top
Click on a topic below for help:
$ Operator
% Operator
Addition
Alias
Arithmetic Operators (+, -, *, /)
Arrays
asm
Assembly Language
Atari 7800
AUDC0
AUDC1
AUDF0
AUDF1
AUDV0
AUDV1
background (kernel_options)
ball
Bank Switching
BCD Compliant Number
Binary Numbers
Bit Operations
Bitwise (Logical) Operators
Boolean Operators (if-then)
BW/Color Switch
Collision Detection
Color Chart
Color/BW Switch
Colors
COLUBK
COLUP0
COLUP1
COLUPF
Compiler Directives
Console Switches
const
Constants
CTRLPF
cycles (debug)
cyclescore (debug)
Data Statements
debug (directive)
Decimal Numbers
Decision Making
Descriptive Names
Difficulty Switches
dim
Directives
Division
DOS Compatibility
drawscreen
else (if-then)
Ephemeral Variables & Registers
Fixed Point Variables
for-next
Functions (User-defined)
gosub
goto
Hacking batari Basic's .asm Files
Health Bar
Hexadecimal Numbers
Horizontal Lines
HUDs
if-then
include
includes file
Indenting
Indexing
inline
inlinerand (optimization)
Joysticks
Jumping Around
kernel (directive)
kernel_options (directive)
Labels
legacy (directive)
let
Life Counter
lifecolor (life counter/status bar)
Line Numbers
lives (life counter/status bar)
Loops
Math
Memory
Minikernels
Miscellaneous Commands
Missiles
Modulus Operation
Multicolored Sprites
Multiplication
Multisprite Kernel
Negative Numbers
next
no_blank_lines (kernel_options)
noinlinedata (optimization)
Noise
noscore
ntsc
Numbers
NUSIZ0
NUSIZ1
Objects
on … goto
optimization (directive)
pal
PF0
pfcolors (kernel_options)
pfheight (Multisprite Kernel)
pfheights (kernel_options)
pfhline (Playfield)
pfpixel (Playfield)
pfread (Playfield)
pfres (Playfield)
pfrowheight (Playfield)
pfscore (pfscore bars)
pfscore bars
pfscore1 (pfscore bars)
pfscore2 (pfscore bars)
pfscorecolor (pfscore bars)
pfscroll (Playfield)
pfvline (Playfield)
Player Graphics
player1colors (kernel_options)
playercolors (kernel_options)
Playfield
pop
rand
Random Numbers
readpaddle (kernel_options)
reboot
REFP0
REFP1
rem
Reset Switch
return
return otherbank
return thisbank
romsize (directive)
score
scorecolor
screenheight
sdata (Sequential Data)
Select Switch
Sequential Data
set
size (optimization)
smartbranching (directive)
Sound
speed (optimization)
Sprites
sread (Sequential Data)
Status Bar
statusbarcolor (status bar)
statusbarlength (status bar)
Subtraction
Superchip RAM
Switches
TIA Registers
Timing
Tone Chart
Troubleshooting
tv (directive)
Variables
vblank
Vertical Lines
Virtual Sprite Registers
Virtual Sprites
White Space