[stella] Euchre: the return!

Subject: [stella] Euchre: the return!
From: "Erik J. Eid" <eeid@xxxxxxxxx>
Date: Fri, 09 Nov 2001 21:49:39 -0500
Finally! I have something more to show with my Euchre game.

I've been on a hiatus of sorts to concentrate on a class and some personal business. It didn't help that before I went on this hiatus, my game had crashed through the 4K barrier and needed some optimization, which as a newbie was a daunting task.

I followed one suggestion to trim my sprites from eight bytes to six, and since they were all the same size, to discard the image pointer tables in favor of calculating a sprite's data on the fly. This saved me more than enough space and enabled me to proceed again.

I was in the process of enabling actual gameplay before the long break. You can now play a full game of Euchre, earning tricks and scoring points along the way. However, the computer's intelligence has not been implemented. All the computer players just play the first available cards in their hands, ignoring the rules of following suit and having no strategy. It turns out I was fairly close in my original gameplay code; I just had to correct a couple of bit masking problems and split up some code I tried to use for multiple conditions.

My next tasks are to start adding computer AI and consider some sound effects. I am also pondering tweaking the display. I have a feeling many players would like to see the trump suit and which team took the bid during the hand. As I was playing a few hands, I found that I wanted this as well. I also should put together a web site so it can be listed in the AtariAge games in development page. :)

Many thanks go out to all those who offered optimization suggestions. Without these, I'd probably still be in the dark.

(As usual, I have provided a profile entry for the Stella emulator. When running in Z26, use the -c0 and -m0 switches.)
;
; Euchre game program for the Atari 2600 video computer system
;
; Copyright 2001 by Erik Eid (eeid@xxxxxxxxx)
;
; Last update: November 9, 2001
;
; Compiled with the dasm assembler 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
HighRandInShuffle = 240  ; when shuffling, throw out random numbers higher than
                         ; this (256 does not divide evenly by 24, but 240 does)

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

RankMask = %00000111      ; Bit mask for rank of a card
DispSuitMask = %00011000  ; Bit mask for suit displayed on a card
RealSuitMask = %01100000  ; Bit mask for suit used when following (the left
                          ; bower becomes the suit of the right bower)
FullSuitMask = %01111000  ; 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 = %00011111  ; Bit mask for rank and suit combined

RightRankValue = %00000111
LeftRankValue = %00000110
AceRankValue = %00000101
KingRankValue = %00000100
JackRankValue = %00000010

HeartSuitValue = %00000000
DiamondSuitValue = %00101000
ClubSuitValue = %01010000
SpadeSuitValue = %01111000

BlackSuitMask = %00010000
FlipColorSuitMask = %00001000  ; 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

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

CenterRankPos = $75  ; Player positions
CenterSuitPos = $66
LeftRankPos = $a2
RightRankPos = $57

MessageP0Pos = $44
MessageP1Pos = $c4

NoCursorPos = $05

MsgNumBlank = $00
MsgNumSelect = $01
MsgNumSwap = $02
MsgNumPass = $03
MsgNumOrder = $04
MsgNumCall = $05
MsgNumAlone = $06
MsgNumTrumpHearts = $07
MsgNumTrumpDiamonds = $08
MsgNumTrumpClubs = $09
MsgNumTrumpSpades = $0a
MsgNumAloneHearts = $0b
MsgNumAloneDiamonds = $0c
MsgNumAloneClubs = $0d
MsgNumAloneSpades = $0e
MsgNumDeal = $0f
MsgNumGameOver = $10
MsgNumSouth = $11
MsgNumWest = $12
MsgNumNorth = $13
MsgNumEast = $14
MsgNumPoint = $15
MsgNumEuchre = $16
MsgNumMarch2 = $17
MsgNumMarch4 = $18

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

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

SubturnShowName = $00
SubturnShowBid = $01
SubturnSelectBid = $01
SubturnSelectSuit = $02

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

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

; 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
ImgPtr5     ds 2
ImgPtr6     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
Subturn     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
PaddlePos       ds 1
HighCardScore   ds 1
HighCardNum     ds 1
TrumpSuitMask   ds 1
HandStartOffset ds 1
HandEndOffset   ds 1
Choice          ds 1
Overlay         ds 15

DeckStart = SouthHand
Upcards = SouthCard

    seg.u vars
    org Overlay

PotentialTrump  ds 1
Upcard          ds 1
DeclinedSuit    ds 1
HighPsblTrks    ds 1
BestSuit        ds 1
PossibleTricks  ds 1
CardScore       ds 1
NumInSuit       ds 1
NumTrumps       ds 1
NumOffLoneAces  ds 1
NumVoids        ds 1
HasRight        ds 1
HasLeft         ds 1
HasTrumpAce     ds 1
HasAce          ds 1

    seg.u vars
    org Overlay

TrickNum        ds 1
Leader          ds 1
LeadSuitMask    ds 1
HasLeadSuit     ds 1
BiddingTeam     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

; 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 #NoCursorPos
    sta CursorPos

    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_DumpPots + #VB_Enable
    sta VBLANK
    lda #VS_Enable
    sta WSYNC
    sta WSYNC
    sta WSYNC
    sta VSYNC
    sta WSYNC
    sta WSYNC
;    sta WSYNC
    lda #VS_Disable
    sta VSYNC
    lda #VB_Enable
    sta VBLANK  ; Start recharge of paddle

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

; When it comes time, check console switches here
    lda NeedShuffle
    beq PostShuffle
    jsr ShuffleDeck
    lda #$00
    sta NeedShuffle

PostShuffle
    jsr RandomBit  ; keep the randomness flowing

    lda #$00
    sta PaddlePos  ; Clear paddle position counter

    lda SWCHA
    and #J0_Right  ; Joystick 0 right is same as paddle 0 trigger
    beq TriggerPressed
    lda TriggerTrack
    cmp #TriggerOff
    beq TriggerEnd
    lda #TriggerOff
    jmp TriggerEnd
TriggerPressed
    lda TriggerTrack
    cmp #TriggerOff
    bne TriggerWasHeld
    lda #TriggerOn
    jmp TriggerEnd
TriggerWasHeld
    lda #TriggerHeld
TriggerEnd
    sta TriggerTrack

WaitVBlank
    lda INTIM
    nop
    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

; 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
    sta COLUP0
    lda #Team2Color
    sta COLUP1
    lda #PF_Score
    sta CTRLPF

    ldx Team1Score
    ldy #ImgPtr1
    jsr GetScoreImage
    ldx Team2Score
    ldy #ImgPtr2
    jsr GetScoreImage

    lda INPT0
    bmi Charged01
    inc PaddlePos
Charged01

WaitEndScore
    lda INTIM
    nop
    bne WaitEndScore

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

    ldx #$09
ScoresLoop
    sta WSYNC
    txa
    lsr
    tay
    lda (ImgPtr1),y
    sta PF1
    ror T1
    ror T1
    nop
    nop
    nop
    lda (ImgPtr2),y
    sta PF1
    dex
    bpl ScoresLoop

