Sunday, November 10, 2019

ACSL AGRAM Card Game Exercise - Just for fun

Something fun. My youngest daughter is a senior in high school & is taking a Computer Programming class. One of her exercises was based on a contest problem sponsored by the ACSL (American Computer Science League). The task is to create an algorithm for a simple card game.

I had a little free time so I quickly created a solution in 2 languages - SQR & WinBatch. My primary goal was to streamline the logic in such as way so it's easy to understand & follow. Parents weren't allowed to help so she was on her own... but I will definitely follow up with her.

***UPDATE*** - PC/370 Assembler Version Created! CLICK HERE

INSTRUCTIONS:


The instructions on the AGRAM game itself are vague - it doesn't provide an explanation on how to conduct and win a complete game. Rather it focuses on the logic within a single hand.


Here is the ACSL PDF version of the rules: CLICK HERE

The assumption is that ALL CARDS are VALID members of a 52 card deck. No error checking is incorporated or required. I also ignored the unnecessary restriction of 5 cards - the logic works for 1 through 51 cards - it's driven entirely by the number of dealer cards presented.

The SQR version follows the INPUT instructions with each hand represented by a comma delimited record in a file. (The WinBatch version will utilize a prompt).

INPUT:

5D,2D,6H,9D,TD,6H
TC,AC,KC,QH,JS,TD
3D,4H,5C,6S,2D,7H
KS,TH,QC,7H,9H,3H
AC,AD,KH,JS,KS,QS
3H,2D,AC,9S,AH,QH,KH,AH,AS,AH,TD,3C,KC,2H,5H,6H,6S,9D,9D,9D,4H,KS
KH,2H,4C,5S,AH,KD
KH,2H,4C,5S,3H,KD


SQR VERSION:

The program reads one record at a time with each one performing the Process_Cards routine (shown below).

The XXX variables represent the card chosen by the dealer - it will contain the final choice once all cards in the dealers hand have been evaluated.

The OPP variables are used to hold the Opponent's card. Simple enough.

The DLR variables are used to hold the latest card (evaluated one by one) in the Dealer's Hand.

I used my own custom Parsing functions TDF_PARSE_Put() & TDF_PARSE_Get() to extract the individual data elements from the record. Index 0 contains the Opponent Card while Index 1 goes up to (but not including) the #O_cols counter. So a 5 card dealer hand has indexes 1 thru 5.

The 2-character card must be broken down & weighted. The function CARD_Parse() is utilized for all cards & returns the suit (C=Clubs, D=Diamonds, H=Hearts, S=Spades) and a weighted value. In this case, the "single-digit" number cards equal themselves (2 thru 9) - an A (Ace) has a value of 1 - while T (Ten), J (Jack), Q (Queen) & K (King) have the values 10 thru 13.

!**********************************************************************
!*       Process Cards                                                *
!**********************************************************************

begin-procedure Process_Cards

let $XXX_card                           = 'XX'
let $XXX_suit                           = 'X'
let #XXX_val                            = 0

