Random Terrain Try batari Basic

 

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

Frequently Used

Index

About this Page

Links that jump to other places on this page are blue. Links that lead to other pages online are red.

Inside Game Loop

If you see a red rectangle to the right of an item that looks like the image above, that means the item needs to be inside of your main game loop because it will be reset after a drawscreen. See Ephemeral Variables & Registers for more information.

 

Example Programs

 

Useful Code and Tips

 

Write Only

 

FAQ

Q. How do I program an Atari 2600 game?

 

A. Try batari Basic. Hop up to the top of this page and start there.

Q. How do you pronounce batari?

 

A. buh-tari

Q. How do I make the playfield blocks square or just make them smaller?

 

A. Squint your eyes? Just kidding. They are called playfield pixels and there's more than one way to do that. Check out the following links:

pfrowheight

pfres

pfheights (kernel_options)

pfheight (Multisprite Kernel)

Superchip RAM

Q. Why do people make Atari 2600 games these days?

 

A. There seems to be three main reasons why people make Atari 2600 games:

 

  1. They want to do something hard to challenge themselves, so they use assembly language.
  2. They're doing it for school.
  3. They had an Atari 2600 as a kid and always wanted to make their own Atari 2600 games because there's something special and magical about the graphics and sound effects that other consoles don't have.

 

The third type of person will usually want and need every tool, trick, and shortcut they can get. That's why they use batari Basic, Visual batari Basic, and various kernels, minikernels, modules and enhancements.

Q. Isn't it lazy to use batari Basic?

 

A. As it says in this post at AtariAge, it's hard to make a fun game that anyone would want to play, whether you use assembly language or batari Basic. There's a lot of crap out there made with assembly language, so using assembly language doesn't mean your game will be any good.

 

The time and energy you save by using batari Basic can be used on trying to make your game more fun. You can think of batari Basic as the lazy way out, but you still have to come up with the most unique concept that you can, write the program and create your sprite animations and sound effects. There's a lot of work to do if you want to create a polished game that hasn't already been done to death.

 

The tool you use doesn't really matter. What matters is that your game must be fun. And it also wouldn't hurt if it's fun while being fairly unique instead of just being another clone or port.

Useful Tools

batari Basic Code Cleaner

Paste your bB program into the text box, generate the indented code, copy and paste the result in a post at AtariAge and the crucial indentation will be preserved.

 

Atari 2600 Label Maker

Produces authentic looking Atari 2600 Labels including the famous "Picture Label" using your own image and title. Related link: Atari Cart & Box Fonts

 

bB Tools and Toys

Quickly convert hex, decimal, binary and more with the programming equivalents tool. There's also a crusty old playfield toy and a magic Firefox spell check box for REM statements.

 

WinMerge

If you use Save As every time you make a major change to your program and your latest version won't compile, but you made enough changes that you don't want to start over from the last save, don't worry. You can use WinMerge to compare the two files. You can see the differences and quickly track down the problem.

 

PhotoFiltre

An image retouching program that makes the kind of arrows you might see in an Atari 2600 game manual. Select the Line Tool and click on the arrow checkbox.

Related Links

 

Table of Contents

New to BASIC and Game Making?

Getting Started

IDE for batari Basic

Download the Manual

Test Games on a Real Atari

Things You Should Know

Indent!

Don't Use Line Numbers

Memory

Timing

White Space

DOS Compatibility

Miscellaneous

rem

reboot

end

Variables

Variable Testing

let (optional)

const

dim

def

Bit Operations

Nybble Variables

Improved Nybble Variables

Bitwise (Logical) Operators

Fixed Point Variables

Ephemeral Variables and Registers

Labels and Line Numbers

Jumping Around

goto

on … goto

gosub

on … gosub

return

pop

Decision Making — Brains of a Game

if-then

Boolean Operators (&&, ||, !)

else

Loops

for-next

next

Random Numbers

rand

Data Statements and Arrays

Sequential Data [sdata, sread]

Colors

COLUBK

COLUPF

COLUP0

COLUP1

scorecolor

Atari 2600 TIA Color Charts

Objects

Player Graphics (Sprites)

Missiles

Ball

The Playfield

Playfield Variable Chart

pfres

pfrowheight

drawscreen

vblank

pfclear

pfpixel

pfhline

pfvline

pfscroll

pfread function

score

pfscore bars

What is a BCD Compliant Number?

TIA Registers

NUSIZ0, NUSIZ1

CTRLPF

REFP0, REFP1

PF0

AUDVx, AUDCx, AUDFx

Collision Detection

Display kernel

Multisprite Kernel

Minikernels/HUDs

life counter/status bar

lives

lifecolor

statusbarlength

statusbarcolor

Writing Your Own Minikernel/HUD

Additional Kernels, Minikernels . . .

Compiler Directives (set command)

smartbranching

tv

romsize

optimization

kernel

kernel_options

readpaddle

player1colors & playercolors

no_blank_lines

pfcolors

pfheights

background

debug

legacy

Bank Switching

Superchip RAM

Sound

AUDVx, AUDCx, AUDFx

Tone Chart

Joysticks

Advanced Joystick Reading

2-Button Games

Console Switches

Numbers

Decimal Numbers

Hexadecimal Numbers

Binary Numbers

Negative Numbers

Math

Addition

Subtraction

Multiplication

Division

Modulus Operation

Functions

macro

Assembly Language (asm)

include

includes file

inline

Hacking batari Basic's .asm Files

Atari 7800

Troubleshooting

Memory Maps

Standard Kernel Memory Map

Multisprite Kernel Memory Map

Last Update: 2012y_01m_31d_1536t

 

Visit the Official batari Basic Forum at AtariAge

Useful AtariAge Links:

Posting Tips for New bB Users

How to Attach Files and Images

Easy Software Versioning

 

 

Note: Over 90 percent of the text on this page was written by batari. Some text is from AtariAge members such as SeaGtGruff, RevEng, and Robert M. Duane Alan Hahn (Random Terrain) only edits this page: adapts existing text, adds a bit of new text when needed, corrects errors when they are pointed out, and adds interactive charts and other goofy stuff.

 

 

 

 

New to BASIC and Game Making?

In case you haven't heard by now, batari Basic (bB) is a BASIC-like language for creating Atari 2600 games. The original beta version was released in 2005. Version 1.0 was released in 2007.

 

This page assumes that the user knows at least a little about BASIC in general. If you know very little about BASIC and know almost nothing about designing a game, the following links might be helpful:

 

If you need help, check out the batari Basic forum at AtariAge. The bB users there will not create a game for you, but they will help you in other ways. Before diving in, you might want to read these two posts first:

Posting Tips for New bB Users

How to Attach Files and Images

 

 

 

 

 

Getting Started

Until the main site is updated, the bBWin7_64bit.zip file in the Visual bB main post under Optional is the most current collection of bB files available. It's not just for 64-bit Windows, the files should work fine with any 32-bit versions of Windows as well. If you downloaded batari Basic from any other source and are having problems, download bBWin7_64bit.zip and see if that fixes things.

 

