Jump to content



0

6 digit bcd score


13 replies to this topic

#1 Primordial Ooze OFFLINE  

Primordial Ooze

    Dragonstomper

  • 504 posts
  • Quacker Blaster Lead Programmer
  • Location:United States of America

Posted Wed Feb 18, 2009 7:07 PM

I would like a link or some sample source code on adding/subtracting to a 6 digit bcd score as well as how to print the 6 digits of the score.

Thanks,

Primordial Ooze

#2 Nukey Shay OFFLINE  

Nukey Shay

    Sheik Yerbouti

  • 20,458 posts
  • Location:The land of Gorch

Posted Wed Feb 18, 2009 8:33 PM

BCD adding and subtracting are generally done by seperate routines (probably because one that handles both would be larger than doing them seperately). The routine is pretty short, so it's easy enough to use seperate entrypoints anyway. The first is the long way...with the adder in the accumulator...

	sed
	clc
	adc score
	sta score
	lda #0;contains carry
	adc score+1
	sta score+1
	lda #0;contains carry
	adc score+2
	sta score+2
	cld


You could mix that with index registers as well if you need to add values > 99. Here's one with A=ones/tens, X=hundreds/thousands, Y=ten & hundred thousands...
	sed
	clc
	adc score
	sta score
	txa
	adc score+1;also adds carry
	sta score+1
	tya
	adc score+2;also adds carry
	sta score+2
	cld

You can save 3 bytes if you aren't worried about other registers by using a loop. The 2-digit adder in Y below...

	sed
	clc
	ldx #3; # of score variables
loop:
	tya
	adc score-1,x
	sta score-1,x
	ldy #0
	dex
	bne loop
	cld

All registers will be zero on exit, too.

That works for 4-digit adders as well by moving the tya instruction to just above ldy #0. Enter the routine with the low adder in A, and the high adder in Y.


To make a subtraction routine for any of these, replace the clc with sec and the adc's with sbc's.

As far as displaying the number goes, that depends on the resources you have available for use...and where you want the score located on the screen (centered, etc). Everything from sprite positioning to digit gfx pointer setup into temporary ram is handled before the routine is needed.

#3 Primordial Ooze OFFLINE  

Primordial Ooze

    Dragonstomper

  • 504 posts
  • Quacker Blaster Lead Programmer
  • Location:United States of America

Posted Wed Feb 18, 2009 8:55 PM

So how exactly would i use this? set the x, y registers and accumulator with the amount i wish to add to the score and then add the above code after?

	ldx #1	; add 5 to the ones place
	ldy #0	; hundreds/thousands place not used
	lda #0	; ten & hundred thousands place not used
	sed
	clc
	adc score
	sta score
	txa
	adc score+1;also adds carry
	sta score+1
	tya
	adc score+2;also adds carry
	sta score+2
	cld

Also could you please comment on the above code you posted to make it easier to understand?

Thanks,

Primordial Ooze

Edited by Primordial Ooze, Wed Feb 18, 2009 9:54 PM.


#4 Nukey Shay OFFLINE  

Nukey Shay

    Sheik Yerbouti

  • 20,458 posts
  • Location:The land of Gorch

Posted Wed Feb 18, 2009 10:06 PM

I thought that it was mostly self-explanitory. It's generic and intended to be used as a subroutine...so that at any point in the program, you can load the registers with the appropriate values and then JSR to add them to the score. BTW in your example above, loading X with 1 would increase the score by 100, not 5.

ScoreSubroutine:
	sed;set decimal mode
	clc;clear carry status
	adc score;Add the accumulator to the score's low digits.
	 ;NOTE: At this point, the carry flag will be set if the value
	 ;wrapped at 99.  Ex: 95 + 10 = 5 with carry set (=1).
	 ;If no wrap occured, carry will still be clear (=0).
	sta score;Store the updated low digits.
	txa;Transfer the X register amount to A
	adc score+1;Add the accumulator to the score's middle digits.
		;Also adds the carry, if set by the previous ADC.
		;In other words, if carry was set, an additional 1
		;would be added here and the carry flag will be
		;clear again (unless this amount also wraps).
	sta score+1;Store the updated middle digits
	tya;Transfer the Y register amount to A
	adc score+2;The same thing happens here for the high digits.
		;Any carry set by the previous ADC will be sent
		;to this amount leaving the carry status updated
		;yet again.
	sta score+2;Store the last 2 digits
	cld;...clear decimal mode
	rts;...and return to the program line that called this routine.


