[stella] Introduction and new game

Subject: [stella] Introduction and new game
From: "Erik J. Eid" <eeid@xxxxxxxxx>
Date: Wed, 06 Jun 2001 22:23:19 -0400
Greetings to everyone reading this listserv!

I'm Erik Eid, a fan of the Atari 2600 fan for over 20 years but very new to programming for it. (I am, however, a professional developer, having worked in languages as varied as C, Delphi, Fortran, and PL/SQL.) I first lurked on this list about three to four years ago, being eager to use the development tools on the Stella Gets a New Brain CD. I played around with programming the 2600 a bit back then, but never got further than a couple of experiments.

I stopped reading a list after a few months and didn't pick it back up until a couple of weeks ago. My interest was renewed after seeing yet more homebrew efforts grace our TV screens. My congratulations go out to all of those who have written new games for the 2600, as you have accomplished something tremendous!

I decided that this time around I would complete a game. Since I am a beginner with both assembly language and 2600 programming, I thought I should try something simple. A card game seemed like a good idea since it would involve a mostly static display. There are also very few card games in the 2600 library (Blackjack, Bridge, and Casino are the only three I can think of).

Since I'm in the midwest U.S., it only makes sense that my game of choice is Euchre. It also has the virtue of being simple.

Attached is a binary and source code. So far you can't actually play a game of Euchre. All that happens is that the player is dealt a hand and four cards are put on the table, just to test out the display of the cards. If you hit the Reset switch, a new set of cards is dealt.

I have one sticky problem right now: the right-most column of the rank of East's trick inherits the right-most column of the suit. This is only evident when the suit is clubs or the rank is Ace or 10. Minor adjustments to statements in hopes of fixing the cycles only seem to make things worse.

Any help you can give to me, a definite novice, is greatly appreciated.

Also, many thanks to Erik Mooney for posting his random number generator and player positioning routine to the Stella list years ago. I have incorporated both into my game and they make it possible for the game to exist at all. (I did take out the positioning table since there's no motion in my game. All positions are computed ahead of time.)


;
; Euchre game program for the Atari 2600 video computer system
;
; Copyright 2001 by Erik Eid (eeid@xxxxxxxxx)
;
; Last update: June 3, 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

Team1Color = $88
Team2Color = $38
TableRegionColor = $d4
CardTableColor = $0f
RedSuitColor = $36
BlackSuitColor = $00
MessageRegionColor = $22

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)
ShowCardMask = %10000000  ; Bit mask for determining if a card is shown

BlackSuitMask = %00010000

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


; Variables
    
    seg.u vars
    org $80

Team1Score  ds 1
Team2Score  ds 1
Team1Tricks ds 1
Team2Tricks ds 1
NorthHand   ds 5  ; Cards in a player's hand
EastHand    ds 5
SouthHand   ds 5
WestHand    ds 5
NorthCard   ds 1  ; Cards down on the table
EastCard    ds 1
SouthCard   ds 1
WestCard    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
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

DeckStart = NorthHand

; 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

ProgStart

;
; Begin a new game
;
GameStart
    lda #$00
    sta Team1Score
    sta Team2Score

;
; Begin a new hand
;
HandStart
    lda #$00
    sta Team1Tricks
    sta Team2Tricks
    lda #$01
    sta NeedShuffle

;
; Main loop
;
Main
; Start of display kernel
; Provide three lines of vertical sync
    lda #VS_Enable
    sta WSYNC
    sta WSYNC
    sta WSYNC
    sta VSYNC
    sta WSYNC
    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

; 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

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

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

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

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

; 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

    ldy #7
DrawWestEastCards
    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
    stx GRP1
    pla
    sta WSYNC
    dey
    bpl DrawWestEastCards

    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

WaitEndSouth
    lda INTIM
    nop
    bne WaitEndSouth

    lda #9  ; burn 8 lines
    sta TIM64T

    lda #$00
    sta COLUBK
    sta PF1
    sta PF2

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

    lda #$f0
    sta PF2
    sta WSYNC
    sta WSYNC
    jsr DrawSingleCard
    lda #$00
    sta GRP0
    sta GRP1
    sta WSYNC
    sta WSYNC
    inc HandCard
    lda HandCard
    cmp #$05
    bne ShowHandLoop

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

    lda #9
    sta TIM64T

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

WaitForGap
    lda INTIM
    nop
    bne WaitForGap

    lda #MessageRegionColor
    sta COLUBK
    lda #MessageP0Pos
    ldx #0
    jsr PositionPlayer
    lda #MessageP1Pos
    ldx #1
    jsr PositionPlayer
    lda P_ThreeClose
    sta NUSIZ0
    sta NUSIZ1

    lda #19  ; 16 lines of message
    sta TIM64T
WaitForMessage
    lda INTIM
    nop
    bne WaitForMessage

    lda #$00
    sta COLUPF

    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 WaitOverscan
    jmp ProgStart

WaitOverscan
    lda INTIM
    nop
    bne WaitOverscan
    sta WSYNC

    jmp Main

GetScoreImage
    txa
    asl
    tax
    lda ScoreImages,x
    sta $00,y
    lda ScoreImages+1,x
    sta $01,y
    rts

GetRankImage
    txa
    asl
    tax
    lda RankImages,x
    sta $00,y
    lda RankImages+1,x
    sta $01,y
    rts

GetSuitImage
    txa
    asl
    tax
    lda SuitImages,x
    sta $00,y
    lda SuitImages+1,x
    sta $01,y
    rts