At this time, the instructions for setting up batari Basic on the main site are kind of out-of-date. If you're not using bBWin7_64bit.zip, you may need to copy or move sed.exe from the sed folder to the main bB folder, but you won't have to set bB's path and environment variable the hard way if you use Visual batari Basic. Just download the latest version of Visual batari Basic, go to the settings page and make sure Add bB Environment Variable and Add bB Compiler to System Path are checked and you'll be ready to go (you'll probably need to close VbB and restart it again). You can read more about the settings here.

 

If you need help, check out the batari Basic forum at AtariAge.

 

 

 

 

 

IDE for batari Basic

Visual batari Basic is an IDEIntegrated Development Environment
(Just about everything a bB programmer needs in one place.)
that makes it easier to create bB games. VbB has all kinds of useful features and tools. Among the cool features: you can easily create multicolored sprites and playfields and preview them with your favorite emulator. Visit the Visual batari Basic Guide for more information.

 

 

 

 

 

Download the Manual

Download the ZIP file below once in a while to be sure you'll always have the latest version of the official offline HTML manual. The ZIP file includes the HTML manual and a folder that contains files the manual needs. The Zip file is usually updated about once a month. Right click on the link below and select Save Link As or Save Target As to download the ZIP file:

Right click here to download the latest version of the official manual and updated folder containing images and example programs

 

 

 

 

 

Test Games on a Real Atari

If you'd like to test your games on a real Atari 2600, the Harmony Cartridge by batari is one of the easiest ways to do that. You don't have to plug or unplug anything. Just leave the Harmony CartridgeHarmony Cartridge
Harmony Cartridge
plugged into your console and the USB cable plugged into the cart, then download your work in progress for testing any time you want. It's as hassle-free as you can get. The Harmony cartridge also lets you use an SD card. Drop your own games on it or hundreds of your favorite classic Atari 2600 game files. Bankswitch type is auto-detected.

 

The deluxe edition includes a USB to mini-B programming cable and a formatted SD card. If you don't want to go hunting for the correct SD card and cable, the deluxe edition seems like the best choice.

 

Emulators can be great for testing, but among other things, your game colors will be darker and sprites can look surprisingly different on a real Atari. If you're even slightly serious about making Atari 2600 games, you'll want to buy a Harmony Cartridge as soon as possible.

 

Once your game is polished and bug-free, be sure to check out the Melody boards at AtariAge. Melody boards are stripped-down Harmony boards that can be distributed on their own dedicated cartridges. As it says at AtariAge, the Melody board "provides several advantages to the typical circuit boards used for new Atari 2600 homebrew games. Most notably, the Melody can be reprogrammed without being removed from the cartridge . . ."

 

 

 

 

 

Things You Should Know

     

    Indent!

    All program statements, data, and sprite/playfield pixel data must be indented by at least one space. (Labels, line numbers, and end must not be indented.)

    See Labels and Line Numbers, asm, and Compiler Errors for more information.

     

     

     

     

    You Don't Have to Use Line Numbers

    Line numbers were mandatory when using old style BASIC languages, but batari Basic is more like later versions of BASIC, so line numbers are no longer necessary. You can use line numbers if you want, but remember to use them like labels instead of putting them at the beginning of every line of code.

    See Labels and Line Numbers for more information.

     

     

     

     

    Memory

    The Atari 2600's microprocessor can only address 4K of ROM at a time, and the console 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 mitigateReduce, lessen, or decrease. 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.

     

     

      Related Link

      Squeezing the bBytes

      RevEng shares some of the techniques he has used to save ROM space.

     

     

     

     

    Timing

    Timing is crucial in batari Basic, in that you only have about 2 milliseconds between successive calls to drawscreen.

     

     

    Although bB has a useful debug directive, there are 4 ways you can use Stella (the Atari 2600 emulator) to make sure your program isn't taking too long and has a steady scanline count:

    Keeping Track of the Scanline Count with Stella

     

    See drawscreen, vblank and debug 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.

 

 

 

Miscellaneous

     

    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 BasicsRun line by line, directly from the source code without compiling first., you can use rem as much as you want and it will not affect the length or speed of your compiled program.

    Example:

       rem  ****************************************************************
       rem  *
       rem  *  Name: Space Nuggets
       rem  *  Version: 2010y_09m_04d_1821t
       rem  *
       rem  ****************************************************************
       rem  *
       rem  *  Programmer: Scadoobie Floinkenburger
       rem  *  Language: batari Basic v1.0
       rem  *  System: Atari 2600 VCS
       rem  *
       rem  ****************************************************************
    

    Remarks can also be added to data and sdata. In this case, rem is not used. No colon, no rem, just put a semicolon and your comment.

     

    Example:

       data musicData
       5,7,18
       0,0,0
       9
       2,7,18 ; Mango knee bone butter
       0,0,0
       3
       7,6,12 ; Elusive fractal salve
       0,0,0
       9
       2,6,12
       0,0,0
       3
       255 ; End of data
    end
    

     

     

     

     

    reboot

    This command will warm bootRestart a computer under software control. your game. Everything is cleared by reboot, 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
    

    It seems that reboot can also mess up random numbers, so you might want to have a Start/Restart section that you can jump to that clears and sets up variables, sprite positions, colors, and so on. Below is an example inspired by RevEng that will clear all normal variables:

       rem  ================================================================
       rem  ================================================================
       rem  =
       rem  =
       rem  =  Clears all normal variables.
       rem  =
       rem  =
       rem  ----------------------------------------------------------------
    
       for temp5 = 0 to 25 : a[temp5] = 0 : next
    

    If you are using Superchip RAM, you can use the example below:

       rem  ================================================================
       rem  ================================================================
       rem  =
       rem  =
       rem  =  Clears all normal variables and old playfield variables.
       rem  =
       rem  =
       rem  ----------------------------------------------------------------
    
       for temp5 = 0 to 25 : a[temp5] = 0 : next
    
       for temp5 = 0 to 47 : var0[temp5] = 0 : next
    

    If you're doing your best to make a professional-looking game that remembers the high score and other things from one game to the next, you'll want to use a modified version of the examples above so your important variables won't be cleared. You can safely use one of the examples above outside of your game, near the beginning of your code, after your dims and defs and so on, but before the Start/Restart section to make sure all of your variables have been cleared when the game first starts.

     

    See switchreset for more information.

     

    Warning: It seems that reboot causes random numbers to be not so random anymore, so if randomness is important to you, do not use reboot.

     

     

     

     

    end

    This is a unique command, as it is never indented. It is used to tell the compiler that you are finished with your data entry, graphics definition or inline assembly. That is, the data, sdata and asm commands, and any command normally ending with a colon (such as playfield:, player0:, lives:, and others).

 

 

 

 

 

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. Sometimes bit operations are too limited, but if you have some variables that never go over 15, you can use one variable as two. See Nybble Variables for more information.

If you do run out of variables, you can use four bytes from the playfield if you're not scrolling it (var44 through var47 in the standard kernel). You can also use temp1 through temp6 as temporary storage, but these are obliteratedDestroyed, erased. 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.

 

 

     

    How to Flip Between Two Numbers  (by RevEng)

    Sometimes it might be necessary to toggle a variable between two values. For example, you might need to flip between -1 (255) and 1. Well, XOR can be used to toggle a variable between any two chosen values.

     

    Picking 2 numbers out of a hat, say 12 and 96 . . .

     

    1. XOR them together with a calculator first, and note the result (12 ^ 96 = 108). [If you're using a software calculator that comes with Windows or Mac OS, switch to Scientific under the View menu for Windows or switch to Programmer under the View menu for Mac OS. Now enter the number 12, click the XOR button, enter the number 96, then click the = button and you'll end up with 108.]
    2. Set your variable to either 12 or 96 initially in your program.
    3. XOR the variable with your calculator value (108) every time you want it to flip to the other value. (because 12 ^ 108 = 96 and 96 ^ 108 = 12)

    Below are a few examples. Replace a with whatever variable or variable alias you want:

     

    Flip between -1 and 1

    Start out with  a = 255  or  a = 1

    Use the following to flip between the two values:

    a = a ^ 254

     

     

    Flip between 8 and 64

    Start out with  a = 8  or  a = 64

    Use the following to flip between the two values:

    a = a ^ 72

     

     

    Flip between 42 and 219

    Start out with  a = 42   or   a = 219

    Use the following to flip between the two values:

    a = a ^ 241

     

    Adapted from a post by RevEng at AtariAge.

     

     

     

     

    Variable Testing

    If you'd like to know the value of a variable when testing your game, you can use the score to display the value. The following example will display the values of two variables:

    Main_Loop
    
       rem  ****************************************************************
       rem  *  Your code to test goes here.
       rem  ****************************************************************
    
       scorecolor = $1A
    
       score = 0
    
       temp5 = a
       if temp5 > 0 then for temp6 = 1 to temp5 : score = score + 1000 : next
     
       temp5 = b
       if temp5 > 0 then for temp6 = 1 to temp5 : score = score + 1 : next
    
       drawscreen
    
       goto Main_Loop
    

    Just change a and b to whatever variables you want to check. The first variable will be displayed in the three digits of the left side of the score and the second variable will be displayed in the three digits of the right side of the score.

     

     

     

     

    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
    

     

     

     

     

    const

    A constant is a special type of variable that cannot be changed while a program is running. 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 Monster_Height = $12
    

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

     

    Older versions of batari Basic had a limit of 50 constants, but that limit was later increased to 500.

     

     

     

     

    dim

    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

      Did You Know?

      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.

      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 either be one of the 26 letters of the alphabet (uppercase or lowercase) or an underscore. Additional characters can be any combination of letters, numbers, or underscores. An alias 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 an alias next or pfpixel and although you could not use 'scorechange' you could use 'changescore'. To avoid any problems, you could always use an underscore as the first character of each alias.

      Examples:

         dim _Monster_xpos = a
         dim _Monster_ypos = 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 integersNumbers without a decimal (positive, negative or zero). Whole numbers. without ever using dim, you cannot use a-z as fixed point variables without using dim. See fixed point variables for more information.

     

     

     

     

    def  (by SeaGtGruff)

    The def statement is similar to the dim statement, but it lets you define an entire string and assign it to a logical name. So if you use def to assign 'a{0}' to 'MyVar,' then everywhere batari Basic sees 'MyVar' in your program, it will replace it with 'a{0}.'

     

    You may already know that dim won't let you assign a specific alias that only works for a certain bit, but the good news is that def will. Of course, the other restrictions and rules still apply, so you have to check it with 'if MyVar then,' or 'if !MyVar then,' instead of using 'if MyVar=0 then,' or 'if MyVar=1 then.'

     

    The def statement also works with math, such as 'def MyVar=a + b - c.' Everywhere batari Basic sees 'MyVar,' it will replace it with 'a + b - c.' But def shouldn't be confused with a function, because it's just doing a "search and replace" in the code, not calling a function. So def can help make your code more readable and concise, but it doesn't save any bytes or cycles.

     

    Although def and dim are totally separate, you can use them in combination if you want:

       dim game_flags = a
    
       def game_level=game_flags & $0F
       def show_title=game_flags{4}
       def game_in_play=game_flags{5}
       def game_over=game_flags{6}
       def show_hi_scores=game_flags{7}
    

    Note: For def to work properly, do not use a space before or after the equals sign.

     

     

     

     

    Bit Operations

Did You Know?

Bit operations do not use an equal sign in if-then statements.

 

So don't do this:

   if a{0} = 1 then gosub moosegizzard

Do this instead:

  if a{0} then gosub moosegizzard

And don't do this:

   if a{0} = 0 then gameover

Do this instead:

  if !a{0} then gameover

    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 8 bits of a byte are numbered 0 through 7, starting from right to left, with 0 being the least significant bit (LSB) or smallest.

     

    Bits 7 6 5 4 3 2 1 0

     

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

       a{0} = 1
       a{0} = 0
       if a{0} then gosub Moose_Gizzard
       if !a{0} then Game_Over
    

    Notice that bit operations do not use an equal sign in if-then statements. And remember that curly brackets {} are used, not round brackets or parentheses ().

     

     

    The example below shows how you can flip a bit:

       a{2} = !a{2}
    

    The bit is toggled from 0 to 1 or 1 to 0. Below is another example that also shows alternative ways to do the same thing:

     

    a{0} = !a{0}

     

    a{1} = !a{1}

     

    a{2} = !a{2}

     

    a{3} = !a{3}

     

    a{4} = !a{4}

     

    a{5} = !a{5}

     

    a{6} = !a{6}

     

    a{7} = !a{7}

    a = a ^ %00000001

     

    a = a ^ %00000010

     

    a = a ^ %00000100

     

    a = a ^ %00001000

     

    a = a ^ %00010000

     

    a = a ^ %00100000

     

    a = a ^ %01000000

     

    a = a ^ %10000000

    a = a ^ 1

     

    a = a ^ 2

     

    a = a ^ 4

     

    a = a ^ 8

     

    a = a ^ 16

     

    a = a ^ 32

     

    a = a ^ 64

     

    a = a ^ 128

     

    The XOR examples produce more compact assembly (thanks to RevEng).

     

     

     

    How to Randomize a Bit  (by RevEng)

    If you'd like to flip a bit randomly, RevEng has an easy solution for that. For example, if you want to randomize bit 0 of the variable a, here's how you do it:

       rem  ****************************************************************
       rem  *  Random number for a{0}
       rem  *
       temp5 = (rand & %00000001)
       a = a ^ temp5
    

    To randomize bit 7 of variable a, do this:

       rem  ****************************************************************
       rem  *  Random number for a{7}
       rem  *
       temp5 = (rand & %10000000)
       a = a ^ temp5
    

    To randomize other bits, all you have to do is move the 1 to the bit position you want to randomize and change the variable from a to whatever variable you're using.

     

    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.

     

    You can use dim to create a normal descriptive variable name and access the individual bits later.

    Example:

       dim my_variable = a
    
       my_variable{0} = 0
       my_variable{1} = 0
       my_variable{2} = 1
       my_variable{3} = 0
       my_variable{4} = 0
       my_variable{5} = 0
       my_variable{6} = 0
       my_variable{7} = 0
    
       rem  *  That will set my_variable to 4, or %00000100.
    

    Here's the same example with a different alias for each bit:

       dim Hero_Shot = a
       dim Hero_Thrust = a
       dim Hero_Crash = a
       dim Hero_Shield = a
       dim Enemy_Shot = a
       dim Enemy_Thrust = a
       dim Enemy_Crash = a
       dim Enemy_Shield = a
    
       Hero_Shot{0} = 0
       Hero_Thrust{1} = 0
       Hero_Crash{2} = 1
       Hero_Shield{3} = 0
       Enemy_Shot{4} = 0
       Enemy_Thrust{5} = 0
       Enemy_Crash{6} = 0
       Enemy_Shield{7} = 0
    
       rem  *  That will set a or any alias for a to 4, or %00000100.
    

    If you use dim to create descriptive variable names, don't try to 'dim' single bits.

     

    Bad examples:

       dim my_variable = a{1}
       dim my_variable{1} = a
       dim my_variable{1} = a{1}
    

    Instead of trying to use dim for that, you can use def.

     

    Example:

       def Hero_Shot=a{0}
       def Hero_Thrust=a{1}
       def Hero_Crash=a{2}
       def Hero_Shield=a{3}
       def Enemy_Shot=a{4}
       def Enemy_Thrust=a{5}
       def Enemy_Crash=a{6}
       def Enemy_Shield=a{7}
    
       Hero_Shot = 0
       Hero_Thrust = 0
       Hero_Crash = 1
       Hero_Shield = 0
       Enemy_Shot = 0
       Enemy_Thrust = 0
       Enemy_Crash = 0
       Enemy_Shield = 0
    
       rem  *  That will set a or any alias for a to 4, or %00000100.
    

    See def for more information.

     

     

     

    How to Check if Two Bits are Different

    The example below shows how you can tell if two bits are different:

       if a{0} then if !a{5} then goto __Pause_Game
    
       if !a{0} then if a{5} then goto __Pause_Game
    

    You might also want to try the solution that RevEng posted at AtariAge.

     

     

     

     

     

    Cycle Count by SeaGtGruff

    a{0} = 0 : rem * turn a bit off
    a{0} = 1 : rem * turn a bit on
    Each of these takes 8 cycles.

    a{0} = !a{0} : rem * flip a bit on or off
    If the bit is off (flipping to on), this takes 24 cycles.
    If the bit is on (flipping to off), this takes 23 cycles (or 24 cycles if a page-crossing occurs).

    a{0} = a{1} : rem * set one bit to another bit of the same variable
    a{0} = b{0} : rem * set one bit to another bit of a different variable
    If the bit being copied is off, this takes 23 cycles (or 24 cycles if a page-crossing occurs).
    If the bit being copied is on, this takes 24 cycles.

    a{0} = !a{1} : rem * set one bit to the opposite of another bit of the same variable
    If the bit being copied/flipped is off, this takes 24 cycles.
    If the bit being copied/flipped is on, this takes 23 cycles (or 24 cycles if a page-crossing occurs).

     

     

     

       

      Nybble Variables

      Sometimes bit operations are too limited and you'll need to use variables in the usual way, but you'll often find that the values of some of your variables barely reach double digits. If you know that some of your variable values will never go over 15, you can split one or more variables in half, so one variable will become two nybble variables. A while ago, batari said that he might be able to have batari Basic handle nybble variables automatically so you can use them like regular variables, but until that happens, you'll have to use the example by SeaGtGruff. Making your own nybble variables can become confusing, so be sure to put comments in your programs that explain what each nybble variable does and how it works.

       

       

      Nybble Me This, Batman!  (by SeaGtGruff)

      Months after this section was created, SeaGtGruff figured out a less confusing way to use nybble variables with macro and def. Below is the old example adapted from a post by SeaGtGruff in case you want to see it:

       

      Here's the .bin file to use with an emulator or Harmony cart:

      Nybble Me This playable .bin file

       

      Here's the bB code:

      Nybble Me This .bas file

       

       

       

      Improved Nybble Variables
      (by SeaGtGruff with help from RevEng and bogax)

      Below is the new and improved version using macro and def:

       

      Here's the .bin file to use with an emulator or Harmony cart:

      Improved Nybble Variables playable .bin file

       

      Here's the bB code:

      Improved Nybble Variables .bas file

       

       

      Notes:

      1. You can't use the same 'variable' name to get *and* set a nibble, because bB would get confused. So you need to 'def' one variable name for getting (reading) the nibble, and a different one for setting the nibble. [Note from RT: Since users of old BASIC languages remember PEEK (read) and POKE (write), I use those as prefixes. For example, if I want to have a nybble variable called Bonus_Item, I'll use PEEK_Bonus_Item and POKE_Bonus_Item. That way they'll be different while looking the same and I'll instantly know what they do.]
      2. To set the nibble, do *not* use an equal sign, just give the name of the 'def' that sets the nibble, followed by a space, followed by the value you want to set it to.
        Example: POKE_Space_Nugget 5
      3. To read the value of a nibble, just give the name of the 'def' that reads the nibble. Example: if PEEK_Space_Nugget = 5 then . . .
      4. If you want to increment or decrement a nibble, you'll need to use a temporary variable. (Parentheses are needed around the PEEK nybble or it won't work.)

        Example:

           temp5 = (PEEK_Game_Level) + 1
           POKE_Game_Level temp5
        

        These nybble variables can roll over from 15 to 0 just like a normal variable rolls over from 255 to 0. They can also roll in the other direction (from 0 to 15). Thanks to SeaGtGruff's use of macro and def, nybble variables are finally easy to use and the confusion factor has been virtually eliminated. The only thing that will be better is when batari finds the time to add real nybble variables that will be automatically handled by batari Basic. Until then, at least we have a great alternative.

       

      See the original post at AtariAge for more information.

       

      Splitting a variable into two nybble variables whenever possible is a great way to get more variables. Instead of having only 26 variables, you potentially have 52. Between nybble variables and bit operations, you could end up having more variables than you need.

       

       

       

       

      Bitwise (Logical) Operators

      There are three operators for logical operations in batari Basic that can be used to change the state of individual bits or to mask multiple bits. They are tokenized as:

      & = AND

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

      ^ = XOR  (exclusive OR)

      Examples:

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

       

       

       

         

        Bitwise Simplified  (by Random Terrain)

        If you are new to this stuff, it can be confusing and most explanations usually seem overly complicated. Maybe these simple explanations and examples will help:

         

        A bit is like a simple light switch. It can be on or off.

         

         

        AND = OFF      (0 = OFF    1 = UNTOUCHED)

        Use 0 to make sure the light switch is off.  Use 1 to leave it as it is.

         

        a = %10101010  :  a = a & %00001111

        a =
        & =
        a =
        1
        0
        0
        0
        0
        0
        1
        0
        0
        0
        0
        0
        1
        1
        1
        0
        1
        0
        1
        1
        1
        0
        1
        0

         

         

        OR = ON      (1 = ON    0 = UNTOUCHED)

        Use 1 to make sure the light switch is on.  Use 0 to leave it as it is.

         

        a = %10101010  :  a = a | %00001111

        a =
        | =
        a =
        1
        0
        1
        0
        0
        0
        1
        0
        1
        0
        0
        0
        1
        1
        1
        0
        1
        1
        1
        1
        1
        0
        1
        1

         

         

        XOR = FLIP      (1 = FLIP    0 = UNTOUCHED)

        Use 1 to reverse the position of the light switch.  Use 0 to leave it as it is.

         

        a = %10101010  :  a = a ^ %00001111

        a =
        ^ =
        a =
        1
        0
        1
        0
        0
        0
        1
        0
        1
        0
        0
        0
        1
        1
        0
        0
        1
        1
        1
        1
        0
        0
        1
        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 integersNumbers without a decimal (positive, negative or zero). Whole numbers. 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 Monster_x = 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.

     

     

     

     

     

     

    Ephemeral Variables and Registers (short-lived)

    By ephemeralLasting for only a short time., we mean variables or TIA registersTelevision Interface Adaptor Registers
    Locations that hold temporary data similar to variables.
    that are routinely obliteratedDestroyed, erased. 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:

    Inside Game Loop

    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.

 

 

 

 

 

