"8C"
might represent the eight of clubs, and so on.This choice has the additional advantage of facilitating debugging.deckimage := &lcase || &ucase
abc ... m
are clubs, the characters nop ... z
are diamonds,
the characters ABC ... M
are hearts, and the characters NOP
... Z
are spades. Except for possible debugging, however, these explicit
correspondences never come up. The specific order of denominations is rather
arbitrary, but it turns out to be convenient for display purposes to rank
the cards according to the order of characters in the following string:
Thus, the characterrank := "AKQJT98765432"
a
is the ace of clubs, the character z
is the two of diamonds, and so on. Again, this never comes up explicitly
in the program.This procedure is an implementation of a method given by Knuth in his book, Seminumerical Algorithms. It operates by starting at the end of the deck, exchanging that card with a randomly chosen one, and then working down toward the beginning, chosing the exchange card from the remainder of the deck. Whether or not this produces a `good' shuffle is somewhat of an open question, but it seems to work well in practice.procedure shuffle(deck) local i every i := *deck to 2 by -1 do deck[?i] :=: deck[i] return deck end
The strings denom and blanks are used here in place of a more direct construction ofdenom := deckimage[1+:13] blanks := repl(" ",13) Cmap := denom || repl(blanks,3)
Cmap
, since they are useful in producing maps for the other
suits.assigns to clubs a string in which all the characters correspond-ing to clubs are left unchanged, while all other characters are blanks. This string still is 13 characters long, and probably contains a lot of blanks. The clubs can be obtained by constructing a cset with the blank removed:clubs := map(hand,deckimage,Cmap)
(There's an augmented assignment operation you don't see very often. It does not appear in the actual program, where the result is computed in a single expression.)clubs --:= ' '
The result comes out correctly, since the automatic conversion of the cset to a string in the first argument to map puts the char-acters in alphabetical order.clubs := map(clubs,denom,rank)
An example of the output from this program is:global deck, deckimage, handsize global suitsize, denom, rank, blanks procedure main() deck := deckimage := &lcase || &ucase handsize := suitsize := *deck / 4 rank := "AKQJT98765432" blanks := repl(" ",suitsize) denom := &lcase[1+:suitsize] every 1 to 5 do display() end procedure display() local layout, i static bar, offset initial { bar := "\n" || repl("-",33) offset := repl(" ",10) } deck := shuffle(deck) layout := [] every push(layout,show(deck[(0 to 3) * handsize + 1 +: handsize])) write() every write(offset,!layout[1]) write() every i := 1 to 4 do write(l every write(offset,!layout[3]) write(bar) end procedure shuffle(deck) local i every i := *deck to 2 by -1 do deck[?i] :=: deck[i] return deck end procedure show(hand) static Cmap, Dmap, Hmap, Smap initial { Cmap := denom || repl(blanks,3) Dmap := blanks || denom || repl(blanks,2) Hmap := repl(blanks,2) || denom || blanks Smap := repl(blanks,3) || denom } return [ "S: " || arrange(hand,Smap), "H: " || arrange(hand,Hmap), "D: " || arrange(hand,Dmap), "C: " || arrange(hand,Cmap) ] end procedure arrange(hand,suit) return map(map(hand,deckimage,suit) -- ' ', denom,rank) end
A couple of things are left for you to consider, including the use ofS: 3 H: T7 D: AKQ762 C: QJ94 S: KQ987 S: A652 H: 52 H: AKQ4 D: T94 D: 3 C: T82 C: A653 S: JT4 H: J9863 D: J85 C: K7
denom
for all suits and the method used to provide the final layout.
deal
, it might be
executed as
The argument followingdeal -h 10 -s 17
-h
indicates that 10 rounds of hands
are to be produced and the argument following -s
indicates
that the seed for random number generation is to be 17. The syntax shown
here is the standard one for UNIX; other operating systems conventionally
handle it differently. Such differences are inessential here. However, such
command-line arguments, in whatever form, should be optional and defaults
should be provided if they are omitted.deal
, as if the
argument to main were
Thus, the main procedure of the program above might be rewritten as:["-h", "10", "-s", "17"]
The procedure use terminates program execution with an error message in case there is an erroneous command-line argument. UNIX favors a terse style for such error messages, and a version of use in this style might beprocedure main(a) local s, hands . . . hands := 5 while s := get(a) do { case s of { "-h": hands := integer(get(a)) | use() "-s": &random := integer(get(a)) | use() default: use() } } every 1 to hands do display() . . .
Note thatprocedure use() stop("usage: deal [-h n] [-s n]") end
get(a)
provides an easy and concise way of obtaining
the command-line arguments. Other possibilities are using an explicit index,
as in a[i]
, or iterating over the list, as in !a
.
Try rephrasing the processing of the command-line arguments above to see
why the use of get is better. Of course, get consumes the list. That does
not matter in the case here. How could this be reformulated to avoid consuming
the list while retaining the advantages that get provides?