So, for example, if the program is dealing with something that adds 5 to the score, load A with 5 and both X and Y with zero, then JSR ScoreSubroutine. If you want to add 1353 to the score...load Y with zero, X with $13 (remember to use HEX amounts!), and $53 to the accumulator...then JSR. Each ADC in the subroutine will handle the issue of byte values spilling over. This carryover will affect the next ADC or SBC encountered.

There is an easier way to update hundreds/thousands and ten&hundred-thousands if those are the multiples you are adding. A way that doesn't involve X and Y at all, but does use the "illegal" triple-NOP opcode:

Update_Low_Score_Digits:
	sed;set decimal mode
	clc;clear carry status
	adc score;Add accumulator to score's low digits w/carry.
	sta score;Store the updated low digits.
;NOTE: Here's where a triple-NOP comes into play.  We need
;a seperate entrypoint to this subroutine for cases where the
;low digits are ignored (like adding 100, for example).  But
;carry status and BCD mode need to have been setup.  So,
;place the triple-NOP opcode here to skip over them...
;with A reset at zero so that only carry affects the score:
	lda #0;reset the accumulator...
	.byte $0C;...and skip over the next 2 bytes

Update_Middle_Score_Digits:
	sed;set decimal mode
	clc;clear carry status
	adc score+1;Add accumulator to score's middle digits w/carry.
	sta score+1;Store the updated middle digits