; Pause for four lines and prepare to show tricks

    sta WSYNC
    lda #$00
    sta PF1

    lda #4
    sta TIM64T

    lda INPT0
    bmi Charged02
    inc PaddlePos
Charged02

WaitBeginTricks
    lda INTIM
    nop
    bne WaitBeginTricks

; Trick graphics are four lines with the same value, so the offset into
; the TrickImages table is for the number of tricks rather than the xth
; byte of an image.
    ldy #$04
TricksLoop
    sta WSYNC
    ldx Team1Tricks
    lda TrickImages,x
    sta PF1
    ror T1
    ror T1
    nop
    nop
    nop
    nop
    ldx Team2Tricks
    lda TrickImages,x
    sta PF1
    dey
    bne TricksLoop

; Pause for eight more lines.

    sta WSYNC
    lda #$00
    sta PF1

    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

    lda INPT0
    bmi Charged03
    inc PaddlePos
Charged03

WaitBeginTable
    lda INTIM
    nop
    bne WaitBeginTable

; Now switch to the "card table" display

    sta WSYNC

    lda #TableRegionColor
    sta COLUBK
    lda #CardTableColor
    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

    lda INPT0
    bmi Charged04
    inc PaddlePos
Charged04

; 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 INPT0
    bmi Charged05
    inc PaddlePos
Charged05

    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
    nop
    bne WaitEndSouth

    lda #9  ; burn 8 lines
    sta TIM64T

    lda #$00
    sta COLUBK
    sta PF1
    sta PF2

    lda INPT0
    bmi Charged06
    inc PaddlePos
Charged06
    sta WSYNC  ; Stabilizer

WaitBeforeHand
    lda INTIM
    nop
    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 COLUBK
    sta PF2
    ldx HandCard
    lda SouthHand,x
    ldx #ImgPtr1
    ldy #ImgPtr2
    jsr GetCardGraphics
    sta COLUP0
    sta COLUP1

WaitToDrawHandCard
    lda INTIM
    nop
    bne WaitToDrawHandCard

    sta WSYNC
    lda INPT0
    bmi Charged07
    inc PaddlePos
Charged07

    lda #$f0
    sta PF2
    sta WSYNC
    sta WSYNC
    lda HandCard
    cmp CursorPos
    beq ShowCursor
    lda #$00
    jmp SC
ShowCursor
    lda #CursorColor
SC
    sta COLUBK
    jsr DrawSingleCard
    lda #$00
    sta GRP0
    sta GRP1
    sta WSYNC
    sta COLUBK
    sta WSYNC
    sta WSYNC
    inc HandCard
    lda HandCard
    cmp #$05
    bne ShowHandLoop

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

    lda #13
    sta TIM64T

    lda #$00
    sta COLUBK
    sta PF2
    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 MessageNum
    ldy #MessagePtr
    jsr GetMessagePointer
    jsr GetMessageImages

    lda INPT0
    bmi Charged08
    inc PaddlePos
Charged08

WaitForGap
    lda INTIM
    nop
    bne WaitForGap

    sta WSYNC

    lda MessageRegionColor
    sta COLUBK

    lda #19  ; 16 lines of message
    sta TIM64T

    lda #P_ThreeClose
    sta NUSIZ0
    sta NUSIZ1
    lda #MessageTextColor
    sta COLUP0
    sta COLUP1
    sta WSYNC
    sta WSYNC
    sta WSYNC

    jsr DrawMessageText

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

WaitForMessage
    lda INTIM
    nop
    bne WaitForMessage

    lda #$00
    sta COLUPF

    lda INPT0
    bmi Charged09
    inc PaddlePos
Charged09

    lda #9  ; 8 lines
    sta INTIM

    lda #$00
    sta WSYNC
    sta PF1
    sta PF2
    sta COLUBK
    sta COLUP0
    sta COLUP1
    sta COLUPF
    sta CTRLPF
    sta GRP0
    sta GRP1
    sta NUSIZ0
    sta NUSIZ1

WaitForEnd
    lda INTIM
    nop
    bne WaitForEnd

    sta WSYNC

    lda #35  ; 30 lines of overscan
    sta TIM64T

    lda #$02
    sta VBLANK

CheckReset
    lda SWCHB
    and #$01
    cmp #$01
    beq CheckStages
    jsr StartNewGame
    jmp ProgStart

CheckStages
    lda Stage
    cmp #StageHold
    bne CheckNewGame
    ;
    jmp EndCase
CheckNewGame
    cmp #StageNewGame
    bne CheckNewHand
    jsr PerformNewGame
    jmp EndCase
CheckNewHand
    cmp #StageNewHand
    bne CheckGameOver
    jsr PerformNewHand
    jmp EndCase
CheckGameOver
    cmp #StageGameOver
    bne CheckShuffle
    jsr PerformGameOver
    jmp EndCase
CheckShuffle
    cmp #StageShuffle
    bne CheckDealing
    jsr PerformShuffle
    jmp EndCase
CheckDealing
    cmp #StageDeal
    bne CheckBidding1
    jsr PerformDeal
    jmp EndCase
CheckBidding1
    cmp #StageBidding1
    bne CheckDiscarding
    jsr PerformBidding1
    jmp EndCase
CheckDiscarding
    cmp #StageDiscarding
    bne CheckBidding2
    jsr PerformDiscarding
    jmp EndCase
CheckBidding2
    cmp #StageBidding2
    bne CheckPlaying
    jsr PerformBidding2
    jmp EndCase
CheckPlaying
    cmp #StagePlaying
    bne CheckAddToTricks
    jsr PerformPlaying
    jmp EndCase
CheckAddToTricks
    cmp #StageAddToTricks
    bne CheckAddToScore
    jsr PerformAddToTricks
    jmp EndCase
CheckAddToScore
    cmp #StageAddToScore
    bne CheckBetweenHands
    jsr PerformAddToScore
    jmp EndCase
CheckBetweenHands
    cmp #StageBetweenHands
    bne EndCase
    jsr PerformBetweenHands
    jmp EndCase

EndCase

WaitOverscan
    lda INTIM
    nop
    bne WaitOverscan
    sta WSYNC

    jmp Main

StartHolding        ; Stop the action - only used during development
    lda #StageHold
    sta Stage
    rts

StartBidding1
    lda #StageBidding1
    sta Stage
    jsr StartBidding
    rts

StartBidding
    clc
    lda Dealer
    adc #$01
    and #PlayersMask
    sta Turn
    lda Turn
    beq SBSouthBids
    lda #SubturnShowName
    sta Subturn
    lda #MessageRegionDisplayColor
    jmp SB1
SBSouthBids
    lda #SubturnSelectBid
    sta Subturn
    lda #MessageRegionChoiceColor
