From 36862e0b4681a95bc85e22e7c842f5984577c526 Mon Sep 17 00:00:00 2001 From: Silas Reinagel Date: Fri, 26 May 2017 23:32:21 -0700 Subject: [PATCH 1/2] Autonomy and Messaging --- src/main/java/common/Media.java | 6 ++ src/main/java/common/Messages.java | 21 +++++ src/main/java/common/PrintStreamMedia.java | 25 ++++++ src/main/java/common/Recipient.java | 6 ++ src/main/java/hangman/Gallows.java | 42 ++++++++++ src/main/java/hangman/Game.java | 47 +++++++++++ src/main/java/hangman/Main.java | 98 ---------------------- src/main/java/hangman/Secret.java | 73 ++++++++++++++++ src/main/java/hangman/UserInterface.java | 66 +++++++++++++++ src/main/java/hangman/WordList.java | 42 ++++++++++ src/test/java/hangman/MainTest.java | 4 +- 11 files changed, 330 insertions(+), 100 deletions(-) create mode 100644 src/main/java/common/Media.java create mode 100644 src/main/java/common/Messages.java create mode 100644 src/main/java/common/PrintStreamMedia.java create mode 100644 src/main/java/common/Recipient.java create mode 100644 src/main/java/hangman/Gallows.java create mode 100644 src/main/java/hangman/Game.java delete mode 100644 src/main/java/hangman/Main.java create mode 100644 src/main/java/hangman/Secret.java create mode 100644 src/main/java/hangman/UserInterface.java create mode 100644 src/main/java/hangman/WordList.java diff --git a/src/main/java/common/Media.java b/src/main/java/common/Media.java new file mode 100644 index 0000000..2df9802 --- /dev/null +++ b/src/main/java/common/Media.java @@ -0,0 +1,6 @@ +package common; + +public interface Media +{ + void print(String text); +} diff --git a/src/main/java/common/Messages.java b/src/main/java/common/Messages.java new file mode 100644 index 0000000..0572ab6 --- /dev/null +++ b/src/main/java/common/Messages.java @@ -0,0 +1,21 @@ +package common; + +import java.util.*; + +public final class Messages +{ + private final Map> _recipients = new HashMap<>(); + + public void subcribeTo(String subject, Recipient recipient) + { + if (!_recipients.containsKey(subject)) + _recipients.put(subject, new HashSet<>()); + _recipients.get(subject).add(recipient); + } + + public void send(String subject, String content) + { + if (_recipients.containsKey(subject)) + _recipients.get(subject).forEach(x -> x.receive(content)); + } +} diff --git a/src/main/java/common/PrintStreamMedia.java b/src/main/java/common/PrintStreamMedia.java new file mode 100644 index 0000000..8c3e39b --- /dev/null +++ b/src/main/java/common/PrintStreamMedia.java @@ -0,0 +1,25 @@ +package common; + +import java.io.OutputStream; +import java.io.PrintStream; + +public final class PrintStreamMedia implements Media +{ + private final PrintStream _stream; + + public PrintStreamMedia(OutputStream out) + { + this(new PrintStream(out)); + } + + public PrintStreamMedia(PrintStream stream) + { + _stream = stream; + } + + @Override + public void print(String text) + { + _stream.print(text); + } +} diff --git a/src/main/java/common/Recipient.java b/src/main/java/common/Recipient.java new file mode 100644 index 0000000..dbcedac --- /dev/null +++ b/src/main/java/common/Recipient.java @@ -0,0 +1,6 @@ +package common; + +public interface Recipient +{ + void receive(String content); +} diff --git a/src/main/java/hangman/Gallows.java b/src/main/java/hangman/Gallows.java new file mode 100644 index 0000000..cf0436b --- /dev/null +++ b/src/main/java/hangman/Gallows.java @@ -0,0 +1,42 @@ +package hangman; + +import common.Messages; +import common.Recipient; + +import java.util.concurrent.atomic.AtomicInteger; + +public final class Gallows implements Runnable +{ + private final Messages _messages; + private final Recipient _guessed; + + private AtomicInteger _mistakes; + private int _max; + + public Gallows(Messages messages, int max) + { + _messages = messages; + _guessed = x -> guessed(x); + _mistakes = new AtomicInteger(); + _max = max; + } + + @Override + public void run() + { + _messages.subcribeTo("GuessedCorrectly", _guessed); + } + + private void guessed(String correctly) + { + if(!correctly.equals("true")) + _messages.send("ShowUser", String.format("Mistake #%d out of %d\n", _mistakes.incrementAndGet(), _max)); + checkForGameEnd(); + } + + private void checkForGameEnd() + { + if (_mistakes.get() == _max) + _messages.send("GameEnded", "lost"); + } +} diff --git a/src/main/java/hangman/Game.java b/src/main/java/hangman/Game.java new file mode 100644 index 0000000..805c8b4 --- /dev/null +++ b/src/main/java/hangman/Game.java @@ -0,0 +1,47 @@ +package hangman; + +import common.Media; +import common.Messages; +import common.PrintStreamMedia; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; + +public class Game implements Runnable +{ + private final List _objects; + private final WordList _words; + private final Messages _messages; + + public static void main(String[] args) + { + new Game(System.in, System.out, 5).run(); + } + + public Game( InputStream in, OutputStream out, int maxMistakes) + { + this(new Messages(), new WordList(), new PrintStreamMedia(out), new Scanner(in), maxMistakes); + } + + public Game(Messages messages, WordList words, Media media, Scanner in, int maxMistakes) + { + this(messages, words, new UserInterface(messages, media, in), new Secret(messages), new Gallows(messages, maxMistakes)); + } + + private Game(Messages messages, WordList words, Runnable... objects) + { + _objects = Arrays.asList(objects); + _messages = messages; + _words = words; + } + + @Override + public void run() + { + _objects.forEach(x -> x.run()); + _messages.send("GameStarted", _words.random()); + } +} diff --git a/src/main/java/hangman/Main.java b/src/main/java/hangman/Main.java deleted file mode 100644 index d870fbb..0000000 --- a/src/main/java/hangman/Main.java +++ /dev/null @@ -1,98 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2017 Yegor Bugayenko - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - */ -package hangman; - -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintStream; -import java.util.Iterator; -import java.util.Random; -import java.util.Scanner; - -public class Main { - - private final InputStream input; - private final OutputStream output; - private final int max; - private static final String[] WORDS = { - "simplicity", "equality", "grandmother", - "neighborhood", "relationship", "mathematics", - "university", "explanation" - }; - - public Main(final InputStream in, final OutputStream out, final int m) { - this.input = in; - this.output = out; - this.max = m; - } - - public static void main(final String... args) { - new Main(System.in, System.out, 5).exec(); - } - - public void exec() { - String word = WORDS[new Random().nextInt(WORDS.length)]; - boolean[] visible = new boolean[word.length()]; - int mistakes = 0; - try (final PrintStream out = new PrintStream(this.output)) { - final Iterator scanner = new Scanner(this.input); - boolean done = true; - while (mistakes < this.max) { - done = true; - for (int i = 0; i < word.length(); ++i) { - if (!visible[i]) { - done = false; - } - } - if (done) { - break; - } - out.print("Guess a letter: "); - char chr = scanner.next().charAt(0); - boolean hit = false; - for (int i = 0; i < word.length(); ++i) { - if (word.charAt(i) == chr && !visible[i]) { - visible[i] = true; - hit = true; - } - } - if (hit) { - out.print("Hit!\n"); - } else { - out.printf( - "Missed, mistake #%d out of %d\n", - mistakes + 1, this.max - ); - ++mistakes; - } - out.append("The word: "); - for (int i = 0; i < word.length(); ++i) { - if (visible[i]) { - out.print(word.charAt(i)); - } else { - out.print("?"); - } - } - out.append("\n\n"); - } - if (done) { - out.print("You won!\n"); - } else { - out.print("You lost.\n"); - } - } - } - -} diff --git a/src/main/java/hangman/Secret.java b/src/main/java/hangman/Secret.java new file mode 100644 index 0000000..13ace8e --- /dev/null +++ b/src/main/java/hangman/Secret.java @@ -0,0 +1,73 @@ +package hangman; + +import common.Messages; +import common.Recipient; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public final class Secret implements Runnable +{ + private final Messages _messages; + private final Recipient _start; + private final Recipient _guessed; + + private String _word; + private List _discovered; + + public Secret(Messages messages) + { + _messages = messages; + _start = x -> startGame(x); + _guessed = x -> guess(x); + } + + @Override + public void run() + { + _messages.subcribeTo("GameStarted", _start); + _messages.subcribeTo("Guessed", _guessed); + } + + private void startGame(String result) + { + _word = result; + _discovered = _word.chars().mapToObj(x -> false).collect(Collectors.toList()); + _messages.send("ShowUser", publicWord()); + _messages.send("GameSetup", "true"); + } + + private void guess(String guess) + { + String result = guessedCorrectly(guess); + _messages.send("ShowUser", publicWord()); + _messages.send("GuessedCorrectly", result); + checkForGameEnd(); + } + + private void checkForGameEnd() + { + if (_discovered.stream().allMatch(x -> x)) + _messages.send("GameEnded", "won"); + } + + private String publicWord() + { + return "The word: " + String.join("", IntStream.range(0, _word.length()) + .mapToObj(i -> _discovered.get(i) ? _word.substring(i, i + 1) : "?") + .collect(Collectors.toList())) + "\n"; + } + + private String guessedCorrectly(String guess) + { + String result = "false"; + for (int i = 0; i < _word.length(); ++i) { + if (_word.charAt(i) == guess.charAt(0) && !_discovered.get(i)) { + _discovered.set(i, true); + result = "true"; + } + } + return result; + } +} diff --git a/src/main/java/hangman/UserInterface.java b/src/main/java/hangman/UserInterface.java new file mode 100644 index 0000000..8892d9a --- /dev/null +++ b/src/main/java/hangman/UserInterface.java @@ -0,0 +1,66 @@ +package hangman; + +import common.Media; +import common.Messages; +import common.Recipient; + +import java.util.Scanner; + +public final class UserInterface implements Runnable +{ + private final Media _media; + private final Scanner _in; + private final Recipient _end; + private final Recipient _start; + private final Recipient _guessed; + private final Recipient _showUser; + private final Messages _messages; + + private boolean _gameOver; + + public UserInterface(Messages messages, Media media, Scanner in) + { + _in = in; + _media = media; + _messages = messages; + _start = x -> playGame(x); + _end = x -> gameEnded(x); + _guessed = x -> guessed(x); + _showUser = x -> showUser(x); + } + + @Override + public void run() + { + _messages.subcribeTo("GameEnded", _end); + _messages.subcribeTo("GameSetup", _start); + _messages.subcribeTo("GuessedCorrectly", _guessed); + _messages.subcribeTo("ShowUser", _showUser); + } + + private void playGame(String result) + { + _gameOver = false; + while(!_gameOver) + { + _media.print("Guess a letter: "); + _messages.send("Guessed", _in.next().charAt(0) + ""); + } + } + + private void gameEnded(String result) + { + _gameOver = true; + _media.print(result.equals("won") ? "You won!\n" : "You lost.\n"); + } + + private void guessed(String correctly) + { + _media.print(correctly.equals("true") ? "Hit!\n\n" : "Missed.\n\n"); + } + + private void showUser(String message) + { + _media.print(message); + } +} diff --git a/src/main/java/hangman/WordList.java b/src/main/java/hangman/WordList.java new file mode 100644 index 0000000..f74370f --- /dev/null +++ b/src/main/java/hangman/WordList.java @@ -0,0 +1,42 @@ +package hangman; + +import java.util.Random; + +public final class WordList +{ + private final String[] _words; + + public WordList() + { + this("simplicity", "equality", "grandmother", "neighborhood", "relationship", "mathematics", "university", "explanation", "adult", + "aeroplane","air","aircraft", "carrier","airforce","airport","album","alphabet","apple","arm","army","baby","baby","backpack", + "balloon","banana","bank","barbecue","bathroom","bathtub","bed","bed","bee","bible","bible","bird","bomb","book","boss", + "bottle","bowl","box","boy","brain","bridge","butterfly","button","cappuccino","car","car-race","carpet","carrot","cave", + "chair","chess", "board","chief","child","chisel","chocolates","church","church","circle","circus","circus","clock","clown", + "coffee","coffee-shop","comet","compact", "disc","compass","computer","crystal","cup","cycle","data", "base","desk","diamond", + "dress","drill","drink","drum","dung","ears","earth","egg","electricity","elephant","eraser","explosive","eyes","family", + "fan","feather","festival","film","finger","fire","floodlight","flower","foot","fork","freeway","fruit","fungus","game", + "garden","gas","gate","gemstone","girl","gloves","god","grapes","guitar","hammer","hat","hieroglyph","highway","horoscope", + "horse","hose","ice","ice-cream","insect","jet", "fighter","junk","kaleidoscope","kitchen","knife","leather", "jacket","leg", + "library","liquid","magnet","man","map","maze","meat","meteor","microscope","milk","milkshake","mist","money","monster", + "mosquito","mouth","nail","navy","necklace","needle","onion","paintbrush","pants","parachute","passport","pebble","pendulum", + "pepper","perfume","pillow","plane","planet","pocket","post-office","potato","printer","prison","pyramid","radar","rainbow", + "record","restaurant","rifle","ring","robot","rock","rocket","roof","room","rope","saddle","salt","sandpaper","sandwich", + "satellite","school","sex","ship","shoes","shop","shower","signature","skeleton","slave","snail","software","solid","space", + "shuttle","spectrum","sphere","spice","spiral","spoon","sports-car","spot", "light","square","staircase","star","stomach","sun", + "sunglasses","surveyor","swimming", "pool","sword","table","tapestry","teeth","telescope","television","tennis","racquet", + "thermometer","tiger","toilet","tongue","torch","torpedo","train","treadmill","triangle","tunnel","typewriter","umbrella", + "vacuum","vampire","videotape","vulture","water","weapon","web","wheelchair","window","woman","worm"); + } + + public WordList(String... words) + { + _words = words; + } + + public String random() + { + return _words[new Random().nextInt(_words.length)]; + } +} + diff --git a/src/test/java/hangman/MainTest.java b/src/test/java/hangman/MainTest.java index d0cc008..5ac776c 100644 --- a/src/test/java/hangman/MainTest.java +++ b/src/test/java/hangman/MainTest.java @@ -25,10 +25,10 @@ public final class MainTest { @Test public void failsAfterManyWrongAttempts() throws Exception { final ByteArrayInputStream input = new ByteArrayInputStream( - "a\na\na\na\na\n".getBytes() + "a\na\na\na\na\na".getBytes() ); final ByteArrayOutputStream output = new ByteArrayOutputStream(); - new Main(input, output, 1).exec(); + new Game(input, output, 1).run(); assertThat(output.toString(), containsString("You lost")); } From 0c03080790587368a2a289947077f0888ca807ad Mon Sep 17 00:00:00 2001 From: Silas Reinagel Date: Sun, 28 May 2017 12:38:11 -0700 Subject: [PATCH 2/2] UserInferface is now responsible for the decision of when and how to display the word and the gallows. Other minor improvements --- src/main/java/hangman/Gallows.java | 14 +++++------ src/main/java/hangman/Game.java | 13 +++++----- src/main/java/hangman/MistakeMax.java | 16 +++++++++++++ src/main/java/hangman/Secret.java | 15 ++++-------- src/main/java/hangman/UserInterface.java | 30 +++++++++++++----------- src/test/java/hangman/MainTest.java | 4 ++-- 6 files changed, 52 insertions(+), 40 deletions(-) create mode 100644 src/main/java/hangman/MistakeMax.java diff --git a/src/main/java/hangman/Gallows.java b/src/main/java/hangman/Gallows.java index cf0436b..8cac2e0 100644 --- a/src/main/java/hangman/Gallows.java +++ b/src/main/java/hangman/Gallows.java @@ -1,22 +1,19 @@ package hangman; import common.Messages; -import common.Recipient; import java.util.concurrent.atomic.AtomicInteger; public final class Gallows implements Runnable { private final Messages _messages; - private final Recipient _guessed; private AtomicInteger _mistakes; - private int _max; + private MistakeMax _max; - public Gallows(Messages messages, int max) + public Gallows(Messages messages, MistakeMax max) { _messages = messages; - _guessed = x -> guessed(x); _mistakes = new AtomicInteger(); _max = max; } @@ -24,19 +21,20 @@ public Gallows(Messages messages, int max) @Override public void run() { - _messages.subcribeTo("GuessedCorrectly", _guessed); + _messages.subcribeTo("GuessedCorrectly", x -> guessed(x)); } private void guessed(String correctly) { if(!correctly.equals("true")) - _messages.send("ShowUser", String.format("Mistake #%d out of %d\n", _mistakes.incrementAndGet(), _max)); + _messages.send("GallowsUpdated", String.format("Mistakes: #%d out of %d\n", + _mistakes.incrementAndGet(), _max.intValue())); checkForGameEnd(); } private void checkForGameEnd() { - if (_mistakes.get() == _max) + if (_mistakes.get() == _max.intValue()) _messages.send("GameEnded", "lost"); } } diff --git a/src/main/java/hangman/Game.java b/src/main/java/hangman/Game.java index 805c8b4..287f510 100644 --- a/src/main/java/hangman/Game.java +++ b/src/main/java/hangman/Game.java @@ -10,7 +10,7 @@ import java.util.List; import java.util.Scanner; -public class Game implements Runnable +public final class Game implements Runnable { private final List _objects; private final WordList _words; @@ -18,17 +18,18 @@ public class Game implements Runnable public static void main(String[] args) { - new Game(System.in, System.out, 5).run(); + new Game(System.in, System.out, new MistakeMax(5)).run(); } - public Game( InputStream in, OutputStream out, int maxMistakes) + public Game(InputStream in, OutputStream out, MistakeMax max) { - this(new Messages(), new WordList(), new PrintStreamMedia(out), new Scanner(in), maxMistakes); + this(new Messages(), new WordList(), new PrintStreamMedia(out), new Scanner(in), max); } - public Game(Messages messages, WordList words, Media media, Scanner in, int maxMistakes) + public Game(Messages messages, WordList words, Media media, Scanner in, MistakeMax max) { - this(messages, words, new UserInterface(messages, media, in), new Secret(messages), new Gallows(messages, maxMistakes)); + this(messages, words, new UserInterface(messages, media, in), new Secret(messages), + new Gallows(messages, max)); } private Game(Messages messages, WordList words, Runnable... objects) diff --git a/src/main/java/hangman/MistakeMax.java b/src/main/java/hangman/MistakeMax.java new file mode 100644 index 0000000..42636a7 --- /dev/null +++ b/src/main/java/hangman/MistakeMax.java @@ -0,0 +1,16 @@ +package hangman; + +public final class MistakeMax +{ + private final int _max; + + public MistakeMax(int max) + { + _max = max; + } + + public int intValue() + { + return _max; + } +} diff --git a/src/main/java/hangman/Secret.java b/src/main/java/hangman/Secret.java index 13ace8e..e67f76a 100644 --- a/src/main/java/hangman/Secret.java +++ b/src/main/java/hangman/Secret.java @@ -1,7 +1,6 @@ package hangman; import common.Messages; -import common.Recipient; import java.util.List; import java.util.stream.Collectors; @@ -10,8 +9,6 @@ public final class Secret implements Runnable { private final Messages _messages; - private final Recipient _start; - private final Recipient _guessed; private String _word; private List _discovered; @@ -19,29 +16,27 @@ public final class Secret implements Runnable public Secret(Messages messages) { _messages = messages; - _start = x -> startGame(x); - _guessed = x -> guess(x); } @Override public void run() { - _messages.subcribeTo("GameStarted", _start); - _messages.subcribeTo("Guessed", _guessed); + _messages.subcribeTo("GameStarted", x -> startGame(x)); + _messages.subcribeTo("Guessed", x -> guess(x)); } private void startGame(String result) { _word = result; _discovered = _word.chars().mapToObj(x -> false).collect(Collectors.toList()); - _messages.send("ShowUser", publicWord()); + _messages.send("WordUpdated", publicWord()); _messages.send("GameSetup", "true"); } private void guess(String guess) { String result = guessedCorrectly(guess); - _messages.send("ShowUser", publicWord()); + _messages.send("WordUpdated", publicWord()); _messages.send("GuessedCorrectly", result); checkForGameEnd(); } @@ -54,7 +49,7 @@ private void checkForGameEnd() private String publicWord() { - return "The word: " + String.join("", IntStream.range(0, _word.length()) + return String.join("", IntStream.range(0, _word.length()) .mapToObj(i -> _discovered.get(i) ? _word.substring(i, i + 1) : "?") .collect(Collectors.toList())) + "\n"; } diff --git a/src/main/java/hangman/UserInterface.java b/src/main/java/hangman/UserInterface.java index 8892d9a..f1d224e 100644 --- a/src/main/java/hangman/UserInterface.java +++ b/src/main/java/hangman/UserInterface.java @@ -10,12 +10,10 @@ public final class UserInterface implements Runnable { private final Media _media; private final Scanner _in; - private final Recipient _end; - private final Recipient _start; - private final Recipient _guessed; - private final Recipient _showUser; private final Messages _messages; + private String _gallows = ""; + private String _word = ""; private boolean _gameOver; public UserInterface(Messages messages, Media media, Scanner in) @@ -23,19 +21,16 @@ public UserInterface(Messages messages, Media media, Scanner in) _in = in; _media = media; _messages = messages; - _start = x -> playGame(x); - _end = x -> gameEnded(x); - _guessed = x -> guessed(x); - _showUser = x -> showUser(x); } @Override public void run() { - _messages.subcribeTo("GameEnded", _end); - _messages.subcribeTo("GameSetup", _start); - _messages.subcribeTo("GuessedCorrectly", _guessed); - _messages.subcribeTo("ShowUser", _showUser); + _messages.subcribeTo("GameSetup", x -> playGame(x)); + _messages.subcribeTo("GameEnded", x -> gameEnded(x)); + _messages.subcribeTo("GuessedCorrectly", x -> guessed(x)); + _messages.subcribeTo("WordUpdated", x -> wordUpdated(x)); + _messages.subcribeTo("GallowsUpdated", x -> gallowsUpdated(x)); } private void playGame(String result) @@ -43,8 +38,10 @@ private void playGame(String result) _gameOver = false; while(!_gameOver) { + _media.print("The word is: " + _word); _media.print("Guess a letter: "); _messages.send("Guessed", _in.next().charAt(0) + ""); + _media.print(_gallows); } } @@ -59,8 +56,13 @@ private void guessed(String correctly) _media.print(correctly.equals("true") ? "Hit!\n\n" : "Missed.\n\n"); } - private void showUser(String message) + private void wordUpdated(String word) { - _media.print(message); + _word = word; + } + + private void gallowsUpdated(String gallows) + { + _gallows = gallows; } } diff --git a/src/test/java/hangman/MainTest.java b/src/test/java/hangman/MainTest.java index 5ac776c..93786a8 100644 --- a/src/test/java/hangman/MainTest.java +++ b/src/test/java/hangman/MainTest.java @@ -25,10 +25,10 @@ public final class MainTest { @Test public void failsAfterManyWrongAttempts() throws Exception { final ByteArrayInputStream input = new ByteArrayInputStream( - "a\na\na\na\na\na".getBytes() + "a\na\na\na\na\n".getBytes() ); final ByteArrayOutputStream output = new ByteArrayOutputStream(); - new Game(input, output, 1).run(); + new Game(input, output, new MistakeMax(1)).run(); assertThat(output.toString(), containsString("You lost")); }