|
|
|
|
|
|
batari Basic Commands
|
|
|
Frequently Used Index 2k, 4k, 8k, 16k or 32k (romsize) Additional Kernels, Minikernels . . . Advanced Joystick Reading (SWCHA) Arithmetic Operators (+, -, *, /) Chart: Tones (Sound and Music) Ephemeral Variables & Registers Even Number (Is a value odd or even?) Hacking batari Basic's .asm Files Health Bar (life counter/status bar) lifecolor (life counter/status bar) lives (life counter/status bar) no_blank_lines (kernel_options) Odd Number (Is a value odd or even?) player1colors (kernel_options) Reflect Player Sprites Horizontally screenheight (Multisprite Kernel) Toggle a Variable Between 2 Values About this Page Links that jump to other places on this page are blue. Links that lead to other pages online are red.
If you see a red rectangle to the right of an item that looks like the image above, that means the item needs to be inside of your main game loop because it will be reset after a drawscreen. See Ephemeral Variables & Registers for more information. Example Programs Sprite/Ball/Missile and Collision 32 x 23 Maze (Animated Sprite) 32 x 12 Maze (Animated Sprite) Useful Code and Tips Write Only FAQ Q. How do I program an Atari 2600 game?
A. Try batari Basic. Hop up to the top of this page and start there. Q. How do you pronounce batari?
A. buh-tari Q. How do I make the playfield blocks square or just make them smaller?
A. Squint your eyes? Just kidding. They are called playfield pixels and there's more than one way to do that. Check out the following links: 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. Useful Tools 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.
Produces authentic looking Atari 2600 Labels including the famous "Picture Label" using your own image and title. Related link: Atari Cart & Box Fonts
Quickly convert hex, decimal, binary and more with the programming equivalents tool. There's also a crusty old playfield toy and a magic Firefox spell check box for REM statements.
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. Related Links
Miscellaneous rem (short for remark)
The rem statement is used for in-program comments. These comments are very helpful not only to other programmers trying to make sense of your code, but to yourself if your memory is anything like mine :)
rem **************************************************************** rem * rem * Name: Space Nuggets rem * Version: 2010y_09m_04d_1821t rem * rem **************************************************************** rem * rem * Programmer: Scadoobie Floinkenburger rem * Language: batari Basic v1.0 rem * System: Atari 2600 VCS rem * rem **************************************************************** Remarks can also be added to data and sdata. In this case, rem is not used. No colon, no rem, just put a semicolon and your comment.
Example: data musicData 5,7,18 0,0,0 9 2,7,18 ; Mango knee bone butter 0,0,0 3 7,6,12 ; Elusive fractal salve 0,0,0 9 2,6,12 0,0,0 3 255 ; End of data end
reboot
This command will warm bootRestart a computer under software control. your game. Everything is cleared by reboot, similar to turning the Atari 2600 off and back on again, so do not use it if you need to store things such as the highest level reached, high score, current game selections and so on.
if switchreset then reboot It seems that reboot can also mess up random numbers, so you might want to have a Start/Restart section that you can jump to that clears and sets up variables, sprite positions, colors, and so on. Below is an example inspired by RevEng that will clear all normal variables: rem ================================================================ rem ================================================================ rem = rem = rem = Clears all normal variables. rem = rem = rem ---------------------------------------------------------------- for temp5 = 0 to 25 : a[temp5] = 0 : next If you are using Superchip RAM, you can use the example below: rem ================================================================ rem ================================================================ rem = rem = rem = Clears all normal variables and old playfield variables. rem = rem = rem ---------------------------------------------------------------- for temp5 = 0 to 25 : a[temp5] = 0 : next for temp5 = 0 to 47 : var0[temp5] = 0 : next If you're doing your best to make a professional-looking game that remembers the high score and other things from one game to the next, you'll want to use a modified version of the examples above so your important variables won't be cleared. You can safely use one of the examples above outside of your game, near the beginning of your code, after your dims and defs and so on, but before the Start/Restart section to make sure all of your variables have been cleared when the game first starts.
See switchreset for more information.
Warning: It seems that reboot causes random numbers to be not so random anymore, so if randomness is important to you, do not use reboot.
end This is a unique command, as it is never indented. It is used to tell the compiler that you are finished with your data entry, graphics definition or inline assembly. That is, the data, sdata and asm commands, and any command normally ending with a colon (such as playfield:, player0:, lives:, and others).
Variables
You have 26 general purpose variables in batari Basic, fixed as a-z. Although they are fixed, you can use the dim command to map an alias to any of these variables.
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.
Variable Testing If you'd like to know the value of a variable when testing your game, you can use the score to display the value. The following example will display the values of two variables: Main_Loop rem **************************************************************** rem * Your code to test goes here. rem **************************************************************** scorecolor = $1A score = 0 temp5 = a if temp5 > 0 then for temp6 = 1 to temp5 : score = score + 1000 : next temp5 = b if temp5 > 0 then for temp6 = 1 to temp5 : score = score + 1 : next drawscreen goto Main_Loop Just change a and b to whatever variables you want to check. The first variable will be displayed in the three digits of the left side of the score and the second variable will be displayed in the three digits of the right side of the score.
let (optional)
The let statement is optional, and is used for variable assignment. It was left in because an early unreleased version of batari Basic required it. If you wish to use it, it will not affect program lengthit will simply be ignored by the compiler.
let x = x + 1
const
A constant is a special type of variable that cannot be changed while a program is running. To declare a constant in batari Basic, use the const command. const declares a constant value for use within a program. This improves readability in a program in the case where a value is used several times but will not change, or you want to try different values in a program but don't want to change your code in several places first.
const MyConst = 200 const Monster_Height = $12 After that, any time MyConst or Monster_Height is used, the compiler will substitute 200 or $12 respectively.
Older versions of batari Basic had a limit of 50 constants, but that limit was later increased to 500.
dim
Used to create more descriptive names for the 26 variables, or to create a fixed-point type and assign it to some of these variables.
Using dim to Create More Descriptive Names Did You Know? More than one alias may be mapped to the same variable. This is useful for when you will inevitably need to reuse variables in multiple places. Unlike other Basics, the most common use of the dim statement is not for arrays in batari Basic, but rather for creating an alias, a more descriptive name for each variable than a-z. The statement simply maps a descriptive name to any of the original 26 variables.
dim _Monster_xpos = a dim _Monster_ypos = b Note that more than one alias may be mapped to the same variable. This is useful for when you will inevitably need to reuse variables in multiple places.
Using dim to Map Variables to Fixed Point Types Although you can use the variables a-z as integersNumbers without a decimal (positive, negative or zero). Whole numbers. without ever using dim, you cannot use a-z as fixed point variables without using dim. See fixed point variables for more information.
def (by SeaGtGruff) The def statement is similar to the dim statement, but it lets you define an entire string and assign it to a logical name. So if you use def to assign 'a{0}' to 'MyVar,' then everywhere batari Basic sees 'MyVar' in your program, it will replace it with 'a{0}.'
You may already know that dim won't let you assign a specific alias that only works for a certain bit, but the good news is that def will. Of course, the other restrictions and rules still apply, so you have to check it with 'if MyVar then,' or 'if !MyVar then,' instead of using 'if MyVar=0 then,' or 'if MyVar=1 then.'
The def statement also works with math, such as 'def MyVar=a + b - c.' Everywhere batari Basic sees 'MyVar,' it will replace it with 'a + b - c.' But def shouldn't be confused with a function, because it's just doing a "search and replace" in the code, not calling a function. So def can help make your code more readable and concise, but it doesn't save any bytes or cycles.
Although def and dim are totally separate, you can use them in combination if you want:
dim game_flags = a
def game_level=game_flags & $0F
def show_title=game_flags{4}
def game_in_play=game_flags{5}
def game_over=game_flags{6}
def show_hi_scores=game_flags{7}
Note: For def to work properly, do not use a space before or after the equals sign.
Bit Operations Did You Know? Bit operations do not use an equal sign in if-then statements.
So don't do this:
if a{0} = 1 then gosub moosegizzard
Do this instead:
if a{0} then gosub moosegizzard
And don't do this:
if a{0} = 0 then gameover
Do this instead:
if !a{0} then gameover
On modern systems, you may not think twice of using an entire byte or even a word for every flag. For example, to determine whether a game is in progress or it is over, often an entire byte is used even though its only value is 0 or 1.
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:
The XOR examples produce more compact assembly (thanks to RevEng).
How to Randomize a Bit (by RevEng) If you'd like to flip a bit randomly, RevEng has an easy solution for that. For example, if you want to randomize bit 0 of the variable a, here's how you do it:
rem ****************************************************************
rem * Random number for a{0}
rem *
temp5 = (rand & %00000001)
a = a ^ temp5
To randomize bit 7 of variable a, do this:
rem ****************************************************************
rem * Random number for a{7}
rem *
temp5 = (rand & %10000000)
a = a ^ temp5
To randomize other bits, all you have to do is move the 1 to the bit position you want to randomize and change the variable from a to whatever variable you're using.
You may also assign one bit to another bit. For example:
d{3} = r{4}
f{5} = !f{5}
Accessing the bits of a variable is almost like turning it into 8 separate variables. Instead of having only 26 variables, you potentially have 208. You just have to remember that these itty-bitty variables can only hold 0 or 1.
You can use dim to create a normal descriptive variable name and access the individual bits later.
dim my_variable = a
my_variable{0} = 0
my_variable{1} = 0
my_variable{2} = 1
my_variable{3} = 0
my_variable{4} = 0
my_variable{5} = 0
my_variable{6} = 0
my_variable{7} = 0
rem * That will set my_variable to 4, or %00000100.
Here's the same example with a different alias for each bit: dim Hero_Shot = a
dim Hero_Thrust = a
dim Hero_Crash = a
dim Hero_Shield = a
dim Enemy_Shot = a
dim Enemy_Thrust = a
dim Enemy_Crash = a
dim Enemy_Shield = a
Hero_Shot{0} = 0
Hero_Thrust{1} = 0
Hero_Crash{2} = 1
Hero_Shield{3} = 0
Enemy_Shot{4} = 0
Enemy_Thrust{5} = 0
Enemy_Crash{6} = 0
Enemy_Shield{7} = 0
rem * That will set a or any alias for a to 4, or %00000100.
If you use dim to create descriptive variable names, don't try to 'dim' single bits.
Bad examples:
dim my_variable = a{1}
dim my_variable{1} = a
dim my_variable{1} = a{1}
Instead of trying to use dim for that, you can use def.
Example:
def Hero_Shot=a{0}
def Hero_Thrust=a{1}
def Hero_Crash=a{2}
def Hero_Shield=a{3}
def Enemy_Shot=a{4}
def Enemy_Thrust=a{5}
def Enemy_Crash=a{6}
def Enemy_Shield=a{7}
Hero_Shot = 0
Hero_Thrust = 0
Hero_Crash = 1
Hero_Shield = 0
Enemy_Shot = 0
Enemy_Thrust = 0
Enemy_Crash = 0
Enemy_Shield = 0
rem * That will set a or any alias for a to 4, or %00000100.
See def for more information.
How to Check if Two Bits are Different The example below shows how you can tell if two bits are different:
if a{0} then if !a{5} then goto __Pause_Game
if !a{0} then if a{5} then goto __Pause_Game
You might also want to try the solution that RevEng posted at AtariAge.
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."
Cycle Count by SeaGtGruff a{0} = 0 : rem * turn a bit off
Nybble Variables Sometimes bit operations are too limited and you'll need to use variables in the usual way, but you'll often find that the values of some of your variables barely reach double digits. If you know that some of your variable values will never go over 15, you can split one or more variables in half, so one variable will become two nybble variables. A while ago, batari said that he might be able to have batari Basic handle nybble variables automatically so you can use them like regular variables, but until that happens, you'll have to use the example by SeaGtGruff. Making your own nybble variables can become confusing, so be sure to put comments in your programs that explain what each nybble variable does and how it works.
Nybble Me This, Batman! (by SeaGtGruff) Months after this section was created, SeaGtGruff figured out a less confusing way to use nybble variables with macro and def. Below is the old example adapted from a post by SeaGtGruff in case you want to see it:
Here's the .bin file to use with an emulator or Harmony cart: Nybble Me This playable .bin file
Here's the bB code:
Improved Nybble Variables Below is the new and improved version using macro and def:
Here's the .bin file to use with an emulator or Harmony cart: Improved Nybble Variables playable .bin file
Here's the bB code: Improved Nybble Variables .bas file
Notes: Example: temp5 = (PEEK_Game_Level) + 1 POKE_Game_Level temp5 These nybble variables can roll over from 15 to 0 just like a normal variable rolls over from 255 to 0. They can also roll in the other direction (from 0 to 15). Thanks to SeaGtGruff's use of macro and def, nybble variables are finally easy to use and the confusion factor has been virtually eliminated. The only thing that will be better is when batari finds the time to add real nybble variables that will be automatically handled by batari Basic. Until then, at least we have a great alternative.
See the original post at AtariAge for more information.
Splitting a variable into two nybble variables whenever possible is a great way to get more variables. Instead of having only 26 variables, you potentially have 52. Between nybble variables and bit operations, you could end up having more variables than you need.
Bitwise (Logical) Operators
There are three operators for logical operations in batari Basic that can be used to change the state of individual bits or to mask multiple bits. They are tokenized as:
a = a & $0F a = b ^ %00110000 a = a | 1
Bitwise Simplified (by Random Terrain) If you are new to this stuff, it can be confusing and most explanations usually seem overly complicated. Maybe these simple explanations and examples will help:
A bit is like a simple light switch. It can be on or off.
AND = OFF (0 = OFF 1 = UNTOUCHED) Use 0 to make sure the light switch is off. Use 1 to leave it as it is.
a = %10101010 : a = a & %00001111
OR = ON (1 = ON 0 = UNTOUCHED) Use 1 to make sure the light switch is on. Use 0 to leave it as it is.
a = %10101010 : a = a | %00001111
XOR = FLIP (1 = FLIP 0 = UNTOUCHED) Use 1 to reverse the position of the light switch. Use 0 to leave it as it is.
a = %10101010 : a = a ^ %00001111
Fixed Point Variables
Fixed point variables can be assigned to fractional values, similar to floating point variables in other languages. bB provides two fixed point types:
To declare these special types, you must use the dim statement. To declare an 8.8 type, you must specify the integer and fractional portion by the following:
dim myvar = a.b dim Monster_x = d.r
The first will use the variable a as the integer and b as the fraction, the second will use d as the integer and r as the fraction. Then any time you use myvar or monsterx in a variable assignment, the compiler will know to use the 8.8 fixed point type.
dim xvelocity = c.c dim yvelocity = d.d
After this dim, using xvelocity will tell the compiler to use the 4.4 type.
include fixed_point_math.asm
Alternatively, you may modify the includes file to include it automatically. See include or includes file for more information.
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.
DAH 32 x 23 Maze with Animated Sprite and Roaming Sprite 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.
Here's the .bin file to use with an emulator or Harmony cart: 32 x 23 Maze with Animated Sprite and Roaming Sprite Example .bin file
Here's the bB code: 32 x 23 Maze with Animated Sprite and Roaming Sprite Example .bas file
DAH 32 x 12 Maze with Animated Sprite and Roaming Sprite 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.
Here's the .bin file to use with an emulator or Harmony cart: 32 x 12 Maze with Animated Sprite and Roaming Sprite Example .bin file
Here's the bB code: 32 x 12 Maze with Animated Sprite and Roaming Sprite Example .bas file
Ephemeral Variables and Registers (short-lived)
By ephemeralLasting for only a short time., we mean variables or TIA registersTelevision Interface Adaptor Registers
Labels and Line Numbers
Alphanumeric labelsCan be made up of letters, numbers, and underscores. are supported by batari Basic. You may use line numbers if you prefer. Some old-school programmers like line numbers, or at least use them as a matter of comfort since they were necessary in early Basics. In any case, labels and line numbers are optional. Typically you will only need them when you want to specify a goto or gosub target.
10 pfpixel 2 3 on 20 drawscreen player0x = player0x + 1 : goto __My_Location player0y = 29 : goto My_Location_2 __My_Location x = x + 1 My_Location_2 x = x - 1
Jumping Around goto
The goto statement is used to jump to a line number or label anywhere in your program.
goto 100 goto My_Subroutine
In bank-switched games, if you are jumping to a routine in another bank, you must specify the bank.
goto Move_Monster bank2
Cycle Count by SeaGtGruff goto = 3 cycles goto with bankswitch = 49 cycles
on goto
This works similar to a case statement in other languages. It is useful for replacing multiple if-then statements when conditions happen in an ordinalIn order. For example, first, second, third, and so on. fashion.
on x goto 100 200 300 400 is the same as: if x = 0 then 100 if x = 1 then 200 if x = 2 then 300 if x = 3 then 400 You cannot use expressions in an on goto statement.
For example: on x-8 goto 100 200 300 400 would need to be replaced with something like this: y = x - 8 : on y goto 100 200 300 400
The on
goto statement can jump only within the current bank. If you are writing a bank-switched game and want to jump to a label in another bank, you can do it from the places that on
goto jumps to.
on x goto Red Green Blue Purple Red goto Red02 bank2 Green goto Green02 bank2 Blue goto Blue02 bank3 Purple goto Purple02 bank3 Warning: There is no automatic checking to see if your variable's value coincides with a label in the list. In the above example, there is no label in the list to jump to if x is equal to 4 or more, but the statement will try to find a label for it anyway, which almost always means that your program will crash. You can check your variable before the on goto to prevent this.
Example: if x < 4 then on x goto 100 200 300 400 If you have too many jumps to put comfortably in a single on goto statement, you can break it up into multiple on goto statements. Example: if x < 4 then on x goto 100 200 300 400 y = x - 4 : if y < 4 then on y goto 500 600 700 800 y = x - 8 : if y < 4 then on y goto 900 1000 1100 1200
gosub
The gosub statement is often used for a subroutine that is called by multiple locations throughout your program.
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.
gosub Move_Monster bank2
Cycle Count by SeaGtGruff gosub + return = 12 cycles gosub with bankswitch + return = 122 cycles gosub with bankswitch + return otherbank = 110 cycles
on gosub
This works very much like on
goto, and has similar restrictions, but control returns back to the statement following the on ... gosub list when a return is encountered.
on x gosub 100 200 300 400 drawscreen The above example will return control to the drawscreen statement following the on gosub list when a return is encountered.
You cannot use expressions in an on gosub statement.
For example: on x-8 gosub 100 200 300 400 would need to be replaced with something like this: y = x - 8 : on y gosub 100 200 300 400 Warning: There is no automatic checking to see if your variable's value coincides with a label in the list. In the above example, there is no label in the list to jump to if x is equal to 4 or more, but the statement will try to find a label for it anyway, which almost always means that your program will crash. You can check your variable before the on gosub to prevent this.
Example: if x < 4 then on x gosub 100 200 300 400 If you have too many jumps to put comfortably in a single on gosub statement, you can break it up into multiple on gosub statements. Example: if x < 4 then on x gosub 100 200 300 400 y = x - 4 : if y < 4 then on y gosub 500 600 700 800 y = x - 8 : if y < 4 then on y gosub 900 1000 1100 1200
return The return statement is used in a subroutine to return to the part of the program right after a gosub which called the subroutine.
Be careful when using return. If a running program encounters one without a gosub that called it, the program will crash or strange things may happen. Another thing to remember is that return can also be used to return a value for a function. See functions for more information.
If using bank switching, there is some additional overhead with every return statement. Although the standard return by itself will automatically return to whatever bank called the subroutine, it takes up ROM space and cycles to automatically return to the proper bank. Instead, you may use thisbank or otherbank after your return:
return thisbank
pop Cancels the return address from a subroutine. This essentially makes the last gosub command work like it was a goto.
Decision Making Brains of a Game Did You Know? You are allowed only one OR for each if-then statement. You can use more than one AND in a line, but you cannot mix AND with OR.
The NOT operator can only be used with statements that do not include OR or a comparison token (<, >, =). See Boolean Operators for more information.
Multiple if-thens in a single line might not work correctly if you are using AND, OR, or NOT.
AND/OR/NOT Chart
AND = && OR = || NOT = !
if-then Perhaps the most important statement is the if-then statement. These can divert the flow of your program based on a condition.
The basic syntax is: if condition then action
Note that in specific cases, assembly of if-then statements with a line number or label as the target will fail. If this happens, the assembler will report a "branch out of range." One way to fix this is to change the offending if-then statement to if-then goto, or you can let the compiler fix the problem for you by turning on smart branching.
To do this, use the following: set smartbranching on Place it near the beginning of your program. Be aware that turning on smartbranching will slightly obfuscateConfuse, darken, make unclear. the generated assembly file, so do not use it if you plan to examine the assembly later to see how it works. Smartbranching will not slow down your program, so you don't have to be afraid to use it. See smartbranching for more information.
There are three types of if-then statements.
#1 Simple True/False Evaluation The first type is a simple check where the condition is a single statement.
The following example diverts program flow to line 20 if a is anything except zero: if a then 20 This type of if-then statement is more often used for checking the state of various read registers. For example, the joysticks, console switches, single bits and hardware collisions are all checked this way.
Example: if joy0up then x = x + 1 That will add 1 to x if the left joystick is pushed up.
if switchreset then 200 Jumps to line 200 if the reset switch on the console is set. if collision(player1,playfield) then t = 1 Sets t to 1 if player1 collides with the playfield.
if !a{3} then a{4} = 1
Sets bit 4 of a if bit 3 of a is zero. See Bit Operations for more information about using single bits.
#2 Simple Comparison A second type of statement includes a simple comparison. Valid comparisons are = , < , >, <=, >=, and <>.
Examples: if a < 2 then 50 if f = g then f = f + 1 if r <> e then r = e
#3 Compound Statement if x < 10 && x > 2 then b = b - 1 if !joy0up && gameover = 0 then 200 if x = 5 || x = 6 then x = x - 4 Warning: Using multiple if-thens in a single line might not work correctly if you are using boolean operators.
Relief for ENDIF Addicts
So instead of endif, which you can't use in batari Basic:
if x = 4 then
x = t - 2 : mosterheight = (monsterheight&7) + 1
pfhline 3 4 x off : a = (rand&127) + 16 : r{0} = 1
endif
or gosub:
if x = 4 then gosub Monster_Kill
. . .
. . .
. . .
Monster_Kill
x = t - 2 : mosterheight = (monsterheight&7) + 1
pfhline 3 4 x off : a = (rand&127) + 16 : r{0} = 1
return
You'd use this instead:
if x <> 4 then goto Skip_Monster_Kill
x = t - 2 : mosterheight = (monsterheight&7) + 1
pfhline 3 4 x off : a = (rand&127) + 16 : r{0} = 1
Skip_Monster_Kill
If you're using boolean operators, just invert each individual condition and change && to || or vice-versa. So this: if x = 4 && y = 2 then r = 4 becomes this: if x <> 4 || y <> 2 then goto Skip_Ninja_Stab r = 4 Skip_Ninja_Stab And this: if r < 4 then e = e + 1 becomes this: if r >= 4 then goto Skip_Fish_Jump e = e + 1 Skip_Fish_Jump
Boolean Operators
Boolean operators are used as conditions in if-then statements. They are tokenized as:
if a < 31 && a > 0 then 50 if a = 2 || a = 4 then a = a + 1 if !joy0up then 200 Warning: If you use || in an if-then statement, the statement must be located at the beginning of a line. At this time, compilation will succeed but your program will probably not work correctly. This issue will probably be fixed in a later version.
else
You may use else after an if-then statement. An if-then will check if a condition is true and divert program flow, but an else allows you to also divert program flow in a different way if the condition turns out to be false.
if r = 2 then 20 else 30 if a > b then r = 2 : pfpixel 3 5 on: d = d - 1 else d = d + 1 : r = 3 if a > b then 20 else c[4] = 12 Warning: The else keyword might not work correctly in a statement containing &&. The else keyword may also not work as expected when there is more than one if-then on a single line.
Loops A common form of a loop is a for-next loop, but a loop in general is any program that repeats. In this sense, all batari Basic programs must be loops, in that the programs never exit.
In batari Basic, you should limit your use of loops that do not include the drawscreen function somewhere. Too many loops take time which is somewhat limited. See drawscreen for more information.
for-next For-next loops work similar to the way they work in other Basics.
The syntax is: for variable = value1 to value2 [step value3]
for x = 1 to 10 for a = b to c step d for l = player0y to 0 step -1 It might be tempting to use a for-next loop with drawscreen in it to slow down a game, but it's better to use a counter since you can slow down specific things while letting other things run at full speed.
next
Normally, you would place a variable after the next keyword, but batari Basic ignores the keyword and instead finds the nearest for and jumps back there. Therefore, the usual way to call next is without a variable. If any variable is specified after a next, it will be ignored.
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 x = 1 to 20 goto 100 for g = 2 to 49 100 next The next above WILL NOT jump back to the first for, instead it will jump to the nearest one, even if this statement has never been executed. Therefore, you should be very careful when using next.
Random Numbers Randomness will help make your games replayable and more fun. Players will get a fresh experience every time they play your games and they'll really be playing instead of memorizing an exact way to 'beat' your game that is set in stone. Unrestrained randomness would make your games impossible to play, so you need to control it. You can use Controlled Randomness to place non-player characters, bonus items, bonus areas, rooms, and more in a certain number of predetermined positions or general areas where you stay in control and nothing will come up in unexpected places. Your games will have replay value and most players will appreciate it. 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.
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.
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!
dim rand16 = <var>
at the beginning of your program. <var> is one of your 26 variables (a-z.) Then you use rand as you normally would.
Did You Know? All data tables, regular or sequential, must be located in the same bank where they are read. If you try to access data in another bank, there will be no errors, but the data you get will certainly be incorrect.
Data Statements and Arrays (read-only)
For convenience, you may specify a list of values that will essentially create a read-only array in ROM. You create these lists of values as data tables using the data statement. Although the data statement is similar in its method of operation as in other Basic languages, there are some important differences. Most notably, access to the data does not need to be linear as with the read function in other Basics, and the size is limited to 256 bytes.
data mydata 200, 43, 33, 93, 255, 54, 22 end To access the elements of the data table, simply index it like you would an array in RAM. For example, mydata[0] is 200, mydata[1] is 43, ... and mydata[6] is 22. The maximum size for a data table is 256 elements. Note that there is no checking to see if you have accessed values beyond the table. Doing so will not cause any errors, but the values you get probably won't be very useful.
To help prevent the reading of values beyond data tables, a constant is defined with every data statement this constant contains the length, or the number of elements, in the data. The constant will have the same name as the name of the data statement, but it will have _length appended to it.
data mydata 1,2,3,4,5,6,7,8,9 end
you can then access the length of the data with mydata_length. You can assign this to variables or use anywhere else you would use a number.
a = mydata_length These data _length constants will not work correctly if they are used before you declare the corresponding data statement. If you need to use a data _length constant before its data statement, declare the data _length constant near the beginning of your program (using the name of the constant as the value).
Example: const mydata_length=mydata_length Note again that these data tables are in ROM. Attempting to write values to data tables with commands like mydata[1]=200 will compile but will perform no function.
Related Link 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.
sdata mymusic=a 1,2,3,4,5,6,7,255 end
The above will set up a sequential data table called mymusic that uses variables a and b to remember the element at which it is currently pointing.
t = sread(mymusic)
This will get the next value in the data table mymusic and place it in t.
Did You Know? You might think you have chosen the perfect colors when testing your games using an emulator, but Atari 2600 colors are darker on televisions and some of the darker grays may appear to be black. For example, if you are using 2, 4, and 6; bump it up to 6, 8, and 10 if you want a picture that will display brightly enough on most televisions.
Colors
The Atari 2600 can display up to 128 unique colors that is, 16 unique hues and 8 luminosityBrightness. levels for each hue for NTSCNational Television Systems Committee
COLUBK (Color-Luminosity Background) Sets the background color. Example: COLUBK = $80.
Write Only: COLUBK cannot be read. For example, a = COLUBK will not work properly.
COLUPF (Color-Luminosity Playfield, Ball) Sets the playfield color and ball color. Example: COLUPF = $CE. If you use pfscore bars, you'll need to have COLUPF in your main loop or the playfield will become the color of the pfscore bars. For a multicolored playfield, see pfcolors or the kernel_options chart.
Write Only: COLUPF cannot be read. For example, a = COLUPF will not work properly.
COLUP0 (Color-Luminosity Player0, Missile0) Sets the color for player 0 and missile 0. Example: COLUP0 = $0A. See Ephemeral Variables & Registers for more information.
Write Only: COLUP0 cannot be read. For example, a = COLUP0 will not work properly.
COLUP1 (Color-Luminosity Player1, Missile1) Sets the color for player 1 and missile 1. Example: COLUP1 = $FE. See Ephemeral Variables & Registers for more information.
Write Only: COLUP1 cannot be read. For example, a = COLUP1 will not work properly.
scorecolor Sets the score color so it will be visible. Example: scorecolor = $68. See score for more information.
Atari 2600 TIA Color Charts The color charts below are interactive. Click on a color and the hexadecimal color value will be displayed. (Based on Glenn Saunder's HTML TIA color charts.)
Related Link Includes an NTSC/PAL color conversion tool and Atari 2600 color compatibility tools that can help you quickly find colors that go great together (possibly saving you a lot of time and energy).
Did You Know? You will not be able to precisely duplicate sprites from classic games because bB uses double-height pixels. The double-height pixels were used because it gives more time in the kernel for features such as the asymmetric playfield.
The good news is that the new DPC+ kernel uses single-height sprite pixels, so you'll be able to recreate the more detailed classic look.
Enemy AI Links 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.
Objects Player Graphics
The Atari 2600 can display two player sprites, which are 8 pixels wide and any height. You access these sprites by using various reserved values and commands. To define a sprite, you use player0: and player1:
player0: %00100010 %01110111 %01111111 end
This will define a player0 sprite which is 3 blocks in height.
DAH Move Sprite Example Use the joystick to move the sprite.
Here's the .bin file to use with an emulator or Harmony cart:
Here's the bB code:
Missiles
Two missiles can be displayed on the screen. These are simply vertical missile0x = 64 missile0y = 64 missile0height = 8 missile1x = 64 missile1y = 80 missile1height = 8 Main_Loop COLUP0 = 140 COLUP1 = 28 NUSIZ0 = $30 NUSIZ1 = $30 drawscreen goto Main_Loop In the multisprite kernel, the missiles are fixed at one unit high, and the missile height variables do not exist. Also remember that one or both of the missiles can become unavailable depending on the kernel options you select. See kernel options for more information.
There are more things you can do with missiles by modifying the NUSIZx TIA registers. See NUSIZ0, NUSIZ1 for more information.
DAH Sprite with Missile Example Use the joystick to move the sprite. Press the fire button to shoot the missile.
Here's the .bin file to use with an emulator or Harmony cart: Sprite with Missile Example .bin file
Here's the bB code:
Ball
The ball is one of the objects that the Atari 2600 can display in the screen.
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.
DAH Sprite with Ball Example Use the joystick to move the sprite. Watch the ball bounce. Press the reset switch to reset the program.
Here's the .bin file to use with an emulator or Harmony cart: Sprite with Ball Example .bin file
Here's the bB code:
The Playfield
In the standard kernel, by default, you get a 32 x 11 bitmapped, asymmetric playfield (32 x 12 if you count the hidden row that is only seen if scrolled). Below is a standard kernel playfield variable chart for advanced users. There are 4 variables for each row (8 bits x 4 = 32).
Did You Know? The second and fourth playfield variables of each row have a reversed bit order. If you don't know they are reversed, you'd expect that "var0=1 : var1=1 : var2=1 : var3=1" or "var0=$01 : var1=$01 : var2=$01 : var3=$01" or "var0=%00000001 : var1=%00000001 : var2=%00000001 : var3=%00000001" would give you this:
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.
You can specify an entire playfield in one command by using the playfield: command. The syntax of the command is:
playfield: <off> <on> 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.
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.
const pfrowheight=7 If you do not want to be stuck using a single, shared row height, you can specify the height of individual playfield rows by setting the pfheights kernel option.
drawscreen
The drawscreen command displays the screen. Any objects with changed colors, positions or height will be updated. Internally, this command runs the display kernel.
Did You Know? Although vblank looks similar to a normal subroutine, you shouldn't try to jump to it using gosub. It is placed at the end of your program or in the last bank of a bank-switched game (outside of any loops) and runs automatically every time a drawscreen is called. If you're using bank switching, remember that vblank is special, so don't replace vblank's return with return otherbank.
vblank
Normally, bB code runs in overscan. This is the portion of the television screen, roughly 2 milliseconds long, after the visible screen but before the synchronization signals are sent to the television. After this is an area called vertical blank, where the picture is blanked for a couple of milliseconds before the display kernel renders the visible screen.
vblank pfscroll left return The code in vblank will be run automatically every time a drawscreen is called. You can specify vblank only once, and the code should be physically separate from the rest of your code (in other words, it's inadvisable to allow your regular bB code to 'run into' the vblank code). Since vblank is a subroutine, you need to end it with a return. And since you will not be calling the subroutine yourself, you do not need to use a label or line number.
Example: Main_Loop COLUBK = $C4 : rem * Green drawscreen goto Main_Loop vblank COLUBK = $44 : rem * Red return Although COLUBK is in the main loop trying to force the background color to be green, the color red in vblank overrides it.
vblank can be used in bank-switched games, but it must be placed in the last bank (bank 2 for 8K, bank 4 for 16K, and bank 8 for 32K games) and it must end with return, not return otherbank.
pfclear (playfield clear) Clears the playfield in the standard kernel or can optionally be used with an argument to fill playfield RAM with the specified value.
Example: pfclear The following example will completely fill the playfield RAM with binary value 10101010: pfclear %10101010 Warning: pfclear does not work with the Multisprite Kernel.
pfpixel (playfield pixel) This draws a single pixel with playfield blocks. Uses 80 processor cycles every frame. The syntax is:
pfpixel xpos ypos function ( 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 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 pfvline 0 0 10 on pfvline 31 4 8 off pfvline 8 2 4 flip Note that there is no checking if the bounds of the function are exceeded. If you do so, strange things may happen, including crashing your program.
pfscroll (playfield scroll) This command scrolls the playfield. It is useful for a moving background or other purposes.
Valid values are:
pfscroll left
Example: pfhline 8 5 23 on COLUBK = $C4 COLUPF = $CE Main_Loop if joy0up then pfscroll up if joy0down then pfscroll down if joy0left then pfscroll left if joy0right then pfscroll right drawscreen goto Main_Loop Using pfscroll left or right will use quite a few processor cycles every frame (500 cycles), so use it sparingly. Using pfscroll up or down uses 650 cycles every 8th time it's called, 30 cycles otherwise.
pfread function
pfread is used to determine whether an existing playfield pixel is on or off. It can only be used in an if-then statement at this time. You may use numbers, variables or arrays as arguments.
if pfread(10,8) then 20 if !pfread(a[x], b) then 40 If you are using the multisprite kernel, you can use a special pfread module, called pfread_msk.asm, made just for this kernel. It is not enabled by default. To enable it, you can use the include or inline command in a 2K or 4K game. For a bank-switched game, only the inline command will work, and the command must be placed in the last bank. For example: inline pfread_msk.asm The command will work in a similar manner to the standard kernel except that y-values are reversed. See inline for more information about this command and its proper use. Alternatively, you can create a custom includes file with this module placed such that it will live in the last bank.
score
The score keyword is used to change the score. The score is fixed at 6 digits, and it currently resides permanently at the bottom of the screen. Unlike other variables, batari Basic accepts values from 0-999999 when dealing with the score.
Score Background Color (by RevEng) If you'd like to have a separate background color for the thin strip where the score is located, RevEng at AtariAge figured out a way. Put the following in the last bank, or if you're not using bank switching, just put it at the end of your code, outside of any loops: asm minikernel sta WSYNC lda scback sta COLUBK rts end Then you can dim scback and use it to change the color behind the score whenever you want. The example below uses the variable z, but you can use any variable: rem **************************************************************** rem * Assign a variable to the score background. rem * dim scback = z rem **************************************************************** rem * Use scback whenever you need to change the score background. rem * scback = $C4 There is one thing you'll need to remember when using scback. COLUBK must be used inside of your main loop or the rest of the background will be the same color as the score background.
To change the score, some examples of valid operations are: score = 1000 score = score + 2000 score = score - 10 score = score + a
Be careful when using the last one. It will compile, but upon execution, a must always be a BCD compliant number. If a non-BCD number is in a, part of your score may end up garbled.
dim sc1 = score dim sc2 = score+1 dim sc3 = score+2 The example above does not use up any of your variables. Starting from left to right, sc1 holds the 100 thousands and 10 thousands digits, sc2 holds the thousands and hundreds, and sc3 holds the tens and ones. Since these are all BCD numbers, you need to place a "$" in front of any values you check. For example, to check if the score is less than 10: if sc1 = $00 && sc2 = $00 && sc3 < $10 then ... To check if the score is greater than 123456: if sc1 > = $12 && sc2 > = $34 && sc3 > $56 then ...
Saving a High Score (by supercat and Nukey Shay) Here's an example that would check for a new high score and save it: if sc1 > High_Score01 then New_High_Score if sc1 < High_Score01 then Skip_High_Score rem * First byte equal. Do the next test. if sc2 > High_Score02 then New_High_Score if sc2 < High_Score02 then Skip_High_Score rem * Second byte equal. Do the next test. if sc3 > High_Score03 then New_High_Score if sc3 < High_Score03 then Skip_High_Score rem * All bytes equal. goto Skip_High_Score New_High_Score High_Score01 = sc1 : High_Score02 = sc2 : High_Score03 = sc3 Skip_High_Score Below is an example program that shows how to save the high score until the game is turned off. Move the joystick up to increase the score. Press the fire button to leave the main loop and the score will flip between the current score and the high score every 2 seconds. Press the fire button or reset switch to go back to the main loop.
Here's the .bin file to use with an emulator or Harmony cart: Save High Score playable .bin file
Here's the bB code:
Breaking out score digits is also useful for setting the score to display special values or custom graphics. You can modify score_graphics.asm to include definitions for digits "A-F" and set the score digits manually. How to do this is beyond the scope of this document, but it has been discussed in detail in the batari Basic forum at AtariAge.
pfscore bars The area reserved for the score now also contains two 8-wide bars that can be used to display lives, health or other game information. This feature may be enabled by setting pfscore using const. Example: const pfscore=1
The value doesn't matter, as bB will simply look to see if pfscore is defined. This method will be replaced at a later date with the set command, which will give other control over the score. Although the const method will be deprecated then, it will continue to work.
pfscorecolor For example: Suppose you want to use the left pfscore bar for lives and the right one for health. To initialize, you might do: pfscore1 = 21 pfscore2 = 255 You might find that binary is more intuitive. The following example is the binary version of the example above: pfscore1 = %00010101 pfscore2 = %11111111 The above examples set the left bar for 3 separate dots (indicating lives) and the right bar will be full-width.
Lives Bar Examples To decrement a life, use this: pfscore1 = pfscore1/4 To increment a life starting at bit 1 (example: pfscore1 = %00101010), use this: pfscore1 = pfscore1*4|2 To increment a life starting at bit 0 (example: pfscore1 = %00010101), use this: pfscore1 = pfscore1*4|1
Health Bar Examples To decrement the health bar, use this: pfscore2 = pfscore2/2 To increment the health bar, use this: pfscore2 = pfscore2*2|1 When there are no lives left, pfscore1 is zero and when the health bar is empty, pfscore2 is zero.
You are not limited to using pfscore1 for lives and pfscore2 as a health bar. You can switch their purposes or have two sets of dots or two health bars. It depends on how you set them up and if you divide by 2 or 4. If the bar is filled, divide by 2. If the bar has dots, divide by 4. Below is a working example program that might be helpful.
DAH pfscore Lives and Health Example When the program starts, there will be two icons that stand for Lives and Health. Move the joystick left to choose the type of bar you want on the left and move the joystick right to choose the type of bar on the right. When you are done, press the fire button.
You will now see your choices near the bottom of the screen.
Left Bar: Move the joystick right to decrease and left to increase.
Right Bar: Move the joystick down to decrease and up to increase.
Hit the reset switch if you want to restart the program.
Here's the .bin file to use with an emulator or Harmony cart: pfscore Lives and Health Toy playable .bin file
Here's the bB code:
Note: You cannot use pfscore bars in conjunction with the HUDs that are currently available without hacking the source files because they use the same memory locations for their variables.
Warning: Remember that pfscore bars are not the same as the Life Counter and Status Bar minikernels. It might be confusing at first.
noscore If you wish to disable the score completely, you may do so by defining noscore. At this time, you can do that by: const noscore = 1 As with pfscore above, this method will be deprecated later when score options are integrated with the set command.
What is a BCD Compliant Number? BCD stands for Binary-coded decimal. In essence, it is a hexadecimal number represented as a decimal number.
For instance, $99 is the BCD number for decimal 99 and $23 is the BCD number for decimal 23. Although a dollar sign is used, the usual A, B, C, D, E, and F letter digits of a normal hexadecimal number are not used. For example, there is no BCD number for $3E since it contains a non-decimal value (the E). If 'a' contained $3E, the score would end up incorrect or garbled. The number must always look like a two-digit decimal number with a dollar sign on the left side of it.
BCD Compliant Numbers and Variables (by RevEng) A time may come when you'll need to add to a variable (a = a + 5), then add that variable to the score (score = score + a). It won't be long before the score will become incorrect or garbled. There is a simple fix for that provided by RevEng at AtariAge: asm sed ; set decimal mode end a = a + $05 asm cld ; clear decimal mode end score = score + a You can copy and paste that into your program and replace 'a = a + $05' and 'score = score + a' with whatever variable and value you want to use. Just remember that the number you add to your variable must be a BCD compliant number (looks like a two-digit decimal number with a dollar sign next to it).
Points Roll Up (by Nukey Shay) There's another solution from Nukey Shay at AtariAge that does not require a BCD compliant number, where the point value will roll up instead of instantly becoming a new total. Here's an example:
dim Debounce_Fire = d
dim Points_Roll_Up = z
dim sc1 = score
dim sc2 = score+1
dim sc3 = score+2
Main_Loop
scorecolor = $1A
if !joy0fire then Debounce_Fire{2} = 0 : goto Skip_Fire
if Debounce_Fire{2} then Skip_Fire
Debounce_Fire{2} = 1
a = a + 5
if a > 30 then a = 100
Points_Roll_Up = Points_Roll_Up + a
Skip_Fire
rem ********************************************************
rem * Points roll up code from Nukey Shay.
rem *
rem '
asm
lda Points_Roll_Up
beq No_Points_To_Add
dec Points_Roll_Up
sed
clc
lda sc3
adc #1
sta sc3
lda sc2
adc #0
sta sc2
lda sc1
adc #0
sta sc1
cld
No_Points_To_Add:
end
drawscreen
goto Main_Loop
The only drawback to the method above is that 'Points_Roll_Up' can never contain more than 255 points.
Warning: Do not try to use a BCD compliant number with the points roll up example above. It's unnecessary and it may give you an incorrect score. For example, 'a = a + $10' would add 16, not 10 because it would be used as a hexadecimal number instead of a BCD compliant number.
TIA Registers There are a few TIA registers that may be useful in batari Basic. This is not a complete list. I'm only mentioning the registers and functions therein that you will most likely find useful. You can learn more by visiting the Stella Programmer's Guide.
NUSIZ0, NUSIZ1
Changes the size and/or other properties of player0/1 and missile0/1.
Write Only: NUSIZ0 and NUSIZ1 cannot be read. For example, a = NUSIZ0 will not work properly.
CTRLPF
Changes properties of the playfield and/or ball.
Note that ball and playfield properties may be combined in a single write.
Write Only: CTRLPF cannot be read. For example, a = CTRLPF will not work properly.
DAH 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.
Here's the .bin file to use with an emulator or Harmony cart: Sprite/Playfield Priority Example .bin file
Here's the bB code:
Related Links 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
Reflects player sprites. (Flips them horizontally
This is useful for asymmetric spritesSprites that are irregular or lopsided. Not a perfect mirror image on both sides. so that they can give the appearance of changing direction without needing to redefine their graphics.
Write Only: REFP0 and REFP1 cannot be read. For example, a = REFP0 will not work properly.
PF0
Set or clear the left and right 10% of the playfield. PF0 must be inside of your game loop.
Write Only: PF0 cannot be read. For example, a = PF0 will not work properly.
More About PF0: The side areas are mirrored and each side can have up to four columns. PF0 is easier to deal with if you use a binary number. The first four digits in the number are the ones that matter. For example, PF0 = %11110000 will give you a thick side border (made of four columns) and PF0 = %10000000 will give you a thin side border right next to the playfield (made of one column).
Example with one column next to playfield: COLUBK = 132 COLUPF = 30 playfield: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX end Main_Loop PF0 = %10000000 drawscreen goto Main_Loop PF0 = %10000000 in the example above can be replaced with other combinations. Below are two examples.
Column farthest away from the playfield: PF0 = %00010000 Four columns: PF0 = %11110000
AUDV0, AUDC0, AUDF0, AUDV1, AUDC1, AUDF1 See sound for more information about these.
Did You Know? You need to place your drawscreen before the section of code that checks for collisions (the Atari 2600 has to draw the frame for the collision registers to get triggered).
Collision Detection if collision (object,object)
This function is used for checking if two objects have collided on the screen. Valid arguments are playfield, ball, player0, player1, missile0, missile1. The two objects can be specified in any order.
if collision(playfield,player0) then a = a + 1 if !collision(player0,missile1) then goto Rat_Hair_Soup
DAH Sprite with Ball, Missile, and Collision Example This example program has a sprite, a bouncing ball, a missile you can shoot, and a mix of real and fake collision detection. In other words, it uses a combination of 'collision prevention' and collision detection.
Use the joystick to move the sprite. Press the fire button to shoot the missile. Press the reset switch to reset the program and toggle between two screens.
Here's the .bin file to use with an emulator or Harmony cart: Sprite with Ball, Missile, and Collision Example .bin file
Here's the bB code:
Display kernel
The display kernelThe fundamental part of an operating system. (usually just "the kernel") is a carefully-written software routine that renders the television display. The Atari 2600's television signal is controlled largely by software because the hardware is so primitive. Although programming a 2600 kernel is quite difficult, the primitive hardware also affords a good deal of flexibility, in that custom kernels can exploit the primitive hardware to suit particular needs.
Multisprite Kernel
This is an entirely new kernel written from scratch that provides significant features that should be suitable for a wide variety of games.
includesfile multisprite_bankswitch.inc set kernel multisprite set romsize 8k
Other Limitations/Considerations
const screenheight = 80 At this time, the only supported values are 80 and 84, and 80 only works for row heights < 8 while 84 only works for row heights < 4. Other values for screenheight may be allowed in the future. If you try to use other values now, strange things may happen.
Minikernels/HUDs
Minikernels are a new feature of bB that allows for further customization of your game. Minikernels are intended for use as a HUD (Heads-up Display), which is the part of the game that shows lives, health, status, time, etc.
Life Counter / Status Bar Two minikernels are included with this release of bB. One contains a 'life counter' that can show up to six icons to represent player lives, and the lives have a variety of display configurations. The other minikernel contains a fixed, left-aligned life counter and a 28-unit 'status bar' (for lack of a better term, as the bar could be used to indicate health, time, power, speed, etc.)
inline 6lives.asm or inline 6lives_statusbar.asm
With 8K or larger games, you must place the inline command in the last bank since your game will look for the module there. See inline for more information about this command (and note the warning.) In a 4K game, you can use include instead of inline for minikernels.
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.
dim lives_centered = 1 dim lives_compact = 1
There are up to four variables that control 6lives_statusbar, and two in 6lives:
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.
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.
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.
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.
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.
Racing Timer (by SeaGtGruff) An example that displays an edited timer minutes in the 1st position, a colon in the 2nd position, seconds in the 3rd and 4th positions, a decimal point in the 5th position, and tenths of a second in the 6th position. That means the timer can go up to only 9:59.9 before wrapping around to 0:00.0, which should probably be okay for a racing game. The program will work for either NTSC/60 or PAL/50.
Custom Score Fonts (by RevEng) Various fonts stuck in a single score_graphics.asm file. The font is selectable from within your bB code by setting the fontstyle constant.
AtariVox Support for bB (by RevEng)
Compiler Directives (set command)
The set command tells the compiler or the assembler to build your code a certain way. The syntax is:
Currently, the following directives are supported:
smartbranching Typically you will use the directives near the beginning of your program. However, smartbranching and optimization were originally designed to be set, reset, and/or turned on and off throughout your code (though I'm not sure if this actually works :| )
smartbranching The smartbranching directive tells the compiler to generate optimal assembly code when it encounters an if-then statement with a line number or label after the then. Smartbranching is set to off by default, because this makes the generated assembly code easier for a human to read and understand.
Normally, an if-then statement with a line number or label after the then can only jump forward 127 bytes or backwards 128 bytes, so you'd need to use then goto for longer jumps. Thanks to smartbranching, you usually won't have to worry about that. If you set smartbranching on and just use then, the compiler will figure out whether to use then or then goto for you. The drawback to doing this is that the generated assembly file will be much harder for a human to read. If you don't care to see the assembly language that bB generates, however, you should set smartbranching on. Smartbranching will not slow down your program, so you don't have to be afraid to use it.
You can also use then goto all of the time and not have to worry about smartbranching at all, but then goto uses more code space than just then so you should take this into consideration as well. See Branches out of Range for more information.
Warning: Smartbranching will sometimes cause the compilation to fail. In this case, you will need to use then goto instead of just then for the if-then statement that caused the problem.
tv
Specifies the tv format. Valid options are ntsc or pal. ntsc is the default and is unnecessary.
romsize
Allows you to specify the size of the generated binary. 4k is the default. Currently you may generate 2k, 4k, 8k, 16k or 32k binaries. Anything 8k or larger will use bank switching, and additional considerations are needed for this. See Bank Switching for more information. Append SC (capitalized) on the end to enable Superchip RAM (valid for 8k or larger binaries only.)
set romsize 2k set romsize 4k set romsize 8k set romsize 16k set romsize 32k set romsize 8kSC set romsize 16kSC set romsize 32kSC
optimization
Tells the compiler to try to generate code that runs faster or has a smaller size. There are five options: speed, size, noinlinedata, inlinerand, and none.
speed
Examples:
set optimization speed set optimization inlinerand
kernel
Determines which kernel to use with your game. Currently there are 28 kernels available. There is a multisprite kernel, a standard kernel, and the remaining 26 of them are technically distinct kernels but are available as options in the standard kernel. The standard kernel is, well, standard, so no directive is needed to use this.
set kernel multisprite See multisprite kernel for more information about the multisprite kernel.
kernel_options
These are options for customizing the kernel for various needs. Generally, adding options will mean that some objects will not be displayed. Currently the options only apply to the standard kernel.
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.
The order of the options doesn't matter, but there are limitations as to which options can be used alone and/or together:
Acceptable singles:
Using kernel_options readpaddle Specify paddle to read in currentpaddle (0-3) then value will be returned in paddle after a call to drawscreen.
Value returned should range from about 0-80 which isn't large enough to span the screen since the players' horizontal
DAH readpaddle Example Move the paddle as you would with any Breakout style game.
Here's the .bin file to use with an emulator or Harmony cart: readpaddle Example playable .bin file
Here's the bB code:
If you use Stella, the Atari 2600 emulator, and it seems like your paddle game isn't working, you'll need to run your game, press the Tab key on your keyboard, click the Game Properties button, click the Controller tab, change both controllers to Paddles, click the OK button and close Stella. Run your game again and it should work properly. If you make changes to your program, you'll have to do the same thing again, so get used to it.
If you are working on a game that uses two paddles at the same time, you'll probably need to change Swap Paddles from No to Yes under the Controller tab so you'll be able to test the other paddle.
Note: Must be used with no_blank_lines.
player1colors and playercolors Specifies player colors a row at a time. When player1colors is used, missile1 is lost and when playercolors is added, missile0 is also lost. You have to decide if multicolored sprites are worth losing the missiles.
playercolors allows both players to be multicolored, while player1colors only allows this for player 1. Remember that playercolors cannot be set by itself; player1colors must also be set.
Use player0color: to define the colors for the player0, and player1color: for player1.
Example: player0color: $f5 $f5 $f5 $43 $f5 end
Note that changing player colors also affects the missile colors during the scanlines where the colors change.
no_blank_lines Gets rid of those irritating gaps between rows of playfield blocks. Problem is that missile0 is lost and there are very few kernel options that you can use with it. You can have one multicolored sprite and/or a multicolored playfield and that's about it. See the kernel_options chart for more information.
If you use no_blank_lines with pfcolors, each playfield pixel will have a thin layer of color on top from the row above it (like icing on a cake). This happens because the graphics data for the next line is loaded and stored before the color data for the next line, so the color of the previous line carries over at the top of the new line. It might be undesirable for some games, but you can use the 'icing' to your advantage and have a high-resolution look with thin lines for various types of games. See the Fake Gravity Platformer Test and the Gyvolver work in progress by Random Terrain.
pfcolors Specifies colors of each playfield row. Must specify 11 or more values, one to a line, followed by end. You can specify this as many times as you want.
Top row bug fix: There seems to be a bug where the top row will not be the color you selected if pfcolors is outside of your main loop. There are two easy fixes. You can either put pfcolors inside your main loop or put COLUPF in your main loop and have it be the color of your top row. If no_blank_lines is used, the top row color will be correct, but the bottom row color will be wrong. Just use 12 colors instead of 11 and make the 12th row the same color as the 11th row.
If you use the kernel option background, COLUPF won't work and neither will putting pfcolors inside of the main loop (unless no_blank_lines is also used). If you're not using no_blank_lines, you'll need to use COLUBK. Put COLUBK in your main loop and have it be the color of your top row. If no_blank_lines is used with background, the top row color of your pfcolors definition will be ignored and the bottom row color will be wrong. You'll need to use 12 or 13 colors instead of 11 and play with the color placement until it looks right.
PF0 color taint fix: If you use PF0 to have a border on the sides of the playfield, you might notice that the bottom of the border has been tainted with another color. Instead of using 11 colors for pfcolors, use 12 and make the 12th row the same color as the 11th row. No more color taint. pfcolors: $f5 $f5 $f5 $43 $f5 $f5 $f5 $f5 $f5 $f5 $f5 $f5 end Warning: When using pfcolors and pfheights together in the same game, you can only define pfheights: and pfcolors: one time, and you need to define pfheights first.
pfheights Specifies heights of each playfield row. You must specify 11 values, one to a line, followed by end.
The default height is 8. The total should not exceed 88 or you will cut into the time available for your bB program. Specifying less than 88 is fine, but doing so will shrink the size of the visible screen. You can specify pfheights: as many times as you want.
pfheights: 8 8 15 1 8 8 8 8 8 8 8 end Warning: When using pfcolors and pfheights together in the same game, you can only define pfheights: and pfcolors: one time, and you need to define pfheights first.
background Have a multicolored background instead of a multicolored playfield using pfcolors data. Normally, pfcolors changes the color of the playfield pixels by row, but background forces the data into the background, so you can have rows of different colors in the background and the usual single color playfield pixels on top of it all.
Warning: If you don't use no_blank_lines with background, timing issues will cause a stairstep effect (the rows will not be perfectly straight).
debug
Used to help find bugs in your bB program. Currently it only helps with determining when too many machine cycles are used in a particular frame.
cycles set debug cycles
cyclescore 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.
legacy
Version 1.0 of bB has several new features and has been optimized such that code written for earlier versions may not work quite right. To retain more compatibility with old code, a legacy mode has been implemented that will (hopefully) allow the games to work properly without extensive modifications.
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.
Bank Switching (Up to 32K of ROM for your games!)
The Atari 2600 can only address 4K at a time. Games larger than 4K are possible by additional hardware on the cartridge that can swap in two or more banks of 4K. Since the 2600 can only 'see' 4K at once, the 2-8 banks also can't see each other; that is, one bank cannot access data in another bank. Also, since an entire bank needs to be swapped in all at once, normally this would make programming somewhat more difficult.
DAH Bankswitching Example Besides being a working example of bankswitching, this example program shows different parts of a game. There is a fake title screen, a fake game, and a game over screen with an initial 2 second freeze.
End the 'game' by touching a wall with the sprite. Pressing the reset switch during the 'game' will take you back to the fake title screen. Pressing the reset switch or the fire button while on the GAME OVER screen will restart the 'game' and skip the fake title screen. If you do nothing for 20 seconds while on the GAME OVER screen, you'll go back to the fake title screen.
Here's the .bin file to use with an emulator or Harmony cart: Bankswitching Example .bin file
Here's the bB code:
Remember, specifying "bank 1" is unnecessary.
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.
Superchip RAM
The Superchip, also called the SARA chip, provides 128 additional bytes of RAM. Superchip RAM is only used in conjunction with bank switching.
set romsize 8kSC
This would enable Superchip RAM with 8K bank switching. See compiler directives for more information.
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.
Optionally, since we have extra RAM for the playfield, it can have greater vertical 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.
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.
Sound There are no special functions for accessing sound in batari Basic. Instead, you must access the TIA registers for sound directly. Don't panic, the TIA registers for sound are quite friendly, at least as far as that damn TIA goes.
Channel 0 AUDV0 Audio Volume for Channel 0 (valid values are 0 to 15)
Write Only: AUDV0 cannot be read. For example, a = AUDV0 will not work properly.
AUDC0 Audio Control for Channel 0 [Also known as Tone, Voice, and Distortion] (valid values are 0 to 15)
Write Only: AUDC0 cannot be read. For example, a = AUDC0 will not work properly.
AUDF0 Audio Frequency for Channel 0 (valid values are 0 to 31)
Write Only: AUDF0 cannot be read. For example, a = AUDF0 will not work properly.
Channel 1 AUDV1 Audio Volume for Channel 1 (valid values are 0 to 15)
Write Only: AUDV1 cannot be read. For example, a = AUDV1 will not work properly.
AUDC1 Audio Control for Channel 1 [Also known as Tone, Voice, and Distortion] (valid values are 0 to 15)
Write Only: AUDC1 cannot be read. For example, a = AUDC1 will not work properly.
AUDF1 Audio Frequency for Channel 1 (valid values are 0 to 31)
Write Only: AUDF1 cannot be read. For example, a = AUDF1 will not work properly.
Setting the values, for instance, by AUDV0 = 10 : AUDC0 = 12 : AUDF0 = 4 will produce a tone, and it will stay on until you set AUDV0 = 0. Typically, a frame counter is set up that keeps sounds on for a specified number of frames (which occur 60 times a second).
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.
DAH Sound Example Using Data 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'.
Here's the .bin file to use with an emulator or Harmony cart: Sound Example Using Data .bin file
Here's the bB code:
DAH 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'.
Here's the .bin file to use with an emulator or Harmony cart: Sound Example Using Data and Bankswitching .bin file
Here's the bB code:
DAH Sound Example Using Data (One Sound Only) Press the fire button to hear part of the drums from Skate Boardin'.
Here's the .bin file to use with an emulator or Harmony cart: Sound Example Using Data (One Sound Only) .bin file
Here's the bB code:
Joysticks
Joysticks are read by using an if-then statement. There are four directional functions and one fire function for each joystick.
Example: if joy0up then y = y - 1 if joy0down then y = y + 1 if joy0left then x = x - 1 if joy0right then x = x + 1 These can also be inverted using the NOT ( ! ) token. For example: if !joy0up then 230
Left Joystick if joy0up True if left joystick is pushed up.
if joy0down True if left joystick is pushed down.
if joy0left True if left joystick is pushed left.
if joy0right True if left joystick is pushed right.
if joy0fire True if left joystick's fire button is pushed.
Right Joystick if joy1up True if right joystick is pushed up.
if joy1down True if right joystick is pushed down.
if joy1left True if right joystick is pushed left.
if joy1right True if right joystick is pushed right.
if joy1fire True if right joystick's fire button is pushed.
The position of both joysticks is stored in an IO memory register of the RIOT labeled SWCHA. When a bit in SWCHA is 0, the joystick is pushed in that direction. There is one bit for each direction Right, Left, Up, and Down. Mechanically, a joystick has only 9 valid positions. The four joystick direction bits can form 16 combinations. Only 9 of those 16 combinations are valid joystick positions. The other seven are invalid (broken joystick). A good program will ignore the invalid positions. Some Atari games do not ignore the invalid positions. In those games, pressing invalid combination will cause illegal player movement such as passing through walls.
Instead of using a long list of if-then statements, an on gosub or on goto statement can be used. For Joy0, we read the value in SWCHA and divide it by 16 which discards the Joy1 bits and shifts the joy0 bits into the lower 4 bit positons, so we'd use temp1 = SWCHA / 16. For Joy1, temp1 = SWCHA & %00001111 would be used.
It may be surprising, but the SWCHA method is often less efficient in terms of cycles and space than if-thens. There is a point at which the on goto with SWCHA will become more efficient. For example, if you are doing something different for up-left than for up and left individually, such as a diagonal sprite, SWCHA may be the better choice.
Bare bones example .bas file: Advanced Joystick Reading with SWCHA (Bare Bones)
Example .bas files: Joystick Reading with SWCHA using on gosub Joystick Reading with SWCHA using on goto
2-Button Games for use with Sega Genesis Controllers RevEng created a little bB program that demonstrates how to read the B and C buttons on a Sega Genesis compatible controller. Great news for bB programmers who are in need of an extra button. For example, platform games can now have a fire button and a jump button.
Why bother? Third party Sega Genesis controllers are inexpensive and work without modification on the Atari 2600, making them a popular choice for those who like gamepads. On top of that, they're trivial to support in your code.
Stella, the Atari 2600 emulator, emulates Sega Genesis compatible controllers, so now you can make two button games without worrying that emulator uses won't be able to play them. When testing your two-button work in progress, just run it as usual, hit the Tab key to bring up the menu, click the Game Properties button, click the Controller tab, set P0 Controller to Sega Genesis, click the OK button, close Stella, then run your program again. Now your work in progress will be able to use two fire buttons instead of one. The first button is tied to the normal joystick button, so it will be the Space key, left Control, USB controller button, or whatever you have that mapped to. The second button is tied to the BoosterGrip Booster button, which by default is the 5 key. This can be remapped if you wish by pressing the Tab key, clicking the Input Settings button, and remapping P0 BossterGrip Boster to whatever key or USB controller button you'd like to use.
The demo program by RevEng can be downloaded at AtariAge.com.
Console Switches Reading the console switches is done by using an if-then statement.
if switchreset True if Reset is pressed. See reboot if you would like to use switchreset to warm boot your game.
if switchbw True if the COLOR/BW switch is set to BW, false if set to COLOR.
if switchselect True if Select is pressed.
if switchleftb True if left difficulty is set to B (amateur), false if A (pro).
if switchrightb True if right difficulty is set to B (amateur), false if A (pro).
For example, these are accessed by: if switchreset then 200 These can all be inverted by the NOT (!) token: if !switchreset then 200
Did You Know? If you have a Windows computer, did you know that the calculator in your Accessories folder can convert decimal, hex, and binary numbers? Just select Scientific under the View menu and you can convert any decimal, hex, or binary number with a simple click of a mouse button. Or you can use this online tool.
Numbers Decimal Numbers
Numbers in batari Basic are assumed to be in decimal unless otherwise specified by either the $ (for hexadecimal) or the % (for binary).
If you'd like to find out if a variable's value is odd or even, just check bit 0. If bit 0 is on, the number is odd.
Example:
if a{0} then odd
COLUPF = $2E a[$12] = $F5
Binary Numbers
Sometimes it is convenient to express numbers as binary instead of decimal or hexadecimal. To express numbers as binary, place the % before a number. Make sure that you define all 8 bits in the byte. The % operator is particularly useful for accessing certain TIA registers that expect individual bits to be set or cleared, without needing to first convert the numbers to hexadecimal or decimal first. The % operator is also useful for defining player sprites.
CTRLPF = %00100010 player0: %00100010 %11100111 end
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."
Negative Numbers
Negative numbers are somewhat supported. Although variable values can contain 0-255, the numbers will wrap if 255 is exceeded. Therefore, you can think of numbers from 128-255 as being functionally equal to -128 to -1. This is called "two's compliment" form because the high bit is set from 128-255, so this high bit can also be called the "sign."
In other words, adding 255 to a variable has exactly the same effect as subtracting 1.
Warning: Assignment to negative numbers will not work properly with certain versions of the assembler (DASM). Versions of bB prior to 1.0 were packaged with DASM 2.20.10, which doesn't process negative numbers correctly. Version 2.20.07 of the assembler does work, and so this has been packaged with bB version 1.0.
Did You Know? If you have a Windows computer, did you know that the calculator in your Accessories folder can convert decimal, hex, and binary numbers? Just select Scientific under the View menu and you can convert any decimal, hex, or binary number with a simple click of a mouse button. Or you can use this online tool.
Math
Full expression evaluation for integerAny number without a decimal (positive, negative or zero). A whole number. math is supported by batari Basic. It evaluates expressions as efficiently as possible by using the stack when needed to perform intermediate calculations, so no additional space is needed in most cases.
a = e-4*2 a = (e-4)*2 a = (e-4)*2*(myarray[4]-(4+c[r]))+2|e Warning: Using complex expressions might result in a localized overflow (that is, an intermediate value that exceeds 255 or is less than zero) if sub-expressions are not well-constructed or variables allowed to get too large (or small, as the case may be.) Also, complex statements, functions, subroutines, and bank switching all use the stack, so using excessively complex statements in a nested function or subroutine may overflow the stack into the variable space, causing weird bugs. However, stack overflows are still considered to be somewhat unlikely.
Addition
The + operator is used for addition. You may use any combination of registers, variables, unsigned values from 0-255 or signed values from -128 to 127 (see also negative numbers).
a = a + 1 COLUPF = r + $5F player0y = player1y + 6 + temp1
Cycle Count by SeaGtGruff a = b + c = 11 cycles
Subtraction
The - operator is used for subtraction. You may use any combination of registers, variables, unsigned values from 0-255 or signed values from -128 to 127 (see also negative numbers).
a = a - 1 COLUPF = r - $5F player0y = player1y - 6 - temp1
Multiplication Multiplication of two integerAny number without a decimal (positive, negative or zero). A whole number. numbers is supported by bB. However, some multiplication operations require you to include a module. In particular, multiplying two variables together requires the module, as does multiplication of a variable or by certain constants greater than 10 (see explanation below). Multiplication of a variable to a number 10 or less does not require you to include a module.
Explanation: Multiplication is generally a slow routine requiring lots of precious machine cycles, but bB tries to optimize for speed based on known methods. The concise but technical answer is: If the multiplicand's prime factorization contains only 2, 3, 5, and/or 7, no module is needed, otherwise the module will be needed. If you're not sure or don't want to do figure this out every time, you can try to compile without the module, and if compilation fails, look for "mul8" or "mul16" in the list of unknown symbols.
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.
Division
Division of two integerAny number without a decimal (positive, negative or zero). A whole number. numbers is supported by bB with some limitations. Some division operations require you to include a module. In particular, dividing two variables requires the module, as does division by any number except a power of two, in other words, dividing by 2, 4, 8, 16, 32, 64, or 128 can be done without the module. You can divide by 1 as well, but I can't imagine why you would want to. If you try to divide by zero, no operation will occur and your program will continue to run.
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.
Cycle Count by SeaGtGruff a = b / 3 = 38 cycles a = b / 2 = 8 cycles
Modulus Operation Other languages have a "modulus" operator, typically "%", that will return the remainder from a division operation. Although bB has no such operator, you can still get the remainder by doing a division using the // operator. See division above for more information.
Functions
A simple interface is provided for you to define your own functions in bB. These functions can be defined within your program itself or compiled to their own separate .asm file then included with the include command. Functions can be written in batari Basic or assembly language.
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.
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.
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.
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:
macro (by SeaGtGruff) The macro statement lets you define a keyword that represents other programming statements. You can use parameters in a macro's definition by putting numbers inside curly brackets. Use '{1}' for the first parameter, '{2}' for the second parameter, '{3}' for the third parameter, and so on. Use an 'end' statement to terminate a macro's definition.
For example, the following macro defines 'setscreencolors' as a keyword that can be used to set the background color, playfield color, and score color:
macro setscreencolors
COLUBK={1}
COLUPF={2}
scorecolor={3}
end
callmacro The callmacro statement lets you call a macro after you've defined it. To pass parameters to the macro, follow the name of the macro with the variable names or values you want the macro to use. Put a space before each parameter, but don't include commas.
For example, the following statement calls the 'setscreencolors' macro that was defined above: callmacro setscreencolors $02 $46 $1C When the batari Basic compiler sees this statement in your program, it replaces the 'callmacro' statement with the macro's code, inserting the parameters in the order they were listed, as follows: COLUBK=$02 COLUPF=$46 scorecolor=$1C
Combining macro, callmacro, and def Although 'macro' essentially lets you create a new instruction of your own, it doesn't look much like a new instruction with the 'callmacro' keyword in front of it. But you can use 'def' to make a keyword that represents the 'callmacro' statement. For example: def scolors=callmacro setscreencolors scolors $02 $46 $1C Now it looks more like a new instruction!
See Improved Nybble Variables to view a working example of macro, callmacro, and def.
More Examples of Using Macros The following example contains a few more macros that could be useful:
macro setplayercolors
COLUP0={1}
COLUP1={2}
end
def pcolors=callmacro setplayercolors
macro setplayer0xy
player0x={1}
player0y={2}
end
def p0xy=callmacro setplayer0xy
macro setplayer1xy
player1x={1}
player1y={2}
end
def p1xy=callmacro setplayer1xy
player0:
%00010000
%00101000
%01000100
%10000010
%01000100
%00101000
%00010000
end
p0xy 20 40
player1:
%11111111
%10000001
%10000001
%10000001
%10000001
%10000001
%11111111
end
p1xy 50 30
loop
pcolors $0E $C6
drawscreen
goto loop
You can even create macros that contain if-then statements, for-next statements, and other complex code, or call a macro from inside another macro. For example:
macro movesprite
if joy0left then {1}x={1}x-1
if joy0right then {1}x={1}x+1
if joy0up then {1}y={1}y-1
if joy0down then {1}y={1}y+1
end
callmacro movesprite player0
In this example, 'callmacro movesprite' will get replaced by the four if-then statements of the 'movesprite' macro, and '{1}' will get replaced by the name of the sprite, so player0 will get moved around by joystick0. If you change it to 'callmacro movesprite missile1', it will move missile1 around instead of player0.
As you can see, macros can help make it easier for you to write your program code. However, macros could also make it harder for someone else to understand your program. For example, if someone sees 'p0xy 20 40' in your program, first they would need to look through your code to find where you defined 'p0xy' to mean 'callmacro setplayer0xy', and then they would need to look further to find where you defined the code for the 'setplayer0xy' macro. On the other hand, that's not much different than seeing a statement like 'gosub moose_tracks' and then having to look for the 'moose_tracks' subroutine to see what it does.
Note: In bank-switched games, you can't use any commands that use a library inside a macro (pf* commands, multiplying by numbers that aren't powers of two, etc.).
Assembly Language Links Instead of reposting the same links here, check out the useful Assembly Language Links on one of the Andrew Davie tutorial pages.
Assembly Language asm
Use the asm statement to insert inline assembly language into your program. You do not need to preserve any register values when using this feature, except the stack pointer. Mnemonics should be indented by at least one space, and labels should not be indented.
asm ldx #47 lda #0 playfieldclear sta playfield,x dex bne playfieldclear end You may also access any variables from assembly that are defined in batari Basic. For example, another way to express the statement a = 20 is: asm lda #20 sta a end
include Did You Know? It seems incude should come before includesfile in your code or it may not compile.
Example: include div_mul.asm includesfile bankswitch_SC.inc set romsize 8kSC
Used to include modules in the final binary that are not normally available. You may also include additional modules using the includesfile command, but you may prefer to use include if you just want an extra module or two to be compiled in addition to what is already in the default includes file.
include fixed_point_math.asm
at the beginning of your program instead of (or in addition to) creating a new includes file, and this will allow you to share your source without also needing to attach your includes file as well.
includes file
An includes file contains the filenames of all modules that will be included in the final binary. Modules may be general routines, functions or custom display kernels. The includes file also specifies the order in which they will appear (this is crucial.) The default includes file (default.inc) contains the standard kernel and some of the more commonly used modules.
includesfile myincludes.inc
You do not need to use the includesfile command to use the default.inc file.
inline
This command works similar to include, as it is used to include an .asm file in your program. But unlike include, inline allows you to specify exactly where an asm module will go. This is useful when you want to use inline asm code from an external file in your game without copying it into your code. inline is also useful for inserting a minikernel into your game.
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.
Atari 7800
The Atari 7800 is reverse-compatible with nearly 100% of Atari 2600 games. All bB games should run on the 7800. However, games that use the COLOR/BW switch may have problems. On the 7800, the switch is meant to be used as a PAUSE switch. On the 2600 this is a toggle or rocker switch, but on the 7800 it is a momentary pushbutton.
Below is an example program by RevEng: rem ** 7800 detection won't work with the Harmony cart in menu mode rem ** since the menu program changes these memory locations. dim atari7800 = z rem ** This has to happen before a drawscreen or subroutine, or anything rem ** else that may obliterate temp1... atari7800 = temp1&1 rem ** Uncomment the line below to test 7800 pausing in stella. rem ** Changing from color to BW will pause. rem ** Switching back to color will remain paused. rem ** Changing from color to BW will unpause. rem ** Switching back to color will will remain unpaused. rem atari7800 = 1 rem ** For a 2600, atari7800 is now 0. For a 7800, atari7800 is now 1. scorecolor=$0A player0: %01111110 %11000011 %11000011 %00000000 %00000000 %01100110 %01100110 end player0x = 40 : player0y = 40 __Main_Loop COLUBK = 0 COLUP0 = $0A player0x = player0x + 1 drawscreen rem ** 2600 pause handling... if atari7800 = 0 && switchbw then goto __Pause_Game rem ** 7800 pause handling... if atari7800 = 1 && switchbw then atari7800 = %00000011 : goto __Pause_Game if atari7800 > 1 then if !switchbw then atari7800 = 1 goto __Main_Loop __Pause_Game COLUBK = $A4 COLUP0 = $0A drawscreen rem ** This code should be at the bottom of the pause loop. rem ** 2600 pause handling... if atari7800 = 0 && !switchbw then goto __Main_Loop if atari7800 = 0 then goto __Pause_Game rem ** 7800 pause handling... if atari7800 = %00000011 && switchbw then goto __Pause_Game atari7800 = %00000111 if switchbw then got |