text:
Getting Started (Latest Version of bB)
Keywords, Constants, & Registers
DPC+ section of Table of Contents
if…then Chart (AND - OR - NOT)
Page last updated on: 2015y_11m_14d_0304t
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
This page has a table of contents, an index and frequently used page links to help you find what you are looking for as quickly as possible. Below the frequently used page links are various helpful sections. Be sure to at least glance over them.
[Note from RT: Since I started working on this page, I've been trying to make the index less like a boring index from a book and more like a reverse dictionary mixed with an index. Anything you can think of should be in the list and if it isn't there, you need to tell me so I can add it to the index. As the index grows, it will become quicker and easier for you and others to find things.]
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.
Inside Game Loop
If you see a red rectangle with the words "Inside Game Loop" in it, that means the variable or register it is near needs to be inside of your main loop because it will be reset after drawscreen is used. See Ephemeral Variables & Registers for more information.
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. Random Terrain only maintains this page: adapts existing text, adds a bit of new text when needed, and corrects errors when they are pointed out. Random Terrain also adds example programs, interactive charts and other goofy stuff.
Click here and download the latest version of batari Basic. It includes DASM, 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. Be sure to check out the install_win.bat subsection below. It's important.
Remember, if you are using Visual batari Basic (VbB), it isn't a separate language and batari Basic isn't built into it. VbB is an IDEIntegrated Development Environment
(Just about everything a bB programmer needs in one place.); a tool that makes batari Basic easier to use.
Use Save As and Back Up Your Files
Every time I make a significant change to a program I'm working on, I use Save As to save it as a new file so I can go back to a previous working version if I mess something up. If you use Save As before making any major changes, then something stops working correctly, you can use a program like WinMerge to compare the latest two versions of your program and the differences will be highlighted.
To make things much easier, I end my program names with the year, month, day and military time. Here's an example:
Doing that means each file will automatically have a different name and the files will be listed in the order they were created. You can read more about it at AtariAge.
Back up your files. Your hard drive will die one day or get attacked by a virus, so be sure to make backups of your files using an external hard drive or thumb drive or something else that humans have invented for storing data. Various people have posted in the batari Basic forum over the years that they lost everything they were working because they didn't back up their files.
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.
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. If you don't do this, you'll still be using the previous version of bB. Selecting the latest version of 2600basic.exe on the VbB settings page doesn't automatically mean that you'll be using that latest version. You must also double click on "install_win.bat" because that's what makes it go.
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. And please do not use the original Tinkernut World batari Basic program. Please use Tinkernut World Deluxe instead. 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.
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
Official batari Basic Forum at AtariAge
How to Attach Files and Images
Keeping Track of the Scanline Count with Stella
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 Cartridge
Harmony Cartridge plugged into your console and the USB cable plugged into the cart, then download your work in progress for testing any time you want. It's as hassle-free as you can get. The Harmony cartridge also lets you use an SD card. Drop your own games on it or hundreds of your favorite classic Atari 2600 game files. Bankswitch type is auto-detected.
The deluxe edition includes a USB to mini-B programming cable and a formatted SD card. If you don't want to go hunting for the correct SD card and cable, the deluxe edition seems like the best choice.
Emulators can be great for testing, but among other things, your game colors will be darker and sprites can look surprisingly different on a real Atari. If you're even slightly serious about making Atari 2600 games, you'll want to buy a Harmony Cartridge as soon as possible.
Once your game is 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.
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:
The Basics: How Programming Works
Atari Basic - A Self-Teaching Guide
bB Programming Tutorial by CurtisP
Code Snippets & Samples for bB Beginners
Quotes from Famous Game Designers and Others
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:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Most of the information below is either directly from or adapted from Wikipedia. Some info is from SeaGtGruff and GroovyBee at AtariAge.
Check this out:
What is a programming language?
A programming language is an artificial language designed to communicate instructions to a computer.
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."
A program is a sequence of instructions that tells the Atari 2600 what to do. Without a program, there is no game.
CPU stands for "Central Processing Unit." It's the brains of the Atari 2600. The CPU carries out the instructions of a program.
In batari Basic, a value is a number within a specific range.
See Glossary: Constants, Variables, and Values for more information.
Binary is a base-2 number system. It's either off or on (zero or one).
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).
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.
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.
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.
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.
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.
Q.How can I make 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?
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.How do I make the playfield blocks square or just make them smaller?
A. Squint your eyes? Just kidding. They are called playfield pixels and there's more than one way to do that. Check out the following links:
Q.Why can't we use the whole screen?
A.This post at AtariAge by SeaGtGruff explains why.
Q.Why do people make Atari 2600 games these days?
A.There seems to be three main reasons why people make Atari 2600 games:
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.
Q.Isn't using more than 4k considered cheating?
A.Atari and other companies used more than 4k in the early 1980s. If it wasn't cheating back then, why would it be cheating now? Take a look at my page about cheating for more information.
Keywords, Constants, and Registers
Below is a quick list of keywords, special constants, and registers.
Text by kisrael (adapted by Random Terrain)
const — set constant
dim/def — extra name for variable (or var. expression)
if…then…else — conditional logic
end — (unindented) block end notation
for…step…next — loop construction
goto — jump code execution
gosub…return — jump code execution, jump back
pop — cancel gosub return ability
on…goto/on…gosub — comparison based jump code execution
let — deprecated variable setting
rand — random number generation
rem — comment
read/data…end — block of raw data
sdata/sread…end — sequentially readable blocks of data
drawscreen — let kernel run
score — score to display, must be BCD!
const noscore = 1 — hide score
scorecolor — color for score
player0:/player1: — set player graphics
player0x/player0y/player0color: — player 0 controls
player1x/player1y/player1color: — player 0 display settings
missile0x/missile0y/missile0height — missile 0 display settings
missile1x/missile1y/missile1height — missile 0 display settings
ballx/bally/ballheight — ball display settings
playfield: — set playfield graphics
pfclear/pfhline/pfvline — bulk playfield operations
pfpixel/pfread — set/check playfield rectangles
pfscroll/playfieldpos — playfield scrolling/jumping
reboot — warm boot game (avoid if using rand)
pfscore1/pfscore2 — binary content of scorebars
const pfscore = 1 — enable scorebars
pfscorecolor — scorebar color
switchreset/switchbw/switchselect/switchleftb/switchrightb — switch reading
joy0up/joy0down/joy0left/joy0right/joy0fire — joystick 0 inputs
joy1up/joy1down/joy1left/joy1right/joy1fire — joystick 1 inputs
AUDV0/AUDC0/AUDF0 — Audio 0
AUDV1/AUDC1/AUDF1 — Audio 1
COLUBK — Background Color
COLUP0/COLUP1 — Player/Missile Colors
REFP0/REFP1 — mirror image for player
COLUPF — Playfield/Ball Colors
CTRLPF — ball and playfield/player effects
NUSIZ0/NUSIZ1 — player missile effects
PF0 — modify sides of PF directly
asm — insert inline assembly
bank/romsize — managing memory banks
return thisbank/return otherbank — cross banking subroutines
macro/callmacro — use of shortcuts
dec — decimal math (BCD) expression
function — callable code bock with return value
include/inline — insert asm module
includesfile — include in batch
set — compiler directives
const pfres = # — playfield number of rows
const pfrowheight = # — height of pf rolls
player#height — override player height (meltaway)
vblank — indicate block in tv picture timing
readpaddle/currentpaddle — get paddle info
set dpcspritemax # — fewer sprites (reclaim memory)
player0color:/player1color: — set up player colors
stack/pull/push — variable stack manipulation
scorecolors: — block of color for score
Life Counter/Status Bar Loop/Kernel
inline 6lives_statusbar.asm — context sensitive enabling
inline 6lives.asm — context sensitive enabling
lives — life indicator graphic and count
lives: — icon definition
lifecolor — color
statusbarlength — length of statusbar
statusbarcolor — color of status bar
a…z — regular variables
a…z, var0…var8, and killed off sprites — DPC+ variables
temp# — additional local variables
var# — additional variables freeable with playfield
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.
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
RevEng shares some of the techniques he has used to save ROM space.
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.
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.
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:
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.
rem **************************************************************** rem * rem * Name: Space Nuggets rem * Version: 2015y_07m_24d_1642t rem * rem **************************************************************** rem * rem * Programmer: Scadoobie Floinkenburger rem * Language: batari Basic v1.0 rem * System: Atari 2600 VCS rem * rem ****************************************************************
Notice how each rem has at least one space after it. If you don't have at least one space after rem, your program may not compile.
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.
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.
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.
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).
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
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 . . .
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.
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:
;*************************************************************** ; ; Variable aliases go here (DIMs). ; ;``````````````````````````````````````````````````````````````` ;``````````````````````````````````````````````````````````````` ; Splits up the score into 3 parts. ; dim sc1 = score dim sc2 = score+1 dim sc3 = score+2 __Main_Loop ;*************************************************************** ; ; Your code to test goes here. ; ;*************************************************************** ;*************************************************************** ; ; Sets color of the score. ; scorecolor = $9C ;*************************************************************** ; ; Puts temp4 in the three score digits on the left side. ; ;``````````````````````````````````````````````````````````````` ; Replace "player0x" with whatever you need to check. ; 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 ;*************************************************************** ; ; Puts temp4 in the three score digits on the right side. ; ;``````````````````````````````````````````````````````````````` ; Replace "player0y" with whatever you need to check. ; 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 ;*************************************************************** ; ; Displays the screen. ; drawscreen goto __Main_Loop
Below is an example program that uses the code above to display object coordinates using the score.
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.
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.
let x = x + 1
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.
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). [Note from Random Terrain: Just remember that if you want anyone to help you with your code, you need to put all of your dims near the beginning of your program and make sure every variable that you use has an alias. The people trying to help you shouldn't have to go on a variable hunt. When every variable has an alias and all aliases are near the beginning of your program in alphabetical order, you and anyone else looking at your code will be able to easily see the variables you are using and which variables are free to use. If you are too lazy to use dim, don't expect anyone to go out of their way to help you. You have to be willing to put in some work.]
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 Random Terrain: 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.]
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.
Aliases can also be used to keep track of bits that you might want to use for various jobs. The bit number can be included with each alias so it will be easier to remember and work with. See this example for more information. Remember that when you use a variable to control up to 8 bits, you can't use that variable for other purposes or you will contaminate the bits and your program won't work properly.
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.
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.]
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 goto Shot2
Do this instead:
if a{0} then goto Shot2
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 about using an entire variable for every on/off condition. For example, to determine whether a game is in progress or it is over, often an entire variable 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 (depending on the kernel you are using), it is very likely that you will need to use individual bits for game state information. For example, a common use of a bit 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.
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.
Remember that when you use the individual bits of a variable to do simple on/off jobs, you can no longer use the whole variable for other jobs. For example, if you use one of the bits of the variable a, you can't use something like a = 48 or a = a + 1 in your program somewhere else. If you do, the bit you are trying to use in the variable a will end up getting overwritten.
You can use dim to create a normal descriptive variable name and access the individual bits later.
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.
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.
Related Link
Why Do You Read Binary Digits Right to Left?
"Numbers do not increase the same way we read (left to right). This is the same way we add, multiply and subtract in math."
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).
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:
Improved Nybble Variables
(by SeaGtGruff with help from RevEng and bogax)
Below is the new and improved version using macro and def:
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.
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:
| = OR (Note: the '|' key is usually above the backslash '\')
^ = XOR (exclusive OR)
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.
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 =
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 =
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 =
Fixed point variables can be assigned to fractional values, similar to floating point variables in other languages. bB provides two fixed point types:
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 are a few examples of how you could move player0 to the left:
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.
8.8 Fixed Point Sprite Movement
Use the joystick to move the sprite. Press the fire button while moving the joystick to go faster.
32 x 23 Maze with Animated Sprite and Roaming Sprite Using 8.8 Fixed Point Movement
Use the joystick to move the Pac-Man style sprite. An enemy sprite randomly moves around the maze. I didn't give the enemy any brains in this example program. It's stupid and can't hurt you.
32 x 12 Maze with Animated Sprite and Roaming Sprite Using 8.8 Fixed Point Movement
Use the joystick to move the Pac-Man style sprite. An enemy sprite randomly moves around the maze. I didn't give the enemy any brains in this example program. It's stupid and can't hurt you.
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:
COLUBK (unless background kernel option set)
COLUPF (unless pfscorecolor variables used and/or pfcolors kernel option set)
The following are obliterated, but values are predictable and don't necessarily need resetting. The TIA register and its value after a drawscreen are noted below:
TIA Reg
Value After Drawscreen
|Inside Game Loop
|same as scorecolor
|same as scorecolor
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.
Text by SeaGtGruff (adapted by Random Terrain)
There are 7 temporary variables, temp1 through temp7—but 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 variables—temp1 through temp6—are 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 requested—so 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 routines—and the source code for bB's routines are always subject to future change—but 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 order—in other words, temp6 is safer than temp5, and temp5 is safer than temp4, etc. The exception is temp7, which—as I mentioned—is 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.
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
The goto statement is used to jump to a line number or label anywhere in your program.
goto 100 goto __My_Subroutine
In bankswitched games, if you are jumping to a routine in another bank, you must specify the bank.
goto __Move_Monster bank2
goto = 3 cycles
goto with bankswitch = 49 cycles
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.
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.
if x < 4 then on x goto 100 200 300 400
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
The gosub statement is often used for a subroutine that is called by multiple locations throughout your program.
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.
gosub __Move_Monster bank2
See Glossary: Subroutines for more information.
gosub + return = 12 cycles
gosub with bankswitch + return = 122 cycles
gosub with bankswitch + return otherbank = 110 cycles
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.
if x < 4 then on x gosub 100 200 300 400
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
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:
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.
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.
Cancels the return address from a subroutine. This essentially makes the last gosub command work like it was a goto.
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
More than one can be used.
Cannot be used with OR.
Safe to use with:
< > =
Only one can be used.
Cannot be used with AND.
Cannot be used with NOT.
Safe to use with:
< > =
More than one can be used.
Cannot be used with OR.
Cannot be used with < > =.
Safe to use with:
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 the label __Fried_Fish if a is anything except zero:
if a then goto __Fried_Fish
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.
if joy0up then x = x + 1
That will add 1 to x if the left joystick is pushed up.
if switchreset then goto __Mucus_Membrane
Jumps to __Mucus_Membrane if the reset switch on the console is set.
if collision(player1,playfield) then t = 1
Sets t to 1 if player1 collides with the playfield.
if !a{3} then a{4} = 1
Sets bit 4 of a if bit 3 of a is zero. See Bit Operations for more information about using single bits.
A second type of statement includes a simple comparison. Valid comparisons are = , < , >, <=, >=, and <>.
if a < 2 then goto __Doggy_Dip if f = g then f = f + 1 if r <> e then r = e
The third type of if…then is a complex or compound statement, that is, one containing a boolean AND (&&) 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 goto __Pig_Knuckle 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.
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 cannot 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
BooleanOne of two values (true or false). operators are used as conditions in if…then statements. They are tokenized as:
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.
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.
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.
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 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.
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.
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.
for x = 1 to 10 : a[x] = x : next
It is also important to note that the next doesn't care about the program flow—it will instead find the nearest preceding for based on distance.
For example:
for x = 1 to 20 goto 100 for g = 2 to 49 100 next
The next above WILL NOT jump back to the first for, instead it will jump to the nearest one, even if this statement has never been executed. Therefore, you should be very careful when using next.
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
Slower Way
a = (rand&1)
0 to 1
a = (rand/128)
a = (rand&1) + 1
1 to 2
a = (rand/128) + 1
a = (rand&3)
0 to 3
a = (rand/64)
a = (rand&3) + 1
1 to 4
a = (rand/64) + 1
a = (rand&7)
0 to 7
a = (rand/32)
a = (rand&7) + 1
1 to 8
a = (rand/32) + 1
a = (rand&15)
0 to 15
a = (rand/16)
a = (rand&15) + 1
1 to 16
a = (rand/16) + 1
a = (rand&31)
0 to 31
a = (rand/8)
a = (rand&31) + 1
1 to 32
a = (rand/8) + 1
a = (rand&63)
0 to 63
a = (rand/4)
a = (rand&63) + 1
1 to 64
a = (rand/4) + 1
a = (rand&127)
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
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.
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.
The example below has two data tables. Press the joystick left to read from one data table and press the joystick right to read from the other. Repeatedly press and release the joystick in either direction to move through the data.
;*************************************************************** ; ; Variable aliases go here (DIMs). ; ;``````````````````````````````````````````````````````````````` ; Data counter. ; dim _D_Counter = m ;``````````````````````````````````````````````````````````````` ; Remembers data. ; dim _D_Mem = n ;``````````````````````````````````````````````````````````````` ; Converts 6 digit score to 3 sets of two digits. ; ; The 100 thousands and 10 thousands digits are held by sc1. ; The thousands and hundreds digits are held by sc2. ; The tens and ones digits are held by sc3. ; dim sc1 = score dim sc2 = score+1 dim sc3 = score+2 ;``````````````````````````````````````````````````````````````` ; Joy0 restrainer bit. ; dim _Bit6_Joy0_Restrainer = y ;*************************************************************** ; ; Sets score color. ; scorecolor = $9C ;*************************************************************** ;*************************************************************** ; ; MAIN LOOP (MAKES THE PROGRAM GO) ; ; __Main_Loop ;*************************************************************** ; ; Joystick section. ; ;``````````````````````````````````````````````````````````````` ; If joystick not moved, clear the joystick restrainer bit ; and skip this subsection. ; if !joy0left && !joy0right then _Bit6_Joy0_Restrainer{6} = 0 : goto __Skip_Joy0 ;``````````````````````````````````````````````````````````````` ; If joystick already moved, skip this subsection. ; if _Bit6_Joy0_Restrainer{6} then goto __Skip_Joy0 ;``````````````````````````````````````````````````````````````` ; Turns on the joystick restrainer bit. ; _Bit6_Joy0_Restrainer{6} = 1 ;``````````````````````````````````````````````````````````````` ; If joystick moved left, get data. ; if joy0left then _D_Mem = _Data_Baked_Potato[_D_Counter] ;``````````````````````````````````````````````````````````````` ; If joystick moved right, get other data. ; if joy0right then _D_Mem = _Data_Toenail_Fungus[_D_Counter] ;``````````````````````````````````````````````````````````````` ; Increments and limits data counter. ; _D_Counter = _D_Counter + 1 : if _D_Counter > 6 then _D_Counter = 0 __Skip_Joy0 ;*************************************************************** ; ; Puts _D_Mem in the three score digits on the right side. ; temp4 = _D_Mem 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 ;*************************************************************** ; ; Displays the screen. ; drawscreen goto __Main_Loop ;*************************************************************** ; ; Data table 01. ; data _Data_Baked_Potato 11, 22, 33, 44, 55, 66, 77 end ;*************************************************************** ; ; Data table 02. ; data _Data_Toenail_Fungus 1, 2, 3, 4, 5, 6, 7 end
To help prevent the reading of values beyond data tables, a constant is defined with every data statement—this constant contains the length, or the number of elements, in the data. The constant will have the same name as the name of the data statement, but it will have _length appended to it.
For example, if you declare:
data _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.
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).
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
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.
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.
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.
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.
Pulsating Color Cycling Example
Cycles through the hues by starting at a lower brightness, moving through to a higher brightness, going back down to the lower brightness, switching to the next hue and repeating that pulsating or throbbing type of pattern. It runs at a speed that is easy on they eyes.
COLUBK (Color-Luminosity Background)
Sets the background color. Example: COLUBK = $80.
For a multicolored background using the standard kernel, see kernel_options: background or the kernel_options chart. You can also have a multicolored background using the DPC+ kernel.
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 using the standard kernel, see pfcolors or the kernel_options chart. You can also have a multicolored playfield using the DPC+ kernel.
Write-Only: COLUPF cannot be read. For example, a = COLUPF will not work properly.
COLUP0 (Color-Luminosity Player0, Missile0) Inside Game Loop
Sets the color for player0 sprite and missile0. Example: COLUP0 = $0A. See Ephemeral Variables & Registers for more information.
For a multicolored sprite using the standard kernel, see player1colors and playercolors or the kernel_options chart. You can also have multicolored sprites using the DPC+ kernel.
Write-Only: COLUP0 cannot be read. For example, a = COLUP0 will not work properly.
COLUP1 (Color-Luminosity Player1, Missile1) Inside Game Loop
Sets the color for player1 sprite and missile1. Example: COLUP1 = $FE. See Ephemeral Variables & Registers for more information.
For a multicolored sprite using the standard kernel, see player1colors and playercolors or the kernel_options chart. You can also have multicolored sprites using the DPC+ kernel.
Write-Only: COLUP1 cannot be read. For example, a = COLUP1 will not work properly.
Sets the score color so it will be visible. Example: scorecolor = $68. See score for more information.
Sets the color of the pfscore bars if they are enabled. Example: pfscorecolor = $84. See pfscore bars for more information.
The color charts below are interactive. Click on a color and the hexadecimal color value will be displayed. Visit the TIA Color Charts and Tools page for an NTSC/PAL color conversion tool and Atari 2600 color compatibility tools.
NTSC (128 unique colors)
Color Value: ██
PAL (104 unique colors)
Color Value: ██
SECAM (8 unique colors)
Color Value: ██
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
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!"
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.
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.
Sprite Q & A
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:
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.
Use the joystick to move the sprite.
Smooth Chase With Bresenham-Like Code
Use the joystick to move the sprite as another sprite chases you. Press the fire button to make the other sprite chase you faster.
The smooth Bresenham-like movement code was provided by bogax at AtariAge.
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).
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
Two missiles can be displayed on the screen at coordinates that you specify. The missiles are just solid rectangles. Only the width, height, and color can be changed. Sprite data cannot be applied to the missiles. The missiles share the same colors with their respective players.
To access missiles, you can set missile0x, missile0y, and missile0height for missile0, and missile1x, missile1y, and missile1height for missile1.
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.
Use the joystick to move the sprite. Press the fire button to shoot the missile.
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.
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.
Use the joystick to move the sprite. Watch the ball bounce. Press the reset switch to reset the program.
Did You Know?
The width of a playfield pixel is the same as a sprite that is 4 pixels wide. The height of the standard playfield pixel before changing any row heights is about the same as a sprite that is 8 pixels tall.
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"
"var0=$01 : var1=$01 : var2=$01 : var3=$01"
"var0=%00000001 : var1=%00000001 : var2=%00000001 : var3=%00000001"
would give you this:
But you wouldn't get that. You'd really get this:
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
|Row 1
|Row 2
|Row 3
|Row 4
|Row 5
|Row 6
|Row 7
|Row 8
|Row 9
|Row 10
|Row 11
The multisprite kernel gives you a 16-wide playfield, which is mirrored about the center of the screen. Plotting commands such as pfpixel, pfvline and pfhline do not work in this kernel. However, pfread does work if you include a special module. Horizontal
Left and Right scrolling is not possible, but support for vertical
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>
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
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:
Row Height
Variables Freed
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.
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.
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.
__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".)
Clears the playfield in the standard kernel or can optionally be used with an argument to fill playfield RAM with the specified value.
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.
This draws a single pixel with playfield blocks. Uses 80 processor cycles every frame. The syntax is:
pfpixel xpos ypos function ( | )
xpos can be 0-31 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.
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)
This draws a horizontal
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 ( | )
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.
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)
This draws a vertical
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 ( | )
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.
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.
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
Simple example:
pfhline 8 5 23 on COLUBK = $C4 COLUPF = $CE __Main_Loop pfscroll down drawscreen goto __Main_Loop
More advanced 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.
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 lines—for example, "playfieldpos = 4" will scroll 4 lines, or half of a playfield row.
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.
Related Link
pfread for the Multisprite Kernel (by bogax)
A replacement for pfread_msk.asm.
The score keyword is used to change the score. The score is fixed at 6 digits, and it currently resides permanently at the bottom of the screen. Unlike other variables, batari Basic accepts values from 0-999999 when dealing with the score.
Before the score will appear, you must set its color:
scorecolor = value
value should be a number between 0 and 255 in accordance with the color chart.
In the standard kernel, "const scorefade=1" will enable the score fade effect. This adds shading to the score.
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.
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.
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.
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.
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:
;*************************************************************** ; Converts 6 digit score to 3 sets of two digits. ; sc1 holds the 100 thousands and 10 thousands ; digits of the score. sc2 holds the thousands and ; hundreds digits of the score. sc3 holds the tens ; and ones digits of the score. ; dim sc1 = score dim sc2 = score+1 dim sc3 = score+2 ;*************************************************************** ; How to check the digits: ; ; 100 thousands digit . sc1 & $F0 (X0 00 00) ; 10 thousands digit .. sc1 & $0F (0X 00 00) ; ; Thousands digit ..... sc2 & $F0 (00 X0 00) ; Hundreds digit ...... sc2 & $0F (00 0X 00) ; ; Tens digit .......... sc3 & $F0 (00 00 X0) ; Ones digit .......... sc3 & $0F (00 00 0X) scorecolor = $FA __Main_Loop ;*************************************************************** ; ; Fire button check. ; ; if !joy0fire then goto __Skip_FireB ;``````````````````````````````````````````````````````````````` ; Remembers the thousands digit. ; temp5 = sc2 & $F0 ;``````````````````````````````````````````````````````````````` ; Add points to score. ; score = score + 10 ;``````````````````````````````````````````````````````````````` ; Grabs the thousands digit. ; temp6 = sc2 & $F0 ;``````````````````````````````````````````````````````````````` ; If thousands digit has NOT changed, skip this part. ; 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:
;*************************************************************** ; ; Checks for a new high score. ; if sc1 > _High_Score01 then goto __New_High_Score if sc1 < _High_Score01 then goto __Skip_High_Score ;``````````````````````````````````````````````````````````````` ; First byte equal. Do the next test. ; if sc2 > _High_Score02 then goto __New_High_Score if sc2 < _High_Score02 then goto __Skip_High_Score ;``````````````````````````````````````````````````````````````` ; Second byte equal. Do the next test. ; if sc3 > _High_Score03 then goto __New_High_Score if sc3 < _High_Score03 then goto __Skip_High_Score ;``````````````````````````````````````````````````````````````` ; All bytes equal. Current score is the same as the high score. ; goto __Skip_High_Score __New_High_Score ;``````````````````````````````````````````````````````````````` ; Saves new high score. ; _High_Score01 = sc1 : _High_Score02 = sc2 : _High_Score03 = sc3 __Skip_High_Score
Below is an example program that shows how to save the high score until the game is turned off. Move the joystick up to increase the score. Press the fire button to leave the main loop and the score will flip between the current score and the high score every 2 seconds. Press the fire button or reset switch to go back to the main loop.
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.
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.
const font = 21stcentury
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.
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:
Color of the bars.
Binary value displayed left of score.
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.
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.
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.
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.
If you want a working game-like example, check out Tinkernut World Deluxe.
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.
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.
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.
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.
NUSIZ0, NUSIZ1 Inside Game Loop
Changes the size and/or other properties of player0/1 and missile0/1. In the chart below, x = don't care.
|missile = 1 pixel wide
|missile = 2 pixels wide
|missile = 4 pixels wide
|missile = 8 pixels wide
|1 copy of player and missile
|2 close-spaced copies of player and missile
|2 medium-spaced copies of player and missile
|3 close-spaced copies of player and missile
|2 wide-spaced copies of player and missile
|double-sized player
|3 medium-spaced copies of player and missile
|quad-sized player
Note that missile and player properties may be combined in a single write.
Example: NUSIZ0=$33 will make missile0 8 pixels wide, plus make three close copies of player0 and missile0.
Write-Only: NUSIZ0 and NUSIZ1 cannot be read. For example, a = NUSIZ0 will not work properly.
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.
|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.
Sprite/Playfield Priority Example
Use the joystick to move the sprite. Press the fire button to flip the priority so the sprite will either be in front of or behind the playfield.
Sprite Priority Based on Y Position
Changing player0 and player1 priority according to their Y positions by RevEng at AtariAge.
Causes player1 to always be in front by RevEng at AtariAge.
REFP0, REFP1 Inside Game Loop
Reflects player sprites. (Flips them horizontally
Left and Right.)
|do not 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.
dim _Bit6_Flip_P0 = y dim _Bit7_Flip_P1 = y 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 _Bit6_Flip_P0{6} = 0 : _Bit7_Flip_P1{7} = 0 if joy0right then _Bit6_Flip_P0{6} = 1 : _Bit7_Flip_P1{7} = 1 ;**************************************************************** ; ; Flips player0 sprite when necessary. ; if _Bit6_Flip_P0{6} then REFP0 = 8 ;**************************************************************** ; ; Flips player1 sprite when necessary. ; if _Bit7_Flip_P1{7} then REFP1 = 8 drawscreen goto __Main_Loop
Write-Only: REFP0 and REFP1 cannot be read. For example, a = REFP0 will not work properly.
PF0 Inside Game Loop
Set or clear the left and right 10% of the playfield. PF0 must be inside of your game loop.
|$0x through $Fx
|sets 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:
Four columns:
See sound for more information about these.
if collision (object,object)
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).
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.
if collision(playfield,player0) then a = a + 1 if !collision(player0,missile1) then goto __Rat_Hair_Soup
Sprite with Collision/Collision Prevention Example
This example program has a sprite you can move around and there is a mix of real and fake collision detection. In other words, the program uses a combination of 'collision prevention' and collision detection to get rid of the sticky wall problem that a lot of games have. The sprite smoothly glides along the playfield pixels instead of sticking to them or repeatedly bouncing off them.
Use the joystick to move the sprite. Press the reset switch to reset the program and toggle between two screens.
Sprite with Ball, Missile, and Collision Example
This example program has a sprite, a bouncing ball, a missile you can shoot, and there is a mix of real and fake collision detection. In other words, the program uses a combination of 'collision prevention' and collision detection to get rid of the sticky wall problem that a lot of games have. The sprite smoothly glides along the playfield pixels instead of sticking to them or repeatedly bouncing off them.
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.
A lot of people seem to use the original Tinkernut World batari Basic program to base their programs on, but that code encourages some bad habits. Instead of trying to fix the same code repeatedly for every new batch of batari Basic users that pop up from time to time, I edited the code and added new features that most people seem to ask for.
For example, the sound effect code has been updated to run while other things are running in the main loop (it no longer needs its own drawscreen). I also added a GAME OVER screen that displays the latest score and the high score. I also added reset/fire button restrainers so the game acts a little more like what is described on the Game Standards and Procedures page.
Below are a few different versions of the same program.
TWD with lives bar:
TWD with health bar:
TWD with sound data and health bar:
TWD with lives bar and two animated sprites:
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.
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.
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 variables—a to z and var0 to var8—though 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 ;*************************************************************** ; ; Mutes volume of both sound channels. ; AUDV0 = 0 : AUDV1 = 0 ;*************************************************************** ; ; Clears all normal variables and the extra 9 (fastest way). ; 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 ;*************************************************************** ;*************************************************************** ; ; Main Loop ; __Main_Loop ;*************************************************************** ; ; Gameplay logic goes here. ; ;*************************************************************** ;*************************************************************** ; ; 88 rows that are 2 scanlines high. ; DF6FRACINC = 255 ; Background colors. DF4FRACINC = 255 ; Playfield colors. DF0FRACINC = 128 ; Column 0. DF1FRACINC = 128 ; Column 1. DF2FRACINC = 128 ; Column 2. DF3FRACINC = 128 ; Column 3. ;*************************************************************** ; ; Displays the screen. ; drawscreen goto __Main_Loop bank 3 temp1=temp1 bank 4 temp1=temp1 bank 5 temp1=temp1 bank 6 temp1=temp1
The DPC+ kernel has 35 variables—a to z and var0 to var8—though 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 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.
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.
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.
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 horizontal
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 vertical
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:.
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
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
Specifies player colors a row at a time.
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
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.
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.
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
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).
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.
_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.
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.
In the DPC+ kernel, you set the colors of each missile using COLUM0 and COLUM1.
;*************************************************************** ; ; 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:
Notice how the colors of player0 contaminate missile0 and how the colors of the virtual sprite contaminate missile1 in the example image below:
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.
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.
A somewhat useless example:
if collision(player0,player1) then pfpixel 9 11 on if collision(player0,player2) then pfpixel 9 20 on if collision(player0,player3) then pfpixel 9 28 on if collision(player0,player4) then pfpixel 9 37 on if collision(player0,player5) then pfpixel 9 46 on if collision(player0,player6) then pfpixel 9 55 on if collision(player0,player7) then pfpixel 9 63 on if collision(player0,player8) then pfpixel 9 72 on if collision(player0,player9) then pfpixel 9 81 on
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, so the example above would be fairly useless.
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 value in temp4 isn't always a perfect match to the y-coordinate. It could be a little less or a little more.
Instead of having nine separate collision checks along with coordinate checking, you can simply have one collision check for player1, then do the coordinate checking after that.
;``````````````````````````````````````````````````````````````` ; Checks for player0 collision with other 9 sprites. ; 10 is used for the y check because these sprites are 11 ; pixels high (11 - 1 = 10). For example, if your sprite is ; 40 pixels tall, use 39 instead of 10. ; if !collision(player0,player1) then goto __Skip_p0_Collision temp5 = _Data_Sprite_Width[_Sprite_Size0] if (player0y + 10) >= player1y && player0y <= (player1y + 10) && (player0x + temp5) >= player1x && player0x <= (player1x + 7) then pfpixel 9 11 on if (player0y + 10) >= player2y && player0y <= (player2y + 10) && (player0x + temp5) >= player2x && player0x <= (player2x + 7) then pfpixel 9 20 on if (player0y + 10) >= player3y && player0y <= (player3y + 10) && (player0x + temp5) >= player3x && player0x <= (player3x + 7) then pfpixel 9 28 on if (player0y + 10) >= player4y && player0y <= (player4y + 10) && (player0x + temp5) >= player4x && player0x <= (player4x + 7) then pfpixel 9 37 on if (player0y + 10) >= player5y && player0y <= (player5y + 10) && (player0x + temp5) >= player5x && player0x <= (player5x + 7) then pfpixel 9 46 on if (player0y + 10) >= player6y && player0y <= (player6y + 10) && (player0x + temp5) >= player6x && player0x <= (player6x + 7) then pfpixel 9 55 on if (player0y + 10) >= player7y && player0y <= (player7y + 10) && (player0x + temp5) >= player7x && player0x <= (player7x + 7) then pfpixel 9 63 on if (player0y + 10) >= player8y && player0y <= (player8y + 10) && (player0x + temp5) >= player8x && player0x <= (player8x + 7) then pfpixel 9 72 on if (player0y + 10) >= player9y && player0y <= (player9y + 10) && (player0x + temp5) >= player9x && player0x <= (player9x + 7) then pfpixel 9 81 on __Skip_p0_Collision
Collisions with the playfield can be tricky when using virtual sprites. For sprites 1 through 9, the one that touches a playfield pixel higher on the screen gets its y-coordinate placed in temp4. Other sprites touching a playfield pixel lower on the screen don't register as long as the sprite is still touching the higher playfield pixel. In other words, the sprite with the highest playfield pixel collision is the only one that gets reported, even if a lower collision is the latest one to happen. When it happens doesn't matter. The highest virtual sprite always wins.
Below is an adapted version of the 13 Objects with Coordinates example program to help us learn more about DPC+ collisions.
13 Objects With Coordinates and Collision (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.
When you move the player0 sprite over the other sprites, a playfield pixel is turned on next to the starting position of the touched sprite.
When a sprite touches the green column on the screen, a playfield pixel is turned on across from the starting position of the sprite on the other side of the column.
You can specify an entire playfield at once by using the playfield: command. The syntax of the command is:
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
This is faster than regular pfclear in the standard kernel because the ARM CPU is faster than a 6507 at clearing its memory.
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.
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:
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 ; Background colors. DF4FRACINC = 255 ; Playfield colors. DF0FRACINC = 128 ; Column 0. DF1FRACINC = 128 ; Column 1. DF2FRACINC = 128 ; Column 2. DF3FRACINC = 128 ; Column 3. ;*************************************************************** ; ; 44 rows that are 4 scanlines high. ; DF6FRACINC = 128 ; Background colors. DF4FRACINC = 128 ; Playfield colors. DF0FRACINC = 64 ; Column 0. DF1FRACINC = 64 ; Column 1. DF2FRACINC = 64 ; Column 2. DF3FRACINC = 64 ; Column 3. ;*************************************************************** ; ; 22 rows that are 8 scanlines high. ; DF6FRACINC = 64 ; Background colors. DF4FRACINC = 64 ; Playfield colors. DF0FRACINC = 32 ; Column 0. DF1FRACINC = 32 ; Column 1. DF2FRACINC = 32 ; Column 2. DF3FRACINC = 32 ; Column 3. ;*************************************************************** ; ; 11 rows that are 16 scanlines high. ; DF6FRACINC = 32 ; Background colors. DF4FRACINC = 32 ; Playfield colors. DF0FRACINC = 16 ; Column 0. DF1FRACINC = 16 ; Column 1. DF2FRACINC = 16 ; Column 2. DF3FRACINC = 16 ; Column 3. ;*************************************************************** ; ; 6 rows that are 32 scanlines high. ; DF6FRACINC = 16 ; Background colors. DF4FRACINC = 16 ; Playfield colors. DF0FRACINC = 8 ; Column 0. DF1FRACINC = 8 ; Column 1. DF2FRACINC = 8 ; Column 2. DF3FRACINC = 8 ; Column 3.
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.
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
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.
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.
Specifies colors of each playfield row and must be followed by end. You can specify this as many times as you want.
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
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.
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.
The DPC+ kernel itself uses vblank to do its own setup, so there would only be a few free cycles left in vblank for your program. In the the DPC+ kernel, vblank would need to go in bank 1, but improvements to the DPC+ kernel have used up almost all available space in the first bank, so there is no room for vblank code. In other words, if you are using the DPC+ kernel, avoid vblank.
This is an entirely new kernel written from scratch that provides significant features that should be suitable for a wide variety of games.
Instead of two player sprites, you get six. Five of the six sprites are subject to certain limitations, however. These limitations are because the 2600's hardware can only display two sprites on any given horizontal
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 vertical
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
There are 6 sprites (player0 at the top, down to player5 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.
Related Link
pfread for the Multisprite Kernel (by bogax)
A replacement for pfread_msk.asm.
Other Limitations/Considerations (Multisprite Kernel)
One major difference between the standard kernel and this kernel is that the playfield is mirrored and located in ROM. This has several implications:
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 are a new feature of bB that allows for further customization of your game. Minikernels are intended for use as a HUD (Heads-up Display), which is the part of the game that shows lives, health, status, time, etc.
The HUDs go just below the bB screen but above the score. The minikernels that display the HUDs are totally modular, can be used in most any bB program, and will not take anything away from the visible screen (unless you choose to.)
Two minikernels are included with this release of bB. One contains a 'life counter' that can show up to six icons to represent player lives, and the lives have a variety of display configurations. The other minikernel contains a fixed, left-aligned life counter and a 28-unit 'status bar' (for lack of a better term, as the bar could be used to indicate health, time, power, speed, etc.)
Of course you never get something for nothing. Since the minikernels obviously take time to run and render their display, this takes some cycles away from your bB game. A typical minikernel will take around 700 machine cycles each frame. Since this is roughly one-quarter to one-third of the available time, you will need to decide if this is more than your game can handle.
If you feel that you can't spare the cycles but you want a way to display lives and/or health, there are three remedies.
The first is to use the pfres and/or pfrowheight constants in the standard kernel or the screenheight constant in the multisprite kernel to shrink the screen height, or the PFheights kernel option with a total height of less than 88. These may reduce the size of the vertical screen and thus may free enough cycles for a HUD.
A second remedy is to eliminate the score. At the moment, you may do this by placing "const noscore=1" near the beginning of your program.
Or, if you don't want to limit the height of your playing area or lose the score, another option is the pfscore bars. Built into the score routine are two 8-wide bars on either side that can be used for a variety of purposes. See 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
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:
The last two aren't used in 6lives. statusbarcolor is optional in 6lives_statusbar.
The new variables are described below:
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:
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.
Sets the color of the lives. When using bankswitching, this must be in your main loop or called from your main loop.
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.
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.
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.
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.
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.
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.
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.
AtariVox Support for bB (by RevEng)
How to use the AtariVox Speech Synthesizer with batari Basic.
Part 2 - Basic Voice Functionality
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.
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:
Typically you will use the directives near the beginning of your program. However, smartbranching and optimization were originally designed to be set, reset, and/or turned on and off throughout your code (though I'm not sure if this actually works :| )
The 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.
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.
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
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.
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.
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!
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.)
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.
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.
Set to read a paddle value. Must be used with no_blank_lines.
Set to use multicolored P1. Cost: loss of missile1.
Set to use both multicolored players. Must be used with above (player1colors). Cost: loss of missile0.
No gaps in playfield blocks. Cost: loss of missile0.
Specify colors of each row of playfield blocks. Cost: free.
Specify height of each row. Cost: free.
pfcolors and pfheights together
Cost: free, but colors and heights are fixed.
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 *
Cost: loss of missile1.
Cost: loss of missile1.
Cost: loss of missile1 and missile0.
Cost: loss of missile1.
Cost: loss of missile1 and missile0.
Cost: Colors and height are fixed.
Cost: loss of missile1 and colors and height are fixed.
Cost: loss of missile1 and missile0 and colors and height are fixed.
Cost: loss of missile0.
Cost: loss of missile1 and missile0.
Cost: loss of missile0.
Cost: loss of missile1 and missile0.
Cost: loss of missile0.
Cost: loss of missile1 and missile0.
Cost: loss of missile1.
Cost: loss of missile1 and missile0.
Cost: loss of missile1.
Cost: loss of missile1 and missile0.
Cost: Colors and height are fixed.
Cost: loss of missile1 and colors and height are fixed.
Cost: loss of missile1 and missile0 and colors and height are fixed.
Cost: loss of missile0.
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:
player1colors Cost: loss of missile1.
no_blank_lines Cost: loss of missile0.
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.
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.
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.
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.
currentpaddle = 0
Note that there are two controller ports, and the paddles come in pairs—two paddles are connected to one plug—so 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.
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.
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).
player0x = paddle * 2 + 1 if player0x > 140 then player0x = 140
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.
set kernel_options no_blank_lines readpaddle ;*************************************************************** ; ; Sets sprite locations. ; player0y = 12 : player1y = 85 ;*************************************************************** ; ; Creates paddles. ; player0: %1111111 %1111111 end player1: %1111111 %1111111 end __Main_Loop ;*************************************************************** ; ; Sets up color and size of player0 (double size). ; COLUP0 = $9C : NUSIZ0 = $05 ;*************************************************************** ; ; Sets up color and size of player1 (double size). ; COLUP1 = $3C : NUSIZ1 = $05 ;*********************************************************** ; ; Selects the next paddle to be read. ; currentpaddle = currentpaddle + 1 ;``````````````````````````````````````````````````````````` ; Paddle 1, then paddle 0, then paddle 1 again, etc. ; if currentpaddle = 2 then currentpaddle = 0 ;*********************************************************** ; ; Draws the screen and reads the current paddle. ; drawscreen ;*********************************************************** ; ; Paddle 0 check. ; ;``````````````````````````````````````````````````````````` ; Skips this subsection if not paddle 0. ; if currentpaddle <> 0 then goto __Skip_Paddle0 ;``````````````````````````````````````````````````````````` ; Converts value to useable coordinate. ; player0x = paddle * 2 + 1 ;``````````````````````````````````````````````````````````` ; Limits player0 movement. ; if player0x > 140 then player0x = 140 __Skip_Paddle0 ;*********************************************************** ; ; Paddle 1 check. ; ;``````````````````````````````````````````````````````````` ; Skips this subsection if not paddle 1. ; if currentpaddle <> 1 then goto __Skip_Paddle1 ;``````````````````````````````````````````````````````````` ; Converts value to useable coordinate. ; player1x = paddle * 2 + 1 ;``````````````````````````````````````````````````````````` ; Limits player1 movement. ; if player1x > 140 then player1x = 140 __Skip_Paddle1 ;*********************************************************** ; ; More code that you want to add goes here. ; ;*********************************************************** goto __Main_Loop
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. Below that is the same example with playfield collision added.
Move the paddle as you would with any Breakout-style game.
Paddle Example With Playfield Collision
Move the paddle as you would with any Breakout-style game. The ball also bounces off the playfield pixels.
If you use Stella, the most popular Atari 2600 emulator, and it seems like your paddle game isn't working, make sure that you have the latest version before doing anything else. Run your game using Stella, press the Tab key on your keyboard, click the Input Settings button, click the Devices & Ports tab, make sure the little box next to "grab mouse in emulation mode" has an x in it, 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. Always remember to hold down the Ctrl key and press R after changing the settings or the changes won't happen.
Warning: In case you missed it above, readpaddle must be used with no_blank_lines.
Related Link
A Breakout-style example program. There are examples for the standard kernel and the DPC+ kernel.
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.
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.
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.
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.
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.
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.
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).
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.
Will flash the background color when the cycle count is exceeded. This has little overhead but its utility is limited.
set debug cycles
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).
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.
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:
Did You Know?
Banks have a space between bank and the number.
bank 2 bank 3 bank 4
When jumping to a label in another bank using goto or gosub, you must specify the bank without a space between bank and the number.
gosub __Limburger bank3 goto __Frog bank2
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
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.
Press the fire button to shoot missiles. Add to the score by hitting the walls with the missiles. 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.
Remember, specifying "bank 1" is unnecessary.
See goto, gosub and return for more information about jumping from one bank to another.
Related Links
Bankswitching Explained in Detail
SeaGtGruff explains just about everything you'd ever want to know about bankswitching at AtariAge.
Bankswitching, if-then length, and pfpixel/pfhline/pfvline
A thread at AtariAge about how you might have to adjust your code more than you thought when moving up to bankswitching.
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.
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.
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 vertical
Up and Down resolution — up to 31+1 lines instead of 11+1. One drawback of this mode is that horizontal
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 (8 pixels high)
12 x 4 = 48
128 - 48 = 80 (r/w080)
You get 80 extra variables (r/w000-r/w079).
|Row 0
|Row 1
|Row 2
|Row 3
|Row 4
|Row 5
|Row 6
|Row 7
|Row 8
|Row 9
|Row 10
|Row 11
18 Rows const pfres=18 (5 pixels high)
18 x 4 = 72
128 - 72 = 56 (r/w056)
You get 56 extra variables (r/w000-r/w055).
|Row 0
|Row 1
|Row 2
|Row 3
|Row 4
|Row 5
|Row 6
|Row 7
|Row 8
|Row 9
|Row 10
|Row 11
|Row 12
|Row 13
|Row 14
|Row 15
|Row 16
|Row 17
23 Rows const pfres=23 (4 pixels high)
23 x 4 = 92
128 - 92 = 36 (r/w036)
You get 36 extra variables (r/w000-r/w035).
|Row 0
|Row 1
|Row 2
|Row 3
|Row 4
|Row 5
|Row 6
|Row 7
|Row 8
|Row 9
|Row 10
|Row 11
|Row 12
|Row 13
|Row 14
|Row 15
|Row 16
|Row 17
|Row 18
|Row 19
|Row 20
|Row 21
|Row 22
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 (3 pixels high)
32 x 4 = 128
128 - 128 = 0 (r/w000)
You get 0 extra variables.
|Row 0
|Row 1
|Row 2
|Row 3
|Row 4
|Row 5
|Row 6
|Row 7
|Row 8
|Row 9
|Row 10
|Row 11
|Row 12
|Row 13
|Row 14
|Row 15
|Row 16
|Row 17
|Row 18
|Row 19
|Row 20
|Row 21
|Row 22
|Row 23
|Row 24
|Row 25
|Row 26
|Row 27
|Row 28
|Row 29
|Row 30
|Row 31
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:
Save these special variables for simple things and use the a-z variables and the old playfield variables (var0-var47) to do the more complicated stuff.
Sound and Music Links
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.
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.
the BASICs of batari music.
Interesting information at Wikipedia.
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 play—0 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 number—for 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 two example programs that show how you can make sounds without using data.
A simple/plain/lazy sound is made in the example below when the fire button is pressed:
dim _Sound0 = c dim _Chan0_Duration = d dim _C0 = e dim _V0 = f dim _F0 = g __Main_Loop ;*************************************************************** ; ; Fire button check. ; ; Turns on channel 0 sound effect 1 if fire button is pressed. ; if joy0fire then if !_Sound0 then _Sound0 = 1 : _Chan0_Duration = 15 ;*************************************************************** ; ; Channel 0 sound effect check. ; ;``````````````````````````````````````````````````````````````` ; If no channel 0 sound effect is on, skip all channel 0 ; sound effect subsections. ; if !_Sound0 then goto __Skip_Channel_0 ;``````````````````````````````````````````````````````````````` ; Decreases the channel 0 duration counter. ; _Chan0_Duration = _Chan0_Duration - 1 ;``````````````````````````````````````````````````````````````` ; If channel 0 duration counter is zero, end current sound ; effect. ; if !_Chan0_Duration then goto __Clear_Channel_0 ;*************************************************************** ; ; Channel 0 sound effect 1. ; ; A simple/plain/lazy sound. ; ;``````````````````````````````````````````````````````````````` ; If sound 1 isn't on, skip this subsection. ; if _Sound0 <> 1 then goto __Skip_Chan0_Sound_001 ;``````````````````````````````````````````````````````````````` ; Sets the tone, volume and frequency. ; AUDC0 = 4 : AUDV0 = 8 : AUDF0 = 19 __Skip_Chan0_Sound_001 ;*************************************************************** ; ; Other channel 0 sound effects go here. ; ;*************************************************************** ;*************************************************************** ; ; Jumps to end of channel 0 area. ; goto __Skip_Channel_0 ;*************************************************************** ; ; Clears channel 0. ; __Clear_Channel_0 ;``````````````````````````````````````````````````````````````` ; Turns off channel 0 sound variable and mutes channel. ; _Sound0 = 0 : AUDV0 = 0 ;*************************************************************** ; ; End of channel 0 area. ; __Skip_Channel_0 ;*************************************************************** ; ; Displays the screen. ; drawscreen goto __Main_Loop
The example below has five sound effects. A different sound effect is assigned to the each of the four joystick directions and the same simple/plain/lazy sound effect from above is still assigned to the fire button:
Once you check out the other four sound effects in the example program above, 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.
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.
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.
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.
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.
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.
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:
No sound (silent).
Buzzy tones.
Carries distortion 1 downward into a rumble.
Flangy wavering tones, like a UFO.
Pure tone.
Same as 4.
Between pure tone and buzzy tone (Adventure death uses this).
Reedy tones, much brighter, down to Enduro car rumble.
White noise/explosions/lightning, jet/spacecraft engine.
Same as 7.
Same as 6.
Same as 0.
Pure tone, goes much lower in pitch than 4 & 5.
Same as 12.
Electronic tones, mostly lows, extends to rumble.
Electronic tones, mostly lows, extends to rumble.
Visual batari Basic has a Music and Sound Editor that should help bB users create Atari 2600 music and sound effects faster and easier than ever before.
Move the joystick in 4 directions and press the fire button to hear sounds. Joystick sounds are on channel 0 and the fire button sound is on channel 1. The fire button sound is a collection of random notes I quickly banged out on the VbB Music and Sound Editor keyboard. Move the joystick up to hear a short piece of a tune based on arcade Donkey Kong, move down to hear the screeching tires style noise from Joust, move left to hear a bit of drums from Pitfall II: Lost Caverns, and move right to hear part of the drums from Skate Boardin'.
Sound Example Using Data and Bankswitching
Move the joystick in 4 directions and press the fire button to hear sounds. Joystick sounds are on channel 0 and the fire button sound is on channel 1. The fire button sound is a collection of random notes I quickly banged out on the VbB Music and Sound Editor keyboard. Move the joystick up to hear a short piece of a tune based on arcade Donkey Kong, move down to hear the screeching tires style noise from Joust, move left to hear a bit of drums from Pitfall II: Lost Caverns, and move right to hear part of the drums from Skate Boardin'.
Sound Example Using Data (One Sound Only)
Press the fire button to hear part of the drums from Skate Boardin'.
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.
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.
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. 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.
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.
True if left joystick is pushed up.
True if left joystick is pushed down.
True if left joystick is pushed left.
True if left joystick is pushed right.
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.
True if right joystick is pushed up.
True if right joystick is pushed down.
True if right joystick is pushed left.
True if right joystick is pushed right.
True if right joystick's fire button is pushed.
Advanced Joystick Reading with SWCHA (by Robert M)
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.
Reading the console switches is done by using an if…then statement.
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.
True if the COLOR/BW switch is set to BW, false if set to COLOR.
True if Select is pressed.
True if left difficulty is set to B (amateur), false if A (pro).
True if right difficulty is set to B (amateur), false if A (pro).
For example, these are accessed by:
if switchreset then 200
These can all be inverted by the NOT (!) token:
if !switchreset then 200
Did You Know?
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 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.
if a{0} then odd
Often it is handy to express hexadecimal numbers in your Basic program. Simply place the $ before a number to use hexadecimal.
COLUPF = $2E a[$12] = $F5
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.
CTRLPF = %00100010 player0: %00100010 %11100111 end
Related Links
Thanks to bogax, this tool has a few improvements, including an interactive byte section.
Why Do You Read Binary Digits Right to Left?
"Numbers do not increase the same way we read (left to right). This is the same way we add, multiply and subtract in math."
Negative numbers are somewhat supported. Although variable values can contain 0-255, the numbers will wrap if 255 is exceeded. Therefore, you can think of numbers from 128-255 as being functionally equal to -128 to -1. This is called "two's compliment" form because the high bit is set from 128-255, so this high bit can also be called the "sign."
In other words, adding 255 to a variable has exactly the same effect as subtracting 1.
If you want to flip the sign of a variable where positive becomes negative or negative becomes positive, you'll be happy to know that assignment to a unary minusFlips the sign of a variable. Positive becomes negative, negative becomes positive. (such as a = -a) is supported by batari Basic. If you have a version of bB that hasn't been updated, you may need to use a=0-a instead. You can learn more about that at AtariAge.
You can also assign a variable to a negative number, such as a = -1. It looks like 4.4 fixed point types were coded properly, but apparently integers were not in older versions of bB, so a = -1 may not work with plain old normal numbers. If you are using an older version of bB, there is a trick you can use: subtract the number you want to use from 256. For example, if you want to use a = -1 that would be 256 - 1, so you'd use a = 255. If you want to use a = -8, that would be 256 - 8, so you'd use a = 248.
Warning: Assignment to negative numbers will not work properly with certain versions of the assembler (DASM). Versions of bB prior to 1.0 were packaged with DASM 2.20.10, which doesn't process negative numbers correctly. Version 2.20.07 of the assembler does work, and so this has been packaged with bB version 1.0.
Did You Know?
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.
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.
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.
a = a + 1 COLUPF = r + $5F player0y = player1y + 6 + temp1
a = b + c = 11 cycles
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.
a = a - 1 COLUPF = r - $5F player0y = player1y - 6 - temp1
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.
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 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.
a = b / 3 = 38 cycles
a = b / 2 = 8 cycles
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.
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.
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
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.
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.).
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.
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
Did You Know?
It seems incude should come before includesfile in your code or it may not compile.
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.
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.
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.
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.
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
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.
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.
There are 3 stages in the bB compilation process where errors may be caught — in the preprocessor, the compiler, or the assembler.
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: "@"
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 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).
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.
[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.]
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.)
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).
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.
2600 Basic Compilation Failed!
Text by Random Terrain
Over the years, when trying to fix problems in other people's programs or my own, I would discover the cause of an error message, but I couldn't put it on the bB page because batari broke up the compilation error messages into preprocessor errors, compiler errors, and assembler errors. I never know what type of error it is. All I know is that I get an error message and when I figure out what is causing it, the error message goes away.
From now on, whenever I find the cause of an error, I'm going to stick it in this subsection.
This is usually caused by forgetting the then in an if…then and having it directly followed by goto. The error message will probably also have these complaints:
LINE --> complex condition detected
Bad code example:
if a = 5 goto __Flying_Frog
Fixed code example:
if a = 5 then goto __Flying_Frog
One thing that can cause this error is a rem statement without a space after the keyword. Looks like batari Basic needs room around the rem to recognize it.
Bad code example:
Fixed code example:
rem ----------------------------------------------
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.
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.
Make sure you are calling drawscreen somewhere in your game loop (and that your game runs in a loop!). If you are using drawscreen, 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.
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.
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 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.
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.
$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]
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."
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 is—why 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 program—you don't need to go searching for all the places it's used and change them individually (potentially missing some places).
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.
Text by SeaGtGruff
Values come in two basic flavors—numbers (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.
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 flavors—ROM and RAM.
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.
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.
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:
A few of these registers—such as the accumulator and the X and Y registers inside the 6507 chip—don't have memory addresses, but we can store information in them by using specific instructions. But most of the other registers—those found inside the RIOT and the TIA—are 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 registers—particularly many of the ones in the TIA—are 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 all—they're called "strobe" registers that make something happen whenever we write to them. The TIA's registers come in two different flavors—read-only and write-only.
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 objects—for example, reading the CXM0P register lets you see if missile0 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.
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 register—for example, writing a color value to the COLUPF register will tell the Atari what color to draw the playfield with—but you can't read a write-only register to see what value it's currently set to.
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 with—only 128 bytes—and 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 2600—and particularly when using batari Basic—you 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 constraints—for 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 goto—after 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.
Sprite/Ball/Missile and Collision
8.8 Fixed Point Sprite Movement
32 x 23 Maze (Animated Sprite)
32 x 12 Maze (Animated Sprite)
Paddle With Playfield Collision
Sound with Data & Bankswitching
Repetition Restrainer for Fire Button
13 Objects with Collision (DPC+)
Playfield Color & Background Color Scrolling with Changeable Score Background Color (DPC+)
How to See if 2 Bits are Different
Quickly convert hex, decimal, binary and more with the programming equivalents tool. There's also a playfield toy and a magic Firefox spell check box for REM statements.
Interactive 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).
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.
If you can't use Visual batari Basic to make sprites, try this online sprite editor.
Produces authentic looking Atari 2600 Labels including the famous "Picture Label" using your own image and title. Related link: Atari Cart & Box Fonts
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.
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.
Test Your Games on a Real Atari
What is a programming language?
Keywords, Constants, and Registers
Line Length and Other Limitations
Decision Making (Brains of Game)
Sequential Data [sdata, sread]
Additional Kernels, Minikernels
2600 Basic Compilation Failed!
Earlier Games Don't Work Correctly
Upgrading bB Doesn't Fix Anything
Constants, Variables, and Values
2k, 4k, 8k, 16k or 32k (romsize)
Additional Kernels, Minikernels...
Arithmetic Operators (+, -, *, /)
Chart: Tones (Sound and Music)
Colors: Playfield Background Color
Colors: Playfield Pixels & Ball Color
Direction Change (Flip Sprites)
Ephemeral Variables & Registers
Fire Button: Repetition Restrainer
Health Bar (life cntr/status bar)
lifecolor (life counter/status bar)
lives (life counter/status bar)
lives: (life counter/status bar)
Multicolored Background (DPC+)
no_blank_lines (kernel_options)
pfscore bars: Health Bar Examples
pfscore bars: Lives Bar Examples
player1colors (kernel_options)
Read-Only Registers (Glossary)
Reflect Player Sprites Horizontally
screenheight (Multisprite Kernel)
Toggle Variable Between 2 Values
Under: Sprites Under Playfield
Write-Only Registers (Glossary)
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 Atari 2600. Use batari Basic at your own risk. I am not responsible if batari Basic makes you cry or gives you brain damage.