Labels and Line Numbers

Alphanumeric labelsCan be made up of letters, numbers, and underscores. are supported by batari Basic. 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'. To avoid any problems, you could always start a label with an underscore or two. I (Duane Alan Hahn) started using one underscore for variable aliases and two underscores for labels and I'm liking it so far.

Example of the various ways you can use labels and line numbers:

10 pfpixel 2 3 on

20

   drawscreen

   player0x = player0x + 1 : goto __My_Location

   player0y = 29 : goto My_Location_2

__My_Location

   x = x + 1

My_Location_2 x = x - 1

 

 

 

 

 

Jumping Around

     

    goto

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

    Examples:

       goto 100
       goto My_Subroutine
    

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

    Example:

       goto Move_Monster bank2
    

     

     

     

    Cycle Count by SeaGtGruff

    goto   =   3 cycles

    goto with bankswitch   =   49 cycles

     

     

     

     

    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 ordinalIn order. For example, first, second, third, and so on. 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
    

    You cannot use expressions in an on … goto statement.

     

    For example:

       on x-8 goto 100 200 300 400
    

    would need to be replaced with something like this:

       y = x - 8 : on y goto 100 200 300 400
    

    The on … goto statement can jump only within the current bank. If you are writing a bank-switched game and want to jump to a label in another bank, you can do it from the places that on … goto jumps to.

    Example:

       on x goto Red Green Blue Purple
    
    Red
       goto Red02 bank2
    
    Green
       goto Green02 bank2
    
    Blue
       goto Blue02 bank3
    
    Purple
       goto Purple02 bank3
    

    Warning: There is no automatic checking to see if your variable's value coincides with a label in the list. In the above example, there is no label in the list to jump to if x is equal to 4 or more, but the statement will try to find a label for it anyway, which almost always means that your program will crash. You can check your variable before the on … goto to prevent this.

     

    Example:

       if x < 4 then on x goto 100 200 300 400
    

    If you have too many jumps to put comfortably in a single on … goto statement, you can break it up into multiple on … goto statements. Example:

       if x < 4 then on x goto 100 200 300 400
       y = x - 4 : if y < 4 then on y goto 500 600 700 800
       y = x - 8 : if y < 4 then on y goto 900 1000 1100 1200
    

     

     

     

     

    gosub

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

    Examples:

       gosub 100
       gosub My_Subroutine
       if x > 10 then gosub Sink_Ship
    

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

    My_Subroutine
       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 Move_Monster bank2
    

     

     

     

    Cycle Count by SeaGtGruff

    gosub + return   =   12 cycles

    gosub with bankswitch + return   =   122 cycles

    gosub with bankswitch + return otherbank   =   110 cycles

     

     

     

     

    on … gosub

    This works very much like on … goto, and has similar restrictions, but control returns back to the statement following the on ... gosub list when a return is encountered.

    For example:

       on x gosub 100 200 300 400
       drawscreen
    

    The above example will return control to the drawscreen statement following the on … gosub list when a return is encountered.

     

    You cannot use expressions in an on … gosub statement.

     

    For example:

       on x-8 gosub 100 200 300 400
    

    would need to be replaced with something like this:

       y = x - 8 : on y gosub 100 200 300 400
    

    Warning: There is no automatic checking to see if your variable's value coincides with a label in the list. In the above example, there is no label in the list to jump to if x is equal to 4 or more, but the statement will try to find a label for it anyway, which almost always means that your program will crash. You can check your variable before the on … gosub to prevent this.

     

    Example:

       if x < 4 then on x gosub 100 200 300 400
    

    If you have too many jumps to put comfortably in a single on … gosub statement, you can break it up into multiple on … gosub statements. Example:

       if x < 4 then on x gosub 100 200 300 400
       y = x - 4 : if y < 4 then on y gosub 500 600 700 800
       y = x - 8 : if y < 4 then on y gosub 900 1000 1100 1200
    

     

     

     

       

      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. Another thing to remember is that 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.

 

 

 

 

 

Decision Making — Brains of a Game

Did You Know?

You are allowed only one OR for each if-then statement. You can use more than one AND in a line, but you cannot mix AND with OR.

 

The NOT operator can only be used with statements that do not include OR or a comparison token (<, >, =). See Boolean Operators for more information.

 

Multiple if-thens in a single line might not work correctly if you are using AND, OR, or NOT.

 

 

AND/OR/NOT Chart

 

 

AND

More than one can be used.

 

Cannot be used with OR.

 

Safe to use with:

 

NOT

 

< > =

 

 

 

 

OR

Only one can be used.

 

Cannot be used with AND.

 

Cannot be used with NOT.

 

Safe to use with:

 

< > =

 

 

 

 

NOT

More than one can be used.

 

Cannot be used with OR.

 

Cannot be used with < > =.

 

Safe to use with:

 

AND

 

 

 

 

 

AND = &&    OR = ||    NOT = !

 

     

    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 obfuscateConfuse, darken, make unclear. the generated assembly file, so do not use it if you plan to examine the assembly later to see how it works. Smartbranching will not slow down your program, so you don't have to be afraid to use it. See smartbranching for more information.

     

    There are three types of if-then statements.

     

    #1 Simple True/False Evaluation

    The first type is a simple check where the condition is a single statement.

     

    The following example diverts program flow to line 20 if a is anything except zero:

       if a then 20
    

    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.

     

    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.

     

     

    #2 Simple Comparison

    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
    

     

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

    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.

     

     

     

    Relief for ENDIF Addicts
    For those who are accustomed to using endif in other programming languages, you don't have to resort to using subroutines to deal with many lines of code. All you have to do is reverse the condition and add a label.

     

    So instead of endif, which you can't use in batari Basic:

       if x = 4 then
    
       x = t - 2 : mosterheight = (monsterheight&7) + 1
    
       pfhline 3 4 x off : a = (rand&127) + 16 : r{0} = 1
    
    endif
    

    or gosub:

       if x = 4 then gosub Monster_Kill
    
       . . .
    
       . . .
    
       . . .
    
    Monster_Kill
    
       x = t - 2 : mosterheight = (monsterheight&7) + 1
    
       pfhline 3 4 x off : a = (rand&127) + 16 : r{0} = 1
    
       return
    

    You'd use this instead:

       if x <> 4 then goto Skip_Monster_Kill
    
       x = t - 2 : mosterheight = (monsterheight&7) + 1
    
       pfhline 3 4 x off : a = (rand&127) + 16 : r{0} = 1
    
    Skip_Monster_Kill
    

    If you're using boolean operators, just invert each individual condition and change && to || or vice-versa. So this:

       if x = 4 && y = 2 then r = 4
    

    becomes this:

       if x <> 4 || y <> 2 then goto Skip_Ninja_Stab
       r = 4
    Skip_Ninja_Stab
    

    And this:

       if r < 4 then e = e + 1
    

    becomes this:

       if r >= 4 then goto Skip_Fish_Jump
       e = e + 1
    Skip_Fish_Jump
    

       

       

       

       

      Boolean Operators

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

      && = AND

      || = OR

      ! = NOT

      You may use && or || in an if-then 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 OR (||) or a comparison token (such as =, <, >, or <>). See the Did You Know? if-then box for more information.

      Examples:

         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.

 

 

 

 

 

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
    

    It might be tempting to use a for-next loop with drawscreen in it to slow down a game, but it's better to use a counter since you can slow down specific things while letting other things run at full speed.

       

       

       

       

      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 preceding 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.

 

 

 

 

 

