Random Terrain

Useful Inventions

Favorite Quotes

Game Design

Atari Memories

Personal Pages

 

Frequently Used

 

Table of Contents

Getting Started

IDE for batari Basic (VbB)

Useful AtariAge Links

Download the Manual

Test Your Games on a Real Atari

Sell Your Games

New to BASIC & Game Making?

Parts of a Program

Basic Questions

How does an Atari 2600 work?

What is a programming language?

What is BASIC?

What is a program?

What is a CPU?

What is a value?

What is binary?

What is a bit?

What is a byte?

What is a variable?

What is an alias?

What is ROM?

What is RAM?

Things You Should Know

Indent!

Don't Use Line Numbers

Memory

Timing

White Space

Line Length and Other Limitations

DOS Compatibility

Miscellaneous

Comments (rem, semicolons...)

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/Registers

Temporary Variables

Labels and Line Numbers

Jumping Around

goto

on … goto

gosub

on … gosub

return

pop

Decision Making (Brains of 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)

player#height

Missiles

Ball

The Playfield

Playfield Variable Chart

pfres

pfrowheight

drawscreen

vblank

pfclear

pfpixel

pfhline

pfvline

pfscroll

playfieldpos

pfread function

score

scorecolor

scorefade

noscore

pfscore bars

BCD Compliant Number

dec

TIA Registers

NUSIZ0, NUSIZ1

CTRLPF

REFP0, REFP1

PF0

AUDVx, AUDCx, AUDFx

Collision Detection

Display kernel

 

DPC+ Kernel

Good News / Bad News

Harmony Cart Fix

Variables (DPC+)

The Stack (DPC+)

Score Colors (DPC+)

Player Graphics (DPC+)

dpcspritemax (DPC+)

playerxcolor (DPC+)

NUSIZx and REFPx (DPC+)

REFPx for Virtual Sprites

Virtual Sprite Masking

Missiles (DPC+)

Collision Detection (DPC+)

The Playfield (DPC+)

pfclear (DPC+)

DFxFRACINC (DPC+)

Playfield Resolution (DPC+)

pfscroll (DPC+)

Playfield Colors (DPC+)

Background Colors (DPC+)

vblank (DPC+)

Multisprite Kernel

Minikernels/HUDs

life counter/status bar

lives

lifecolor

statusbarlength

statusbarcolor

Writing Your Own Minikernel

Additional Kernels, Minikernels

Compiler Directives (set)

smartbranching

tv

romsize

optimization

kernel

kernel_options

readpaddle

player1colors & playercolors

no_blank_lines

pfcolors

pfheights

background

debug

legacy

Bankswitching

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 bB .asm Files

Atari 7800

Troubleshooting

Compilation Errors

Preprocessor Errors

Compiler Errors

Assembler Errors

Searching the Assembly File

Other Errors

Memory Maps

Standard Kernel Memory Map

Multisprite Kernel Memory Map

Glossary of Terms

Constants, Variables, and Values

Constants

Variables

Values

Memory

ROM

RAM

Registers

Read-Only Registers

Write-Only Registers

 

Index

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

Page last updated on: 2014y_12m_18d_2126t

 

 

What is batari Basic?

The amazing 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.

 

The average person who wants to make an Atari 2600 game no longer has to spend years trying to figure out assembly language thanks to batari Basic. And people who can grasp BASIC, but have learning disabilities that keep them from understanding things like assembly language can finally make Atari 2600 games too. [Note from Random Terrain: I have Asperger's Syndrome, memory problems, and learning disabilities, but I managed to make an Atari 2600 game (with a lot of help), so there's a good chance that you can too.]

 

 

Read the Getting Started Section

If you are new to batari Basic or you've been using the Tinkernut installer, be sure to read the Getting Started section.

 

 

Table of Contents, Index, and Frequently Used Links

There is a table of contents an index and frequently used links on the right side of this page to help you find what you are looking for as quickly as possible. Below the index are various helpful sections. Be sure to at least glance over them.

 

 

Link Colors and Info Hover Boxes

Links that jump to other places on this page are blue. Links that lead to other pages online are red. Whenever you see a word or phrase on this page that is underlined with dashes or dots (tooltip/infotip/hint), you can hover your mouse pointer over it and a definition or explanation of some kind will pop up in a hover box.

 

 

The Magical Red Rectangle

Inside Game Loop

If you see a red rectangle to the right of an item with the words "Inside Game Loop" in it, that means the item needs to be inside of your main loop because it will be reset after drawscreen is used. See Ephemeral Variables & Registers for more information.

 

 

About This Page

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

 

 

 

 

 

Getting Started

Click here and download the latest version of batari Basic. It includes the DPC+ kernel, bug fixes, and various improvements. STOP! Did you download the latest version of bB? If you don't download the latest version, you could have various problems when you try to compile a program. Remember to unzip the folder and put it where you want it.

 

 

Make bB More Fun with Visual batari Basic

If you are able to run Windows software, you'll probably also want to download Visual batari Basic (VbB). You can read more about it in the next section below which is called IDE for batari Basic. In case it's not clear, VbB isn't a separate language, it's an IDEIntegrated Development Environment
(Just about everything a bB programmer needs in one place.)
; a tool that makes batari Basic easier to use.

 

 

 

install_win.bat

Double click on the file "install_win.bat" that is in the latest batari Basic folder that you have downloaded and unzipped. It will set bB's path and environment variable for you.

 

 

Don't Use the Tinkernut Installer

If you downloaded batari Basic from any other source, such as the horribly outdated Tinkernut, get the version linked to above. Again, do not use the Tinkernut installer. It is out-of-date. Click the link below instead and do what it says:

How to 'Install' Visual batari Basic on a Windows PC

 

 

Atari 2600 Emulator

If you don't already have the latest version of Stella installed, download it and install it (Stella is the most popular Atari 2600 emulator). Stella isn't a substitute for testing your games on a real Atari 2600 using the Harmony cart, but it's great for quick and dirty testing.

 

You might also want to check out JAVATARI. It's a very good emulator and it can also be embedded on your own web site so people can play the games you make online (if you like the idea of people playing your games online).

 

 

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

 

 

 

 

 

IDE for batari Basic

Visual batari Basic (VbB) is an IDEIntegrated Development Environment
(Just about everything a bB programmer needs in one place.)
that makes it easier to create bB games. VbB is not the same as Crimison Editor. Besides the Code Editor, VbB has a Sprite Editor, a Playfield Editor, a Score Editor, a Music and Sound Editor, a Sprite Animator, a Title Screen Kernel Editor, and a lot more.

 

Visit the Visual batari Basic Guide for more information. Especially check out this section:

How to 'Install' Visual batari Basic on a Windows PC

 

 

 

 

 

Useful AtariAge Links

Official batari Basic Forum at AtariAge

Posting Tips for New bB Users

How to Attach Files and Images

Easy Software Versioning

Keeping Track of the Scanline Count with Stella

 

 

 

 

 

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

 

 

 

 

 

Sell Your Games

Once your game is tested, 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 . . ."

 

You can also have a single Custom Atari 2600 Cartridge made, if that's all you want.

 

 

 

 

 

New to BASIC and Game Making?

This page assumes 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

 

 

 

 

 

Parts of a Program

Text by Random Terrain

I'm just an amateur BASIC programmer, so experts may not agree with everything below, but it still might be helpful to new users who don't have the parts of a game set in their minds. To me, every section of a BASIC program is like a little box or module. Remember, not every game will follow this exact pattern. It's just an outline that can give you guidance if you feel like a plastic bag blowing in the wind.

 

 

Program Setup

This is the area where you put things such as kernel options, set commands, romsize, includes, variable aliases using dim, def statements, constants, and so on.

 

 

Start/Restart

This is where you make the program jump to when the game is reset and after the game over loop. The volume is muted and object positions are moved off screen. Most variables are cleared and a few variables are left alone so things can be remembered after a game is over (such as the high score and the game over bit). If the game over bit is turned on, the game goes to the main loop setup instead of the title screen setup.

 

 

Title Screen Setup

Some colors are set. Some variables are set to certain values so they'll be ready for the title screen loop. If objects are used, they'll need to be set up here. A reset switch/fire button repetition restrainer bit is set to keep one section of the game from contaminating another if the player holds down the reset switch or fire button.

 

 

Title Screen Loop

Certain colors may need to be set in the loop. If you are making your own title screen instead of a fancy one using the Titlescreen Kernel by RevEng, you'll need to use drawscreen.

 

If you want the game to play by itself when the player doesn't start the game within a certain amount of time, a counter can be used that will turn on auto play. During auto play, the score should flip between the current score and the high score every 2 seconds.

 

The title screen loop should check the reset switch and the fire button. If either one is pressed, the program should jump the the main loop setup.

 

 

Main Loop Setup

Everything for the actual game needs to be set up here, including any variable values, object locations, object shapes, playfield data, and so on. The repetition restrainer bit for the reset switch and the repetition restrainer bit for the fire button must be turned on to keep one section of the game from contaminating another if the player holds down the reset switch or fire button.

 

 

Main Loop

This is the engine, or the heart, or the brain of your game. It's the part of the code that makes things go. Your if-thens, collision detection, and other important things go here.

 

 

Game Over Setup

The high score is checked, variable values are set, the game over playfield is drawn, the game over bit is turned on, a reset switch/fire button repetition restrainer bit is set to keep one section of the game from contaminating another if the player holds down the reset switch or fire button, and so on.

 

 

Game Over Loop

Certain colors may need to be set in the loop. If you are making your own game over screen instead of a fancy one using the Titlescreen Kernel by RevEng, you'll need to use drawscreen.

 

The score flips between the current score and the high score every 2 seconds.

 

The loop ignores fire button and reset switch input for 2 seconds. After that, the loop checks to see if the reset switch or fire button has been pressed. If either is pressed, the program jumps to the start/restart section.

 

 

Subroutines

If a significantly large piece of code needs to be used in many places in your program, you'll probably want to use gosub/return to save space. If the code is fairly small, it's usually better to just repeat the code instead of using a subroutine to avoid wasting cycles.

 

 

Code Blocks

Large pieces of code that aren't used by more than one area in your program can be placed outside of your main loop and jumped to using goto or on … goto. You can have your block of code jump back to a label that you have placed right after the goto that jumped to that block of code.

 

Jumping to large blocks of code can make your main loop easier for you to handle and it really becomes necessary if your program grows so large that it will have to be spread out over multiple banks.

 

 

Data

Regular data should be outside of your main loop. Your code doesn't need to run over regular data statements for them to work. There's no need to jump to them with goto or gosub.

 

 

 

 

 

Basic Questions

Most of the information below is either directly from or adapted from Wikipedia. Some info is from SeaGtGruff and GroovyBee at AtariAge.

 

 

 

How does an Atari 2600 work?

Check this out:

How does an Atari VCS work?

 

 

 

What is a programming language?

A programming language is an artificial language designed to communicate instructions to a computer.

 

 

 

What is BASIC?

BASIC is a high-level programming language that was designed to be easy to use. BASIC is an acronym that stands for "Beginner's All-purpose Symbolic Instruction Code."

 

 

 

What is a program?

A program is a sequence of instructions that tells the Atari 2600 what to do. Without a program, there is no game.

 

 

 

What is a CPU?

CPU stands for "Central Processing Unit." It's the brains of the Atari 2600. The CPU carries out the instructions of a program.

 

 

 

What is a "value"?

In batari Basic, a value is a number within a specific range.

See Glossary: Constants, Variables, and Values for more information.

 

 

 

What is binary?

Binary is a base-2 number system. It's either off or on (zero or one).

 

 

 

What is a bit?

The word bit is a contraction of binary digit. A bit can be thought of as a tiny box that can hold the value of either 0 or 1 (zero or one).

 

 

 

What is a byte?

A byte is most commonly made up of eight bits. It can be thought of as a larger box that can hold a value of 0 through 255.

 

 

 

What is a variable?

The word variable means something that is likely to vary (something that can change). In batari Basic, a variable can be thought of as a box with a name that can hold a value ranging from 0 to 255. The standard bB variable names are the letters of the alphabet: a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z.

See Variables and Glossary: Constants, Variables, and Values for more information.

 

 

 

What is an alias?

A variable alias is a more understandable name that you can think up to use in place of a standard variable name. For example, instead of using a = a + 1, you could use _Alien_Shot_Count = _Alien_Shot_Count + 1.

See dim for more information about variable aliases.

 

 

 

What is ROM?

ROM stands for "Read-Only Memory." The ROM in a normal game cartridge contains program instructions and data that cannot be changed by the Atari's CPU. The information in ROM is always there, even when power to the console is switched off.

See Glossary: Memory for more information.

 

 

 

What is RAM?

RAM stands for "Random Access Memory." RAM is used to store data that can change during a game, such as variables, the score, or positions of the objects on the screen. Any values written to RAM are lost when the console's power is switched off.

See Glossary: Memory for more information.

 

Back to Top

 

 

 

Things You Should Know

     

    Indent!

    Indenting in batari Basic means putting at least one space at the beginning of a line [Note from RT: I always indent with 3 spaces]. 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 bankswitching and/or Superchip RAM. Bankswitching allows programs up to 32K in size, and the Superchip gives us another 128 bytes of RAM. There are drawbacks to both, however.

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

     

     

     

     

    Line Length and Other Limitations

    Text from bogax and RevEng (adapted by Random Terrain)

    It seems bB doesn't like lines over 190 characters. There seems to be a limit of around 45 labels on a single line when using on … goto or on … gosub, but you'll probably want to use fewer labels than that for the sake of readability using this tip for on … goto or this tip for on … gosub.

     

    Testing shows that 46 parameters can be passed to a macro. That makes sense, as statements are limited to 50 strings, and the command, macroname, and newline chars use up 4 strings.

     

     

     

     

    DOS Compatibility

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

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

Back to Top

 

 

 

 

 

