Sunday, November 23, 2008

Acey-Ducy: New Basic

As if often the case, code using modern styles is more efficient, but one often ends up writing more of it anyway to either improve the user experience or to create code that's more maintainable, reusable or robust.

Modern good practices, for example, require variable declaration. So that's extra code that needs to be written. But when I redid Acey-Ducey in the New, it found an error that had come over from the old. That is, I had mispelled a variable. In old basics, the environment was no help finding misspellings, since if you meant "M" but entered "N", it would just assume N was a new variable.

But look at this main loop:
WHILE (GameOver=0)
?"You now have ";Bank;" dollars"
?"Here are your next two cards "
A=DrawCard
B=DrawCard
If A>=B then Swap A, B
? "First card ===> "+CardText(A)
? "Second card ==> "+CardText(B)
?:?

SELECT CASE GetBet(Bet, Bank)
CASE PlayerBet
C = DrawCard
? "Third Card is "+CardText(C)
if ((C>=A) AND (C<B) AND (Bet>0)) or (Bet<0) then ?"You win!!!" else ?"Sorry, you lose."
if (C>=A) AND (C<B) then Bank += Bet else Bank -= Bet
GameOver = (Bank <= 0)
CASE PlayerPass
REM Do Nothing
CASE PlayerQuit
GameOver = 1
END SELECT
WEND
Ah. Much better. You can see exactly how things go.

Line numbers are terrible; initially I replaced them with labels so that "GOTO 260" was "GOTO DRAW2CARDS". That's somewhat better, but of course, on of the principles of top-down structured programming is to have one entry point and one exit point.

Doing this did remind me why I don't program in Basic much if I can help it. FreeBasic doesn't exactly match Microsoft Visual Basic and, of course, VB.NET is an entirely different beast. But all Basics have this combination of "Do whatever you want. We know what's best" that makes them irritating. (Pascal, by comparison, insists you follow a consistent pattern, no matter how stupid.)

Here's the full code. Note that I started out with an enumerated type for the game states which, I think, benefits readability. A lot of Basics don't have enums.

ENUM GameStates
PlayerBet
PlayerPass
PlayerQuit
Unknown
END ENUM

REM ***SUBROUTINES USED IN PROGRAM ***

Function DrawCard as Integer
Return Int(rnd(1)*13)+2
END FUNCTION

Function CardText(Card as Integer) as String
SELECT CASE CARD
CASE IS < 11: Return Str(Card)
CASE 11 : Return "Jack"
CASE 12 : Return "Queen"
CASE 13 : Return "King"
CASE 14 : Return "Ace"
CASE ELSE : Return "Error!"
END SELECT
End Function

Function GetBet(ByRef Bet as Integer, Bank as Integer) as GameStates
DIM S as String
DIM GS as GameStates = UNKNOWN
While (GS = Unknown)
Input"What is your bet? (0 to Pass, Q to Quit)";S
Bet = Val(S)
IF LEFT(UCASE(S),1) = "Q" THEN
GS = PlayerQuit
ELSEIF Bet = 0 THEN
?"Chicken!!"
GS = PlayerPass
ELSEIF Bet > Bank THEN
?"Sorry, my friend, but you bet too much."
ELSEIF Bet <>0 THEN
GS = PlayerBet
END IF
WEND
RETURN GS
End Function


REM ***END SUBROUTINE SECTION ***

PRINT Tab(26);"Acey Ducey Card Game"
PRINT TAB(29);"New Basic Redux"
?:?:?
?"Acey-Ducey is played in the following manner "
?"The Dealer (Computer) deals two cards face up"
?"You have an option to bet or not bet depending"
?"on whether or not you feel the card will have"
?"a value between the first two."
?"If you do not want to bet, input a 0"

INIT:
DIM Bet AS INTEGER = 100
DIM Bank AS INTEGER = 100
DIM AS INTEGER A, B, C
DIM Response AS STRING
DIM GameOver as Integer = 0

WHILE (GameOver=0)
?"You now have ";Bank;" dollars"
?"Here are your next two cards "
A=DrawCard
B=DrawCard
If A>=B then Swap A, B
? "First card ===> "+CardText(A)
? "Second card ==> "+CardText(B)
?:?

SELECT CASE GetBet(Bet, Bank)
CASE PlayerBet
C = DrawCard
? "Third Card is "+CardText(C)
if ((C>=A) AND (C<B) AND (Bet>0)) or (Bet<0) then ?"You win!!!" else ?"Sorry, you lose."
if (C>=A) AND (C<B) then Bank += Bet else Bank -= Bet
GameOver = (Bank <= 0)
CASE PlayerPass
REM Do Nothing
CASE PlayerQuit
GameOver = 1
END SELECT
WEND

if Bank <= 0 then ?"Sorry, friend, but you blew your wad." else ?"Yay! You still have money left!"
Input "Try again (YES or NO)";Response
if Response="YES" then GOTO INIT
?"OK, hope you had fun!"
END
I see now that I left a GOTO in at the end with GOTO INIT.

I went through a phase of being allergic to GOTO as all reformed spaghetti programmers must, but ultimately I think clarity rules above all. If getting rid of the GOTO would make code harder to read, then leave it in. For the past 20 years, though, it hasn't been a case of removing GOTOs but putting them in.

So, I've had only two cases where I've added GOTOs because they improved the clarity the code, and both of those were before exceptions had made it into the language I was using.

So, here? Nah, it's not necessary, but it's not "harmful" here, and we don't need to spend more time on it. This does point out the difference between modifying existing code versus designing anew.

I think it's also why programmers usually prefer to start over rather than try to make old code work. This little 100 line program has a few confusing parts in it. When you get into 10,000 lines or more, it can be hard work.

No comments:

Followers