Random Numbers

Randomness will help make your games replayable and more fun. Players will get a fresh experience every time they play your games and they'll really be playing instead of memorizing an exact way to 'beat' your game that is set in stone. Unrestrained randomness would make your games impossible to play, so you need to control it. You can use Controlled Randomness to place non-player characters, bonus items, bonus areas, rooms, and more in a certain number of predetermined positions or general areas where you stay in control and nothing will come up in unexpected places. Your games will have replay value and most players will appreciate it.

     

     

     

    rand

Did You Know?

If you need a quick random number within a certain range, you can use AND (&) with the numbers 1, 3, 7, 15, 31, 63, or 127 (shown in the chart below). You can read more about it in a post by RevEng at AtariAge.

 

Faster Way

Binary

Range

Slower Way

a = (rand&1)

00000001

0 to 1

a = (rand/128)

a = (rand&1) + 1

 

1 to 2

a = (rand/128) + 1

a = (rand&3)

00000011

0 to 3

a = (rand/64)

a = (rand&3) + 1

 

1 to 4

a = (rand/64) + 1

a = (rand&7)

00000111

0 to 7

a = (rand/32)

a = (rand&7) + 1

 

1 to 8

a = (rand/32) + 1

a = (rand&15)

00001111

0 to 15

a = (rand/16)

a = (rand&15) + 1

 

1 to 16

a = (rand/16) + 1

a = (rand&31)

00011111

0 to 31

a = (rand/8)

a = (rand&31) + 1

 

1 to 32

a = (rand/8) + 1

a = (rand&63)

00111111

0 to 63

a = (rand/4)

a = (rand&63) + 1

 

1 to 64

a = (rand/4) + 1

a = (rand&127)

01111111

0 to 127

a = (rand/2)

a = (rand&127) + 1

 

1 to 128

a = (rand/2) + 1

 

It seems AND (&) uses the fewest cycles compared to other techniques that have been posted at AtariAge. For example, a = (rand&7) is faster than a = (rand/32).

 

Notice that adding a number changes the minimum and maximum numbers. For example, a = (rand&7) + 8 would give you a random number between 8 (0 + 8 = 8) and 15 (7 + 8 = 15).

 

If you need a random number that is either -1 or 1, try this:

a = 255 + (rand&2)

 

Randomizing a bit is easy thanks to RevEng. Check it out.

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

    The rand function returns a pseudo-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 inlinerand for more information.)

     

     

     

 

 

 

 

Did You Know?

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

 

 

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 the reading of values beyond data tables, 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

These data _length constants will not work correctly if they are used before you declare the corresponding data statement. If you need to use a data _length constant before its data statement, declare the data _length constant near the beginning of your program (using the name of the constant as the value).

 

Example:

   const mydata_length=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.

 

 

    Related Link

    RAM Array

    SeaGtGruff talks about RAM arrays at AtariAge.

     

     

     

     

    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). SequentialIn a row or in order without gaps. 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.

 

 

 

 

Did You Know?

You might think you have chosen the perfect colors when testing your games using an emulator, but Atari 2600 colors are darker on televisions and some of the darker grays may appear to be black. For example, if you are using 2, 4, and 6; bump it up to 6, 8, and 10 if you want a picture that will display brightly enough on most televisions.

 

 

Colors

The Atari 2600 can display up to 128 unique colors — that is, 16 unique hues and 8 luminosityBrightness. levels for each hue for NTSCNational Television Systems Committee
(Or jokingly, Never The Same Color.)
consoles (see tv directive.) PALPhase Alternation Line
(Or jokingly, Picture Always Lousy or Pay Another Licence.)
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 = $80.

     

    Write Only: COLUBK cannot be read. For example, a = COLUBK will not work properly.

     

     

     

     

    COLUPF  (Color-Luminosity Playfield, Ball)

    Sets the playfield color and ball color. Example: COLUPF = $CE. If you use pfscore bars, you'll need to have COLUPF in your main loop or the playfield will become the color of the pfscore bars. For a multicolored playfield, see pfcolors or the kernel_options chart.

     

    Write Only: COLUPF cannot be read. For example, a = COLUPF will not work properly.

     

     

    Inside Game Loop

     

     

    COLUP0  (Color-Luminosity Player0, Missile0)

    Sets the color for player 0 and missile 0. Example: COLUP0 = $0A. See Ephemeral Variables & Registers for more information.

     

    Write Only: COLUP0 cannot be read. For example, a = COLUP0 will not work properly.

     

     

    Inside Game Loop

     

     

    COLUP1  (Color-Luminosity Player1, Missile1)

    Sets the color for player 1 and missile 1. Example: COLUP1 = $FE. See Ephemeral Variables & Registers for more information.

     

    Write Only: COLUP1 cannot be read. For example, a = COLUP1 will not work properly.

     

     

     

     

    scorecolor

    Sets the score color so it will be visible. Example: scorecolor = $68. See score for more information.

     

     

     

     

    Atari 2600 TIA Color Charts

    The color charts below are interactive. Click on a color and the hexadecimal color value will be displayed. (Based on Glenn Saunder's HTML TIA color charts.)

     

    NTSC  (128 unique colors)

    PAL  (104 unique colors)

    SECAM  (8 unique colors)

    Color Value:

     

     

    0

    2

    4

    6

    8

    A

    C

    E

    0

    1

    2

    3

    4

    5

    6

    7

    8

    9

    A

    B

    C

    D

    E

    F

    Color Value:

     

     

    0

    2

    4

    6

    8

    A

    C

    E

    0

    1

    2

    3

    4

    5

    6

    7

    8

    9

    A

    B

    C

    D

    E

    F

    Color Value:

     

     

    0

    2

    4

    6

    8

    A

    C

    E

    0

    1

    2

    3

    4

    5

    6

    7

    8

    9

    A

    B

    C

    D

    E

    F

 

 

      Related Link

      TIA Color Charts and Tools

      Includes an NTSC/PAL color conversion tool and Atari 2600 color compatibility tools that can help you quickly find colors that go great together (possibly saving you a lot of time and energy).

 

 

 

 

Did You Know?

You will not be able to precisely duplicate sprites from classic games because bB uses double-height pixels. The double-height pixels were used because it gives more time in the kernel for features such as the asymmetric playfield.

 

The good news is that the new DPC+ kernel uses single-height sprite pixels, so you'll be able to recreate the more detailed classic look.

 

 

 

Enemy AI Links

Enemy AI

Tips from AtariAge members.

 

Artificial Brilliance - Television Tropes & Idioms

Artificial Brilliance is, quite simply, the ability of the computer characters to make the player think "Hey, these guys are actually pretty smart!"

 

A* Pathfinding for Beginners

The A* (pronounced A-star) algorithm can be complicated for beginners. While there are many articles on the web that explain A*, most are written for people who understand the basics already. This article is for the true beginner.

 

The Secrets Of Enemy AI In Uncharted 2

Most games have some form of AI and regardless of the genre of game, there are fairly common requirements that have to be met.

 

Enemy AI by Annie Dickerson

The first time I heard of a game designer working on enemy AI, I thought, pfft, how hard could it be?

 

A study in stealth: How AI shapes the genre

Twelve men were stationed on that boat; one-by-one, I dragged them to their doom. Even the last one strolled around an empty boat devoid of fellow guards and remained utterly oblivious until my hand snaked up and caught his belt.

 

 

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 want to edit sprite data right there in your code instead of using a sprite editor, you can use Visual batari Basic to temporarily flip the sprite data right-side-up. Then you can flip the sprite data back upside down when you're done.

    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. For multicolored sprites, see the kernel_options chart and player1colors/playercolors.

    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

    Two missiles can be displayed on the screen. These are simply verticalUp-Down
    Up and Down
    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.

    Example:

       missile0x = 64
       missile0y = 64
       missile0height = 8
    
       missile1x = 64
       missile1y = 80
       missile1height = 8
    
    
    Main_Loop
    
       COLUP0 = 140
       COLUP1 = 28 
    
       NUSIZ0 = $30
       NUSIZ1 = $30
    
       drawscreen
    
       goto Main_Loop
    

    In the multisprite kernel, the missiles are fixed at one unit high, and the missile height variables do not exist. Also remember that one or both of the missiles can become unavailable depending on the kernel options you select. See kernel options for more information.

     

    There are more things you can do with missiles by modifying the NUSIZx TIA registers. See NUSIZ0, NUSIZ1 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.

    Example:

       ballx = 64
       bally = 64
    
       ballheight = 4 : rem * Ball 4 pixels high.
    
       CTRLPF = $21 : rem * Ball 4 pixels wide.
    
       COLUPF = $1E : rem * Ball/playfield color.
    
    Main_Loop
     
       drawscreen
    
       goto Main_Loop
    

    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 CTRLPF for more information.

     

     

 

 

 

 

 

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). Below is a standard kernel playfield variable chart for advanced users. There are 4 variables for each row (8 bits x 4 = 32).

Did You Know?

The second and fourth playfield variables of each row have a reversed bit order. If you don't know they are reversed, you'd expect that

"var0=1 : var1=1 : var2=1 : var3=1"

or

"var0=$01 : var1=$01 : var2=$01 : var3=$01"

or

"var0=%00000001 : var1=%00000001 : var2=%00000001 : var3=%00000001"

would give you this:

. . . . . . . X . . . . . . . X . . . . . . . X . . . . . . . X

But you wouldn't get that. You'd really get this:

. . . . . . . X X . . . . . . . . . . . . . . X X . . . . . . .

As long as you remember that the second and fourth playfield variables of each row have a reversed bit order, you won't rip your hair out in frustration or wonder if you have lost your mind.

Row 0   var0 var1 var2 var3
Row 1   var4 var5 var6 var7
Row 2   var8 var9 var10 var11
Row 3   var12 var13 var14 var15
Row 4   var16 var17 var18 var19
Row 5   var20 var21 var22 var23
Row 6   var24 var25 var26 var27
Row 7   var28 var29 var30 var31
Row 8   var32 var33 var34 var35
Row 9   var36 var37 var38 var39
Row 10   var40 var41 var42 var43
Row 11   var44 var45 var46 var47


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. HorizontalLeft-Right
Left and Right
scrolling is not possible, but support for verticalUp-Down
Up and Down
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 (PF0 = %10000000) See PF0 for more information. 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

Because of a bug in the preprocessor, <off> and <on> do not work at this time, but when the problem is fixed in a future update of bB, they will be optional and 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.

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 RAM for more information.

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 since 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 a single, shared row height, you can specify the height of individual playfield rows by setting the pfheights kernel option.

     

     

     

     

    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.

     

     

     

Did You Know?