SB1
    sta MessageRegionColor
    lda Turn
    clc
    adc #MsgNumSouth
    sta MessageNum
    lda #FramesWait
    sta FrameCounter
    rts

PerformBidding1
    lda Turn
    bne PB1NotSouth        ; Handle case of human player separately
    jmp PB1South           ; Can't beq PB1South - too far away
PB1NotSouth
    dec FrameCounter
    beq PB1TimeToAct
    rts                    ; Can't bne PB1End - too far away
PB1TimeToAct
    lda Subturn
    cmp #SubturnShowBid
    beq PB1Advance
    lda #SubturnShowBid
    sta Subturn
    ldx Turn
    jsr GetHandOffsets
    lda Upcard
    and #FullSuitMask
    sta PotentialTrump
    jsr AnalyzeHand
    jsr DecideBid
    lda Choice
    cmp #ChoicePass
    bne PB1Call
    lda #MsgNumPass
    jmp PB1SetMessage
PB1Call
    cmp #ChoiceCall
    bne PB1Alone
    lda #MsgNumCall
    jmp PB1SetMessage
PB1Alone
    lda #MsgNumAlone
PB1SetMessage
    sta MessageNum
    lda #FramesWait
    sta FrameCounter
    rts
PB1Advance
    lda Choice
    cmp #ChoicePass
    beq PB1Pass
    lda Upcard
    and #FullSuitMask
    sta TrumpSuitMask
    jsr CreateBowers
    jsr StartDiscarding
;    jsr StartPlaying
    rts
PB1Pass
    lda Turn
    cmp Dealer          ; Did the dealer just pass?
    beq PB1DealerPass   ; Yes!
    clc                 ; No, advance to the next player
    lda Turn
    adc #$01
    and #PlayersMask
    sta Turn
    lda Turn
    beq PB1SouthBids
    lda #MessageRegionDisplayColor
    jmp PB1a
PB1SouthBids
    lda #MessageRegionChoiceColor
PB1a
    sta MessageRegionColor
    lda Turn
    clc
    adc #MsgNumSouth
    sta MessageNum
    lda #FramesWait
    sta FrameCounter
    lda #SubturnShowName
    sta Subturn
    rts
PB1DealerPass
    jsr StartBidding2
    rts
PB1South
    lda TriggerTrack
    cmp #TriggerOn
    beq PB1Advance
    lda PaddlePos
    cmp #$0a
    bmi PB1S1
    lda #ChoicePass
    sta Choice
    lda #MsgNumPass
    sta MessageNum
    rts
PB1S1
    cmp #$04
    bmi PB1S2
    lda #ChoiceCall
    sta Choice
    lda #MsgNumCall
    sta MessageNum
    rts
PB1S2
    lda #ChoiceAlone
    sta Choice
    lda #MsgNumAlone
    sta MessageNum
    rts
PB1End
    rts

StartBidding2
    lda #StageBidding2
    sta Stage
    ldx Dealer
    lda Upcards,x
    ora #HideCardValue
    sta Upcards,x
    and #FullSuitMask
    sta DeclinedSuit
    jsr StartBidding
    rts

PerformBidding2
    lda Turn
    bne PB2NotSouth        ; Handle case of human player separately
    jmp PB2South           ; Can't beq PB2South - too far away
PB2NotSouth
    dec FrameCounter
    beq PB2TimeToAct
    rts                    ; Can't bne PB1End - too far away
PB2TimeToAct
    lda Subturn
    cmp #SubturnShowBid
    bne PB2StartShowBid
    jmp PB2Advance
PB2StartShowBid
    lda #SubturnShowBid
    sta Subturn
    ldx Turn
    jsr GetHandOffsets
    ldy DeclinedSuit
    cpy #HeartSuitValue
    beq PB2HeartsDeclined
    lda #HeartSuitValue
    sta PotentialTrump
    jsr AnalyzeHand
    lda PossibleTricks
    jmp PB2Hearts
PB2HeartsDeclined
    lda #$00
PB2Hearts
    sta HighPsblTrks       ; This is the first suit checked; assume it's best
    lda #HeartSuitValue
    sta BestSuit

    ldy DeclinedSuit
    cpy #DiamondSuitValue
    beq PB2DiamondsDeclined
    lda #DiamondSuitValue
    sta PotentialTrump
    jsr AnalyzeHand
    lda PossibleTricks
    jmp PB2Diamonds
PB2DiamondsDeclined
    lda #$00
PB2Diamonds
    cmp HighPsblTrks     ; Is the number of possible tricks with diamonds
                           ; trump better than the best suit so far?
    bcc PB2Next1           ; No, carry on to the next suit
    sta HighPsblTrks       ; Yes, store this number and set the best suit
    lda #DiamondSuitValue
    sta BestSuit
PB2Next1
    ldy DeclinedSuit
    cpy #ClubSuitValue
    beq PB2ClubsDeclined
    lda #ClubSuitValue
    sta PotentialTrump
    jsr AnalyzeHand
    lda PossibleTricks
    jmp PB2Clubs
PB2ClubsDeclined
    lda #$00
PB2Clubs
    cmp HighPsblTrks     ; Is the number of possible tricks with clubs
                           ; trump better than the best suit so far?
    bcc PB2Next2           ; No, carry on to the next suit
    sta HighPsblTrks       ; Yes, store this number and set the best suit
    lda #ClubSuitValue
    sta BestSuit
PB2Next2
    ldy DeclinedSuit
    cpy #SpadeSuitValue
    beq PB2SpadesDeclined
    lda #SpadeSuitValue
    sta PotentialTrump
    jsr AnalyzeHand
    lda PossibleTricks
    jmp PB2Spades
PB2SpadesDeclined
    lda #$00
PB2Spades
    cmp HighPsblTrks     ; Is the number of possible tricks with spades
                           ; trump better than the best suit so far?
    bcc PB2Next3           ; No, carry on to the next suit
    sta HighPsblTrks       ; Yes, store this number and set the best suit
    lda #SpadeSuitValue
    sta BestSuit
PB2Next3
    lda HighPsblTrks
    sta PossibleTricks
    jsr DecideBid          ; Submit the highest possible tricks to be taken
                           ; with this hand to get a choice
    lda Choice
    cmp #ChoicePass
    bne PB2Bid
    lda #MsgNumPass
    jmp PB2SetMessage
PB2Bid
    lda BestSuit
    and #RealSuitMask      ; Transform suit mask into index between 0 and 3
    lsr                    ; Suit is now in bits 5 and 4
    lsr                    ; Suit is now in bits 4 and 3
    lsr                    ; Suit is now in bits 3 and 2
    lsr                    ; Suit is now in bits 2 and 1
    lsr                    ; Suit is now in bits 1 and 0
    sta T1
    lda Choice
    cmp #ChoiceCall
    bne PB2Alone
    lda #MsgNumTrumpHearts
    clc
    adc T1                 ; Add number derived from suit to message to show
                           ; the correct chosen suit
    jmp PB2SetMessage
