mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-28 13:46:06 -08:00
58 lines
2.5 KiB
Java
58 lines
2.5 KiB
Java
import java.io.InputStreamReader;
|
|
import java.io.OutputStreamWriter;
|
|
import java.io.Reader;
|
|
import java.io.Writer;
|
|
import java.util.Collections;
|
|
|
|
/**
|
|
* Plays a game of blackjack on the terminal. Looking at the code, the reader
|
|
* might conclude that this implementation is "over engineered." We use many
|
|
* techniques and patterns developed for much larger code bases to create more
|
|
* maintainable code, which may not be as relevant for a simple game of
|
|
* Blackjack. To wit, the rules and requirements are not likely to ever change
|
|
* so there is not so much value making the code flexible.
|
|
*
|
|
* Nevertheless, this is meant to be an example that the reader can learn good
|
|
* Java coding techniques from. Furthermore, many of the "over-engineering"
|
|
* tactics are as much about testability as they are about maintainability.
|
|
* Imagine trying to manually test infrequent scenarios like Blackjack,
|
|
* insurance, or splitting without any ability to automate a specific scenario
|
|
* and the value of unit testing becomes immediately apparent.
|
|
*
|
|
* Another "unnecessary" aspect of this codebase is good Javadoc. Again, this is
|
|
* meant to be educational, but another often overlooked benefit is that most
|
|
* IDEs will display Javadoc in "autocomplete" suggestions. This is remarkably
|
|
* helpful when using a class as a quick reminder of what you coded earlier.
|
|
* This is true even if no one ever publishes or reads the HTML output of the
|
|
* javadoc.
|
|
*
|
|
*/
|
|
public class Blackjack {
|
|
public static void main(String[] args) {
|
|
// Intuitively it might seem like the main program logic should be right
|
|
// here in 'main' and that we should just use System.in and System.out
|
|
// directly whenever we need them. However, notice that System.out and
|
|
// System.in are just an OutputStream and InputStream respectively. By
|
|
// allowing alternate streams to be specified to Game at runtime, we can
|
|
// write non-interactive tests of the code. See UserIoTest as an
|
|
// example.
|
|
// Likewise, by allowing an alternative "shuffle" algorithm, test code
|
|
// can provide a deterministic card ordering.
|
|
try (Reader in = new InputStreamReader(System.in)) {
|
|
Writer out = new OutputStreamWriter(System.out);
|
|
UserIo userIo = new UserIo(in, out);
|
|
Deck deck = new Deck(cards -> {
|
|
userIo.println("RESHUFFLING");
|
|
Collections.shuffle(cards);
|
|
return cards;
|
|
});
|
|
Game game = new Game(deck, userIo);
|
|
game.run();
|
|
} catch (Exception e) {
|
|
// This allows us to elegantly handle CTRL+D / CTRL+Z by throwing an exception.
|
|
System.out.println(e.getMessage());
|
|
System.exit(1);
|
|
}
|
|
}
|
|
}
|