Jump to content
IGNORED

cribbage squares solitaire - beta 1


h0trod

Recommended Posts

After 20 years of thinking about it, I decided it was time I tried to make something.  

 

Here's a very ugly but mostly functional prototype of a Cribbage Squares solitaire game.  The aim is to arrange 16 playing cards, plus a 17th "starter" card into a 4x4 grid (and starter space) such that the total value of the 8 cribbage hands formed by the horizontal and vertical rows of the grid + the starter card (which is shared by every hand) is as high as possible.

 

Here's a brief description on Wikipedia: https://en.wikipedia.org/wiki/Cribbage_Square_Solitaire

 

The fire button starts the game, and presents you with the first card to place.

 

Use left/right to move the current card in the grid.  Use the fire button once you've selected your spot for the card.  The starter card goes in the one available slot on the fifth row.

 

When all cards have been placed, your grid will be scored.  I think over 100 is pretty darn good.  Press fire a couple times to play again.

 

Some development notes:

  • This is my first Atari 2600 code, and my first assembly code of any kind.  It's really, really bloated.
  • I worked out the logic and scoring first and then "borrowed" a six digit kernel to use as a display routine to see if I could get something functional.  Good: something functions!  Bad: I understand the 6 digit display routine in broad strokes, but not enough that I can manage to even center it in the screen.  But the biggest problem with this is there's no time to set the P0/P1 colors and make the cards Red/Black appropriately.  Is the route to a 4 sprite per line kernel the same approach as six sprite but with each player doubled instead of tripled, or is there another common method I can apply?  (Flicker is also an option.)
  • Unexpected source of trouble #1: Joystick debounce logic.  This took forever to get right and I still feel like I'm jumping through too many hoops.  But it seems to work.
  • Unexpected source of trouble #2: Randomization.  I grabbed a common randomization routine and use it to shuffle the cards.  I run the "Randomize" routine until the user presses the fire button, and then I shuffle the cards, at which point I need a lot of random numbers consecutively with no opportunity for user input to introduce further randomization.  I have a 16 bit randomization routine but currently only using one of the random bytes in the shuffle.  I'm not sure how to use a 16 bit random number to help me generate random digits between 0-51 for shuffling?  I think this is limiting me to 255 possible sequences of cards that the player could get. 
  • Expected source of trouble: There must be an elegant or fast method of scoring Cribbage hands but this ain't it.  The timing definitely goes off the rails for a few frames while your score is being calculated.

 

Props to Andrew Davie, SpiceWare, Steven Hugg, and Gustavo Pezzi and the AtariAge community for putting the resources out there that allow a beginner to get started.

cribbage.bin

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

22 hours ago, h0trod said:

 

  • Unexpected source of trouble #2: Randomization.  I grabbed a common randomization routine and use it to shuffle the cards.  I run the "Randomize" routine until the user presses the fire button, and then I shuffle the cards, at which point I need a lot of random numbers consecutively with no opportunity for user input to introduce further randomization.  I have a 16 bit randomization routine but currently only using one of the random bytes in the shuffle.  I'm not sure how to use a 16 bit random number to help me generate random digits between 0-51 for shuffling?  I think this is limiting me to 255 possible sequences of cards that the player could get. 

With only one point of randomness, you will not be able to produce the 52! possible permutations of card shuffles. To accurately reproduce all possible shuffles, it would require a random number generator that used a 226-bit seed (29 bytes).

 

I don’t think you want to devote that much RAM to randomizing the deal. It might be better to store the 32-bit random value on any frame that the user makes an input, and then swap that card with then next random value that the user makes an input.

 

 

 

Link to comment
Share on other sites

1 hour ago, CapitanClassic said:

With only one point of randomness, you will not be able to produce the 52! possible permutations of card shuffles. To accurately reproduce all possible shuffles, it would require a random number generator that used a 226-bit seed (29 bytes).

 

I don’t think you want to devote that much RAM to randomizing the deal. It might be better to store the 32-bit random value on any frame that the user makes an input, and then swap that card with then next random value that the user makes an input.

 

I like the idea but I am confused about the details when it comes to using a random value of more than one byte to generate a number between 0-51 for swapping purposes.  With a single random byte, I can just use the low six bytes and throw the number away if it's > 51.  But generating a multi-byte random number and throwing away all but six bits of one of the bytes does not seem like an improvement.  I think there's something fundamental here that I'm not understanding.

