CardChasm

Programming the 6502 microprocessor and its relatives in assembly and other languages.
sburrow
Posts: 833
Joined: 09 Oct 2021
Location: Texas

Re: CardChasm

Post by sburrow »

Alright, after some tears and morale loss, I'm back at it. I'm basically starting with a blank document, and slowly copying code on a 'as needed' basis. Here is a sample of what I'm doing:

Code: Select all

game_function
LDX game_state
LDA game_function_table+0,X
STA game_jump_low
LDA game_function_table+1,X
STA game_jump_high
JMP (game_jump_low)

game_function_00
; sub-routines here
RTS

game_function_02
; sub-routines here
RTS

...

game_function_table
.WORD game_function_00
.WORD game_function_02
...
I've also been renaming my variables. So things like "tun_scr" has turned into "tunnel_scroll", etc. I figured, why not?

So far it's going well. I really am having a mentality of "I'm not putting this in unless it's needed in this particular game state". Eventually I will be reusing nearly all my code, but I want to do it responsibly :)

Classes started again this week, so I've not been able to do as much as I wish here, but I'm fitting in between this and that.

Thanks everyone! Your advice has helped a lot.

Chad
teamtempest
Posts: 443
Joined: 08 Nov 2009
Location: Minnesota
Contact:

Re: CardChasm

Post by teamtempest »

Judging from the names you've given the state functions, I assume you've already noticed that the current state needs to be scaled by two in order to access the dispatch table. But just in case...:)

If you are using a 65C02 processor, note that there is a JMP (ABS, X) instruction. It would be a bit smaller and faster to do this:

Code: Select all

LDX game_state
JMP (game_function_table, X)
or if you decided to number states by ones starting with zero and going up to 127:

Code: Select all

LDA game_state
ASL
TAX
JMP (game_function_table, X)
However, your game function skeletons end with an RTS instruction. If you enter them via a jump, there is no address on the stack to make a return to. One way around that might be something like this:

Code: Select all

mainloop:
  LDA game_state
  ASL
  TAX
  JSR dispatch
  BRA mainloop

dispatch:
 JMP (game_function_table, X)
If that doesn't appeal, another might be to end each function with JMP mainloop instead of RTS. Or if a little extra space doesn't bother you, even end each function with the dispatch to the next state:

Code: Select all

LDA game_state
ASL
TAX
JMP (game_function_table, X)
That assumes there is no additional housekeeping going on every time through the main loop, in which case this would be a Bad Idea :-)
sburrow
Posts: 833
Joined: 09 Oct 2021
Location: Texas

Re: CardChasm

Post by sburrow »

teamtempest wrote:
If you are using a 65C02 processor, note that there is a JMP (ABS, X) instruction.
Unfortunately the NES uses a plan 6502 without the ability to do that. Thus the use of JMP (ABS) instead.
teamtempest wrote:
However, your game function skeletons end with an RTS instruction.
The way I call this sub-routine is through a JSR:

Code: Select all

	; clear v-blank flag
	LDA #$00
	STA vblank_ready

main
	; wait for v-blank flag
	LDA vblank_ready
	BEQ main

	; clear v-blank flag
	LDA #$00
	STA vblank_ready

	; set background color and sprite colors
	LDA ppu_status
	LDA #$3F
	STA ppu_addr
	LDA #$10
	STA ppu_addr
	LDA background_color
	STA ppu_data
	LDX #$01
@palette_loop
	LDA card_palette_array,X
	STA ppu_data
	INX
	CPX #$10
	BNE @palette_loop

	; set ppu nametable and sprite size
	LDA #$B0
	ORA tunnel_name
	STA ppu_ctrl

	; set ppu scroll
	LDA ppu_status
	LDA #$00
	STA ppu_scroll
	LDA tunnel_scroll
	STA ppu_scroll

	; trigger oam dma
	LDA #$02
	STA oam_dma

	; enable rendering
	LDA #$1E
	STA ppu_mask

	; store buttons
	JSR buttons

	; check game state to do what is needed
	JSR game_function

	; wait for sprite zero hit
@zero1
	LDA vblank_ready
	BNE @jump
	LDA ppu_status
	AND #$40
	BNE @zero1
@zero2
	LDA vblank_ready
	BNE @jump
	LDA ppu_status
	AND #$40
	BEQ @zero2

	; delay to make look pretty
	LDX #$9C
@wait
	DEX
	BNE @wait

	; set ppu nametable and sprite size
	LDA #$B0
	STA ppu_ctrl

	; set ppu addr (not scroll)
	LDA ppu_status
	LDA #$23
	STA ppu_addr
	LDA #$00
	STA ppu_addr

	; wait to disable rendering
	LDY #$03
@blank1
	LDX #$28
