mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2026-01-11 20:55:40 -08:00
202 lines
8.2 KiB
Java
202 lines
8.2 KiB
Java
import java.util.Random;
|
|
import java.util.Scanner;
|
|
|
|
/**
|
|
* A modern version (JDK17) of ACEY DUCEY using post Java 8 features. Notes
|
|
* regarding new java features or differences in the original basic
|
|
* implementation are numbered and at the bottom of this code.
|
|
* The goal is to recreate the exact look and feel of the original program
|
|
* minus a large glaring bug in the original code that lets you cheat.
|
|
*/
|
|
public class AceyDucey17 {
|
|
|
|
public static void main(String[] args) {
|
|
// notes [1]
|
|
System.out.println("""
|
|
ACEY DUCEY CARD GAME
|
|
CREATIVE COMPUTING MORRISTOWN, NEW JERSEY
|
|
|
|
|
|
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""");
|
|
|
|
do {
|
|
playGame();
|
|
} while (stillInterested());
|
|
System.out.println("O.K., HOPE YOU HAD FUN!");
|
|
}
|
|
|
|
public static void playGame() {
|
|
int cashOnHand = 100; // our only mutable variable note [11]
|
|
System.out.println("YOU NOW HAVE "+ cashOnHand +" DOLLARS.");// note [6]
|
|
while (cashOnHand > 0) {
|
|
System.out.println();
|
|
System.out.println("HERE ARE YOUR NEXT TWO CARDS:");
|
|
|
|
final Card lowCard = Card.getRandomCard(2, Card.KING); //note [3]
|
|
System.out.println(lowCard);
|
|
final Card highCard = Card.getRandomCard(lowCard.rank() + 1, Card.ACE);
|
|
System.out.println(highCard);
|
|
|
|
final int bet = getBet(cashOnHand);
|
|
final int winnings = determineWinnings(lowCard,highCard,bet);
|
|
cashOnHand += winnings;
|
|
if(winnings != 0 || cashOnHand != 0){ //note [2]
|
|
System.out.println("YOU NOW HAVE "+ cashOnHand +" DOLLARS.");//note [6]
|
|
}
|
|
}
|
|
}
|
|
|
|
public static int determineWinnings(Card lowCard, Card highCard, int bet){
|
|
if (bet <= 0) { // note [5]
|
|
System.out.println("CHICKEN!!");
|
|
return 0;
|
|
}
|
|
Card nextCard = Card.getRandomCard(2, Card.ACE);
|
|
System.out.println(nextCard);
|
|
if(nextCard.between(lowCard,highCard)){
|
|
System.out.println("YOU WIN!!!");
|
|
return bet;
|
|
}
|
|
System.out.println("SORRY, YOU LOSE");
|
|
return -bet;
|
|
}
|
|
|
|
public static boolean stillInterested(){
|
|
System.out.println();
|
|
System.out.println();
|
|
System.out.println("SORRY, FRIEND, BUT YOU BLEW YOUR WAD.");
|
|
System.out.println();
|
|
System.out.println();
|
|
System.out.print("TRY AGAIN (YES OR NO)? ");
|
|
Scanner input = new Scanner(System.in);
|
|
boolean playAgain = input.nextLine()
|
|
.toUpperCase()
|
|
.startsWith("Y"); // note [9]
|
|
System.out.println();
|
|
System.out.println();
|
|
return playAgain;
|
|
}
|
|
|
|
public static int getBet(int cashOnHand){
|
|
int bet;
|
|
do{
|
|
System.out.println();
|
|
System.out.print("WHAT IS YOUR BET? ");
|
|
bet = inputNumber();
|
|
if (bet > cashOnHand) {
|
|
System.out.println("SORRY, MY FRIEND, BUT YOU BET TOO MUCH.");
|
|
System.out.println("YOU HAVE ONLY "+cashOnHand+" DOLLARS TO BET.");
|
|
}
|
|
}while(bet > cashOnHand);
|
|
return bet;
|
|
}
|
|
|
|
public static int inputNumber() {
|
|
final Scanner input = new Scanner(System.in);
|
|
// set to negative to mark as not entered yet in case of input error.
|
|
int number = -1;
|
|
while (number < 0) {
|
|
try {
|
|
number = input.nextInt();
|
|
} catch(Exception ex) { // note [7]
|
|
System.out.println("!NUMBER EXPECTED - RETRY INPUT LINE");
|
|
System.out.print("? ");
|
|
try{
|
|
input.nextLine();
|
|
}
|
|
catch(Exception ns_ex){ // received EOF (ctrl-d or ctrl-z if windows)
|
|
System.out.println("END OF INPUT, STOPPING PROGRAM.");
|
|
System.exit(1);
|
|
}
|
|
}
|
|
}
|
|
return number;
|
|
}
|
|
|
|
record Card(int rank){
|
|
// Some constants to describe face cards.
|
|
public static final int JACK = 11, QUEEN = 12, KING = 13, ACE = 14;
|
|
private static final Random random = new Random();
|
|
|
|
public static Card getRandomCard(int from, int to){
|
|
return new Card(random.nextInt(from, to+1)); // note [4]
|
|
}
|
|
|
|
public boolean between(Card lower, Card higher){
|
|
return lower.rank() < this.rank() && this.rank() < higher.rank();
|
|
}
|
|
|
|
@Override
|
|
public String toString() { // note [13]
|
|
return switch (rank) {
|
|
case JACK -> "JACK";
|
|
case QUEEN -> "QUEEN";
|
|
case KING -> "KING";
|
|
case ACE -> "ACE\n"; // note [10]
|
|
default -> " "+rank+" "; // note [6]
|
|
};
|
|
}
|
|
}
|
|
|
|
/*
|
|
Notes:
|
|
1. Multiline strings, a.k.a. text blocks, were added in JDK15.
|
|
2. The original game only displays the players balance if it changed,
|
|
which it does not when the player chickens out and bets zero.
|
|
It also doesn't display the balance when it becomes zero because it has
|
|
a more appropriate message: Sorry, You Lose.
|
|
3. To pick two cards to show, the original BASIC implementation has a
|
|
bug that could cause a race condition if the RND function never chose
|
|
a lower number first and higher number second. It loops infinitely
|
|
re-choosing random numbers until the condition is met of the first
|
|
one being lower. The logic is changed a bit here so that the first
|
|
card picked is anything but an ACE, the highest possible card,
|
|
and then the second card is between the just picked first card upto
|
|
and including the ACE.
|
|
4. Random.nextInt(origin, bound) was added in JDK17, and allows to
|
|
directly pick a range for a random integer to be generated. The second
|
|
parameter is exclusive of the range and thus why they are stated with
|
|
+1's to the face card.
|
|
5. The original BASIC implementation has a bug that allows negative value
|
|
bets. Since you can't bet MORE cash than you have you can always bet
|
|
less including a very, very large negative value. You would do this when
|
|
the chances of winning are slim or zero since losing a hand SUBTRACTS
|
|
your bet from your cash; subtracting a negative number actually ADDS
|
|
to your cash, potentially making you an instant billionaire.
|
|
This loophole is now closed.
|
|
6. The subtle behavior of the BASIC PRINT command causes a space to be
|
|
printed before all positive numbers as well as a trailing space. Any
|
|
place a non-face card or the players balance is printed has extra space
|
|
to mimic this behavior.
|
|
7. Errors on input were probably specific to the interpreter. This program
|
|
tries to match the Vintage Basic interpreter's error messages. The final
|
|
input.nextLine() command exists to clear the blockage of whatever
|
|
non-number input was entered. But even that could fail if the user
|
|
types Ctrl-D (windows Ctrl-Z), signifying an EOF (end of file) and thus
|
|
the closing of STDIN channel. The original program on an EOF signal prints
|
|
"END OF INPUT IN LINE 660" and thus we cover it roughly the same way.
|
|
All of this is necessary to avoid a messy stack trace from being
|
|
printed as the program crashes.
|
|
9. The original game only accepted a full upper case "YES" to continue
|
|
playing if bankrupted. This program is more lenient and will accept
|
|
any input that starts with the letter 'y', uppercase or not.
|
|
10. The original game prints an extra blank line if the card is an ACE. There
|
|
is seemingly no rationale for this.
|
|
11. Modern java best practices are edging toward a more functional paradigm
|
|
and as such, mutating state is discouraged. All other variables besides
|
|
the cashOnHand are final and initialized only once.
|
|
12. Refactoring of the concept of a card is done with a record. Records were
|
|
introduced in JDK14. Card functionality is encapsulated in this example
|
|
of a record. An enum could be a better alternative since there are
|
|
technically only 13 cards possible.
|
|
13. Switch expressions were introduced as far back as JDK12 but continue to
|
|
be refined for clarity, exhaustiveness. As of JDK17 pattern matching
|
|
for switch expressions can be accessed by enabling preview features.
|
|
*/
|
|
}
|