[comp.lang.icon] Draw Poker Game

TENAGLIA@mis.mcw.edu (Chris Tenaglia - 257-8765) (12/15/90)

Since the submission of the solitaire game, I felt it might be nice if I
could contribute one. Here's a draw poker game. I use ICON on VAX/VMS 5.2
with VT terminals, so I do everything with ANSI escape sequences. I also
realize that I could have done some of the table manipulations more
efficiently, but I thought in this case, tab["key"] := something, helped
the readability of the code. Invoke it with ICONX DRAW [credits]. The
parameter is how much virtual money you want to start with. 3 is default.
Have fun!

Chris Tenaglia (System Manager) | Medical College of Wisconsin
8701 W. Watertown Plank Rd.     | Milwaukee, WI 53226
(414)257-8765                   | tenaglia@mis.mcw.edu, mcwmis!tenaglia

#########################################################
#                                                       #
# DRAW.ICN          12/15/90          BY TENAGLIA       #
#                                                       #
# SIMPLE BUT FUN DRAW POKER GAME. WORKS ON ANSI SCREEN. #
# USAGE : ICONX DRAW [starting credits]                 #
#                                                       #
#########################################################
global money, message
procedure main(param)
  money := integer(param[1]) | 3
  write("\e[2J\e[2H          \e#3\e[1;7mDRAW : Nothing Wild\e[m")
  write("          \e#4\e[1;7mDRAW : Nothing Wild\e[m")
  repeat
    {
    deck    := shuffle()
    message := ""
    hand    := []
    every 1 to 5 do put(hand,pop(deck))
    display(hand,1,4)
    repeat
      {
      keep := obtain("\e[12;1fKeep which ones (1 2 3 4 5):\e[J")
      if map(input("\e[12;50fAre you sure y/n :\e[K"))[1] == "y" then break
      }
    all := set() ; every i := 1 to 5 do insert(all,i)
    every delete(all,integer(!keep))
    every hand[!all] := pop(deck)
    money +:= evaluate(hand)
    display(hand,1,15)
    if map(input("\e[23;1fAnother game? y/n :\e[K"))[1] ~== "y" then break
    }
  write("\e[2J\e[H")
  end

#
# THIS SECTION OBTAINS THE VALIDATES INPUT
#
procedure obtain(prompt)
  bad := 0
  repeat
    {
    nums := parse(input(prompt),' \t,')
    every num := !nums do
      {
      integer(num)       | { input(num || " not a number. Press <RETURN>")            ; bad := 1 ; next }
      (integer(num) > 0) | { input(num || " must be between 1 and 5. Press <RETURN>") ; bad := 1 ; next }
      (integer(num) < 6) | { input(num || " must be between 1 and 5. Press <RETURN>") ; bad := 1 ; next }
      }
    (bad = 1) | return nums
    }
  end

#
# THIS ROUTINE DISPLAYS THE CURRENT HAND AT THE GIVEN X,Y COORDINATES
#
procedure display(cards,x,y)
  all   := []
  every card := !cards do
    {
    j := y
    if find(card[2],"CS") then card := "\e[1;7m" || card || "\e[m"
    shape := ["\e["||(j+:=1)||";"||x||"f\e(0lqqqqqqqk\e(B"]
    put(shape,"\e["||(j+:=1)||";"||x||"f\e(0x " || card || "    x\e(B")
    put(shape,"\e["||(j+:=1)||";"||x||"f\e(0x       x\e(B")
    put(shape,"\e["||(j+:=1)||";"||x||"f\e(0x       x\e(B")
    put(shape,"\e["||(j+:=1)||";"||x||"f\e(0x       x\e(B")
    put(shape,"\e["||(j+:=1)||";"||x||"f\e(0x    " || card || " x\e(B")
    put(shape,"\e["||(j+:=1)||";"||x||"f\e(0mqqqqqqqj\e(B")
    put(all,shape)
    x +:= 14
    }
  while shape := pop(all) do every writes(!shape)
  writes(message,"\e[",y+2,";",x-3,"fCredits\e[K")
  writes("\e[",y+3,";",x-5,"f  \e[1m",right(money,7),"\e[m\e[K")
  end

