Jump to content
IGNORED

Nyan Cat Game - Work in Progress


JeremiahK

Recommended Posts

Excellent idea, as always! So the highest 3 bits of the address are ignored? That can be useful. I love how the VCS has all these "features" as a byproduct of cost-cutting, though at the end of the day I would much rather have more than 4K of addressable space.

 

The real reason is the address bus on the physical chip, which is also why it is smaller than a regular 6502.

Link to comment
Share on other sites

Aha, good to clear that up, I didn't realize that was how ROR/ROL worked.

 

 

The real reason is the address bus on the physical chip, which is also why it is smaller than a regular 6502.

 

Because those bits on the address bus aren't even used, correct? Meaning the CPU itself thinks it can access 16-bit memory addresses, but only 13 of the "lanes" or "pins" are actually connected to anything.

Edited by JeremiahK
Link to comment
Share on other sites

I have done some minor optimizations to the code. I improved the efficiency of the code that sets the 5 object positions for the scoreboard display, restructuring reads and writes and timing to use the fewest number of writes to registers. There are still long SLEEP commands, but that is easily fixed once I know what to fill in those gaps with.

 

I re-wrote the routine that pushes the scoreboard graphics onto the stack, and cut it down from spending 1245 cycles to 685 cycles. This of course is at the expense of some ROM space, but no extra RAM was needed for the pointers because I am sharing the RAM with other variables. I also have it set up in such a way that wouldn't mess up my ability to use the highest 3 bits of the pointers for other things. I moved the 4K code segment from $F000 to $1000 so that all the labels would have 0's the highest 3 bits, as well.

 

Here is the code for the new scoreboard loading routine. I changed the Level variable from BCD mode to binary mode, so I could potentially use the highest 3 bits for something else. I just realized that this is not neccesary, because the range is only from 0-19 anyway. I will change it back to BCD tomorrow.

; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
; Load Scoreboard
;
; Get graphics data for the scoreboard and push it onto the stack
;
; Takes 685 cycles to complete (9 full scanlines + 1 cycle)
; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>

    SUBROUTINE
    
; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
; Prepare pointer for level digit graphics
    
    lda #>LevelGfx	; 2
    sta LevelLoadPtr+1	; 3 - set MSB of level digit graphics pointer
    
    lda Level		; 3

; if I change the level variable back to BCD mode, mod 10 will be easier
;
;   and #$0F            ; that's it
    
    sec			; 2 - perform a mod 10 to isolate left digit
    sbc #10		; 2
    
    bcc .Negative	; 3/2
    bcs .Positive	; 3 - done this way to use the same number of cycles
			;     either way, may or may not be neccesary
.Negative
    adc #10		; 2
.Positive
    
    asl			; 2
    asl			; 2
    asl			; 2
    adc #<LevelGfx	; 2 - add graphics table offset
    sta LevelLoadPtr	; 3 - set LSB of level digit graphics pointer

; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
; Prepare one pointer MSB and multiple LSB's for score digit graphics
    
    lda #>ScoreGfx	; 2
    sta ScoreLoadPtr+1	; 3 - set MSB of score digit graphics pointer
    
    lax BCDScore+0	; 3
    and #$F0		; 2
    lsr			; 2
    sta ScoreDigit0	; 3 - set LSB for digit 0
    txa			; 2
    and #$0F		; 2
    asl			; 2
    asl			; 2
    asl			; 2
    sta ScoreDigit1	; 3 - set LSB for digit 1
    
    lax BCDScore+1	; 3
    and #$F0		; 2
    lsr			; 2
    sta ScoreDigit2	; 3 - set LSB for digit 2
    txa			; 2
    and #$0F		; 2
    asl			; 2
    asl			; 2
    asl			; 2
    sta ScoreDigit3	; 2 - set LSB for digit 3
    
    lax BCDScore+2	; 3
    and #$F0		; 2
    lsr			; 2
    sta ScoreDigit4	; 3 - set LSB for digit 4
    txa			; 2
    and #$0F		; 2
    asl			; 2
    asl			; 2
    asl			; 2
    sta ScoreDigit5	; 3 - set LSB for digit 5
    
; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
; Load the stack with the graphics for the scoreboard
    
    ldy #6		; 2

.LoadScoreboard

    lda (LevelLoadPtr),y; 5
    pha			; 3
    
    lda ScoreDigit5	; 3
    sta ScoreLoadPtr	; 3
    lda (ScoreLoadPtr),y; 5
    pha			; 3
    
    lda ScoreDigit4	; 3
    sta ScoreLoadPtr	; 3
    lda (ScoreLoadPtr),y; 5
    pha			; 3
    
    lda ScoreDigit3	; 3
    sta ScoreLoadPtr	; 3
    lda (ScoreLoadPtr),y; 5
    pha			; 3
    
    lda ScoreDigit2	; 3
    sta ScoreLoadPtr	; 3
    lda (ScoreLoadPtr),y; 5
    pha			; 3
    
    lda ScoreDigit1	; 3
    sta ScoreLoadPtr	; 3
    lda (ScoreLoadPtr),y; 5
    pha			; 3
    
    lda ScoreDigit0	; 3
    sta ScoreLoadPtr	; 3
    lda (ScoreLoadPtr),y; 5
    pha			; 3
    
    dey			; 2
    bpl .LoadScoreboard	; 2/3

Link to comment
Share on other sites

  • 2 weeks later...

I re-wrote the scoreboard loading routine again to use 7 actual pointers, rather than 1 for the level counter graphics and 1 recycled over and over for each score digit's graphics. Now it takes only 535 cycles, and a few less bytes of ROM.

 

I also improved many other areas of the code, especially the scoreboard kernel. I have added support for a "level color" which controls the color of the scoreboard, progress/health display, and throbbing lines between rows, so each level can have it's own color.

 

I was planning on rearanging some of the pages, but since I still have a few pages left, I will wait a bit to do that. It's starting to look like this will turn into an 8K project. I have a couple ideas of things to add when that happens, such as a detailed game select screen.

 

Epilepsy warning: this demo quickly loops through all the colors to demonstrate the level color control, causing full-screen craziness.

nyancat.bin

Edited by JeremiahK
  • Like 2
Link to comment
Share on other sites

I hope you all had a Merry Christmas!

 

I spent today working on (eventually) making the cat able to move vertically. I haven't explained exactly how I will do this yet, so I will now.

 

I have the graphics tables for the cat's face, the cat's tart body, and the rainbow colors set up in such a way that they are padded with 0's. This way, I can easily move the cat vertically by moving the pointers' positions in the graphics tables and simply update the graphics every scanline, rather than checking each scanline for when to start drawing them, which I don't have time for.

When I first planned this, I thought I would need to have one big kernel loop for the whole screen, which would use up a ton of ROM for all the 0's in the graphics tables. That's when I decided to split the kernel into sections, drawing cat-less rows above and below the cat's two rows (the cat needs two rows so it can be positioned halfway between two rows). This way, the graphics tables can be much smaller, since they are only used for 33 scanlines (14 per row, plus 5 for the throbbing row seperator).

 

I wrote out a complete timing plan for the cat's kernel. This kernel is tight! Every cycle is used, and I needed to move both the rainbow color graphics and the cat's tart graphics into RAM, saving 3 cycles, to make it possible.

