mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-26 04:41:52 -08:00
Merge pull request #469 from jnellis/main
An exact match of the original Acey Ducey game using some new features in java after 1.8
This commit is contained in:
201
01_Acey_Ducey/java/src/AceyDucey17.java
Normal file
201
01_Acey_Ducey/java/src/AceyDucey17.java
Normal file
@@ -0,0 +1,201 @@
|
||||
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.
|
||||
*/
|
||||
}
|
||||
@@ -32,6 +32,8 @@ To run from the command line, you will need a Java SDK (eg. [Oracle JDK](https:/
|
||||
* eg. `javac AceyDuceyGame.java`
|
||||
1. Run the compiled program with `java`:
|
||||
* eg. `java AceyDuceyGame`
|
||||
|
||||
or if you are **using JDK11 or later** you can now execute a self contained java file that has a main method directly with `java <filename>.java`.
|
||||
|
||||
## javascript
|
||||
|
||||
|
||||
Reference in New Issue
Block a user