HangmanCore.java
1 import java.lang.System.Logger; 2 import java.lang.System.Logger.Level; 3 import java.util.Scanner; 4 5 /// Hangman game - core executable and embeddable class 6 /// 7 /// SPDX-License-Identifier: GPL-3.0-or-later 8 /// SPDX-FileCopyrightText: 2024 Jonas Smedegaard <dr@jones.dk> 9 /// 10 /// Guess letters for a secret word 11 /// 12 /// Core class usable as self-contained executable via method main() 13 /// and also embedded in a larger system by instantiating GridCore(). 14 /// 15 /// * v0.0.3 16 /// * use System.Logger, and picocli for main executable 17 /// * v0.0.2 18 /// * Java reimplementation from memory in 2024 19 /// * v0.0.1 20 /// * C64 BASIC implementation in 1985 21 /// (lost on a tape somewhere) 22 /// 23 /// @version 0.0.3 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 public final class HangmanCore { 27 28 /// construct word object 29 /// 30 /// @param logger logger object 31 /// @param word word to guess 32 /// @param missMax maximum failed guesses 33 public HangmanCore( 34 final Logger logger, 35 final String word, 36 final int missMax 37 ) { 38 this.logger = logger; 39 this.word = word; 40 this.missMax = missMax; 41 } 42 43 /// main method 44 /// 45 /// @param args (ignored) 46 public static void main(final String[] args) { 47 48 // simplify logging format 49 // @since JDK 7 50 // @see <https://stackoverflow.com/a/34229629/18619283> 51 System.setProperty("java.util.logging.SimpleFormatter.format", 52 "%5$s%6$s%n"); 53 54 // Instantiation as dictated by assignment 55 HangmanCore core = new HangmanCore( 56 System.getLogger(""), 57 "banana", 58 8); 59 60 // minimal viable product 61 while (core.hasMaskedCharacters() && core.hasMoreAttempts()) { 62 core.logger.log(Level.INFO, 63 "[miss: {0}] Guess this word: {1}", 64 core.miss(), 65 core.mask()); 66 core.ask("Guess a character, and hit ENTER: "); 67 } 68 } 69 70 /// logger object 71 // @see <https://renato.athaydes.com/posts/java-system-logging> 72 private final Logger logger; 73 74 /// maximum misses 75 private final Integer missMax; 76 77 /// the word to guess 78 private final String word; 79 80 /// characters guessed 81 private String chars = ""; 82 83 /// amount of false guesses 84 private int miss; 85 86 /// is word still masked? 87 /// 88 /// @return boolean whether the word has been guessed 89 public boolean hasMaskedCharacters() { 90 if (!mask().contains(".")) { 91 logger.log(Level.INFO, "game over - word guessed"); 92 return false; 93 } 94 return true; 95 } 96 97 /// are any guessing attempts left? 98 /// 99 /// @return boolean whether more gueeses remains 100 public boolean hasMoreAttempts() { 101 if (miss() >= missMax) { 102 logger.log(Level.INFO, "game over - 8 wrong guesses"); 103 return false; 104 } 105 return true; 106 } 107 108 /// getter for misses 109 /// 110 /// @return amount of misses 111 public int miss() { 112 return miss; 113 } 114 115 /// query for and process a guess 116 /// 117 /// @param prompt string encouraging to type a character 118 public void ask(final String prompt) { 119 Scanner scanner = new Scanner(System.in); 120 System.out.print(prompt); 121 String input = scanner.next(); 122 while (input.isEmpty()) { 123 input = scanner.next(); 124 } 125 126 String s = input.substring(0, 1); 127 chars += s; 128 if (!word.contains(s)) { 129 logger.log(Level.INFO, "unused character {0}", s); 130 miss++; 131 } 132 } 133 134 /// hide not yet guessed characters in word 135 /// 136 /// @return masked word 137 public String mask() { 138 String maskedWord = word; 139 140 // sloppily check potential duplicates repeatedly 141 for (int i = 0; i < word.length(); i++) { 142 String s = word.substring(i, i + 1); 143 if (!chars.contains(s)) { 144 maskedWord = maskedWord.replace(s, "."); 145 } 146 } 147 148 return maskedWord; 149 } 150 }