PB2Alone
    lda #MsgNumAloneHearts
    clc
    adc T1
PB2SetMessage
    sta MessageNum
    lda #FramesWait
    sta FrameCounter
    rts
PB2Advance
    lda Choice
    cmp #ChoicePass
    beq PB2Pass
    lda BestSuit
    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
    lda Turn
    adc #$01
    and #PlayersMask
    sta Turn
    lda Turn
    beq PB2SouthBids
    lda #SubturnShowName
    sta Subturn
    lda #MessageRegionDisplayColor
    jmp PB2a
PB2SouthBids
    lda #SubturnSelectBid
    sta Subturn
    lda #MessageRegionChoiceColor
PB2a
    sta MessageRegionColor
    lda Turn
    clc
    adc #MsgNumSouth
    sta MessageNum
    lda #FramesWait
    sta FrameCounter
    rts
PB2DealerPass
    jsr StartHolding
    ; Throw hand in and start a fresh one
    rts
PB2South
    lda Subturn
    cmp #SubturnSelectBid    ; Is the player making a bid or choosing trump?
    bne PB2SouthSuit         ; Choosing trump, so jump
    lda TriggerTrack
    cmp #TriggerOn
    bne PB2S0
    lda Choice               ; Player pressed trigger, so bid is submitted
    cmp #ChoicePass          ; Did the human player pass?
    beq PB2Advance           ; Yes, go to the next player
    lda #SubturnSelectSuit   ; No, in the next frame player will select a suit
    sta Subturn
    rts
PB2S0
    lda PaddlePos
    cmp #$0a
    bmi PB2S1
    lda #ChoicePass
    sta Choice
    lda #MsgNumPass
    sta MessageNum
    rts
PB2S1
    cmp #$04
    bmi PB2S2
    lda #ChoiceCall
    sta Choice
    lda #MsgNumCall
    sta MessageNum
    rts
PB2S2
    lda #ChoiceAlone
    sta Choice
    lda #MsgNumAlone
    sta MessageNum
    rts
PB2SouthSuit
    lda TriggerTrack
    cmp #TriggerOn
    bne PB2SouthSelecting
    lda BestSuit           ; South selected a suit by pressing the trigger
    cmp DeclinedSuit       ; Is this suit the one that was turned down?
    beq PB2ChoseDeclined   ; Yes, reject the chosen suit
    jmp PB2Advance         ; No, accept the chosen suit
PB2ChoseDeclined
    ; buzz?
    rts
PB2SouthSelecting
    lda PaddlePos
    cmp #$0b
    bmi PB2S3
    lda #MsgNumTrumpHearts
    sta MessageNum
    lda #HeartSuitValue
    sta BestSuit
    rts
PB2S3
    cmp #$08
    bmi PB2S4
    lda #MsgNumTrumpDiamonds
    sta MessageNum
    lda #DiamondSuitValue
    sta BestSuit
    rts
PB2S4
    cmp #$04
    bmi PB2S5
    lda #MsgNumTrumpClubs
    sta MessageNum
    lda #ClubSuitValue
    sta BestSuit
    rts
PB2S5
    lda #MsgNumTrumpSpades
    sta MessageNum
    lda #SpadeSuitValue
    sta BestSuit
    rts

StartDiscarding
    lda #StageDiscarding
    sta Stage
    lda #MsgNumSwap
    sta MessageNum
    lda #MessageRegionDisplayColor
    sta MessageRegionColor
    ldx Dealer
    jsr GetHandOffsets
    lda Upcards,x
    and #FullSuitMask
    sta PotentialTrump
    rts

PerformDiscarding
    lda Dealer
    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
    lda DeckStart,x
    and #FullSuitMask
    cmp PotentialTrump     ; Is this card a trump?
    beq PDCardTrump        ; Yes, do not add to the score
    lda CardScore
    clc
    adc #$08               ; Card is not trump; add 8 to 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 #FullSuitMask
    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 #FullSuitMask
    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 #$08               ; Add 8 to 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
    jmp PDSwap
PDSouth
    lda TriggerTrack
    cmp #TriggerOn
    bne PDSouthSelecting
    lda CursorPos
    clc
    adc HandStartOffset
    jmp PDSwap
PDSouthSelecting
    jsr GetSelection
    rts
PDSwap                     ; Assumes index of card to swap is in accumulator
    tax
    lda DeckStart,x
    sta T1
    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)
    sta T2                 ; Save the upcard
    tya                    ; Retrieve index of card to swap
    tax
    lda T2                 ; Retrieve upcard
    sta DeckStart,x        ; Place upcard in hand
    ldx Dealer
    lda T1                 ; Retrieve card to be swapped
    ora #HideCardValue
    sta Upcards,x          ; Discard it!
    jsr StartPlaying
    rts

StartShuffle
    lda #StageShuffle
    sta Stage
    lda #$08
    sta Turn
    lda #MsgNumDeal
    sta MessageNum
    rts

PerformShuffle
    lda Turn
    cmp #$07
    bne SkipRefresh
    jsr RefreshDeck
SkipRefresh
    jsr ShuffleDeck
    dec Turn
    bpl PSOut
    jsr StartDeal
PSOut
    rts

StartDeal
    lda #StageDeal
    sta Stage
    lda #MsgNumDeal
    sta MessageNum
    rts

PerformDeal
    ldx #$04
DealLoop
    lda SouthHand,x
    and #ShowCardMask
    sta SouthHand,x
    dex
    bpl DealLoop
    ldx Dealer
    lda Upcards,x
    and #ShowCardMask
    sta Upcards,x
    sta Upcard          ; Save upcard for use in AnalyzeHand
    jsr StartBidding1
    rts

StartNewGame
    lda #StageNewGame
    sta Stage
    lda #MsgNumBlank
    sta MessageNum
    lda #MessageRegionDisplayColor
    sta MessageRegionColor
    rts

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

StartNewHand
    lda #StageNewHand
    sta Stage
    lda #MsgNumBlank
    sta MessageNum
    lda #MessageRegionDisplayColor
    sta MessageRegionColor
    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 #MsgNumGameOver
    sta MessageNum
    lda #MessageRegionDisplayColor
    sta MessageRegionColor
    rts

PerformGameOver
    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
    jmp SPDoSkip
SPCalcSkip
    lda #$03
    sta CardsInFullTrick
    lda Turn
    clc
    adc #$02
    and #PlayersMask
SPDoSkip
    sta PlayerToSkip
    lda Turn
    and #$01
    sta BiddingTeam
    lda Dealer
SPAdvanceLeader
    clc
    adc #$01
    and #PlayersMask
    cmp PlayerToSkip
    beq SPAdvanceLeader
    sta Leader
    sta Turn
ContinuePlaying
    lda #StagePlaying
    sta Stage
    lda #MsgNumBlank
    sta MessageNum
    lda #NoCursorPos
    sta CursorPos
    lda #$00
    sta CardInTrickNum
    lda #FramesWait
    sta FrameCounter
    rts

