Jump to content
IGNORED

Macros


SeaGtGruff

Recommended Posts

Here's a little document I put together about using the 'macro' and 'callmacro' statements, as well as using a 'def' statement to make a 'callmacro' statement look more like a new instruction. I've put the document in a text file so Random Terrain can grab it and adapt it for his bB page if he wants.

 

Michael

 

 

 

macro

-----

 

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, def, and callmacro

-----------------------------------

 

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!

 

 

 

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.

Macros.txt

Edited by SeaGtGruff
  • Like 1
Link to comment
Share on other sites

It's on the bB page now:

 

http://www.randomterrain.com/atari-2600-memories-batari-basic-commands.html#macro

 

 

Can some magic be done with macro and def to make the Nybble me this, Batman! trick so easy a cave man could do it? What would be the best, easiest, least code wasting way to make our own nybble variables? We might get lucky and bB will automatically handle nybble variables in a future update, but until that day comes, looks like macro and def are our only hope for dummy-proof nybble variables.

Link to comment
Share on other sites

Can some magic be done with macro and def to make the Nybble me this, Batman! trick so easy a cave man could do it? What would be the best, easiest, least code wasting way to make our own nybble variables? We might get lucky and bB will automatically handle nybble variables in a future update, but until that day comes, looks like macro and def are our only hope for dummy-proof nybble variables.

I tried all kinds of things, but since callmacro can only take numbers and can't use variables or rand, I can't create a fake nybble variable that works similar to a real one.

Link to comment
Share on other sites

This is the best I've come up with so far. Here's a sample program that defines two macros for setting the two nibbles, and uses some 'def' statements to make it easier to set or read a nibble variable.

 

  include div_mul.asm

  macro set_lo_nibble
  {1}={1}&$F0
  {1}={1}|{2}
end

  macro set_hi_nibble
  {1}={1}&$0F
  {1}={1}|16*{2}
end

  def gamelevel=a&$0F
  def game_level=callmacro set_lo_nibble a

  def hitpoints=a/16
  def hit_points=callmacro set_hi_nibble a

  rem **************************************************************

  game_level 1
  hit_points 5

loop

  if gamelevel=1 then COLUBK=$02 else COLUBK=$12
  if hitpoints=5 then scorecolor=$0E else scorecolor=$1E

  drawscreen

  goto loop

Notes:

 

(1) You must include 'div_mul.asm' or it won't compile.

 

(2) The two macros are 'set_lo_nibble' and 'set_hi_nibble'. They each take two parameters-- the variable you want to set a nibble for, and the value you want to set it to (0 to 15).

Example: 'callmacro set_lo_nibble x 4' will set the lo nibble of variable 'x' to 4, without affecting the hi nibble.

Example: 'callmacro set_hi_nibble y 7' will set the hi nibble of variable 'y' to 7, without affecting the lo nibble.

 

(3) You don't need a macro to get (read) the value of a nibble, since you can do it easier with a 'def' statement.

 

(4) You can't use the same "variable" name to get *and* set a nibble, because bB would get confused. So you need to 'def' one variable name for getting (reading) the nibble, and a different one for setting the nibble.

Example: 'def gamelevel=a&$0F' will define 'gamelevel' to be the lo nibble of 'a'. 'gamelevel' is the name you'd use to *read* the nibble.

Example: 'def game_level=callmacro set_lo_nibble a' will define 'game_level' to be the 'callmacro' statement for *setting* the nibble.

 

(5) To set the nibble, do *not* use an equal sign, just give the name of the 'def' that sets the nibble, followed by a space, followed by the value you want to set it to.

Example: 'game_level 1' will get changed to 'callmacro set_lo_nibble a 1', which will set the lo nibble of 'a' to 1.

Example: 'hit_points 5' will get changed to 'callmacro set_hi_nibble a 5', which will set the hi nibble of 'a' to 5.

 

(6) To read the value of a nibble, just give the name of the 'def' that reads the nibble.

Example: 'if gamelevel=1 then dosomething' will read the lo nibble of 'a' and compare it to 1.

 

(7) If you want to increment or decrement a nibble, you'll need to use a temporary variable.

Example:

  temp1=gamelevel+1 : rem * read the lo nibble of 'a', add 1 to it, and store the result in 'temp1'
  game_level temp1 : rem * set the lo nibble of 'a' to the new value