Although vblank looks similar to a normal subroutine, you shouldn't try to jump to it using gosub. It is placed at the end of your program or in the last bank of a bank-switched game (outside of any loops) and runs automatically every time a drawscreen is called. If you're using bank switching, remember that vblank is special, so don't replace vblank's return with return otherbank.

 

       

      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.

      Kernel setup routines in bB are run 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). Since vblank is a subroutine, you need to end it with a return. And since you will not be calling the subroutine yourself, you do not need to use a label or line number.

       

      Example:

      Main_Loop
      
         COLUBK = $C4 : rem  *  Green
      
         drawscreen
      
         goto Main_Loop
      
      
         vblank
      
         COLUBK = $44 : rem  *  Red
      
         return
      

      Although COLUBK is in the main loop trying to force the background color to be green, the color red in vblank overrides it.

       

      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) and it must end with return, not return otherbank.

      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".)

     

     

     

     

    pfclear (playfield clear)

    Clears the playfield in the standard kernel or can optionally be used with an argument to fill playfield RAM with the specified value.

     

    Example:

       pfclear
    

    The following example will completely fill the playfield RAM with binary value 10101010:

       pfclear %10101010
    

    Warning: pfclear does not work with the Multisprite Kernel.

     

     

     

     

    pfpixel (playfield pixel) •

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

     

    pfpixel xpos ypos function   (X-across animation by Duane Alan Hahn | Y-vertical animation by Duane Alan Hahn)

    xpos can be 0-31 and ypos can be 0-11 (11 is hidden off of the screen and only seen if scrolled.) If you use Superchip RAM and pfres, you can have more than 11 rows.

    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.

    Examples:

       pfpixel 16 2 on
       pfpixel 8 4 off
       pfpixel 24 8 flip
    

    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)  Left-Right

    This draws a horizontalLeft-Right
    Left and Right
    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   (X-across animation by Duane Alan Hahn | Y-vertical animation by Duane Alan Hahn)

    xpos can be 0-31 and ypos can be 0-11 (11 is hidden off of the screen and only seen if scrolled). If you use Superchip RAM and pfres, you can have more than 11 rows.

    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.

    Examples:

       pfhline 0 0 31 on
       pfhline 4 2 8 off
       pfhline 2 8 24 flip
    

    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)  Up-Down

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

    pfvline xpos ypos endypos function   (X-across animation by Duane Alan Hahn | Y-vertical animation by Duane Alan Hahn)

    xpos can be 0-31 and ypos can be 0-11 (11 is hidden off of the screen and only seen if scrolled). If you use Superchip RAM and pfres, you can have more than 11 rows.

    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.

    Examples:

       pfvline 0 0 10 on
       pfvline 31 4 8 off
       pfvline 8 2 4 flip
    

    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

     

    Example:

       pfhline 8 5 23 on
    
       COLUBK = $C4
       COLUPF = $CE
    
    Main_Loop
    
       if joy0up then pfscroll up
       if joy0down then pfscroll down
       if joy0left then pfscroll left
       if joy0right then pfscroll right
    
       drawscreen
    
       goto Main_Loop
    

    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.

     

     

     

    Score Background Color  (by RevEng)

    If you'd like to have a separate background color for the thin strip where the score is located, RevEng at AtariAge figured out a way. Put the following in the last bank, or if you're not using bank switching, just put it at the end of your code, outside of any loops:

       asm
    minikernel
       sta WSYNC
       lda scback
       sta COLUBK
       rts
    end
    

    Then you can dim scback and use it to change the color behind the score whenever you want. The example below uses the variable z, but you can use any variable:

       rem  ****************************************************************
       rem  *  Assign a variable to the score background.
       rem  *
    
       dim scback = z
    
    
       rem  ****************************************************************
       rem  *  Use scback whenever you need to change the score background.
       rem  *
    
       scback = $C4
    

    There is one thing you'll need to remember when using scback. COLUBK must be used inside of your main loop or the rest of the background will be the same color as the score background.

     

     

    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 the 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
    

    The example above does not use up any of your variables. Starting from left to right, sc1 holds the 100 thousands and 10 thousands digits, sc2 holds the thousands and hundreds, and sc3 holds the tens and ones. 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 ...
    

     

     

     

    Saving a High Score  (by supercat and Nukey Shay)

    Here's an example that would check for a new high score and save it:

       if sc1 > High_Score01 then New_High_Score
       if sc1 < High_Score01 then Skip_High_Score
    
       rem  *  First byte equal. Do the next test.
    
       if sc2 > High_Score02 then New_High_Score
       if sc2 < High_Score02 then Skip_High_Score
    
       rem  *  Second byte equal. Do the next test.
    
       if sc3 > High_Score03 then New_High_Score
       if sc3 < High_Score03 then Skip_High_Score
    
       rem  *  All bytes equal.
    
       goto Skip_High_Score
    
    New_High_Score
    
       High_Score01 = sc1 : High_Score02 = sc2 : High_Score03 = sc3
    
    Skip_High_Score
    

    Below is an example program that shows how to save the high score until the game is turned off. Move the joystick up to increase the score. Press the fire button to leave the main loop and the score will flip between the current score and the high score every 2 seconds. Press the fire button or reset switch to go back to the main loop.

     

    Here's the .bin file to use with an emulator or Harmony cart:

    Save High Score playable .bin file

     

    Here's the bB code:

    Save High Score .bas file

     

    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 batari Basic forum at AtariAge.

     

     

     

       

      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 = 21
         pfscore2 = 255
      

      You might find that binary is more intuitive. The following example is the binary version of the example above:

         pfscore1 = %00010101
         pfscore2 = %11111111
      

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

       

       

      Lives Bar Examples

      To decrement a life, use this:

         pfscore1 = pfscore1/4
      

      To increment a life starting at bit 1 (example: pfscore1 = %00101010), use this:

         pfscore1 = pfscore1*4|2
      

      To increment a life starting at bit 0 (example: pfscore1 = %00010101), use this:

         pfscore1 = pfscore1*4|1
      

       

       

      Health Bar Examples

      To decrement the health bar, use this:

         pfscore2 = pfscore2/2
      

      To increment the health bar, use this:

         pfscore2 = pfscore2*2|1
      

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

       

      You are not limited to using pfscore1 for lives and pfscore2 as a health bar. You can switch their purposes or have two sets of dots or two health bars. It depends on how you set them up and if you divide by 2 or 4. If the bar is filled, divide by 2. If the bar has dots, divide by 4. Below is a working example program that might be helpful.

       

       

         

        DAH

        pfscore Lives and Health Example

        When the program starts, there will be two icons that stand for Lives and Health. Move the joystick left to choose the type of bar you want on the left and move the joystick right to choose the type of bar on the right. When you are done, press the fire button.

         

        You will now see your choices near the bottom of the screen.

         

          Left Bar: Move the joystick right to decrease and left to increase.

           

          Right Bar: Move the joystick down to decrease and up to increase.

         

        Hit the reset switch if you want to restart the program.

         

         

        Here's the .bin file to use with an emulator or Harmony cart:

        pfscore Lives and Health Toy playable .bin file

         

        Here's the bB code:

        pfscore Lives and Health Toy .bas file

       

       

      Note: 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.

       

      Warning: Remember that pfscore bars are not the same as the Life Counter and Status Bar minikernels. It might be confusing at first.

       

       

       

       

      noscore

      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 and $23 is the BCD number for decimal 23. Although a dollar sign is used, the usual A, B, C, D, E, and F letter digits of a normal hexadecimal number are not used. For example, there is no BCD number for $3E since it contains a non-decimal value (the E). If 'a' contained $3E, the score would end up incorrect or garbled. The number must always look like a two-digit decimal number with a dollar sign on the left side of it.

       

       

       

      BCD Compliant Numbers and Variables  (by RevEng)

      A time may come when you'll need to add to a variable (a = a + 5), then add that variable to the score (score = score + a). It won't be long before the score will become incorrect or garbled. There is a simple fix for that provided by RevEng at AtariAge:

         asm
         sed ; set decimal mode
      end
      
         a = a + $05
      
         asm
         cld ; clear decimal mode
      end
      
         score = score + a
      

      You can copy and paste that into your program and replace 'a = a + $05' and 'score = score + a' with whatever variable and value you want to use. Just remember that the number you add to your variable must be a BCD compliant number (looks like a two-digit decimal number with a dollar sign next to it).

       

       

       

      Points Roll Up  (by Nukey Shay)

      There's another solution from Nukey Shay at AtariAge that does not require a BCD compliant number, where the point value will roll up instead of instantly becoming a new total. Here's an example:

         dim Debounce_Fire = d
         dim Points_Roll_Up = z
      
         dim sc1 = score
         dim sc2 = score+1
         dim sc3 = score+2
      
      
      Main_Loop
      
         scorecolor = $1A
      
      
         if !joy0fire then Debounce_Fire{2} = 0 : goto Skip_Fire
      
         if Debounce_Fire{2} then Skip_Fire
      
         Debounce_Fire{2} = 1
      
         a = a + 5
      
         if a > 30 then a = 100
      
         Points_Roll_Up = Points_Roll_Up + a
      
      Skip_Fire
      
      
         rem  ********************************************************
         rem  *  Points roll up code from Nukey Shay. 
         rem  *
         rem  '
         asm
         lda  Points_Roll_Up
         beq  No_Points_To_Add
         dec  Points_Roll_Up
         sed
         clc
         lda  sc3
         adc  #1
         sta  sc3
         lda  sc2
         adc  #0
         sta  sc2
         lda  sc1
         adc  #0
         sta  sc1
         cld
      No_Points_To_Add:
      end
      
         drawscreen
      
         goto Main_Loop
      

      The only drawback to the method above is that 'Points_Roll_Up' can never contain more than 255 points.

       

      Warning: Do not try to use a BCD compliant number with the points roll up example above. It's unnecessary and it may give you an incorrect score. For example, 'a = a + $10' would add 16, not 10 because it would be used as a hexadecimal number instead of a BCD compliant number.

       

       

       

     

    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.

    Inside Game Loop

     

     

       

      NUSIZ0, NUSIZ1

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

      Value

      Effect

      $0x (x = 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=$33 will make missile0 8 pixels wide, plus make three close copies of player0 and missile0.

       

      Write Only: NUSIZ0 and NUSIZ1 cannot be read. For example, a = NUSIZ0 will not work properly.

       

       

       

       

       

      CTRLPF

      Changes properties of the playfield and/or ball.

      Value

      Effect

      $0x (x = 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.

       

      Write Only: CTRLPF cannot be read. For example, a = CTRLPF will not work properly.

       

       

       

       

       

      Inside Game Loop

       

       

       

      REFP0, REFP1

      Reflects player sprites. (Flips them horizontallyLeft-Right
      Left and Right
      .)

      Value

      Effect

      0 do not reflect
      8 reflect

       

      This is useful for asymmetric spritesSprites that are irregular or lopsided. Not a perfect mirror image on both sides. so that they can give the appearance of changing direction without needing to redefine their graphics.

       

      Write Only: REFP0 and REFP1 cannot be read. For example, a = REFP0 will not work properly.

       

      Inside Game Loop

       

       

       

      PF0

      Set or clear the left and right 10% of the playfield. PF0 must be inside of your game loop.

      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.

       

      Write Only: PF0 cannot be read. For example, a = PF0 will not work properly.

       

       

      More About PF0:

      The side areas are mirrored and each side can have up to four columns. PF0 is easier to deal with if you use a binary number. The first four digits in the number are the ones that matter. For example, PF0 = %11110000 will give you a thick side border (made of four columns) and PF0 = %10000000 will give you a thin side border right next to the playfield (made of one column).

       

      Example with one column next to playfield:

         COLUBK = 132 
         COLUPF = 30
      
         playfield:
         XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
         ................................
         XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
         ................................
         XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
         ................................
         XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
         ................................
         XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
         ................................
         XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
      end
      
      Main_Loop
      
         PF0 = %10000000
      
         drawscreen
      
         goto Main_Loop
      

      PF0 = %10000000 in the example above can be replaced with other combinations. Below are two examples.

       

      Column farthest away from the playfield:

         PF0 = %00010000
      

      Four columns:

         PF0 = %11110000
      

       

       

      AUDV0, AUDC0, AUDF0, AUDV1, AUDC1, AUDF1

      See sound for more information about these.

 

 

 

 

Did You Know?

You need to place your drawscreen before the section of code that checks for collisions (the Atari 2600 has to draw the frame for the collision registers to get triggered).

 

 

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 goto Rat_Hair_Soup
    

     

       

      DAH

      Sprite with Ball, Missile, and Collision Example

      This example program has a sprite, a bouncing ball, a missile you can shoot, and a mix of real and fake collision detection. In other words, it uses a combination of 'collision prevention' and collision detection.

       

      Use the joystick to move the sprite. Press the fire button to shoot the missile. Press the reset switch to reset the program and toggle between two screens.

       

      Here's the .bin file to use with an emulator or Harmony cart:

      Sprite with Ball, Missile, and Collision Example .bin file

       

      Here's the bB code:

      Sprite with Ball, Missile, and Collision Example .bas file

 

 

 

 

 

Display kernel

The display kernelThe fundamental part of an operating system. (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 horizontalLeft-Right
    Left and Right
    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 verticalUp-Down
    Up and Down
    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.

    The multisprite kernel will not maintain sprite definitions for player 0 outside of the game loop, so player0 needs to be defined within the game loop.

    If you'd like to use more than 4k with the multisprite kernel, the first line of your program should be "includesfile multisprite_bankswitch.inc". Here's an example of the proper order that you should use:

       includesfile multisprite_bankswitch.inc
       set kernel multisprite
       set romsize 8k
    

       

       

       

      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
      • The pfclear command does not work (your program will not compile if you try to use it)
      • 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.)

     

     

       

      Life Counter / Status Bar

      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 pfscore bars 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!

     

     

     

     

    Additional Kernels, Minikernels, Modules and Enhancements

    This section has links to useful kernels, minikernels. modules and enhancements. Use them at your own risk. If you know about other helpful additions to bB that are not listed here, please contact Random Terrain at AtariAge or at his web site. Thanks.

     

     

       

      Titlescreen Kernel  (by RevEng)

      The Titlescreen Kernel is a custom assembly module that allows you to display a high quality title screen in your batari Basic game, without having to write any assembly code yourself.

       

      The title screen doesn't have to be just a pretty picture; your batari Basic program can manipulate different parts of the title screen - scrolling an image, flipping through different frames of an animation, changing colors, etc.

       

      The Titlescreen Kernel

       

       

      Playerscores Minikernel  (by CurtisP)

      The playerscores minikernel provides two or four independently colored two-digit scores. The primary use is to display a separate score for each player, while allowing the main six-digit score to be used for something else.

       

      Playerscores Minikernel

       

       

      Racing Timer  (by SeaGtGruff)

      An example that displays an edited timer — minutes in the 1st position, a colon in the 2nd position, seconds in the 3rd and 4th positions, a decimal point in the 5th position, and tenths of a second in the 6th position. That means the timer can go up to only 9:59.9 before wrapping around to 0:00.0, which should probably be okay for a racing game. The program will work for either NTSC/60 or PAL/50.

       

      Racing Timer

       

       

      Custom Score Fonts  (by RevEng)

      Various fonts stuck in a single score_graphics.asm file. The font is selectable from within your bB code by setting the fontstyle constant.

       

      Custom Score Fonts

       

       

       

      AtariVox Support for bB (by RevEng)

      How to use the AtariVox Speech Synthesizer with batari Basic.

       

      Part 1 - AtariVox EEPROM

      Part 2 - Basic Voice Functionality

      Part 3 - More Natural Sounding Voices

 

 

 

 

 

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 :| )

 

 

     

    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. Smartbranching is set to off by default, because this makes the generated assembly code easier for a human to read and understand.

     

    Normally, an if-then statement with a line number or label after the then can only jump forward 127 bytes or backwards 128 bytes, so you'd need to use then goto for longer jumps. Thanks to smartbranching, you usually won't have to worry about that. If you set smartbranching on and just use then, 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 don't care to see the assembly language that bB generates, however, you should set smartbranching on. Smartbranching will not slow down your program, so you don't have to be afraid to use it.

     

    You can also use then goto all of 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. See Branches out of Range for more information.

     

    Warning: Smartbranching will sometimes cause the compilation to fail. In this case, you will need to use then goto instead of just then for the if-then statement that caused the problem.

     

     

     

     

    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.)

    You'd use one of the following examples:

       set romsize 2k
       set romsize 4k
       set romsize 8k
       set romsize 16k
       set romsize 32k
    
       set romsize 8kSC
       set romsize 16kSC
       set romsize 32kSC
    

     

     

     

     

    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 your 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 playfield blocks. Cost: loss of missile0.

       

      pfcolors

      Specify colors of each row of playfield blocks. Cost: free.

       

      pfheights

      Specify height of each row. Cost: free.

       

      pfcolors and pfheights together

      Cost: free, but colors and heights are fixed.

       

      background

      Have a multicolored background instead of a multicolored playfield using pfcolors data.

       

       

    The syntax is:

     

    set kernel_options option [option2 option3 ...]

     

     

    Below is a kernel_options chart based on the chart created by kisrael. When you see what you want, left click on the row and the code will appear above the chart. You can then copy the code and paste it into your program.

     

     

   set kernel_options  

