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