Miscellaneous

     

    Comments (rem, semicolons, and C-style)

    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: 2012y_06m_02d_0656t
       rem  *
       rem  ****************************************************************
       rem  *
       rem  *  Programmer: Scadoobie Floinkenburger
       rem  *  Language: batari Basic v1.0
       rem  *  System: Atari 2600 VCS
       rem  *
       rem  ****************************************************************
    

    Semicolons and C-style multi-line comments are also supported.

     

    Here are a few examples using semicolons:

       dim _Monkey_Brains = a ; Keeps track of how many brains have been eaten.
    
       a = 5 ; I Like Pizza
    
       ;----------------------------------------------------------------
       ;  
       ;  Add points.
       ;  
       _Points_Roll_Up = _Points_Roll_Up + 50
    

    Semicolons can also be used with data and sdata. Remember, rem is not used. No colon, no rem, just put a semicolon and your comment.

     

    Example:

       data _Music_Data
       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
    

    Here's an example using C-style comments:

       /* comment #1 */
    
    
       /* comment #2
       */
    
    
       /*
       comment #3
       and some more of #3
       */
    

    Note: There's a subtle difference between the two commenting styles. When you use ";" and "/* */" comments, the comments don't show up in the generated assembly code. When you use "rem" they do.

     

     

     

     

    reboot [do not use this if you use rand]

    This command will warm bootRestart a computer under software control. your game, but you should probably never use it. 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  *  Clears all normal variables.
       rem  `
       for temp5 = 0 to 25 : a[temp5] = 0 : next
    

    This seems to be faster than the example above:

       rem  ****************************************************************
       rem  *
       rem  *  Clears all normal variables.
       rem  `
       a = 0 : b = 0 : c = 0 : d = 0 : e = 0 : f = 0 : g = 0 : h = 0 : i = 0
       j = 0 : k = 0 : l = 0 : m = 0 : n = 0 : o = 0 : p = 0 : q = 0 : r = 0
       s = 0 : t = 0 : u = 0 : v = 0 : w = 0 : x = 0 : y = 0 : z = 0
    

    If you are using Superchip RAM, you can use the example below to clear normal variables and the old playfield variables:

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

    This seems to be faster than the example above:

       rem  ****************************************************************
       rem  *
       rem  *  Clears all normal variables and old playfield variables.
       rem  `
       a = 0 : b = 0 : c = 0 : d = 0 : e = 0 : f = 0 : g = 0 : h = 0 : i = 0
       j = 0 : k = 0 : l = 0 : m = 0 : n = 0 : o = 0 : p = 0 : q = 0 : r = 0
       s = 0 : t = 0 : u = 0 : v = 0 : w = 0 : x = 0 : y = 0 : z = 0
    
       var0 = 0 : var1 = 0 : var2 = 0 : var3 = 0 : var4 = 0 : var5 = 0
       var6 = 0 : var7 = 0 : var8 = 0 : var9 = 0 : var10 = 0 : var11 = 0
       var12 = 0 : var13 = 0 : var14 = 0 : var15 = 0 : var16 = 0 : var17 = 0
       var18 = 0 : var19 = 0 : var20 = 0 : var21 = 0 : var22 = 0 : var23 = 0
       var24 = 0 : var25 = 0 : var26 = 0 : var27 = 0 : var28 = 0 : var29 = 0
       var30 = 0 : var31 = 0 : var32 = 0 : var33 = 0 : var34 = 0 : var35 = 0
       var36 = 0 : var37 = 0 : var38 = 0 : var39 = 0 : var40 = 0 : var41 = 0
       var42 = 0 : var43 = 0 : var44 = 0 :  var45 = 0 : var46 = 0 : var47 = 0
    

    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 data-related command normally ending with a colon (such as playfield:, player0:, lives:, and others).

     

    Examples:

       playfield:
       X.X...X..XX..X.XX.X....XX..X.X..
       X.X....XX..X.X..X.X...X..XX..X.X
    end
    
    
    
       player0:
       %00100010
       %01110111
       %01111111
    end
    
    
    
       data _My_Data
       200, 43, 33, 93, 255, 54, 22
    end
    
    
    
       asm
    minikernel
       sta WSYNC
       lda scback
       sta COLUBK
       rts
    end
    

Back to Top

 

 

 

 

 

Variables

Template

For your convenience, here is a fill-in-the-blank dim template:

   dim _ = a
   dim _ = b
   dim _ = c
   dim _ = d
   dim _ = e
   dim _ = f
   dim _ = g
   dim _ = h
   dim _ = i
   dim _ = j
   dim _ = k
   dim _ = l
   dim _ = m
   dim _ = n
   dim _ = o
   dim _ = p
   dim _ = q
   dim _ = r
   dim _ = s
   dim _ = t
   dim _ = u
   dim _ = v
   dim _ = w
   dim _ = x
   dim _ = y
   dim _ = z

If you haven't used variables before, see What is a variable? and Glossary: Constants, Variables, and Values before you dive in.

 

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.

There are only 26 variables, 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're using the standard kernel and run out of variables, you can use four bytes from the playfield if you're not scrolling it (var44 through var47). You can also use temporary variables temp1 through temp6 as temporary storage, but these are obliteratedDestroyed, erased. when drawscreen is called, and some are used for playfield operations as well, so use these at your own risk. If you're not using the Life Counter or Status Bar minikernels, you can use the variable statusbarlength and if you're also not using pfscore bars, you can use the variables lives and lifecolor. As with the regular 26 variables, aliases can be assigned to the playfield variables, statusbarlength, lives, and lifecolor.

 

Another option to get more variables is to use Superchip RAM which gives you 48 extra variables (from the old playfield) and more variables that can only be read and written to in a special way.

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, 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 or coordinates of a sprite/missile/ball when testing your game, you can use the score to display it. The following is an adapted example by bogax that will display two values using the score:

       rem  ****************************************************************
       rem  *
       rem  *  Create aliases for variables.
       rem  *
       rem  ****************************************************************
       rem  `
       rem  `  (You can have more than one alias for each variable.)
       rem  `
       rem  ````````````````````````````````````````````````````````````````
    
       rem  ````````````````````````````````````````````````````````````````
       rem  `  Splits up the score into 3 parts.
       rem  `
       dim sc1 = score
       dim sc2 = score+1
       dim sc3 = score+2
    
    
    __Main_Loop
    
       rem  ****************************************************************
       rem  *
       rem  *  Your code to test goes here.
       rem  *
       rem  ****************************************************************
    
    
       rem  ****************************************************************
       rem  *
       rem  *  Sets color of the score.
       rem  `
       scorecolor = $9C
    
    
    
       rem  ****************************************************************
       rem  *
       rem  *  Puts temp4 in the three score digits on the left side.
       rem  *
       rem  ````````````````````````````````````````````````````````````````
       rem  `  Replace "player0x" with whatever you need to check.
       rem  `
       temp4 = player0x
    
       sc1 = 0 : sc2 = sc2 & 15
       if temp4 >= 100 then sc1 = sc1 + 16 : temp4 = temp4 - 100
       if temp4 >= 100 then sc1 = sc1 + 16 : temp4 = temp4 - 100
       if temp4 >= 50 then sc1 = sc1 + 5 : temp4 = temp4 - 50
       if temp4 >= 30 then sc1 = sc1 + 3 : temp4 = temp4 - 30
       if temp4 >= 20 then sc1 = sc1 + 2 : temp4 = temp4 - 20
       if temp4 >= 10 then sc1 = sc1 + 1 : temp4 = temp4 - 10
       sc2 = (temp4 * 4 * 4) | sc2
    
    
    
       rem  ****************************************************************
       rem  *
       rem  *  Puts temp4 in the three score digits on the right side.
       rem  *
       rem  ````````````````````````````````````````````````````````````````
       rem  `  Replace "player0y" with whatever you need to check.
       rem  `
       temp4 = player0y
    
       sc2 = sc2 & 240 : sc3 = 0
       if temp4 >= 100 then sc2 = sc2 + 1 : temp4 = temp4 - 100
       if temp4 >= 100 then sc2 = sc2 + 1 : temp4 = temp4 - 100
       if temp4 >= 50 then sc3 = sc3 + 80 : temp4 = temp4 - 50
       if temp4 >= 30 then sc3 = sc3 + 48 : temp4 = temp4 - 30
       if temp4 >= 20 then sc3 = sc3 + 32 : temp4 = temp4 - 20
       if temp4 >= 10 then sc3 = sc3 + 16 : temp4 = temp4 - 10
       sc3 = sc3 | temp4
    
    
    
       rem  ****************************************************************
       rem  *
       rem  *  Displays the screen.
       rem  `
       drawscreen
    
    
       goto __Main_Loop
    

    Below is an example program that uses the code above to display object coordinates using the score.

     

     

       

      DAH

      Find Border Coordinates

      There is a sprite, a missile, and a ball on the screen. The sprite is selected by default. Move it around to see the coordinates for the sprite in the score.

       

      To select another object, hold down the fire button and press the joystick either up or down.

       

      To change the size of a selected object, hold down the fire button and press left or right to cycle through the different sizes.

       

      The joystick has a repetition restrainer to make it easier for you to select objects and to change their sizes. You can keep the fire button pressed down, but you'll need to press the joystick in a direction and let go, press and let go, press and let go . . .

       

      Remember, you can paste in your own playfield, kernel options, and anything else you might need to make your playfield display properly. You can also paste in your own sprite.

       

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

      Find Border Coordinates Example .bin file

       

      Here's the bB code:

      Find Border Coordinates Example .bas file

     

     

     

     

    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

    If you haven't used constants before, see Glossary: Constants, Variables, and Values before you dive in.

     

    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.

    See Glossary: Constants, Variables, and Values for more information.

     

     

     

     

    dim

    If you haven't used dim before, see What is an alias? before you dive in.

     

    The dim statement is used to create more descriptive names for 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. [Note from RT: I put one underscore in front of variable aliases and two underscores in front of labels. Then I don't have to worry if I'm using a keyword by mistake and I don't have to worry if a label has the same name as a variable 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. [Note from Random Terrain: I had all kinds of mysterious problems when using def. It may work correctly for you, but I stopped using it.]

     

     

     

 

Example Programs

 

Useful Code and Tips

 

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 you make larger sprites?

     

    A.You can make sprites wider by using NUSIZx. You can make them taller by drawing taller sprites.

 

    Q.How do you make multiple copies of a sprite?

     

    A.You use NUSIZx.

 

 

 

    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

     

    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 Shot02

Do this instead:

  if a{0} then gosub Shot02

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  *
       rem  *  Random number for a{0}.
       rem  `
       temp5 = (rand & %00000001)
       a = a ^ temp5
    

    To randomize bit 7 of variable a, do this:

       rem  ****************************************************************
       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.
    
     

    Here's the same example with the bit number added to each alias so it will be easier to remember and work with:

       dim _Bit0_Hero_Shot = a
       dim _Bit1_Hero_Thrust = a
       dim _Bit2_Hero_Crash = a
       dim _Bit3_Hero_Shield = a
       dim _Bit4_Enemy_Shot = a
       dim _Bit5_Enemy_Thrust = a
       dim _Bit6_Enemy_Crash = a
       dim _Bit7_Enemy_Shield = a
    
       _Bit0_Hero_Shot{0} = 0
       _Bit1_Hero_Thrust{1} = 0
       _Bit2_Hero_Crash{2} = 1
       _Bit3_Hero_Shield{3} = 0
       _Bit4_Enemy_Shot{4} = 0
       _Bit5_Enemy_Thrust{5} = 0
       _Bit6_Enemy_Crash{6} = 0
       _Bit7_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 as shown in the bad examples below.

     

    Bad examples:

       dim _my_variable = a{1}
       dim _my_variable{1} = a
       dim _my_variable{1} = a{1}
    

    Instead, use the example showing the bit number added to each alias, or you can attempt to 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. [Note from Random Terrain: I had all kinds of mysterious problems when using def. It may work correctly for you, but I stopped using it.]

     

     

     

    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 _My_Var = 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 _My_Var or _Monster_x 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. Here's an example using player0 and player1:

       dim _P0_L_R = player0x.a
       dim _P0_U_D = player0y.b
    
       dim _P1_L_R = player1x.c
       dim _P1_U_D = player1y.d
    

    And here's one example of how you could move player0 to the left:

       _P0_L_R = _P0_L_R - 0.85
    

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

     

     

     

       

      Temporary Variables

      Text by SeaGtGruff (adapted by Random Terrain)

      There are 7 temporary variables, temp1 through temp7but temp7 is used exclusively with bankswitching (I think), so you should avoid it like the plague if you're using bankswitching in your game, although with 2K and 4K games it should actually be the *safest* temp variable to use (I think). The other temp variablestemp1 through temp6are used by the main kernel and by various bB statements, so they can (generally) be safely used on a *temporary* basis as long as whatever you're using them for doesn't conflict with how bB uses them, and also as long as you realize they might get blown away when you call drawscreen or use some specific instruction.

       

      For example, the "div_mul.asm" and "div_mul16.asm" routines use temp1 and temp2, so those two temp variables should (generally) be avoided in statements that use any math requiring those routines, because whatever you've set them to will get destroyed whenever the math is done. (But I think something like "temp1 = a * 23" should be okay, because even though bB will destroy the contents of temp1 when the math is done, it will then move the results into temp1 as requestedso it should be okay as long as you don't do any *more* math that uses the "div_mul.asm" or "div_mul16.asm" routines.)

       

      If you define a user function, calling the user function may also use some of the temp variables.

       

      I haven't memorized all of the bB routinesand the source code for bB's routines are always subject to future changebut I *think* bB uses the temp variables in the order they're needed. That is, bB will use temp1 first, and will not use temp2 unless it's already using temp1 for something and needs to use another temp variable. So that means, generally speaking, that the temp variables are safest to use in reverse orderin other words, temp6 is safer than temp5, and temp5 is safer than temp4, etc. The exception is temp7, whichas I mentionedis reserved for bankswitching, so you shouldn't use temp7 in a bankswitched game or you could screw up the bankswitching big time. The best thing to do is use the temp variables sparingly, and only for brief situations where their values will be used right away and it's okay that they'll eventually lose their values when bB uses them for its own purposes.

Back to Top

 

 

 

 

 

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. Although you could not use 'scorechange' you could use 'changescore'. To avoid any problems, you could always start a label with one or two underscores. [Note from RT: I put one underscore in front of variable aliases and two underscores in front of labels. Then I don't have to worry if I'm using a keyword by mistake and I don't have to worry if a label has the same name as a variable alias.]

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

Back to Top

 

 

 

 

 

Jumping Around

     

    goto

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

    Examples:

       goto 100
       goto __My_Subroutine
    

    In bankswitched 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 bankswitched 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
    
     

    Too Many Labels

    There seems to be a limit of around 45 labels on a single line when using on … goto. 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 bankswitched games, if you are jumping to a subroutine in another bank, you must specify the bank.

    Example:

       gosub __Move_Monster bank2
    

     

    See Glossary: Subroutines for more information.

     

     

     

      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
    
     

    Too Many Labels

    There seems to be a limit of around 45 labels on a single line when using on … gosub. 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 bankswitching, there is some additional overhead with every return statement. Although the standard return by itself will automatically return to whatever bank called the subroutine, it takes up ROM space and cycles to automatically return to the proper bank. Instead, you may use thisbank or otherbank after your return:

       

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

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

       

       

       

       

      pop

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

Back to Top

 

 

 

 

 

Decision Making (Brains of a Game)

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

      BooleanOne of two values (true or false). 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.

Back to Top

 

 

 

 

 

Loops

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

 

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

     

     

     

    for-next

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

     

    The syntax is:

    for variable = value1 to value2 [step value3]

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

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

    Examples:

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

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

Back to Top

 

 

 

 

 

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.

 

 

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.

 

Warning: Using only division or only AND seems to create visible patterns. Mixing up division with AND seems to get rid of the patterns.

 

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 (a number between 0 and 255 is called when using rand16 or the DPC+ kernel). 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 is not random enough for most games. It's better to use the optional 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 the following near the beginning of your program:

       dim rand16 = <var>
    

    <var> is one of your 26 variables (a-z) or one of your 48 extra variables (var0-var47) when using Superchip RAM. Then you use rand as you normally would. The DPC+ kernel has an ARM-based 32-bit LFSR for random numbers, so "dim rand16 = <var>" should not be used when making games with the DPC+ kernel.

     

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

     

    Below is an imperfect, but close enough list of percentages in case you ever need to limit the chance of something happening:

      5% = if rand < 13 then do something
     10% = if rand < 26 then do something
     15% = if rand < 38 then do something
     20% = if rand < 51 then do something
     25% = if rand < 64 then do something
     30% = if rand < 77 then do something
     35% = if rand < 90 then do something
     40% = if rand < 102 then do something
     45% = if rand < 115 then do something
     50% = if rand < 128 then do something
     55% = if rand < 141 then do something
     60% = if rand < 154 then do something
     65% = if rand < 166 then do something
     70% = if rand < 179 then do something
     75% = if rand < 192 then do something
     80% = if rand < 205 then do something
     85% = if rand < 218 then do something
     90% = if rand < 230 then do something
     95% = if rand < 243 then do something
    

Back to Top

 

 

 

 

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

   data _My_Data
   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, _My_Data[0] is 200, _My_Data[1] is 43, ... and _My_Data[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 statementthis 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 _My_Data
   1,2,3,4,5,6,7,8,9 
end

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

 

Example:

   a = _My_Data_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 _My_Data_length=_My_Data_length

Note again that these data tables are in ROM. Attempting to write values to data tables with commands like _My_Data[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 _My_Music=a
       1,2,3,4,5,6,7,255
    end
    

    The above will set up a sequential data table called _My_Music 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(_My_Music)
    

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

     

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

     

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

     

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

Back to Top

 

 

 

 

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

     

 

 

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

Back to Top

 

 

 

 

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 (Sprites)

    Sprite Q & A

      Q.How do I get larger sprites?

       

      A.You can make them taller by drawing taller sprites and you can make them wider buy using NUSIZx.

       

      Q.How do I make multiple copies of a sprite?

       

      A.NUSIZx can do that for you.

    The standard kernel can display two player sprites, which are 8 pixels wide and any height (up to 255). 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.

     

    Note: You don't need to have your sprite data in the main loop. Whatever you tell your sprites to look like before the main loop starts is what they will look like forever until you tell them to look differently.

     

     

      6 Sprites

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

     

     

      10 Multicolored Sprites

      The DPC+ kernel has ten sprites. The player0 sprite works much how it did in the standard kernel. Player1 through player9 sprites are 'virtual' sprites, and are displayed by repositioning the 2600's second hardware sprite 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.

     

     

     

     

     

       

      player#height

      Text by RevEng (adapted by Random Terrain)

      The player#height variable is automatically set to the player's height as part of the player#: command. You can use it to make a player "melt away" (or the reverse) just by decreasing or increasing the value without drawing new frames with the player#: command.

       

      In the standard kernel, player0height and player1height can be used. In the multisprite kernel, player0height through player5height can be used. In the DPC+ kernel, player0height through player9height can be used.

       

      If you'd like to have a sprite that seems to melt away completely or seems to grow from nothing, you may need to use a blank row of zeros at the very bottom of the sprite (depending on your programming style).

       

      Example:

         player0:
         %00000000
         %00111100
         %01111110
         %11000011
         %10111101
         %11111111
         %11011011
         %01111110
         %00111100
      end
      
         COLUBK = 0
      
         player0x = 77
         player0y = 53
      
      __Main_Loop
      
         COLUP0 = $9C
      
         if !joy0up then goto __Skip_Up
         if player0height < 8 then player0height = player0height + 1
      __Skip_Up
      
         if !joy0down then goto __Skip_Down
         if player0height > 0 then player0height = player0height - 1
      __Skip_Down
      
         drawscreen
      
         goto __Main_Loop
      

       

     

     

     

     

    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
    

    The smallest height that can be used for missile0height and missile1height in the standard kernel is zero. When set to zero, a missile appears to be around two scanlines high.

     

    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.

     

    When using the DPC+ kernel, the smallest height that can be used with missile0height and missile1height is two. A missile will seem to disappear when its height is set to zero. And setting the height to one will cause the missile to appear on even rows and disappear on odd rows.

     

    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. If a multicolored playfield is used, the ball will use the color of whatever row it is on. The ball 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.

     

     

Back to Top

 

 

 

 

 

The Playfield

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.

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

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.

 

Note: You don't need to have your playfield data in the main loop. Whatever you tell the playfield to look like before the main loop starts is what it will look like forever until you tell it to look differently.

 

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 bankswitched game (outside of any loops) and runs automatically every time a drawscreen is called. If you're using bankswitching, 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 bankswitched 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
    

    If you are using the standard kernel and are not scrolling the screen, you can clear the screen without clearing the four playfield variables on the bottom hidden row, and that means you get four extra variables. Here's an example:

       rem  ****************************************************************
       rem  *
       rem  *  Clears screen without clearing var44, var45, var46, or var47.
       rem  `
       playfield:
       ................................
       ................................
       ................................
       ................................
       ................................
       ................................
       ................................
       ................................
       ................................
       ................................
       ................................
    end
    

    This seems to be even faster than the example above:

       rem  ****************************************************************
       rem  *
       rem  *  Clears screen without clearing var44 through var47.
       rem  `
       var0 = 0 : var1 = 2 : var2 = 0 : var3 = 0 : var4 = 0 : var5 = 0
       var6 = 0 : var7 = 0 : var8 = 0 : var9 = 0 : var10 = 0 : var11 = 0
       var12 = 0 : var13 = 0 : var14 = 0 : var15 = 0 : var16 = 0 : var17 = 0
       var18 = 0 : var19 = 0 : var20 = 0 : var21 = 0 : var22 = 0 : var23 = 0
       var24 = 0 : var25 = 0 : var26 = 0 : var27 = 0 : var28 = 0 : var29 = 0
       var30 = 0 : var31 = 0 : var32 = 0 : var33 = 0 : var34 = 0 : var35 = 0
       var36 = 0 : var37 = 0 : var38 = 0 : var39 = 0 : var40 = 0 : var41 = 0
       var42 = 0 : var43 = 0
    

    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. For scrolling with the DPC+ kernel, see pfscroll (DPC+).

     

    Valid values are:

    pfscroll left
    pfscroll right
    pfscroll up
    pfscroll down
    pfscroll upup
    pfscroll downdown

     

    Example:

   dim rand16 = z

   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

   if playfieldpos <> 8 then goto __Skip_Draw

   temp5 = rand : temp6 = rand/2

   if temp5 > temp6 then var44 = 0 : var45 = 0 : var46  = 0 : var47 = 0 : goto __Skip_Draw

   var44 = rand : var45 = rand : var46  = rand : var47 = rand

__Skip_Draw

   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.

     

     

     

       

      playfieldpos

      This is an internal system variable that controls which line to start drawing the top playfield block and when to stop drawing the bottom one. The scrolling routines update it automatically so it's not necessary to use playfieldpos unless you need to know where the playfield is located.

       

      For 7 out of 8 calls, playfieldpos is updated and checked, and this variable instructs the kernel as to what line to start drawing the first playfield block. Every 8th call, playfieldpos is reset to zero and the entire playfield memory is shifted. These figures are true for standard RAM. If using Superchip RAM, the scrolling will take more time and the memory move may occur more often.

       

      Besides checking on the position of the playfield by reading playfieldpos, you can also use it to jump to a certain position. If you use "playfieldpos = 8" with the normal 32 x 11 resolution playfield, it will reset the scroll position to normal. You can also set playfieldpos to any value from 1 to 8 to scroll the playfield that many linesfor example, "playfieldpos = 4" will scroll 4 lines, or half of a playfield row.

     

     

     

     

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

     

     

       

      scorecolor

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

       

      scorecolor = value

       

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

       

       

       

       

      scorefade

      In the standard kernel, "const scorefade=1" will enable the score fade effect. This adds shading to the score.

       

      Example:

         const scorefade = 1
      
         scorecolor = $9C
      
      __Main_Loop
      
         drawscreen
      
         goto __Main_Loop
      

      If you keep incrementing the scorecolor variable, it will do the typical Atari color bar effect.

       

      Example:

         const scorefade = 1
      
      __Main_Loop
      
         x = x + 1
      
         scorecolor = x
      
         drawscreen
      
         goto __Main_Loop
      

      Warning: If you're using pfscore bars, scorefade isn't available.

     

     

     

     

    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 _Bit2_FireB_Restrainer = d
       dim _Points_Roll_Up = z
    
       dim sc1 = score
       dim sc2 = score+1
       dim sc3 = score+2
    
    
    __Main_Loop
    
       scorecolor = $1A
    
    
       if !joy0fire then _Bit2_FireB_Restrainer{2} = 0 : goto __Skip_Fire
    
       if _Bit2_FireB_Restrainer{2} then goto __Skip_Fire
    
       _Bit2_FireB_Restrainer{2} = 1
    
       a = a + 5
    
       if a > 30 then a = 100
    
       _Points_Roll_Up = _Points_Roll_Up + a
    
    __Skip_Fire
    
    
       rem  ****************************************************************
       rem  *
       rem  *  Points roll up code from Nukey Shay.
       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. You can see a working example of Points Roll Up in Seaweed Assault.

     

    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.

     

     

     

     

    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 bankswitching, 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  *
       rem  *  Assign a variable to the score background.
       rem  `
       dim scback = z
    
    
       rem  ****************************************************************
       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.

     

     

     

    DPC+ Score Background Color

    Using a background color with the DPC+ kernel is a little different. The asm code goes in the first bank, but after the goto that jumps to bank 2.

     

    Example:

       set kernel DPC+
    
       goto __Start_Restart bank2
    
       asm
    minikernel
       ldx _Score_Background
       stx COLUBK
       rts
    end
    
       bank 2
       temp1=temp1
    
       dim _Score_Background = y ; Used by asm code to set score bg color.
    
    __Start_Restart
    

    Try this scrolling example program to see the above code in action. If you don't want the score background color to change during your game, you can use a color after ldx instead of a variable.

     

    Example:

       set kernel DPC+
    
       goto __Start_Restart bank2
    
       asm
    minikernel
       ldx $84
       stx COLUBK
       rts
    end
    
       bank 2
       temp1=temp1
    
    __Start_Restart
    

     

     

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

    The example below increases the score when the fire button is pressed and changes the color of the background each time the thousands digit changes:

       rem  ````````````````````````````````````````````````````````````````
       rem  `  Converts 6 digit score to 3 sets of two digits.
       rem  `  sc1 holds the 100 thousands and 10 thousands
       rem  `  digits of the score. sc2 holds the thousands and
       rem  `  hundreds digits of the score. sc3 holds the tens
       rem  `  and ones digits of the score.
       rem  `
       dim sc1 = score
       dim sc2 = score+1
       dim sc3 = score+2
    
    
       rem  ````````````````````````````````````````````````````````````````
       rem  `  How to check the digits:
       rem  `
       rem  `  100 thousands digit . sc1 & $F0 (X0 00 00)
       rem  `  10 thousands digit .. sc1 & $0F (0X 00 00)
       rem  `
       rem  `  Thousands digit ..... sc2 & $F0 (00 X0 00)
       rem  `  Hundreds digit ...... sc2 & $0F (00 0X 00)
       rem  `
       rem  `  Tens digit .......... sc3 & $F0 (00 00 X0)
       rem  `  Ones digit .......... sc3 & $0F (00 00 0X)
    
    
       scorecolor = $FA
    
    
    __Main_Loop
    
    
       rem  ****************************************************************
       rem  *
       rem  *  Fire button check.
       rem  *
       rem  `
       if !joy0fire then goto __Skip_FireB
    
    
       rem  ````````````````````````````````````````````````````````````````
       rem  `  Remembers the thousands digit.
       rem  `
       temp5 = sc2 & $F0
    
    
       rem  ````````````````````````````````````````````````````````````````
       rem  `  Add points to score.
       rem  `
       score = score + 10
    
    
       rem  ````````````````````````````````````````````````````````````````
       rem  `  Grabs the thousands digit.
       rem  `
       temp6 = sc2 & $F0
    
    
       rem  ````````````````````````````````````````````````````````````````
       rem  `  If thousands digit has NOT changed, skip this part.
       rem  `  
       if temp5 = temp6 then goto __Skip_FireB
    
       COLUBK = $B4 : c = 45
    
    __Skip_FireB
    
    
       if c then c = c - 1 : if !c then COLUBK = 0
    
    
       drawscreen
    
    
       goto __Main_Loop
    

     

     

     

    Saving a High Score  (by supercat and Nukey Shay)

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

       rem  ****************************************************************
       rem  *
       rem  *  Checks for a new high score.
       rem  `
       if sc1 > _High_Score01 then goto __New_High_Score
       if sc1 < _High_Score01 then goto __Skip_High_Score
    
    
       rem  ````````````````````````````````````````````````````````````````
       rem  `  First byte equal. Do the next test. 
       rem  `
       if sc2 > _High_Score02 then goto __New_High_Score
       if sc2 < _High_Score02 then goto __Skip_High_Score
    
    
       rem  ````````````````````````````````````````````````````````````````
       rem  `  Second byte equal. Do the next test. 
       rem  `
       if sc3 > _High_Score03 then goto __New_High_Score
       if sc3 < _High_Score03 then goto __Skip_High_Score
    
    
       rem  ````````````````````````````````````````````````````````````````
       rem  `  All bytes equal. Current score is the same as the high score.
       rem  `
       goto __Skip_High_Score
    
    
    __New_High_Score
    
    
       rem  ````````````````````````````````````````````````````````````````
       rem  `  Save new high score.
       rem  `
       _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.

     

     

     

       

      Custom Score Fonts

      To set a custom score font in any batari kernel, just set "const font=<fontname>". The available fonts are: 21stcentury, alarmclock, handwritten, interrupted, retroputer, whimsey, and (in bB with Native 64k Cart Support) tiny.

       

      Example:

         const font = 21stcentury
      

       

       

       

       

      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.

       

       

       

       

      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.

       

      You can use either pfscore bar for lives or health. You can also have two sets of dots or two health bars if you want. It depends on how you set them up and if you divide by 2 or 4 to decrease them. If it's a health bar, divide by 2. If the bar has dots, divide by 4. Check out the examples below to learn more.

       

       

       

      Lives Bar Examples

      To decrement a life, use this:

         pfscore1 = pfscore1/4 : rem * If using a lives bar on left side.
         pfscore2 = pfscore2/4 : rem * If using a lives bar on right side.
      

      To increment a life starting at bit 1 (%00000010), use this:

         pfscore1 = pfscore1*4|2 : rem * If using a lives bar on left side.
         pfscore2 = pfscore2*4|2 : rem * If using a lives bar on right side.
      

      To increment a life starting at bit 0 (%00000001), use this:

         pfscore1 = pfscore1*4|1 : rem * If using a lives bar on left side.
         pfscore2 = pfscore2*4|1 : rem * If using a lives bar on right side.
      

       

      Two ways to increment a life were given above to help you, not to confuse you. If you want your lives on the left side to be a little farther away from the score, start at bit 1.

       

      Here are 3 lives starting at bit 0 on the left side of the score: pfscore1 = %00010101.

      Here are 3 lives starting at bit 1 on the left side of the score: pfscore1 = %00101010.

       

       

       

       

      Health Bar Examples

      To decrement the health bar, use this:

         pfscore1 = pfscore1/2 : rem * If using a health bar on left side.
         pfscore2 = pfscore2/2 : rem * If using a health bar on right side.
      

      To increment the health bar, use this:

         pfscore1 = pfscore1*2|1 : rem * If using a health bar on left side.
         pfscore2 = pfscore2*2|1 : rem * If using a health bar on right side.
      

       

       

         

        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.

     

     

     

       

      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.

       

       

         

        dec

        Text by RevEng (adapted by Random Terrain)

        A time may come when you'll need to add a value 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's now a simple fix for that. The dec statement is a variation of let that adds and subtracts values in decimal mode. Other math operations aren't valid in decimal mode. The one thing to keep in mind is that you need to use BCD numbers when adding or subtracting. So to add 14, you'd actually add $14.

         

        Example:

           if s{0} then dec a = a + $02
           if s{1} then dec a = a + $05
           if s{2} then dec a = a + $10
           if s{3} then dec a = a + $20
        
           score = score + a
        

        Before the dec statement was added, you could wrap the arithmetic with assembly "sed" and "cld" commands like in the example below:

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

        Or you could use large numbers in your arithmetic to bypass other score digits. For example, score = score + 100 would add 1 to the middle score digits.

         

        Having the dec command keeps the source compact and means you can use it in the middle of a long if-then. Avoiding the "large number" method means you don't need to worry about the score byte overflowing into the higher score digit. The converse of that is, if you do use dec, the score digit will roll over once it goes past 99, or roll back if it goes under 0.

Back to Top

 

 

 

 

 

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. In the chart below, x = don't care.

    Value

    Effect

     
    $0x missile = 1 pixel wide  
    $1x missile = 2 pixels wide  
    $2x missile = 4 pixels wide  
    $3x missile = 8 pixels wide  
    $x0 1 copy of player and missile x0
    $x1 2 close-spaced copies of player and missile x1
    $x2 2 medium-spaced copies of player and missile x2
    $x3 3 close-spaced copies of player and missile x3
    $x4 2 wide-spaced copies of player and missile x4
    $x5 double-sized player x5
    $x6 3 medium-spaced copies of player and missile x6
    $x7 quad-sized player x7


    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

    This section was updated by SpiceWare (adapted by Random Terrain).

    Changes properties of the ball and the playfield.

     

    Use value $bp where b is the ball setting and p is the playfield setting.

     

    Value

    Effect

    b = 0 1 pixel wide ball
    b = 1 2 pixel wide ball
    b = 2 4 pixel wide ball
    b = 3 8 pixel wide ball
    p = 1 a "normal" bB playfield
    p = 3 "special color" playfield where left half of PF gets player0 color,
    right half gets player1 color
    p = 5 players move behind playfield
    p = 7 both "special color" and "payers behind" playfield

     

    For example, CTRLPF = $25 will set a 4 pixel wide ball and make the players move behind the playfield.

     

    Note that ball and playfield properties are both set with 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.

     

    REFP0 and REFP1 are also used by batari Basic to display the score properly, so they revert back to zero every time drawscreen is used. You can take advantage of that to save a little ROM since you won't have to write code that changes REFP0 or REFP1 to zero when you want to keep the sprite from flipping.

     

    Example:

       dim _Bit2_Flip_p1 = a
       dim _Bit3_Flip_p0 = a
    
       player0:
       %01111110
       %11111111
       %00011111
       %00000111
       %00011111
       %11111111
       %01111110
    end
    
       player1:
       %10101010
       %11111111
       %10000011
       %10101011
       %11111111
       %10010011
       %11011011
       %10010011
       %01111110
    end
    
       COLUBK = 0
    
       player0x = 68 : player1x = player0x + 20
    
       player0y = 55 : player1y = 55
    
    
    
    __Main_Loop
    
       COLUP0 = $1E : COLUP1 = $AE
    
       if joy0left then _Bit3_Flip_p0{3} = 0 : _Bit2_Flip_p1{2} = 0
       if joy0right then _Bit3_Flip_p0{3} = 1 : _Bit2_Flip_p1{2} = 1
    
    
       rem  ````````````````````````````````````````````````````````````````
       rem  `  Note from SpiceWare:
       rem  `
       rem  `  REFPx registers ignore everything except the value in bit 3.
       rem  `
       REFP0 = _Bit3_Flip_p0
    
    
       rem  ````````````````````````````````````````````````````````````````
       rem  `  Note from SpiceWare:
       rem  `
       rem  `  Multiplying by 2 will shift all bits to the left one
       rem  `  position, so what was in bit 2 ends up in bit 3.
       rem  `
       REFP1 = _Bit2_Flip_p1 * 2
    
    
       drawscreen
    
    
       goto __Main_Loop
    

     

    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.

Back to Top

 

 

 

 

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 Collision/Collision Prevention Example

      This example program has a sprite you can move around 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 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 Collision Example .bin file

       

      Here's the bB code:

      Sprite with Collision Example .bas file

       

      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

Back to Top

 

 

 

 

 

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 29 possible kernels to choose from. 27 of those 29 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. The DPC+ kernel only uses one kernel option and that's to help with collisions.

 

 

 

Did You Know?

DPC+ is based on the Display Processor Chip that was designed by David Crane for Pitfall II: Lost Caverns. The DPC+ kernel is hardware assisted and only works with Melody/Harmony cartridges and any version of Stella released after October of 2012.

 

In March of 2010, SpiceWare did some experiments with DPC. He liked what he saw, but thought something better could be done, so he asked if it would be possible to expand DPC. SpiceWare, batari, and cd-w hashed out what eventually became DPC+. They decided not to implement unused DPC functions and replaced them with other features.

 

They developed DPC+ in Stella first (with stephena's help), then batari wrote the DPC+ driver for Harmony/Melody. You can learn more at AtariAge.

 

 

     

    DPC+ Kernel

    Text by RevEng, batari, iesposta, Atarius Maximus, SpiceWare, ScumSoft, and possibly others (adapted by Random Terrain)

    The DPC+ kernel is similar to the multisprite kernel, but it has an asymmetric (non-mirrored) multicolored playfield with simultaneous single line background and foreground colors. It also has 10 multicolored single-height sprites instead of 6 mono-colored double-height sprites. Click here to view the DPC+ section of the Table of Contents.

     

    Until the DPC+ kernel, all bB sprites had double-thick rows, so they weren't as pretty as the sprites that assembly language programmers could make. Thanks to the DPC+ kernel, bB users can create single-height sprites that are as detailed as the ones made by Imagic and Activision.

     

    To use the DPC+ kernel, put this at the beginning of your code:

       set kernel DPC+
    

    Kernel options for the standard kernel are not used with the DPC+ kernel

     

    You don't have to declare set romsize 32k when using the DPC+ kernel as it automatically sets the ROM size to 32k for you. The DPC+ kernel has a 4k graphics bank where your sprite and playfield data automaticaly go. You cannot place your own code in the graphics bank. There's another 4k bank that you cannot use that holds the ARM code. Most of bank 1 is used up, but banks 2, 3, 4, 5, and 6 are completely free for normal use. So that's 4K bB system, 20K of your basic code, 4K graphics data, and 4K ARM code = 32K binary.

     

    The DPC+ kernel goes in bank 1, so there's very little free room there. It's a good idea to put a goto statement in bank 1 to a label in bank 2 and start your code there.

     

    Example:

       goto __Bank_2 bank2
    
       bank 2
    
       temp1=temp1
    
    __Bank_2
    

    There are less than 100 bytes free to use in bank 1 and that will shrink over time as improvements are made. To make sure your games will always compile with the latest version of bB, about the only code you should have in bank 1 is a goto that jumps to bank 2.

     

    Click here to download the latest version of bB with the DPC+ kernel that has all of the bug fixes and various improvements. Click here to download the latest version of Visual batari Basic, the bB IDE.

       

       

       

       

      The Good News and the Bad News

      The bad news is that there are only 35 variablesa to z and var0 to var8though you may free up more memory by killing off sprites with dpcspritemax. Another slight bit of bad news is that we can't put data values directly into playfield variables. DPC+ uses ARM memory for the playfield and bB can't access the playfield without using playfield:, pfpixel, pfvline or pfhline.

       

      The good news is that pfpixel, pfvline, pfhline, pfread, pfclear, and pfscroll are now supported by the DPC+ kernel. Also, the DPC+ kernel has an ARM-based 32-bit LFSR for improved random numbers, so "dim rand16 = <var>" is no longer needed when using this kernel. That's one variable we don't have to waste.

       

       

       

       

      Harmony Cart Fix (with DPC+ template)

      To fix issues with the Harmony cart, put temp1=temp1 right after each bank declaration. Here's a simple example that you can use as a DPC+ template:

         set kernel DPC+
         set tv ntsc
         set kernel_options collision(player1,playfield)
      
      
         goto __Bank_2 bank2
      
      
         bank 2
         temp1=temp1
      
      
      __Bank_2
      
      
         ;***************************************************************
         ;
         ;  Variable aliases go here (DIMs).
         ;
         ;***************************************************************
         ;
         ;  (You can have more than one alias for each variable.)
         ;
         ;```````````````````````````````````````````````````````````````
      
      
      
         ;***************************************************************
         ;***************************************************************
         ;
         ;  Program Start/Restart
         ;
      __Start_Restart
      
      
         ;***************************************************************
         ;
         ;  Displays the screen to avoid going over 262 when reset.
         ;
         drawscreen
      
      
         ;***************************************************************
         ;
         ;  Clears the playfield.
         ;
         pfclear
      
      
         ;***************************************************************
         ;
         ;  Mute volume of both sound channels.
         ;
         AUDV0 = 0 : AUDV1 = 0
      
      
         ;***************************************************************
         ;
         ;  Clears all normal variables.
         ;
         a = 0 : b = 0 : c = 0 : d = 0 : e = 0 : f = 0 : g = 0 : h = 0 : i = 0
         j = 0 : k = 0 : l = 0 : m = 0 : n = 0 : o = 0 : p = 0 : q = 0 : r = 0
         s = 0 : t = 0 : u = 0 : v = 0 : w = 0 : x = 0 : y = 0 : z = 0
      
      
      
      
      
         ;***************************************************************
         ;***************************************************************
         ;
         ;  Main Loop
         ;
      __Main_Loop
      
      
         ;***************************************************************
         ;
         ;  Gameplay logic goes here.
         ;
         ;***************************************************************
      
      
         ;***************************************************************
         ;
         ;  88 rows that are 2 scanlines high.
         ;
         DF6FRACINC = 255
         DF4FRACINC = 255
      
         DF0FRACINC = 128
         DF1FRACINC = 128
         DF2FRACINC = 128
         DF3FRACINC = 128
      
      
         ;***************************************************************
         ;
         ;  Displays the screen.
         ;
         drawscreen
      
      
         goto __Main_Loop
      
      
         bank 3
         temp1=temp1
      
      
         bank 4
         temp1=temp1
      
      
         bank 5
         temp1=temp1
      
      
         bank 6
         temp1=temp1
      

       

       

       

       

       

      Variables (DPC+)

      The DPC+ kernel has 35 variablesa to z and var0 to var8though you may free up more memory by killing off sprites with dpcspritemax. You can also 'push' variables to the stack and reuse them, then 'pull' them back off. There are 256 bytes in the stack that can be used in a program.

       

       

       

       

      The Stack (DPC+)

      The DPC+ stack can be used to save and restore your bB variables. There are 256 stack locations available, with the top-most location being #0 and the bottom-most being #255.

       

      To store variable contents on the stack, you use the "push" command. As you push the values on the stack, the receiving location (aka the stack pointer) automatically changes, bringing it closer to the top of the stack.

       

      Similarly, to retrieve previously stored values from the stack you can use the "pull" command. As you pull the values from the stack, the stack pointer automatically changes, bringing the pointer closer to the bottom of the stack.

       

      You may not always want to retrieve values from the stack in the same order that they were stored. To accomplish a less structured kind of access, you can use the bB "stack" command to change the location the stack pointer is pointing at.

       

      Below is an example that stores a value to location 200, then retrieves it:

         ; Position stack at location 200 and push the value stored in player0x.
         ;
         stack 200
      
         push player0x
      
      
         ; [Code that reuses player0x can go here.]
      
      
         ; Position stack above location 200 and pull off the item below.
         ;
         stack 199
      
         pull player0x
      

      The pull and push commands can work with multiple values at once as well. To push a range of variables from j to m, you could use the command push j-m. To push some arbitrary locations, you can separate them with spaces: push a x missile0x player0y.

       

      Note: The DPC+ stack is a special stack just for you that is 100% dedicated to your push and pull commands. Nothing else uses it. It's all yours.

       

       

       

       

      Score Colors (DPC+)

      With the DPC+ kernel, the color of each horizontal row of the score is specified using scorecolors:. This allows for interesting visual effects, like gradients.

       

      Example:

         scorecolors:
         $3E
         $3C
         $3A
         $38
         $36
         $36
         $34
         $32
      end
      

      If you'd like to have a good way to control the background color behind the score, check out the DPC+ Score Background Color asm code by RevEng.

       

       

       

       

      Player Graphics (DPC+)

      Instead of two player sprites, you get ten. Nine of the ten 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-player9 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 two 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.

       

      The virtual sprites are also given their own NUSIZ registers. NUSIZ2-NUSIZ9 correspond to sprites 2-9. For player1, use _NUSIZ1. Although these look like TIA registers, they are not. The actual TIA register NUSIZ1 can be set, but it will probably have no effect in the DPC+ kernel.

       

      Although the DPC+ 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-NUSIZ9, as this bit is unused by the TIA register NUSIZx.

       

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

       

      Player1 only wraps because values larger than 165 cannot be supported, so to prevent crashes, DPC+ wraps automatically. It is up to the programmer to wrap player0, and the missiles and ball.

       

      If you want to disable a sprite, do not set its Y value to zero (it would still be visible). Set it to around 200 to move it off the screen.

       

      To define a sprite, you use player0: through player9:.

       

      Example:

         player0:
         %00011111
         %11111111
         %00111110
         %11111111
         %00011111
      end
      
         player1:
         %00011111
         %11111111
         %00111110
         %11111111
         %00011111
      end
      
         player2:
         %00011111
         %11111111
         %00111110
         %11111111
         %00011111
      end
      
         player3:
         %00011111
         %11111111
         %00111110
         %11111111
         %00011111
      end
      
         player4:
         %00011111
         %11111111
         %00111110
         %11111111
         %00011111
      end
      

      Since player1-player9 are actually all player1, if any of those sprites have the same shape, you can have them share the same sprite data (as long as the sprites are in sequential order). Here's an example where player1-player9 share the same data:

         player1-9:
         %00011111
         %11111111
         %00111110
         %11111111
         %00011111
      end
      

      Here's an example where only player2 and player3 share the same data:

         player2-3:
         %00011111
         %11111111
         %00111110
         %11111111
         %00011111
      end
      

      In this example, player1-player4 share data, player5-player7 share data, and player8 and player9 share data:

         player1-4:
         %00011111
         %11111111
         %00111110
         %11111111
         %00011111
      end
      
         player5-7:
         %01111110
         %11111111
         %11111111
         %11111111
         %01111110
      end
      
         player8-9:
         %11011011
         %11011011
         %11011011
         %11011011
         %11011011
      end
      

 

 

 

Did You Know?

Variables liberated with dpcspritemax can be used with dim. Below is a template list that might be helpful. Only copy and paste the section of the list that has numbers that are larger than the number used with dpcspritemax.

   dim _ = player2x
   dim _ = player2y
   dim _ = NUSIZ2
   dim _ = player2height
   dim _ = player3x
   dim _ = player3y
   dim _ = NUSIZ3
   dim _ = player3height
   dim _ = player4x
   dim _ = player4y
   dim _ = NUSIZ4
   dim _ = player4height
   dim _ = player5x
   dim _ = player5y
   dim _ = NUSIZ5
   dim _ = player5height
   dim _ = player6x
   dim _ = player6y
   dim _ = NUSIZ6
   dim _ = player6height
   dim _ = player7x
   dim _ = player7y
   dim _ = NUSIZ7
   dim _ = player7height
   dim _ = player8x
   dim _ = player8y
   dim _ = NUSIZ8
   dim _ = player8height
   dim _ = player9x
   dim _ = player9y
   dim _ = NUSIZ9
   dim _ = player9height

 

And for your convenience, here is a template for the rest of the variables:

   dim _ = a
   dim _ = b
   dim _ = c
   dim _ = d
   dim _ = e
   dim _ = f
   dim _ = g
   dim _ = h
   dim _ = i
   dim _ = j
   dim _ = k
   dim _ = l
   dim _ = m
   dim _ = n
   dim _ = o
   dim _ = p
   dim _ = q
   dim _ = r
   dim _ = s
   dim _ = t
   dim _ = u
   dim _ = v
   dim _ = w
   dim _ = x
   dim _ = y
   dim _ = z
   dim _ = var0
   dim _ = var1
   dim _ = var2
   dim _ = var3
   dim _ = var4
   dim _ = var5
   dim _ = var6
   dim _ = var7
   dim _ = var8

 

 

         

        dpcspritemax (DPC+)

        If you're working on a game that doesn't use all of the virtual sprites, you don't have to let all of that memory go to waste. You can use dpcspritemax to reduce the number of virtual sprites and liberate some precious variables. Use "set dpcspritemax #", where # is a number from 1-9.

         

        You get 4 variables for each virtual sprite that you disable. These variables can be used with dim to make meaningful aliases. Below are a few examples that show all of the variables that could be available to you.

         

        Using "set dpcspritemax 4" would free up 20 extra variables:

        player5x, player5y, NUSIZ5, player5height

        player6x, player6y, NUSIZ6, player6height

        player7x, player7y, NUSIZ7, player7height

        player8x, player8y, NUSIZ8, player8height

        player9x, player9y, NUSIZ9, player9height

         

        Using "set dpcspritemax 3" would free up 24 extra variables:

        player4x, player4y, NUSIZ4, player4height

        player5x, player5y, NUSIZ5, player5height

        player6x, player6y, NUSIZ6, player6height

        player7x, player7y, NUSIZ7, player7height

        player8x, player8y, NUSIZ8, player8height

        player9x, player9y, NUSIZ9, player9height

         

        Using "set dpcspritemax 2" would free up 28 extra variables:

        player3x, player3y, NUSIZ3, player3height

        player4x, player4y, NUSIZ4, player4height

        player5x, player5y, NUSIZ5, player5height

        player6x, player6y, NUSIZ6, player6height

        player7x, player7y, NUSIZ7, player7height

        player8x, player8y, NUSIZ8, player8height

        player9x, player9y, NUSIZ9, player9height

         

         

         

         

        playerxcolor (DPC+)

        Specifies player colors a row at a time.

         

        Example:

           player0color:
           $08
           $0C
           $06
           $0C
           $08
        end
        
           player1color:
           $18
           $1C
           $16
           $1C
           $18
        end
        
           player2color:
           $28
           $2C
           $26
           $2C
           $28
        end
        
           player3color:
           $38
           $3C
           $36
           $3C
           $38
        end
        
           player4color:
           $48
           $4C
           $46
           $4C
           $48
        end
        

        Since player1-player9 are actually all player1, if any of those sprites use the same colors, you can have them share the same color data (as long as the sprites are in sequential order). Here's an example where player1-player9 share the same color data:

           player1-9color:
           $18
           $1C
           $16
           $1C
           $18
        end
        

        Here's an example where only player2 and player3 share the same color data:

           player2-3color:
           $38
           $3C
           $36
           $3C
           $38
        end
        

        In this example, player1-player4 share color data, player5-player7 share color data, and player8 and player9 share color data:

           player1-4color:
           $18
           $1C
           $16
           $1C
           $18
        end
        
           player5-7color:
           $58
           $5C
           $56
           $5C
           $58
        end
        
           player8-9color:
           $88
           $8C
           $86
           $8C
           $88
        end
        

         

         

         

         

        NUSIZx and REFPx (DPC+)

        Player0 uses NUSIZ0 and REFP0, just like in the standard kernel. NUSIZ1 and REFP1 aren't used in the DPC+ kernel because all 9 virtual sprites are created using player1. Those virtual sprites have their own NUSIZ variables. NUSIZ2-NUSIZ9 correspond to sprites 2 through 9. Player1 uses _NUSIZ1 (with an underscore). Although these look like TIA registers, they are not. The actual TIA register NUSIZ1 can be set, but it will probably have no effect in the DPC+ kernel.

         

         

        REFPx for Virtual Sprites

        Although the DPC+ kernel doesn't have special REFPx variables for the 9 virtual sprites, you can set the reflection bit for each individual sprite by using bit 3 of _NUSIZ1 or NUSIZ2 through NUSIZ9, as this bit is unused by the TIA register NUSIZx.

         

        Examples:

           if joy0left then player1x = player1x - 1 : _NUSIZ1{3} = 0
        
           if joy0right then player1x = player1x + 1 : _NUSIZ1{3} = 1
        
           if joy0left then player2x = player2x - 1 : NUSIZ2{3} = 0
        
           if joy0right then player2x = player2x + 1 : NUSIZ2{3} = 1
        

         

         

        Virtual Sprite Masking

        The DPC+ kernel includes masking for all virtual sprites (player1-player9). This allows a virtual sprite to move off the left or right side of the screen and not immediately appear on the opposite side. Bit 7 of NUSIZx is used to enable and disable the effect (1 is on, 0 is off).

         

        Example:

           _NUSIZ1{7} = 1 : NUSIZ2{7} = 1 : NUSIZ3{7} = 1
           NUSIZ4{7} = 1 : NUSIZ5{7} = 1 : NUSIZ6{7} = 1
           NUSIZ7{7} = 1 : NUSIZ8{7} = 1 : NUSIZ9{7} = 1
        

        Bit 6 of NUSIZx sets left or right masking. Setting the bit to 0 masks the sprite when leaving the left side of the screen and setting it to 1 masks the sprite when leaving the right side of the screen.  Once the last pixel of the virtual sprite is off the screen, it should be moved to a new location. If you don't change its location as soon as it moves off screen, it will appear on the other side.

         

        Examples:

           _NUSIZ1{7} = 1
        
        __Main_Loop
        
           if joy0left then player1x = player1x - 1 : _NUSIZ1{6} = 0
        
           if joy0right then player1x = player1x + 1 : _NUSIZ1{6} = 1
        
           drawscreen
        
           goto __Main_Loop
        
           NUSIZ2{7} = 1
        
        __Main_Loop
        
           if joy0left then player2x = player2x - 1 : NUSIZ2{6} = 0
        
           if joy0right then player2x = player2x + 1 : NUSIZ2{6} = 1
        
           drawscreen
        
           goto __Main_Loop
        

        Note: The bits mentioned above don't work with NUSIZ0. Player0 can't be masked. Player0 isn't virtual, so NUSIZ0 is a real hardware register that acts the same as it does in the standard kernel.

         

        Warning: Masking does not work with double or quad sized virtual sprites.

       

       

         

        DAH

        13 Objects with Coordinates (DPC+)

        There are 10 sprites (player0 at the top, down to player9 at the bottom), 2 missiles (missile0 above missile1) and 1 ball on the screen. Hold down the fire button and press the joystick up or down to select an object. Hold down the fire button and move the joystick left or right to change the size of the currently selected object.

         

        To move the currently selected object, release the fire button and press the joystick in any direction.

         

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

        13 Objects with Coordinates (DPC+) .bin file

         

        Here's the bB code:

        13 Objects with Coordinates (DPC+) .bas file

       

       

       

       

      Missiles (DPC+)

      In the DPC+ kernel, you set the colors of each missile using COLUM0 and COLUM1.

       

      Example:

         ;***************************************************************
         ;
         ;  Sets color of missiles.
         ;
         COLUM0 = $FE : COLUM1 = $AC 
      

      A hardware limitation causes missile0 to display the colors of player0 and missile1 to display the colors of player1-player9 if they are on the same scanline.

       

      The example image below shows player0, a virtual sprite, missile0, and missile1:

       

      Missile Example

       

      Notice how the colors of player0 contaminate missile0 and how the colors of the virtual sprite contaminate missile1 in the example image below:

       

      Missile Example

       

      This contamination can be used to your advantage in some cases. For example, a missile of the right size could be placed next to a sprite to make part of that sprite seem wider than it actually is. Add a hand and arm, a nose, the brim of a hat, a piece of a spaceship, or whatever you can dream up.

       

      The height of each missile is set using missile0height and missile1height. The smallest height that can be used with missile0height and missile1height is two. A missile will seem to disappear when its height is set to zero. And setting the height to one will cause the missile to appear on even rows and disappear on odd rows. See Missiles for more information.

       

      Just like in the standard kernel, missile width is changed using NUSIZx. To make missile1 retain a width greater than 1 in the the DPC+ kernel the NUSIZx for any sprites used on screen must be set. For example, if you use all 9 virtual sprites on the screen, _NUSIZ1 through NUSIZ9 must be set. Check out 13 Objects with Coordinates for a working example of this.

       

       

       

       

      Collision Detection (DPC+)

      DPC+ collision is pixel perfect, but doesn't take hardware-reflected sprites into account. The virtual sprites are all player1, so you can't use collision() to detect collisions between them, and when a virtual sprite collides with the playfield, you won't know exactly which one did it without coordinate checking. If you use NUSIZ0 to make a double-sized or quad-sized player0, only player1 will be able to collide with all parts of it. Player2 through player9 will only register a collision with the first 8 bits to the left as if player0 is still small.

       

      Using set kernel_options collision(player1,playfield) will return the y-coordinate where the first such collision occurs, so you can later figure out what sprite it was. Value is returned in temp4 after a drawscreen.

       

       

       

       

      The Playfield (DPC+)

      You can specify an entire playfield at once by using the playfield: command. The syntax of the command is:

       

        playfield:
        (pixels)
      end

       

      Example:

         playfield:
         ..XXXX....XXXX....XXXX....XXXX..
         .XX..XX..XX..XX..XX..XX..XX..XX.
         .XX..XX..XX..XX..XX..XX..XX..XX.
         .XX..XX..XX..XX..XX..XX..XX..XX.
         .XX..XX..XX..XX..XX..XX..XX..XX.
         .XX..XX..XX..XX..XX..XX..XX..XX.
         .XX..XX..XX..XX..XX..XX..XX..XX.
         ..XXXX....XXXX....XXXX....XXXX..
         ................................
         ................................
         ....X.......X.......X.......X...
         ...XX......XX......XX......XX...
         ..XXX.....XXX.....XXX.....XXX...
         ...XX......XX......XX......XX...
         ...XX......XX......XX......XX...
         ...XX......XX......XX......XX...
         ...XX......XX......XX......XX...
         .XXXXXX..XXXXXX..XXXXXX..XXXXXX.
         ................................
         ................................
         ..XXXX....XXXX....XXXX....XXXX..
         .X...XX..X...XX..X...XX..X...XX.
         .....XX......XX......XX......XX.
         .....XX......XX......XX......XX.
         ..XXXX....XXXX....XXXX....XXXX..
         .XX......XX......XX......XX.....
         .XX......XX......XX......XX.....
         .XXXXXX..XXXXXX..XXXXXX..XXXXXX.
         ................................
         ................................
         ..XXXX....XXXX....XXXX....XXXX..
         .X...XX..X...XX..X...XX..X...XX.
         .....XX......XX......XX......XX.
         ...XXX.....XXX.....XXX.....XXX..
         .....XX......XX......XX......XX.
         .....XX......XX......XX......XX.
         .X...XX..X...XX..X...XX..X...XX.
         ..XXXX....XXXX....XXXX....XXXX..
         ................................
         ................................
         ....XX......XX......XX......XX..
         ...XXX.....XXX.....XXX.....XXX..
         ..X.XX....X.XX....X.XX....X.XX..
         .X..XX...X..XX...X..XX...X..XX..
         .X..XX...X..XX...X..XX...X..XX..
         .XXXXXX..XXXXXX..XXXXXX..XXXXXX.
         ....XX......XX......XX......XX..
         ....XX......XX......XX......XX..
         ................................
         ................................
         .XXXXXX..XXXXXX..XXXXXX..XXXXXX.
         .XX......XX......XX......XX.....
         .XX......XX......XX......XX.....
         ..XXXX....XXXX....XXXX....XXXX..
         .....XX......XX......XX......XX.
         .....XX......XX......XX......XX.
         .X...XX..X...XX..X...XX..X...XX.
         ..XXXX....XXXX....XXXX....XXXX..
         ................................
         ................................
         ..XXXX....XXXX....XXXX....XXXX..
         .XX...X..XX...X..XX...X..XX...X.
         .XX......XX......XX......XX.....
         .XXXXX...XXXXX...XXXXX...XXXXX..
         .XX..XX..XX..XX..XX..XX..XX..XX.
         .XX..XX..XX..XX..XX..XX..XX..XX.
         .XX..XX..XX..XX..XX..XX..XX..XX.
         ..XXXX....XXXX....XXXX....XXXX..
         ................................
         ................................
         ..XXXXX...XXXXX...XXXXX...XXXXX.
         .X....X..X....X..X....X..X....X.
         .....XX......XX......XX......XX.
         ....XX......XX......XX......XX..
         ...XX......XX......XX......XX...
         ..XX......XX......XX......XX....
         ..XX......XX......XX......XX....
         ..XX......XX......XX......XX....
         ................................
         ................................
         ..XXXX....XXXX....XXXX....XXXX..
         .XX..XX..XX..XX..XX..XX..XX..XX.
         .XX..XX..XX..XX..XX..XX..XX..XX.
         ..XXXX....XXXX....XXXX....XXXX..
         .XX..XX..XX..XX..XX..XX..XX..XX.
         .XX..XX..XX..XX..XX..XX..XX..XX.
         .XX..XX..XX..XX..XX..XX..XX..XX.
         ..XXXX....XXXX....XXXX....XXXX..
      end
      

       

       

       

         

        pfclear (DPC+)

        This is faster than regular pfclear in the standard kernel because the ARM CPU is faster than a 6507 at clearing its memory.

         

         

         

         

        DFxFRACINC (DPC+)

        The DPC+ kernel uses DFxFRACINC registers for certain jobs. No, it doesn't have anything to do with evil hydrofracking; it stands for Data Fetcher Fractional Increment. Normal data fetchers are 32 bit integers and automatically increment by 1 every time you read from them. Fractional data fetchers use the same idea as fixed point variables. Since 32 bit values are used in the ARM processor, the value is divided up as 24.8 instead of 8.8. Instead of incrementing by 1, they increment by 0.y where y is whatever you assigned DFxFRACINC.

         

        Note: DFxFRACINC must be placed in the main loop before/with drawscreen.

         

         

         

         

        Playfield Resolution (DPC+)

        Horizontal resolution is 32 playfield pixels across (same as the standard kernel). Vertical resolution with the DPC+ kernel is a little more complicated. The playfield is divided into 4 columns and each column can have a different resolution (pixel height). Column resolution is controlled using 4 registers: DF0FRACINC, DF1FRACINC, DF2FRACINC, and DF3FRACINC.

         

        Under the image below is an example program by RevEng:

         

        Playfield Example

         

        The value stored to the DF0FRACINC-DF3FRACINC registers determines how many scanlines it takes to change a playfield row.  When using these registers, you shouldn't be thinking about the number of rows in the playfield but rather approximately how many scanlines tall each playfield row is. Currently the kernel displays 176 scanlines, but this is subject to change.

         

        The value in DF4FRACINC determines how many times playfield pixels can change color from top to bottom.

         

        The value in DF6FRACINC determines how many times the background color can be changed from top to bottom.

         

        Below is a table of some playfield values that should work in the DPC+ kernel. These aren't the only values that work, or the only combination of values that work, so I encourage experimentation if you're doing something different (such as not aligning color changes to playfield rows.)

         

        The first column in the table below is the number of scanlines in each row. The next is the approximate resolution (rounded up, and may change.)  Next is DF(0-3)FRACINC, which control the number of scanlines in each row of each playfield column. Last is the playfield color value (DF4FRACINC). Complicating matters somewhat is that color updates occur half as often, so not all playfield values can have colors that work nicely with them.  If this is the case, a dash is shown in place of the DF4FRACINC value. The values for DF4FRACINC may also be stored to DF6FRACINC if you wish to match the background colors with the playfield rows.

        Scanlines       Resolution      DF(0-3)FRACINC          DF4FRACINC
        1               176             255                     -
        2               88              128                     255 
        3               59 *            86                      -
        4               44              64                      128
        5               36 *            52                      -
        6               30 *            43                      86
        7               26 *            37                      -
        8               22              32                      64
        9               20 *            29                      -
        10              18 *            26                      52
        11              16              24                      -
        12              15 *            22                      44
        13              14 *            20                      -
        14              13 *            19                      38
        15              12 *            18                      -
        16              11              16                      32
        18              10 *            15                      30
        19              10 *            14                      -
        20              9 *             13                      26
        22              8               12                      24
        24              8 *             11                      22
        26              7 *             10                      20
        29              7 *             9                       -
        32              6 *             8                       16
        37              5 *             7                       -
        44              4               6                       12
        52              4 *             5                       10
        64              3 *             4                       8
        86              3 *             3                       6
        128             2 *             2                       4
        176             1               1 or 0                  1 or 0
        

        Note: A "*" in the resolution column means that the bottom row isn't full height.

         

        A few examples:

         
           ;***************************************************************
           ;
           ;  88 rows that are 2 scanlines high.
           ;
           DF6FRACINC = 255
           DF4FRACINC = 255
        
           DF0FRACINC = 128
           DF1FRACINC = 128
           DF2FRACINC = 128
           DF3FRACINC = 128
        
        
           ;***************************************************************
           ;
           ;  44 rows that are 4 scanlines high.
           ;
           DF6FRACINC = 128
           DF4FRACINC = 128
        
           DF0FRACINC = 64
           DF1FRACINC = 64
           DF2FRACINC = 64
           DF3FRACINC = 64
        
        
           ;***************************************************************
           ;
           ;  22 rows that are 8 scanlines high.
           ;
           DF6FRACINC = 64
           DF4FRACINC = 64
        
           DF0FRACINC = 32
           DF1FRACINC = 32
           DF2FRACINC = 32
           DF3FRACINC = 32
        
        
           ;***************************************************************
           ;
           ;  11 rows that are 16 scanlines high.
           ;
           DF6FRACINC = 32
           DF4FRACINC = 32
        
           DF0FRACINC = 16
           DF1FRACINC = 16
           DF2FRACINC = 16
           DF3FRACINC = 16
        
        
           ;***************************************************************
           ;
           ;  6 rows that are 32 scanlines high.
           ;
           DF6FRACINC = 16
           DF4FRACINC = 16
        
           DF0FRACINC = 8
           DF1FRACINC = 8
           DF2FRACINC = 8
           DF3FRACINC = 8
        

         

         

           

          DAH

          DFxFRACINC Tool (DPC+)

          Press the joystick left or right to select DF6FRACINC, DF0FRACINC, DF1FRACINC, DF2FRACINC, DF3FRACINC or DF4FRACINC. Press the joystick up or down to increase or decrease the selected register. Press the fire button to slow things down when you get near a number that you'd like to stop on. Press the select switch for presets. Press the reset switch to go back to the default settings.

           

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

          DFxFRACINC Tool (DPC+) .bin file

           

          Here's the bB code:

          DFxFRACINC Tool (DPC+) .bas file

         

         

         

         

        pfscroll (DPC+)

        Unlike pfscroll for the other kernels, the syntax is "pfscroll [value]" or "pfscroll [variable]". It does a coarse scroll, 1 PF line at a time, so if you want smooth scrolling, you need a fine DF#FRACINC resolution. The playfield strips are 256 bytes in size, so if you want to scroll down, use "pfscroll 255" to scroll down by 1, "pfscroll 254" to scroll down by 2, and so on.

         

        The DPC+ pfscroll command also has 2 optional queue values to indicate the starting column and ending column that you want to scroll. Right now variables won't work for the queue numbers, but you can still use a variable for the scrolling value, even if you add queue values.

         

        The syntax is:

        pfscroll [value or variable] [queue value] [queue value]

         

        0 = DF0FRACINC Playfield Data

        1 = DF1FRACINC Playfield Data

        2 = DF2FRACINC Playfield Data

        3 = DF3FRACINC Playfield Data

        4 = Playfield Color Data

        6 = Background Color Data

         

        Playfield Example

         

        Examples:

           pfscroll 1 ; Scrolls the DPC+ playfield one line.
           pfscroll 1 0 1 ; Scrolls DFO and DF1 one line.
           pfscroll 1 2 3 ; Scrolls DF2 and DF3 one line.
           pfscroll 1 1 3 ; Scrolls DF1 through DF3 one line.
           pfscroll 1 0 4 ; Scrolls whole playfield and PF colors one line.
           pfscroll 1 4 4 ; Scrolls only the PF colors one line.
        

        A note on the last 2 examples. Due to the way the kernel updates PF colors, to keep colors lock-step with the PF graphics you should either set DF4FRACINC to twice the number of DF[0-3]FRACINC fetchers, or scroll the PF queues twice as much as the 4th (color) queue. The same also applies to DF6FRACINC:

           pfscroll 1 0 6 ; Scrolls playfield and background colors one line.
           pfscroll 1 6 6 ; Scrolls only the background colors one line.
        

         

         

           

          DAH

          Playfield Color & Background Color Scrolling

          with Changeable Score Background Color (DPC+)

          Press fire button to change score background color. Hold to change colors slowly. Quickly press and release repeatedly to change colors faster.

           

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

          Color Scrolling (DPC+) .bin file

           

          Here's the bB code:

          Color Scrolling (DPC+) .bas file

         

         

         

         

        Playfield Colors (DPC+)

        Specifies colors of each playfield row and must be followed by end. You can specify this as many times as you want.

         

        Example:

           pfcolors:
           $0E
           $0C
           $0A
           $08
           $06
           $1E
           $1C
           $1A
           $18
           $16
           $2E
           $2C
           $2A
           $28
           $26
           $3E
           $3C
           $3A
           $38
           $36
           $4E
           $4C
           $4A
           $48
           $46
           $5E
           $5C
           $5A
           $58
           $56
           $6E
           $6C
           $6A
           $68
           $66
           $7E
           $7C
           $7A
           $78
           $76
           $9E
           $9C
           $9A
           $98
           $96
           $AE
           $AC
           $AA
           $A8
           $A6
           $BE
           $BC
           $BA
           $B8
           $B6
           $CE
           $CC
           $CA
           $C8
           $C6
           $DE
           $DC
           $DA
           $D8
           $D6
           $EE
           $EC
           $EA
           $E8
           $E6
           $3E
           $3C
           $3A
           $38
           $36
           $4E
           $4C
           $4A
           $48
           $46
           $5E
           $5C
           $5A
           $58
           $56
           $6E
           $6C
           $6A
           $68
           $66
        end
        

        Remember, the value in DF4FRACINC determines how many times playfield pixels can change color from top to bottom.

         

        The code below will make all of the playfield pixels blue:

           DF4FRACINC = 0
        
           pfcolors:
           $86
        end
        

         

         

         

         

        Background Colors (DPC+)

        Lets you pick a color for each scanline in the background and must be followed by end. You can specify this as many times as you want.

         

        Example:

           bkcolors:
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $80
           $82
           $82
           $82
           $82
           $82
           $82
           $82
           $82
           $84
           $84
           $84
           $84
           $84
           $84
           $84
           $86
           $86
           $86
           $86
           $86
           $86
           $88
           $88
           $88
           $88
           $88
           $8A
           $8A
           $8A
           $8A
           $8C
           $8C
           $8C
           $8E
           $8E
           $00
           $F0
           $F2
           $F2
           $F4
           $F4
           $F4
           $F6
           $F6
           $F6
           $F6
           $F8
           $F8
           $F8
           $F8
           $F8
           $F8
           $F8
           $F8
           $00
        end
        

        Remember, the value in DF6FRACINC determines how many times the background color can be changed from top to bottom.

         

        The code below will make the background all red:

           DF6FRACINC = 0
        
           bkcolors:
           $44
        end
        

      If you'd like to have a good way to control the background color behind the score, check out the DPC+ Score Background Color asm code by RevEng.

       

       

       

       

      vblank (DPC+)

      The DPC+ kernel itself uses vblank to do its own setup, so there are only a few free cycles left in vblank for your program. If you must use vblank, it needs to go in bank 1.

    Back to Top

     

     

     

     

    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.

     

    Some versions of the bB multisprite kernel require player0 to be defined each time you go through the loop, so try defining player0 outside the loop first, and if that doesn't work then move it inside the 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
    

       

       

      Related Link

      pfread for the Multisprite Kernel (by bogax)

      A replacement for pfread_msk.asm.

       

       

       

      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. When using bankswitching, this must be in your main loop or called from your main loop.

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

       

      Note: To keep your title screen from getting reversed and scrambled, set REFP0 and REFP1 to zero before jumping back to it. And when your game leaves the title screen, if you don't set missile0height and missile1height in your main loop setup, be sure to set them to zero and move the missile positions off the screen.

       

      The Titlescreen Kernel

       

       

       

      256k, 128k, 64k, 32k, Multikernel Frameworks  (by RevEng)

      Build a game out of individual 4k projects that can have their own separate kernels and kernel options.

       

      256k, 128k, 64k, 32k, Multikernel Frameworks

       

       

       

      bB with Native 64k Cart Support (by RevEng)

      This is the same as regular 1.1d bB, except it has 64k ROM format support and a number of general bB fixes.

       

      bB with Native 64k Cart Support

       

       

       

      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

       

      See this post at AtariAge by RevEng if you are using more than one bank.

       

       

       

      Read Atari Keyboard Controllers  (by CurtisP)

      CurtisP cleaned up SeaGtGruff's code, added left or right keypad selection, and packaged it into a couple of include files. The ZIP file contains the include files, two sample programs, and minimal documentation.

       

      Keypad Games

       

       

      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

       

       

       

      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

       

       

       

      Colorplayers  (by RevEng)

      This standard kernel mod lets you use no_blank_lines and have two multicolored sprites instead of just one. The trade-offs are the loss of both missiles and the center bytes of the playfield are symmetric.

       

      Colorplayers

Back to Top

 

 

 

 

 

Compiler Directives (set command)

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

set <directive> <option>

Currently, the following directives are supported:

    smartbranching
    tv
    romsize
    optimization
    kernel
    kernel_options
    debug
    legacy

 

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

 

 

     

    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. [Note from Random Terrain: I had all kinds of mysterious problems when using smartbranching with a very long program. Switching to then goto fixed all of the weird problems, so I dropped smartbranching. I use then goto instead.]

     

    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 bankswitching, and additional considerations are needed for this. See Bankswitching 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. Except for none, you can use any combination of options. The options must be on separate lines (unlike kernel_options, they cannot be placed on the same line). Below is an example showing what it would look like if you wanted to use all of the options in your program:

       set optimization speed
       set optimization size
       set optimization noinlinedata
       set optimization inlinerand
    

    All of these also apply to the DPC+ kernel, except size, which only applies to the old multisprite kernel.

     

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

      size (multisprite kernel only)
      May decrease the size of generated code when using the multisprite kernel. bB will attempt to reuse bytes that may otherwise go wasted when it tries to align sprite data.

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

     

    Warning: Using "set optimization none" will disable previous optimization lines, so don't use it if you are using any of the other options. (Thanks to RevEng for clarification.)

     

     

     

     

    kernel

    Determines which kernel to use with your game. Currently there are 29 kernels available. There is a DPC+ kernel, 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.

     

    DPC+ kernel example:

       set kernel DPC+
    

    See DPC+ kernel for more information about the DPC+ kernel.

     

    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. These options will not work with the DPC+ 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. These options are for the standard kernel and are not used with the DPC+ kernel.

     

     

   set kernel_options  

   rem  *   

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

        Text by SeaGtGruff (adapted by Random Terrain)

        Paddles only work with the standard kernel. Before you can read paddles with your batari Basic program, you must set the readpaddle and no_blank_lines kernel_options.

         

        Example:

           set kernel_options no_blank_lines readpaddle
        

        Note that readpaddle must be used with no_blank_lines. As it shows in the interactive kernel_options chart, the cost of using no_blank_lines is the loss of missile0. If you add player1colors, you'll also lose missile1.

         

        Example:

           set kernel_options player1colors no_blank_lines readpaddle
        

        To read a particular paddle, you must set currentpaddle to the number of the paddle you want to read.

         

        Example:

           currentpaddle = 0
        

        Note that there are two controller ports, and the paddles come in pairstwo paddles are connected to one plugso you can have either two paddles plugged into one port (and something else, like a joystick, plugged into the other port), or you can have four paddles plugged into two ports. Paddles 0 and 1 are the two paddles plugged into the left controller port and paddles 2 and 3 are the two paddles plugged into the right controller port.

         

        After you set currentpaddle to the number of the paddle you want to read, you must call drawscreen.

         

        Example:

           drawscreen
        

        That doesn't mean you have to call drawscreen right away, but the paddle you've selected gets read during drawscreen, so you won't be able to tell what the value of the selected paddle is until after the drawscreen routine has finished.

         

        To get the value of the selected paddle, you must check the paddle variable, either inside an if-then statement, or more likely by setting some other variable to it.

         

        Example:

           if paddle = 10 then do_something : rem  *  not very useful
        
           player0x = paddle : rem  *  more useful
        

        Note that the value of paddle will be between 0 and 77, so that means you'll probably want to use a formula to convert the value of the paddle into a suitable screen coordinate, depending on whether you're using the paddle to move an object in the vertical or horizontal direction, and whether you want the object to move the full range of that direction, or restrict it to some portion of the screen. You'll also want to take the size of the object into account, since that affects what its minimum and maximum coordinates can be. For example, if you want to move a sprite horizontally (its x coordinate), and you want the sprite to move across the full width of the screen but with no wraparound, and the sprite is 8 pixels wide drawn at the single-width size, then its x coordinate can range from 1 (farthest left) to 161 - 8 = 153 (farthest right).

         

        Example:

           player0x = paddle * 2 + 1
        
           if player0x > 153 then player0x = 153
        

        You can only read one paddle at a time, so if you want to read two paddles, you can use currentpaddle as a way of telling your program which paddle to process after drawscreen has finished.

         

        Example:

           set kernel_options no_blank_lines readpaddle
        
        __Main_Loop
        
        
           rem  ************************************************************
           rem  *
           rem  *  Selects the next paddle to be read.
           rem  `
           currentpaddle = currentpaddle + 1
        
        
           rem  ````````````````````````````````````````````````````````````
           rem  `  Paddle 1, then paddle 0, then paddle 1 again, etc.
           rem  `
           if currentpaddle = 2 then currentpaddle = 0
        
        
        
        
           rem  ************************************************************
           rem  *
           rem  *  Draws the screen and reads the current paddle.
           rem  `
           drawscreen
        
        
        
           rem  ************************************************************
           rem  *
           rem  *  Jumps to the appropriate paddle section.
           rem  `
           on currentpaddle goto __Paddle_0 __Paddle_1
        
        
        __Come_Back
        
        
           rem  ************************************************************
           rem  *
           rem  *  More code that you want to add goes here.
           rem  *
           rem  ************************************************************
        
        
           goto __Main_Loop
        
        
        
        
           rem  ************************************************************
           rem  ************************************************************
           rem  *
           rem  *  Code for paddle 0.
           rem  *
           rem  `
        __Paddle_0
        
        
           rem  ************************************************************
           rem  *
           rem  *  Converts value to useable coordinate.
           rem  `
           player0x = paddle * 2 + 1
        
        
           rem  ````````````````````````````````````````````````````````````
           rem  `  Limits player movement.
           rem  `
           if player0x > 153 then player0x = 153
        
        
           goto __Come_Back
        
        
        
        
           rem  ************************************************************
           rem  ************************************************************
           rem  *
           rem  *  Code for paddle 1.
           rem  *
           rem  `
        __Paddle_1
        
        
           rem  ************************************************************
           rem  *
           rem  *  Converts value to useable coordinate.
           rem  `
           player1x = paddle * 2 + 1
        
        
           rem  ````````````````````````````````````````````````````````````
           rem  `  Limits player movement.
           rem  `
           if player1x > 153 then player1x = 153
        
        
           goto __Come_Back
        

        The button on paddle 0 is read by using joy0right, paddle 1 button = joy0left, paddle 2 button = joy1right, and paddle 3 button = joy1left.

         

        Below is a simple single-player example program for you to play around with.

         

         

         

         

        If you use Stella, the most popular 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 Input Settings button, click the Devices & Ports tab, make sure the little boxes next to "grab mouse in emulation mode" and "use mouse as a controller" have an x in them, then click the OK button. Now click the Game Properties button, click the Controller tab, change both controllers to Paddles, click the OK button, then hold down the Ctrl key and press R. Your paddle game should now work properly.

         

        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.

         

        Warning: In case you missed it above, readpaddle 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.

         

        Warning: If you are going to use playercolors, it must be used with player1colors. Let the kernel_options Chart be your guide.

         

         

         

         

        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. Also remember that if you use pfcolors and try to compile your game without defining pfcolors:, you will probably get an error.

         

         

         

         

        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. Also remember that if you use pfheights and try to compile your game without defining pfheights:, you will probably get an error.

         

         

         

         

        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.

Back to Top

 

 

 

 

 

Bankswitching (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.

Bankswitching 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 bankswitching to a few times each frame.

To activate bankswitching, 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 sprite 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.

 

For the standard kernel, playfield graphics data gets stored in whatever bank you place it in. For the multisprite kernel, playfield graphics data automatically gets stored in the last bank. For the DPC+ kernel, playfield graphics data automatically gets stored in the "graphics" bank.

 

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.

 

 
   set romsize 8k
   bank 2
   set romsize 8kSC
   bank 2
   set romsize 16k
   bank 2
   bank 3
   bank 4
   set romsize 16kSC
   bank 2
   bank 3
   bank 4
   set romsize 32k
   bank 2
   bank 3
   bank 4
   bank 5
   bank 6
   bank 7
   bank 8
   set romsize 32kSC
   bank 2
   bank 3
   bank 4
   bank 5
   bank 6
   bank 7
   bank 8

 

     

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

    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 bankswitching 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 bankswitching. See compiler directives for more information.

    Most existing bB code written for standard bankswitching 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.

Back to Top

 

 

 

 

 

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

Text by SeaGtGruff (adapted by Random Terrain)

The Atari 2600's sound is generated by the TIA chip, which has two independent audio channels that work identically (channel 0 and channel 1). Each channel has three registers that determine the sound that will be produced (AUDV0, AUDC0, AUDF0, AUDV1, AUDC1, AUDF1).

 

AUDVx stands for AUDio Volume. The volume register determines the volume or amplitude of the sound that will be produced. Valid values are 0 through 15.

 

AUDCx stands for AUDio Control. The control register determines the waveform or tonal quality that will be produced. Valid values are 0 through 15.

 

AUDFx stands for AUDio Frequency. The frequency register determines the frequency or note that will be produced. Valid values are 0 through 31.

 

Set AUDV0 or AUDV1 to pick the volume or amplitude of the sound you want to play0 is "off" or "mute" and 15 is the loudest.

 

Set AUDC0 or AUDC1 to pick the waveform you want to use. With one exception, each waveform is just a stream of 0s and 1s that are repeated endlessly in a specific pattern, causing the speaker to vibrate in that pattern and create a sound. The length of the pattern determines the primary frequency of the sound, and the complexity of the pattern determines how "pure" or "noisy" the sound is. One waveform is just a stream of 1s (or "always on"), so it sounds "silent" (because the speaker doesn't vibrate back and forth the way it does with the other waveforms)-- but you can use the volume register with this "always on" waveform to create your own waveforms.

 

Set AUDF0 or AUDF1 to pick the frequency or note you want to play. The TIA doesn't use the standard set of musical notes (C, D, F#, G, etc.)instead, it uses harmonics (or really "subharmonics") of the primary frequency that's been selected with the AUDC0 or AUDC1 register. To determine what the resulting frequency will be, add 1 to the AUDF0 or AUDF1 setting and divide the primary frequency by that numberfor example, AUDF0 = 3 divides the primary frequency by 4 (3 plus 1), whereas AUDF0 = 7 divides it by 8.

 

Unless you're using the "always on" waveform, making music with the TIA is a "set it and forget it" thing, meaning you can set AUDC0, AUDV0, and AUDF0 (or AUDC1, AUDV1, and AUDF1) and they'll keep their values, playing the same sound continuously until you change their settings to pick a different sound. In practical terms, this means you don't need to keep setting them over and over again within a loop to keep playing the same sound; you just set them once and then you don't have to worry about them again until you're ready to play a different sound.

 

 

Below are four examples that show how you can make sounds without using data.

 

A plain, simple sound is made in the example below when the fire button is pressed:

   dim _Sound01 = a


__Main_Loop

   if joy0fire then if !_Sound01 then _Sound01 = 15

   if !_Sound01 then goto __Skip_Sound01
   AUDV0 = 8 : AUDC0 = 4 : AUDF0 = 19 : _Sound01 = _Sound01 - 1
   if !_Sound01 then AUDV0 = 0

__Skip_Sound01

   drawscreen

   goto __Main_Loop

 

 

This example changes the volume of the sound when the fire button is pressed:

   dim _Sound01 = a
   dim _Vol01 = b


__Main_Loop

   if joy0fire then if !_Sound01 then _Sound01 = 30 : _Vol01 = 12

   if !_Sound01 then goto __Skip_Sound01
   temp5 = _Sound01 & %000000010
   if temp5 && _Vol01 then _Vol01 = _Vol01 - 1
   AUDV0 = _Vol01  : AUDC0 = 4 : AUDF0 = 19
   _Sound01 = _Sound01 - 1 : if !_Sound01 then AUDV0 = 0

__Skip_Sound01

   drawscreen

   goto __Main_Loop

 

 

The frequency is changed in this example when the fire button is pressed:

   dim _Sound01 = a
   dim _Freq01 = c


__Main_Loop

   if joy0fire then if !_Sound01 then _Sound01 = 31 : _Freq01 = 31

   if !_Sound01 then goto __Skip_Sound01
   if _Freq01 then _Freq01 = _Freq01 - 1
   AUDV0 = 8 : AUDC0 = 4 : AUDF0 = _Freq01
   _Sound01 = _Sound01 - 1 : if !_Sound01 then AUDV0 = 0

__Skip_Sound01

   drawscreen

   goto __Main_Loop

 

 

This example changes the volume and the frequency when the fire button is pressed:

   dim _Sound01 = a
   dim _Vol01 = b
   dim _Freq01 = c


__Main_Loop

   if joy0fire then if !_Sound01 then _Sound01 = 31 : _Vol01 = 12 : _Freq01 = 31

   if !_Sound01 then goto __Skip_Sound01
   temp5 = _Sound01 & %000000010
   if temp5 && _Vol01 then _Vol01 = _Vol01 - 1
   if _Freq01 then _Freq01 = _Freq01 - 1
   AUDV0 = _Vol01  : AUDC0 = 4 : AUDF0 = _Freq01
   _Sound01 = _Sound01 - 1 : if !_Sound01 then AUDV0 = 0

__Skip_Sound01

   drawscreen

   goto __Main_Loop

 

 

If you tried all four examples in a row, you can see how easy it is to take a plain old lazy sound effect and make it sound more complex or more pleasing to the ear.

 

 

 

     

     

    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


Back to Top

 

 

 

 

 

Joysticks

Did You Know?

The joysticks and fire buttons are similar to bit operations in how they are read (an equal sign is not used).

 

So don't do this:

  if joy0fire = 1 then . . .

Do this instead:

  if joy0fire then . . .

And don't do this:

   if joy0fire = 0 then . . .

Do this instead:

  if !joy0fire then . . .

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
   if joy0fire then goto __Purple_Monkey

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

   if !joy0up then goto __Tasty_Pilgrim

Below are two example programs that might be useful. The first one disables rapid-fire. The second program uses variable-speed rapid-fire.

 

 

     

    DAH

    Repetition Restrainer for the Fire Button

    This example program disables rapid-fire using a single bit instead of wasting a full variable. Press the fire button to shoot the missile. The fire button must be released and pressed to shoot the missile again. Nothing will happen if the fire button is held down. This is used for games where you do not want rapid-fire. A slightly edited version of this can be used to keep a button press in a title screen section or a game over section from contaminating another part of your game. For example, you don't want a title screen button press to count as a button press in the actual game. A similar thing can be done for the reset switch.

     

    Atari's Game Standards and Procedures says "When a game is started with the joystick button, the game should not use the same button depression for a game action (like firing a shot, for instance). The Reset switch should be debounced so it does not start another game until it is first released."

     

    Note: The word debounce was used by Atari and others to mean that a switch or button should be kept from repeating when held down, but the word has another meaning when it comes to electronics. Edge detection is supposed to be a better term for it, but it has very little meaning and appeal to those of use who don't know much about electronics. The word debounce at least made some sense because it contained the word bounce. If you didn't want a fire button or a console switch to 'bounce' in rapid-fire succession when held down, the word debounce seemed fit. Repetition restrainer has now been chosen as a replacement.

     

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

    Repetition Restrainer for the Fire Button Example .bin file

     

    Here's the bB code:

    Repetition Restrainer for the Fire Button Example .bas file

     

     

     

    DAH

    Variable-speed Rapid-fire

    This example program uses variable-speed rapid-fire. Press the fire button to shoot the missile. If the fire button is not released, the missile will repeat at a certain speed. Change the _FBSpeed constant from 20 to a larger number to slow it down.

     

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

    Variable-speed Rapid-fire Example .bin file

     

    Here's the bB code:

    Variable-speed Rapid-fire Example .bas file

     

     

     

    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. Since around 1982, it's basically been the consensus standard that an Atari 2600 game should start if the reset switch is pressed and if the left fire button is pressed. See Game Standards and Procedures: Starting a Game for more information.

     

     

     

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

Back to Top

 

 

 

 

 

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. Since around 1982, it's basically been the consensus standard that an Atari 2600 game should start if the reset switch is pressed and if the left fire button is pressed. See Game Standards and Procedures: Starting a Game for more information.

     

     

    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

Back to Top

 

 

 

 

Did You Know?

The calculator on your computer can convert decimal, hex, and binary numbers, but this online tool seems to be faster, easier to use, and more fun.

 

 

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.


    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.

Back to Top

 

 

 

 

Did You Know?

The calculator on your computer can convert decimal, hex, and binary numbers, but this online tool seems to be faster, easier to use, and more fun.

 

 

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 bankswitching 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 bankswitched 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 temporary 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 bankswitched 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

    Text by SeaGtGruff with a bit of text from RevEng (adapted by Random Terrain)

    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. Testing shows that 46 parameters can be passed to a macro. That makes sense, as statements are limited to 50 strings, and the command, macroname, and newline chars use up 4 strings.

     

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

Back to Top

 

 

 

 

 

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 very 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 the beginning of your program, before anything else). 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.

Back to Top

 

 

 

 

 

Atari 7800

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

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

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

 

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

   goto __Pause_Game

 

 

Back to Top

 

 

 

 

Did You Know?

If you get a "complex condition detected" error message, you might have an if-then that is missing a then. Using things in an if-then that aren't supposed to be there can also cause this error. Check out the "Did You Know?" box here and here.

 

 

Troubleshooting

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

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

 

 

     

    Compilation Errors

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

     

     

     

       

      Preprocessor Errors

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

      An example of a preprocessor error:

      (34) unrecognized character: "@"

       

       

       

       

      Compiler Errors

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

      A common cause of compiler errors arises from a failure to indent keywords or from indenting labels and line numbers. For more information about indenting, see Labels and Line Numbers.

      An example of a compiler error:

      (34) Error: Unknown Keyword: hgoto

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

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

       

       

       

       

      Assembler Errors

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

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

      An example of an assembler error:

      (1767) Error: Value in 'cmp #512' must be <$100

      This means that the value you were comparing to or tried to assign (in this case 512) was too large. All values must be 0-255 except score, and even that can't be compared normally (see score).

       

       

       

         

        Branches out of Range

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

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

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

           set smartbranching on
        

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

         

         

         

         

        Syntax Errors

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

         

         

         

         

        Duplicate Labels

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

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

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

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

         

         

         

         

        Unresolved Symbols

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

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

           goto 300
        

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

           a = myfunction(33)
        

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

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

     

     

     

     

    Searching the Assembly File

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

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

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

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

     

     

     

     

    Other Errors

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

     

     

      Blank Screen

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

       

       

       

      Players Use Score Color

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

       

       

       

      Timing problems

      My game jitters, shakes or rolls!

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

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

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

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

      See drawscreen for more information about timing problems.

       

       

       

      Games for Earlier Versions of bB Don't Work Correctly

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

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

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

       

       

       

      Upgrading to Latest Version of bB Doesn't Fix Anything

      Make sure that an old std_kernel.asm file is not in the project folder that you're using or you'll always be using the old std_kernel.asm file instead of the new one that comes with the latest version of bB.

Back to Top

 

 

 

 

 

Memory Maps

Note: playfieldpos is an internal system variable that controls which line to start drawing the top playfield block and when to stop drawing the bottom one. The scrolling routines update it automatically so it's not necessary to use it unless you need to know where the playfield is located.

 

 

 

Standard Kernel Memory Map

 

Multisprite Kernel Memory Map

$80    player0x
$81    player1x
$82    player0colorstore
       missile0x
$83    missile1x
$84    ballx
$85    player0y
       objecty
$86    player1y
$87    player1color
       missile1height
$88    missile1y
$89    bally
$8a    player0pointer
       player0pointerlo
$8b    player0pointerhi
$8c    player1pointer
       player1pointerlo
$8d    player1pointerhi
$8e    player0height
$8f    player1height
$90    player0color
       currentpaddle
       missile0height
$91    paddle
       missile0y
$92    ballheight
$93    score
$94    
$95    
$96    scorepointers
$97    
$98    
$99    
$9a    
$9b    
$9c    temp1
$9d    temp2
$9e    temp3
$9f    temp4
$a0    temp5
$a1    temp6
$a2    rand
$a3    scorecolor
$a4    var0 
$a5    var1 
$a6    var2 
$a7    var3 
$a8    var4 
$a9    var5 
$aa    var6 
$ab    var7 
$ac    var8 
$ad    var9 
$ae    var10
$af    var11
$b0    var12
$b1    var13
$b2    var14
$b3    var15
$b4    var16
$b5    var17
$b6    var18
$b7    var19
$b8    var20
$b9    var21
$ba    var22
$bb    var23
$bc    var24
$bd    var25
$be    var26
$bf    var27
$c0    var28
$c1    var29
$c2    var30
$c3    var31
$c4    var32
$c5    var33
$c6    var34
$c7    var35
$c8    var36
$c9    var37
$ca    var38
$cb    var39
$cc    var40
$cd    var41
$ce    var42
$cf    var43
$d0    var44
$d1    var45
$d2    var46
$d3    var47
$d4    temp7
$d5    playfieldpos
$d6    A
       a
$d7    B
       b
$d8    C
       c
$d9    d
       D
$da    E
       e
$db    F
       f
$dc    G
       g
$dd    H
       h
$de    I
       i
$df    J
       j
$e0    K
       k
$e1    L
       l
$e2    M
       m
$e3    N
       n
$e4    O
       o
$e5    P
       p
$e6    Q
       q
$e7    R
       r
$e8    S
       s
$e9    T
       t
$ea    U
       u
$eb    V
       v
$ec    W
       w
$ed    X
       x
$ee    Y
       y
$ef    Z
       z
$f0    pfheighttable
       pfcolortable
       aux1
$f1    aux2
$f2    lifepointer
       aux3
       pfscore1
$f3    aux4
       pfscore2
       lives
$f4    aux5
       pfscorecolor
       lifecolor
$f5    statusbarlength
       aux6
$f6    stack1
$f7    stack2
$f8    stack3
$f9    stack4
$fa    [reserved for the stack]
$fb    [reserved for the stack]
$fc    [reserved for the stack]
$fd    [reserved for the stack]
$fe    [reserved for the stack]
$ff    [reserved for the stack]
$80    missile0x
$81    missile1x
$82    ballx
$83    objecty
       missile0y
$84    missile1y
$85    bally
$86    SpriteIndex
$87    player0x
$88    NewSpriteX
       player1x
$89    player2x
$8a    player3x
$8b    player4x
$8c    player5x
$8d    player0y
$8e    NewSpriteY
       player1y
$8f    player2y
$90    player3y
$91    player4y
$92    player5y
$93    NewNUSIZ
       _NUSIZ1
$94    NUSIZ2
$95    NUSIZ3
$96    NUSIZ4
$97    NUSIZ5
$98    NewCOLUP1
       _COLUP1
$99    COLUP2
$9a    COLUP3
$9b    COLUP4
$9c    COLUP5
$9d    SpriteGfxIndex
$9e    
$9f    
$a0    
$a1    
$a2    player0pointer
       player0pointerlo
$a3    player0pointerhi
$a4    P0Bottom
$a5    P1Bottom
$a6    player1pointerlo
$a7    player2pointerlo
$a8    player3pointerlo
$a9    player4pointerlo
$aa    player5pointerlo
$ab    player1pointerhi
$ac    player2pointerhi
$ad    player3pointerhi
$ae    player4pointerhi
$af    player5pointerhi
$b0    player0height
$b1    spriteheight
       player1height
$b2    player2height
$b3    player3height
$b4    player4height
$b5    player5height
$b6    PF1temp1
$b7    PF1temp2
$b8    PF2temp1
$b9    PF2temp2
$ba    pfpixelheight
$bb    playfield
       PF1pointer
$bc    
$bd    PF2pointer
$be    
$bf    aux3
       statusbarlength
$c0    pfscorecolor
       lifecolor
       aux4
$c1    aux5
       pfscore1
       lifepointer
$c2    lives
       aux6
       pfscore2
$c3    playfieldpos
$c4    scorepointers
$c5    
$c6    
$c7    
$c8    
$c9    
$ca    temp1
$cb    temp2
       P1Display
$cc    temp3
$cd    temp4
       RepoLine
$ce    temp5
       P0Top
$cf    temp6
$d0    temp7
$d1    score
$d2    
$d3    
$d4    pfheight
$d5    scorecolor
$d6    rand
$d7    A
       a
$d8    B
       b
$d9    C
       c
$da    D
       d
$db    E
       e
$dc    F
       f
$dd    G
       g
$de    H
       h
$df    I
       i
$e0    J
       j
$e1    K
       k
$e2    L
       l
$e3    M
       m
$e4    N
       n
$e5    O
       o
$e6    P
       p
$e7    Q
       q
$e8    R
       r
$e9    S
       s
$ea    T
       t
$eb    U
       u
$ec    V
       v
$ed    W
       w
$ee    X
       x
$ef    Y
       y
$f0    Z
       z
$f1    spritesort
$f2    spritesort2
$f3    spritesort3
$f4    spritesort4
$f5    spritesort5
$f6    stack1
$f7    stack2
$f8    stack3
$f9    stack4
$fa    [reserved for the stack]
$fb    [reserved for the stack]
$fc    [reserved for the stack]
$fd    [reserved for the stack]
$fe    [reserved for the stack]
$ff    [reserved for the stack]

 

 

 

 

 

Glossary of Terms

This non-alphabetical glossary is a work in progress. More will be added to it over time.

 

 

     

    Constants, Variables, and Values

    Text by SeaGtGruff (adapted by Random Terrain)

    Constants and variables are like boxes that can hold things, and those things that are stored in the boxes are called values. In the Atari 2600, each box is normally the size of one byte, but you can also divide a box into smaller sections (bits or groups of bits), and you can combine two or more boxes to create a larger box.

     

    These boxes have addresses within the Atari's memory, but we give them names so they're easier for us to use. For example, "address $83" tells us the address of that particular box, but doesn't tell us what "lives" at that address. If we're going to be using that box to store the number of lives a player has remaining, then it's easier to write our program (and to read and understand it later on) if we call it something like "_Lives_Remaining" rather than "$83."

     

     

     

       

      Constants

      Text by SeaGtGruff (adapted by Random Terrain)

      A constant is a box that contains a value which will never change and that can't be changed once the box has been filled. For example, if we've taken a box, named it "_Three," and then put the number 3 in it, we can use the constant "_Three" in places where we want to use the number 3. That may sound silly, and that particular example iswhy not just use 3?but constants can be very useful in programming.

       

      Suppose you want the spaceship in your game to be yellow. You can define a constant named "_Ship_Color" and put the value for the color yellow in it. Then anywhere you want to refer to the ship's color you say "_Ship_Color." Later on you may decide you want the ship to be light blue instead of yellow. All you have to do is change the definition of "_Ship_Color" to be the value for light blue and it will change everywhere "_Ship_Color" was used in your programyou don't need to go searching for all the places it's used and change them individually (potentially missing some places).

       

       

       

       

      Variables

      Text by SeaGtGruff (adapted by Random Terrain)

      A variable is a box that contains a value which needs to be able to change while the program is running. For example, if we've named a box "_Lives_Remaining," then we can store the number of spare lives the player starts with in that variable. As the game is being played, we can reduce the value of "_Lives_Remaining" by 1 whenever the player loses a life, or increase it by 1 whenever the player earns an additional spare life.

       

       

       

       

      Values

      Text by SeaGtGruff

      Values come in two basic flavorsnumbers (numeric values) and characters (alphanumeric values, meaning letters, numbers, or other symbols). However, batari Basic doesn't have alphanumeric values, so all we need to understand in batari Basic are numeric values. A constant or variable is limited in the values it can contain. A single byte can hold any whole number from 0 to 255. A single bit can hold a 0 or a 1. So depending on the size of the box (how many bits wide it is), it can hold a value ranging from 0 to 1 (1 bit wide), or from 0 to 7 (3 bits wide), or from 0 to 255 (8 bits or 1 byte wide), or from 0 to 65535 (2 bytes wide), and so forth.

     

     

     

     

     

    Memory

    Text by SeaGtGruff

    A memory location is like a box with an address. Memory is used to store constants, variables, and the programming instructions that tell the Atari how the game works. Memory comes in two basic flavorsROM and RAM.

     

     

     

       

      ROM

      Text by SeaGtGruff (adapted by Random Terrain)

      ROM stands for "Read Only Memory." It is memory that can't be changed once it's been written to.

       

       

       

       

      RAM

      Text by SeaGtGruff (adapted by Random Terrain)

      RAM stands for "Random Access Memory" It is memory that can be changed. RAM is used to store variables and other information that needs to be able to change, such as a screen display. The Atari 2600 doesn't actually have "screen memory" and screen displays are often defined in ROM, but in its standard kernel, batari Basic sets aside some RAM to use as "screen memory" for the playfield.

       

      The Atari 2600 only has 128 bytes of RAM, but some game cartridges have extra (or expansion) RAM on them so more variables or more detailed screen displays can be used.

     

     

     

     

     

    Registers

    Text by SeaGtGruff

    Registers are special memory locations that are used for specific purposes by the Atari 2600. They are found on the three chips that make up the "brain" of the Atari:

    1. 6507 or CPU (which performs the game instructions).
    2. 6532 or RIOT chip (which supplies the RAM as well as the timer and interfaces with the input/output devices like the console switches and game controllers).
    3. TIA or "Television Interface Adapter" chip (which handles the video and audio).

     

    A few of these registerssuch as the accumulator and the X and Y registers inside the 6507 chipdon't have memory addresses, but we can store information in them by using specific instructions. But most of the other registersthose found inside the RIOT and the TIAare mapped to memory addresses so we can read from them or write to them. Most registers are like RAM addresses because their contents can change, but some registersparticularly many of the ones in the TIAare less than 8 bits, so they can hold only specific ranges of values. And a few of the TIA's registers don't even have any bits at allthey're called "strobe" registers that make something happen whenever we write to them. The TIA's registers come in two different flavorsread-only and write-only.

     

     

     

       

      Read-Only Registers

      Text by SeaGtGruff (adapted by Random Terrain)

      A read-only register can be read, but you can't write to it. The TIA's read-only registers are used to read collisions between the graphical objectsfor example, reading the CXM0P register lets you see if missile 0 has overlapped (collided) with the player 0 sprite or the player 1 sprite. There are also read-only registers for reading the paddles or the joystick buttons.

       

       

       

       

      Write-Only Registers

      Text by SeaGtGruff (adapted by Random Terrain)

      A write-only register can be written to, but you can't read it. The TIA's write-only registers are used to draw the game on the screen, play sounds, or perform some specific action like clearing the collision registers or moving the sprites to the left or right. You can write a new value to a write-only registerfor example, writing a color value to the COLUPF register will tell the Atari what color to draw the playfield withbut you can't read a write-only register to see what value it's currently set to.

       

       

 

 

 

 

 

Subroutines

Text by SeaGtGruff (adapted by Random Terrain)

When you perform a gosub, the return address must be pushed onto the stack so the program can eventually return to where you called the subroutine from. The 2600 has a small stack to begin withonly 128 bytesand it coincides with the zero-page RAM that's used for variables, so you need to be careful about how much you use gosub (or JSR if you're using assembly). And batari Basic has even less usable stack space because it uses most of the zero-page RAM for what I call batari Basic's "system variables" (things like the player pointers, sprite x and y coordinates, etc.), the playfield RAM (if you aren't using the Superchip option in a bank-switching game), and the user variables (a through z). So that means you have to be extra careful about how much you use gosub in batari Basic. Note that it isn't the number of subroutines that's the issue, but rather the number of nested subroutines. So it's perfectly safe to call a subroutine, but you want to try to avoid calling a subroutine that calls another subroutine that calls another subroutine that calls another subroutine, etc.

 

If you're used to programming in a high-level language on a computer, you might not think twice about using a bunch of nested subroutines, but when programming for the Atari 2600and particularly when using batari Basicyou need to be mindful of how many levels of nested subroutines your code contains, otherwise the stack could end up overwriting some of your variables and causing program glitches. I think a batari Basic program can have up to 5 levels of nested subroutines before the stack begins to overwrite some of the variables, and you can nest more than 5 levels of subroutines if you aren't using any of the variables that would be overwritten-- but keep in mind that batari Basic also uses subroutines (for example, drawscreen is a subroutine). So it's probably best not to go beyond 3 levels of nested subroutines. And make sure that whenever you call a subroutine it will eventually do a return, otherwise the return address will get left on the stack. You can use pop to remove a return address from the stack, but that should only be done if you're sure you know what you're doing.

 

Calling a subroutine and then doing a return takes up more machine cycles than doing a straight goto. That generally isn't a problem unless you're working on a section of code that has really tight timing constraintsfor example, if you're writing your own display kernel in assembly then you might not want to do a JSR in the loop that draws the active lines (unless you've allowed for the extra time needed for the JSR and RTS). On the other hand, if your batari Basic program contains a large number of gosubs and returns, it might start to eat into your free cycles. And this is even more the case if you're using bankswitching and are doing gosubs and returns between different banks, because gosubs and returns between banks take up even more cycles than regular gosubs and returns. So it's a good idea to design your batari Basic programs to use goto instead of gosub wherever possible.

 

Of course, sometimes you do need to use gosub instead of gotoafter all, subroutines are an essential part of efficient programming. You don't want to repeat the same code in multiple places in your program if it would be more ROM-efficient to put that section of code in a subroutine and then gosub to it from different places in your program. But you should avoid using gosub in cases where it really isn't necessary, such as if the code in the subroutine is only called from one spot, or if the subroutine is so short that it would actually be more efficient to just duplicate that code wherever it's needed.

Back to Top

 

Spread the Word

If you like this page, please link to it and share it:

 

 

 

Disclaimer

View this page and any external web sites at your own risk. I am not responsible for any possible spiritual, emotional, physical, financial or any other damage to you, your friends, family, ancestors, or descendants in the past, present, or future, living or dead, in this dimension or any other.

 

Use any example programs at your own risk. I am not responsible if they blow up your computer or melt your Harmony cartridge.