@blank2
	INX
	BNE @blank2
	DEY
	BNE @blank1

	; disable background
	LDA #$16
	STA ppu_mask

	; short wait for v-reg to be good
	LDX #$1C
@blank3
	DEX
	BNE @blank3
	
	; disable everything
	LDA #$00
	STA ppu_mask

	; loop back
@jump
	JMP main
That's the main loop. Obviously lots of bookkeeping for the NES. But the main computation takes place in the 'game_function' sub-routine.

All good, thank you for your suggestions :)

Chad
sburrow
Posts: 833
Joined: 09 Oct 2021
Location: Texas

Re: CardChasm

Post by sburrow »

Alright! I'm back on track at this point. The game-state concept is in full roll-out and working better than ever! See attached pics.

You have already seen what I am doing differently above, but there are some particulars I'll share. I have "game_state" which determines which sub-routines I'm running, but I also have "game_delay" which is used on some of the game states to literally delay going to the next state. This is useful for special task game states. For example, "shift cards upward" game state isn't something I'm going to stay in for long, but I want to wait for it to complete. Thing is, I'd rather use the game_delay whenever possible so that I that I have a universal method of calling for delays. The *only* time I'm using a different variable to check to move to another state is the walking effect, because it's variable and changes depending on which card is selected.

tl;dr I'm using a universal delay system alongside the game state to insure uniformity.

At this point I have only the "titles" left to implement from old code to new code. However, I am debating if I will even use them again! I have a lot of space in the bottom HUD, and it might be better putting info down there instead. Castlevania and even Kirby did something like that, where the enemy health bar was actually in the HUD space, not above the enemy's head or something (though for different purposes perhaps).

I spent a LOT of time this morning reorganizing my pattern tables. I really wanted it to look more organized, especially my sprites. What I have now is very beautiful on the backend, though you won't ever see that from the user side.

Next up is more attack animations, such as fire, electric, etc, but also healing effects. I need to eventually put in the health bar too, which I think I'm going to use a sprite along with the name table to get a cool 'seamless' effect. Then there are little things like *actually* shuffling the deck, enemy positions, and then the 'campsite' which is kind of like the main menu.

Thanks for the advice earlier, the results are very nice :)

Chad
Attachments
CardChasmNES-Screenshot-1-16-26.png
CardChasmNES-Gameplay-1-16-26.gif
CardChasmNES-Gameplay-1-16-26.gif (3.36 MiB) Viewed 906 times
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: CardChasm

Post by barnacle »

Might you not achieve a consistent delay using a system tick?

On each tick, your current state performs its necessary functions, selects the next state, and then pauses until the next tick. That way your delays are independent of the time it takes to perform those functions, unless they have to do so much that they run out of time before the next tick. And at that point, you'd have a stutter anyway?

Neil
User avatar
BigDumbDinosaur
Posts: 9425
Joined: 28 May 2009
Location: Midwestern USA (JB Pritzker’s dystopia)
Contact:

Re: CardChasm

Post by BigDumbDinosaur »

barnacle wrote:
Might you not achieve a consistent delay using a system tick?
Funny you should mention that, as I was going to suggest the same thing.  :mrgreen:

While I have never been interested in writing gaming software, I did study some games written for the Commodore 64 to see how timing was implemented (numerous hours peering into Jim Butterfield’s Supermon 64).  Many of these programs utilized VIC-generated raster IRQs to sequence them and generate predictable delays, a capability that Commodore exploited in the C-128.  The raster IRQ was what produced smooth action and also made it possible to program more than eight sprites.

Chad is developing on the NES, with whose hardware I’m not familiar—other than having an NMOS 6502.  However, I’d be somewhat surprised if no timer or raster IRQ is possible.
x86?  We ain't got no x86.  We don't NEED no stinking x86!
sburrow
Posts: 833
Joined: 09 Oct 2021
Location: Texas

Re: CardChasm

Post by sburrow »

barnacle wrote:
Might you not achieve a consistent delay using a system tick?
BigDumbDinosaur wrote:
However, I’d be somewhat surprised if no timer or raster IRQ is possible.
Right! The NES has NMI and IRQ capabilities of course. The NMI is tied to the PPU (picture processing unit) which is essentially what drives the video signal separate of the 6502. That PPU generates the NMI once a frame during V-blank. My game state timer is ticked down once per frame, so if I wanted a 1 second delay I would start with #$3C (60 in decimal). At this point to keep things going fast for the player I'm doing 1/2 second delays on most everything.

Unfortunately the IRQ is not nearly as easy to implement. On the base NES hardware, you can use it for audio purposes, but outside of that, or even inside of that, it is near impossible to use consistently. And I've tried, many times now. Instead they use Sprite Zero Hit, which you have to poll for, but that is the most commonly used method for making hud's and menus. If you notice that white horizontal line in the bottom-right corner of the screen, that's Sprite Zero. Later mappers like MMC3 have IRQ functionality based on scanlines, which essentially eliminates the need for Sprite Zero Hit. But I am using the UNROM mapper because if I did want to make my own cartridge it would be far far easier to make using some basic RAM, ROM, and 1x or 2x 74' logic chips.