player1colors

playercolors

pfcolors

pfheights

no_blank_lines

readpaddle

background

 

player1colors

           

Cost: loss of missile1.

   

pfcolors

         

player1colors

 

pfcolors

       

Cost: loss of missile1.

player1colors

playercolors

pfcolors

       

Cost: loss of missile1 and missile0.

     

pfheights

       

player1colors

   

pfheights

     

Cost: loss of missile1.

player1colors

playercolors

 

pfheights

     

Cost: loss of missile1 and missile0.

   

pfcolors

pfheights

     

Cost: Colors and height are fixed.

player1colors

 

pfcolors

pfheights

     

Cost: loss of missile1 and colors and height are fixed.

player1colors

playercolors

pfcolors

pfheights

     

Cost: loss of missile1 and missile0 and colors and height are fixed.

       

no_blank_lines

   

Cost: loss of missile0.

player1colors

     

no_blank_lines

   

Cost: loss of missile1 and missile0.

   

pfcolors

 

no_blank_lines

   

Cost: loss of missile0.

player1colors

 

pfcolors

 

no_blank_lines

   

Cost: loss of missile1 and missile0.

       

no_blank_lines

readpaddle

 

Cost: loss of missile0.

player1colors

     

no_blank_lines

readpaddle

 

Cost: loss of missile1 and missile0.

player1colors

 

pfcolors

     

background

Cost: loss of missile1.

player1colors

playercolors

pfcolors

     

background

Cost: loss of missile1 and missile0.

player1colors

   

pfheights

   

background

Cost: loss of missile1.

player1colors

playercolors

 

pfheights

   

background

Cost: loss of missile1 and missile0.

   

pfcolors

pfheights

   

background

Cost: Colors and height are fixed.

player1colors

 

pfcolors

pfheights

   

background

Cost: loss of missile1 and colors and height are fixed.

player1colors

playercolors

pfcolors

pfheights

   

background

Cost: loss of missile1 and missile0 and colors and height are fixed.

   

pfcolors

 

no_blank_lines

 

background

Cost: loss of missile0.

player1colors

 

pfcolors

 

no_blank_lines

 

background

Cost: loss of missile1 and missile0.

     

     

    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   Cost: loss of missile1.
      no_blank_lines   Cost: loss of missile0.
      pfcolors
      pfheights

      Invalid singles:
      playercolors
      readpaddle
      background

      Acceptable combinations:
      pfcolors pfheights   Cost: Colors and height are fixed.
      pfcolors pfheights background   Cost: Colors and height are fixed.
      pfcolors no_blank_lines   Cost: loss of missile0.
      pfcolors no_blank_lines background   Cost: loss of missile0.
      player1colors no_blank_lines   Cost: loss of missile1 and missile0.
      player1colors pfcolors   Cost: loss of missile1.
      player1colors pfheights   Cost: loss of missile1.
      player1colors pfcolors pfheights   Cost: loss of missile1 and colors and height are fixed.
      player1colors pfcolors background   Cost: loss of missile1.
      player1colors pfheights background   Cost: loss of missile1.
      player1colors pfcolors pfheights background   Cost: loss of missile1 and colors and height are fixed.
      player1colors no_blank_lines readpaddle   Cost: loss of missile1 and missile0.
      player1colors no_blank_lines pfcolors   Cost: loss of missile1 and missile0.
      player1colors no_blank_lines pfcolors background   Cost: loss of missile1 and missile0.
      playercolors player1colors pfcolors   Cost: loss of missile1 and missile0.
      playercolors player1colors pfheights   Cost: loss of missile1 and missile0.
      playercolors player1colors pfcolors pfheights   Cost: loss of missile1 and missile0 & colors and height are fixed.
      playercolors player1colors pfcolors background   Cost: loss of missile1 and missile0.
      playercolors player1colors pfheights background   Cost: loss of missile1 and missile0.
      playercolors player1colors pfcolors pfheights background   Cost: loss of both missiles & colors/height are fixed.
      no_blank_lines readpaddle   Cost: loss of missile0.

     

     

     

      Using kernel_options

         

        readpaddle

        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 which isn't large enough to span the screen since the players' horizontalLeft-Right
        Left and Right
        positions range from 0 to 160. If we multiply paddle by 2 and add 1, we get a value between 0 and 155, which is better. If your sprite is 8 pixels wide and each pixel is 1 color clock wide, then you'd want a maximum horizontal position of 153, which would put the player's rightmost pixel at position 160. Below is an example program.

         

         

         

         

        If you use Stella, the Atari 2600 emulator, and it seems like your paddle game isn't working, you'll need to run your game, press the Tab key on your keyboard, click the Game Properties button, click the Controller tab, change both controllers to Paddles, click the OK button and close Stella. Run your game again and it should work properly. If you make changes to your program, you'll have to do the same thing again, so get used to it.

         

        If you are working on a game that uses two paddles at the same time, you'll probably need to change Swap Paddles from No to Yes under the Controller tab so you'll be able to test the other paddle.

         

        Note: Must be used with no_blank_lines.

         

         

         

        player1colors and playercolors

        Specifies player colors a row at a time. When player1colors is used, missile1 is lost and when playercolors is added, missile0 is also lost. You have to decide if multicolored sprites are worth losing the missiles.

         

        playercolors allows both players to be multicolored, while player1colors only allows this for player 1. Remember that playercolors cannot be set by itself; player1colors must also be set.

         

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

         

        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.

         

         

         

         

        no_blank_lines

        Gets rid of those irritating gaps between rows of playfield blocks. Problem is that missile0 is lost and there are very few kernel options that you can use with it. You can have one multicolored sprite and/or a multicolored playfield and that's about it. See the kernel_options chart for more information.

         

        If you use no_blank_lines with pfcolors, each playfield pixel will have a thin layer of color on top from the row above it (like icing on a cake). This happens because the graphics data for the next line is loaded and stored before the color data for the next line, so the color of the previous line carries over at the top of the new line. It might be undesirable for some games, but you can use the 'icing' to your advantage and have a high-resolution look with thin lines for various types of games. See the Fake Gravity Platformer Test and the Gyvolver work in progress by Random Terrain.

         

         

         

         

        pfcolors

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

         

          Top row bug fix: There seems to be a bug where the top row will not be the color you selected if pfcolors is outside of your main loop. There are two easy fixes. You can either put pfcolors inside your main loop or put COLUPF in your main loop and have it be the color of your top row. If no_blank_lines is used, the top row color will be correct, but the bottom row color will be wrong. Just use 12 colors instead of 11 and make the 12th row the same color as the 11th row.

           

          If you use the kernel option background, COLUPF won't work and neither will putting pfcolors inside of the main loop (unless no_blank_lines is also used). If you're not using no_blank_lines, you'll need to use COLUBK. Put COLUBK in your main loop and have it be the color of your top row. If no_blank_lines is used with background, the top row color of your pfcolors definition will be ignored and the bottom row color will be wrong. You'll need to use 12 or 13 colors instead of 11 and play with the color placement until it looks right.

           

          PF0 color taint fix: If you use PF0 to have a border on the sides of the playfield, you might notice that the bottom of the border has been tainted with another color. Instead of using 11 colors for pfcolors, use 12 and make the 12th row the same color as the 11th row. No more color taint.

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

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

         

         

         

         

        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.

        Note: 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
        

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

         

         

         

         

        background

        Have a multicolored background instead of a multicolored playfield using pfcolors data. Normally, pfcolors changes the color of the playfield pixels by row, but background forces the data into the background, so you can have rows of different colors in the background and the usual single color playfield pixels on top of it all.

         

        Warning: If you don't use no_blank_lines with background, timing issues will cause a stairstep effect (the rows will not be perfectly straight).

     

     

     

     

    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.

      Example:

         set debug cycles
      

       

       

      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).

      Example:

         set debug cyclescore
      

      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.

 

 

 

 

 

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 (you can just leave all of your animation frames in the same bank that contains your main loop). 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.

 

 

     

    DAH

    Bankswitching Example

    Besides being a working example of bankswitching, this example program shows different parts of a game. There is a fake title screen, a fake game, and a game over screen with an initial 2 second freeze.

     

    End the 'game' by touching a wall with the sprite. Pressing the reset switch during the 'game' will take you back to the fake title screen. Pressing the reset switch or the fire button while on the GAME OVER screen will restart the 'game' and skip the fake title screen. If you do nothing for 20 seconds while on the GAME OVER screen, you'll go back to the fake title screen.

     

    Here's the .bin file to use with an emulator or Harmony cart:

    Bankswitching Example .bin file

     

    Here's the bB code:

    Bankswitching Example .bas file

 

 

