Hangman.java
1 import java.io.IOException; 2 import java.lang.System.Logger.Level; 3 import picocli.CommandLine; 4 import picocli.CommandLine.Command; 5 import picocli.CommandLine.Option; 6 7 /// Hangman game - picocli executable 8 /// 9 /// SPDX-License-Identifier: GPL-3.0-or-later 10 /// SPDX-FileCopyrightText: 2024 Jonas Smedegaard <dr@jones.dk> 11 /// 12 /// Guess letters for a secret word 13 /// 14 /// * v0.0.3 15 /// * use System.Logger, and picocli for main executable 16 /// * v0.0.2 17 /// * Java reimplementation from memory in 2024 18 /// * v0.0.1 19 /// * C64 BASIC implementation in 1985 20 /// (lost on a tape somewhere) 21 /// 22 /// @version 0.0.3 23 /// @since JDK 15 24 /// @see <https://app.radicle.xyz/nodes/ash.radicle.garden/rad:z3YzABgyz2D36LiKe3YcdJ6PcDCXM/tree/assign5/README.md> 25 /// @see <https://moodle.ruc.dk/mod/assign/view.php?id=508316> 26 @Command(name = "Hangman", 27 version = "Hangman 0.0.3", 28 mixinStandardHelpOptions = true, 29 // TODO: enable when picocli v4.7 is in Debian 30 // @see <https://bugs.debian.org/1086095> 31 //sortSynopsis = false, 32 description = "Guess letters in a secret word, limited to 8 wrong guesses.", 33 // use heredoc 34 // @since JDK 15 35 // @see <https://openjdk.org/jeps/378> 36 footer = """ 37 38 EXAMPLES: 39 java Hangman 40 java Hangman --word=juggernaut --clear 41 export HANGMAN_WORD=juggernaut java Hangman --clear 42 43 By default, a randomly selected word is used. 44 to instead use a user-provided word, 45 provide it either with command-line option --word 46 or via environment variable HANGMAN_WORD. 47 """) 48 public final class Hangman implements Runnable { 49 50 /// Constructs a new instance of Hangman. 51 // (this is explicit only to silence javadoc) 52 public Hangman() { 53 } 54 55 /// main method for the CLI tool 56 /// 57 /// @param args command-line arguments 58 public static void main(final String[] args) { 59 int exitCode = new CommandLine(new Hangman()).execute(args); 60 System.exit(exitCode); 61 } 62 63 /// logger object 64 // @see <https://renato.athaydes.com/posts/java-system-logging> 65 private System.Logger logger; 66 67 /// word to guess 68 @Option(names = { "--word" }, paramLabel = "WORD", 69 description = "provide a word to guess") 70 private String word = "banana"; 71 72 /// flag whether to clear screen initially 73 @Option(names = { "--clear" }, 74 description = "try to initially clear the console") 75 private boolean clear; 76 77 public void run() { 78 if (clear) { 79 clearConsole(); 80 } 81 82 // simplify logging format 83 // @since JDK 7 84 // @see <https://stackoverflow.com/a/34229629/18619283> 85 System.setProperty("java.util.logging.SimpleFormatter.format", 86 "%5$s%6$s%n"); 87 logger = System.getLogger(""); 88 89 // Instantiation as dictated by assignment 90 HangmanCore core = new HangmanCore(logger, word, 8); 91 92 // minimal viable product 93 while (core.hasMaskedCharacters() && core.hasMoreAttempts()) { 94 logger.log(Level.INFO, 95 "[miss: {0}] Guess this word: {1}", 96 core.miss(), 97 core.mask()); 98 core.ask("Guess a character, and hit ENTER: "); 99 } 100 } 101 102 /// Try to clear the console 103 /// 104 /// @see <https://stackoverflow.com/a/64038023/18619283> 105 public static void clearConsole() { 106 try { 107 if (System.getProperty("os.name").contains("Windows")) { 108 new ProcessBuilder("cmd", "/c", "cls") 109 .inheritIO().start().waitFor(); 110 } else { 111 System.out.print("\033\143"); 112 System.out.flush(); 113 } 114 } catch (IOException | InterruptedException ex) { } 115 } 116 117 /// Environment variable lookup 118 /// 119 /// @param variable the environment variable to look up 120 /// @param fallback a fallback string if variable is undefined 121 /// @return contents of environment variable, 122 /// or fallback string 123 public static String envString( 124 final String variable, 125 final String fallback 126 ) { 127 String s = System.getenv(variable); 128 if (s != null && s.length() > 0) { 129 return s; 130 } 131 return fallback; 132 } 133 }