As a reminder, I did create my own NES emulator from scratch :) So I am pretty familiar with how the NES hardware operates, generally.

A small update: Been working on battle animations all morning. I got a REALLY good lightning animation using palette swaps and sprite reflections. Some others are meh, but it'll do for now. I have a few more to do, but I'm almost done, then I need to do health bars and all that.

Thanks everyone :)

Chad
sburrow
Posts: 833
Joined: 09 Oct 2021
Location: Texas

Re: CardChasm

Post by sburrow »

Quick update:

I finally put my project on GitHub: https://github.com/stevenchadburrow/CardChasm

Added a health bar and other little things. I also finally split my code files into categories, making it easier to find something but harder to alter names. Overall it will be for the better.

Thanks everyone!

Chad
Attachments
CardChasmNES-Screenshot-1-19-26.png
sburrow
Posts: 833
Joined: 09 Oct 2021
Location: Texas

Re: CardChasm

Post by sburrow »

Updates:

It has been such a busy week that I had so little time to code. Weeks like this make me wonder what the heck I got myself into to deserve it.

This morning I was able to add some small battle mechanics such as elemental weaknesses, guarding against attacks, healing, and then phrases. In the original game, the enemies sometimes don't attack but say something silly. This helps indicate where they are in their attack pattern and adds some flavor. Not sure how I want to use it, but it's there. See attached.

Two things coming up next:

1) I need to figure out randomizing cards and enemies positions. My random function is not great and it shows: Current = 5 * Previous + 17. I was thinking of making a 256-byte table, but not sure how to use it efficiently. Any moderately simple suggestions are welcome!

2) More enemies. I was able to make it so that I can import new enemy portraits and battle stats very easily, but I need to actually make a dozen or so of them now. And without AI!

After these, the 'main game' is nearing the finish state. I need to make the 'campsite' which is where you switch out cards from your deck and functions as a level select menu. Then different tunnel backgrounds, and save/load features. Getting closer!

Thanks everyone.

Chad
Attachments
CardChasmNES-Screenshot-1-23-26.png
SamCoVT
Posts: 344
Joined: 13 May 2018

Re: CardChasm

Post by SamCoVT »

sburrow wrote:
1) I need to figure out randomizing cards and enemies positions. My random function is not great and it shows: Current = 5 * Previous + 17. I was thinking of making a 256-byte table, but not sure how to use it efficiently. Any moderately simple suggestions are welcome!
There are several folks on here who are very knowledgable on pseudo-random number generators, but I'll just mention I've had good luck with Linear Feedback Shift Registers (LFSR) as they are quite simple to code for a reasonable amount of pseudo-random. The math looks scary, but it's actually really easy - just look at a code example. Do make sure to pick a known-good mask to XOR with, based on the number of bits you want, as only a handful of values produce good results and are free of short cycles.
User avatar
gilhad
Posts: 85
Joined: 26 Jan 2024
Location: Prague; Czech Republic; Europe; Earth
Contact:

Re: CardChasm

Post by gilhad »

Some ideas for the table:

The simplest is to have 1 byte variable Rptr as index to this table and random=table[Rptr++].

This leads to 256 long loop of values, which for lot purposes is long enought so nobody notice nothing.

If you use also 2 or more bytes random values sometimes it may make this loop even longer.

I would use random for as many things as convenient, so the efect of loop will be even more hidden.

Also there is another source of randomness - player - so I will use it as well, for example asign different values to his actions and when he do some action, take(and discard) so many random numbers. You may also discart random number say every 0.1 second while waiting for keypress - the number of skipped randoms will vary somehow to be less predictable.

If there are some levels or camps or any event, which is not too often, but happens again and again and if there is a little time, you may exchange two rows in the table based on something slightly random (like exact score, exact time or so) to change the table a little while keeping it random.

After all it does not matter, if things are really random, what matter is, if player does not see it as boring repetitions. (And using him as source of randomness too, you may subvert his attempt to "learn the randomness" :) )
sburrow
Posts: 833
Joined: 09 Oct 2021
Location: Texas

Re: CardChasm

Post by sburrow »

SamCoVT wrote:
I'll just mention I've had good luck with Linear Feedback Shift Registers (LFSR) as they are quite simple to code for a reasonable amount of pseudo-random
Interesting! I think I heard some NES games use this, but I cannot remember which ones in particular right now. It's funny to see how it works :) I did a bit of reading on it, and it seems that they are good at shorter random values, but not larger amounts unless I start using more bits. Overall it's something to consider, thank you.
gilhad wrote:
The simplest is to have 1 byte variable Rptr as index to this table and random=table[Rptr++].
I had been thinking of that, but I just didn't like that it's always the same. However, like you said, the idea is that the player won't see it anyways. I feel like a purest, but there isn't need for purity here :)