Michael

Edited by SeaGtGruff
  • Like 1
Link to comment
Share on other sites

Wow, I didn't know you could do things like "{1}={1}|16*{2}". Thanks.

 

To help me avoid confusion even more, I'd probably adapt it so both def variables have the same names, but will start with the prefix Peek or Poke. Take a look at this:

 

  include div_mul.asm

  set smartbranching on

  dim rand16 = z

  macro set_lo_nibble
  {1}={1}&$F0
  {1}={1}|{2}
end

  macro set_hi_nibble
  {1}={1}&$0F
  {1}={1}|16*{2}
end

  def Peek_Game_Level=a&$0F
  def Poke_Game_Level=callmacro set_lo_nibble a

  def Peek_Hit_Points=a/16
  def Poke_Hit_Points=callmacro set_hi_nibble a


  scorecolor = $1A : COLUBK = $C4


Loop

  w = w + 1 : if w < 30 then Skip_It_All

  temp5 = (Peek_Game_Level) + 1 : Poke_Game_Level temp5

  temp5 = rand & 15 : Poke_Hit_Points temp5

  score = 0 : w = 0

  temp5 = Peek_Game_Level
  if temp5 > 0 then for temp6 = 1 to temp5 : score=score+1000 : next
  
  temp5 = Peek_Hit_Points
  if temp5 > 0 then for temp6 = 1 to temp5 : score=score+1 : next

Skip_It_All

  drawscreen

  goto Loop

 

One is a random number and the other adds up until it hits 15, then starts at 0 again. I was surprised to find out that I didn't need an if-then to keep the count from messing up. Once it hits 15, it automatically rolls over to zero.

 

Since you found a way to have reusable macros, setting up more nybble variables will be easy using def.

Edited by Random Terrain
Link to comment
Share on other sites

It looks okay to me, and I like your idea of just putting PEEK_ and POKE_ in front of the nibble variable name. Why didn't I think of that? :ponder:

Thanks. If you notice anything later that doesn't sound right, let me know and I'll fix it. Seems like I always discover something days, weeks, or months later.

Link to comment
Share on other sites

It looks okay to me, and I like your idea of just putting PEEK_ and POKE_ in front of the nibble variable name. Why didn't I think of that? icon_ponder.gif

Thanks. If you notice anything later that doesn't sound right, let me know and I'll fix it. Seems like I always discover something days, weeks, or months later.

One thing I did notice that didn't sound right-- when setting a nibble, you should be careful to set it to a value between 0 and 15, because after the code does a bitwise AND to clear the nibble that's about to be set, it does a bitwise OR to combine the new nibble value with the whole byte. If you're setting the lo nibble, and the new value is greater than 15, you might end up accidentally changing one of the bits in the hi nibble. This isn't a problem when setting the hi nibble, since the new value gets multiplied by 16 before it's combined with the whole byte. There was one place where you said something about incrementing the old value of the lo nibble and it automatically rolled over from 15 to 0. It would actually go from 15 to 16, which would roll over the lo nibble from 15 to 0, but it would also turn on one of the bits in the hi nibble (bit 4). If that bit was already on anyway, "no harm no foul." But if that bit was originally off, it would get changed. So when you add something to the value of the lo nibble, be sure the new value doesn't exceed 15. The easiest way to do that is to AND the result with 15 ($0F) before you set the lo nibble to the new value.

 

Michael

Link to comment
Share on other sites

It looks okay to me, and I like your idea of just putting PEEK_ and POKE_ in front of the nibble variable name. Why didn't I think of that? icon_ponder.gif

Thanks. If you notice anything later that doesn't sound right, let me know and I'll fix it. Seems like I always discover something days, weeks, or months later.

One thing I did notice that didn't sound right-- when setting a nibble, you should be careful to set it to a value between 0 and 15, because after the code does a bitwise AND to clear the nibble that's about to be set, it does a bitwise OR to combine the new nibble value with the whole byte. If you're setting the lo nibble, and the new value is greater than 15, you might end up accidentally changing one of the bits in the hi nibble. This isn't a problem when setting the hi nibble, since the new value gets multiplied by 16 before it's combined with the whole byte. There was one place where you said something about incrementing the old value of the lo nibble and it automatically rolled over from 15 to 0. It would actually go from 15 to 16, which would roll over the lo nibble from 15 to 0, but it would also turn on one of the bits in the hi nibble (bit 4). If that bit was already on anyway, "no harm no foul." But if that bit was originally off, it would get changed. So when you add something to the value of the lo nibble, be sure the new value doesn't exceed 15. The easiest way to do that is to AND the result with 15 ($0F) before you set the lo nibble to the new value.

