How do you cram Video Poker into 10 lines of 70’s era Atari BASIC?!
This all started Wednesday October 4th, 2023 when Y-Combinator Hacker News posted Show HN: Classic Video Poker(https://lfgslots.com/classicvideopoker/).
I like poker, especially 5 card games like Texas Hold Em’ and Classic Video Poker. I shared the link with a couple other veteran video game developers (Hi Brian & Kevin!), and conversation turned to how video poker should be programmed. This made me wonder if it could be coded in 10 lines of BASIC for the 2024 BASIC 10 Liner Contest .
The Rules
Jacks or Better VIDEO POKER is a 5 card draw poker game. The player places a bet, the deck is shuffled, and 5 cards are dealt. The player then selects which cards to hold and which to discard. Cards are discarded and replaced with new cards from the deck, the hand is evaluated, and if the player holds a winning hand they are rewarded with a payout based on the hand value and proportional to the initial bet. This process is repeated so long as the player has enough money to cover a bet, the player may add money to the game between hands.
The ranking and description of winning hands from highest to lowest:
- Royal Flush Ten, Jack, Queen, King, Ace in the same suit
- Straight Flush Five cards of consecutive values in the same suit
- Four of a Kind Four cards of the same value
- Full House Three of a kind and Two of a kind
- Flush Any five cards in the same suit
- Straight Any five cards of consecutive value, Ace may be low (A2345) or high (TJQKA) but may not wrap (not QKA23)
- Three of a Kind Three cards of the same value
- Two Pair Two pairs of cards of the same value
- Jacks or better Two cards of the same value (“a pair”) that are Jack, Queen, King, or Ace
Cards do not need to be dealt in order in the hand (e.g. 7,8,4,6,5 is a straight equivalent to 4,5,6,7,8)
The Program
It was not easy. Here is the 2024 contest entry including the Atari floppy disk image and instructions: https://bunsen.itch.io/jacks-or-better-video-poker-atari-8-bit-by-d-scott-williamson
The 10 Line BASIC Program
1N0=0:N1=1:N2=N1+N1:N4=N2+2:N5=N4+N1:N9=N5+N4:N10=N5+N5:N13=N9+N4:N14=N13+N1:N15=N14+N1:N17=N15+N2:N19=N15+N4:N21=N19+N2:N25=N15+N10:N29=N25+N4:N119=119:N128=N119+N9:NKE=155:M=N25:B=N1:DIM D$(N119),HN$(165),H(N10+N10),H$(N10),T$(N128),C(N14),DC(N5)
2N7=N5+N2:N8=N7+N1:N16=N15+N1:F.I=N0 TO N19:REA.T:H(I)=T:N.I:O.#N1,N4,N0,"K":POK.82,N0:GR.N0:POK.752,1:SE.N2,N7,N2:SE.N1,N0,15:REA.D$:REA.T$:HN$=T$(N2):REA.T$:HN$(61)=T$(N2):?:F.I=N1 TO N15:?" ";:N.I:POS.9,2:?"Ö É Ä Å Ï Ð Ï Ë Å Ò"
3F.I=N9 TO N1 STEP -N1:T=N10-I:POS.N0,T+N4:?HN$(I*N15+N1,I*N15+N15):F.J=N1 TO N5:POS.N10+N1+N5*J,T+N4:?J*H(I+N10):N.J:N.I:?:?:?"Û±ÝÛ²ÝÛ³ÝÛ´ÝÛµÝ ÛÝÛ«Ý Û¤Ý":POS.N0,N21:?,,," ÛÅÎÔÅÒÝ":?,,," Ä Å Á Ì";
4POS.N29,N19:?"Cash:$";M;" ":POS.N21,N19:?"Bet:$";B:GET#N1,K:B=B+(N1*(K=43)*(B<N5))-((K=45)*(B>1)):M=M+N25*(K=36):POS.N0,20:G.N4+(K=NKE)*(M>=B):D. 0,8,9,11,4,3,13,17,2,1, 0,1,2,3,4,6,9,25,50,250
5?HN$(150,164):?HN$(150,164):?HN$(N1,N15);HN$(N1,N15):F.I=N1 TO20STEP N2:J=INT(RND(N0)*52)*N2+N1:T$=D$(I):D$(I,I+N1)=D$(J):D.²€³€´€µ€¶€·€¸€¹€Ô€Ê€Ñ€Ë€Á€²à³à´àµà¶à·à¸à¹àÔàÊàÑàËàÁಳ´µ¶·¸¹ÔÊÑËÁ²û³û´ûµû¶û·û¸û¹ûÔûÊûÑûËûÁûÁ²³´µ¶·¸¹ÔÊÑËÁ
6D$(J,J+N1)=T$:N.I:M=M-B:POS.N29,N19:?"Cash:$";M;" ":F.I=N1 TO N9 STEP N2:H$(I,I+N1)=D$(I):N.I: POS.N1,N19:F.I=N0 TO N5:DC(I)=N1:N.I:F.I=N1 TO N9 STEP N2: ?H$(I,I+N1);" ";:N.I:POS.N1,N17:D." Jacks or BetterTwo Pair Three of a Kind"
7F.I=N0 TO N4: DC(I)=DC(I)+(K=(I+49)):DC(I)=DC(I)-INT(DC(I)/N2)*N2:?CHR$(72-DC(I)*N4);" ";:N.I: GET#N1,K: POS.N1,N17:G. N9-N2+(K=NKE): D."Straight Flush Full House Four of a Kind Straight Flush Royal Flush "
8?HN$(N1,N13):F.I=N0 TO N4: J=I*N2+N1: T=J+DC(I)*N10:H$(J)=D$(T,T+N1):N.I:POS.N1,N19:F.I=N1 TO N9 STEP N2: ?H$(I,I+N1);" ";:N.I:?:F.I=N1 TO N14:C(I)=N0:N.I:R=N0: F.I=N1 TO N14:F.J=N1 TO N9 STEP N2:T=104+I:C(I)=C(I)+H$(J,J)=D$(T,T):N.J
9R=R+C(I)*C(I)*(I<N14): N.I:S=N0:F.I=N1 TO N10:T=N1:F.J=N0 TO N4:T=T*C(I+J):N.J:S=S+T:N.I:F=N1:F.I=N4 TO N10 STEP N2:F=F*H$(N2,N2)=H$(I,I):N.I:JB=N0:F.I=N10+N1 TO N14:JB=JB+(C(I)=N2):N.I:JB=JB*(R=N5+N2): R1=R+JB-S-F*2-(S*F*(C(N13))):HI=N0:W=N0:?:?
10F. I=N1 TO N10:HI=HI+I*(H(I)=R1):N.I:?" ";HN$(HI*N15+N1,HI*N15+N15);:T=H(HI+N10):F.I=N1 TO B:W=W+T:M=M+T:POS.N21,22:?"WIN $";W:POS.N29,N19:?"Cash:$";M:F.J=(T>N0)*(N16)TO N0 STEP-N4:SO.N1,N29,N10,J:SE.N2,N15-(J=N0)*N8,J+N2:N.J:SE.N2,N7,N2:N.I:G.N4
Program Overview
Line 1 Initialize constants, Dimension arrays
Line 2 Initialize constants, read hand ranks array from data, open keyboard device for key input, set left margin to 0, set graphics mode, hide the cursor, set colors, read deck of cards into D$ from data, read hand names from data into HN$, print white banner and VIDEO POKER title
Line 3 Display the winning hands and payout table, print legends for inputs
Line 4 Begin main game loop, pre-deal loop on line 4, print cash and bet, get input key in K, update bet B based on key if + or – pressed, update money (“Cash”) if $ pressed, GOTO 5 if ENTER pressed, otherwise GOTO 4 to continue pre-deal input loop, DATA statement with hand ranks and payouts
Line 5 Initial print of card bodies, clear previous hand and winning hand string from screen, shuffle the (first 10 cards of) deck D$, data containing initial deck of cards and card values for histogram
Line 6 Shuffle continued, subtract bet from cash to begin hand, update cash display, deal 5 cards by copying first 5 cards from the deck D$ to the hand H$, set 5 discard flags DC to 1, print cards in hand, position cursor to print hold flags, DATA containing hand name strings
Line 7 Discard input loop, loop through 5 cards, if a corresponding number key K is pressed toggle hold/discard flag in DC(I), print H or D based on DC(I), get key from keyboard in K, GOTO 7 loop until enter is pressed, DATA holding hand names
Line 8 Clear hold/discard characters, replace discarded cards with those from the deck, print cards in players hand, clear histogram C(), reset rank, loop over card values to calculate value counts of cards in the players hand to build a histogram
Line 9 Continue building histogram, and accumulate histogram values squared into initial rank R, detect straight, detect flush, detect jacks or better, combine rank, straight, flush, and jacks or better flags into a final rank R1 calculation, initialize the hand index and win to 0 in case the hand is not a winning hand
Line 10 Compare rank to winning hand ranks to get hand index HI, print hand name HN$(HI*15), lookup payout H(HI+10), payout loop once for each bet (1-5), award payout to win W and money M (“Cash”), print winnings and updated Cash balance, loop to cycle colors and ring bell, reset the screen color, GOTO 4 to return to the pre-deal state
The Program Expanded And Explained
Here each line number is multiplied by 100, command abbreviations are expanded and placed on separate lines with remarks.
100 N0=0 : REM CONSTANT VARIABLE 0
101 N1=1 : REM CONSTANT VARIABLE 1
102 N2=N1+N1 : REM CONSTANT VARIABLE 2
103 N4=N2+2 : REM CONSTANT VARIABLE 4
104 N5=N4+N1 : REM CONSTANT VARIABLE 5
105 N9=N5+N4 : REM CONSTANT VARIABLE 9
106 N10=N5+N5 : REM CONSTANT VARIABLE 10
107 N13=N9+N4 : REM CONSTANT VARIABLE 13
108 N14=N13+N1 : REM CONSTANT VARIABLE 14
109 N15=N14+N1 : REM CONSTANT VARIABLE 15
110 N17=N15+N2 : REM CONSTANT VARIABLE 17
111 N19=N15+N4 : REM CONSTANT VARIABLE 19
112 N21=N19+N2 : REM CONSTANT VARIABLE 21
113 N25=N15+N10 : REM CONSTANT VARIABLE 25
114 N29=N25+N4 : REM CONSTANT VARIABLE 29
115 N119=119 : REM CONSTANT VARIABLE 119
116 N128=N119+N9: REM CONSTANT VARIABLE 128
117 NKE=155 : REM CONSTANT VARIABLE 155 (KEYCODE FOR ENTER)
118 M=N25 : REM INITIALIZE MONEY TO 25 ($)
120 B=N1 : REM INITIALIZE BET TO 1 ($)
121 DIM D$(N119),HN$(165),H(N10+N10),H$(N10),T$(N128),C(N14),DC(N5) : REM DIMENSION VARIABLES
122 REM D$(1,52*2) IS THE DECK OF CARDS, EACH CARD HAS A NUMBER AND A SUIT
123 REM D$(105,119) ARE THE CARD NUMBERS FOR CALCULATING HISTOGRAMS A23456789TJKQA
124 REM HN$ IS THE HANDS NAME STRING, FOR DISPLAYING THE RATE TABLE AND HAND WHEN THE PLAYER WINS
125 REM H(20) IS THE HAND VALUE TABLE, EACH OF THE 10 WINNING HANDS HAS A RANK AND A PAYOUT
126 REM H$(10) IS THE PLAYERS 5 CARD HAND WHERE EVERY TWO CHARACTERS IS A CARD
127 REM T$(N128) IS A TEMPORARY STRING FOR READING STRINGS FROM DATA AND HOLDING STRINGS FOR CALCULATIONS
128 REM C(14) HISTOGRAM OF CARD COUNTS IN PLAYERS HAND
129 REM DC(5) DISCARD FLAG FOR DISCARDING OR HOLDING CARDS
200 N7=N5+N2 : REM CONSTANT VARIABLE 7
201 N8=N7+N1 : REM CONSTANT VARIABLE 8
202 N16=N15+N1 : REM CONSTANT VARIABLE 16
203 FOR I=N0 TO N19 : REM LOOP TO READ HAND RANKS AND PAYOUTS FROM DATA INTO ARRAY H
204 READ T:?I,T : REM READ DATA INTO TEMPORARY VARIABLE
205 H(I)=T : REM COPY DATA INTO ARRAY (YOU CANNOT READ DATA DIRECTLY INTO AN ARRAY VARIABLE
206 NEXT I : REM LOOP
207 OPEN #N1,N4,N0,"K": REM OPEN THE KEYBOARD FOR INPUT SO KEYSTROKES CAN BE READ WITHOUT REQUIRING ENTER KEY
208 POKE 82,N0 : REM SET LEFT MARGIN TO 0 FROM DEFAULT OF 2 TO USE ENTIRE SCREEN WIDTH
209 GRAPHICS N0 : REM GRAPHICS 0, CLEAR SCREEN AND SET ATARI 40 COLUMN 24 ROW TEXT SCREEN MODE
210 POKE 752,1 : REM HIDE CURSOR
211 SETCOLOR N2,N7,N2 : REM SET THE BACKGROUND COLOR TO DARK BLUE
212 SETCOLOR N1,N0,15 : REM SET THE TEXT COLOR TO BRIGHT WHITE
213 READ D$ : REM READ THE DECK OF CARDS STRING FROM DATA
214 READ T$ : REM READ PARTIAL HAND NAMES FROM DATA
215 HN$=T$(N2) : REM COPY THE HAND NAMES STRING SKIPPING THE INITIAL DOUBLE QUOTE CHARACTER IN T$
216 READ T$ : REM READ MORE OF THE HAND NAMES FROM THE NEXT DATA STATEMENT
217 HN$(61)=T$(N2) : REM COPY THE HAND NAMES TO THE END OF HN$ SKIPPING THE INITIAL DOUBLE QUOTE CHARACTER IN T$
218 PRINT : REM PRINT A BLANK LINE
219 FOR I=N1 TO N15 : REM LOOP TO PRINT WHITE BANNER AT THE TOP OF THE SCREEN
220 PRINT " "; : REM PRINT 8 INVERTED SPACES
221 NEXT I : REM LOOP
222 POSITION 9,2 : REM POSITION CURSOR IN MIDDLE OF THE BANNER
223 PRINT "Ö É Ä Å Ï Ð Ï Ë Å Ò": REM PRINT INVERTED "V I D E O P O K E R"
300 FOR I=N9 TO N1 STEP -N1 : REM LOOP TO DISPLAY THE WINNING HANDS AND PAYOUTS FOR BETS
301 T=N10-I : REM TEMPORARY VARIABLE T INDEXES THE ROW POSITION ON THE SCREEN IN DESCENDING ORDER
302 POSITION N0,T+N4 : REM POSITION THE CURSOR TO PRINT THE NAME OF THE HAND
303 PRINT HN$(I*N15+N1,I*N15+N15) : REM PRINT HAND NAME SUBSTRING OUT OF HN$ INDEXED BY I. EACH NAME IS 15 CHARACTERS LONG
304 FOR J=N1 TO N5 : REM LOOP OVER THE BET AMOUNTS FROM 1 TO 5
305 POSITION N10+N1+N5*J,T+N4 : REM POSITION THE CURSOR IN THE RIGHT COLUMN AND ROW TO DISPLAY THE PAYOUT FOR HAND I AT BET LEVEL J
306 PRINT J*H(I+N10) : REM PRINT PAYOUT FOR HAND I AT BET LEVEL J
307 NEXT J : REM LOOP OVER BET LEVELS
308 NEXT I : REM LOOP OVER HANDS
309 PRINT : REM PRINT A BLANK LINE
310 PRINT : REM PRINT A BLANK LINE
311 PRINT "Û±ÝÛ²ÝÛ³ÝÛ´ÝÛµÝ ÛÝÛ«Ý Û¤Ý" : REM PRINT KEY LEGEND, INVERTED "[1][2][3][4][5] [-][+] [$]"
312 POSITION N0,N21 : REM POSITION CURSOR TO PRINT DEAL KEY LEGEND
313 PRINT ,,," ÛÅÎÔÅÒÝ" : REM PRINT INVERTED "[ENTER]" TO THE RIGHT
314 PRINT ,,," Ä Å Á Ì"; : REM PRINT INVERTED "D E A L" TO THE RIGHT, SEMICOLON PREVENTS NEWLINE FROM SCROLLING THE SCREEN
400 POSITION N29,N19 : REM THIS IS THE BET AND CASH INPUT LOOP, POSITION THE CURSOR TO PRINT PLAYER CASH
401 PRINT "Cash:$";M;" " : REM PRINT THE AMOUNT OF MONEY THE PLAYER HAS, TRAILING SPACE NEEDED TO CLEAR DIGITS AS PLAYER CASH DECREASES
402 POSITION N21,N19 : REM POSITION THE CURSOR TO PRINT THE PLAYER BET
403 PRINT "Bet:$";B : REM PRINT THE PLAYER BET LEVEL
404 GET#N1,K : REM READ A KEY FROM THE KEYBOARD INTO K
405 B=B+(N1*(K=43)*(B<N5))-((K=45)*(B>1)) : REM UPDATE BET, ADD 1 ONLY IF K=43 ("+") AND THE BET IS LESS THAN 5, SUBTRACT 1 IF K=45 ("-") AND B>1
406 M=M+N25*(K=36) : REM UPDATE PLAYER MONEY, ADD 25 ONLY IF THE "$" KEY WAS PRESSED WHEN K=36
407 POSITION N0,20 : REM POSITION CURSOR TO DISPLAY CARDS
408 GOTO 100*(N4+(K=NKE)*(M>=B)) : REM GOTO N4+(K=NKE)*(M>=B), GOTO IF ENTER PRESSED AND MONEY>=BET GOTO 5 (DEAL), OTHERWISE GOTO 4 (LOOP)
409 DATA 0,8,9,11,4,3,13,17,2,1, 0,1,2,3,4,6,9,25,50,250
410 REM DATA ON LINE 409 IS 10 HAND RANKS FOLLOWED BY 10 CORRESPONDING HAND VALUES
500 PRINT HN$(150,164) : REM PRINT FIVE PAIRS OF INVERTED SPACES TO LOOK LIKE CARDS FROM THE END OF THE HAND NAMES STRING
501 PRINT HN$(150,164) : REM PRINT FIVE PAIRS OF INVERTED SPACES TO LOOK LIKE CARDS FROM THE END OF THE HAND NAMES STRING
502 PRINT HN$(N1,N15);HN$(N1,N15) : REM PRINT 15 BLANK SPACES TWICE TO CLEAR THE HAND AND "WIN$:X" STRINGS FROM THE SCREEN (THE FIRST HAND NAME IS BLANK, USED FOR ALL LOSING HANDS)
503 FOR I=N1 TO 20 STEP N2 : REM SHUFFLE THE DECK, ONLY THE FIRST 10 CARDS AS THEY ARE THE ONLY ONES THAT MAY BE USED IN EACH HAND
504 J=INT(RND(N0)*52)*N2+N1 : REM PICK ANOTHER RANDOM CARD IN THE DECK INDEXED BY J TO SWAP WITH CARD AT INDEX I
505 T$=D$(I) : REM COPY THE DECK AT I TO T$
506 D$(I,I+N1)=D$(J) : REM COPY THE 2 CHARACTER CARD FROM LOCATION J IN THE DECK TO LOCATION I
507 DATA²€³€´€µ€¶€·€¸€¹€Ô€Ê€Ñ€Ë€Á€²à³à´àµà¶à·à¸à¹àÔàÊàÑàËàÁಳ´µ¶·¸¹ÔÊÑËÁ²û³û´ûµû¶û·û¸û¹ûÔûÊûÑûËûÁûÁ²³´µ¶·¸¹ÔÊÑËÁ
508 REM THE DATA IN LINE 507 IS THE INVERTED CHARACTERS OF THE DECK OF CARDS FOLLOWED BY VALUES OF CARDS (A123456789TJQKA) USED TO CALCULATE RANKING HISTOGRAM
600 D$(J,J+N1)=T$ : REM COPY THE TWO CHARACTER CARD FROM POSITION I SAVED IN T$ TO LOCATION J
601 NEXT I : REM LOOP TO SHUFFLE NEXT CARD
602 M=M-B : REM SUBTRACT BET FROM MONEY
603 POSITION N29,N19 : REM POSITION CURSOR TO PRINT PLAYER CASH
604 PRINT "Cash:$";M;" " : REM PRINT PLAYER CASH
605 FOR I=N1 TO N9 STEP N2 : REM LOOP TO DEAL 5 CARDS
606 H$(I,I+N1)=D$(I) : REM COPY THE FIRST 5 CARDS FROM THE DECK INTO THE PLAYERS HAND 2 CHARACTERS AT A TIME
607 NEXT I : REM LOOP DEAL
608 POSITION N1,N19 : REM POSITION CURSOR TO PRINT CARDS
609 FOR I=N0 TO N5 : REM LOOP TO SET ALL 5 DISCARD FLAGS TO 1
610 DC(I)=N1 : REM SET DISCARD FLAG I TO 1
611 NEXT I : REM LOOP INITIALIZE DISCARD FLAGS
612 FOR I=N1 TO N9 STEP N2 : REM LOOP TO PRINT CARDS
613 PRINT H$(I,I+N1);" "; : REM PRINT 2 CHARACTER CARD FROM THE HAND FOLLOWED BY A SPACE
614 NEXT I : REM LOOP TO PRINT CARDS
615 POSITION N1,N17 : REM POSITION CURSOR TO PRINT DISCARD/HOLD FLAGS
616 DATA" Jacks or BetterTwo Pair Three of a Kind" : REM FIRST DATA FOR HN$
700 FOR I=N0 TO N4 : REM DISCARD/HOLD INPUT LOOP, LOOP THROUGH 5 CARD DISCARD/HOLD VALUES
701 DC(I)=DC(I)+(K=(I+49)) : REM INCREMENT D(I) DISCARD/HOLD IF K=49+I, KEYS 1-5 ARE KEY CODES 49-53
702 DC(I)=DC(I)-INT(DC(I)/N2)*N2 : REM EFFECTIVELY MODULUS 2, LIMITS THE VALUES TO 1 OR 0
703 PRINT CHR$(72-DC(I)*N4);" "; : REM PRINT "D" IF DC(I) IS 1 OR "H" IF IT IS NOT FOLLOWED BY TWO SPACES
704 NEXT I : REM LOOP THROUGH DISCARD ARRAY
705 GET #N1,K : REM GET KEY FROM KEYBOARD
706 POSITION N1,N17 : REM POSITION CURSOR TO PRINT DISCARD VALUES
707 GOTO 100*(N9-N2+(K=NKE)) : REM GOTO N9-N2+(K=NKE), IF ENTER PRESSED GOTO 800 OTHERWISE GOTO 700
708 DATA"Straight Flush Full House Four of a Kind Straight Flush Royal Flush " : REM SECOND DATA FOR HN$
800 PRINT HN$(N1,N13) : REM PRINT BLANK HAND NAME OVER DISCARD VALUES TO REMOVE THEM
801 FOR I=N0 TO N4 : REM LOOP THROUGH 5 CARDS TO DEAL NEW CARDS FOR DISCARDS
802 J=I*N2+N1 : REM CALCULATE INDEX j OF CARD I IN THE HAND H$ (AND IN THE DECK D$ ON DEAL)
803 T=J+DC(I)*N10 : REM CALCULATE INDEX OF THE CARD IN THE DECK, IF DISCARD IS 1, SELECT CORRESPONDING CARD FROM NEXT 5 CARDS IN THE DECK
804 H$(J)=D$(T,T+N1) : REM COPY CARD FROM THE DECK TO THE HAND (EITHER HOLD OR DISCARD AND REDEAL CARD+5)
805 NEXT I : REM LOOP DEAL DISCARDS
806 POSITION N1,N19 : REM POSITION CURSOR TO PRINT CARDS
807 FOR I=N1 TO N9 STEP N2 : REM LOOP OVER CARDS TO PRINT CARDS
808 PRINT H$(I,I+N1);" "; : REM PRINT 2 CHARACTER CARD FROM HAND AT INDEX I
809 NEXT I : REM LOOP
810 PRINT : REM PRINT BLANK LINE IN PREPARATION FOR PRINTING HAND DESCRIPTION AND WINNING
811 FOR I=N1 TO N14 : REM LOOP OVER CARD VALUES TO CLEAR HISTOGRAM
812 C(I)=N0 : REM SET HISTOGRAM VALUE C(I) TO ZERO
813 NEXT I : REM LOOP
814 R=N0 : REM SET RANK TO 0
815 FOR I=N1 TO N14 : REM LOOP I OVER CARD VALUES TO CALCULATE HISTOGRAM IN C()
816 FOR J=N1 TO N9 STEP N2 : REM LOOP J OVER CARD INDEXES IN HAND
817 T=104+I : REM SET TEMPORARY VALUE TO 104+I,INDEX OF VALUE CHARACTER IN DECK STRING
818 C(I)=C(I)+H$(J,J)=D$(T,T) : REM ADD 1 TO C(I) IF THE VALUE CHARACTER IN THE HAND FOR CARD J MATCHES THE VALUE CHARACTER FROM THE DECK STRING
819 NEXT J : REM LOOP NEXT CARD
900 R=R+C(I)*C(I)*(I<N14) : REM RANK IS THE SUM OF THE CARD COUNTS SQUARED ONLY INCLUDING THE ACE ONCE (IT'S INCLUDED TWICE TO DETECT STRAIGHTS)
901 NEXT I : REM NEXT CARD VALUE, R CONTAINS RANK SUM OF SQUARES OF CARD COUNTS FOR THE HAND
902 S=N0 : REM SET STRAIGHT FLAG S TO ZERO
903 FOR I=N1 TO N10 : REM LOOP OVER LOWEST CARD FOR 10 POSSIBLE STRAIGHTS (A-5 THROUGH T-A)
904 T=N1 : REM SET TEMP TO 1
905 FOR J=N0 TO N4 : REM LOOP THROUGH 5 CONSECUTIVE CARD COUNTS C(I+J) IN THE HISTOGRAM
906 T=T*C(I+J) : REM MULTIPLY T BY THE HISTOGRAM VALUE AT I+J
907 NEXT J : REM LOOP NEXT CARD
908 S=S+T : REM ADD T TO S, T CAN ONLY BE 1 IF THE HAND CONTAINS 5 CONSECUTIVE CARDS
909 NEXT I : REM LOOP NEXT POTENTIAL STRAIGHT
910 F=N1 : REM SET FLUSH DETECTION TO 1
911 FOR I=N4 TO N10 STEP N2 : REM LOOP HAND INDEX I FROM THE SECOND CARD SUIT TO THE FIFTH CARD SUIT
912 F=F*H$(N2,N2)=H$(I,I) : REM MULTIPLY THE FLUSH FLAG BY ONE ONLY IF THE SUIT CHARACTER OF THE FIRST CARD MATCHES THE SUIT OF THE INDEXED CARD
913 NEXT I : REM NEXT CARD SUIT INDEX, WHEN DONE F WILL ONLY BE 1 IF ALL CARDS ARE THE SAME SUIT
914 JB=N0 : REM SET JACKS OR BETTER FLAG TO 0
915 FOR I=N10+N1 TO N14 : REM LOOP INDEX I THROUGH THE CARD HISTOGRAMS FOR JACK (11) THROUGH HIGH ACE (14)
916 JB=JB+(C(I)=N2) : REM ADD 1 TO JB ONLY IF THE HISTOGRAM FOR VALUE I IS EQUAL TO 2
917 NEXT I : REM LOOP THROUGH JACKS OR BETTER CARD COUNTS
918 JB=JB*(R=N5+N2) : REM JB IS MULTIPLIED BY 1 ONLY IF THE RANK IS 7, OTHERWISE 0. RANK 7 IS A SINGLE PAIR (2^2+3*1^2)
919 R1=R+JB-S-F*2-(S*F*(C(N13))) : REM R1 IS THE FINAL COMPOSITE HAND RANK INCORPORATING FLUSH, STRAIGHT, JACKS OR BETTER FLAG, AND DETECTION OF ROYAL FLUSH (S*F*(C(N13)))
920 HI=N0 : REM SET THE HAND INDEX HI TO ZERO
921 W=N0 : REM WIN AMOUNT TO ZERO
922 PRINT : REM PRINT BLANK LINE IN PREPARATION FOR PRINTING HAND DESCRIPTION AND WINNING
923 PRINT : REM PRINT BLANK LINE IN PREPARATION FOR PRINTING HAND DESCRIPTION AND WINNING
1000 FOR I=N1 TO N10 : REM LOOP THROUGH THE POTENTIAL WINNING HANDS (INDEX 1 - 10)
1001 HI=HI+I*(H(I)=R1) : REM ADD WINNING HAND INDEX I TO HI ONLY IF THE HAND RANK EQUALS THE HAND RANK AT THE INDEX H(I)
1002 NEXT I : REM LOOP NEXT WINNING HAND, WHEN THIS IS DONE HI WILL CONTAIN AN INDEX INTO HAND NAMES AND PAYOUTS
1003 PRINT " ";HN$(HI*N15+N1,HI*N15+N15); : REM PRINT HAND NAME, THE NAME AT INDEX ZERO IS BLANK FOR NON-WINNING HANDS
1004 T=H(HI+N10) : REM COPY THE WINNING BASE PAYOUT TO T
1005 FOR I=N1 TO B : REM LOOP FROM 1 TO BET, HIGHER BETS, MORE WINNING PIZAZZ
1006 W=W+T : REM WITH EACH ITERATION OF THE LOOP THE WIN INDICATOR IS AWARDED THE BASE PAYOUT
1007 M=M+T : REM WITH EACH ITERATION OF THE LOOP THE PLAYERS MONEY IS INCREASED BY T
1008 POSITION N21,22 : REM POSITION CURSOR TO PRINT PLAYER WINNINGS
1009 PRINT "WIN $";W : REM PRINT WINNINGS
1010 POSITION N29,N19 : REM POSITION CURSOR TO PRINT PLAYER MONEY
1011 PRINT "Cash:$";M : REM PRINT PLAYER MONEY
1012 FOR J=(T>N0)*(N16)TO N0 STEP-N4 : REM IF THE WINNING BASE PAYOUT IS NOT ZERO, LOOP FROM 16 TO 0
1013 SOUND N1,N29,N10,J : REM MAKE WINNING SOUND
1014 SETCOLOR N2,N15-(J=N0)*N8,J+N2 : REM SET SCREEN TO EXCITING WINNING COLOR
1015 NEXT J : REM END OF MULTIMEDIA REWARD LOOP (END OF FLASH AND DING)
1016 SETCOLOR N2,N7,N2 : REM RESTORE SCREEN COLOR
1017 NEXT I : REM LOOP BET AMOUNT TIMES
1018 GOTO 100*N4:REM GOTO N4 : GO TO BET, MONEY, AND DEAL INPUT LOOP LINE
Programming techniques
The deck of cards
The program manages a deck of 52 cards in a D$ where each card is represented by a value character (2-9,T,J,Q,K,A) and suit character (hearts, clubs, diamonds, spades). Generating cards randomly risks dealing duplicate cards and checking for duplicates is more work than managing a deck. The initial deck is read into D$ from data in line 2. The deck is shuffled in a loop spanning lines 5 and 6 by swapping each card with a random one in the deck. Initially, the program shuffled all 52 cards but since the deck is shuffled between each deal and each hand can only use a maximum of the first 10 cards (5 cards discarded and re-dealt) it is only necessary to shuffle the first 10 cards of the deck between hands reducing the delay considerably.
Ranking hands
This is the heart of this program, it accurately ranks a Jacks or Better Poker hand in about 1 line of code spanning 2 lines of this program. The ranking system must accurately rank hands according to many rules and be compact enough to fit in the 10 line program. IF … THEN logic would quickly consume too many lines so I needed to devise a ranking system. The hand ranks from highest to lowest are:
- Royal Flush : Ten, Jack, Queen, King, Ace in the same suit
- Straight Flush : Five cards in a row in the same suit
- Four of a Kind : Four cards of the same value
- Full House : Three of a kind and Two of a kind
- Flush : Any five cards in the same suit
- Straight : Any five cards in order, Ace may be low (A2345) or high (TJQKA) but may not wrap (not QKA23)
- Three of a Kind : Three cards of the same value
- Two Pair : Two pairs of any cards of the same value
- Jacks or better : Two cards of the same value that are Jack, Queen, King, or Ace
Some combination of rules like a “Royal Straight” Ten, Jack, Queen, King, Ace are not legal Poker Hands and must not fall out of the hand ranking system.
To rank a hand several calculations are made and flags are set then combined to determine a numerical rank used to lookup hand values from a table.
Variables used in ranking:
- R Initial Rank
- R1 Final Rank
- S Straight detection (1 if there are 5 sequential card values in the hand in any order)
- F Flush detection (1 if all 5 cards in the hand are the same suit)
- JB Jacks or Better detection (1 only if there is a single pair in the hand (rank 7) and the pair is Jack, Queen, King, or ACE (a 2 in the histogram for one of those values)
- C(14) Counts (histogram) of cards A123456789TJQKA (includes A at both ends for straight detection)
Steps in the calculation
- Count the number of cards of each value in the hand into array C to create the histogram (line 8-9). A is counted twice, once at each end for straight calculation later.
- The sum of the squares of the histogram values (line 9) for the first 13 histogram values (so A is not included twice) is accumulated in the initial rank R. At this point the rank values have the following meanings:
- 17 (4*4)+1 Four of a kind
- 13 (3*3)+(2*2) Full house
- 11 (3*3)+1+1 Three of a kind
- 9 (2*2)+(2*2)+1 Two pair
- 7 (2*2)+1+1+1 Pair
- 5 1+1+1+1+1 Five different value cards
- Search for straights (line 9): Use a nested loop 0-10 then 0-4 to multiply consecutive card counts in the histogram A straight will be 5 ones in a row so S can only be 1 if there are 5 cards in a row in any order in the hand.
- Search for flush (line 9) by comparing the suit of the last 4 cards in the hand to the suit of the first and take the product of the comparisons. It can only be 1 if all cards are the same suit.
- Search for Jacks or Better JB (line 9) loop through counts of Jack, Queen, King, and Ace and add 1 to JB if the count is 2, then keep the result only if the initial rank is 7 (single pair). JB is only a 1 if the hand contains a single pair of Jacks or better.
- The final rank calculation (line 9) combines R with JB, S, F, and the count of Aces (C(13)) as follows R1=R+JB-S-F*2-(S*F*(C(N13))) resulting in the final hand ranking. Values have the following meanings:
- 17 (4*4)+1 Four of a kind
- 13 (3*3)+(2*2) Full house
- 11 (3*3)+1+1 Three of a kind
- 9 (2*2)+(2*2)+1 Two pair
- 8 7+JB Jacks or better
- 7 (2*2)+1+1+1 Pair (use jacks or better)
- 5 1+1+1+1+1 Nothing
- 4 5-S Straight
- 3 5-2*F flush
- 2 5-S-2*F Straight flush
- 1 5-S-2*F-(S*F*(C(N13))) Royal flush
- The winning hand values are stored in the table with the payouts. A loop scans all the potential winning hands to see if the rank matches one of the hands (line 10) and if so lookup the corresponding payout
Payout
If the player wins then the payout loop flashes the screen and rings the bell once for each dollar bet and adds the payout amount to the players total cash each time.
Audio Visuals
Just like in real gambling machines, wins are accompanied by ringing bells and flashing lights while losses pass silently leaving only reward and anticipation to drive continued play.
Input method
There are too many options to use the joystick and it would have been unintuitive and using BASIC’s INPUT requires pressing enter after every input and validating inputs so I decided to access the keyboard device to get blocking input on individual key-presses. The keyboard device is opened for input in line 2 O.#N1,N4,N0,"K"
(OPEN #1,4,0,"K"
) then keys are retrieved in input loops in line 4 and 7 GET#N1,K
(GET #1,K
). The GET statement waits for a key and returns the ATASCII value in K which is used in conditionals in assignment and GOTO statements. Examples include pressing $ to add money to the player account M=M+N25*(K=36) which adds 25 if K= 36 the ATASCII code for $ and 0 otherwise or G.N4+(K=NKE)*(M>=B) which loops at line 4 until ENTER (ATASCII 155 assigned to NKE for number keyboard enter) is pressed and the player has enough money to cover their bet.
Use of conditional expressions as integers (booleans)
Atari BASIC has no ELSE, so everything to the right of THEN (on one of only 10 BASIC program lines) is only executed if the IF clause is true placing a hard limit on the amount and structure of logic possible in a short program. The result of a comparison (=,<,<=,>,>=,<>) is evaluated to 1 if true and 0 if false. This can be used to create conditional calculations in without using IF … THEN. This technique is used extensively in this program to handle input, make conditional calculations in hand ranking, and in GOTO statements to vector to intended lines based on conditions.
Use of variables for constants
From the Atari BASIC Manual: “Each time a constant (4,5,16,3.14159, etc.) is used, it takes 7 bytes. Defining a new variable requires 8 bytes plus the length of the variable name (in characters). But each time it is used after being defined, it takes only I byte, regardless of its length.“
Lines that contain too many constants overflow the internal tokenization buffer and cannot use the maximum 253 characters per line permitted by the Atari BASIC input buffer length. Using variables for constants is much more compact, furthermore deriving constants from other constants is even more compact during initialization.
Modulus operator
The following modulus 2 calculation is used in line 5 to toggle holding cards:DC(I)=DC(I)-INT(DC(I)/N2)*N2
Atari BASIC does not have a modulus operator, modulus(a,b)
returns the remainder of a/b
Instead A-INT(A/B)*B
is used, let’s look at it in parts.INT(A/B)
is the integer portion of A/B
, aka truncated value of A/B
, aka ceil(A/B)
INT(A/B)*B
is the integer portion of A
larger than B A-INT(A/B)*B
Subtracting the integer portion of A larger than B from A leaves the remainder or modulus.
Conclusion
I love these highly constrained programming challenges and I think Jacks or Better VIDEO POKER turned out great. It’s certainly been fun play-testing it! I think this style of technical programming is probably my favorite part of developing games. Working with Atari, especially Atari BASIC, is a special kind of nostalgia for me that always brings me back to my programming and literal adolescence. I hope you found this entertaining, wish me luck! Contest results to be announced April 6th 2024.