[stella] Euchre: squeezing in more

Subject: [stella] Euchre: squeezing in more
From: Erik Eid <eeid@xxxxxxxxx>
Date: Sun, 28 Jul 2002 20:50:05 -0400
Good evening, everyone!  I have added a few more features to Euchre in the 
last couple of weeks.

In my previous update, I mentioned that I wanted to add sound, a game-over 
indicator, and if there was space, include a "stick the dealer" option.  I am 
pleased to report that I was able to do all three of these.

With only 130 bytes left at the time, I took a decidedly minimalist approach 
to sound.  I initially wanted to play tunes a few notes in length, but 
abandoned that due to the amount of overhead and sound data it would have 
needed.  I instead looked again to the Atari board and card games for my 
inspiration.  In those, sounds normally occurred only when making a move 
(such as picking up or placing a piece) and sometimes when scoring (such as 
with Othello).  I decided to use only a single sound per event and settled on 
four events: player's team scores, other team scores, player makes a valid 
selection, and player makes an invalid selection.  To make Euchres and 
marches (taking all five tricks) more impressive, I changed the scoring 
routines so it would add to scores in one-point increments, sounding a beep 
for each point.  If you or your opponents succeed in taking all five tricks 
while alone, you'll hear four tones.

I dug up an old message by Nick Bensema on color-cycling 
(http://www.biglist.com/lists/stella/archives/199707/msg00023.html).  
Basically, to accomplish color-cycling, just exclusive-or some set of bits 
before setting a color register.  When not in attract mode, the mask should 
be zero, so that no bits are changed.  During the game over period, I just 
let a counter decrement each frame and change the mask when it rolls over.  
(I apologize for some of the hideous color combinations that result from the 
random masks that are generated.  I kept the background black just so it 
wouldn't be truly offensive to sensitive eyes.  <g>)

I decided to go for gold and add the "stick the dealer" variant.  If the Left 
Difficulty switch is set to A, then the dealer cannot pass after all the 
other players have passed twice.  The dealer must call a trump suit.  This is 
a commonly used variation that is intended to keep the game moving; otherwise 
several hands can go by in which no one calls trump.

After all that was done, I found myself with zero bytes left.

Since there are still some tweaks to make (such as the "west" player putting 
its card down immediately after the human "south", with no pause), I wanted 
to free up some space.  I started up another round of optimizations and 
managed to clear out 78 bytes.

Two changes were very profitable.  In the first, the joystick read routine, I 
noticed I was continuously reloading the joystick value so I could apply a 
different bit mask.  Noticing that bit shifts affect the carry flag, and that 
branches can be made based on the carry flag, I changed this:

    lda SWCHA
    sta T1
    and #J0_Up
    beq JoyMinus
    lda T1
    and #J0_Left
    beq JoyMinus
    lda T1
    and #J0_Down
    beq JoyPlus
    lda T1
    and #J0_Right
    beq JoyPlus
    lda #$00
    sta JoyPause
    beq JoySetDir

to this:

    lda SWCHA
    asl                 ; Move bit 7 (right) to carry
    bcc JoyPlus         ; If carry is clear, stick was moved right
    asl                 ; Move bit 6 (left) to carry
    bcc JoyMinus        ; If carry is clear, stick was moved left
    asl                 ; Move bit 5 (down) to carry
    bcc JoyPlus         ; If carry is clear, stick was moved down
    asl                 ; Move bit 4 (up) to carry
    bcc JoyMinus        ; If carry is clear, stick was moved up
    lda #$00
    sta JoyPause
    beq JoySetDir

(This only works if you do not need diagonal movement.)

I also replaced the jsr-jmp pairs in StageJumpTable with jsr-rts pairs.  I 
put the indirect jump in its own subroutine, which allowed me to use rts
to get out of the jump table after each stage routine finished.  (A side 
effect was that I only had to multiply the stage number by four instead of 
six, saving a few bytes more.)  Since the jump table is fairly long, please 
see the source if you are interested in looking at it.

I finally replaced the "RB" and "LB" indicators that I was using to easily 
spot the bowers.  However, I left them in the source; if you compile with the 
-DBOWER option, they will replace the "J" in the right and left bowers.

I am still not finished with the game.  As I mentioned, there's a few rough 
edges to clean up; the remaining 78 bytes will help make that happen.  If I 
have enough space afterward, I can look into implementing the suggestions 
given by the list readers.  I also still have to create a PAL version.  (Then 
there's also a manual to consider...)

If you haven't already, please give the game a try.  Let me know if it 
behaves oddly and if the sound is a bit too obnoxious.  :)  (Also let me know 
if a computer player ever takes the bid alone.  I almost never see it.  Then 
again, I almost never get a hand I can use alone myself.)

I have attached the source, binary, and Stella profile entry as usual. ?The 
version of vcs.h that I am using until the list decides on a standard is 
at http://www.io.com/~nickb/atari/doc/vcs.h.
"Cartridge.MD5" "80e52315919bd8a8b82a407ccd9bb13f"
"Cartridge.Name" "Euchre (July 28, 2002 pre-release)"
"Cartridge.Manufacturer" "Erik Eid"
"Cartridge.Rarity" "New Release"
"Cartridge.Type" "4K"
"Controller.Left" "Joystick"
"Display.Format" "NTSC"
""

Attachment: Euchre.bin
Description: Euchre ROM

;
; Euchre game program for the Atari 2600 video computer system
;
; Copyright 2001 by Erik Eid (eeid@xxxxxxxxx)
;
; Last update: July 28, 2002
;
; Compiled with the dasm assembler (version 2.12.04) using the -f3 option
;

    processor 6502
    include vcs.h

; Constants

    seg.u defines

CardsInDeck = $18  ; 24 cards in a Euchre deck
CardsInHand = $05
TricksInRound = $05
SuitsInDeck = $04

Team1Color = $88
Team2Color = $38
TableRegionColor = $d4
CardTableColor = $0f
RedSuitColor = $36
BlackSuitColor = $00
MessageRegionDisplayColor = $22
MessageRegionChoiceColor = $84
MessageTextColor = $1c
CursorColor = $82
TrumpDisplayColor = %00101000

RankMask = %00011100      ; Bit mask for rank of a card
DispSuitMask = %01100000  ; Bit mask for suit displayed on a card
RealSuitMask = %00000011  ; Bit mask for suit used when following (the left
                          ; bower becomes the suit of the right bower)
FullSuitMask = %01100011  ; Bit mask for both display and real suit
CardHiddenMask = %10000000  ; Bit mask for determining if a card is hidden
ShowCardMask = %01111111  ; Bit mask to force a card to be shown
HideCardValue = %10000000  ; Bit mask to force a card to be hidden
CardPlayedMask = %10000000
CardPlayedValue = %10000000

; This mask is used only when calculating the strength of a hand
; because they rely on the original rank and suit of a card
RankSuitMask = %01111100  ; Bit mask for rank and suit combined

RightRankValue = %00011100
LeftRankValue  = %00011000
AceRankValue   = %00010100
KingRankValue  = %00010000
QueenRankValue = %00001100
JackRankValue  = %00001000
TenRankValue   = %00000100
NineRankValue  = %00000000

HeartSuitValue   = %00000000
DiamondSuitValue = %00100001
ClubSuitValue    = %01000010
SpadeSuitValue   = %01100011

BlackSuitMask = %00000010
FlipColorSuitMask = %00000001  ; EOR with this to change suit to other suit
                               ; of the same color

PlayersMask = %00000011
NoPlayer = %11111111  ; Since players are numbered 0-3, a 255 indicates
                      ; that no player meets the condition

NoSuit = %11111111
NoCard = %11111111
NoChoice = %11111111

SWACNT = $281  ; Strangely not part of the original vcs.h
VS_Disable = 0  ; Ditto

CenterRankPos = $75  ; Player positions
CenterSuitPos = $66
LeftRankPos = $a2
RightRankPos = $57
LeftScorePos = $31
RightScorePos = $97
LeftTrickPos = $f1
RightTrickPos = $97
BidArrowPos = $35
BidDecisionPos = $b5
InstructionPos = $f5

MessageP0Pos = $c4
MessageP1Pos = $35

NoCursorPos = $ff

MsgNumBlank = $00
MsgNumSelectAction = $01
MsgNumSelectTrump = $02
MsgNumDeal = $03

StageNewGame = $00
StageNewHand = $01
StageGameOver = $02
StageShuffle = $03
StageDeal = $04
StageBidding1 = $05
StageDiscarding = $06
StageBidding2 = $07
StagePlaying = $08
StageAddToTricks = $09
StageAddToScore = $0a
StageBetweenHands = $0b

FramesWait = 90  ; Number of frames to wait for 1 1/2 seconds
FramesShortWait = 45  ; Number of frames to wait for 3/4 second
FramesLongWait = 180  ; Number of frames to wait for 3 seconds
FramesJoyWait = 15

FramesScoreIncrement = 20
FramesValidSelect = 2
FramesInvalidSelect = 10

SoundTeam1Scores = $00  ; Do not change this value!
SoundTeam2Scores = $01  ; Do not change this value!
SoundValidSelect = $02
SoundInvalidSelect = $03

ChoicePass = $00
ChoiceCall = $01
ChoiceAlone = $02

TriggerOff = $00
TriggerOn = $01
TriggerHeld = $02

CursorModeNone = $00
CursorModeCard = $01
CursorModeAction = $02
CursorModeTrump = $03
CursorModeSelectMask = %00000010

TrumpScoreValue = %01000000  ; Value to add to rank for trump cards when
                             ; considering the card to play
LeadSuitScoreValue = %00100000  ; Value to add to rank for cards of the
                                ; led suit when considering the card to play

UnbrokenModifier = %00100010  ; Value to add to rank for cards of an unbroken
                              ; suit when considering the card to lead
BrokenModifier = %00100000  ; Value to add to rank for cards of a broken
                            ; suit when considering the card to lead

; Variables

    seg.u vars
    org $80

Team1Score  ds 1
Team2Score  ds 1
Team1Tricks ds 1
Team2Tricks ds 1
SouthHand   ds 5  ; Cards in a player's hand
WestHand    ds 5
NorthHand   ds 5
EastHand    ds 5
SouthCard   ds 1  ; Cards down on the table
WestCard    ds 1
NorthCard   ds 1
EastCard    ds 1
ImgPtr1     ds 2  ; Pointers to playfield and player images
ImgPtr2     ds 2
ImgPtr3     ds 2
ImgPtr4     ds 2
HandCard    ds 1  ; Pointer to a card in a hand
T1          ds 1  ; Temporary variables used in subroutines
T2          ds 1
T3          ds 1
T4          ds 1
rand1       ds 1  ; Locations to hold bits of random number
rand2       ds 1
rand3       ds 1
rand4       ds 1
NeedShuffle ds 1  ; Flag indicating if a shuffle is needed
MessageNum  ds 1  ; Number of message to display
MessagePtr  ds 2
CursorPos   ds 1  ; Card selection cursor position
Stage       ds 1
Turn        ds 1
BiddingTeam     ds 1
Dealer      ds 1
FrameCounter  ds 1
RightBowerMask ds 1  ; Calculated masks for bower when figuring hand strength
LeftBowerMask  ds 1
TriggerTrack ds 1    ; Low four bits are number of frames held, high bit
                     ; indicates release after a full hold
MessageRegionColor  ds 1  ; Color to use as background of message area
JoyDir          ds 1
HighCardScore   ds 1
HighCardNum     ds 1
TrumpSuitMask   ds 1
HandStartOffset ds 1
HandEndOffset   ds 1
Choice          ds 1
JoyPause        ds 1
CursorMode      ds 1
NumTrumps       ds 1
HighestRemainingTrump ds 1
NumTrumpsLeft   ds 1  ; Amount of trumps left in the round
TrumpsLeft      ds 1  ; Array of bits representing which trumps are left
SuitBroken      ds 4
DeclinedSuit    ds 1
DeclinedBower   ds 1
AttractMask     ds 1
AttractFrameCounter ds 1
SoundTableOffset ds 1
SoundFrameCounter ds 1
Overlay         ds 30

DeckStart = SouthHand
Upcards = SouthCard

    seg.u vars
    org Overlay

PotentialTrump  ds 1
Upcard          ds 1
HighStrength    ds 1
BestSuit        ds 1
HandStrength    ds 1
CardScore       ds 1
NumInSuit       ds 1
NumOffLoneAces  ds 1
NumVoids        ds 1
HasRight        ds 1
HasLeft         ds 1
HasTrumpAce     ds 1
HasAce          ds 1
LowTrumpFactor  ds 1
UpcardFactor    ds 1
OtherFactor     ds 1
Bidder          ds 1

    seg.u vars
    org Overlay

TrickNum        ds 1
Leader          ds 1
LeadSuitMask    ds 1
HasLeadSuit     ds 1
PlayerToSkip    ds 1
TrickWinner     ds 1
CardInTrickNum  ds 1
CardsInFullTrick  ds 1
CardToPlay      ds 1
CardToPlayOffset  ds 1
LeadSuitCount   ds 1
HighCard        ds 1
LegalPlays      ds 5
GatheredInfo    ds 1
TrickWinningScore ds 1
CurrentCardScore ds 1
OpponentsWinning ds 1
TrueIdealScore ds 1
HighWinnerNum   ds 1
HighWinnerScore ds 1
LowWinnerNum    ds 1
LowWinnerScore  ds 1
LowCardNum      ds 1
LowCardScore    ds 1
IdealTrumpNum   ds 1
IdealTrumpScore ds 1

BestLeadNum = HighWinnerNum
BestLeadScore = HighWinnerScore
BestTrumpNum = LowWinnerNum
BestTrumpScore = LowWinnerScore
ProcessedCounters = GatheredInfo
ScoringTeam = HighWinnerNum
ScoreIncrement = HighWinnerScore

; Program

    seg code
    org $f000  ; 4K cartridge

;
; Initialization
;
CartStart
    sei  ; Disable all interrupts
    cld  ; Clear decimal mode (so carry is at 256, not 100)

    ldx #$ff
    txs  ; Reset the stack pointer to the highest point possible

; Clear out registers and variables
    lda #$00
ClearRAM
    sta $00,x
    dex
    bne ClearRAM  ; Loop does not zero WSYNC, but it's not needed

    sta SWACNT  ; Tell port A to accept input

    lda #$6d  ; seed random number generator
    sta rand1
    sta rand2
    sta rand3
    sta rand4

    lda #$80
    ldx #CardsInDeck
HideLoop
    dex
    sta DeckStart,x
    bne HideLoop

    jsr StartGameOver
    jmp Main

ProgStart             ; These initializations are done when Reset is pressed
    lda #NoCursorPos
    sta CursorPos
    lda #TriggerOff
    sta TriggerTrack

;
; Main loop
;
Main
; Start of display kernel
; Provide three lines of vertical sync
    lda #VB_Enable
    sta VBLANK
    lda #VS_Enable
    sta WSYNC
    sta WSYNC
    sta WSYNC
    sta VSYNC
    sta WSYNC
    sta WSYNC
    lda #VS_Disable
    sta VSYNC

; Provide 37 scanlines of vertical blank
    lda #43  ; 37 lines * 76 cycles/line = 2812 cycles / 64 cycles/interval = 43.96 intervals
    sta TIM64T

    jsr RandomBit  ; keep the randomness flowing

    lda INPT4
    bpl TriggerPressed
    lda #TriggerOff
    beq TriggerEnd  ; We know TriggerOff = 0 so we can beq instead of jmp
TriggerPressed
    lda TriggerTrack
    cmp #TriggerOff
    bne TriggerWasHeld
    lda #TriggerOn
    bne TriggerEnd  ; We know TriggerOn <> 0 so we can bne instead of jmp
TriggerWasHeld
    lda #TriggerHeld
TriggerEnd
    sta TriggerTrack

    lda JoyPause
    beq JoyRead
    dec JoyPause
    lda #$00
    beq JoySetDir
JoyRead
    lda SWCHA
    asl                 ; Move bit 7 (right) to carry
    bcc JoyPlus         ; If carry is clear, stick was moved right
    asl                 ; Move bit 6 (left) to carry
    bcc JoyMinus        ; If carry is clear, stick was moved left
    asl                 ; Move bit 5 (down) to carry
    bcc JoyPlus         ; If carry is clear, stick was moved down
    asl                 ; Move bit 4 (up) to carry
    bcc JoyMinus        ; If carry is clear, stick was moved up
    lda #$00
    sta JoyPause
    beq JoySetDir
JoyPlus
    lda #FramesJoyWait
    sta JoyPause
    lda #$01
    bne JoySetDir
JoyMinus
    lda #FramesJoyWait
    sta JoyPause
    lda #$FF
JoySetDir
    sta JoyDir
JoyEnd

    lda #CursorModeNone
    ldx Turn
    bne DCM4
DetermineCursorMode
    ldx CursorPos
    bmi DCM4
    ldx Stage
    cpx #StageDiscarding
    beq DCM0
    cpx #StagePlaying
    bne DCM1
DCM0
    lda #CursorModeCard
    bne DCM4
DCM1
    cpx #StageBidding1
    bne DCM2
    lda #CursorModeAction
    bne DCM4
DCM2
    cpx #StageBidding2
    bne DCM4
    ldx Choice
    bmi DCM3
    lda #CursorModeTrump
    bne DCM4
DCM3
    lda #CursorModeAction
DCM4
    sta CursorMode

    lda SoundFrameCounter
    bmi SoundOff
    dec SoundFrameCounter
    ldx SoundTableOffset
    lda FreqTable,x
    sta AUDF0
    lda ToneTable,x
    sta AUDC0
    lda #$08
    sta AUDV0
    bne SoundOut
SoundOff
    lda #$00
    sta AUDV0
SoundOut

WaitVBlank
    lda INTIM
    bne WaitVBlank
    sta WSYNC   ; Finish up last line
    sta VBLANK  ; Stop vertical blank (accumulator holds zero)

; Now we start on the visible portion of the screen

    lda Stage
    cmp #StageShuffle
    bne NormalKernel
ShuffleKernel
    lda #228  ; 192 lines * 76 cycles/line = 14592 cycles / 64 cycles/interval = 228 intervals
    sta TIM64T
; Shuffling takes an awfully long time.  It costs about 5.6 lines to get one random byte and
; we need 24 random numbers for one shuffle, or 134.4 lines.  Since there's not enough time
; to do this during overscan (30 lines), we'll just draw a blank screen whenever we're
; shuffling.  This will last for only eight frames, or 0.133 seconds (0.16 seconds in PAL),
; which should not be terribly disruptive.

ShuffleDeck
    ldy #CardsInDeck-1
SDOnce
    jsr RandomByte      ; Assume this is a number R between 0 and 1
    lsr
    lsr
    lsr
    lsr                 ; Four high bits are now four low, let this be R * 16
    sta T2
    lsr                 ; Now the three high bits are low, let this be R * 8
    clc
    adc T2              ; A = R * 8 + R * 16 = R * 24, a number between 0 and 23
                        ; (The shift rights are more efficient than rotating
                        ; left into a separate variable.)
    tax
    lda DeckStart,y
    pha
    lda DeckStart,x
    sta DeckStart,y
    pla
    sta DeckStart,x
    dey
    bpl SDOnce

    sta WSYNC
    jmp WaitForEnd

NormalKernel
; First eight lines are blank...
    lda #9  ; 8 lines * 76 cycles/line = 608 cycles / 64 cycles/interval = 9.5 intervals
    sta TIM64T

; Since we have some time, prepare the playfield for displaying the scores
; and get pointers to playfield images for them.
    lda #Team1Color
    eor AttractMask
    sta COLUP0
    lda #Team2Color
    eor AttractMask
    sta COLUP1
    lda #LeftScorePos
    ldx #0
    jsr PositionPlayer
    lda #RightScorePos
    inx
    jsr PositionPlayer
    sta WSYNC
    sta HMOVE
    lda #P_Quad
    sta NUSIZ0
    sta NUSIZ1
    lda Team1Score
    ldx #ImgPtr1
    jsr GetScoreImage
    lda Team2Score
    ldx #ImgPtr2
    jsr GetScoreImage

WaitEndScore
    lda INTIM
    bne WaitEndScore

; Now we spend ten lines drawing the scores on the playfield

    ldx #$01
    jsr DrawBookkeeping

; Pause for four lines and prepare to show tricks

    sta WSYNC
    lda #$00
    sta GRP0
    sta GRP1

    lda #P_Reflect
    sta REFP0
    lda #LeftTrickPos
    ldx #0
    jsr PositionPlayer
    lda #RightTrickPos       ; Despite the fact that the position for the second
    ldx #1                   ; score and tricks are identical, the HMOVE will
    jsr PositionPlayer       ; cause another move, so we need to reset the
                             ; position.
    sta WSYNC
    sta HMOVE

    lda Team1Tricks
    ldx #ImgPtr1
    jsr GetTrickImage
    lda Team2Tricks
    ldx #ImgPtr2
    jsr GetTrickImage

    ldx #$00
    jsr DrawBookkeeping

    sta WSYNC
    lda #$00
    sta GRP0
    sta GRP1
    sta NUSIZ0
    sta NUSIZ1
    sta REFP0

; Draw an informational region.  Depending on the stage, it can be the current
; trump suit, an arrow indicating the current bidder and bid, or an instruction
; to the human player (D = deal, S = swap).

    lda Stage
    cmp #StageBidding1
    beq PrepareShowBid
    cmp #StageBidding2
    beq PrepareShowBid
    cmp #StageBetweenHands
    beq PrepareShowD
    cmp #StageDiscarding
    beq PrepareShowS
    cmp #StagePlaying
    bcc PrepareShowNothing
PrepareShowTrump
    sta WSYNC
    lda TrumpSuitMask
    ldx #ImgPtr1
    jsr GetSuitImage
    lda #<LetterImageSpace
    ldx #ImgPtr2
    jsr GetLetterImage
    ldx BiddingTeam
    lda BidTeamToTrumpPos,x
    ldx #0
    jsr PositionPlayer
    jmp ShowInformation
PrepareShowNothing
    sta WSYNC           ; Compensate for this being a short routine
    sta WSYNC
    sta WSYNC
    lda #<LetterImageSpace
    ldx #ImgPtr1
    jsr GetLetterImage
    lda #<LetterImageSpace
    ldx #ImgPtr2
    jsr GetLetterImage
    jmp ShowInformation
PrepareShowD
    lda #<LetterImageD
PSD2
    ldx #ImgPtr1
    jsr GetLetterImage
    lda #<LetterImageSpace
    ldx #ImgPtr2
    jsr GetLetterImage
    sta WSYNC           ; Compensate for this being a short routine
    lda #InstructionPos
    ldx #0
    jsr PositionPlayer
    jmp ShowInformation
PrepareShowS
    lda Turn
    bne PrepareShowNothing
    lda #<LetterImageS
    bne PSD2
PrepareShowBid
    lda Turn
    ldx #ImgPtr1
    jsr GetArrowImage
    lda Choice
    and #$03            ; Change NoChoice ($FF) to $03
    tax
    lda ChoiceToLetterTable,x
    ldx #ImgPtr2
    jsr GetLetterImage
    lda #BidArrowPos
    ldx #0
    jsr PositionPlayer
    lda #BidDecisionPos
    ldx #1
    jsr PositionPlayer
ShowInformation
    sta WSYNC
    sta HMOVE
    lda #TrumpDisplayColor
    eor AttractMask
    sta COLUP0
    sta COLUP1
    jsr DrawSingleCard

    lda #$00
    sta GRP0
    sta GRP1
    sta WSYNC

    lda #7
    sta TIM64T

; Position the players for display of a card.  This is well in advance but
; we have time now.
    lda #CenterRankPos
    ldx #0
    jsr PositionPlayer
    lda #CenterSuitPos
    ldx #1
    jsr PositionPlayer
    sta WSYNC
    sta HMOVE

WaitBeginTable
    lda INTIM
    bne WaitBeginTable

; Now switch to the "card table" display

    sta WSYNC

    lda #TableRegionColor
    eor AttractMask
    sta COLUBK
    lda #CardTableColor
    eor AttractMask
    sta COLUPF
    lda #PF_Reflect
    sta CTRLPF
    lda #$0f
    sta PF1
    lda #$ff
    sta PF2

    lda NorthCard
    ldx #ImgPtr1
    ldy #ImgPtr2
    jsr GetCardGraphics
    sta COLUP0
    sta COLUP1

    jsr DrawSingleCard

    lda #$00
    sta GRP0
    sta GRP1

; Now we come to the hard one... both West and East

    lda #P_TwoClose    ; Two copies close
    sta NUSIZ0
    sta NUSIZ1

    lda WestCard
    ldx #ImgPtr1
    ldy #ImgPtr3
    jsr GetCardGraphics
    sta COLUP0

    lda EastCard
    ldx #ImgPtr2
    ldy #ImgPtr4
    jsr GetCardGraphics
    sta COLUP1

    lda #LeftRankPos
    ldx #0
    jsr PositionPlayer
    lda #RightRankPos
    ldx #1
    jsr PositionPlayer
;    sta WSYNC
;    sta HMOVE

    jsr DrawTwoCards

    lda #$00
    sta GRP0
    sta GRP1
    sta NUSIZ0
    sta NUSIZ1

    lda #CenterRankPos
    ldx #0
    jsr PositionPlayer
    lda #CenterSuitPos
    ldx #1
    jsr PositionPlayer
    sta WSYNC
    sta HMOVE

    lda SouthCard
    ldx #ImgPtr1
    ldy #ImgPtr2
    jsr GetCardGraphics
    sta COLUP0
    sta COLUP1

    jsr DrawSingleCard

    lda #4
    sta TIM64T

    lda #$00
    sta GRP0
    sta GRP1

WaitEndSouth
    lda INTIM
    bne WaitEndSouth
    sta WSYNC  ; Stabilizer

    lda #9  ; burn 8 lines
    sta TIM64T

    lda #$00
    sta PF1
    sta PF2
    sta COLUBK

WaitBeforeHand
    lda INTIM
    bne WaitBeforeHand

; Draw the five cards in the player's hand.  For each of the cards, draw four
; black lines then twelve card lines.  The middle eight lines of the card have
; the images.  During the four black lines, get the image pointers and player
; colors.

    lda #$00
    sta HandCard
ShowHandLoop
    lda #4
    sta TIM64T
    lda #$00
    sta PF2
    sta COLUBK
    ldx HandCard
    lda SouthHand,x
    ldx #ImgPtr1
    ldy #ImgPtr2
    jsr GetCardGraphics
    sta COLUP0
    sta COLUP1

WaitToDrawHandCard
    lda INTIM
    bne WaitToDrawHandCard

    lda #$f0
    sta PF2
    sta WSYNC
    lda #$00
    ldx CursorMode
    cpx #CursorModeCard
    bne SC
    ldx HandCard
    cpx CursorPos
    bne SC
ShowCursor
    lda #CursorColor
    eor AttractMask
SC
    sta WSYNC
    sta COLUBK
    jsr DrawSingleCard
    lda #$00
    sta COLUBK
    sta GRP0
    sta GRP1
    sta WSYNC
    sta WSYNC
    sta WSYNC
    inc HandCard
    lda HandCard
    cmp #CardsInHand
    bne ShowHandLoop

; Now the gap between the last card and the message region

    lda #13
    sta TIM64T

    lda #$00
    sta PF2
    sta COLUBK
    eor AttractMask
    sta COLUP0
    sta COLUP1

; Prepare for the message section
    lda #MessageP0Pos
    ldx #0
    jsr PositionPlayer
    lda #MessageP1Pos
    ldx #1
    jsr PositionPlayer
    sta WSYNC
    sta HMOVE

    ldx #MessagePtr
    lda CursorMode
    cmp #CursorModeAction
    beq DetermineMessageAction
    cmp #CursorModeTrump
    beq DetermineMessageTrump
    ldy MessageNum
    jmp DM0
DetermineMessageAction
    ldy #MsgNumSelectAction
    bne DM0
DetermineMessageTrump
    ldy #MsgNumSelectTrump
DM0
    jsr GetMessagePointer
    jsr GetMessageImages

WaitForGap
    lda INTIM
    bne WaitForGap

    sta WSYNC

    lda MessageRegionColor
    ; No need to use attract mask - messages do not appear when game is over.
    sta COLUBK

    lda #19  ; 16 lines of message
    sta TIM64T

    lda #P_TwoClose
    sta NUSIZ0
    sta NUSIZ1
    lda #MessageTextColor
    ; No need to use attract mask - messages do not appear when game is over.
    sta COLUP0
    sta COLUP1
    sta WSYNC
    sta WSYNC
    sta WSYNC

    jsr DrawMessageText

    lda #$00
    sta GRP0
    sta GRP1
    sta VDELP0
    sta VDELP1
    sta NUSIZ0
    sta NUSIZ1

    lda CursorMode
    and #CursorModeSelectMask
    bne DrawSelector
    lda #$00
    beq DS2
DrawSelector
    lda #%11000000
DS2
    pha
    lda #MessageTextColor
    ; No need to use attract mask - messages do not appear when game is over.
    sta COLUP0
    ldx CursorPos
    lda CursorPosToSelectorPos,x
    ldx #$00
    jsr PositionPlayer
    sta WSYNC
    sta HMOVE
    pla
    sta GRP0
    sta WSYNC
    sta WSYNC
    lda #$00
    sta GRP0

WaitForMessage
    lda INTIM
    bne WaitForMessage

;    lda #1  ; 8 lines
;    sta INTIM

    lda #$00
    sta WSYNC
    ; This is shutting off all images and player data, so there is again
    ; no need to use the attract mask.   
    sta COLUP0
    sta COLUP1
    sta GRP0
    sta GRP1
    sta NUSIZ0
    sta NUSIZ1

WaitForEnd
;    lda INTIM
;    bne WaitForEnd
    sta WSYNC
    sta WSYNC

    lda #35  ; 30 lines of overscan
    sta TIM64T

    lda #$02
    sta VBLANK

CheckReset
    lda SWCHB
    and #Con_Start
    bne CheckStages
    jsr StartNewGame
    jmp ProgStart

CheckStages
    lda Stage
    asl
    asl
    ; No clear carry needed - stage numbers are too low to affect the
    ; carry after only two shifts.
    adc #<StageJumpTable
    sta T2
    lda #>StageJumpTable
    adc #$00
    sta T3
    jsr StageJump
    jmp EndCase

StageJump
    jmp (T2)
    rts
StageJumpTable
    jsr PerformNewGame
    rts
    jsr PerformNewHand
    rts
    jsr PerformGameOver
    rts
    jsr PerformShuffle
    rts
    jsr PerformDeal
    rts
    jsr PerformBidding1
    rts
    jsr PerformDiscarding
    rts
    jsr PerformBidding2
    rts
    jsr PerformPlaying
    rts
    jsr PerformAddToTricks
    rts
    jsr PerformAddToScore
    rts
    jsr PerformBetweenHands
    rts
EndCase

WaitOverscan
    lda INTIM
    bne WaitOverscan
    sta WSYNC

    jmp Main

StartBidding1
    lda #StageBidding1
    sta Stage
    lda #NoSuit
    sta DeclinedSuit
    sta DeclinedBower
    jsr StartBidding
    rts

StartBidding
    lda Dealer
    clc
    adc #$01
    and #PlayersMask
    sta Turn
    lda #NoChoice
    sta Choice
    lda #$00
    sta CursorPos
    rts

PerformBidding1
    ldx Turn
    beq PB1South
PB1NotSouth
    lda Choice             ; Has the computer player made a decision?
    bmi PB1TimeToAct       ; No - get the player's bid.
    dec FrameCounter       ; Yes - pause for a bit, then go to next player
    beq PB1Advance
    rts
PB1TimeToAct
    jsr GetHandOffsets     ; x still has the turn number, so this is fine
    lda Upcard
    and #RealSuitMask
    sta PotentialTrump
    jsr AnalyzeHand
    jsr DecideBid
PB1SetDelay
    lda #FramesWait
    sta FrameCounter
    rts
PB1Advance
    lda Choice
    cmp #ChoicePass
    beq PB1Pass
    lda Turn
    sta Bidder
    and #$01
    sta BiddingTeam
    lda Upcard
    and #RealSuitMask
    sta TrumpSuitMask
    jsr CreateBowers
    jsr StartDiscarding    ; StartDiscarding also starts play
    rts
PB1Pass
    lda Turn
    cmp Dealer          ; Did the dealer just pass?
    beq PB1DealerPass   ; Yes!
    clc                 ; No, advance to the next player
    adc #$01
    and #PlayersMask
    sta Turn
    lda #NoChoice
    sta Choice
    rts
PB1DealerPass
    jsr StartBidding2
    rts
PB1South
    lda TriggerTrack
    cmp #TriggerOn
    bne PB1SouthMove
    jsr InitializeSelectSound
    lda CursorPos
    sta Choice
    jmp PB1Advance
PB1SouthMove
    ldx #$03
    jsr MoveCursor
    rts

; x = highest possible choice number plus one (lowest is zero)

MoveCursor
    stx T1
    lda JoyDir
    clc
    adc CursorPos
    bmi MCUnder
    cmp T1
    bne MCSave
    lda #$00                 ; "Wrap" from highest to lowest by loading lowest
    beq MCSave
MCUnder
    dex
    txa                      ; "Wrap" from lowest to highest by loading highest
MCSave
    sta CursorPos
    rts

StartBidding2
    lda #StageBidding2
    sta Stage
    ldx Dealer
    lda Upcards,x
    ora #HideCardValue
    sta Upcards,x
    and #RealSuitMask
    sta DeclinedSuit
    lda Upcards,x
    and #RankMask
    cmp #JackRankValue
    bne SB2A
    lda DeclinedSuit
    sta DeclinedBower
SB2A
    jsr StartBidding
    rts

PerformBidding2
    ldx Turn
    beq PB2South
PB2NotSouth
    lda Choice             ; Has the computer player made a decision?
    bmi PB2TimeToAct       ; No - get the player's bid.
    dec FrameCounter       ; Yes - pause for a bit, then go to next player
    beq PB2Advance
    rts
PB2TimeToAct
    jsr GetHandOffsets     ; x still has the turn number, so this is fine
    lda #$00
    sta HighStrength
    lda #$03
    sta T4
PB2FindBestSuit
    lda T4                 ; Real suits are 0-3; usable as loop control
    cmp DeclinedSuit
    beq PB2Declined
    sta PotentialTrump
    jsr AnalyzeHand
    lda HandStrength
    jmp PB2CheckIfHigh
PB2Declined
    lda #$00
PB2CheckIfHigh
    cmp HighStrength       ; Is the strength of this hand with the suit
                           ; being checked as trump better than the best
                           ; suit so far?
    bcc PB2NextSuit        ; No, carry on to the next suit
    sta HighStrength       ; Yes, store this number and set the best suit
    lda T4
    sta BestSuit
PB2NextSuit
    dec T4
    bpl PB2FindBestSuit

    lda HighStrength
    sta HandStrength
    jsr DecideBid          ; Submit the highest possible tricks to be taken
                           ; with this hand to get a choice
PB2SetDelay
    lda #FramesWait
    sta FrameCounter
    rts
PB2Advance
    lda Choice
    cmp #ChoicePass
    beq PB2Pass
PB2Accept
    lda Turn
    sta Bidder
    and #$01
    sta BiddingTeam
    lda BestSuit
    and #RealSuitMask
    sta TrumpSuitMask
    jsr CreateBowers
    jsr StartPlaying
    rts
PB2Pass
    lda Turn
    cmp Dealer          ; Did the dealer just pass?
    beq PB2DealerPass   ; Yes!
    clc                 ; No, advance to the next player
    adc #$01
    and #PlayersMask
    sta Turn
    lda #NoChoice
    sta Choice
    rts
PB2DealerPass
    lda SWCHB
    and #%01000000           ; Is the left difficulty switch on A or B?
    beq PB2AllowPass         ; It's on B, so the computer player can pass.
    lda #ChoiceCall          ; It's on A, so the computer must call a suit.
    sta Choice
    bne PB2Accept
PB2AllowPass
    jsr StartBetweenHands    ; Everybody passed!  Throw in the hand.
    rts
PB2South
    lda Choice               ; Has south made a bid yet?
    bpl PB2SouthSuit         ; Yes, so go set a trump suit
    lda TriggerTrack         ; No, so see if south is setting the bid now
    cmp #TriggerOn
    bne PB2SouthMoveAction   ; South is still in the process of selecting
    jsr InitializeSelectSound
    lda CursorPos
    sta Choice
    cmp #ChoicePass          ; Did the human player pass?
    bne PB2SouthReadySuit    ; No, in next frame start suit selection
    lda SWCHB
    and #%01000000           ; Is the left difficulty switch on A or B?
    beq PB2Advance           ; It's on B, so the pass will be allowed.
                             ; Go on to the next player.
    lda Dealer               ; It's on A.  Check if south is the dealer.
    bne PB2Advance           ; South did not deal, so it is fine to pass.
    jsr InitializeBuzzSound  ; South did deal, so a pass is not allowed.
    lda #NoChoice
    sta Choice
    rts
PB2SouthReadySuit
    lda #$00
    sta CursorPos
    rts
PB2SouthMoveAction
    ldx #$03
    jsr MoveCursor
    rts
PB2SouthSuit
    lda TriggerTrack         ; Did south pick a suit yet?
    cmp #TriggerOn
    bne PB2SouthMoveSuit     ; No, south is still selecting a trump suit
    lda CursorPos
    cmp DeclinedSuit         ; Did south set trump to the turned-down suit?
    beq PB2ChoseDeclined
    sta BestSuit
    jsr InitializeSelectSound
    jmp PB2Advance
PB2ChoseDeclined
    jsr InitializeBuzzSound
    rts
PB2SouthMoveSuit
    ldx #$04
    jsr MoveCursor
    rts

StartDiscarding
    lda #StageDiscarding
    sta Stage
    ldx Dealer
    stx Turn               ; Need to show cursor position if south dealt
    jsr GetHandOffsets
    lda Upcards,x
    and #RealSuitMask
    sta PotentialTrump
    lda #$00
    sta CursorPos
    rts

PerformDiscarding
    lda Dealer
    beq PDSouth
;    bne PDNotSouth         ; Handle case of human player separately
;    jmp PDSouth            ; Can't beq PDSouth - too far away
PDNotSouth
    lda #$00
    sta HighCardScore      ; Assume highest rating of a card is zero
    ldx HandEndOffset
    stx HighCardNum        ; Assume the best card is the last one
PDCardLoop1
    lda DeckStart,x        ; Get a card from the hand
    and #RankMask          ; Extract its rank
    eor #RankMask          ; Effectively inverts rank, turning high to low
    sta CardScore          ; Starts the card's score as seven minus its rank
                           ; (offset by two bits - no reason to move them)
    lda DeckStart,x
    and #RealSuitMask
    cmp PotentialTrump     ; Is this card a trump?
    beq PDCardTrump        ; Yes, do not add to the score
    lda CardScore
    clc
    adc #$20               ; Card is not trump; increase its score
    sta CardScore
    lda DeckStart,x
    and #RankMask
    cmp #AceRankValue      ; Is this card an off-suit ace?
    beq PDCardAce          ; Yes, do not check if it's alone
    lda DeckStart,x
    and #RealSuitMask
    sta T1                 ; T1 will hold the suit of this card so we can use
                           ; it to count how many of the same suit we have
    stx T2                 ; Stow the current loop index for recovery later
    lda #$00
    sta NumInSuit
    ldx HandEndOffset
PDCardLoop2
    lda DeckStart,x
    and #RealSuitMask
    cmp T1                 ; Is this card of the same suit?
    bne PDC1               ; No, move on
    inc NumInSuit          ; Yes, increment count of the suit
PDC1
    dex
    cpx HandStartOffset
    bpl PDCardLoop2
    ldx T2                 ; Restore the original loop index
    lda NumInSuit
    cmp #$01               ; Is this card the only one of its suit?
    bne PDC2               ; No, there are others
    lda CardScore          ; Yes, it's the only one of the suit
    clc
    adc #$20               ; Increase its card score to encourage voiding
    sta CardScore
PDCardAce
PDC2
PDCardTrump
    lda CardScore
    cmp HighCardScore      ; Is the score of the current card the highest?
    bcc PDC3               ; No, go on to the next card
    lda CardScore          ; Yes, save the score and the index of the card
    sta HighCardScore
    stx HighCardNum
PDC3
    dex
    cpx HandStartOffset
    bpl PDCardLoop1
    lda HighCardNum
    bpl PDSwap             ; HighCardNum should always be positive
PDSouth
    lda TriggerTrack
    cmp #TriggerOn
    bne PDSouthSelecting
    jsr InitializeSelectSound
    lda CursorPos
    clc
    adc HandStartOffset
    bpl PDSwap             ; Result of addition should always be positive
PDSouthSelecting
    ldx #$05
    jsr MoveCursor
    rts
PDSwap                     ; Assumes index of card to swap is in accumulator
    tax
    lda DeckStart,x
    pha                    ; Push card to swap onto stack
    txa
    tay                    ; Save its index in the y register
    ldx Dealer
    lda Upcards,x          ; Get the upcard (need original because dealer might
                           ; not be the bidder)
    pha                    ; Save the upcard to the stack
    tya                    ; Retrieve index of card to swap
    tax
    pla                    ; Retrieve the upcard from the stack
    sta DeckStart,x        ; Place upcard in hand
    ldx Dealer
    pla                    ; Retrieve card to be swapped from stack
    ora #HideCardValue
    sta Upcards,x          ; Discard it!
    jsr StartPlaying
    rts

StartShuffle
    lda #StageShuffle
    sta Stage
    lda #$08
    sta Turn
RefreshDeck
    ldx #CardsInDeck-1
RD1
    lda NewDeck,x
    sta DeckStart,x
    dex
    bpl RD1
    rts

PerformShuffle
    ; Note that the shuffling is done from within the specialized
    ; shuffle kernel, since one shuffle takes too long to do in overscan.
    ; However, the counter of shuffles (Turn) is still decremented here.
    dec Turn
    bpl PSOut
    jsr StartDeal
PSOut
    rts

StartDeal
    lda #StageDeal
    sta Stage
    rts

PerformDeal
    ; Flag all the cards in all the hands as available.
    ldx #CardsInHand*4-1
DealLoop
    lda DeckStart,x
    and #ShowCardMask
    sta DeckStart,x
    dex
    bpl DealLoop
    ; Reveal the turned-up card.
    ldx Dealer
    lda Upcards,x
    and #ShowCardMask
    sta Upcards,x
    sta Upcard          ; Save upcard for use in AnalyzeHand
    jsr StartBidding1
    rts

StartNewGame
    ; StageNewGame, MsgNumBlank, and the value to clear the attract-mode
    ; color adjustment are all zero.
    lda #$00
    sta Stage
    sta MessageNum
    sta AttractMask
    rts

PerformNewGame
    lda #$00
    sta Team1Score
    sta Team2Score
    jsr RandomByte
    and #PlayersMask
    sta Dealer
    jsr StartNewHand
    rts

StartNewHand
    lda #StageNewHand
    sta Stage
    rts

PerformNewHand
    lda #$00
    sta Team1Tricks
    sta Team2Tricks
    lda Dealer
    clc
    adc #$01
    and #PlayersMask
    sta Dealer
    jsr StartShuffle
    rts

StartGameOver
    lda #StageGameOver
    sta Stage
    lda #MsgNumBlank
    sta MessageNum
;    lda #FramesLongWait
;    sta FrameCounter
    lda #$00
    sta AttractFrameCounter
    lda #$ff
    sta SoundFrameCounter
    rts

PerformGameOver
    lda TriggerTrack
    cmp #TriggerOn
    bne PGOHold
    jsr StartNewGame
    jmp ProgStart
PGOHold
    dec AttractFrameCounter   ; Is it time to change the color scheme?
    bne PGOOut                ; No, just exit.
                              ; (Color change occurs every 256 frames,
                              ; about 4.266 seconds NTSC and 5.12
                              ; seconds PAL.)
PGOColorChange
    jsr RandomByte            ; Get a random color adjustment factor.
    sta AttractMask
PGOOut
    rts

StartPlaying
    lda TrumpSuitMask     ; Has both display and "real" suit during bidding
    and #RealSuitMask
    sta TrumpSuitMask     ; Now has only "real" suit
    lda #$00
    sta TrickNum
    lda Choice
    cmp #ChoiceAlone
    beq SPCalcSkip
    lda #$04
    sta CardsInFullTrick
    lda #NoPlayer
    bmi SPDoSkip
SPCalcSkip
    lda #$03
    sta CardsInFullTrick
    ldx Bidder
    lda PlayerToPartnerTable,x
SPDoSkip
    sta PlayerToSkip
    lda Dealer
SPAdvanceLeader
    clc
    adc #$01
    and #PlayersMask
    cmp PlayerToSkip
    beq SPAdvanceLeader
    sta Leader
    sta Turn

    lda #$00
    ldx #SuitsInDeck-1
SPClearBroken
    sta SuitBroken,x
    dex
    bpl SPClearBroken

    ldx DeclinedSuit
    bmi SPSetTrumpsLeft
    lda #$01             ; Mark the turned-down suit as broken since one
    sta SuitBroken,x     ; less card is available.

SPSetTrumpsLeft
    lda DeclinedBower
    eor #FlipColorSuitMask
    cmp TrumpSuitMask
    bne SPSetTrumpsLeft20
    lda #%10111011      ; Left bower was turned down, so there are only
    ldx #$06            ; six trump available.
    bne SPSetTrumpsLeft30
SPSetTrumpsLeft20
    lda #%11111011      ; Left bower was not turned down, so there are
    ldx #$07            ; seven trump available.
SPSetTrumpsLeft30
    sta TrumpsLeft
    stx NumTrumpsLeft
    lda #RightRankValue
    sta HighestRemainingTrump

ContinuePlaying
    lda #StagePlaying
    sta Stage
    lda Turn
    beq CP1
    sta CursorPos            ; Value for south and cursor are both $00
CP1
    lda #$00
    sta CardInTrickNum
    sta GatheredInfo
    lda #FramesWait
    sta FrameCounter
    rts

PerformPlaying
    ldx Turn
    beq PPSouth
;    bne PPNotSouth
;    jmp PPSouth
PPNotSouth
    lda Upcards,x            ; Has a card been laid down by the computer player?
    bmi PPTimeToAct          ; No, go pick a card
    dec FrameCounter
    beq PPAdvance
    rts
PPAdvance
    inc CardInTrickNum
    lda CardInTrickNum
    cmp CardsInFullTrick
    beq PPTrickComplete
    lda #$00
    sta GatheredInfo
    sta CursorPos
    lda Turn
PPAdvanceTurn
    clc
    adc #$01
    and #PlayersMask
    cmp PlayerToSkip
    beq PPAdvanceTurn
    sta Turn
PPSetDelay
    lda #FramesWait
    sta FrameCounter
    rts
PPTrickComplete
    jsr StartAddToTricks
    rts
PPTimeToAct
    ldx Turn
    jsr GetHandOffsets

    lda GatheredInfo
    beq PPComputerGatherInfo
    ldx CardInTrickNum
    beq PPComputerLead
    jsr PickCardToPlay
    bpl PPPlayCard      ; Result of addition at end of PickCardToPlay is
                        ; always positive.  Use bpl instead of jmp.
PPComputerLead
    jsr PickCardToLead  ; Result of addition at end of PickCardToLead is
                        ; always positive.  Use bpl instead of jmp.
    bpl PPPlayCard
PPComputerGatherInfo
    jsr FindLegalPlays
    ldx CardInTrickNum
    beq PPComputerGatherInfo2
    jsr GatherInfoNonLeader
    bmi PPComputerGatherInfoDone  ; When GatherInfoNonLeader finishes, it is
                                  ; due to a loop index becoming negative.
                                  ; Use bmi instead of jmp.
PPComputerGatherInfo2
    jsr GatherInfoLeader
PPComputerGatherInfoDone
    lda #$01
    sta GatheredInfo
    rts
PPSouth
    lda TriggerTrack
    cmp #TriggerOn
    bne PPSouthSelecting
    ldx Turn
    jsr GetHandOffsets
    lda CursorPos
    clc
    adc HandStartOffset
    sta CardToPlayOffset
    jsr IsCardValidPlay
    beq PPInvalidSelection
    jsr InitializeSelectSound
    lda #NoCursorPos
    sta CursorPos
    jmp PPPlayCard
PPInvalidSelection
    jsr InitializeBuzzSound
    rts
PPSouthSelecting
    ldx #$05
    jsr MoveCursor
    rts
PPPlayCard
    ldx CardToPlayOffset
    lda DeckStart,x
    sta CardToPlay        ; Tuck away the card to be played for safe keeping
    lda CardInTrickNum    ; Check if this is the first play of the trick
    bne PPNotFirst        ; It's not the first play of the trick
    lda CardToPlay
    sta HighCard          ; It is the first card of the trick, so it's the
    and #RealSuitMask     ; winner and its suit is recorded as the led suit
    sta LeadSuitMask
    lda Turn
    sta TrickWinner
    bpl PPShowPlay        ; Turn is always a positive value.  Use bpl instead
                          ; of jmp.
PPNotFirst
    lda CardToPlay
    and #RealSuitMask
    cmp TrumpSuitMask    ; Is the card that was played a trump?
    beq PPTrumped        ; Yes, go handle it
    cmp LeadSuitMask     ; The card wasn't trump - is it in the led suit?
    bne PPShowPlay       ; No, so just show the card, since it won't have any
                         ; impact on the trick.
PPFollowedSuit
    lda HighCard
    and #RealSuitMask
    cmp TrumpSuitMask    ; Is the current high card a trump?
    beq PPShowPlay       ; Yes, and the card just played wasn't, so it can't win
    lda HighCard         ; No, so see if this card beat the high card
    and #RankMask
    sta T1
    lda CardToPlay
    and #RankMask
    cmp T1               ; Is the card that was played higher than the current
                         ; winning card in the trick?
    bmi PPShowPlay       ; No, so just show the card
    lda CardToPlay       ; Yes, save its information
    sta HighCard
    lda Turn
    sta TrickWinner
    bpl PPShowPlay       ; Turn is always a positive value.  Use bpl instead
                         ; of jmp.
PPTrumped
    lda HighCard
    and #RealSuitMask
    cmp TrumpSuitMask    ; Is the current high card a trump?
    bne PPTrumpSucceeds  ; No, so this card will become high
    lda HighCard         ; Yes, so we have to see if this card is a higher trump
    and #RankMask
    sta T1
    lda CardToPlay
    and #RankMask
    cmp T1               ; Is the card that was played higher than the current
                         ; winning card in the trick?
    bmi PPShowPlay       ; No, so just show the card
PPTrumpSucceeds
    lda CardToPlay
    sta HighCard
    lda Turn
    sta TrickWinner
PPShowPlay
    ldx Turn
    lda CardToPlay
    sta Upcards,x        ; Put the card on the "table"
    ldx CardToPlayOffset
    ora #CardPlayedValue
    sta DeckStart,x      ; Mark the card played as "used"
    lda Turn
    bne PPOut
    jmp PPAdvance        ; Force an advance if it's south's turn.  (jmp needed)
PPOut
    rts

StartAddToTricks
    lda #StageAddToTricks
    sta Stage
    lda TrickWinner
    and #$01
    tax
    inc Team1Tricks,x
    lda #$00
    sta ProcessedCounters
    lda #FramesShortWait
    sta FrameCounter
    rts

PerformAddToTricks
    dec FrameCounter
    beq PAT010
    rts
PAT010
    lda ProcessedCounters
    bne PAT200
    inc ProcessedCounters
    inc FrameCounter    ; Force one more process frame
    ldx #$03
PAT025
    lda Upcards,x
    bmi PAT050          ; The card wasn't played since the partner went alone.
    and #RealSuitMask
    tay
    lda #$01
    sta SuitBroken,y    ; Register that the suit of this card is now broken.
    cpy TrumpSuitMask   ; Is this card trump?
    bne PAT050          ; No, so perform no more checks on it.
    dec NumTrumpsLeft
    lda Upcards,x
    and #RankMask
    lsr
    lsr                 ; Convert rank to an index
    tay
    lda TrumpsLeft
    and RankToTrumpsLeftTable,y
    sta TrumpsLeft      ; Clear the bit corresponding to this trump in the
                        ; array of remaining trumps.
PAT050
    dex
    bpl PAT025
    ldx #$07            ; Scan for highest remaining trump, starting with
                        ; the right bower.
PAT100
    lda TrumpsLeft
    and TrumpsLeftIndexTable,x
    beq PAT180          ; This rank is not among those remaining.
    txa
    asl
    asl                 ; Convert index into the proper bits for a card rank.
    sta HighestRemainingTrump
    rts
PAT180
    dex
    bpl PAT100
                        ; If there are no trumps left in the game, the
                        ; HighestRemainingTrump variable will still hold the
                        ; last trump that was high.  However, since the
                        ; leader will be out of trump, the variable will not
                        ; be examined.
    rts
PAT200
    ldx #$03
PATClear
    lda Upcards,x
    ora #NoCard
    sta Upcards,x
    dex
    bpl PATClear
    lda #$00
    sta CardInTrickNum
    inc TrickNum
    lda TrickNum
    cmp #TricksInRound
    beq PATHandComplete
    lda TrickWinner
    sta Leader
    sta Turn
    jsr ContinuePlaying
    rts
PATHandComplete
    jsr StartAddToScore
    rts

StartAddToScore
    lda #StageAddToScore
    sta Stage
    ldx BiddingTeam
    lda Team1Tricks,x
    cmp #$03
    bmi SASEuchre
    cmp #$05
    beq SASMarch
    lda #$01
    bne SASSet
SASEuchre
    txa
    eor #$01
    tax
SASMarch2
    lda #$02
    bne SASSet
SASMarch
    lda PlayerToSkip
    cmp #NoPlayer
    beq SASMarch2
    lda #$04
SASSet
    sta ScoreIncrement
    stx ScoringTeam
    stx SoundTableOffset
    lda #FramesScoreIncrement
    sta FrameCounter
    rts

PerformAddToScore
    lda ScoreIncrement
    beq PASAdvance
    dec FrameCounter
    lda FrameCounter
    cmp #FramesScoreIncrement/4
    beq PASIncrement
    cmp #$00
    beq PASNext
    rts
PASIncrement
    ldx ScoringTeam
    inc Team1Score,x
    lda FrameCounter
    sta SoundFrameCounter
    rts
PASNext
    dec ScoreIncrement
    lda #FramesScoreIncrement
    sta FrameCounter
    rts
PASAdvance
    lda Team1Score
    cmp #$0a
    bpl PASOver
    lda Team2Score
    cmp #$0a
    bpl PASOver
    jsr StartBetweenHands
    rts
PASOver
    jsr StartGameOver
    rts

StartBetweenHands
    lda #StageBetweenHands
    sta Stage
    rts

PerformBetweenHands
    lda TriggerTrack
    cmp #TriggerOn
    bne PBH1
    jsr StartNewHand
PBH1
    rts

; determine if a card can be played on a trick
;
; Requires values in CardToPlayOffset, HandStartOffset, HandEndOffset,
; Turn, Leader
;
; returns: a = 00 if card cannot be played, 01 if it can

IsCardValidPlay
    ldx CardToPlayOffset
    lda DeckStart,x
    bpl VPAvailable          ; No, continue checking
    lda #$00                 ; Yes, so it cannot be played again
    rts
VPAvailable
    lda Turn
    cmp Leader               ; Is this card leading the trick?
    beq VPIsLeader           ; Yes, so any card can be played
    lda DeckStart,x          ; Card is not leading the trick
    and #RealSuitMask
    cmp LeadSuitMask         ; Does this card follow the led suit?
    beq VPFollowingSuit      ; Yes, so it can be played
    lda #$00                 ; No, check if hand has the led suit
    sta LeadSuitCount
    ldx HandEndOffset
VPCountLeadSuit
    lda DeckStart,x
    and #CardPlayedMask      ; Was this card already played?
    cmp #CardPlayedValue
    beq VPAlreadyPlayed      ; Yes, so don't bother checking its suit
    lda DeckStart,x
    and #RealSuitMask
    cmp LeadSuitMask         ; Is this card in the lead suit?
    bne VPNotLeadSuit        ; No, carry on
    inc LeadSuitCount        ; Yes!
VPNotLeadSuit
VPAlreadyPlayed
    dex
    cpx HandStartOffset
    bpl VPCountLeadSuit
    lda LeadSuitCount        ; Does this hand have a card in the lead suit?
    beq VPVoidInLeadSuit     ; No, so an off-suit or trump can be played
    lda #$00                 ; Yes, so this card cannot be played
    rts
VPVoidInLeadSuit
VPFollowingSuit
VPIsLeader
    lda #$01
    rts

; analyze a hand for its ability to take tricks
;
; Requires values in HandStartOffset, HandEndOffset, and PotentialTrump

AnalyzeHand
    lda #$00
    sta NumTrumps
    sta NumOffLoneAces
    sta HandStrength
    sta UpcardFactor
    lda PotentialTrump
    ora #JackRankValue
    and #RankMask+#RealSuitMask
    sta RightBowerMask
    eor #FlipColorSuitMask
    sta LeftBowerMask
    
; Check on the trumps in the hand

    ldx HandEndOffset
AHCountLoop1
    lda DeckStart,x
    and #RealSuitMask
    cmp PotentialTrump  ; Is this card a trump?
    bne AHNotTrump      ; No, so do nothing more with it, unless the left bower
AHIsTrump
    inc NumTrumps       ; Yes!  Add one more to the count
    lda DeckStart,x
    jsr GetTrumpCardStrength
AHIT2
    clc
    adc HandStrength
    sta HandStrength
    bne AHCL1
AHNotTrump
    lda DeckStart,x
    and #RankMask+#RealSuitMask
    cmp LeftBowerMask   ; It's not of the trump suit, but is it the left bower?
    bne AHCL1           ; No, go on to next card
    lda #$06            ; Strength of the left bower
    bne AHIT2           ; Add left bower to the hand strength
AHCL1
    dex
    cpx HandStartOffset
    bpl AHCountLoop1

; Check on voids and off-suit lone aces, looping once through
; the hand for each non-trump suit

    ldy #SuitsInDeck-1
AHCountLoop2
    lda #$00
    sta NumInSuit
    ldx HandEndOffset
AHCountLoop3
    cpy PotentialTrump  ; Is this loop for trump?
    beq AHCL2           ; Yes, advance out of the inner loop
    sty T1              ; Loop control variable equivalent to suit
    lda DeckStart,x
    and #RealSuitMask
    cmp T1              ; Is this card in the suit we're examining?
    bne AHCL3           ; no, so ignore it
    inc NumInSuit       ; Yes, increase our count of the suit
    lda DeckStart,x
    and #RankMask
    cmp #AceRankValue   ; Is this card an ace?
    bne AHCL3           ; No, carry on
    inc HasAce          ; Yes, indicate we have the ace of this suit
AHCL3
    dex
    cpx HandStartOffset
    bpl AHCountLoop3

    lda NumInSuit
    cmp #$01            ; Do we have only one card in this suit?
    bne AHCL2
    lda HasAce          ; Yes - is it the ace?
    beq AHCL2
    inc NumOffLoneAces  ; It's a lone off-suit ace.
    
AHCL2
    dey
    bpl AHCountLoop2

    ldx NumTrumps
    lda NumTrumpsToStrength,x
    clc
    ldx NumOffLoneAces
    adc NumOffLoneAcesToStrength,x
    clc
    adc HandStrength
    sta HandStrength

    lda Stage
    cmp #StageBidding1  ; Is this the first bidding stage?
    bne AHOut           ; No, so the upcard isn't relevant
    lda Dealer
    eor Turn            ; But 0 will be 1 if the upcard goes to an opponent
    and #$01
    bne AHUpOpp         ; Upcard goes to opponent
    lda Dealer          ; Upcard goes to my team
    cmp Turn            ; Is it to me?
    bne AHOut           ; No, it's to my partner, and it's unclear what
                        ; effect it will have
    lda #$01
    sta UpcardFactor
    bne AHUpCalc
AHUpOpp
    lda #$02
    sta UpcardFactor
AHUpCalc
    lda Upcard
    jsr GetTrumpCardStrength
    sta T1
    lda UpcardFactor
    cmp #$01            ; Does the upcard go to me?
    beq AHUpMe
    lda HandStrength    ; No, subtract the strength of the upcard plus 3
                        ; from the strength of my hand
    sec
    sbc T1
    sec
    sbc #$03
    bmi AHBelowZero     ; If strength is negative, set it to zero
AHUCSet
    sta HandStrength
AHOut
    rts
AHBelowZero
    lda #$00
    beq AHUCSet
AHUpMe
    lda HandStrength    ; Upcard goes to me; add its strength to my hand
    clc
    adc T1
    sta HandStrength
    lda NumTrumps
    cmp #$03
    bmi AHOut           ; If I have less than three trumps, the upcard
                        ; won't add the many-trump advantage
    beq AHUpMe3         ; Do I have three trump exactly?
    cmp #$05            ; No... how about five?
    beq AHUpMeX         ; If I have five trump, the upcard won't help in
                        ; terms of number of trump
    lda #$03
AHUpMeW
    clc                 ; I have four trump and will go to five
    adc HandStrength
    sta HandStrength
    bne AHUpMeX
AHUpMe3
    lda #$04            ; I have three trump and will go to four
    bne AHUpMeW
AHUpMeX
    lda NumTrumps
    clc
    adc NumOffLoneAces
    cmp #$05            ; If I currently have only trump and off-trump
                        ; lone aces, the upcard will eliminate an advantage.
                        ; Subtract a fudge factor of 2.
    bne AHOut
    lda HandStrength
    sec
    sbc #$02
    jmp AHUCSet
    
; Assumptions: card is in accumulator, is trump, and the LeftBowerMask is set

GetTrumpCardStrength
    and #RankMask+#RealSuitMask
    cmp LeftBowerMask
    beq GTCSLeft
    and #RankMask
    lsr
    lsr
    tay
    lda PotentialTrumpRankToStrength,y  ; Must be y since calling routines
                                        ; normally use x for loop control
    rts
GTCSLeft
    lda #$06
    rts
   
DecideBid
    lda HandStrength
    cmp #24
    bpl DBAlone
    cmp #23
    beq DBUorA
    cmp #14
    bpl DBUp
    cmp #13
    beq DBPorU
    lda #ChoicePass
    beq DBEnd
DBAlone
    lda #ChoiceAlone
    beq DBEnd
DBUorA
    lda Dealer
    clc
    adc #$01
    and #$03
    cmp Turn            ; Will I lead first?
    beq DBAlone         ; If so, go alone, otherwise just call.
DBUp
    lda #ChoiceCall
    bne DBEnd
DBPorU
    lda Turn            ; Borderline hand... depending on the score, choose
                        ; to pass or call.
    and #$01            ; If last bit is set, bidder is west or east
    beq DBNS            ; Bidder is north or south
    ldx Team2Score
    ldy Team1Score
    jmp DB3a
DBNS
    ldx Team1Score
    ldy Team2Score
                        ; The end result is that x has "our" score
                        ; and y has "their" score
DB3a
    cpx #$09            ; Do we have nine points?
    bne DB3b            ; No, move on...
    cpy #$08            ; Does the other team have eight or more points?
    bpl DB3c            ; Yes, go to next check
    cpy #$06            ; Does the other team have six or seven points?
    bmi DB3c            ; No, go to next check
    lda #ChoiceCall     ; With score 9-6 or 9-7, it's better to risk a euchre
                        ; than a march
    bne DBEnd
DB3b
    cpx #$06            ; Do we have six points?
    bne DB3c            ; No, move on...
    cpy #$04            ; Does the other team have four or more points?
    bpl DB3c            ; Yes, go to next check
    cpy #$02            ; Does the other team have two or three points?
    bmi DB3c            ; No, go to next check
    lda #ChoiceCall     ; Call to protect the lead (a march would let the
                        ; other team catch up and another would win the
                        ; game for them)
    bne DBEnd
DB3c
    lda #ChoicePass
    beq DBEnd
DBEnd
    sta Choice
    rts                 ; Whew!

; determine which cards in a hand are legal to play in a trick
;
; Requires values in CardToPlayOffset, HandStartOffset, HandEndOffset,
; Turn, Leader (all due to use of IsCardValidPlay)
;
; returns: five-byte array in LegalPlays; each byte = 00 if card cannot be
; played, 01 if it can

FindLegalPlays
    ldx #CardsInHand-1
FLP2
    stx T3
    txa
    clc
    adc HandStartOffset
    sta CardToPlayOffset
    jsr IsCardValidPlay
    ldx T3
    sta LegalPlays,x
    dex
    bpl FLP2
    rts

GatherInfoLeader
    lda #$00
    sta NumTrumps
    lda #NoCard
    sta BestLeadNum
    sta BestTrumpNum
    
    lda HandEndOffset
    sta T1
    ldx #CardsInHand-1
GILLoop
    lda LegalPlays,x
    beq GILOut          ; This card was already played, so don't examine it.
    stx T2              ; Save x, the offset into the hand, for later use.
    ldx T1              ; Load the offset into the whole deck into x.
    lda DeckStart,x
    and #RankMask
    sta CurrentCardScore
    lda DeckStart,x
    ldx T2              ; Restore original value of x.
    and #RealSuitMask
    cmp TrumpSuitMask   ; Is the card being examined trump?
    bne GIL100          ; No.
    inc NumTrumps       ; Yes, so increase the count of trumps in this hand.
    lda CurrentCardScore ; Reload since it is expected for the upcoming
                         ; comparison and the store in GIL010.
    ldy BestTrumpNum    ; Have we designated a best trump?
    bmi GIL010          ; No, so do so!
    cmp BestTrumpScore  ; Is this card the best trump so far?
    bmi GILOut          ; No, so skip the other checks.
GIL010
    sta BestTrumpScore
    stx BestTrumpNum
    jmp GILOut
GIL100
    tay                 ; Copy suit (from prior to cmp TrumpSuitMask) to y
    lda SuitBroken,y    ; 0 if not broken, 1 if broken
    bne GIL150
    lda #BrokenModifier
    bne GIL180
GIL150
    lda #UnbrokenModifier
GIL180
    clc
    adc CurrentCardScore
    sta CurrentCardScore
    ldy BestLeadNum
    bmi GIL190
    cmp BestLeadScore
    bmi GILOut
GIL190
    sta BestLeadScore
    stx BestLeadNum
GILOut
    dec T1
    dex
    bpl GILLoop
    rts

PickCardToLead
    lda BestTrumpNum    ; Was any trump found in the hand?
    bmi PCTL250         ; No!
    lda BestTrumpScore
    cmp HighestRemainingTrump  ; Yes - were any of them the highest still out?
    bne PCTL250         ; No!
    lda NumTrumpsLeft   ; We have the highest trump still in the game.
    lsr
    sta T4              ; Number of remaining trump, divided by 2, rounded down.
    lda NumTrumps
    cmp T4              ; Do we have half of the trump left?
    bmi PCTL250         ; No.
PCTL200
    lda BestTrumpNum    ; Yes, so play our highest trump.
    bpl PCTLCardSelected
PCTL250
    lda BestLeadNum     ; Play the card designated as "best" lead.
    bmi PCTL200         ; If no best non-trump lead exists, play the best trump.
PCTLCardSelected
    clc
    adc HandStartOffset
    sta CardToPlayOffset
    rts

GatherInfoNonLeader
    lda #NoCard
    sta HighWinnerNum
    sta LowWinnerNum
    sta LowCardNum
    sta IdealTrumpNum
    lda HighCard
    and #RankMask
    sta TrickWinningScore
    lda HighCard
    and #RealSuitMask
    cmp TrumpSuitMask   ; Is the winning card trump?
    beq GINL10          ; Yes!
    lda #LeadSuitScoreValue  ; No, so it must be of the led suit
    bne GINL15
GINL10
    lda #TrumpScoreValue
GINL15
    clc
    adc TrickWinningScore
    sta TrickWinningScore

    ldy Turn
    lda PlayerToPartnerTable,y
    cmp TrickWinner     ; Is my partner winning the trick?
    bne GINLOpponentsWinning
    lda #$00
    beq GINL20
GINLOpponentsWinning
    lda #$01
GINL20
    sta OpponentsWinning  ; 1 if the opponents are winning
                          ; the trick or 0 if my partner is winning
    ldy TrickNum
    lda TrickToIdealTrump,y
    sta TrueIdealScore

    lda HandEndOffset
    sta T1
    ldx #CardsInHand-1
GINLLoop
    lda LegalPlays,x
    beq GINLOut         ; This card is not a valid play or was already played,
                        ; so don't examine it
    stx T2
    ldx T1
    lda DeckStart,x
    and #RankMask
    sta CurrentCardScore
    lda DeckStart,x
    ldx T2
    and #RealSuitMask
    cmp TrumpSuitMask   ; Is the current card trump?
    beq GINL40          ; Yes!
    cmp LeadSuitMask    ; No.  Is it of the led suit?
    bne GINL70          ; No, so there's nothing to add.
    lda #LeadSuitScoreValue  ; Yes, this suit was led, so it is of more worth.
    bne GINL60
GINL40
    lda #TrumpScoreValue
GINL60
    clc
    adc CurrentCardScore
    sta CurrentCardScore
    bne GINL80
GINL70
    lda CurrentCardScore  ; There was nothing to add to the current card's
                          ; relative strength, but it needs to be in the
                          ; accumulator.  The last data loaded there was
                          ; the card's suit!
GINL80
    ldy LowCardNum      ; Has a card been designated as lowest in the hand?
    bmi GINL90          ; No, so designate this one.  This is particularly
                        ; important as it is the default card to play.
    cmp LowCardScore    ; Is it the lowest so far?
    bpl GINLHigher      ; No, but continue comparing it...
GINL90
    sta LowCardScore
    stx LowCardNum
GINLHigher
    cmp TrickWinningScore  ; Can the current card beat the trick winner?
    bmi GINLOut            ; No, so stop processing it
    ldy HighWinnerNum   ; Has a card been designated as the highest winner?
    bmi GINLHigherSet   ; No, so designate this one.
    cmp HighWinnerScore ; Does this card beat the current highest winner?
    bmi GINLLowWinner   ; No, but check if it is the lowest winning card.
GINLHigherSet
    sta HighWinnerScore ; Yes, it's the highest winner encountered so far.
    stx HighWinnerNum
GINLLowWinner
    ldy LowWinnerNum    ; Has a card been designated as the lowest winner?
    bmi GINLLowWinSet   ; No, so designate this one.
    cmp LowWinnerScore  ; Is this card lower than the current lowest winner?
    bpl GINLIdeal       ; No, but check if it's an ideal trump.
GINLLowWinSet
    sta LowWinnerScore  ; Yes, it's the lowest winner encountered so far.
    stx LowWinnerNum
GINLIdeal
    cmp #NineRankValue+#TrumpScoreValue  ; Is the current card a trump?
    bmi GINLOut         ; No, so there's nothing else to consider.
    ldy LeadSuitMask
    cpy TrumpSuitMask   ; Was trump led?
    beq GINLOut         ; Yes; treat it as a normal suit, ignoring
                        ; considerations of "ideal".
    ldy IdealTrumpNum   ; Has any trump been encountered yet?
    bmi GINLSetNewIdeal ; No, so this one is our "ideal" so far.
    cmp TrueIdealScore  ; How does this trump compare to the true ideal?
    bmi GINLLessThanTrueIdeal  ; It's lower...
    cmp IdealTrumpScore ; It's >= true ideal, but is it less than closest?
    bpl GINLOut         ; No, so we've got the closest so far
GINLSetNewIdeal
    sta IdealTrumpScore
    stx IdealTrumpNum
    jmp GINLOut
GINLLessThanTrueIdeal
    cmp IdealTrumpScore ; This trump is less than true ideal; can we get closer?
    bpl GINLSetNewIdeal ; Yes!
GINLOut
    dec T1
    dex
    bpl GINLLoop
    rts

PickCardToPlay
    ; Now we've collected all the information about the hand we need to make
    ; a decision.  "Player" refers to the player in the trick; i.e. player #2
    ; is the second person to play in the trick, the one immediately after
    ; the leader.
    lda CardInTrickNum
    cmp #$01            ; Is this for player #2?
PCTPPlayer2
    bne PCTPPlayer3     ; No, keep checking
    ; Player 2: If it's possible to win the trick, do so with the highest
    ; possible non-trump or the trump closest to ideal, otherwise play the
    ; lowest valid card
PCTPP2a
    lda HighWinnerNum
    bmi PCTPPlayLow
    lda IdealTrumpNum
    bpl PCTPCardSelected
    lda HighWinnerNum
    bpl PCTPCardSelected
PCTPPlayLow
    lda LowCardNum
    bpl PCTPCardSelected
PCTPPlayer3
    cmp #$02            ; Is this for player #3?
    bne PCTPPlayer4     ; No, so it must be for player #4
    ; Player 3: If partner is winning with a queen or lower, or if an
    ; opponent is winning the trick, beat the trick just as player 2
    ; does, otherwise play the lowest valid card
    lda OpponentsWinning ; Is opponent winning?
    bne PCTPP2a         ; Yes, try to beat it
    lda TrickWinningScore ; Partner is winning, but high enough?
    cmp #KingRankValue  ; Is the winning card a king or better?
    bmi PCTPP2a         ; No, try to beat it
    bpl PCTPPlayLow     ; Yes, go under if possible
PCTPPlayer4
    ; Player 4: If partner is winning, play low, otherwise win with the lowest
    ; available winning card
    lda OpponentsWinning ; Is opponent winning?
    beq PCTPPlayLow     ; No, so play low
    lda LowWinnerNum    ; Yes - can this hand win?
    bmi PCTPPlayLow     ; No, so play low
    ;bpl PCTPCardSelected   ; Yes, play this card
PCTPCardSelected
    clc
    adc HandStartOffset
    sta CardToPlayOffset
    rts

; Change jack of trumps and jack of suit of same color as trumps to the right
; bower and left bower
;
; Requires that TrumpSuitMask be set prior to call

CreateBowers
    lda TrumpSuitMask
    ora #JackRankValue
    sta RightBowerMask
    eor #FlipColorSuitMask
    sta LeftBowerMask
    ldx #CardsInDeck-1
CBLoop
    lda DeckStart,x
    and #RankMask+#RealSuitMask  ; compare against card information only
    cmp RightBowerMask  ; Is this card the jack of trump?
    bne CBLB            ; No, check if it is the other jack of the same color
    lda TrumpSuitMask
    ora #RightRankValue ; Grant the card the right bower rank
    bne CBSet           ; Will always be > 0, bne instead of jmp
CBLB
    cmp LeftBowerMask   ; Is this card the jack of the same color as trump?
    bne CBLoopEnd       ; No, it is not either bower
    lda TrumpSuitMask   ; By doing this we make the "real" suit trump while the
                        ; displayed suit remains the original.
    ora #LeftRankValue  ; Grant the card the left bower rank
CBSet
    sta T4
    lda DeckStart,x
    and #HideCardValue+#DispSuitMask
    ora T4              ; Do not change whether card is hidden or shown
    sta DeckStart,x
CBLoopEnd
    dex
    bpl CBLoop
    rts

; find the zero page address of the first and last cards of a player's hand
; x = hand (0=south, 1=west, 2=north, 3=east)
;
; returns: HandStartOffset = zero page address of first card of desired hand
;          HandEndOffset = zero page address of last card of desired hand

GetHandOffsets
    lda HandToStartOffset,x
    sta HandStartOffset
    clc
    adc #CardsInHand-1
    sta HandEndOffset
    rts

; routine for getting images and colors of a card
; a = card
; x = image pointer for rank
; y = image pointer for suit
; returns: a = color of card

GetCardGraphics
    sta T1
    sty T2
    and #RankMask
    lsr
    lsr
    jsr GetRankImage
    lda T1
    and #DispSuitMask
    lsr
    lsr
    lsr
    lsr
    lsr
    ldx T2
    jsr GetSuitImage

    lda T1
    bmi HideCard
    and #RealSuitMask
    tax
    lda SuitToColorTable,x
    eor AttractMask
    rts
HideCard
    lda #CardTableColor
    eor AttractMask
    rts

; routine to draw a single card
; assumes ImgPtr1 and ImgPtr2 point to proper images, sprites are
; positioned, and so on

DrawSingleCard
    ldy #$05
DrawCardLoop
    sta WSYNC
    lda (ImgPtr1),y
    sta GRP0
    lda (ImgPtr2),y
    sta GRP1
    dey
    bpl DrawCardLoop
    sta WSYNC
    rts

; routine to draw two cards
; assumes ImgPtr1-4 point to proper images, sprites are positioned,
; and so on

DrawTwoCards
    sta WSYNC
    sta HMOVE
    ldy #$05
DrawCardLoop2
    nop
    nop
    ror T1
    ror T1
    lda (ImgPtr3),y
    tax
    lda (ImgPtr1),y
    sta GRP0
    stx GRP0
    pha
    lda (ImgPtr4),y
    tax
    lda (ImgPtr2),y
    sta GRP1
    nop
    stx GRP1
    pla
    sta WSYNC
    dey
    bpl DrawCardLoop2

    rts

; input: x = $00 for single-line, $01 for double

DrawBookkeeping
    ldy #$05
    stx T4
DrB1
    ldx T4
DrB2
    sta WSYNC
    lda (ImgPtr1),y
    sta GRP0
    lda (ImgPtr2),y
    sta GRP1
    dex
    bpl DrB2
    dey
    bpl DrB1
    rts

; Six-digit score display
; original version by Robert Colbert in the Stella mailing list message
; "Re: [stella] Displaying scores - how?" from March 11, 1997
; (http://www.biglist.com/lists/stella/archives/199703/msg00219.html)
; Adapted to four-digit display

DrawMessageText
    ldy #$05
loop2
    sta WSYNC
    sty T2
    lda (ImgPtr1),y
    sta GRP0
    lda (ImgPtr2),y
    sta GRP1
    lda (ImgPtr3),y
    tax
    lda (ImgPtr4),y
    tay
    ror T1
    lda $00
    lda $00
    stx GRP0
    sty GRP1
    ldy T2
    dey
    bpl loop2
    rts

END_UTIL = *

    org $fe00
    echo *-END_UTIL, "bytes available in utility area 1: ", END_UTIL, "-", *-1

ImageBanks

; All images are reversed since they are read by decrementing loops.

LetterImageStart
LetterImageSpace
    .byte %00000000
    .byte %00000000
    .byte %00000000
    .byte %00000000
    .byte %00000000
    .byte %00000000
LetterImageD
    .byte %01111100
    .byte %01100110
    .byte %01100110
    .byte %01100110
    .byte %01100110
    .byte %01111100
LetterImageP
    .byte %01100000
    .byte %01100000
    .byte %01111100
    .byte %01100110
    .byte %01100110
    .byte %01111100
LetterImageS
    .byte %00111100
    .byte %00000110
    .byte %00000110
    .byte %00111100
    .byte %01100000
    .byte %00111100
LetterImageU
    .byte %00111100
    .byte %01100110
    .byte %01100110
    .byte %01100110
    .byte %01100110
    .byte %01100110
RankImageStart
RankImage9
    .byte %00111100
    .byte %01000110
    .byte %00000110
    .byte %00111110
    .byte %01100110
    .byte %00111100
RankImage10
    .byte %11101110
    .byte %01011011
    .byte %01011011
    .byte %01011011
    .byte %11011011
    .byte %01001110
RankImageJ
LetterImageJ
    .byte %00111100
    .byte %01100110
    .byte %00000110
    .byte %00000110
    .byte %00000110
    .byte %00001110
RankImageQ
LetterImageQ
    .byte %00111010
    .byte %01100100
    .byte %01101010
    .byte %01100110
    .byte %01100110
    .byte %00111100
RankImageK
LetterImageK
    .byte %01100110
    .byte %01100110
    .byte %01101100
    .byte %01111000
    .byte %01101100
    .byte %01100110
RankImageA
LetterImageA
    .byte %01100110
    .byte %01100110
    .byte %01111110
    .byte %01100110
    .byte %00111100
    .byte %00011000

    ; The two bower images are normally copies of the "J" used for the jack.
    ; It's simpler to spend twelve bytes duplicating the letter and allow
    ; the rank (multiplied by six) to be used to access the image address
    ; directly than to always adjust for bowers.  If this is compiled with
    ; the BOWER constant, "LB" and "RB" are used instead of "J".
 IFCONST BOWER
RankImageLeft
    .byte %11101100
    .byte %10001010
    .byte %10001010
    .byte %10001100
    .byte %10001010
    .byte %10001100
RankImageRight
    .byte %10101100
    .byte %10101010
    .byte %10101010
    .byte %11001100
    .byte %10101010
    .byte %11001100
 ELSE
RankImageLeft
    .byte %00111100
    .byte %01100110
    .byte %00000110
    .byte %00000110
    .byte %00000110
    .byte %00001110
RankImageRight
    .byte %00111100
    .byte %01100110
    .byte %00000110
    .byte %00000110
    .byte %00000110
    .byte %00001110
 ENDIF

SuitImageStart
SuitImageHeart
    .byte %00010000
    .byte %00111000
    .byte %01111100
    .byte %11111110
    .byte %11101110
    .byte %01000100
SuitImageDiamond
    .byte %00011000
    .byte %00111100
    .byte %01111110
    .byte %01111110
    .byte %00111100
    .byte %00011000
SuitImageClub
    .byte %00011000
    .byte %01111110
    .byte %11111111
    .byte %00011000
    .byte %00111100
    .byte %00011000
SuitImageSpade
    .byte %00111000
    .byte %10111010
    .byte %11111110
    .byte %01111100
    .byte %00111000
    .byte %00010000

ArrowImageStart
ArrowImageSouth
    .byte %00011000
    .byte %00111100
    .byte %01111110
    .byte %11011011
    .byte %00011000
    .byte %00011000
ArrowImageWest
    .byte %00110000
    .byte %01100000
    .byte %11111111
    .byte %11111111
    .byte %01100000
    .byte %00110000
ArrowImageNorth
    .byte %00011000
    .byte %00011000
    .byte %11011011
    .byte %01111110
    .byte %00111100
    .byte %00011000
ArrowImageEast
    .byte %00001100
    .byte %00000110
    .byte %11111111
    .byte %11111111
    .byte %00000110
    .byte %00001100

ScoreImageStart
    .byte %00000000
    .byte %01110111
    .byte %01010101
    .byte %01010101
    .byte %01010101
    .byte %01110111

    .byte %00000000
    .byte %01110111
    .byte %01010010
    .byte %01010010
    .byte %01010110
    .byte %01110010

    .byte %00000000
    .byte %01110111
    .byte %01010100
    .byte %01010111
    .byte %01010001
    .byte %01110111

    .byte %00000000
    .byte %01110111
    .byte %01010001
    .byte %01010011
    .byte %01010001
    .byte %01110111

    .byte %00000000
    .byte %01110001
    .byte %01010001
    .byte %01010111
    .byte %01010101
    .byte %01110001

    .byte %00000000
    .byte %01110111
    .byte %01010001
    .byte %01010111
    .byte %01010100
    .byte %01110111

    .byte %00000000
    .byte %01110111
    .byte %01010101
    .byte %01010111
    .byte %01010100
    .byte %01110111

    .byte %00000000
    .byte %01110100
    .byte %01010100
    .byte %01010010
    .byte %01010001
    .byte %01110111

    .byte %00000000
    .byte %01110111
    .byte %01010101
    .byte %01010010
    .byte %01010101
    .byte %01110111

    .byte %00000000
    .byte %01110111
    .byte %01010001
    .byte %01010111
    .byte %01010101
    .byte %01110111

    .byte %00000000
    .byte %01110111
    .byte %00100101
    .byte %00100101
    .byte %01100101
    .byte %00100111

    .byte %00000000
    .byte %01110111
    .byte %00100010
    .byte %00100010
    .byte %01100110
    .byte %00100010

    .byte %00000000
    .byte %01110111
    .byte %00100100
    .byte %00100111
    .byte %01100001
    .byte %00100111

    .byte %00000000
    .byte %01110111
    .byte %00100001
    .byte %00100011
    .byte %01100001
    .byte %00100111

TrickImageStart
    .byte %00000000
    .byte %00000000
    .byte %00000000
    .byte %00000000
    .byte %00000000
    .byte %00000000

    .byte %00000000
    .byte %00000000
    .byte %00000000
    .byte %00000000
    .byte %00000001
    .byte %00000001

    .byte %00000000
    .byte %00000000
    .byte %00000000
    .byte %00000000
    .byte %00000101
    .byte %00000101

    .byte %00000001
    .byte %00000001
    .byte %00000000
    .byte %00000000
    .byte %00000101
    .byte %00000101

    .byte %00000101
    .byte %00000101
    .byte %00000000
    .byte %00000000
    .byte %00000101
    .byte %00000101

    .byte %00000101
    .byte %00000101
    .byte %00000010
    .byte %00000010
    .byte %00000101
    .byte %00000101

NewDeck
    .byte #HideCardValue + #NineRankValue  + #HeartSuitValue
    .byte #HideCardValue + #TenRankValue   + #HeartSuitValue
    .byte #HideCardValue + #JackRankValue  + #HeartSuitValue
    .byte #HideCardValue + #QueenRankValue + #HeartSuitValue
    .byte #HideCardValue + #KingRankValue  + #HeartSuitValue
    .byte #HideCardValue + #AceRankValue   + #HeartSuitValue
    .byte #HideCardValue + #NineRankValue  + #DiamondSuitValue
    .byte #HideCardValue + #TenRankValue   + #DiamondSuitValue
    .byte #HideCardValue + #JackRankValue  + #DiamondSuitValue
    .byte #HideCardValue + #QueenRankValue + #DiamondSuitValue
    .byte #HideCardValue + #KingRankValue  + #DiamondSuitValue
    .byte #HideCardValue + #AceRankValue   + #DiamondSuitValue
    .byte #HideCardValue + #NineRankValue  + #ClubSuitValue
    .byte #HideCardValue + #TenRankValue   + #ClubSuitValue
    .byte #HideCardValue + #JackRankValue  + #ClubSuitValue
    .byte #HideCardValue + #QueenRankValue + #ClubSuitValue
    .byte #HideCardValue + #KingRankValue  + #ClubSuitValue
    .byte #HideCardValue + #AceRankValue   + #ClubSuitValue
    .byte #HideCardValue + #NineRankValue  + #SpadeSuitValue
    .byte #HideCardValue + #TenRankValue   + #SpadeSuitValue
    .byte #HideCardValue + #JackRankValue  + #SpadeSuitValue
    .byte #HideCardValue + #QueenRankValue + #SpadeSuitValue
    .byte #HideCardValue + #KingRankValue  + #SpadeSuitValue
    .byte #HideCardValue + #AceRankValue   + #SpadeSuitValue

MessageTableStart
MessageBlank
    .byte #<LetterImageSpace, #<LetterImageSpace
    .byte #<LetterImageSpace, #<LetterImageSpace
MessageSelectAction
ChoiceToLetterTable
    .byte #<LetterImageP, #<LetterImageU
    .byte #<LetterImageA, #<LetterImageSpace
MessageSelectTrump
    .byte #<SuitImageHeart, #<SuitImageDiamond
    .byte #<SuitImageClub, #<SuitImageSpade

CursorPosToSelectorPos
    .byte $94, $05, $76, $f6

BidTeamToTrumpPos
    .byte $a2,$48

TrickToIdealTrump
    .byte #TenRankValue + #TrumpScoreValue
    .byte #QueenRankValue + #TrumpScoreValue
    .byte #AceRankValue + #TrumpScoreValue
    .byte #RightRankValue + #TrumpScoreValue
    .byte #RightRankValue + #TrumpScoreValue

PlayerToPartnerTable
    .byte $02, $03, $00, $01

HandToStartOffset
    .byte $00, $05, $0a, $0f

ImageBankOffsets
    .byte $00
    .byte (#SuitImageStart-#RankImageStart)/6
    .byte (#ArrowImageStart-#RankImageStart)/6
    .byte (#ScoreImageStart-#RankImageStart)/6
    .byte (#TrickImageStart-#RankImageStart)/6

SuitToColorTable
    .byte #RedSuitColor, #RedSuitColor, #BlackSuitColor, #BlackSuitColor

NumTrumpsToStrength
    .byte $00, $00, $00, $00, $04, $07

NumOffLoneAcesToStrength
    .byte $00, $03, $05, $06

PotentialTrumpRankToStrength
   ; Since the bower masks haven't been set yet, the rank of the jack is
   ; still third-lowest.  The GetTrumpCardStrength routine adjusts for the
   ; left bower, and since the input card is definitely trump, its jack
   ; rank gets the highest strength value.
   .byte $01, $01, $08, $02, $02, $04

RankToTrumpsLeftTable
   ; These are masks used to remove a particular trump from the list of
   ; trumps left in a round.  Notice that the third-from-last bit is always
   ; zero since the jack is promoted to a bower.
   .byte %11111010, %11111001, %11111011, %11110011
   .byte %11101011, %11011011, %10111011, %01111011

TrumpsLeftIndexTable
   ; These are used to translate a loop index into a bit of a byte.  In
   ; this game, they are only for examining the TrumpsLeft bit array.
   ; The third value is intentionally zero since there is no jack rank
   ; in the trump suit.
   .byte %00000001, %00000010, %00000000, %00001000
   .byte %00010000, %00100000, %01000000, %10000000

; routine to generate a random number
; original version by Erik Mooney in the Stella mailing list message
; "Re: [stella] demo update: PCMSD20.BIN" from April 14, 1997
; (http://www.biglist.com/lists/stella/archives/199704/msg00136.html)
; requires four memory locations to be reserved for generation
;
; returns: a = random number

RandomBit
    lda rand4  ; +3 = 3
    asl        ; +2 = 5
    asl        ; +2 = 7
    asl        ; +2 = 9
    eor rand4  ; +3 = 12  ;new bit is now in bit 6 of A
    asl        ; +2 = 14
    asl        ; +2 = 16  ;new bit is now in carry
    rol rand1  ; +5 = 21  ;shift new bit into bit 0 of register
                          ;bit 7 goes into carry
    rol rand2  ; +5 = 26  ;shift old bit 7 into bit 8, etc.
    rol rand3  ; +5 = 31
    rol rand4  ; +5 = 36
    rts        ; +5 = 41

RandomByte
    ldx #8     ; +2
RandomByte1
    jsr RandomBit  ; +6+41 = +47
    dex            ; +2
    bne RandomByte1  ; +3 when taken (7 times), +2 when not (1 time)
                     ; Total after all loops is 2+(47+2+3)*7+(47+2+2)=417
    lda rand1      ; +3 = 420
    rts            ; +5 = 425, or roughly 5.6 scan lines

; routine to position a player
; original version by Erik Mooney in the Stella mailing list message
; "Re: [stella] sexp8.bin Multi-Japanese Sprites" from April 18, 1998
; (http://www.biglist.com/lists/stella/archives/199804/msg00170.html)
; modified to work on both player 0 and 1 and to take a hard-coded
; position value rather than look at a table (there is no motion in
; this game, so the table is not necessary)
; This must not cross a page boundary!
;
; a = position value - high nybble = fine position, low nybble =
; course position
; x = player number

PositionPlayer
    sta WSYNC
    ror T1  ; waste 5 cycles
    sta HMP0,x
    and #$0f
    tay
P0
    dey
    bpl P0
    sta RESP0,x
; Rather than WSYNC and HMOVE now, let the calling routine do it.  If both
; players are positioned in succession, this saves a scanline.
    rts

; Routines that get an image from a table
;
; a = image number out of the set
; x = offset to the addresses to receive the pointer

GetTrickImage
    ldy #$04
    bne GRI2
GetScoreImage
    ldy #$03
    bne GRI2
GetArrowImage
    ldy #$02
    bne GRI2
GetSuitImage
    ldy #$01
    bne GRI2
GetRankImage
    ldy #$00
GRI2
    clc
    adc ImageBankOffsets,y
    asl
    sta T4
    asl
    clc
    adc T4
    adc #<RankImageStart
    sta $00,x
    lda #>RankImageStart
    adc #$00
    sta $01,x
    rts

GetLetterImage
    clc
    adc #<LetterImageStart
    sta $00,x
    lda #>LetterImageStart
    adc #$00
    sta $01,x
    rts

GetMessagePointer
    tya
    asl
    asl
    clc
    adc #<MessageTableStart
    sta $00,x
    lda #>MessageTableStart
    adc #$00
    sta $01,x
    rts

GetMessageImages
    ldy #$04
MessageImageLoop
    dey
    sty T4
    lda (MessagePtr),y
    sta T3
    tya
    asl
    clc
    adc #ImgPtr1
    tax
    lda T3
    jsr GetLetterImage
    ldy T4
    bne MessageImageLoop
    rts

InitializeSelectSound
    lda #SoundValidSelect
    sta SoundTableOffset
    lda #FramesValidSelect
    sta SoundFrameCounter
    rts

InitializeBuzzSound
    lda #SoundInvalidSelect
    sta SoundTableOffset
    lda #FramesInvalidSelect
    sta SoundFrameCounter
    rts

FreqTable
    .byte $17, $14, $13, $04
ToneTable
    .byte $05, $1c, $05, $0f

; This and other free space calculations were taken from Brian Watson's
; Poker Squares.  See "[stella] Poker solitaire source, versions 012 & 014"
; from November 26, 2001.
; (http://www.biglist.com/lists/stella/archives/200111/msg00421.html)
END_DATA = *

    org $fff8
    echo *-END_DATA, "bytes available in image and table area: ", END_DATA, "-", *-1
    .byte $00, $00, $00, $00  ; Locations $fff8 and $fff9 are reserved for
                              ; use in a Supercharger.  Accessing them can
                              ; crash a regular 4K game.  Since the processor
                              ; reads two bytes for one-byte instructions, a
                              ; one-byte instruction should not be at $fff7.
    .word CartStart
    .word CartStart
Current Thread