mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-23 07:29:02 -08:00
Merge pull request #503 from jnellis/main
Java port of Craps. Updated Readme with detailed breakdown of the original BASIC code.
This commit is contained in:
@@ -17,3 +17,211 @@ As published in Basic Computer Games (1978):
|
||||
|
||||
Downloaded from Vintage Basic at
|
||||
http://www.vintage-basic.net/games.html
|
||||
|
||||
### Comments on the BASIC code for re-implementers.
|
||||
|
||||
15 LET R=0
|
||||
`R` is a variable that tracks winnings and losings. Unlike other games that
|
||||
start out with a lump sum of cash to spend this game assumes the user has as
|
||||
much money as they want and we only track how much they lost or won.
|
||||
|
||||
21 LET T=1
|
||||
22 PRINT "PICK A NUMBER AND INPUT TO ROLL DICE";
|
||||
23 INPUT Z
|
||||
24 LET X=(RND(0))
|
||||
25 LET T =T+1
|
||||
26 IF T<=Z THEN 24
|
||||
This block of code does nothing other than try to scramble the random number
|
||||
generator. Random number generation is not random, they are generated from the
|
||||
previous generated number. Because of the slow speed of these systems back then,
|
||||
gaming random number generators was a concern, mostly for gameplay quality.
|
||||
If you could know the "seed value" to the generator then you could effectively
|
||||
know how to get the exact same dice rolls to happen and change your bet to
|
||||
maximize your winnings and minimize your losses.
|
||||
|
||||
The first reason this is an example of bad coding practice is the user is asked
|
||||
to input a number but no clue is given as to the use of this number. This number
|
||||
has no bearing on the game and as we'll see only has bearing on the internal
|
||||
implementation of somehow trying to get an un-game-able seed for the random number
|
||||
generator (since all future random numbers generated are based off this seed value.)
|
||||
|
||||
The `RND(1)` command generates a number from a seed value that is always
|
||||
the same, everytime, from when the machine is booted up (old C64 behavior). In
|
||||
order to avoid the same dice rolls being generated, a special call to `RND(-TI)`
|
||||
would initialize the random generator with something else. But RND(-TI) is not
|
||||
a valid command on all systems. So `RND(0)`, which generates a random number
|
||||
from the system clock is used. But technically this could be gamed because the
|
||||
system clock was driven by the bootup time, there wasn't a BIOS battery on these
|
||||
systems that kept an internal real time clock going even when the system was
|
||||
turned off, unlike your regular PC. Therefore, in order to ensure as true
|
||||
randomness as possible, insert human reaction time by asking for human input.
|
||||
|
||||
But a human could just be holding down the enter key on bootup and that would
|
||||
just skip any kind of multi-millisecond variance assigned by a natural human
|
||||
reaction time. So, paranoia being a great motivator, a number is asked of the
|
||||
user to avoid just holding down the enter key which negates the timing variance
|
||||
of a human reaction.
|
||||
|
||||
What comes next is a bit of nonsense. The block of code loops a counter, recalling
|
||||
the `RND(0)` function (and thus reseeding it with the system clock value)
|
||||
and then comparing the counter to the user's number input
|
||||
in order to bail out of the loop. Because the `RND(0)` function is based off the
|
||||
system clock and the loop of code has no branching other than the bailout
|
||||
condition, the loop also takes a fixed amount of time to execute, thus making
|
||||
repeated calls to `RND(0)` predictive and this scheming to get a better random
|
||||
number is pointless. Furthermore, the loop is based on the number the user inputs
|
||||
so a huge number like ten million causes a very noticable delay and leaves the
|
||||
user wondering if the program has errored. The author could have simply called
|
||||
`RND(0)` once and used a prompt that made more sense like asking for the users
|
||||
name and then using that name in the game's replies.
|
||||
|
||||
It is advised that you use whatever your languages' random number generator
|
||||
provides and simply skip trying to recreate this bit of nonsense including
|
||||
the user input.
|
||||
|
||||
27 PRINT"INPUT THE AMOUNT OF YOUR WAGER.";
|
||||
28 INPUT F
|
||||
30 PRINT "I WILL NOW THROW THE DICE"
|
||||
40 LET E=INT(7*RND(1))
|
||||
41 LET S=INT(7*RND(1))
|
||||
42 LET X=E+S
|
||||
.... a bit later ....
|
||||
60 IF X=1 THEN 40
|
||||
65 IF X=0 THEN 40
|
||||
|
||||
|
||||
`F` is a variable that represents the users wager for this betting round.
|
||||
`E` and `S` represent the two individual and random dice being rolled.
|
||||
This code is actually wrong because it returns a value between 0 and 6.
|
||||
`X` is the sum of these dice rolls. As you'll see though further down in the
|
||||
code, if `X` is zero or one it re-rolls the dice to maintain a potential
|
||||
outcome of the sum of two dice between 2 and 12. This skews the normal distribution
|
||||
of dice values to favor lower numbers because it does not consider that `E`
|
||||
could be zero and `S` could be 2 or higher. To show this skewing of values
|
||||
you can run the `distribution.bas` program which creates a histogram of the
|
||||
distribution of the bad dice throw code and proper dice throw code.
|
||||
|
||||
Here are the results:
|
||||
|
||||
DISTRIBUTION OF DICE ROLLS WITH INT(7*RND(1)) VS INT(6*RND(1)+1)
|
||||
THE INT(7*RND(1)) DISTRIBUTION:
|
||||
2 3 4 5 6 7 8 9 10 11 12
|
||||
6483 8662 10772 13232 15254 13007 10746 8878 6486 4357 2123
|
||||
THE INT(6*RND(1)+1) DISTRIBUTION
|
||||
2 3 4 5 6 7 8 9 10 11 12
|
||||
2788 5466 8363 11072 13947 16656 13884 11149 8324 5561 2790
|
||||
If the dice rolls are fair then we should see the largest occurrence be a 7 and
|
||||
the smallest should be 2 and 12. Furthermore the occurrences should be
|
||||
symetrical meaning there should be roughly the same amount of 2's as 12's, the
|
||||
same amount of 3's as 11's, 4's as 10's and so on until you reach the middle, 7.
|
||||
But notice in the skewed dice roll, 6 is the most rolled number not 7, and the
|
||||
rest of the numbers are not symetrical, there are many more 2's than 12's.
|
||||
So the lesson is test your code.
|
||||
|
||||
The proper way to model a dice throw, in almost every language is
|
||||
`INT(6*RND(1)+1)` or `INT(6*RND(1))+1`
|
||||
|
||||
SideNote: `X` was used already in the
|
||||
previous code block discussed but its value was never used. This is another
|
||||
poor coding practice: **Don't reuse variable names for different purposes.**
|
||||
|
||||
50 IF X=7 THEN 180
|
||||
55 IF X=11 THEN 180
|
||||
60 IF X=1 THEN 40
|
||||
62 IF X=2 THEN 195
|
||||
65 IF X=0 THEN 40
|
||||
70 IF X=2 THEN 200
|
||||
80 IF X=3 THEN 200
|
||||
90 IF X=12 THEN 200
|
||||
125 IF X=5 THEN 220
|
||||
130 IF X =6 THEN 220
|
||||
140 IF X=8 THEN 220
|
||||
150 IF X=9 THEN 220
|
||||
160 IF X =10 THEN 220
|
||||
170 IF X=4 THEN 220
|
||||
|
||||
This bit of code determines the routing of where to go for payout, or loss.
|
||||
Of course, line 60 and 65 are pointless as we've just shown and should be removed
|
||||
as long as the correct dice algorithm is also changed.
|
||||
|
||||
62 IF X=2 THEN 195
|
||||
....
|
||||
70 IF X=2 THEN 200
|
||||
The check for a 2 has already been made and the jump is done. Line 70 is
|
||||
therefore redundant and can be left out. The purpose of line 62 is only to
|
||||
print a special output, "SNAKE EYES!" which we'll see in the next block creates
|
||||
duplicate code.
|
||||
|
||||
Lines 125-170 are also pointlessly checked because we know previous values have
|
||||
been ruled out, only these last values must remain, and they are all going to
|
||||
the same place, line 220. Line 125-170 could have simply been replaced with
|
||||
`GOTO 220`
|
||||
|
||||
|
||||
|
||||
180 PRINT X "- NATURAL....A WINNER!!!!"
|
||||
185 PRINT X"PAYS EVEN MONEY, YOU WIN"F"DOLLARS"
|
||||
190 GOTO 210
|
||||
195 PRINT X"- SNAKE EYES....YOU LOSE."
|
||||
196 PRINT "YOU LOSE"F "DOLLARS."
|
||||
197 LET F=0-F
|
||||
198 GOTO 210
|
||||
200 PRINT X " - CRAPS...YOU LOSE."
|
||||
205 PRINT "YOU LOSE"F"DOLLARS."
|
||||
206 LET F=0-F
|
||||
210 LET R= R+F
|
||||
211 GOTO 320
|
||||
|
||||
This bit of code manages instant wins or losses due to 7,11 or 2,3,12. As
|
||||
mentioned previously, lines 196 and 197 are essentially the same as lines
|
||||
205 and 206. A simpler code would be just to jump after printing the special
|
||||
message of "SNAKE EYES!" to line 205.
|
||||
|
||||
Lines 197 and 206 just negate the wager by subtracting it from zero. Just saying
|
||||
`F = -F` would have sufficed. Line 210 updates your running total of winnings
|
||||
or losses with this bet.
|
||||
|
||||
220 PRINT X "IS THE POINT. I WILL ROLL AGAIN"
|
||||
230 LET H=INT(7*RND(1))
|
||||
231 LET Q=INT(7*RND(1))
|
||||
232 LET O=H+Q
|
||||
240 IF O=1 THEN 230
|
||||
250 IF O=7 THEN 290
|
||||
255 IF O=0 THEN 230
|
||||
|
||||
This code sets the point, the number you must re-roll to win without rolling
|
||||
a 7, the most probable number to roll. Except in this case again, it has the
|
||||
same incorrect dice rolling code and therefore 6 is the most probable number
|
||||
to roll. The concept of DRY (don't repeat yourself) is a coding practice which
|
||||
encourages non-duplication of code because if there is an error in the code, it
|
||||
can be fixed in one place and not multiple places like in this code. The scenario
|
||||
might be that a programmer sees some wrong code, fixes it, but neglects to
|
||||
consider that there might be duplicates of the same wrong code elsewhere. If
|
||||
you practice DRY then you never worry much about behaviors in your code diverging
|
||||
due to duplicate code snippets.
|
||||
|
||||
260 IF O=X THEN 310
|
||||
270 PRINT O " - NO POINT. I WILL ROLL AGAIN"
|
||||
280 GOTO 230
|
||||
290 PRINT O "- CRAPS. YOU LOSE."
|
||||
291 PRINT "YOU LOSE $"F
|
||||
292 F=0-F
|
||||
293 GOTO 210
|
||||
300 GOTO 320
|
||||
310 PRINT X"- A WINNER.........CONGRATS!!!!!!!!"
|
||||
311 PRINT X "AT 2 TO 1 ODDS PAYS YOU...LET ME SEE..."2*F"DOLLARS"
|
||||
312 LET F=2*F
|
||||
313 GOTO 210
|
||||
|
||||
This is the code to keep rolling until the point is made or a seven is rolled.
|
||||
Again we see the negated `F` wager and lose message duplicated. This code could
|
||||
have been reorganized using a subroutine, or in BASIC, the GOSUB command, but
|
||||
in your language its most likely just known as a function or method. You can
|
||||
do a `grep -r 'GOSUB'` from the root directory to see other BASIC programs in
|
||||
this set that use GOSUB.
|
||||
|
||||
The rest of the code if fairly straight forward, replay the game or end with
|
||||
a report of your winnings or losings.
|
||||
|
||||
|
||||
|
||||
|
||||
24
29_Craps/distributions.bas
Normal file
24
29_Craps/distributions.bas
Normal file
@@ -0,0 +1,24 @@
|
||||
10 PRINT "DISTRIBUTION OF DICE ROLLS WITH INT(7*RND(1)) VS INT(6*RND(1)+1)"
|
||||
20 DIM A(12)
|
||||
30 DIM B(12)
|
||||
100 FOR X = 1 TO 100000 : REM CHOOSE A LARGE NUMBER TO GET A FINER GRAINED HISTOGRAM
|
||||
140 REM GET A NUMBER FROM 0 TO 6 INCLUSIVE WITH THE INTENT TO THROW AWAY ZEROES.
|
||||
150 LET D1 = INT(7*RND(1))
|
||||
155 LET D2 = INT(7*RND(1))
|
||||
160 LET S1 = D1+D2
|
||||
165 REM IF THIS SUM IS LESS THAN TWO THEN TRY AGAIN.
|
||||
170 IF S1<2 THEN 150
|
||||
199 REM GET A NUMBER FROM 0 TO 5 THEN ADD 1 TO IT TO MAKE IT 1 TO 6
|
||||
200 LET D3 = INT(6*RND(1))+1
|
||||
210 LET D4 = INT(6*RND(1))+1
|
||||
220 LET S2 = D3+D4
|
||||
245 REM USE OUR ARRAY AS A HISTOGRAM, COUNTING EACH OCCURRENCE OF DICE ROLL
|
||||
250 A(S1) = A(S1) + 1
|
||||
260 B(S2) = B(S2) + 1
|
||||
290 NEXT X
|
||||
300 PRINT "THE INT(7*RND(1)) DISTRIBUTION:"
|
||||
310 FOR I = 2 TO 12 :PRINT I,:NEXT:PRINT
|
||||
320 FOR I = 2 TO 12 :PRINT A(I),:NEXT:PRINT
|
||||
325 PRINT "THE INT(6*RND(1)+1) DISTRIBUTION"
|
||||
330 FOR I = 2 TO 12 :PRINT I,:NEXT:PRINT
|
||||
340 FOR I = 2 TO 12 :PRINT B(I),:NEXT:PRINT
|
||||
125
29_Craps/java/src/Craps.java
Normal file
125
29_Craps/java/src/Craps.java
Normal file
@@ -0,0 +1,125 @@
|
||||
import java.util.Random;
|
||||
import java.util.Scanner;
|
||||
|
||||
/**
|
||||
* Port of Craps from BASIC to Java 17.
|
||||
*/
|
||||
public class Craps {
|
||||
public static final Random random = new Random();
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("""
|
||||
CRAPS
|
||||
CREATIVE COMPUTING MORRISTOWN, NEW JERSEY
|
||||
|
||||
|
||||
2,3,12 ARE LOSERS; 4,5,6,8,9,10 ARE POINTS; 7,11 ARE NATURAL WINNERS.
|
||||
""");
|
||||
double winnings = 0.0;
|
||||
do {
|
||||
winnings = playCraps(winnings);
|
||||
} while (stillInterested(winnings));
|
||||
winningsReport(winnings);
|
||||
}
|
||||
|
||||
public static double playCraps(double winnings) {
|
||||
double wager = getWager();
|
||||
System.out.println("I WILL NOW THROW THE DICE");
|
||||
int roll = rollDice();
|
||||
double payout = switch (roll) {
|
||||
case 7, 11 -> naturalWin(roll, wager);
|
||||
case 2, 3, 12 -> lose(roll, wager);
|
||||
default -> setPoint(roll, wager);
|
||||
};
|
||||
return winnings + payout;
|
||||
}
|
||||
|
||||
public static int rollDice() {
|
||||
return random.nextInt(1, 7) + random.nextInt(1, 7);
|
||||
}
|
||||
|
||||
private static double setPoint(int point, double wager) {
|
||||
System.out.printf("%1$ d IS THE POINT. I WILL ROLL AGAIN%n",point);
|
||||
return makePoint(point, wager);
|
||||
}
|
||||
|
||||
private static double makePoint(int point, double wager) {
|
||||
int roll = rollDice();
|
||||
if (roll == 7)
|
||||
return lose(roll, wager);
|
||||
if (roll == point)
|
||||
return win(roll, wager);
|
||||
System.out.printf("%1$ d - NO POINT. I WILL ROLL AGAIN%n", roll);
|
||||
return makePoint(point, wager); // recursive
|
||||
}
|
||||
|
||||
private static double win(int roll, double wager) {
|
||||
double payout = 2 * wager;
|
||||
System.out.printf("%1$ d - A WINNER.........CONGRATS!!!!!!!!%n", roll);
|
||||
System.out.printf("%1$ d AT 2 TO 1 ODDS PAYS YOU...LET ME SEE...$%2$3.2f%n",
|
||||
roll, payout);
|
||||
return payout;
|
||||
}
|
||||
|
||||
private static double lose(int roll, double wager) {
|
||||
String msg = roll == 2 ? "SNAKE EYES.":"CRAPS";
|
||||
System.out.printf("%1$ d - %2$s...YOU LOSE.%n", roll, msg);
|
||||
System.out.printf("YOU LOSE $%3.2f%n", wager);
|
||||
return -wager;
|
||||
}
|
||||
|
||||
public static double naturalWin(int roll, double wager) {
|
||||
System.out.printf("%1$ d - NATURAL....A WINNER!!!!%n", roll);
|
||||
System.out.printf("%1$ d PAYS EVEN MONEY, YOU WIN $%2$3.2f%n", roll, wager);
|
||||
return wager;
|
||||
}
|
||||
|
||||
public static void winningsUpdate(double winnings) {
|
||||
System.out.println(switch ((int) Math.signum(winnings)) {
|
||||
case 1 -> "YOU ARE NOW AHEAD $%3.2f".formatted(winnings);
|
||||
case 0 -> "YOU ARE NOW EVEN AT 0";
|
||||
default -> "YOU ARE NOW UNDER $%3.2f".formatted(-winnings);
|
||||
});
|
||||
}
|
||||
|
||||
public static void winningsReport(double winnings) {
|
||||
System.out.println(
|
||||
switch ((int) Math.signum(winnings)) {
|
||||
case 1 -> "CONGRATULATIONS---YOU CAME OUT A WINNER. COME AGAIN!";
|
||||
case 0 -> "CONGRATULATIONS---YOU CAME OUT EVEN, NOT BAD FOR AN AMATEUR";
|
||||
default -> "TOO BAD, YOU ARE IN THE HOLE. COME AGAIN.";
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static boolean stillInterested(double winnings) {
|
||||
System.out.print(" IF YOU WANT TO PLAY AGAIN PRINT 5 IF NOT PRINT 2 ");
|
||||
int fiveOrTwo = (int)getInput();
|
||||
winningsUpdate(winnings);
|
||||
return fiveOrTwo == 5;
|
||||
}
|
||||
|
||||
public static double getWager() {
|
||||
System.out.print("INPUT THE AMOUNT OF YOUR WAGER. ");
|
||||
return getInput();
|
||||
}
|
||||
|
||||
public static double getInput() {
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
System.out.print("> ");
|
||||
while (true) {
|
||||
try {
|
||||
return scanner.nextDouble();
|
||||
} catch (Exception ex) {
|
||||
try {
|
||||
scanner.nextLine(); // flush whatever this non number stuff is.
|
||||
} catch (Exception ns_ex) { // received EOF (ctrl-d or ctrl-z if windows)
|
||||
System.out.println("END OF INPUT, STOPPING PROGRAM.");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
System.out.println("!NUMBER EXPECTED - RETRY INPUT LINE");
|
||||
System.out.print("> ");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user