Thanks. After testing both nybbles again (keeping the other at zero), I see that one side can roll over, but the other can't without messing with the other side.

 

Before I update the page, is there a simple thing that can be done with the macros that would keep both nibbles between 0 and 15? That way bB users wouldn't have to worry about it.

Edited by Random Terrain
Link to comment
Share on other sites

  macro set_lo_nibble
  {1}={1}&$F0
  {2}={2}&$0F
  {1}={1}|{2}
end

Thanks. I couldn't get that to compile, so I tried this:

 

  macro set_lo_nibble
  {1}={1}&$F0
  {1}={1}|({2}&$0F)
end

 

It compiles and seems to work. I'll use that if you don't see any problems with it.

 

Is it safe to tell bB users that the nybble variables will always stay between 0 and 15 and the numbers will roll over from 15 to 0 or from 0 to 15 similar to how a normal variable rolls over from 255 to 0 or from 0 to 255?

Link to comment
Share on other sites

  macro set_lo_nibble
  {1}={1}&$F0
  {2}={2}&$0F
  {1}={1}|{2}
end

Thanks. I couldn't get that to compile, so I tried this:

 

  macro set_lo_nibble
  {1}={1}&$F0
  {1}={1}|({2}&$0F)
end

 

It compiles and seems to work. I'll use that if you don't see any problems with it.

 

Is it safe to tell bB users that the nybble variables will always stay between 0 and 15 and the numbers will roll over from 15 to 0 or from 0 to 15 similar to how a normal variable rolls over from 255 to 0 or from 0 to 255?

D'oh! You mean you can't set a literal value to something? I thought 16=16&$0F was a perfectly legitimate statement! :roll: Oh, well, that'll teach me to actually try it out next time before I post it. icon_skull.gif

 

Funny thing is, I thought about doing it your way, but I wasn't sure if the compiler could handle it. Like I said, I should of actually *tried* it.

 

Michael

Link to comment
Share on other sites

No, its a macro, so an copy of it gets in-lined wherever you use it.

 

But the macro in question is this...

  macro set_hi_nibble
  {1}={1}&$0F
  {1}={1}|16*{2}
end

 

The bb code created by this winds up bank-switching to the 8 bit multiplication subroutines for the multiply by 16.

 

If RT changes it to the following, an expensive bank switch will be avoided...

 

  macro set_hi_nibble
  {1}={1}&$0F
  {1}={1}|({2}*16)
end

 

It also might "fix" the issue, though possibly not the root of the problem.

Link to comment
Share on other sites

Ok, I understand the problem better. In a nutshell...

 

1. The newer test versions of bB (e.g from this thread) seem to have a broken "* power of 2" detection. They seem to always use the 8 bit multiplication library, even with either one of these simple examples...

 

a=b*16

a=16*b

 

2. When your macro is built, it's being built with a fixed return label, so the math routine knows where to jump back to. Unfortunately, when you use the macro more than once it creates a duplicate label in your source.

 

3. The duplicate label is causing an imcomplete build of your binary, which is giving you the black screen. If you check the size of your binary, I'm betting it's wrong.

 

Short term workaround...

 

  macro set_hi_nibble
asm
  lda {1}
  and #$0f
  sta {1}
  lda {2}
  asl
  asl
  asl
  asl
  ora {1}
  sta {1}
end
end

Edited by RevEng
  • Like 1
Link to comment
Share on other sites

On closer inspection, I think #1 might be a feature instead of a bug. It seems that "power of 2" detection works for numbers less than 16.

 

Either way, it's the wrong thing to do in a bankswitched binary. That's a big time penalty to pay for a multiply by 16.

I'd think the compiler should be checking for any power of 2 less than 256-- 2, 4, 8, 16, 32, 64, or 128. Actually, I don't know how often the higher numbers are used, but /16 and *16 ought to be very commonly used, since they're essential for working with nibbles. There's no reason to do them with standard division and multiplication routines, especially if that means having to call a "system subroutine" that has to switch banks in a multi-banked game.

 

Michael

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...