Link to comment
Share on other sites

2 hours ago, h0trod said:

I like the idea but I am confused about the details when it comes to using a random value of more than one byte to generate a number between 0-51 for swapping purposes.  With a single random byte, I can just use the low six bytes and throw the number away if it's > 51.  But generating a multi-byte random number and throwing away all but six bits of one of the bytes does not seem like an improvement.  I think there's something fundamental here that I'm not understanding.


It isn’t clear to me how your random deck is generated. Can you post the code/pseudo code?

 

One possible way to shuffle a deck in O(N) time is https://en.m.wikipedia.org/wiki/Fisher–Yates_shuffle

 

The meaning behind a sufficiently shuffled deck is for there to be an equally likelihood that each card could be at any of the possible 52 positions in the deck. A way to do this is to take all 52 cards in order and then swap the top card with a random card in the remaining non-sorted cards in the deck. The. Take the next card and do the same, with the remaining n-1 cards.

 

Imagine if you only have a pseudo random number generator with a period of 255 (an 8-bit PRNG). That means, that you can only have a maximum of 255 possible shuffles/permutations. After that, the same seed value will result in the exact same shuffle.

 

The problem you will experience with a PRNG that is only 16-bit is that the pattern of “random” values will repeat after 65535 possible shuffles. To accurately be able to generate the number of possible permutations of a real deck of cards requires a much bigger PRNG with a longer period.

Link to comment
Share on other sites

19 minutes ago, CapitanClassic said:


It isn’t clear to me how your random deck is generated. Can you post the code/pseudo code?

 

One possible way to shuffle a deck in O(N) time is https://en.m.wikipedia.org/wiki/Fisher–Yates_shuffle

 

The meaning behind a sufficiently shuffled deck is for there to be an equally likelihood that each card could be at any of the possible 52 positions in the deck. A way to do this is to take all 52 cards in order and then swap the top card with a random card in the remaining non-sorted cards in the deck. The. Take the next card and do the same, with the remaining n-1 cards.

 

Imagine if you only have a pseudo random number generator with a period of 255 (an 8-bit PRNG). That means, that you can only have a maximum of 255 possible shuffles/permutations. After that, the same seed value will result in the exact same shuffle.

 

The problem you will experience with a PRNG that is only 16-bit is that the pattern of “random” values will repeat after 65535 possible shuffles. To accurately be able to generate the number of possible permutations of a real deck of cards requires a much bigger PRNG with a longer period.

I am doing a Fisher-Yates shuffle, as you guessed.  And it is not my intent to actually create all 52! possible shuffles with my code.  I would be happy to understand how to use two random bytes effectively to create 65535 possible shuffles.  (And, I'd like to seed the RNG differently every time, I've read about loading from INTIM - is this value not deterministic upon startup?)

 

ShuffleCards: subroutine
  ldx #51           ; X = offset (card to swap)
.keepShuffling:
  jsr Randomize     ; A = Random 0 - 255
  and #%00111111    ; A = Random 0 - 63
  sta Temp          ; Temp = A = Random 0 - 63
  cpx Temp          ; Compare X (offset for card to swap) to Temp = A = Random 0 - 63.
  bcc .keepShuffling  ; X < Random Number, this is bad, find another random number.
                    ; Otherwise, continue; Temp = Random 0 - 63 <= X
  lda Deck,X        ; A = Card to be swapped out
  pha               ; Top of stack = Card to be swapped out
  ldy Temp
  lda Deck,Y        ; A = Random Card
  sta Deck,X        ; Store Random Card where card to be swapped out was
  pla               ; Pull Card to be swapped out off of stack
  sta Deck,Y        ; Store Card to be swapped out where the random card was
  dex               ; decremment X
  bne .keepShuffling  ; repeat until x is zero
  stx DeckOffset    ; X = 0, set the DeckOffset to the top card

  rts

As you can see, I use an AND bitmask to go from range of 0-255 to a range of 0-64.  I don't understand how to effectively go from 0-65535 to 0-255.  The AND bitmask doesn't work, because then I'm just ignoring one of the random bytes wholesale and I'm back to where I started.  Should I be combining the two random bytes in some way (EOR?) and then applying the bitmask?

 

Thanks for your thoughts!

Link to comment
Share on other sites