NewDeck
    .byte $00,$01,$02,$03,$04,$05
    .byte $28,$29,$2a,$2b,$2c,$2d
    .byte $50,$51,$52,$53,$54,$55
    .byte $78,$79,$7a,$7b,$7c,$7d

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

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,$05,$07,$01,$01
ScoreImage5
    .byte $07,$01,$07,$04,$07
ScoreImage6
    .byte $07,$04,$07,$05,$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 $72,$22,$22,$62,$22
ScoreImage12
    .byte $77,$24,$27,$61,$27
ScoreImage13
    .byte $77,$21,$23,$61,$27

ScoreImages
    .byte <ScoreImage0
    .byte >ScoreImage0
    .byte <ScoreImage1
    .byte >ScoreImage1
    .byte <ScoreImage2
    .byte >ScoreImage2
    .byte <ScoreImage3
    .byte >ScoreImage3
    .byte <ScoreImage4
    .byte >ScoreImage4
    .byte <ScoreImage5
    .byte >ScoreImage5
    .byte <ScoreImage6
    .byte >ScoreImage6
    .byte <ScoreImage7
    .byte >ScoreImage7
    .byte <ScoreImage8
    .byte >ScoreImage8
    .byte <ScoreImage9
    .byte >ScoreImage9
    .byte <ScoreImage10
    .byte >ScoreImage10
    .byte <ScoreImage11
    .byte >ScoreImage11
    .byte <ScoreImage12
    .byte >ScoreImage12
    .byte <ScoreImage13
    .byte >ScoreImage13

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

; routine to draw a single card
; assumes ImgPtr1 and ImgPtr2 point to proper images, sprites are
; positioned, and so on
    
DrawSingleCard
    ldy #07
DrawCardLoop
    sta WSYNC
    lda (ImgPtr1),y
    sta GRP0
    lda (ImgPtr2),y
    sta GRP1
    dey
    bpl DrawCardLoop
    rts

; routine to shuffle the deck

ShuffleDeck
    ldx #CardsInDeck
RefreshDeck
    dex
    lda NewDeck,x
    sta DeckStart,x
    bne RefreshDeck

    lda #$08
    sta T3
ShuffleLoop
    ldx #CardsInDeck
OneShuffle
    dex
    stx T1
    jsr RandomByte
ModLoop
    cmp #CardsInDeck
    bcc LTCards
    sec
    sbc #CardsInDeck
    jmp ModLoop
LTCards
    ldx T1
    tay
    lda DeckStart,x
    sta T2
    lda DeckStart,y
    sta DeckStart,x
    lda T2
    sta DeckStart,y

    ldx T1
    bne OneShuffle

    dec T3
    bne ShuffleLoop

    rts
    
    org $fd00

; 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
    lda T1
    and #RankMask
    tax
    ldy T2
    jsr GetRankImage
    lda T1
    and #DispSuitMask
    lsr
    lsr
    lsr
    tax
    ldy T3
    jsr GetSuitImage

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

    org $fe00

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

    org $ff00

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

RankImage9
    .byte $00,$3c,$46,$06,$3e,$66,$66,$3c
RankImage10
    .byte $00,$ee,$5b,$5b,$5b,$5b,$db,$4e
RankImageJack
    .byte $00,$3c,$66,$06,$06,$06,$06,$0e
RankImageQueen
    .byte $00,$3a,$64,$6a,$66,$66,$66,$3c
RankImageKing
    .byte $00,$66,$6c,$78,$70,$78,$6c,$66
RankImageAce
    .byte $00,$c6,$c6,$fe,$fe,$c6,$7c,$38
RankImageLeft
    .byte $00,$ec,$8a,$8a,$8c,$8a,$8a,$8c  ; debug "LB"
;   .byte $00,$3c,$66,$06,$06,$06,$06,$0e
RankImageRight
    .byte $00,$ac,$aa,$aa,$cc,$aa,$aa,$cc  ; debug "RB"
;   .byte $00,$3c,$66,$06,$06,$06,$06,$0e

SuitImageHeart
    .byte $00,$10,$38,$7c,$fe,$fe,$ee,$44
SuitImageDiamond
    .byte $00,$10,$38,$7c,$fe,$7c,$38,$10
SuitImageClub
    .byte $00,$18,$7e,$ff,$7e,$18,$3c,$18
SuitImageSpade
    .byte $00,$38,$ba,$fe,$fe,$7c,$38,$10

RankImages
    .byte <RankImage9
    .byte >RankImage9
    .byte <RankImage10
    .byte >RankImage10
    .byte <RankImageJack
    .byte >RankImageJack
    .byte <RankImageQueen
    .byte >RankImageQueen
    .byte <RankImageKing
    .byte >RankImageKing
    .byte <RankImageAce
    .byte >RankImageAce
    .byte <RankImageLeft
    .byte >RankImageLeft
    .byte <RankImageRight
    .byte >RankImageRight

SuitImages
    .byte <SuitImageHeart
    .byte >SuitImageHeart
    .byte <SuitImageDiamond
    .byte >SuitImageDiamond
    .byte <SuitImageClub
    .byte >SuitImageClub
    .byte <SuitImageSpade
    .byte >SuitImageSpade

    org $fffc
    .byte <CartStart
    .byte >CartStart
    .byte <CartStart
    .byte >CartStart

Attachment: Euchre.bin
Description: Binary data


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