#
# THIS ROUTINE SHUFFLES THE CARD DECK
#
procedure shuffle()
  static faces, suits
  local cards
  initial {
          &random := map(&clock,":","7")   # initial on multiple shuffles
          faces   := ["2","3","4","5","6","7","8","9","T","J","Q","K","A"]
          suits   := ["D","H","C","S"]
          }
  cards   := []
  every put(cards,!faces || !suits)
  swaps := 52 + ?12
  every 1 to swaps do cards[?52] :=: cards[?52]
  return cards
  end

#
# THIS SECTION EVALUATES THE FINAL HAND, TALLIES WINNINGS OR LOOSINGS
#
procedure evaluate(cards)

  static  hash1,hash2
  initial {
          hash1 := "23456789TJQKA"
          hash2 := "CSHD"
          }

  temp  := table(0) ; suit := table(0) ; result := table(0)
  four  := 0 ; three := 0 ; twopair := 0 ; job     := 0 ; points := -1
  flush := 0 ; full  := 0 ; royal   := 0 ; straight:= 0 ; pair:= 0

  every card := !cards do
    temp[card[1]] +:= 1
  every tmp := key(temp) do
    case temp[tmp] of
      {
      4 : { result["four"]  := 1 ; result["job"] := 1 }
      3 : { result["three"] := 1 ; result["job"] := 1 }
      2 : { result["pair"] +:= 1 ; result["job"] := if find(tmp,hash1) > 9 then 1 }
      }
  if result["pair"] = 2 then { result["twopair"] := 1 ; result["job"] := 1 }
  every card := !cards    do suit[card[2]] +:= 1
  every tmp  := key(suit) do if suit[tmp] = 5 then { result["flush"] := 1 ; result["job"] := 1 }
  tmp := sort(cards) ; test := ""
  every card := !tmp do test ||:= card[1]
  if (result["three"] = 1) & (result["pair"] = 1) then { result["full"] := 1 ; result["job"] := 1 }
  if find(test,hash1) then
    {
    result["straight"] := 1
    result["job"]      := 1
    if test[1] == "T" then result["royal"] := 1
    }
  if result["job"]     = 1 then points +:= 1
  if result["twopair"] = 1 then points +:= 1
  if result["three"]   = 1 then points +:= 5
  if result["full"]    = 1 then points +:= 10
  if result["flush"]   = 1 then points +:= 10
  if result["straight"]= 1 then points +:= 10
  if result["four"]    = 1 then points +:= 100
  if result["royal"]   = 1 then points +:= 500
  message := "\e[14;1fHand Evaluation -> "
  every thing := key(result) do message ||:= thing || ", "
  return points
  end

#
# THIS ROUTINE PARSES A STRING WITH RESPECT TO SOME DELIMITER
#
procedure parse(line,delims)
  static chars
  chars  := &cset -- delims
  tokens := []
  line ? while tab(upto(chars)) do put(tokens,tab(many(chars)))
  return tokens
  end

#
# THIS ROUTINE PROMPTS FOR INPUT AND RETURNS A STRING
#
procedure input(prompt)
  writes(prompt)
  return read()
  end

goer@quads.uchicago.edu (Richard L. Goerwitz) (12/16/90)

In article <67EEF267C0600950@mis.mcw.edu>
TENAGLIA@mis.mcw.edu (Chris Tenaglia - 257-8765) writes:

>global money, message
>procedure main(param)
>  money := integer(param[1]) | 3
>  write("\e[2J\e[2H          \e#3\e[1;7mDRAW : Nothing Wild\e[m")
          ^^^^^^^^^^          ^^^^^^^^^^^^                  ^^^^^