PerformPlaying
    lda Turn
    bne PPNotSouth
    jmp PPSouth
PPNotSouth
    dec FrameCounter
    lda FrameCounter
    cmp #FramesWait-1
    beq PPTimeToAct
    cmp #$00
    beq PPAdvance
    rts
PPAdvance
    inc CardInTrickNum
    lda CardInTrickNum
    cmp CardsInFullTrick
    beq PPTrickComplete
    lda Turn
PPAdvanceTurn
    clc
    adc #$01
    and #PlayersMask
    cmp PlayerToSkip
    beq PPAdvanceTurn
    sta Turn
    lda #FramesWait
    sta FrameCounter
    rts
PPTrickComplete
    jsr StartAddToTricks
    rts
PPTimeToAct
    ldx Turn             ; This is a stub that simply picks the nth card from
                         ; a computer player's hand, where n is the number of
                         ; the current trick (0-4)
    jsr GetHandOffsets
    lda TrickNum
    clc
    adc HandStartOffset
    sta CardToPlayOffset
    jmp PPPlayCard
PPSouth
    lda TriggerTrack
    cmp #TriggerOn
    bne PPSouthSelecting
    ldx Turn
    jsr GetHandOffsets
    lda CursorPos
    clc
    adc HandStartOffset
    sta CardToPlayOffset
    jsr IsCardValidPlay
    beq PPInvalidSelection
    lda #NoCursorPos
    sta CursorPos
    jmp PPPlayCard
PPInvalidSelection
    ; buzz?
    rts
PPSouthSelecting
    jsr GetSelection
    rts
PPPlayCard
    ldx CardToPlayOffset
    lda DeckStart,x
    and #ShowCardMask
    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
    jmp PPShowPlay
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?
    beq PPFollowedSuit   ; Yes, go handle it
    jmp PPShowPlay       ; If card isn't trump or in led suit, it can't win,
                         ; so just go show the card
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
    jmp PPShowPlay
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
    ; sound?
    lda #MsgNumBlank
    sta MessageNum
    lda #MessageRegionDisplayColor
    sta MessageRegionColor
    lda #FramesShortWait
    sta FrameCounter
    rts

PerformAddToTricks
    dec FrameCounter
    beq PAT1
    rts
PAT1
    ldx #$03
PATClear
    lda Upcards,x
    ora #CardPlayedValue
    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
    inc Team1Score,x
    lda #MsgNumPoint
    sta MessageNum
    jmp SAS1
SASEuchre
    txa
    eor #$01
    tax
    lda Team1Score,x
    clc
    adc #$02
    sta Team1Score,x
    lda #MsgNumEuchre
    sta MessageNum
    jmp SAS1
SASMarch
    lda PlayerToSkip
    cmp #NoPlayer
    beq SASMarch2
    lda #$04
    sta T1
    lda #MsgNumMarch4
    sta T2
    jmp SASFive
SASMarch2
    lda #$02
    sta T1
    lda #MsgNumMarch2
    sta T2
SASFive
    lda Team1Score,x
    clc
    adc T1
    sta Team1Score,x
    lda T2
    sta MessageNum
SAS1
    lda #FramesWait*2
    sta FrameCounter
    rts

PerformAddToScore
    dec FrameCounter
    beq PAS1
    rts
PAS1
    lda Team1Score
    cmp #$0a
    bpl PASOver
    lda Team2Score
    cmp #$0a
    bpl PASOver
    jsr StartBetweenHands
    rts
PASOver
    ; sound?
    jsr StartGameOver
    rts

StartBetweenHands
    lda #StageBetweenHands
    sta Stage
    lda #MsgNumDeal
    sta MessageNum
    lda #MessageRegionChoiceColor
    sta MessageRegionColor
    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
    and #CardPlayedMask      ; Has the card already been played?
    cmp #CardPlayedValue
    bne 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 NumVoids
    sta HasRight
    sta HasLeft
    sta HasTrumpAce

    lda PotentialTrump
    ora #JackRankValue
    and #RankSuitMask
    sta RightBowerMask
    lda PotentialTrump
    eor #FlipColorSuitMask
    ora #JackRankValue
    and #RankSuitMask
    sta LeftBowerMask

; Check on the trumps in the hand
    
    ldx HandEndOffset
AHCountLoop1
    lda DeckStart,x
    and #FullSuitMask
    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
    and #RankSuitMask
    cmp RightBowerMask  ; Is the card the jack of trump?
    bne AHNotRight      ; No, check for other ranks
    inc HasRight        ; Yes, it's the right bower!
    jmp AHCL1
AHNotTrump
    lda DeckStart,x
    and #RankSuitMask
    cmp LeftBowerMask   ; It's not of the trump suit, but is it the left bower?
    bne AHCL1           ; No, go on to next card
    inc NumTrumps
    inc HasLeft
    jmp AHCL1
AHNotRight
    cmp #AceRankValue   ; Is the card the ace of trump?
    bne AHCL1           ; No, give up...
    inc HasTrumpAce     ; Yes, it's the ace of trump!
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 #$03
AHCountLoop2
    lda #$00
    sta NumInSuit
    ldx HandEndOffset
AHCountLoop3
    tya                 ; Suit is in bits 1 and 0
    asl                 ; Suit is in bits 2 and 1
    asl                 ; Suit is in bits 3 and 2
    asl                 ; Suit is in bits 4 and 3
    sta T1              ; Hold on to this value
    asl                 ; Suit is in bits 5 and 4
    asl                 ; Suit is in bits 6 and 5
    ora T1              ; Suit is in bits 6 and 5 and repeated in 4 and 3
    sta T1
    cmp PotentialTrump  ; Is this loop for trump?
    beq AHCL2           ; Yes, advance out of the inner loop
    lda DeckStart,x
    and #FullSuitMask
    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
    bne AHNotVoid       ; If a <> 0, we had a card in the suit
    inc NumVoids        ; If a = 0, we were void in the suit
    jmp AHCL2
AHNotVoid
    lda NumInSuit
    cmp #$01            ; Did we have just one card in the suit?
    bne AHCL2           ; No, so we didn't get an off-suit lone ace
    lda HasAce
    beq AHCL2           ; If a = 0, we do not have any aces in the suit
    inc NumOffLoneAces  ; If a <> 0, we have an off-suit lone ace!
AHCL2
    dey
    bpl AHCountLoop2
    
; Now calculate the number of tricks this hand will probably take.
; It's possible to calculate for greater than five since this is just
; an estimate.  Hands that will take five tricks will be played alone.

GetPossibleTricks
    lda #$00
    sta PossibleTricks
    lda HasRight   ; Do we have the right bower?
    beq PT1             ; No, move on to next check
    inc PossibleTricks  ; Yes - a guaranteed trick!