I wanted to see if I could create a four-cards-per-row kernel with enough time to set the correct COLUP1/COLUP2 card colors without having to resort to the VDELP0/VDELP1 tricks of the six line kernel.  (I guess this is my first experience "racing the beam", stepping through the Stella debugger keeping track of which clock cycles things get displayed on and how much time I have to do updates.)

 

I don't love the "wide" look, and it's off-center to the right because I needed more time at the beginning of the cycle to prep some data.  I could probably rectify this by moving some of the loading to the end of the previous scanline.

 

I think my next experiments will be with "two copies medium" players to get the cards closer together, I expect I will have to resort to VDELP0/VDELP1 tricks to make that work, but we'll see.  I've attached the latest version below.

 

cribbage.thumb.png.9688b8f633257ec691f341c6267fe414.png

 

cribbage.bin

 

Link to comment
Share on other sites

I didn't have much luck with using both players and two copies medium.  I wasn't able to find enough time in the right places to get the bitmaps loaded, color loaded, bitmap stored to GRPx, and color stored to COLUPx.  I don't know all (any) of the tricks, but there is a lot to do in very little time.  The best I was able to come up with is using an empty-graphics line between every display line to buy some extra cycles to do some of the pre-display pre-processing for the next line.  It's unattractive, and ultimately uses too many scanlines for my purposes anyways.  

 

If anyone knows of a game that uses two copies medium with both players to achieve the effect of four different horizontal sprites with arbitrary colors, please let me know, I'd love to dig into it!  (I'll probably also ask this in a separate post.)

 

Screenshot of my failure attached, for laughs.

 

cribbage_1.thumb.png.eb48e4ceb8e3b999e1520ce772707d4c.png

Edited by h0trod
typo
Link to comment
Share on other sites

If you go for a white card on black background, the problem is easier. You can use a red (asymmetric) playfield to determine the card color. You'll need to suppress the color/pf from repeating where there are no cards, but the cycles aren't as tight further away from your cards.

  • Like 1
Link to comment
Share on other sites

9 hours ago, RevEng said:

If you go for a white card on black background, the problem is easier. You can use a red (asymmetric) playfield to determine the card color. You'll need to suppress the color/pf from repeating where there are no cards, but the cycles aren't as tight further away from your cards.

Clever, I will give it a shot.  Thanks.

  • Like 1
Link to comment
Share on other sites

I'm going to suggest something a lot simpler for your shuffle routine: Don't shuffle the cards in the first place. ?

 

Just start out with all the possible card values in a block of memory somewhere, in whatever order you want to generate them. Maintain a pointer that refers, at any given moment, to one of the cards. Every frame, increment (or decrement) that pointer. When it gets to the top (or bottom), bump it down (or up) to wherever the other end currently is*. So it's as if the game is constantly cycling through every remaining card, one after the other, all the time.

 

When the player starts the game with that first "fire", they get whatever card the pointer is currently pointing at. Remove that card from the "deck" and then remove the "memory hole" by copying values above it down one byte (or below it up one byte). Adjust references to the end of your memory block accordingly, or you could use a terminator value, such as zero, to signal the end (or beginning) of the block.

 

When they place that card with the fire button, you then "deal" then next card that's at whatever position the constantly-cycling pointer is at that point. There's no way a human can possibly time where the pointer is, so it is essentially random. Repeat the process of shifting memory to remove the "hole", waiting for the next "fire" to figure out what the next card is, etc.

 

This will appear, for all intents and purposes, as if you are dealing from the top of a randomly-shuffled deck. But really it's more like you're dealing a card from a randomly-selected place in an unshuffled deck each time, with the "randomness" supplied by the human player's own timing. The effect should be the same either way.

 

I did something similar on an Apple 1 game I wrote recently, although with dice roll outcomes rather than cards (and without needing to remove values each time like you will). It's also 6502 assembly, so feel free to take a look:  https://github.com/JeffJetton/apple1-shut-the-box

 

* You might want to keep careful track of how many cycles the loop back to the bottom (or top) of the memory block takes when you reach the end, and then make sure that you take up the same number of cycles when you don't reach the end and instead just do the increment or decrement. This could be done by putting in one or more nop instructions or something like that. Otherwise, the card at the top (or bottom) will be slightly more likely to be dealt out, since the pointer will "linger" there slightly longer than on all the other cards.

Edited by JeffJetton
Link to comment
Share on other sites

5 hours ago, JeffJetton said:

Just start out with all the possible card values in a block of memory somewhere, in whatever order you want to generate them. Maintain a pointer that refers, at any given moment, to one of the cards. Every frame, increment (or decrement) that pointer. When it gets to the top (or bottom), bump it down (or up) to wherever the other end currently is*. So it's as if the game is constantly cycling through every remaining card, one after the other, all the time.

I like this idea, thanks.  The only downside I can see is that currently I can stick the shuffled deck at the end of memory and not really worry if the stack overflows into the end of the deck, because I know I'll only be using the top 17 cards or so (maybe a few more with some variants I'm hoping to add.)  With this method I need to be careful none of the cards get clobbered.  I'm making some RAM saving changes though and should be able to make it work.

Link to comment
Share on other sites

Small update with a two-copies-medium kernel (thanks @Karl G) and some visual "slots" for cards.  Also added up / down controls.

 

Screen shot:

cribbage_2.thumb.png.e79574d8ad07804c954a07c58e27a9e5.png

 

Bin:

cribbage.bin

 

Next steps:

  • Instead of handling 13 card value (A, 2, 3 .. Q, K) sprites and 4 suit sprites separately, try 52 card (value + suit combined) sprites.  This should free up 8 bytes of RAM I'm using for pointers into the suit sprite graphics table at the expense of some ROM, which I can currently spare.
  • Explore the white-sprite over black background/red playfield technique for displaying cards that @RevEng suggested in a comment above.
  • Incorporate @JeffJetton's shuffle alternative also suggested in a comment above.

It's not a great game but it's a good learning experience and I appreciate all the help from the community.  Maybe as I get better at this, there's a chance of this becoming a full cribbage game in the future.

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

Broke out the Harmony cart today to give this a try on some real hardware: it works, but strangely requires multiple fire button presses before the first card is successfully dealt.  After that it proceeds normally until the next game, then, same issue.  This doesn't occur on Stella.  Should be a fun one to try and figure out.

 

 

Link to comment
Share on other sites

20 minutes ago, h0trod said:

Broke out the Harmony cart today to give this a try on some real hardware: it works, but strangely requires multiple fire button presses before the first card is successfully dealt.  After that it proceeds normally until the next game, then, same issue.  This doesn't occur on Stella.  Should be a fun one to try and figure out.

 

As a developer you should turn on Developer Settings, it exposes bugs in your code that work OK on some hardware but not on other.

 

403164919_ScreenShot2020-06-18at1_06_01PM.thumb.png.e8974a70e1c0210a3cf8247b3fb35f5f.png

 

In this case I see you're doing this:

lda INPT4
sta ram_81
cmp ram_85
bne Lf15f

 

So are probably expecting A to be either $80 or $00.  The problem is on real hardware only the bits that are connected in the TIA registers can be trusted.  Looking in the Stella Programmer's Guide you'll find this table:

 

40712993_ScreenShot2020-06-18at1_11_15PM.thumb.png.b40ac53813e756036f2f16bba676511b.png

 

So for CXBLPF, INPT0 thru INPT5 you can only count on bit 7 being valid, for the other collision registers only bits 7 and 6 are valid.

 

Because if this, if you're not holding down the fire button then reading INPT4 could return $80 one time, and $A5 the next.

 

The Developer Setting that exposes this issue is Drive unused TIA pins randomly on a read/peak.  Player settings does not enable this option because there are a number of games from back in the day that won't run correctly if it is turned on.

 

  • Like 3
  • Thanks 1
Link to comment
Share on other sites

10 minutes ago, SpiceWare said:

 

The problem is on real hardware only the bits that are connected in the TIA registers can be trusted.

 

Wow.  I'm not sure I ever would have figured this out since I wasn't aware of the Developer settings in Stella or of this behavior of the TIA registers - and within 15 minutes of my post you laid out a comprehensive explanation.  Thanks again.

Link to comment
Share on other sites

21 minutes ago, h0trod said:

Wow.  I'm not sure I ever would have figured this out since I wasn't aware of the Developer settings in Stella or of this behavior of the TIA registers - and within 15 minutes of my post you laid out a comprehensive explanation.  Thanks again.

Your welcome!

 

An easy fix if you're saving the value for later is to add an AND #$80:

lda INPT4
and #$80
sta ram_81
cmp ram_85
bne Lf15f

 

If you're not saving it for later then use BIT and you won't trample what's in A, X, or Y registers.

bit INPT4
bmi FIRE_NOT_PRESSED
; fire pressed logic here


or