do TDF_PARSE_Put($rec, ',', '', #O_cols)

do TDF_PARSE_Get( 0, $OPP_card)

do CARD_Parse($OPP_card, $OPP_suit, #OPP_val)

show 'Opponent: ' $OPP_card ' . ' $OPP_suit ' . ' #OPP_val edit 999

let #idx                                = 1

while #idx                              < #O_cols

   do TDF_PARSE_Get(#idx, $DLR_card)

   do CARD_Parse($DLR_card, $DLR_suit, #DLR_val)

   show '  Dealer: ' $DLR_card ' . ' $DLR_suit ' . ' #DLR_val edit 999

   !   Always Select the First Card (Skip Comparisons)
   !   ===============================================
   if  #idx                             = 1
       let $XXX_card                    = $DLR_card
       let $XXX_suit                    = $DLR_suit
       let #XXX_val                     = #DLR_val

   else

       !   No Suit Match Yet - If Lower Value OR Suit Select Card
       !   ======================================================
       if  $XXX_suit                   <> $OPP_suit
           if  #DLR_val                 < #XXX_val
           or  $DLR_suit                = $OPP_suit
               let $XXX_card            = $DLR_card
               let $XXX_suit            = $DLR_suit
               let #XXX_val             = #DLR_val
           end-if

       !   Same Suit - Ignore All other Suits from Dealer
       !   ==============================================
       else
           if  $XXX_suit                = $DLR_suit

               !   OVER Opponent Value - Select CARD if also over but < prior pick
               !   ===============================================================
               if  #XXX_val             > #OPP_val
                   if  #DLR_val         > #OPP_val
                   and #DLR_val         < #XXX_val
                       let $XXX_card    = $DLR_card
                       let $XXX_suit    = $DLR_suit
                       let #XXX_val     = #DLR_val
                   end-if

               !   UNDER Opponent Value - Select CARD if OVER Opponent or < prior pick
               !   ===================================================================
               else
                   if  #XXX_val         < #DLR_val
                       if #OPP_val      < #DLR_val
                          let $XXX_card = $DLR_card
                          let $XXX_suit = $DLR_suit
                          let #XXX_val  = #DLR_val
                       end-if
                   else
                       let $XXX_card    = $DLR_card
                       let $XXX_suit    = $DLR_suit
                       let #XXX_val     = #DLR_val
                   end-if
               end-if

           end-if

       end-if

   end-if

   let #idx                             = #idx + 1

end-while

show ' '
show 'Results: ' $rec ' ==> ' $XXX_card
show ' '

end-procedure

!**********************************************************************


The CARD_Parse() routine does NOT use hard-coded evaluate, switch or case constructs - or worse yet a series of convoluted If-Then-Else statements. I control the weighted values using a string of face characters represented by the order of magnitude. If I want a character to be high I place it at the end - any character - it could be a 7 if I like - who cares. The INSTR function returns the position in the string and uses that for the weighted value. Zero complexity.

!**********************************************************************
!*       CARD Parse                                                   *
!**********************************************************************

begin-procedure CARD_Parse($I_card, :$O_suit, :#O_val)

let $X_face = substr($I_card, 1, 1)
let $O_suit = substr($I_card, 2, 1)
let #O_val  = instr('A23456789TJQK', $X_face, 1) 

end-procedure

!**********************************************************************


Results (Snipped from LOG File):

Results: 5D,2D,6H,9D,TD,6H ==> 9D
Results: TC,AC,KC,QH,JS,TD ==> KC
Results: 3D,4H,5C,6S,2D,7H ==> 2D
Results: KS,TH,QC,7H,9H,3H ==> 3H
Results: AC,AD,KH,JS,KS,QS ==> AD
Results: 3H,2D,AC,9S,AH,QH,KH,AH,AS,AH,TD,3C,KC,2H,5H,6H,6S,9D,9D,9D,4H,KS ==> 4H
Results: KH,2H,4C,5S,AH,KD ==> AH
Results: KH,2H,4C,5S,3H,KD ==> 2H


WINBATCH VERSION:

For WinBatch I created a dialog panel to enter the opponent's card along with a comma delimited string of dealer cards. When the OK button is pressed the proper dealer card is selected & displayed in RED.

I'll show the Sample Results first this time followed by the routine.






The dialog has the GUI variables eOpponent & eCards to hold the input (as opposed to the file used in the SQR version). The GUI variable vResult will hold the result. In general the logic is identical to the SQR version  - the only difference being syntax/functions between the two languages. For example, StrIndex() is used to weight the cards instead of the Instr() function - same result.

;**********************************************************************
;*      Process Cards                                                 *
;**********************************************************************

:Process_Cards

DLR_hand                            = eCards
OPP_card                            = eOpponent

OPP_xlat                            = StrSub(OPP_card, 1, 1)
OPP_suit                            = StrSub(OPP_card, 2, 1)
OPP_val                             = StrIndex('A23456789TJQK', OPP_xlat, 1, @FWDSCAN)

XXX_card                            = 'XX'
XXX_suit                            = 'X'
XXX_val                             = 0

count                               = ItemCountCSV(DLR_hand, 0,",")

For x                               = 1 to count

    DLR_card                        = ItemExtractCSV(x, DLR_hand, 0, ",")
    DLR_xlat                        = StrSub(DLR_card, 1, 1)
    DLR_suit                        = StrSub(DLR_card, 2, 1)
    DLR_val                         = StrIndex('A23456789TJQK', DLR_xlat, 1, @FWDSCAN)

   ;   Always Select the First Card (Skip Comparisons)
   ;   ===============================================
   if  x                           == 1
       XXX_card                     = DLR_card
       XXX_suit                     = DLR_suit
       XXX_val                      = DLR_val

   else

       ;   No Suit Match Yet - If Lower Value OR Suit Select Card
       ;   ======================================================
       if  XXX_suit                <> OPP_suit
           if  DLR_val              < XXX_val   ||  DLR_suit    == OPP_suit
               XXX_card             = DLR_card
               XXX_suit             = DLR_suit
               XXX_val              = DLR_val
           endif

       ;   Same Suit - Ignore All other Suits from Dealer
       ;   ==============================================
       else
           if  XXX_suit            == DLR_suit

               ;   OVER Opponent Value - Select CARD if also over but < prior pick
               ;   ===============================================================
               if  XXX_val          > OPP_val
                   if  DLR_val      > OPP_val   &&  DLR_val      < XXX_val
                       XXX_card     = DLR_card
                       XXX_suit     = DLR_suit
                       XXX_val      = DLR_val
                   endif

               ;   UNDER Opponent Value - Select CARD if OVER Opponent or < prior pick
               ;   ===================================================================
               else
                   if  XXX_val      < DLR_val
                       if OPP_val   < DLR_val
                          XXX_card  = DLR_card
                          XXX_suit  = DLR_suit
                          XXX_val   = DLR_val
                       endif
                   else
                       XXX_card     = DLR_card
                       XXX_suit     = DLR_suit
                       XXX_val      = DLR_val
                   endif
               endif

           endif

       endif

   endif

Next

vResult                             = XXX_card

Return

;**********************************************************************


I'm looking forward to the next exercise my daughter brings home...