;Here we do the same thing (in case the game needs to add 10,000...
;or 100,000 increments to a score (ignoring all the lower ones)
;Again, A is reset at zero so that only carry affects the score:
	lda #0;reset the accumulator...
	.byte $0C;...and skip over the next 2 bytes

Update_High_Score_Digits:
	sed;set decimal mode
	clc;clear carry status
	adc score+1;Add accumulator to score's high digits w/carry.
	sta score+2;Store the last 2 digits

;With the score updated, clear BCD mode and exit via RTS:
	cld;...clear decimal mode
	rts;...and return to the program line that called this routine.

So if you have a circumstance where 5 needs to be added to the score...
LDA #$05
JSR Update_Low_Score_Digits

If you want to add 4400 to a score...
LDA #$44
JSR Update_Middle_Score_Digits

If you want to add 280000 to a score...
LDA #$28
JSR Update_High_Score_Digits

The drawback is that mixed numbers like 431, 32500, etc. won't work here...since they affect 2 different variables. To be able to do those, use the previous example that involves Y or X & Y.

Edited by Nukey Shay, Wed Feb 18, 2009 10:27 PM.


#5 Nukey Shay OFFLINE  

Nukey Shay

    Sheik Yerbouti

  • 20,458 posts
  • Location:The land of Gorch

Posted Wed Feb 18, 2009 10:29 PM

I forgot to LDA #0 above the triple-NOPs in the previous post! Sorry about that :P

#6 Primordial Ooze OFFLINE  

Primordial Ooze

    Dragonstomper

  • 504 posts
  • Quacker Blaster Lead Programmer
  • Location:United States of America

Posted Wed Feb 18, 2009 11:33 PM

View PostNukey Shay, on Wed Feb 18, 2009 11:29 PM, said:

I forgot to LDA #0 above the triple-NOPs in the previous post! Sorry about that :P
That's ok, did you update the post or do i have to add the lda #0 to the code above?

Sincerely,

Primordial Ooze

#7 Omegamatrix OFFLINE  

Omegamatrix

    River Patroller

  • 4,795 posts
  • Location:Oh, Canada

Posted Thu Feb 19, 2009 4:43 AM

That is a good use for a triple nop, Nukey. I suppose you could do the same thing with BIT, but then you're changing the N,V,Z flags. The triple nop is a good way to do it.



Jeff

#8 Nukey Shay OFFLINE  

Nukey Shay

    Sheik Yerbouti

  • 20,458 posts
  • Location:The land of Gorch

Posted Thu Feb 19, 2009 7:43 AM

Shouldn't matter that way either, carry is the only flag it needs.

Tho there's really no need for it if the game in question only has 1 or 2 instances where you need to bypass the regular entrypoint...just place a clc and sed before the jsr. If you are scoring things all over the place, it would be better to use an index variable with a lookup table instead (so you just need to update the index whenever).

View PostPrimordial Ooze, on Wed Feb 18, 2009 9:33 PM, said:

That's ok, did you update the post or do i have to add the lda #0 to the code above?
Already been done when you read it.

#9 Primordial Ooze OFFLINE  

Primordial Ooze

    Dragonstomper

  • 504 posts
  • Quacker Blaster Lead Programmer
  • Location:United States of America

Posted Tue Mar 10, 2009 9:04 PM

Once i update the score, how do i go about displaying it?

Sincerely,

Primordial Ooze

#10 Nukey Shay OFFLINE  

Nukey Shay

    Sheik Yerbouti

  • 20,458 posts
  • Location:The land of Gorch

Posted Tue Mar 10, 2009 10:20 PM

That's 2 questions. The first is to convert the BCD variable into a pair of vectors, the second is using those vectors to pull bitmap image data from ROM and store it to the sprite.

There's a number of ways to go about this...depending on what resources you've set aside for the kernel and what registers you've got free to do the bitmap address calculation. Perhaps the easiest way (for a 6-sprite score) is to use a collection of six 2-byte vectors set up in temporary ram, and have the bitmaps of all 10 digits placed directly on a page break (so that the first line of the zero bitmap exists at address $Fp00 ... p = any page you choose). If the digit bitmaps are all 8 scanlines high, this makes calculating the addresses very easy. Just fetch the value sitting in each nybble and multiply it by 8 (for the low nybble) or divide it by 2 (for the high nybble). For example, 0x8 = 0...address $Fp00 = "zero" bitmap, 1x8 = 8...address $Fp08 = "one" bitmap, and so on.

After the vectors are in ram, use a generic 6-sprite routine to display them.

One method that I particularly like is to place all the vectors at the end of ram. Since you can put off setting up the vectors until you are just about to draw the display, that lets you reuse this temp ram for other things after the digits have been drawn...or nesting subroutines without much worry about accidentally corrupting other variables.

;NOTE: Stack should be clear before entering.

   LDX #2;# of BCD variables to convert to vectors-1
ConvertDigits:
;first, convert the high nybble into a 2-byte vector...
   LDA #>Digit_Bitmaps;the page where bitmaps exist
   PHA;push it to the end-of-ram stack ( = MSB address)
   LDA Score,X;get the digit pair
   AND #$F0;keep the high nybble only
   LSR;divide by 2 (= LSB address)
   PHA;push the bitmap LSB

;Now convert the low nybble into a 2-byte vector...
   LDA #>Digit_Bitmaps;the page where bitmaps exist
   PHA;push it to the end-of-ram stack ( = MSB address)
   LDA Score,X;get the digit pair again
   AND #$0F;keep the low nybble only
   ASL;multiply by 8...
   ASL;...
   ASL;...(= LSB address)
   PHA;push the bitmap LSB

   DEX;reduce the index
   BPL ConvertDigits;...and loop for all 3 digit pairs

Notice how the MSB's are PUSHED to the stack first...that is because the stack begins at the very last ram location (unless you've manually reset it somewhere else) and works downward in memory. What you end up with is this:

$F4 = bitmap LSB address of Score+0's low nybble
$F5 = MSB of digit bitmaps
$F6 = bitmap LSB address of Score+0's high nybble
$F7 = MSB of digit bitmaps
$F8 = bitmap LSB address of Score+1's low nybble
$F9 = MSB of digit bitmaps
$FA = bitmap LSB address of Score+1's high nybble
$FB = MSB of digit bitmaps
$FC = bitmap LSB address of Score+2's low nybble
$FD = MSB of digit bitmaps
$FE = bitmap LSB address of Score+2's high nybble
$FF = MSB of digit bitmaps

...so displaying them is just a matter of using the indirect-Y addressing mode to fetch the bitmap pattern and throw it into a sprite register. If you had sprite 0 to the left of sprite 1, you can do this to display the digit pair of Score+1 (for example)...

   LDY #7;the number of scanlines to be drawn -1