bit INPT4
bpl FIRE_PRESSED
; fire not pressed logic here

 

Link to comment
Share on other sites

8 minutes ago, SpiceWare said:

 

An easy fix if you're saving the value for later is to add an AND #$80:

 

If you're not saving it for later then use BIT and you won't trample what's in A, X, or Y registers.

 

I use BIT to check, but also save the value of the last SWCHA / INPT4 state for debouncing purposes - which is what's causing me trouble.  I will have to check on which bits of SWCHA can be trusted as well.

Link to comment
Share on other sites

A small correction to Spiceware's post above: bit 6 is actually always defined and it's 0 if the TIA register doesn't use it.


It was my understanding too that only bits actually used by the TIA registers were valid, until, during tests of the new TIA core in Stella 5.0, a rom showed a different behaviour for bits 6-7.


The explanation is that the TIA has output transistors to drive bits 6 and 7 and,  on a read access, will always drive those (also in addresses that do not correspond to any register, like $0E, $0F and mirrors).

If unused, they default to 0.

 

Bits 0-5 are undriven and you shouldn't rely on their value. (and you should use the mentioned "Drive unused TIA bits..." developer option to easily spot bugs)

 

There's a recent bugfix in Stella regarding this (bit 6 of INPTx was considered undriven like bits 0-5) that will be in the upcoming release.

 

  • Like 3
Link to comment
Share on other sites

What @SpiceWare pointed out was certainly an issue, and fixing it made the game behave with the Developer settings on in Stella; but the game is still behaving improperly on real hardware.  The problem appears to be in this code, which executes in the Vertical Blanking period:

 

WaitForFireButtonToStart:
  jsr IncrementDeckPointer
  lda #%10000000
  bit INPT4
  bne VBlankWait
  lda INPT4
  and #$80
  sta Debounce

  lda #0
  sta GameStatus


GoGame:
  lda Card        ; load Card into A.  at game start and after a card has just been placed this should be zero.
  bne .jumpOut    ; if it's not zero we already have a card and can skip all this
  ldx DeckPointer  ; if we don't have a card we execute this code.. get the current deck pointer
  lda Deck,X      ; and get the card at that offset, the "top" card
  sta Card        ; store it in Card, now we have a Card.  now we want to put that card in the first open position in the grid.
  jsr FixMemoryHole ;
  ldx #0          ; we're going to try positions until we find an empty one starting with 0
.tryAgain
  lda Grid,X      ; Grid,0 is it empty?
  beq .foundEmpty ; if it is - run the foundEmpty code
  inx             ; if it's not, increment X and try again
  jmp .tryAgain
.foundEmpty       ; now we've found an empty slot
  stx CardPos     ; set CardPos = X
  lda Card        ; get the Card back in A
  sta Grid,X      ; and put it in the grid at Grid,X
.jumpOut
                  ; if we already have a card we don't have to do any of this stuff!
                  ; the fire button handler should set the Card back to zero,
                  ; and increment CardsPlayed

  ; END Code to execute in VBlank

 

The symptom is that tapping the fire button flickers the screen as if I'm outputting too many scanlines, but the first card is not played to the board.  The same flicker occurs in Stella, and I do see the scan line count momentarily flick higher than 262, but the first card is always played.  On real hardware, it usually takes about a half-dozen presses before the game starts, and which point it behaves properly.

 

A hint is that if I hold down the fire button on real hardware (in the WaitForFireButtonToStart code), in the failure case (no card played) the screen will go black and remain black until I release the fire button, but in the success case (card played), the screen will flicker and the card will play.  Going to do some trial and error debugging, which is a little painful on real hardware with the setup I have here! 

 

Edited by h0trod
Link to comment
Share on other sites

Good news: found and fixed the problem that was causing the behavior above.  Don't yet understand why it wasn't occurring on Stella.

 

Bad news: fixing this uncovered another bug that is only showing up on real hardware.  

 

All these issues are tie back to my game state and debounce logic which is a mess.  Won't have a lot of time today but I probably need to focus on cleaning that up next.

Link to comment
Share on other sites

31 minutes ago, h0trod said:

Good news: found and fixed the problem that was causing the behavior above.  Don't yet understand why it wasn't occurring on Stella.

 

Bad news: fixing this uncovered another bug that is only showing up on real hardware.

Please describe what the problem was in your code, and how you fixed it.  Then we can look into why Stella isn't detecting it, and perhaps fix it so it does.

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