What I did just now was create a C program that made a 256-byte randomized table from $00 to $FF, which I copied to my 6502 code. When I'm looking for a small random value, I just use the original code, but now for longer stretches I'm doing:

Code: Select all

JSR random_function ; original function, current = 5 * previous + 17
TAY
LDA randomized_table,Y
TAY
@loop
INY
LDA randomized_table,Y
...
This seems to give me something alright for now. Again, I'm only using this when doing large swaths of values, like shuffling the deck of cards or creating enemy positions in the tunnel. For small one-off values, usually I just do:

Code: Select all

JSR random_function
And that's alright, for now, I guess.

Thank you for the suggestions, I will have to look back at the LFSR stuff for sure :)

Chad
sburrow
Posts: 833
Joined: 09 Oct 2021
Location: Texas

Re: CardChasm

Post by sburrow »

I think I'm learning some techniques on this pixel art. First I go to a Perchance AI Image Generator https://perchance.org/1b4m3f1dvk. I find something I like, then scale it from 512x512 down to 64x64. I add a layer on top and set the original image's opacity to 50%. I then trace with black the outline, adjusting to what I want. Flood-fill white and grey, and red background (transparent). Then lots of cleanup and adjustments to make it look a little better. I definitely can tell I have a style going on here, so at least they all look the same, even if not the greatest.

I'll be working on a new tunnel background now. I'm thinking "trees" as the theme, since I can draw it with or without a ceiling. I also need to redo the randomization again. Instead of the theoretical 25% chance of enemy on a tile, I need to say "1 to 4 tiles between enemies", making it more consistent.

Here in Texas the winter blast is in full effect right now, and we are all very under prepared here, so everyone is staying home and cancelling plans. I suppose it's a good time to code :)

Thanks everyone, for the initial encouragement to do my own pixel art.

Chad
Attachments
Mine1.png
Mine1.png (1.35 KiB) Viewed 731 times
Mine2.png
Mine2.png (11.32 KiB) Viewed 731 times
Mine3.png
Mine3.png (12.64 KiB) Viewed 731 times
Mine4.png
Mine4.png (11.3 KiB) Viewed 731 times
sburrow
Posts: 833
Joined: 09 Oct 2021
Location: Texas

Re: CardChasm

Post by sburrow »

Been doing a lot of artwork! Lots of learning going on :) See attached. I wanted some new levels before I move on to the next thing. My wife helped me with the perspective and little things here and there, but I think I have the hang of it now. I still feel I can alter these a bit to make them even better, but overall the feeling is there and that's what matters.

It's a snow day here in Texas, no school, so I'm at home doing this I guess. The cats beside me look cold since I'm keeping the ambient temperature pretty low, but it's better for the electric grid and I feel like I aught to feel cold if it's actually cold outside :) [ Weird? ]

Thanks everyone!

Chad
Attachments
Cave.png
Forest.png
Castle.png
sburrow
Posts: 833
Joined: 09 Oct 2021
Location: Texas

Re: CardChasm

Post by sburrow »

Well, it's been a couple of weeks. This project isn't finished! I just had a different 'project' come up that was time sensitive, and today is the first day back at this :) I have to admit it was a bit hard to get started again this morning, but after an hour or so of fiddling with some code I am starting to feel more engaged.

First I wanted to set up an actual deck of cards, and not just randomized values all the time. After some time I implemented a way to use the cartridge RAM to save whatever cards you have in your deck. But then I realized my card shuffling function was definitely not working, so I fixed that. I also redid the randomization for the enemies on the path, and it is a whole lot better! After all of this I started making pixel art again, see attached. I'm not terribly happy with my 'archer' enemy, but I think the point is getting across. At this point I will just have to come back and touch up on stuff I don't like later.

I want to make 2 more enemies, to total up to 7. I already have 3 levels so I'm good there for now. Then I need to work on the 'campsite' (aka main menu / level select) and finally being able to swap out cards between your deck and sideboard. The original game has a fixed deck of 15 cards and then 5 'booster' cards of your choice, but in my opinion it is just meh because it doesn't allow much customization. Here the player will have a 40 card deck and (right now) up to 64 cards in the sideboard that you can swap in and out. IDK what will happen when the players fill up their sideboard, but we'll come to that later :)

There's the update! Mostly just saying, "This project is still alive!" Thanks everyone :)

Chad

EDIT: Just reminding you that my GitHub page has the latest code, https://github.com/stevenchadburrow/CardChasm
Attachments
CardChasmNES-Screenshot-2-7-26.png
Post Reply