Display2digits:
   STA WSYNC;new scanline
   LDA ($F8),Y;get the bitmap from vector+Y
   STA GRP0;store it to sprite 0
   LDA ($FA),Y;get the bitmap from vector+Y
   STA GRP1;store it to sprite 1
   DEY;count down the index
   BPL Display2digits;loop for all 8 scanlines

That works for a pair of digits. To do more than that, you need to use cycle-exact timing to write to additional copies of the sprites just as the electron beam of the television reaches the spots where they begin (basically). This is totally dependant on where on the screen the digits are to be shown (centered, left-justified, etc).

I suggest you visit the Minidig and download a 6-sprite routine to toy around with. Be sure to note that indirectly-reading and then writing 6 sprites generally takes up most all of the cycle time for each scanline...so there's usually not much free time to do other things (like color the sprites differently on each scanline or such) without involving additional tricks. Absolute precision is required for positioning the close triple-copies of each sprite (RESP# and HMP#), and then writing to them. Basically, the routine writes the first two the long way (just as the above), then sets up all the rest to be written in quick succession.


Another way is to use the playfield pixels to draw digits. The 2600 has a special display mode that can be used here (called "score mode"). What this does is automatically set the color of the playfield pixels to be GRP0's color for the left half, and GRP1's color for the right...very conveniently coloring the score digits the same as the corresponding players' character.

Edited by Nukey Shay, Tue Mar 10, 2009 10:23 PM.


#11 Primordial Ooze OFFLINE  

Primordial Ooze

    Dragonstomper

  • 504 posts
  • Quacker Blaster Lead Programmer
  • Location:United States of America

Posted Tue Mar 10, 2009 11:21 PM

View PostNukey Shay, on Tue Mar 10, 2009 11:20 PM, said:

That's 2 questions. The first is to convert the BCD variable into a pair of vectors, the second is using those vectors to pull bitmap image data from ROM and store it to the sprite.

There's a number of ways to go about this...depending on what resources you've set aside for the kernel and what registers you've got free to do the bitmap address calculation. Perhaps the easiest way (for a 6-sprite score) is to use a collection of six 2-byte vectors set up in temporary ram, and have the bitmaps of all 10 digits placed directly on a page break (so that the first line of the zero bitmap exists at address $Fp00 ... p = any page you choose). If the digit bitmaps are all 8 scanlines high, this makes calculating the addresses very easy. Just fetch the value sitting in each nybble and multiply it by 8 (for the low nybble) or divide it by 2 (for the high nybble). For example, 0x8 = 0...address $Fp00 = "zero" bitmap, 1x8 = 8...address $Fp08 = "one" bitmap, and so on.

After the vectors are in ram, use a generic 6-sprite routine to display them.

One method that I particularly like is to place all the vectors at the end of ram. Since you can put off setting up the vectors until you are just about to draw the display, that lets you reuse this temp ram for other things after the digits have been drawn...or nesting subroutines without much worry about accidentally corrupting other variables.

;NOTE: Stack should be clear before entering.

   LDX #2;# of BCD variables to convert to vectors-1
ConvertDigits:
;first, convert the high nybble into a 2-byte vector...
   LDA #>Digit_Bitmaps;the page where bitmaps exist
   PHA;push it to the end-of-ram stack ( = MSB address)
   LDA Score,X;get the digit pair
   AND #$F0;keep the high nybble only
   LSR;divide by 2 (= LSB address)
   PHA;push the bitmap LSB

;Now convert the low nybble into a 2-byte vector...
   LDA #>Digit_Bitmaps;the page where bitmaps exist
   PHA;push it to the end-of-ram stack ( = MSB address)
   LDA Score,X;get the digit pair again
   AND #$0F;keep the low nybble only
   ASL;multiply by 8...
   ASL;...
   ASL;...(= LSB address)
   PHA;push the bitmap LSB

   DEX;reduce the index
   BPL ConvertDigits;...and loop for all 3 digit pairs

Notice how the MSB's are PUSHED to the stack first...that is because the stack begins at the very last ram location (unless you've manually reset it somewhere else) and works downward in memory. What you end up with is this:

$F4 = bitmap LSB address of Score+0's low nybble
$F5 = MSB of digit bitmaps
$F6 = bitmap LSB address of Score+0's high nybble
$F7 = MSB of digit bitmaps
$F8 = bitmap LSB address of Score+1's low nybble
$F9 = MSB of digit bitmaps
$FA = bitmap LSB address of Score+1's high nybble
$FB = MSB of digit bitmaps
$FC = bitmap LSB address of Score+2's low nybble
$FD = MSB of digit bitmaps
$FE = bitmap LSB address of Score+2's high nybble
$FF = MSB of digit bitmaps

...so displaying them is just a matter of using the indirect-Y addressing mode to fetch the bitmap pattern and throw it into a sprite register. If you had sprite 0 to the left of sprite 1, you can do this to display the digit pair of Score+1 (for example)...

   LDY #7;the number of scanlines to be drawn -1
Display2digits:
   STA WSYNC;new scanline
   LDA ($F8),Y;get the bitmap from vector+Y
   STA GRP0;store it to sprite 0
   LDA ($FA),Y;get the bitmap from vector+Y
   STA GRP1;store it to sprite 1
   DEY;count down the index
   BPL Display2digits;loop for all 8 scanlines

That works for a pair of digits. To do more than that, you need to use cycle-exact timing to write to additional copies of the sprites just as the electron beam of the television reaches the spots where they begin (basically). This is totally dependant on where on the screen the digits are to be shown (centered, left-justified, etc).

I suggest you visit the Minidig and download a 6-sprite routine to toy around with. Be sure to note that indirectly-reading and then writing 6 sprites generally takes up most all of the cycle time for each scanline...so there's usually not much free time to do other things (like color the sprites differently on each scanline or such) without involving additional tricks. Absolute precision is required for positioning the close triple-copies of each sprite (RESP# and HMP#), and then writing to them. Basically, the routine writes the first two the long way (just as the above), then sets up all the rest to be written in quick succession.


Another way is to use the playfield pixels to draw digits. The 2600 has a special display mode that can be used here (called "score mode"). What this does is automatically set the color of the playfield pixels to be GRP0's color for the left half, and GRP1's color for the right...very conveniently coloring the score digits the same as the corresponding players' character.
How do i use the atari 2600's "score mode" to draw the players score? Also could you provide a link to the minidig site, thanks.

Sincerely,

Primordial Ooze

#12 Nukey Shay OFFLINE  

Nukey Shay

    Sheik Yerbouti

  • 20,458 posts
  • Location:The land of Gorch

Posted Tue Mar 10, 2009 11:53 PM

Score mode is activated by setting bit1 of CTRLPF. You can set it with or without playfield reflection (bit0), but the Stella programmer guide states that playfield priority (bit2) should be off when using score mode. Although not really suited for 6-digit display for each player...since the font should be at least 5 lines x 3 pixels to keep individual digits legible, you can use the venetian blinds technique (as seen in Space Invaders) to provide up to 5 digits for each half of the screen...or a single 10-digit value if not using score mode.

Plenty of disassemblies to find examples of each method.

Check out Debro's Berzerk disassembly for a centered 6-sprite display example. Scroll down to the ScoreKernel tag. The first part resets the sprites to triple copies, sets the color and turns on vertical delay, the second part horizontally positions the sprites, and the last part draws the digits in a loop.

http://qotile.net/minidig/

#13 Wickeycolumbus OFFLINE  

Wickeycolumbus

    River Patroller

  • 4,063 posts
  • Location:Michigan

Posted Wed Mar 11, 2009 2:58 PM

Quote

How do i use the atari 2600's "score mode" to draw the players score? Also could you provide a link to the minidig site, thanks.

Sincerely,

Primordial Ooze

Enabling score mode only changes the left half of the PF to P0's color and the right to P1's color. It wont automatically display the score.

#14 supercat ONLINE  

supercat

    Quadrunner

  • 6,367 posts

Posted Wed Mar 11, 2009 4:50 PM

View PostNukey Shay, on Tue Mar 10, 2009 11:53 PM, said:

...but the Stella programmer guide states that playfield priority (bit2) should be off when using score mode.

Setting playfield priority causes anything that would be shown using COLUPF to take precedence over anything that would be shown using COLUP0 or COLUP1. Electrically, score mode causes the playfield to be drawn using COLUP0 and COLUP1 in addition to COLUPF. If COLUP0/COLUP1 have priority, that will effectively cover up the COLUPF-colored playfield. But if PFP is set, the COLUPF-colored playfield takes precedence.




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users