Remember, specifying "bank 1" is unnecessary.

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 Harmony Cart, 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 (capitalized) 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 on…goto/gosub, 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 ameliorateMake better, improve. 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. If you plan to use the default 12 row screen resolution, you'll have those 48 regular variables from where the old playfield used to be and 80 more special read/write variables on top of that for a total of 128 extra variables.

     

    Optionally, since we have extra RAM for the playfield, it can have greater verticalUp-Down
    Up and Down
    resolution — up to 31+1 lines instead of 11+1. One drawback of this mode is that horizontalLeft-Right
    Left and Right
    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 (3, 4, 6, 8, 12, 16, 24, or 32), 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.

     

    No matter how many rows you use, the playfield always ends at r/w127, so when figuring how many extra variables you have left that the Superchip playfield isn't using, start from 127 and work your way backwards. For example, the default resolution is 12 rows (11 visible) and each row is made up of 4 variables, so 12 rows multiplied by 4 variables equals 48. Subtract 48 from 128 and you'll get 80. Now you know the top left playfield variable starts at r/w080. That means you can use r/w000 through r/w079 as variables. That's 80 extra variables, besides the 48 variables you gained from the old playfield when you switched to Superchip RAM. Below are a few examples with playfield charts.

     

 

12 Rows (pfres not used)

12 x 4 = 48

128 - 48 = 80 (r/w080)

You get 80 extra variables (r/w000-r/w079).

 

Row 0   r/w080 r/w081 r/w082 r/w083
Row 1   r/w084 r/w085 r/w086 r/w087
Row 2   r/w088 r/w089 r/w090 r/w091
Row 3   r/w092 r/w093 r/w094 r/w095
Row 4   r/w096 r/w097 r/w098 r/w099
Row 5   r/w100 r/w101 r/w102 r/w103
Row 6   r/w104 r/w105 r/w106 r/w107
Row 7   r/w108 r/w109 r/w110 r/w111
Row 8   r/w112 r/w113 r/w114 r/w115
Row 9   r/w116 r/w117 r/w118 r/w119
Row 10   r/w120 r/w121 r/w122 r/w123
Row 11   r/w124 r/w125 r/w126 r/w127

18 Rows (const pfres=18 makes square playfield pixels)

18 x 4 = 72

128 - 72 = 56 (r/w056)

You get 56 extra variables (r/w000-r/w055).

 

Row 0   r/w056 r/w057 r/w058 r/w059
Row 1   r/w060 r/w061 r/w062 r/w063
Row 2   r/w064 r/w065 r/w066 r/w067
Row 3   r/w068 r/w069 r/w070 r/w071
Row 4   r/w072 r/w073 r/w074 r/w075
Row 5   r/w076 r/w077 r/w078 r/w079
Row 6   r/w080 r/w081 r/w082 r/w083
Row 7   r/w084 r/w085 r/w086 r/w087
Row 8   r/w088 r/w089 r/w090 r/w091
Row 9   r/w092 r/w093 r/w094 r/w095
Row 10   r/w096 r/w097 r/w098 r/w099
Row 11   r/w100 r/w101 r/w102 r/w103
Row 12   r/w104 r/w105 r/w106 r/w107
Row 13   r/w108 r/w109 r/w110 r/w111
Row 14   r/w112 r/w113 r/w114 r/w115
Row 15   r/w116 r/w117 r/w118 r/w119
Row 16   r/w120 r/w121 r/w122 r/w123
Row 17   r/w124 r/w125 r/w126 r/w127

 

23 Rows (const pfres=23)

23 x 4 = 92

128 - 92 = 36 (r/w036)

You get 36 extra variables (r/w000-r/w035).

 

Row 0   r/w036 r/w037 r/w038 r/w039
Row 1   r/w040 r/w041 r/w042 r/w043
Row 2   r/w044 r/w045 r/w046 r/w047
Row 3   r/w048 r/w049 r/w050 r/w051
Row 4   r/w052 r/w053 r/w054 r/w055
Row 5   r/w056 r/w057 r/w058 r/w059
Row 6   r/w060 r/w061 r/w062 r/w063
Row 7   r/w064 r/w065 r/w066 r/w067
Row 8   r/w068 r/w069 r/w070 r/w071
Row 9   r/w072 r/w073 r/w074 r/w075
Row 10   r/w076 r/w077 r/w078 r/w079
Row 11   r/w080 r/w081 r/w082 r/w083
Row 12   r/w084 r/w085 r/w086 r/w087
Row 13   r/w088 r/w089 r/w090 r/w091
Row 14   r/w092 r/w093 r/w094 r/w095
Row 15   r/w096 r/w097 r/w098 r/w099
Row 16   r/w100 r/w101 r/w102 r/w103
Row 17   r/w104 r/w105 r/w106 r/w107
Row 18   r/w108 r/w109 r/w110 r/w111
Row 19   r/w112 r/w113 r/w114 r/w115
Row 20   r/w116 r/w117 r/w118 r/w119
Row 21   r/w120 r/w121 r/w122 r/w123
Row 22   r/w124 r/w125 r/w126 r/w127

 

 

See 32 x 23 Maze with Animated Sprite and Roaming Sprite for a working example that uses const pfres=23.

32 Rows (const pfres=32)

32 x 4 = 128

128 - 128 = 0 (r/w000)

You get 0 extra variables.

 

Row 0   r/w000 r/w001 r/w002 r/w003
Row 1   r/w004 r/w005 r/w006 r/w007
Row 2   r/w008 r/w009 r/w010 r/w011
Row 3   r/w012 r/w013 r/w014 r/w015
Row 4   r/w016 r/w017 r/w018 r/w019
Row 5   r/w020 r/w021 r/w022 r/w023
Row 6   r/w024 r/w025 r/w026 r/w027
Row 7   r/w028 r/w029 r/w030 r/w031
Row 8   r/w032 r/w033 r/w034 r/w035
Row 9   r/w036 r/w037 r/w038 r/w039
Row 10   r/w040 r/w041 r/w042 r/w043
Row 11   r/w044 r/w045 r/w046 r/w047
Row 12   r/w048 r/w049 r/w050 r/w051
Row 13   r/w052 r/w053 r/w054 r/w055
Row 14   r/w056 r/w057 r/w058 r/w059
Row 15   r/w060 r/w061 r/w062 r/w063
Row 16   r/w064 r/w065 r/w066 r/w067
Row 17   r/w068 r/w069 r/w070 r/w071
Row 18   r/w072 r/w073 r/w074 r/w075
Row 19   r/w076 r/w077 r/w078 r/w079
Row 20   r/w080 r/w081 r/w082 r/w083
Row 21   r/w084 r/w085 r/w086 r/w087
Row 22   r/w088 r/w089 r/w090 r/w091
Row 23   r/w092 r/w093 r/w094 r/w095
Row 24   r/w096 r/w097 r/w098 r/w099
Row 25   r/w100 r/w101 r/w102 r/w103
Row 26   r/w104 r/w105 r/w106 r/w107
Row 27   r/w108 r/w109 r/w110 r/w111
Row 28   r/w112 r/w113 r/w114 r/w115
Row 29   r/w116 r/w117 r/w118 r/w119
Row 30   r/w120 r/w121 r/w122 r/w123
Row 31   r/w124 r/w125 r/w126 r/w127

     

     

    Warning: In case you missed it above, these special read/write variables cannot be used for on…goto/gosub, 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. In other words, these special read/write variables can be safely used for:

     

    • adding and subtracting
    • multiplying and dividing (as long as include div_mul16.asm is not used)
    • if-then conditions (the read version only)

    Save these special variables for simple things and use the a-z variables and the old playfield variables (var0-var47) to do the more complicated stuff.

 

 

 

 

 

Sound and Music Links

Tone Toy 2008

A program for the Atari 2600 made with batari Basic that lets you play with sounds and sound effects using loops. It can simply be used as a toy or you can use it to think up possible new sound effects for your games.

 

VbB Music and Sound Editor

Easily create music and sound effects for your games using this built-in VbB editor.

 

The Atari 2600 Music and Sound Page

Expanded information about Atari 2600 sound.

 

do re bB

the BASICs of batari music.

 

Piano Key Frequencies

Interesting information at Wikipedia.

 

 

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  

      Audio Volume for Channel 0 (valid values are 0 to 15)

       

      Write Only: AUDV0 cannot be read. For example, a = AUDV0 will not work properly.

       

       

      AUDC0  

      Audio Control for Channel 0 [Also known as Tone, Voice, and Distortion] (valid values are 0 to 15)

       

      Write Only: AUDC0 cannot be read. For example, a = AUDC0 will not work properly.

       

       

      AUDF0  

      Audio Frequency for Channel 0 (valid values are 0 to 31)

       

      Write Only: AUDF0 cannot be read. For example, a = AUDF0 will not work properly.

     

     

     

    Channel 1

      AUDV1

      Audio Volume for Channel 1 (valid values are 0 to 15)

       

      Write Only: AUDV1 cannot be read. For example, a = AUDV1 will not work properly.

       

       

      AUDC1  

      Audio Control for Channel 1 [Also known as Tone, Voice, and Distortion] (valid values are 0 to 15)

       

      Write Only: AUDC1 cannot be read. For example, a = AUDC1 will not work properly.

       

       

      AUDF1  

      Audio Frequency for Channel 1 (valid values are 0 to 31)

       

      Write Only: AUDF1 cannot be read. For example, a = AUDF1 will not work properly.

 

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 chart 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


 

 

 

 

 

Joysticks

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

Example:

   if joy0up then y = y - 1
   if joy0down then y = y + 1
   if joy0left then x = x - 1
   if joy0right then x = x + 1

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

   if !joy0up then 230

     

     

     

    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.

     

     

     

     

    Advanced Joystick Reading with SWCHA  (by Robert M)