I love to try to port people's things to Unix, but it's sometimes
pretty hard.  The code above is part of a really neat game, and I
like it a lot.  But unless a person happens to know ANSI escape se-
quences like the back of his or her hand, it's kinda hard to know
what is going on.  It's also a fact that most ANSI terminals don't
really implement the full, exact ANSI standard, and in general peo-
ple (at least here at the U of Chicago) are using Wyse, Televideo,
or VT-100 terminals (or emulators).

It's hard to say this, especially when the code is otherwise so clear
and clean, but my own personal observation is that code which assumes
hard-coded screen control 1) is hard to read, 2) is harder to main-
tain, and 3) is very tedious to port.

One solution (the one used in the klondike game) is to make all the
screen control sequences into global variables, and put them together
in one place, so they can easily be altered.  If the global variables
are mnemonic, this solves 1 and 2, but leaves 3 still up in the air.
(I've written a lot of code that has all three problems, so don't
take this as a flame.)  The workability of the "klondike" solution is
evinced by the fact that I was able to port it to Unix in a very short
period of time and repost.  I admit that the repost was something of a
hack.  When the next klondike version comes out I'll do a more serious
job.  The mnemonic global variables really helped.

Another solution is to isolate screen control sequences in a single
procedure, which would be called mnemonically.  This solution is not
perfect, because it is not always as easy to read as the "klondike"
solution.  But is does make maintenance a snap, and leaves would-be
port-ers with very little work to do.  I'll give a little example
below that will show how things can be done this way, and yet kept
readable:

>  write("\e[2J\e[2H          \e#3\e[1;7mDRAW : Nothing Wild\e[m")

   output("clear")
   output("goto",1,2)
   writes("          ")
   output(whatever \e#3 does)
   output("bold-reverse")  # or output("bold"); output("reverse") ???
   write("DRAW : Nothing Wild")
   output("normal")

By doing things this way, even someone who doesn't know the least bit
about ANSI screen codes will be able to understand what is going on.
What is more, no changes will be required if, say, some escape sequence
needs to be changed.  In fact, one could insert codes for a _whole dif-
ferent terminal_ and it wouldn't require changing a single scratch of
the above code.  All changes would be isolated within the output pro-
cedure.

I admit that things are more bulky this way.  But you don't have to have
your ANSI chart out while programming.  And in fact, you don't even have
to think about what sort of terminal you're using.  Nor does anyone else
who reads your code.  The sequence output("clear") is infinitely more
clear, portable, and maintainable than "\e[2J"!

One more bit that might be useful to inject here:  Most terminals can han-
dle 24 lines of text.  Not all can handle 25.  To hit the majority of
users, keep screen I/O within a range of 24 lines.

I hope that this will help, and not seem like a gratuitous flame!

-Richard

ron@mlfarm.UUCP (Ronald Florence) (12/17/90)

I'd like to add a strong second to Richard Goerwitz's comments on code
portability.  One of the most welcome features of Icon is the
implementation independence of the code.  With few exceptions, Icon
code works on a broad range of machines.  How depressing, then, to
discover valuable utilites and clever games which require hours of
porting with an editor because of hard-coded screen-control escape
sequences.  Richard Goerwitz's Icon termcap library, which includes
ms-dos entries, is one solution.  For those who don't want to use the
termcap code, screen control sequences can be isolated in global
strings or functions, so that porting doesn't require line-by-line
changes of an entire program.

While I'm griping, I'd urge that program instructions omit
implementation-dependent exceptions:

 > # DRAW.ICN          12/15/90          BY TENAGLIA       #
 > #                                                       #
 > # SIMPLE BUT FUN DRAW POKER GAME. WORKS ON ANSI SCREEN. #
 > # USAGE : ICONX DRAW [starting credits]                 #

If I'm not wrong, the need for `iconx' before the program name is
confined to ms-dos and VMS.  Users of those systems surely know how to
run Icon programs on their systems.

My comments are not in any way intended as a flame of Chris Tenaglia's
excellent game, which would probably work unchanged on some of the
terminals here.  With a few changes that would be easy when the code
is written, though time-consuming later, the game would work right off
the icon-group mailing list on any terminal and operating system
running Icon.  
--

Ronald Florence			ron@mlfarm.com