If I used a scanline counter with a dey/bne loop for each of the two rows, I would have to move the graphics pointers before drawing the throbbing line, which isn't possible. So instead, I am using a single counter for the whole thing. I added a special byte to the end of each food item's graphics (making sure it's never used in the graphics themselves) so I can use a cmp/bne loop, using the uniform height of the food items to know when to break from the loop.

 

I haven't made a working demo yet, as I still need to write code to load the tables into RAM. I will need to figure out a way that uses as little RAM as possible.

; Stack pointer is pre-loaded with 2nd food item's color
; END_FOOD is any value that isn't present in any of the food item's graphics

; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
; Kernel used when food items are closer to the left edge of the screen
; Playfield must be set to MIRRORED mode
; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
    
.LeftLoop
    
    ; 3 cycles
    sta GRP1        ; 03 - set 1st food item's gfx
    
    ; 7 cycles
    lda RamBowColors,y    ; 07 - set rainbow colors
    sta COLUBK        ; 10
    
    ; 7 cycles
    lda RamBowColors+1,y; 14
    sta COLUPF        ; 17
    
    ; 6 cycles
    lda FoodColor1    ; 20 - set 1st food item's color
    sta COLUP1        ; 23 - (1 color clock late, but that's fine with me)
    
    ; 8 cycles
    lda #COL_CAT_TART    ; 25
    sta COLUPF        ; 28 - MUST set tart color at cycle 28
    stx COLUBK        ; 31 - MUST set face/bg color to black at cycle 31
    
    ; 13 cycles
    lda (FoodGfxPtr2),y    ; 35 - load 2nd food item's gfx
    tsx            ; 38 - load 2nd food item's color
    sta GRP1        ; 41 - set 2nd food item's gfx
    stx COLUP1        ; 44 - set 2nd food item's color
    
    ; 2 cycles
    dey            ; 46 - succeeding reads/writes are for next scanline
    
.LeftEntrance        ; enter loop here
    
    ; 5 cycles
    ldx #COL_BACKGROUND    ; 48 - X must be set to 0 (black)
    stx COLUPF        ; 51 - don't draw rainbow/tart on right side
    
    ; 8 cycles
    lda (CatGfxPtr),y    ; 56 - set cat's head gfx
    sta GRP0        ; 59
    
    ; 7 cycles
    lda RamTartGfx,y    ; 63 - set cat's tart gfx
    sta PF1        ; 66
    
    ; 10 cycles
    lda (FoodGfxPtr1),y    ; 71 - load 1st food item's gfx
    cmp #END_FOOD    ; 73 - if gfx != END_FOOD...
    bne .LeftLoop    ; 00/75 - ...draw this row's next scanline...
    bmi .End        ; 02 - ...otherwise row is complete

; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
; Kernel used when food items are closer to the right edge of the screen
; Playfield must be set to COPIED mode
; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
    
.RightLoop
    
    ; 3 cycles
    sta GRP1        ; 10 03 - set 1st food item's gfx
    
    ; 7 cycles
    lda RamBowColors+1,y; 14 - set background's rainbow color
    sta COLUPF        ; 17 - (1 color clock late, but that's fine with me)
    
    ; 6 cycles
    lda FoodColor1    ; 20 - set 1st food item's color
    sta COLUP1        ; 23
    
    ; 8 cycles
    lda #COL_CAT_TART    ; 25
    sta COLUPF        ; 28 - MUST set tart color at cycle 28
    stx COLUBK        ; 31 - MUST set face/bg color to black at cycle 31
    stx COLUPF        ; 34 - don't draw rainbow/tart on right side
    
    ; 5 cycles
    lax (FoodGfxPtr2),y    ; 39 - load 2nd food item's gfx before dey
    
    ; 2 cycles
    dey            ; 41 - succeeding reads/writes are for next scanline
    
    ; 7 cycles
    lda RamTartGfx,y    ; 45 - set cat's tart gfx
    sta PF1        ; 48
    
    ; 4 cycles
    lda RamBowColors+1,y; 52 - use 4 cycles to load playfield's rainbow color
    
    ; 8 cycles
    stx GRP1        ; 55 - set 2nd food item's gfx no sooner than cycle 55
    tsx            ; 57 - load 2nd food item's color
    stx COLUP1        ; 60 - set 2nd food item's color no later than cycle 60
    
.RightEntrance        ; enter loop here
    
    ; 2 cycles
    ldx #COL_BACKGROUND    ; 62 - X register must be set to 0 (black)
    
    ; 3 cycles
    sta COLUPF        ; 65 - set PF's rainbow color no sooner than cycle 59
    
    ; 8 cycles
    lda (CatGfxPtr),y    ; 70 - set cat's head gfx
    sta GRP0        ; 73
    
    ; 10 cycles
    lda (FoodGfxPtr1),y    ; 02 - load 1st food item's gfx
    cmp #END_FOOD    ; 04 - if gfx != END_FOOD...
    bne .RightLoop    ; 07/06 - ...draw this row's next scanline...
                ; ...otherwise row is complete

; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>

.End

Edited by JeremiahK
Link to comment
Share on other sites

"0 can be replaced with $ff and color change?"

 

That might actually work, with some modifications to the graphics. The only problem is that the black square for blank items will end up being drawn over the cat and rainbow at certain times, but I think that could be fixed by changing the playfield priority when necessary.

Link to comment
Share on other sites

I realized that this plan won't work. It would draw the cat correctly, but not the food items. I will just have to use another couple pointers for the 2nd row's cat graphics. I might be able to share these pointers with the health logo pointers.

I modified the kernel, and it does work as it should.

I had a free cycle, so I used it to load the tart color from RAM, allowing me to change the cat's color. Now I can add a customizable cat color, and do a flashing animation when you lose health.

Link to comment
Share on other sites

Happy 2018!

 

Wow, I completely underestimated how complicated it would be to draw Nyan Cat with the rainbow, with two unique food items on the same row, and then continue to draw Nyan Cat with the rainbow over the throbbing lines, all while setting up the position of the next food items, and then draw another cat/rainbow/food row.

 

But I did it! Here is a full grid of swift fruit and veggies, with a moving cat and rainbow!

 

Right now I can't put the cat on the top or bottom row, but that is a quick fix. I just wanted to keep you all posted!

nyancat.bin

  • Like 5
Link to comment
Share on other sites

Looking good!

 

Clever use of the missiles and ball for what I presume is the level indicator. Have the digits look different is a bit odd though. A couple options for that:

  • change score font
  • draw it with the players
To draw it with the player's you'd need to change NUSIZ# twice per scanline alternating between 3 copies close and 2 copies wide. I'm not sure if there's enough time to change both players without the use of something like DPC+.
  • Like 1
Link to comment
Share on other sites

Looking good!

 

Clever use of the missiles and ball for what I presume is the level indicator. Have the digits look different is a bit odd though. A couple options for that:

  • change score font
  • draw it with the players
To draw it with the player's you'd need to change NUSIZ# twice per scanline alternating between 3 copies close and 2 copies wide. I'm not sure if there's enough time to change both players without the use of something like DPC+.

 

 

Yep, that's the level indicator. It can display any number from 0-19, although I'm not planning on using 0 in the game.

 

That's pretty genius with the 2 copies wide, I'll have to look into that. Maybe I can get rid of the use of the collision registers to know when to break from the loop, although I kind of like it that way. :D As it is now, I have all the graphics pre-loaded into the stack so I can save 7 cycles per scanline. This uses 49 bytes of RAM, but some of the bytes can be reused for variables in the gameplay kernel, plus 34 bytes for the rainbow.

 

Is there any way to do a 6-digit score without enabling the VDELPx registers? That could save me some cycles.

Link to comment
Share on other sites

Maybe I can get rid of the use of the collision registers to know when to break from the loop, although I kind of like it that way. :D

 

 

I was wondering about that, the score added some extra scanlines when I was disabling objects to analyze your kernel.

post-3056-0-98610200-1514938631_thumb.png

 

 

Is there any way to do a 6-digit score without enabling the VDELPx registers? That could save me some cycles.

Time Warp from back in the day has a 6 digit score without using VDEL

 

via Bus Stuffing:

ShowGraphic:
; X = # of rows to output
SGloop:
    sta WSYNC
;---------------------------------------
    sty GRP0        ; 3  3 
    sty GRP1        ; 3  6
    SLEEP 33        ;33 39
    dex             ; 2 41
    sty GRP0        ; 3 44
    sty GRP1        ; 3 47
    sty GRP0        ; 3 50
    sty GRP1        ; 3 53
    bne SGloop      ; 2 55 (3 56)
    sta WSYNC
;---------------------------------------
    stx GRP1        ; 3  3 GRP0 now blank (as X was 0)
    stx GRP0        ; 3  6 GRP1 now blank
    rts
  • Like 1
Link to comment
Share on other sites

Here's the code I thought up in bed last night. Unfortunately, it doesn't quite work.

			;	66
    ldy #6		; 2	68
    
.ScoreboardLoop
    
    ; 5 cycles to set NUSIZ1 for score
    lda #3		; 2	70
    sta NUSIZ1		; 3	73
    
    ; 7 cycles to load Temp with level digit
    lda LevelDigit,y	; 4	01
    sta Temp		; 3	04
    
    ; 6 cycles to load Y with digit 5
    lda Digit5,y	; 4	08
    tay			; 2	10
    
    ; 6 cycles to load stack pointer with digit 6
    ldx Digit6,y	; 4	14
    txs			; 2	16
    
    ; 14 cycles to load GRPx's with digits 1 and 2
    lda Digit1,y	; 4	20
    ldx Digit2,y	; 4	24
    sta GRP0		; 3	27
    stx GRP1		; 3	30
    
    ; 8 cycles to load A and X with digits 3 and 4
    lda Digit3,y	; 4	34
    ldx Digit4,y	; 4	38
    
    ; 14 cycles to write to GRPx's for score
    sta GRP0		; 3	41
    stx GRP1		; 3	44
    sty GRP0		; 3	47
    tsx			; 2	49
    stx GRP1		; 3	52
    
    ; 5 cycles to set NUSIZ1 for level digit
    lda #4		; 2	54
    sta NUSIZ1		; 3	57
    
    ; 6 cycles to load GRP1 with level digit
    lda Temp		; 3	60
    sta GRP1		; 3	63
    
    ; 5 cycles for loop management
    dey			; 2	65
    bpl .ScoreboardLoop	; 3/2	68

I forgot that Y gets loaded with graphics, so it can't be used as the loop counter. If the code were rearranged a bit and changed to use a RAM variable to control the loop, it should be possible to have a tightly-spaced 6-digit score without using VDEL, but not with another digit using NUSIZ. Also, the leftmost graphics bit would have to be cleared in the graphics, but that's not really a problem because it would be neccesary anyway with digits that close together.

 

It should actually be possible to do both the score and level counter if I position the score on the right so that the level digit would wrap around to the left side, but I think that would look funny.

Edited by JeremiahK
Link to comment
Share on other sites

Omegamatrix was able to do a 48 pixel display using INTIM as the loop control <searching>:

 

It works!!

 

I've successfully gotten a routine running. I laid out graphics for digits 0-8 from $FE00 to $FE4B, and graphics for digit 9 & letters A-H from $FE4C to $FE97. You can add more very easily. I was too tired to do this for the demo, but you will get the idea. I am only doing a loop of 8 scanlines in height, but you can do more. I layed it out as bunch of equates to make it easier to read. I'll see what you guys think. Macro's would be a good idea as well as a command line utility as Spiceware suggested.

 

This saves 4 cycles, and a zero page ram location over a regular 48 bit display.

 

In the demo the top numbers are generated by a standard 48 bit display, and the bottom numbers are done by the faster (uses less cycles) 48 bit routine.

 

 

 

attachicon.giftestTIM8T.png

 

attachicon.giftestTIM8T.zip

Emphasis added, might prove useful to what you're trying to do. Hit the little curled arrow in the top-right of the quoted text to see the original reply in context as there's a bit more about this in that topic.

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...