From c2b84a56883cfca5eb4e663e4f5dbad98a1ba3da Mon Sep 17 00:00:00 2001 From: junming403 Date: Wed, 27 Feb 2019 00:15:37 +0800 Subject: [PATCH 1/8] Add config files for exercise hangman --- config.json | 39 ++++++++++++++++++++----------- exercises/hangman/example.py | 0 exercises/hangman/hangman.py | 0 exercises/hangman/hangman_test.py | 0 4 files changed, 25 insertions(+), 14 deletions(-) create mode 100644 exercises/hangman/example.py create mode 100644 exercises/hangman/hangman.py create mode 100644 exercises/hangman/hangman_test.py diff --git a/config.json b/config.json index 14c12f0d0f..75937e3960 100644 --- a/config.json +++ b/config.json @@ -43,13 +43,13 @@ ] }, { - "uuid": "574d6323-5ff5-4019-9ebe-0067daafba13", "slug": "high-scores", + "uuid": "574d6323-5ff5-4019-9ebe-0067daafba13", "core": true, "unlocked_by": null, "difficulty": 1, "topics": [ - "control-flow (if-else statements)", + "control_flow_if_else_statements", "sequences", "text_formatting" ] @@ -351,7 +351,6 @@ ] }, { - "uuid": "505e7bdb-e18d-45fd-9849-0bf33492efd9", "slug": "run-length-encoding", "uuid": "505e7bdb-e18d-45fd-9849-0bf33492efd9", "core": false, @@ -1332,6 +1331,18 @@ "trees" ] }, + { + "slug": "hangman", + "uuid": "adad6be5-855d-4d61-b14a-22e468ba5b44", + "core": false, + "unlocked_by": null, + "difficulty": 5, + "topics": [ + "events", + "reactive_programming", + "strings" + ] + }, { "slug": "custom-set", "uuid": "bb07c236-062c-2980-483a-a221e4724445dcd6f32", @@ -1402,15 +1413,15 @@ ] }, { - "slug": "dnd-character", - "uuid": "58625685-b5cf-4e8a-b3aa-bff54da0689d", - "core": false, - "unlocked_by": null, - "difficulty": 1, - "topics": [ - "randomness", - "integers" - ] + "slug": "dnd-character", + "uuid": "58625685-b5cf-4e8a-b3aa-bff54da0689d", + "core": false, + "unlocked_by": null, + "difficulty": 1, + "topics": [ + "integers", + "randomness" + ] }, { "slug": "retree", @@ -1432,8 +1443,8 @@ "unlocked_by": null, "difficulty": 2, "topics": [ - "conditionals", - "math" + "conditionals", + "math" ] }, { diff --git a/exercises/hangman/example.py b/exercises/hangman/example.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/exercises/hangman/hangman.py b/exercises/hangman/hangman.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/exercises/hangman/hangman_test.py b/exercises/hangman/hangman_test.py new file mode 100644 index 0000000000..e69de29bb2 From 16331f259776806082e020582cb4c347a15eba57 Mon Sep 17 00:00:00 2001 From: junming403 Date: Wed, 27 Feb 2019 00:16:22 +0800 Subject: [PATCH 2/8] Add README.md for exercise hangman --- exercises/hangman/README.md | 52 +++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 exercises/hangman/README.md diff --git a/exercises/hangman/README.md b/exercises/hangman/README.md new file mode 100644 index 0000000000..ded7937edb --- /dev/null +++ b/exercises/hangman/README.md @@ -0,0 +1,52 @@ +# Hangman + +Implement the logic of the hangman game using functional reactive programming. + +[Hangman][] is a simple word guessing game. + +[Hangman]: https://en.wikipedia.org/wiki/Hangman_%28game%29 + +## Exception messages + +Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to +indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not +every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include +a message. + +To raise a message with an exception, just write it as an argument to the exception type. For example, instead of +`raise Exception`, you should write: + +```python +raise Exception("Meaningful message indicating the source of the error") +``` + +## Running the tests + +To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)): + +- Python 2.7: `py.test hangman_test.py` +- Python 3.4+: `pytest hangman_test.py` + +Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version): +`python -m pytest hangman_test.py` + +### Common `pytest` options + +- `-v` : enable verbose output +- `-x` : stop running tests on first failure +- `--ff` : run failures from previous test before running other test cases + +For other options, see `python -m pytest -h` + +## Submitting Exercises + +Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/hangman` directory. + +You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`. + +For more detailed information about running tests, code style and linting, +please see [Running the Tests](http://exercism.io/tracks/python/tests). + +## Submitting Incomplete Solutions + +It's possible to submit an incomplete solution so you can see how others have completed the exercise. From c85fefbc44dea7cd7dd0c30c2b4cb82c0c775104 Mon Sep 17 00:00:00 2001 From: junming403 Date: Wed, 27 Feb 2019 00:16:54 +0800 Subject: [PATCH 3/8] Add sample solution and test cases for exercise hangman --- exercises/hangman/example.py | 50 ++++++++++++++++ exercises/hangman/hangman_test.py | 95 +++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) diff --git a/exercises/hangman/example.py b/exercises/hangman/example.py index e69de29bb2..885cdaa02c 100644 --- a/exercises/hangman/example.py +++ b/exercises/hangman/example.py @@ -0,0 +1,50 @@ +STATUS_WIN = "win" +STATUS_LOSE = "lose" +STATUS_ONGOING = "ongoing" + + +class Hangman: + def __init__(self, word): + self.remainingGuesses = 9 + self.status = STATUS_ONGOING + self.word = word + self.masked_word = '' + self.guesses = [] + for i in self.word: + self.masked_word += '_' + + def guess(self, char): + if self.status != STATUS_ONGOING: + raise Exception("Game already ended, you " + self.status) + + self.update_remaining_guesses(char) + self.update_masked_word() + self.update_status() + + def update_masked_word(self): + self.masked_word = '' + for i in self.word: + if i not in self.guesses: + self.masked_word += '_' + else: + self.masked_word += i + + def update_remaining_guesses(self, char): + if char not in self.word or char in self.guesses: + self.remainingGuesses -= 1 + else: + self.guesses.append(char) + + def update_status(self): + if self.masked_word == self.word: + self.status = STATUS_WIN + elif self.remainingGuesses < 0: + self.status = STATUS_LOSE + else: + self.status = STATUS_ONGOING + + def get_masked_word(self): + return self.masked_word + + def get_status(self): + return self.status diff --git a/exercises/hangman/hangman_test.py b/exercises/hangman/hangman_test.py index e69de29bb2..55affbc36a 100644 --- a/exercises/hangman/hangman_test.py +++ b/exercises/hangman/hangman_test.py @@ -0,0 +1,95 @@ +import unittest + +from hangman import Hangman + + +# Tests adapted from csharp//hangman/HangmanTest.cs + +class HangmanTests(unittest.TestCase): + def test_initially_9_failures_are_allowed(self): + game = Hangman('foo') + self.assertEqual(game.get_status(), "ongoing") + self.assertEqual(game.remainingGuesses, 9) + + def test_initially_no_letters_are_guessed(self): + game = Hangman('foo') + + self.assertEqual(game.get_masked_word(), '___') + + def test_after_10_failures_the_game_is_over(self): + game = Hangman('foo') + + for i in range(10): + game.guess('x') + + self.assertEqual(game.get_status(), 'lose') + try: + game.guess('x') + except Exception as e: + self.assertEquals(e.message, "Game already ended, you lose") + + def test_feeding_a_correct_letter_removes_underscores(self): + game = Hangman('foobar') + + game.guess('b') + self.assertEqual(game.get_status(), 'ongoing') + self.assertEqual(game.remainingGuesses, 9) + self.assertEqual(game.get_masked_word(), '___b__') + + game.guess('o') + self.assertEqual(game.get_status(), 'ongoing') + self.assertEqual(game.remainingGuesses, 9) + self.assertEqual(game.get_masked_word(), '_oob__') + + def test_feeding_a_correct_letter_twice_counts_as_a_failure(self): + game = Hangman('foobar') + + game.guess('b') + self.assertEqual(game.get_status(), 'ongoing') + self.assertEqual(game.remainingGuesses, 9) + self.assertEqual(game.get_masked_word(), '___b__') + + game.guess('b') + self.assertEqual(game.get_status(), 'ongoing') + self.assertEqual(game.remainingGuesses, 8) + self.assertEqual(game.get_masked_word(), '___b__') + + def test_getting_all_the_letters_right_makes_for_a_win(self): + game = Hangman('hello') + + game.guess('b') + self.assertEqual(game.get_status(), 'ongoing') + self.assertEqual(game.get_status(), 'ongoing') + self.assertEqual(game.remainingGuesses, 8) + self.assertEqual(game.get_masked_word(), '_____') + + game.guess('e') + self.assertEqual(game.get_status(), 'ongoing') + self.assertEqual(game.remainingGuesses, 8) + self.assertEqual(game.get_masked_word(), '_e___') + + game.guess('l') + self.assertEqual(game.get_status(), 'ongoing') + self.assertEqual(game.remainingGuesses, 8) + self.assertEqual(game.get_masked_word(), '_ell_') + + game.guess('o') + self.assertEqual(game.get_status(), 'ongoing') + self.assertEqual(game.remainingGuesses, 8) + self.assertEqual(game.get_masked_word(), '_ello') + + game.guess('h') + self.assertEqual(game.get_status(), 'win') + self.assertEqual(game.get_masked_word(), 'hello') + + try: + game.guess('x') + except Exception as e: + self.assertEquals(e.message, "Game already ended, you win") + + def assertRaisesWithMessage(self, exception, message): + self.assertRaisesRegexp(exception, message) + + +if __name__ == '__main__': + unittest.main() From 929db66e6144e4da0c18785ccd5ded89642ae47a Mon Sep 17 00:00:00 2001 From: junming403 Date: Wed, 27 Feb 2019 00:17:47 +0800 Subject: [PATCH 4/8] Add skeleton code for exercise hangman --- exercises/hangman/hangman.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/exercises/hangman/hangman.py b/exercises/hangman/hangman.py index e69de29bb2..4c316f0b70 100644 --- a/exercises/hangman/hangman.py +++ b/exercises/hangman/hangman.py @@ -0,0 +1,13 @@ +class Hangman(object): + def __init__(self, word): + self.remainingGuesses = 9 + self.status = 'busy' + + def guess(self, char): + pass + + def get_masked_word(self): + pass + + def get_status(self): + pass From dd57cbd1da6f6ca7b99c8f0895bb7ad3d5f7b727 Mon Sep 17 00:00:00 2001 From: junming403 Date: Wed, 27 Feb 2019 00:37:20 +0800 Subject: [PATCH 5/8] fix consistency issue in python3 --- exercises/hangman/hangman_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/hangman/hangman_test.py b/exercises/hangman/hangman_test.py index 55affbc36a..e5b5da7842 100644 --- a/exercises/hangman/hangman_test.py +++ b/exercises/hangman/hangman_test.py @@ -26,7 +26,7 @@ def test_after_10_failures_the_game_is_over(self): try: game.guess('x') except Exception as e: - self.assertEquals(e.message, "Game already ended, you lose") + self.assertEquals((str(e)), "Game already ended, you lose") def test_feeding_a_correct_letter_removes_underscores(self): game = Hangman('foobar') @@ -85,7 +85,7 @@ def test_getting_all_the_letters_right_makes_for_a_win(self): try: game.guess('x') except Exception as e: - self.assertEquals(e.message, "Game already ended, you win") + self.assertEquals(str(e), "Game already ended, you win") def assertRaisesWithMessage(self, exception, message): self.assertRaisesRegexp(exception, message) From 79ea4eb6ed067a0114cf366471dff75556d95147 Mon Sep 17 00:00:00 2001 From: junming403 Date: Wed, 27 Feb 2019 11:11:50 +0800 Subject: [PATCH 6/8] fixes according to Review --- exercises/hangman/.meta/hints.md | 3 +++ exercises/hangman/README.md | 16 +++++++++++ exercises/hangman/example.py | 2 +- exercises/hangman/hangman.py | 9 ++++++- exercises/hangman/hangman_test.py | 44 +++++++++++++++++-------------- 5 files changed, 52 insertions(+), 22 deletions(-) create mode 100644 exercises/hangman/.meta/hints.md diff --git a/exercises/hangman/.meta/hints.md b/exercises/hangman/.meta/hints.md new file mode 100644 index 0000000000..4a76cf360f --- /dev/null +++ b/exercises/hangman/.meta/hints.md @@ -0,0 +1,3 @@ +## Hints + +Please ignore the part regarding FRP library, a third party library is not required for this exercise. \ No newline at end of file diff --git a/exercises/hangman/README.md b/exercises/hangman/README.md index ded7937edb..225f7a7a57 100644 --- a/exercises/hangman/README.md +++ b/exercises/hangman/README.md @@ -4,7 +4,23 @@ Implement the logic of the hangman game using functional reactive programming. [Hangman][] is a simple word guessing game. +[Functional Reactive Programming][frp] is a way to write interactive +programs. It differs from the usual perspective in that instead of +saying "when the button is pressed increment the counter", you write +"the value of the counter is the sum of the number of times the button +is pressed." + +Implement the basic logic behind hangman using functional reactive +programming. You'll need to install an FRP library for this, this will +be described in the language/track specific files of the exercise. + [Hangman]: https://en.wikipedia.org/wiki/Hangman_%28game%29 +[frp]: https://en.wikipedia.org/wiki/Functional_reactive_programming + +## Hints + +Please ignore the part regarding FRP library, a third party library is not required for this exercise. + ## Exception messages diff --git a/exercises/hangman/example.py b/exercises/hangman/example.py index 885cdaa02c..82f4db699d 100644 --- a/exercises/hangman/example.py +++ b/exercises/hangman/example.py @@ -15,7 +15,7 @@ def __init__(self, word): def guess(self, char): if self.status != STATUS_ONGOING: - raise Exception("Game already ended, you " + self.status) + raise ValueError("Game already ended, you " + self.status) self.update_remaining_guesses(char) self.update_masked_word() diff --git a/exercises/hangman/hangman.py b/exercises/hangman/hangman.py index 4c316f0b70..1fc24eb6b9 100644 --- a/exercises/hangman/hangman.py +++ b/exercises/hangman/hangman.py @@ -1,7 +1,14 @@ +# Game status categories +# Change the values as you see fit +STATUS_WIN = None +STATUS_LOSE = None +STATUS_ONGOING = None + + class Hangman(object): def __init__(self, word): self.remainingGuesses = 9 - self.status = 'busy' + self.status = STATUS_ONGOING def guess(self, char): pass diff --git a/exercises/hangman/hangman_test.py b/exercises/hangman/hangman_test.py index e5b5da7842..9e5e4a4d43 100644 --- a/exercises/hangman/hangman_test.py +++ b/exercises/hangman/hangman_test.py @@ -1,5 +1,6 @@ import unittest +import hangman from hangman import Hangman @@ -8,7 +9,7 @@ class HangmanTests(unittest.TestCase): def test_initially_9_failures_are_allowed(self): game = Hangman('foo') - self.assertEqual(game.get_status(), "ongoing") + self.assertEqual(game.get_status(), hangman.STATUS_ONGOING) self.assertEqual(game.remainingGuesses, 9) def test_initially_no_letters_are_guessed(self): @@ -22,22 +23,20 @@ def test_after_10_failures_the_game_is_over(self): for i in range(10): game.guess('x') - self.assertEqual(game.get_status(), 'lose') - try: + self.assertEqual(game.get_status(), hangman.STATUS_LOSE) + with self.assertRaisesWithMessage(ValueError): game.guess('x') - except Exception as e: - self.assertEquals((str(e)), "Game already ended, you lose") def test_feeding_a_correct_letter_removes_underscores(self): game = Hangman('foobar') game.guess('b') - self.assertEqual(game.get_status(), 'ongoing') + self.assertEqual(game.get_status(), hangman.STATUS_ONGOING) self.assertEqual(game.remainingGuesses, 9) self.assertEqual(game.get_masked_word(), '___b__') game.guess('o') - self.assertEqual(game.get_status(), 'ongoing') + self.assertEqual(game.get_status(), hangman.STATUS_ONGOING) self.assertEqual(game.remainingGuesses, 9) self.assertEqual(game.get_masked_word(), '_oob__') @@ -45,12 +44,12 @@ def test_feeding_a_correct_letter_twice_counts_as_a_failure(self): game = Hangman('foobar') game.guess('b') - self.assertEqual(game.get_status(), 'ongoing') + self.assertEqual(game.get_status(), hangman.STATUS_ONGOING) self.assertEqual(game.remainingGuesses, 9) self.assertEqual(game.get_masked_word(), '___b__') game.guess('b') - self.assertEqual(game.get_status(), 'ongoing') + self.assertEqual(game.get_status(), hangman.STATUS_ONGOING) self.assertEqual(game.remainingGuesses, 8) self.assertEqual(game.get_masked_word(), '___b__') @@ -58,37 +57,42 @@ def test_getting_all_the_letters_right_makes_for_a_win(self): game = Hangman('hello') game.guess('b') - self.assertEqual(game.get_status(), 'ongoing') - self.assertEqual(game.get_status(), 'ongoing') + self.assertEqual(game.get_status(), hangman.STATUS_ONGOING) + self.assertEqual(game.get_status(), hangman.STATUS_ONGOING) self.assertEqual(game.remainingGuesses, 8) self.assertEqual(game.get_masked_word(), '_____') game.guess('e') - self.assertEqual(game.get_status(), 'ongoing') + self.assertEqual(game.get_status(), hangman.STATUS_ONGOING) self.assertEqual(game.remainingGuesses, 8) self.assertEqual(game.get_masked_word(), '_e___') game.guess('l') - self.assertEqual(game.get_status(), 'ongoing') + self.assertEqual(game.get_status(), hangman.STATUS_ONGOING) self.assertEqual(game.remainingGuesses, 8) self.assertEqual(game.get_masked_word(), '_ell_') game.guess('o') - self.assertEqual(game.get_status(), 'ongoing') + self.assertEqual(game.get_status(), hangman.STATUS_ONGOING) self.assertEqual(game.remainingGuesses, 8) self.assertEqual(game.get_masked_word(), '_ello') game.guess('h') - self.assertEqual(game.get_status(), 'win') + self.assertEqual(game.get_status(), hangman.STATUS_WIN) self.assertEqual(game.get_masked_word(), 'hello') - try: + with self.assertRaisesWithMessage(ValueError): game.guess('x') - except Exception as e: - self.assertEquals(str(e), "Game already ended, you win") - def assertRaisesWithMessage(self, exception, message): - self.assertRaisesRegexp(exception, message) + # Utility functions + def setUp(self): + try: + self.assertRaisesRegex + except AttributeError: + self.assertRaisesRegex = self.assertRaisesRegexp + + def assertRaisesWithMessage(self, exception): + return self.assertRaisesRegex(exception, r".+") if __name__ == '__main__': From 75ec5d3843981109e2f7903c2c690e0fdc380f65 Mon Sep 17 00:00:00 2001 From: junming403 Date: Fri, 1 Mar 2019 15:42:25 +0800 Subject: [PATCH 7/8] fixes according to review --- exercises/hangman/hangman.py | 6 +++--- exercises/hangman/hangman_test.py | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/exercises/hangman/hangman.py b/exercises/hangman/hangman.py index 1fc24eb6b9..013cd6593c 100644 --- a/exercises/hangman/hangman.py +++ b/exercises/hangman/hangman.py @@ -1,8 +1,8 @@ # Game status categories # Change the values as you see fit -STATUS_WIN = None -STATUS_LOSE = None -STATUS_ONGOING = None +STATUS_WIN = "win" +STATUS_LOSE = "lose" +STATUS_ONGOING = "ongoing" class Hangman(object): diff --git a/exercises/hangman/hangman_test.py b/exercises/hangman/hangman_test.py index 9e5e4a4d43..26cf25ce84 100644 --- a/exercises/hangman/hangman_test.py +++ b/exercises/hangman/hangman_test.py @@ -58,7 +58,6 @@ def test_getting_all_the_letters_right_makes_for_a_win(self): game.guess('b') self.assertEqual(game.get_status(), hangman.STATUS_ONGOING) - self.assertEqual(game.get_status(), hangman.STATUS_ONGOING) self.assertEqual(game.remainingGuesses, 8) self.assertEqual(game.get_masked_word(), '_____') From 4e7328ffea4da16ff5dce4168afdff626e29303a Mon Sep 17 00:00:00 2001 From: junming403 Date: Tue, 5 Mar 2019 00:08:34 +0800 Subject: [PATCH 8/8] update config.json --- config.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/config.json b/config.json index 75937e3960..3e97953979 100644 --- a/config.json +++ b/config.json @@ -1338,8 +1338,6 @@ "unlocked_by": null, "difficulty": 5, "topics": [ - "events", - "reactive_programming", "strings" ] },