|
|
|
|
|
|
batari Basic Commands
|
|
|
Index Advanced Joystick Reading (SWCHA) Arithmetic Operators (+, -, *, /) Ephemeral Variables & Registers 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) player1colors (kernel_options) Reflect Player Sprites Horizontally screenheight (Multisprite Kernel) Software Versioning Here's an unofficial tip from me, Random Terrain (Duane Alan Hahn), about software versioning. When I work on a program, each time I make a significant change, I save it as a new file. I don't have time to figure out what version number it should be (1.2.0.1? Huh?), so I simply put the year, month, day, and military time. Here's an example:
kasploder_2010y_07m_16d_1428t
I changed the system clock on my computer to military time, so all I have to do is look down in the corner. No more complicated version numbers, just the fast and easy date and time. To be clear, I use the program name, 4 digits + y for the year, 2 digits + m for the month, 2 digits + d for the day, 4 digits + t for the time, and underscores that separate each part.
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: 2008y-06m-13d-0244t 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. 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
Warning: Semicolons cannot be used with sdata at this time. The ability will be added in a future version of batari Basic.
reboot
This command will warm boot your game. reboot will clear everything similar to turning the Atari 2600 off and back on again, so do not use it if you need to store things such as the highest level reached, high score, current game selections and so on. It also seems that reboot causes random numbers to be not so random anymore, so if randomness is important to you, do not use reboot.
if switchreset then reboot See switchreset for more information.
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.
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 scorecolor = $1A rem ***************************************************** rem * Your code to test goes here. rem ***************************************************** 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
dim (create descriptive names for variables/assign names to fixed point types)
Used to create more descriptive names for the 26 variables, or to create a fixed-point type and assign it to some of these variables.
Using dim to Create More Descriptive Names
Unlike other Basics, the most common use of the dim statement is not for arrays in batari Basic, but rather for creating an alias, a more descriptive name for each variable than a-z. The statement simply maps a descriptive name to any of the original 26 variables.
dim monsterxpos=a dim monsterypos=b Note that more than one alias may be mapped to the same variable. This is useful for when you will inevitably need to reuse variables in multiple places.
Using dim to Map Variables to Fixed Point Types Although you can use the variables a-z as integers without ever using dim, you cannot use a-z as fixed point variables without using dim. See fixed point variables for more information.
Bit Operations (squeeze more out of your variables) 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.
For example, to access the LSB in a variable or register:
a{0} = 1
a{0} = 0
if a{0} then gosub moosegizzard
if !a{0} then gameover
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 ().
d{3}=r{4}
f{5}=!f{5}
dim my_variable = a{1}
dim my_variable{1} = a
dim my_variable{1} = a{1}
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.
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.
Nybble me this, Batman!
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.
rem ******************************************************************** rem * Nybble me this, Batman! rem ******************************************************************** rem * Variable "a" will be used to store two nybble values in this example. rem * You need to include div_mul.asm for this. include div_mul.asm rem * Store the two values in "temp5" and "temp6," just for now. temp5 = 5 temp6 = 10 rem * Use multiplication and addition to set "a." rem * The "temp5" value will go in the high nybble, rem * and the "temp6" value will go in the low nybble. a = 16 * temp5 + temp6 rem * Now clear "temp5" and "temp6." temp5 = 0 temp6 = 0 rem * Here's how to retrieve the two nybbles: temp5 = a / 16 temp6 = a & %00001111 rem * Now let's use the score to display them: score = 0 if temp5 > 0 then for temp4 = 1 to temp5 : score = score + 1000 : next if temp6 > 0 then for temp4 = 1 to temp6 : score = score + 1 : next COLUBK = $00 scorecolor = $1A loop_1 drawscreen if !joy0fire then loop_1 rem * Here's how to change just the high nybble (to 3): a = a & %00001111 a = a | 16 * 3 rem * Here's how to change just the low nybble (to 6): a = a & %11110000 a = a | 6 rem * Now get and display the new values: temp5 = a / 16 temp6 = a & %00001111 score = 0 if temp5 > 0 then for temp4 = 1 to b : score = score + 1000 : next if temp6 > 0 then for temp4 = 1 to temp6 : score = score + 1 : next loop_2 drawscreen goto loop_2 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. They are tokenized as:
a = a & $0F a = b ^ %00110000 a = a | 1
Fixed Point Variables
Fixed point variables can be assigned to fractional values, similar to floating point variables in other languages. bB provides two fixed point types:
dim myvar=a.b dim monsterx=d.r
The first will use the variable a as the integer and b as the fraction, the second will use d as the integer and r as the fraction. Then any time you use myvar or monsterx in a variable assignment, the compiler will know to use the 8.8 fixed point type.
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.
Ephemeral Variables and Registers (short-lived)
By ephemeral, we mean variables or TIA registers that are routinely obliterated through normal functioning of bB. Note that this section is not yet complete.
Constants (variables with a fixed value)
To declare a constant in batari Basic, use the const command. const declares a constant value for use within a program. This improves readability in a program in the case where a value is used several times but will not change, or you want to try different values in a program but don't want to change your code in several places first.
const myconst=200 const monsterheight=$12
After that, any time myconst or monsterheight is used, the compiler will substitute 200 or $12 respectively.
Labels and Line Numbers
Alphanumeric labels 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 mysubroutine
In bank-switched games, if you are jumping to a routine in another bank, you must specify the bank.
goto movemonster bank2
on goto
This works similar to a case statement in other languages. It is useful for replacing multiple if-then statements when conditions happen in an ordinal fashion.
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 Red2 bank2 Green goto Green2 bank2 Blue goto Blue2 bank3 Purple goto Purple2 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 mysubroutine if x > 10 then gosub sinkship To return control back to the main program, issue a return in your subroutine. Example: mysubroutine a = a - 1 x = x + 10 return
Note that each gosub will use two bytes of stack space, which will be recovered after a return. Only 6 bytes of stack space are reserved for this, so do not use too many nested subroutines, especially since this may be changed in later versions.
gosub Move_Monster bank2
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 (&&) and OR (||). The NOT ( ! ) operator may only be used with statements that do not include OR (||) or a comparison token (such as =, <, >, or <>). See Boolean Operators for more information. 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 obfuscate 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
if x=4 then
x=t-2 : mosterheight=(monsterheight&7)+1
pfhline 3 4 x off : a=(rand&127)+16 : r{0}=1
gosub KillMonster
endif
You'd use this:
if x<>4 then Skip01
x=t-2 : mosterheight=(monsterheight&7)+1
pfhline 3 4 x off : a=(rand&127)+16 : r{0}=1
gosub killmonster
Skip01
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 Skip02 r=4 Skip02
And this: if !joy0up || r<4 then e=e+1 becomes this: if joy0up && r>=4 then Skip03 e=e+1 Skip03
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 every time 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 'bad guys,' 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, below is a list that might be useful:
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 first and last numbers. For example, a = (rand&7) + 8 would give you a random number between 8 (0 + 8 = 8) and 15 (7 + 8 = 15).
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.
Data Statements and Arrays (read-only) Did You Know? All data tables, regular or sequential, must be located in the same bank where they are read. If you try to access data in another bank, there will be no errors, but the data you get will certainly be incorrect.
For convenience, you may specify a list of values that will essentially create a read-only array in ROM. You create these lists of values as data tables using the data statement. Although the data statement is similar in its method of operation as in other Basic languages, there are some important differences. Most notably, access to the data does not need to be linear as with the read function in other Basics, and the size is limited to 256 bytes.
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.
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 Note again that these data tables are in ROM. Attempting to write values to data tables with commands like mydata[1]=200 will compile but will perform no function.
Sequential Data (or large data)
The sdata statement will define a set of data that is accessed more like the data in other Basics. The 256-byte limitation is also removed the effective length is only limited by the size of each bank (4K). Sequential data is useful for things like music or a large set of scrolled playfield data. There is also no need to specify a pointer into the data.
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.
Colors 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 classic televisions and some of the darker grays may appear to be black. For example, if you are using 2, 4, and 6 bump it up to 6, 8, and 10 if you want a picture that will display brightly enough on most televisions.
The Atari 2600 can display up to 128 unique colors that is, 16 unique hues and 8 luminosity levels for each hue for NTSC consoles (see tv directive.) PAL consoles can display 104 unique colors (13 hues with 8 luminosity levels.)
COLUBK (Color-Luminosity Background) Sets the background color. Example: COLUBK = 112.
COLUPF (Color-Luminosity Playfield, Ball) Sets the playfield color and ball color. Example: COLUPF = 154. 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.
COLUP0 (Color-Luminosity Player0, Missile0) Sets the color for player 0 and missile 0. Example: COLUP0 = 42
COLUP1 (Color-Luminosity Player1, Missile1) Sets the color for player 1 and missile 1. Example: COLUP1 = 222
scorecolor Sets the score color so it will be visible. Example: scorecolor = 30
Atari 2600 NTSC TIA Color Chart Below is an interactive color chart. Click on a color and the hexadecimal number will be displayed. If you need PAL colors, check out Glenn Saunder's HTML TIA color chart.
Related Link Atari 2600 Color Compatibility Tool Quickly find colors that go great together on the Atari 2600 without the tedious trial and error of typing various color combinations in your code and running it in an emulator dozens of times until you get it right.
Objects 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 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.
Missiles
Two missiles can be displayed on the screen. These are simply vertical lines of a height you specify, and at coordinates you specify. The missiles are the same color as their respective players.
missile0x = 64 missile0y = 64 missile0height = 8 missile1x = 64 missile1y = 80 missile1height = 8 Main_Loop COLUP0 = 140 COLUP1 = 28 NUSIZ0 = $30 NUSIZ1 = $30 drawscreen goto Main_Loop
In the multisprite kernel, the missiles are fixed at one unit high, and the missile height variables do not exist. Also remember that one or both of the missiles can become unavailable depending on the kernel options you select. See kernel options for more information.
There are more things you can do with missiles by modifying the NUSIZx TIA registers. See NUSIZ0, NUSIZ1 for more information.
Ball
The ball is one of the objects that the Atari 2600 can display in the screen.
ballx=64 bally=64 ballheight=4 COLUPF=30 CTRLPF=$21 mainloop drawscreen goto mainloop
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 400
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 constant row heights, you can specify the height of individual playfield rows by setting the pfheights kernel option. See Compiler Directives for more information.
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.
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.
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.
Note: Does not work with the Multisprite Kernel. pfclear
pfclear %10101010
pfpixel (playfield pixel)
This draws a single pixel with playfield blocks. Uses 80 processor cycles every frame. The syntax is:
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 line with playfield blocks. Uses 250 to 1500 processor cycles every frame depending on length (Approx 210+42*length). The syntax is:
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 line with playfield blocks. Uses 230 to 600 processor cycles every frame depending on length (Approx 200+34*length). The syntax is:
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
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 = 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 sc1 will contain the first two digits, sc2 the 3rd and 4th, and sc3 the last two. Since these are all BCD numbers, you need to place a "$" in front of any values you check. For example, to check if the score is less than 10: if sc1=$00 && sc2=$00 && sc3<$10 then ... To check if the score is greater than 123456: if sc1>=$12 && sc2>=$34 && sc3>$56 then ... Breaking out score digits is also useful for setting the score to display special values or custom graphics. You can modify score_graphics.asm to include definitions for digits "A-F" and set the score digits manually. How to do this is beyond the scope of this document, but it has been discussed in detail in the 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 = 168 pfscore2 = 255 You might find that binary is more intuitive. The following example is the binary version of the example above: pfscore1 = %10101000 pfscore2 = %11111111
The above examples set the left bar for 3 separate dots (indicating lives) and the right bar will be full-width.
pfscore1 = pfscore1/4 To decrement the health bar: pfscore2 = pfscore2/2 When there are no lives left, pfscore1 is zero and pfscore2 is zero when the healthbar is empty.
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.
Instructions:
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: pfscore Lives and Health Toy playable .bin file
Here's the code: pfscore Lives and Health Toy .bas file
Note: You cannot use pfscore bars in conjunction with the HUDs that are currently available without hacking the source files because they use the same memory locations for their variables.
Warning: Remember that pfscore bars are not the same as the Life Counter and Status Bar minikernels. It might be confusing at first.
noscore If you wish to disable the score completely, you may do so by defining noscore. At this time, you can do that by: const noscore=1 As with pfscore above, this method will be deprecated later when score options are integrated with the set command.
What is a BCD Compliant Number?
BCD stands for Binary-coded decimal. In essence, it is a hexadecimal number represented as a decimal number.
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.
CTRLPF
Changes properties of the playfield and/or ball.
REFP0, REFP1
Reflects player sprites. (Flips them horizontally.)
PF0
Set or clear the left and right 10% of the playfield. PF0 must be inside of your game loop.
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
Example with one column farthest away from the playfield: COLUBK = 132 COLUPF = 30 playfield: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX end Main_Loop PF0 = %00010000 drawscreen goto Main_Loop
Example with four columns: COLUBK = 132 COLUPF = 30 playfield: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX end Main_Loop PF0 = %11110000 drawscreen goto Main_Loop
AUDV0, AUDC0, AUDF0, AUDV1, AUDC1, AUDF1 See sound for more information about these.
Display kernel
The display kernel (usually just "the kernel") is a carefully-written software routine that renders the television display. The Atari 2600's television signal is controlled largely by software because the hardware is so primitive. Although programming a 2600 kernel is quite difficult, the primitive hardware also affords a good deal of flexibility, in that custom kernels can exploit the primitive hardware to suit particular needs.
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.
Minikernel Links 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.
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.
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 16k set romsize 8kSC
optimization
Tells the compiler to try to generate code that runs faster or has a smaller size. There are five options: speed, size, noinlinedata, inlinerand, and none.
speed
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.
Have a multicolored background instead of a multicolored playfield using pfcolors data.
The syntax is:
Acceptable singles:
Below is a kernel_options chart based on the chart created by kisrael.
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 positions range from 0 to 160. If we multiply paddle by 2 and add 1, we get a value between 0 and 155, which is better. If your sprite is 8 pixels wide and each pixel is 1 color clock wide, then you'd want a maximum horizontal position of 153, which would put the player's rightmost pixel at position 160.
rem * read paddle 0, use it to position player0x across the screen currentpaddle = 0 drawscreen player0x = 2 * paddle + 1 : if player0x > 153 then player0x = 153 rem * read paddle 1, use it to position player1x across the screen currentpaddle = 1 drawscreen player1x = 2 * paddle + 1 : if player1x > 153 then player1x = 153
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.
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.
set debug cycles
set debug cyclescore
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.
bank 2 bank 8
Note: specifying "bank 1" is not necessary.
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 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.
const pfres=24
Note that you can set values from 3-32, but if the number doesn't evenly divide 96, the resultant screen might be smaller than normal. The amount of Superchip RAM used is 4 times the height. The largest size, 32, will use all available Superchip RAM.
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)
AUDC0 Audio Control for Channel 0 [Also known as Tone, Voice, and Distortion] (valid values are 0 to 15)
AUDF0
Audio Frequency for Channel 0 (valid values are 0 to 31)
Channel 1 AUDV1 Audio Volume for Channel 1 (valid values are 0 to 15)
AUDC1 Audio Control for Channel 1 [Also known as Tone, Voice, and Distortion] (valid values are 0 to 15)
AUDF1 Audio Frequency for Channel 1 (valid values are 0 to 31)
Setting the values, for instance, by AUDV0 = 10 : AUDC0 = 12 : AUDF0 = 4 will produce a tone, and it will stay on until you set AUDV0 = 0. Typically, a frame counter is set up that keeps sounds on for a specified number of frames (which occur 60 times a second).
Visual batari Basic has a Music and Sound Editor that should help bB users create Atari 2600 specific music and sound effects faster and easier than ever before.
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 batari Basic Music and Sound Page Expanded information about Atari 2600 sound. Atari 2600 VCS Precise Sound Values and Distortion Breakdown Atari 2600 VCS Sound Frequency and Waveform Guide
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:
Main_Loop
temp1 = SWCHA / 16
rem * NOTE: To get the same 4 bit value for Joy1, use this code:
rem * temp1 = SWCHA & %00001111
on temp1 gosub Still Still Still Still Still GoDownRight GoUpRight GoRight Still GoDownLeft GoUpLeft GoLeft Still GoDown GoUp Still
drawscreen
goto Main_Loop
Still
rem * Code goes here.
return thisbank
GoUp
rem * Code goes here.
return thisbank
GoDown
rem * Code goes here.
return thisbank
GoLeft
rem * Code goes here.
return thisbank
GoRight
rem * Code goes here.
return thisbank
GoUpLeft
rem * Code goes here.
return thisbank
GoUpRight
rem * Code goes here.
return thisbank
GoDownLeft
rem * Code goes here.
return thisbank
GoDownRight
rem * Code goes here.
return thisbank
Example programs:
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
Numbers 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 in batari Basic are assumed to be in decimal unless otherwise specified by either the $ (for hexadecimal) or the % (for binary).
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
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.
Math 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.
Full expression evaluation for integer 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
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 integer 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.
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 integer 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.
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:
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
Assembly Language Links Describes the complete instruction set in detail while staying fairly compact so it's easier to comprehend. The 56 instructions you can give the 6502 (or 6510) chip. Machine Language For Beginners This book is designed to teach machine language to those who have a working knowledge of BASIC.
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.
Troubleshooting
This section does not cover all possible problems, but it does explain some common programming errors and their resolutions.
Compilation Errors There are 3 stages in the bB compilation process where errors may be caught in the preprocessor, the compiler or the assembler.
Preprocessor Errors
The preprocessor can only catch the most obvious of errors, such as unrecognized symbols in the code.
Compiler Errors
The compiler can catch a wider range of errors, but quite a few errors will sneak past the compiler and will only be caught by the assembler.
Assembler Errors
Assembler errors are more cryptic and harder to find and fix. This is because they occur only after the Basic file is converted to assembly and linked together to a composite asm file. There are many things that can go wrong at this stage, but there are four common errors that can be caught and fixed without too much trouble.
Branches out of Range
A branch out of range is the easiest to resolve. This usually happens when an if-then statement is located too far away from its target. For example, "if a=1 then 40" will trigger this error if line 40 is far away from the if-then.
set smartbranching on Smart branching is not enabled by default because it will complicate the assembly. Since the original vision of batari Basic was that the created .asm file could be studied to learn assembly language, complicating the assembly by default would conflict with that vision.
Syntax Errors Often caused by a typographical error in a data statement, player or playfield declaration, inline asm or possibly other places. These are sometimes difficult to spot, and can often only be resolved by searching the created .asm file (see Searching the Assembly File below.)
Duplicate Labels
These occur when you use the same label or linenumber for two different lines of code. For example,
10 a=4 20 r=4 10 e=10
will compile but the assembler will claim there is a duplicate label (10.) The resolution is to look in your Basic source and change one of the labels to something else.
Unresolved Symbols
Any time the assembler finds an error, it will list some unresolved symbols. If the list is empty, simply ignore it; the problem lies elsewhere. If there is something on the list, this is likely the cause of your error.
goto 300
It can also be caused by a function call to a non-existent function. The following would cause this error if myfunction was not defined:
a=myfunction(33)
Another cause occurs when a call to a routine in a module or bB internal function is made but the proper module is not included. For example, the statement "a=e/17" will produce "div8" as an unresolved symbol if the module "div_mul.asm" is not included.
Searching the Assembly File
With most assembler errors, the line of code in the composite .asm file will be echoed in parentheses. This line will often be large (sometimes > 10000) so hand-counting the lines is not feasible you will need to open the file a text editor utility that can jump to this line or at least tell you what line the cursor is on. If you don't have an adequate text editor in Windows, you can use the DOS 'EDIT' program, which will tell you what row of text the cursor is on.
Other Errors This section is intended to help find common errors where compilation is successful but the game doesn't work. This section will be expanded at a later date.
Blank Screen Make sure you are calling drawscreen somewhere in your game loop (and that your game runs in a loop!) If you are, you probably didn't set colors, as they are all black by default. You use COLUP0 for player0 and missile0, COLUP1 for player1 and missile1, COLUPF for the playfield and ball, and COLUBK for the background.
Players Use Score Color The score actually uses player objects, so COLUP0 and COLUP1 must be set during every frame or their colors will revert to that of the score.
Timing problems
My game jitters, shakes or rolls!
Games for earlier versions of bB don't work correctly
bB 1.0 has many changes that might 'break' your game. Chances are, the problem is that the players are no longer in the correct places (specifically, 14 pixels to the right.)
Memory Maps $80 player0x $81 player1x $82 player0colorstore missile0x $83 missile1x $84 ballx $85 player0y objecty $86 player1y $87 player1color missile1height $88 missile1y $89 bally $8a player0pointer player0pointerlo $8b player0pointerhi $8c player1pointer player1pointerlo $8d player1pointerhi $8e player0height $8f player1height $90 player0color currentpaddle missile0height $91 paddle missile0y $92 ballheight $93 score $94 $95 $96 scorepointers $97 $98 $99 $9a $9b $9c temp1 $9d temp2 $9e temp3 $9f temp4 $a0 temp5 $a1 temp6 $a2 rand $a3 scorecolor $a4 var0 Playfield Variables $a5 var1 $a6 var2 There are 4 variables for each row. $a7 var3 (8 bits x 4 = 32) $a8 var4 $a9 var5 $aa var6 $ab var7 $ac var8 $ad var9 $ae var10 $af var11 $b0 var12 $b1 var13 $b2 var14 $b3 var15 $b4 var16 $b5 var17 $b6 var18 $b7 var19 $b8 var20 $b9 var21 $ba var22 $bb var23 $bc var24 $bd var25 $be var26 $bf var27 $c0 var28 $c1 var29 $c2 var30 $c3 var31 $c4 var32 $c5 var33 $c6 var34 $c7 var35 $c8 var36 $c9 var37 $ca var38 $cb var39 $cc var40 $cd var41 $ce var42 $cf var43 $d0 var44 $d1 var45 $d2 var46 $d3 var47 $d4 temp7 $d5 playfieldpos $d6 A a $d7 B b $d8 C c $d9 d D $da E e $db F f $dc G g $dd H h $de I i $df J j $e0 K k $e1 L l $e2 M m $e3 N n $e4 O o $e5 P p $e6 Q q $e7 R r $e8 S s $e9 T t $ea U u $eb V v $ec W w $ed X x $ee Y y $ef Z z $f0 pfheighttable pfcolortable aux1 $f1 aux2 $f2 lifepointer aux3 pfscore1 $f3 aux4 pfscore2 lives $f4 aux5 pfscorecolor lifecolor $f5 statusbarlength aux6 $f6 stack1 $f7 stack2 $f8 stack3 $f9 stack4 $fa [reserved for the stack] $fb [reserved for the stack] $fc [reserved for the stack] $fd [reserved for the stack] $fe [reserved for the stack] $ff [reserved for the stack]
$80 missile0x $81 missile1x $82 ballx $83 objecty missile0y $84 missile1y $85 bally $86 SpriteIndex $87 player0x $88 NewSpriteX player1x $89 player2x $8a player3x $8b player4x $8c player5x $8d player0y $8e NewSpriteY player1y $8f player2y $90 player3y $91 player4y $92 player5y $93 NewNUSIZ _NUSIZ1 $94 NUSIZ2 $95 NUSIZ3 $96 NUSIZ4 $97 NUSIZ5 $98 NewCOLUP1 _COLUP1 $99 COLUP2 $9a COLUP3 $9b COLUP4 $9c COLUP5 $9d SpriteGfxIndex $9e $9f $a0 $a1 $a2 player0pointer player0pointerlo $a3 player0pointerhi $a4 P0Bottom $a5 P1Bottom $a6 player1pointerlo $a7 player2pointerlo $a8 player3pointerlo $a9 player4pointerlo $aa player5pointerlo $ab player1pointerhi $ac player2pointerhi $ad player3pointerhi $ae player4pointerhi $af player5pointerhi $b0 player0height $b1 spriteheight player1height $b2 player2height $b3 player3height $b4 player4height $b5 player5height $b6 PF1temp1 $b7 PF1temp2 $b8 PF2temp1 $b9 PF2temp2 $ba pfpixelheight $bb playfield PF1pointer $bc $bd PF2pointer $be $bf aux3 statusbarlength $c0 pfscorecolor lifecolor aux4 $c1 aux5 pfscore1 lifepointer $c2 lives aux6 pfscore2 $c3 playfieldpos $c4 scorepointers $c5 $c6 $c7 $c8 $c9 $ca temp1 $cb temp2 P1Display $cc temp3 $cd temp4 RepoLine $ce temp5 P0Top $cf temp6 $d0 temp7 $d1 score $d2 $d3 $d4 pfheight $d5 scorecolor $d6 rand $d7 A a $d8 B b $d9 C c $da D d $db E e $dc F f $dd G g $de H h $df I i $e0 J j $e1 K k $e2 L l $e3 M m $e4 N n $e5 O o $e6 P p $e7 Q q $e8 R r $e9 S s $ea T t $eb U u $ec V v $ed W w $ee X x $ef Y y $f0 Z z $f1 spritesort $f2 spritesort2 $f3 spritesort3 $f4 spritesort4 $f5 spritesort5 $f6 stack1 $f7 stack2 $f8 stack3 $f9 stack4 $fa [reserved for the stack] $fb [reserved for the stack] $fc [reserved for the stack] $fd [reserved for the stack] $fe [reserved for the stack] $ff [reserved for the stack]
Tools VbB is an IDE that makes it easier to create bB games. It has all kinds of useful features and tools. Among the cool features, you can easily create multicolored sprites and playfields and preview them with your favorite emulator. Quickly convert hex, decimal, binary and more with the programming equivalents tool. There's also a simple calculator and a fairly useless playfield toy. A free online tool to help make music for games. 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 Makes it easy to play with Atari 2600 sound effects. Useful for finding what you need for games and music. If you'd like to test your games on a real Atari 2600, the Harmony Cartridge is one of the easiest ways to do that. The Harmony Cartridge also lets you use an SD card. Drop your own games on it or hundreds of your favorite classic Atari 2600 game files. Bankswitch type is auto-detected.
Related Links batari Basic Tutorial by CurtisP Code Snippets & Samples for bB Beginners shakescreen (hidden command in the standard kernel) Atari Age 2600 Programming For Newbies Forum Glenn Saunder's HTML TIA color chart Atari 2600 Technical Information Atari 2600 Advanced Programming Guide Nick Bensema's Guide to Cycle Counting on the Atari 2600 Atari 2600 VCS Precise Sound Values and Distortion Breakdown Atari 2600 VCS Sound Frequency and Waveform Guide z26 (Emulator) x26 (Front End for z26) One Picture is Worth a Thousand Bytes Computer Hope Dictionary: Over 6,000 computer definitions and terms
|
|
Disclaimer View this page and any external web sites at your own risk. Use batari Basic and Visual batari Basic at your own risk. I am not responsible for any possible spiritual, emotional, physical, financial or any other damage to you, your friends, family, ancestors, or descendants in the past, present, or future, living or dead, in this dimension or any other.
|
|
|
Main Navigation |
Background Color Changer |
||
|
|
|
|
|
|
|