PT1
    lda NumOffLoneAces  ; Do we have any off-suit lone aces?
    beq PT2             ; No, move on to next check
    cmp #$03            ; Do we have all three of them?
    bmi PT1a            ; No, two or less
    lda #$02            ; Yes, count only two of them as likely
    jmp PT1b
PT1a
    lda NumOffLoneAces
PT1b
    sta T1
    lda PossibleTricks
    clc
    adc T1              ; Increase number of possible tricks by number of
                        ; off-suit lone aces (up to two)
    sta PossibleTricks
PT2
    lda NumTrumps
    cmp #$02            ; Does hand have more than two trumps?
    bcc PT3             ; No, move on to next check
    sec
    sbc #$02            ; Find number of trumps over two...
    clc                 ; then increase the likely tricks by that number
    adc PossibleTricks  ; (there are only seven trumps, so if one hand
    sta PossibleTricks  ; has three, no player is likely to have more than two)
PT3
    lda HasLeft    ; Does hand have the left bower?
    beq PT4             ; No, move on to next check
    lda NumTrumps
    cmp #$01            ; Is the left bower the only trump?
    bne PT3a            ; No, there is more than one
    lda NumVoids        ; Do we have a void?
    bne PT4             ; No void and the left bower is alone, so it will
                        ; possibly be taken by the right bower
PT3a
    inc PossibleTricks  ; Left bower is not alone or it is and there is a void,
                        ; so the bower should take a trick

PT4
    lda HasTrumpAce     ; Does hand have the ace of trump?
    beq PT5             ; No, move on to next check
    lda NumTrumps
    cmp #$01            ; Is the ace the only trump?
    bne PT4a            ; No, there is more than one
    lda NumVoids        ; Do we have a void?
    bne PT5             ; No void and the ace is alone, so it will probably
                        ; be taken by the right or left bower
PT4a
    inc PossibleTricks  ; Ace is not alone or it is and there is a void, so
                        ; the bower should take a trick

PT5
    lda Stage
    cmp #StageBidding1  ; Are we in the first stage of bidding or the second?
    beq PTExamineUpcard ; First stage, go ahead with checking the upcard
    jmp PT7             ; Second stage, skip check of upcard
PTExamineUpcard
    lda Dealer
    eor Turn            ; Bit 0 will be 1 if the upcard goes to an opponent
    and #$01            ; Does upcard belong to opponent?
    beq PT6             ; No, move on to next check
    lda Upcard
    and #RankSuitMask
    cmp RightBowerMask  ; Is the upcard the right bower?
    beq PT5a            ; Yes!
    lda NumTrumps       ; No, see how many trumps the hand has
    cmp #$04            ; If this hand has at least four trumps, there are only
                        ; three left to be distributed among three players and
                        ; the leftovers.  That makes it less likely the dealer
                        ; will get a void from the exchange.
    bcc PT6
PT5a
    dec PossibleTricks  ; Upcard is right bower or hand has less than four
                        ; trumps, so upcard may take a trick or cause a void

PT6
    lda Dealer
    cmp Turn            ; Is the dealer also the current bidder?
    bne PT7             ; No, move on to next check
    lda NumTrumps
    cmp #$05            ; Does hand already have five trumps?
    beq PT7             ; Yes, so upcard won't have much effect
    cmp #$02            ; Does hand have at least two trumps?
    bmi PT6a
    inc PossibleTricks  ; Yes, hand will therefore have from three to five
                        ; trumps, adding a probable trick
PT6a
    lda Upcard
    and #RankSuitMask
    cmp RightBowerMask  ; Is the upcard the right bower?
    bne PT6b            ; No, check for another rank
    inc PossibleTricks  ; Yes, and it will take a trick
PT6b
    lda NumTrumps
    cmp #$02            ; Does hand have exactly two trumps?
    bne PT6c            ; No, go on to next check
    lda NumVoids        ; Is there a void in this hand?
    bne PT6c            ; Yes, go on to next check
    inc PossibleTricks  ; With no voids, the addition of a third trump will
                        ; create one
PT6c
    lda NumTrumps
    cmp #$01            ; Does hand have only one trump?
    bne PT7
    lda HasLeft         ; Yes - find if it is the left bower or ace
    ora HasTrumpAce
    beq PT6d            ; The trump is low
    inc PossibleTricks  ; Addition of upcard can be used to protect the
                        ; left bower or ace, so it should get a trick
PT6d
    lda Upcard
    and #RankMask
    cmp #AceRankValue
    bne PT7
    inc PossibleTricks  ; With only one trump in hand, if the upcard is
                        ; an ace, it may be protected and may (but not
                        ; definitely) cause a void
PT7
    rts

DecideBid
    lda Stage
    cmp #StageBidding1  ; Is this the first round of bidding?
    bne DB0a            ; No
    lda Dealer
    eor Turn            ; Bit 0 will be set if the upcard goes to an opponent
    and #$01            ; Does upcard belong to opponent?
    beq DB0a            ; No...
    lda #$01            ; Yes!
    jmp DB0b
DB0a
    lda #$00
DB0b
    sta T1              ; T1 = 1 if the upcard goes to an opponent
DB1
    lda PossibleTricks
    cmp #$05            ; Likely to take five tricks?
    bmi DB1a            ; No, move on...
    lda #ChoiceAlone    ; Go alone!
    jmp DBEnd
DB1a
    cmp #$04            ; Likely to take four tricks?
    bmi DB2             ; No, move on...
    lda T1              ; Yes, will the upcard go to the other team?
    beq DB1b            ; No...
    lda #ChoiceCall     ; Yes, call but do not go alone
    jmp DBEnd
DB1b
    lda #ChoiceAlone    ; The upcard does not go to the other team or this is
                        ; the second round, so try going alone
    jmp DBEnd
DB2
    cmp #$03            ; Likely to take three tricks?
    bmi DB3             ; No, move on...
    lda #ChoiceCall     ; Yes, pick or order up or call trump
    jmp DBEnd
DB3
    cmp #$02            ; Likely to take two tricks?
    bmi DB4             ; No, move on...
    lda Turn
    and #$01            ; If last bit is set, bidder is west or east
    beq DBNS            ; Bidder is north or south
    lda Team2Score
    tax
    lda Team1Score
    tay
    jmp DB3a
DBNS
    lda Team1Score
    tax
    lda Team2Score
    tay
                        ; 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 DB3b            ; Yes, go to next check
    cpy #$06            ; Does the other team have six or seven points?
    bmi DB3b            ; No, go to next check
    lda #ChoiceCall     ; With score 9-6 or 9-7, it's better to risk a euchre
                        ; than a march
    jmp DBEnd
DB3b
    cpy #$08            ; Does the other team have eight points?
    bne DB3c            ; No, move on...
    lda #ChoicePass     ; Pass to avoid a euchre
    jmp DBEnd
DB3c
    cpx #$06            ; Do we have six points?
    bne DB3d            ; No, move on...
    cpy #$04            ; Does the other team have four or more points?
    bpl DB3d            ; Yes, go to next check
    cpy #$02            ; Does the other team have two or three points?
    bmi DB3d            ; 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)
    jmp DBEnd