SWCHA

    The position of both joysticks is stored in an IO memory register of the RIOT labeled SWCHA. When a bit in SWCHA is 0, the joystick is pushed in that direction. There is one bit for each direction Right, Left, Up, and Down. Mechanically, a joystick has only 9 valid positions. The four joystick direction bits can form 16 combinations. Only 9 of those 16 combinations are valid joystick positions. The other seven are invalid (broken joystick). A good program will ignore the invalid positions. Some Atari games do not ignore the invalid positions. In those games, pressing invalid combination will cause illegal player movement such as passing through walls.

     

    Instead of using a long list of if-then statements, an on … gosub or on … goto statement can be used. For Joy0, we read the value in SWCHA and divide it by 16 which discards the Joy1 bits and shifts the joy0 bits into the lower 4 bit positons, so we'd use temp1 = SWCHA / 16. For Joy1, temp1 = SWCHA & %00001111 would be used.

     

    It may be surprising, but the SWCHA method is often less efficient in terms of cycles and space than if-thens. There is a point at which the on…goto with SWCHA will become more efficient. For example, if you are doing something different for up-left than for up and left individually, such as a diagonal sprite, SWCHA may be the better choice.

     

    Bare bones example .bas file:

    Advanced Joystick Reading with SWCHA (Bare Bones)

     

     

    Example .bas files:

    Joystick Reading with SWCHA using on … gosub

    Joystick Reading with SWCHA using on … goto

     

     

     

     

    2-Button Games for use with Sega Genesis Controllers

    RevEng created a little bB program that demonstrates how to read the B and C buttons on a Sega Genesis compatible controller. Great news for bB programmers who are in need of an extra button. For example, platform games can now have a fire button and a jump button.

     

    Why bother? Third party Sega Genesis controllers are inexpensive and work without modification on the Atari 2600, making them a popular choice for those who like gamepads. On top of that, they're trivial to support in your code.

     

    Stella, the Atari 2600 emulator, emulates Sega Genesis compatible controllers, so now you can make two button games without worrying that emulator uses won't be able to play them. When testing your two-button work in progress, just run it as usual, hit the Tab key to bring up the menu, click the Game Properties button, click the Controller tab, set P0 Controller to Sega Genesis, click the OK button, close Stella, then run your program again. Now your work in progress will be able to use two fire buttons instead of one. The first button is tied to the normal joystick button, so it will be the Space key, left Control, USB controller button, or whatever you have that mapped to. The second button is tied to the BoosterGrip Booster button, which by default is the 5 key. This can be remapped if you wish by pressing the Tab key, clicking the Input Settings button, and remapping P0 BossterGrip Boster to whatever key or USB controller button you'd like to use.

     

    The demo program by RevEng can be downloaded at AtariAge.com.

 

 

 

 

 

Console Switches

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

     

     

    if switchreset

    True if Reset is pressed. See reboot if you would like to use switchreset to warm boot your game.

     

     

    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).

 

For example, these are accessed by:

   if switchreset then 200

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

   if !switchreset then 200

 

 

 

 

Did You Know?

If you have a Windows computer, did you know that the calculator in your Accessories folder can convert decimal, hex, and binary numbers? Just select Scientific under the View menu and you can convert any decimal, hex, or binary number with a simple click of a mouse button. Or you can use this online tool.

 

 

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.

     

    If you'd like to find out if a variable's value is odd or even, just check bit 0. If bit 0 is on, the number is odd.

     

    Example:

       if a{0} then odd
    

     

     

     

     

    Hexadecimal Numbers

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

    Hex

    Decimal

    1
    1
    2
    2
    3
    3
    4
    4
    5
    5
    6
    6
    7
    7
    8
    8
    9
    9
    A
    10
    B
    11
    C
    12
    D
    13
    E
    14
    F
    15


    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.

    If you want to flip the sign of a variable where positive becomes negative or negative becomes positive, you'll be happy to know that assignment to a unary minusFlips the sign of a variable. Positive becomes negative, negative becomes positive. (such as a = -a) is supported by batari Basic. If you have a version of bB that hasn't been updated, you may need to use a=0-a instead. You can learn more about that at AtariAge.

    You can also assign a variable to a negative number, such as a = -1. It looks like 4.4 fixed point types were coded properly, but apparently integers were not in older versions of bB, so a = -1 may not work with plain old normal numbers. If you are using an older version of bB, there is a trick you can use: subtract the number you want to use from 256. For example, if you want to use a = -1 that would be 256 - 1, so you'd use a = 255. If you want to use a = -8, that would be 256 - 8, so you'd use a = 248.

     

    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.

 

 

 

 

Did You Know?

If you have a Windows computer, did you know that the calculator in your Accessories folder can convert decimal, hex, and binary numbers? Just select Scientific under the View menu and you can convert any decimal, hex, or binary number with a simple click of a mouse button. Or you can use this online tool.

 

 

Math

Full expression evaluation for integerAny number without a decimal (positive, negative or zero). A whole number. math is supported by batari Basic. 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
    

     

     

     

    Cycle Count by SeaGtGruff

    a = b + c   =   11 cycles

     

     

     

     

    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.

    Assignment to a unary minusFlips the sign of a variable. Positive becomes negative, negative becomes positive. (such as a = -a) is also supported by batari Basic.

    Examples:

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

     

     

     

     

    Multiplication

    Multiplication of two integerAny number without a decimal (positive, negative or zero). A whole number. numbers is supported by bB. 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 and before set romsize:

       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

    Division of two integerAny number without a decimal (positive, negative or zero). A whole number. numbers is supported by bB 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.

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

     

     

     

    Cycle Count by SeaGtGruff

    a = b / 3   =   38 cycles

    a = b / 2   =   8 cycles

       

       

       

       

      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

    A simple interface is provided for you to define your own functions in bB. 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.

     

     

     

     

    macro  (by SeaGtGruff)

    The macro statement lets you define a keyword that represents other programming statements. You can use parameters in a macro's definition by putting numbers inside curly brackets. Use '{1}' for the first parameter, '{2}' for the second parameter, '{3}' for the third parameter, and so on. Use an 'end' statement to terminate a macro's definition.

     

    For example, the following macro defines 'setscreencolors' as a keyword that can be used to set the background color, playfield color, and score color:

       macro setscreencolors
       COLUBK={1}
       COLUPF={2}
       scorecolor={3}
    end
    

     

     

       

      callmacro

      The callmacro statement lets you call a macro after you've defined it. To pass parameters to the macro, follow the name of the macro with the variable names or values you want the macro to use. Put a space before each parameter, but don't include commas.

       

      For example, the following statement calls the 'setscreencolors' macro that was defined above:

         callmacro setscreencolors $02 $46 $1C
      

      When the batari Basic compiler sees this statement in your program, it replaces the 'callmacro' statement with the macro's code, inserting the parameters in the order they were listed, as follows:

         COLUBK=$02
         COLUPF=$46
         scorecolor=$1C
      

       

       

       

      Combining macro, callmacro, and def

      Although 'macro' essentially lets you create a new instruction of your own, it doesn't look much like a new instruction with the 'callmacro' keyword in front of it. But you can use 'def' to make a keyword that represents the 'callmacro' statement. For example:

         def scolors=callmacro setscreencolors
         scolors $02 $46 $1C
      

      Now it looks more like a new instruction!

       

      See Improved Nybble Variables to view a working example of macro, callmacro, and def.

       

       

      More Examples of Using Macros

      The following example contains a few more macros that could be useful:

         macro setplayercolors
         COLUP0={1}
         COLUP1={2}
      end
      
         def pcolors=callmacro setplayercolors
      
         macro setplayer0xy
         player0x={1}
         player0y={2}
      end
      
         def p0xy=callmacro setplayer0xy
      
         macro setplayer1xy
         player1x={1}
         player1y={2}
      end
      
         def p1xy=callmacro setplayer1xy
      
         player0:
         %00010000
         %00101000
         %01000100
         %10000010
         %01000100
         %00101000
         %00010000
      end
      
         p0xy 20 40
      
         player1:
         %11111111
         %10000001
         %10000001
         %10000001
         %10000001
         %10000001
         %11111111
      end
      
         p1xy 50 30
      
      loop
      
         pcolors $0E $C6
         drawscreen
         goto loop
      

      You can even create macros that contain if-then statements, for-next statements, and other complex code, or call a macro from inside another macro. For example:

         macro movesprite
         if joy0left then {1}x={1}x-1
         if joy0right then {1}x={1}x+1
         if joy0up then {1}y={1}y-1
         if joy0down then {1}y={1}y+1
      end
      
         callmacro movesprite player0
      

      In this example, 'callmacro movesprite' will get replaced by the four if-then statements of the 'movesprite' macro, and '{1}' will get replaced by the name of the sprite, so player0 will get moved around by joystick0. If you change it to 'callmacro movesprite missile1', it will move missile1 around instead of player0.

       

      As you can see, macros can help make it easier for you to write your program code. However, macros could also make it harder for someone else to understand your program. For example, if someone sees 'p0xy 20 40' in your program, first they would need to look through your code to find where you defined 'p0xy' to mean 'callmacro setplayer0xy', and then they would need to look further to find where you defined the code for the 'setplayer0xy' macro. On the other hand, that's not much different than seeing a statement like 'gosub moose_tracks' and then having to look for the 'moose_tracks' subroutine to see what it does.

       

      Note: In bank-switched games, you can't use any commands that use a library inside a macro (pf* commands, multiplying by numbers that aren't powers of two, etc.).

 

 

 

 

 

Assembly Language Links

Instead of reposting the same links here, check out the useful Assembly Language Links on one of the Andrew Davie tutorial pages.

 

 

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

Did You Know?

It seems incude should come before includesfile in your code or it may not compile.

 

Example:

   include div_mul.asm
   includesfile bankswitch_SC.inc
   set romsize 8kSC

    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.

    Both the current directory and the includes directory will be searched by bB for any .asm files it needs to build your game. 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.

 

 

 

 

 

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.

 

Below is an example program by RevEng:

   rem ** 7800 detection won't work with the Harmony cart in menu mode 
   rem ** since the menu program changes these memory locations. 

   dim atari7800 = z

   rem ** This has to happen before a drawscreen or subroutine, or anything
   rem ** else that may obliterate temp1...

   atari7800 = temp1&1

   rem ** Uncomment the line below to test 7800 pausing in stella.
   rem ** Changing from color to BW will pause.
   rem ** Switching back to color will remain paused.
   rem ** Changing from color to BW will unpause.
   rem ** Switching back to color will will remain unpaused.

   rem atari7800 = 1

   rem ** For a 2600, atari7800 is now 0. For a 7800, atari7800 is now 1.

   scorecolor=$0A

   player0:
   %01111110
   %11000011
   %11000011
   %00000000
   %00000000
   %01100110
   %01100110
end

   player0x = 40 : player0y = 40

__Main_Loop

   COLUBK = 0
   COLUP0 = $0A

   player0x = player0x + 1

   drawscreen

   rem ** 2600 pause handling...
   if atari7800 = 0 && switchbw then goto __Pause_Game

   rem ** 7800 pause handling...
   if atari7800 = 1 && switchbw then atari7800 = %00000011 : goto __Pause_Game
   if atari7800 > 1 then if !switchbw then atari7800 = 1

   goto __Main_Loop



__Pause_Game

   COLUBK = $A4
   COLUP0 = $0A

   drawscreen

   rem ** This code should be at the bottom of the pause loop.

   rem ** 2600 pause handling...
   if atari7800 = 0 && !switchbw then goto __Main_Loop
   if atari7800 = 0 then goto __Pause_Game

   rem ** 7800 pause handling...
   if atari7800 = %00000011 && switchbw then goto __Pause_Game

   atari7800 = %00000111

   if switchbw then got