DB3d
    lda T1              ; Does upcard belong to opponent?
    beq DB3e            ; No...
    lda #ChoicePass     ; Yes, don't risk on this hand
    jmp DBEnd
DB3e
    lda #ChoiceCall     ; With two tricks and no other compelling factors, call
    jmp DBEnd
DB4
    lda #ChoicePass     ; Pass when likely to take one trick or no tricks
DBEnd
    sta Choice
    
    rts                 ; Whew!

; 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
    stx T1
    txa
    asl                    ; Take number of player...
    asl                    ; Multiply by 4...
    clc
    adc T1                 ; Add same number to effectively multiply by 5...
    sta HandStartOffset
    clc
    adc #CardsInHand-1
    sta HandEndOffset
    rts

; translate a paddle position into a selection of one of five cards
;
; returns: CursorPos = which card is selected (0-4)

GetSelection
    lda PaddlePos
    cmp #$0b
    bmi GS1
    lda #$00
    jmp GSEnd
GS1
    cmp #$09
    bmi GS2
    lda #$01
    jmp GSEnd
GS2
    cmp #$06
    bmi GS3
    lda #$02
    jmp GSEnd
GS3
    cmp #$03
    bmi GS4
    lda #$03
    jmp GSEnd
GS4
    lda #$04
GSEnd
    sta CursorPos
    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 #%00101000
    sta LeftBowerMask
    ldx #CardsInDeck-1
CBLoop
    lda DeckStart,x
    and #ShowCardMask   ; 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
    sta T4
    lda DeckStart,x
    and #%10000000
    ora T4              ; Do not change whether card is hidden or shown
    sta DeckStart,x
    jmp CBLoopEnd
CBLB
    cmp LeftBowerMask   ; Is this card the jack of the same color as trump?
    bne CBLoopEnd       ; No, it is not either bower
    lda TrumpSuitMask
    eor #FlipColorSuitMask  ; Make the "real" suit trump, but leave the display
                            ; suit alone
    ora #LeftRankValue  ; Grant the card the left bower rank
    sta T4
    lda DeckStart,x
    and #%10000000
    ora T4              ; Do not change whether card is hidden or shown
    sta DeckStart,x
CBLoopEnd
    dex
    bpl CBLoop
    rts

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

GetScoreImage
    stx T4
    txa
    asl
    asl
    clc
    adc T4
    adc #<ScoreImageStart
    sta $00,y
    lda #>ScoreImageStart
;    adc #$00
    sta $01,y
    rts

GetRankImage
    txa
    asl
    sta T4
    asl
    clc
    adc T4
    adc #<RankImageStart
    sta $00,y
    lda #>RankImageStart
    adc #$00
    sta $01,y
    rts

GetSuitImage
    txa
    asl
    sta T4
    asl
    clc
    adc T4
    adc #<SuitImageStart
    sta $00,y
    lda #>SuitImageStart
;    adc #$00
    sta $01,y
    rts

GetLetterImage
    txa
    asl
    sta T3
    asl
    clc
    adc T3
    adc #<LetterImageStart
    sta $00,y
    lda #>LetterImageStart
;    adc #$00
    sta $01,y
    rts

GetMessagePointer
    txa
    asl
    sta T1
    asl
    clc
    adc T1
    adc #<MessageTableStart
    sta $00,y
    lda #>MessageTableStart
;    adc #$00
    sta $01,y
    rts

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

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

; routine to shuffle the deck

RefreshDeck
    ldx #CardsInDeck
RD1
    dex
    lda NewDeck,x
    sta DeckStart,x
    txa
    bne RD1
    rts

ShuffleDeck
    ldx #CardsInDeck
OneShuffle
    dex
    stx T1
SD1
    jsr RandomByte
    cmp #HighRandInShuffle
    bcs SD1
SD2
    cmp #CardsInDeck
    bcc SD3
    sec
    sbc #CardsInDeck
    jmp SD2
SD3
    ldx T1
    tay
    lda DeckStart,x
    sta T2
    lda DeckStart,y
    sta DeckStart,x
    lda T2
    sta DeckStart,y
    ldx T1
    bne OneShuffle
    rts

;    org $fc00

; 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
    asl
    asl
    asl
    eor rand4 ;new bit is now in bit 6 of A
    asl
    asl        ;new bit is now in carry
    rol rand1 ;shift new bit into bit 0 of register; bit 7 goes into carry
    rol rand2 ;shift old bit 7 into bit 8, etc.
    rol rand3
    rol rand4
    rts

RandomByte
    ldx #8
RandomByte1
    jsr RandomBit
    dex
    bne RandomByte1
    lda rand1
    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
    stx T2
    sty T3
    and #RankMask
    tax
    ldy T2
    jsr GetRankImage
    lda T1
    and #DispSuitMask
    lsr
    lsr
    lsr
    tax
    ldy T3
    jsr GetSuitImage

    lda T1
    and #CardHiddenMask
    bne HideCard
    lda T1
    and #BlackSuitMask
    bne CardIsBlack
    lda #RedSuitColor
    jmp LeaveGetCardGraphics
CardIsBlack
    lda #BlackSuitColor
    jmp LeaveGetCardGraphics
HideCard
    lda #CardTableColor
LeaveGetCardGraphics
    rts

    org $fd00

; 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)
;
; 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

; 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

; 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)

DrawMessageText
    lda #$01
    sta VDELP0
    sta VDELP1
    ldy #$05
    sty T2
loop2
    ldy T2
    lda (ImgPtr1),y
    sta GRP0
    sta WSYNC
    lda (ImgPtr2),y
    sta GRP1
    lda (ImgPtr3),y
    sta GRP0
    lda (ImgPtr4),y
    sta T1
    lda (ImgPtr5),y
    tax
    lda (ImgPtr6),y
    tay
    lda T1
    sta GRP1
    stx GRP0
    sty GRP1
    sta GRP0
    dec T2
    bpl loop2
    rts

    org $fe00

ScoreImageStart
ScoreImage0
    .byte $07,$05,$05,$05,$07
ScoreImage1
    .byte $07,$02,$02,$06,$02
ScoreImage2
    .byte $07,$04,$07,$01,$07
ScoreImage3
    .byte $07,$01,$03,$01,$07
ScoreImage4
    .byte $01,$01,$07,$05,$01
ScoreImage5
    .byte $07,$01,$07,$04,$07
ScoreImage6
    .byte $07,$05,$07,$04,$07
ScoreImage7
    .byte $04,$04,$02,$01,$07
ScoreImage8
    .byte $07,$05,$02,$05,$07
ScoreImage9
    .byte $07,$01,$07,$05,$07
ScoreImage10
    .byte $77,$25,$25,$65,$27
ScoreImage11
    .byte $77,$22,$22,$66,$22
ScoreImage12
    .byte $77,$24,$27,$61,$27
ScoreImage13
    .byte $77,$21,$23,$61,$27

TrickImages
    .byte $00,$01,$05,$15,$55,$FF

NewDeck
    .byte $80,$81,$82,$83,$84,$85
    .byte $a8,$a9,$aa,$ab,$ac,$ad
    .byte $d0,$d1,$d2,$d3,$d4,$d5
    .byte $f8,$f9,$fa,$fb,$fc,$fd

MessageTableStart
MessageBlank
    .byte $00,$00,$00,$00,$00,$00
MessageSelect
    .byte $13,$05,$0c,$05,$03,$14
MessageSwap
    .byte $00,$13,$17,$01,$10,$00
MessagePass
    .byte $00,$10,$01,$13,$13,$00
MessageOrder
    .byte $0f,$12,$04,$05,$12,$00
MessageCall
    .byte $00,$03,$01,$0c,$0c,$00
MessageAlone
    .byte $01,$0c,$0f,$0e,$05,$00
MessageTrumpHearts
    .byte $14,$12,$15,$0d,$10,$1b
MessageTrumpDiamonds
    .byte $14,$12,$15,$0d,$10,$1c
MessageTrumpClubs
    .byte $14,$12,$15,$0d,$10,$1d
MessageTrumpSpades
    .byte $14,$12,$15,$0d,$10,$1e
MessageAloneHearts
    .byte $01,$0c,$0f,$0e,$05,$1b
MessageAloneDiamonds
    .byte $01,$0c,$0f,$0e,$05,$1c
MessageAloneClubs
    .byte $01,$0c,$0f,$0e,$05,$1d
MessageAloneSpades
    .byte $01,$0c,$0f,$0e,$05,$1e
MessageDeal
    .byte $00,$04,$05,$01,$0c,$00
MessageGameOver
    .byte $00,$0f,$16,$05,$12,$00
MessageSouth
    .byte $13,$0f,$15,$14,$08,$00
MessageWest
    .byte $00,$17,$05,$13,$14,$00
MessageNorth
    .byte $0e,$0f,$12,$14,$08,$00
MessageEast
    .byte $00,$05,$01,$13,$14,$00
MessagePoint
    .byte $10,$0f,$09,$0e,$14,$00
MessageEuchre
    .byte $05,$15,$03,$08,$12,$05
MessageMarch2
    .byte $0d,$01,$12,$03,$08,$1f
MessageMarch4
    .byte $0d,$01,$12,$03,$08,$20

    org $ff00

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

LetterImageStart
LetterImageSpace
    .byte $00,$00,$00,$00,$00,$00  ;
LetterImageA
    .byte $66,$66,$7e,$66,$3c,$18  ; A
LetterImageB
    .byte $7c,$66,$66,$7c,$66,$7c  ; B
LetterImageC
    .byte $3c,$66,$60,$60,$66,$3c  ; C
LetterImageD
    .byte $7c,$66,$66,$66,$66,$7c  ; D
LetterImageE
    .byte $7e,$60,$60,$78,$60,$7e  ; E
LetterImageF
    .byte $60,$60,$60,$78,$60,$7e  ; F
LetterImageG
    .byte $3c,$66,$6e,$60,$66,$3c  ; G
LetterImageH
    .byte $66,$66,$66,$7e,$66,$66  ; H
LetterImageI
    .byte $7e,$18,$18,$18,$18,$7e  ; I
LetterImageJ
    .byte $3c,$66,$06,$06,$06,$0e  ; J
LetterImageK
    .byte $66,$66,$6c,$78,$6c,$66  ; K
LetterImageL
    .byte $7e,$60,$60,$60,$60,$60  ; L
LetterImageM
    .byte $c6,$c6,$d6,$fe,$ee,$c6  ; M
LetterImageN
    .byte $c6,$ce,$de,$f6,$e6,$c6  ; N
LetterImageO
    .byte $3c,$66,$66,$66,$66,$3c  ; O
LetterImageP
    .byte $60,$60,$7c,$66,$66,$7c  ; P
LetterImageQ
    .byte $3a,$64,$6a,$66,$66,$3c  ; Q
LetterImageR
    .byte $66,$66,$66,$7c,$66,$7c  ; R
LetterImageS
    .byte $3c,$06,$06,$3c,$60,$3c  ; S
LetterImageT
    .byte $18,$18,$18,$18,$18,$7e  ; T
LetterImageU
    .byte $3c,$66,$66,$66,$66,$66  ; U
LetterImageV
    .byte $10,$38,$6c,$c6,$c6,$c6  ; V
LetterImageW
    .byte $c6,$ee,$fe,$d6,$c6,$c6  ; W
LetterImageX
    .byte $c6,$c6,$6c,$38,$6c,$c6  ; X
LetterImageY
    .byte $18,$18,$18,$3c,$66,$66  ; Y
LetterImageZ
    .byte $7e,$60,$38,$1c,$06,$7e  ; Z
SuitImageStart
SuitImageHeart
    .byte $10,$38,$7c,$fe,$ee,$44
SuitImageDiamond
    .byte $18,$3c,$7e,$7e,$3c,$18
SuitImageClub
    .byte $18,$7e,$ff,$18,$3c,$18
SuitImageSpade
    .byte $38,$ba,$fe,$7c,$38,$10
DigitImage2
    .byte $7e,$60,$3c,$06,$46,$3c
DigitImage4
    .byte $0c,$7e,$4c,$2c,$1c,$0c

RankImageStart
RankImage9
    .byte $3c,$46,$06,$3e,$66,$3c
RankImage10
    .byte $ee,$5b,$5b,$5b,$db,$4e
RankImageJ
    .byte $3c,$66,$06,$06,$06,$0e  ; J
RamkImageQ
    .byte $3a,$64,$6a,$66,$66,$3c  ; Q
RankImageK
    .byte $66,$66,$6c,$78,$6c,$66  ; K
RankImageA
    .byte $66,$66,$7e,$66,$3c,$18  ; A
RankImageLeft
    .byte $ec,$8a,$8a,$8c,$8a,$8c  ; debug "LB"
RankImageRight
    .byte $ac,$aa,$aa,$cc,$aa,$cc  ; debug "RB"

    org $fffc
    .word CartStart
    .word CartStart

Attachment: Euchre.bin
Description: Binary data

"Cartridge.MD5" "b1b20536aef4eed9c79dc5804f077862"
"Cartridge.Name" "Euchre (November 9, 2001 pre-release)"
"Cartridge.Manufacturer" "Erik Eid"
"Cartridge.Rarity" "New Release"
"Cartridge.Type" "4K"
"Controller.Left" "Paddles"
"Display.Format" "NTSC"
""

. . \_ +\_ + o_-/ . O
. \-_o -=/- . .
. . \=// .
---------\_______ ||O ___.______
/* Erik Eid */ \____||L/\_____/
/* eeid@xxxxxxxxx */_______________________
Current Thread