From 39c23bc70d121a13c1533299ef333831a08ee644 Mon Sep 17 00:00:00 2001 From: Dmitry Kokorin Date: Mon, 2 Aug 2021 18:01:38 +0300 Subject: [PATCH] [WIP] Python: yacht --- python/yacht/.exercism/metadata.json | 1 + python/yacht/README.md | 84 +++++++++++++++++++++ python/yacht/yacht.py | 107 +++++++++++++++++++++++++++ python/yacht/yacht_test.py | 95 ++++++++++++++++++++++++ 4 files changed, 287 insertions(+) create mode 100644 python/yacht/.exercism/metadata.json create mode 100644 python/yacht/README.md create mode 100644 python/yacht/yacht.py create mode 100644 python/yacht/yacht_test.py diff --git a/python/yacht/.exercism/metadata.json b/python/yacht/.exercism/metadata.json new file mode 100644 index 0000000..8c87668 --- /dev/null +++ b/python/yacht/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"python","exercise":"yacht","id":"1f7f5c0525554e33943447264159e72e","url":"https://exercism.io/my/solutions/1f7f5c0525554e33943447264159e72e","handle":"DmitryKokorin","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/python/yacht/README.md b/python/yacht/README.md new file mode 100644 index 0000000..e8b857d --- /dev/null +++ b/python/yacht/README.md @@ -0,0 +1,84 @@ +# Yacht + +# Score a single throw of dice in *Yacht* + +The dice game [Yacht](https://en.wikipedia.org/wiki/Yacht_(dice_game)) is from +the same family as Poker Dice, Generala and particularly Yahtzee, of which it +is a precursor. In the game, five dice are rolled and the result can be entered +in any of twelve categories. The score of a throw of the dice depends on +category chosen. + +## Scores in Yacht + +| Category | Score | Description | Example | +| -------- | ----- | ----------- | ------- | +| Ones | 1 × number of ones | Any combination | 1 1 1 4 5 scores 3 | +| Twos | 2 × number of twos | Any combination | 2 2 3 4 5 scores 4 | +| Threes | 3 × number of threes | Any combination | 3 3 3 3 3 scores 15 | +| Fours | 4 × number of fours | Any combination | 1 2 3 3 5 scores 0 | +| Fives | 5 × number of fives| Any combination | 5 1 5 2 5 scores 15 | +| Sixes | 6 × number of sixes | Any combination | 2 3 4 5 6 scores 6 | +| Full House | Total of the dice | Three of one number and two of another | 3 3 3 5 5 scores 19 | +| Four of a Kind | Total of the four dice | At least four dice showing the same face | 4 4 4 4 6 scores 16 | +| Little Straight | 30 points | 1-2-3-4-5 | 1 2 3 4 5 scores 30 | +| Big Straight | 30 points | 2-3-4-5-6 | 2 3 4 5 6 scores 30 | +| Choice | Sum of the dice | Any combination | 2 3 3 4 6 scores 18 | +| Yacht | 50 points | All five dice showing the same face | 4 4 4 4 4 scores 50 | + +If the dice do not satisfy the requirements of a category, the score is zero. +If, for example, *Four Of A Kind* is entered in the *Yacht* category, zero +points are scored. A *Yacht* scores zero if entered in the *Full House* category. + +## Task +Given a list of values for five dice and a category, your solution should return +the score of the dice for that category. If the dice do not satisfy the requirements +of the category your solution should return 0. You can assume that five values +will always be presented, and the value of each will be between one and six +inclusively. You should not assume that the dice are ordered. + + +## 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 `pytest yacht_test.py` + +Alternatively, you can tell Python to run the pytest module: +`python -m pytest yacht_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/yacht` 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). + +## Source + +James Kilfiger, using wikipedia [https://en.wikipedia.org/wiki/Yacht_(dice_game)](https://en.wikipedia.org/wiki/Yacht_(dice_game)) + +## Submitting Incomplete Solutions + +It's possible to submit an incomplete solution so you can see how others have completed the exercise. diff --git a/python/yacht/yacht.py b/python/yacht/yacht.py new file mode 100644 index 0000000..8bb8832 --- /dev/null +++ b/python/yacht/yacht.py @@ -0,0 +1,107 @@ +""" +This exercise stub and the test suite contain several enumerated constants. + +Since Python 2 does not have the enum module, the idiomatic way to write +enumerated constants has traditionally been a NAME assigned to an arbitrary, +but unique value. An integer is traditionally used because it’s memory +efficient. +It is a common practice to export both constants and functions that work with +those constants (ex. the constants in the os, subprocess and re modules). + +You can learn more here: https://en.wikipedia.org/wiki/Enumerated_type +""" + +from collections import Counter + +# Score categories. +# Change the values as you see fit. +YACHT = 0 +ONES = 1 +TWOS = 2 +THREES = 3 +FOURS = 4 +FIVES = 5 +SIXES = 6 +FULL_HOUSE = 7 +FOUR_OF_A_KIND = 8 +LITTLE_STRAIGHT = 9 +BIG_STRAIGHT = 10 +CHOICE = 11 + + +def _yacht(dice): + return 50*all(x == dice[0] for x in dice) + + +def _ones(dice): + return Counter(dice)[1] + + +def _twos(dice): + return 2*Counter(dice)[2] + + +def _threes(dice): + return 3*Counter(dice)[3] + + +def _fours(dice): + return 4*Counter(dice)[4] + + +def _fives(dice): + return 5*Counter(dice)[5] + + +def _sixes(dice): + return 6*Counter(dice)[6] + + +def _full_house(dice): + c = Counter(dice) + + if sorted(list(c.values())) != [2, 3]: + return 0 + + return sum(k*v for k, v in c.items()) + + +def _four_of_a_kind(dice): + + c = Counter(dice) + keys = list(c.keys()) + if c[keys[0]] >= 4: + return keys[0]*4 + if c[keys[1]] >= 4: + return keys[1]*4 + return 0 + + +def _little_straight(dice): + return 30*(sorted(dice) == [1, 2, 3, 4, 5]) + + +def _big_straight(dice): + return 30*(sorted(dice) == [2, 3, 4, 5, 6]) + + +def _choice(dice): + return sum(dice) + + +_CATEGORIES = {YACHT: _yacht, + ONES: _ones, + TWOS: _twos, + THREES: _threes, + FOURS: _fours, + FIVES: _fives, + SIXES: _sixes, + FULL_HOUSE: _full_house, + FOUR_OF_A_KIND: _four_of_a_kind, + LITTLE_STRAIGHT: _little_straight, + BIG_STRAIGHT: _big_straight, + CHOICE: _choice} + + +def score(dice, category): + return _CATEGORIES[category](dice) diff --git a/python/yacht/yacht_test.py b/python/yacht/yacht_test.py new file mode 100644 index 0000000..bebcd61 --- /dev/null +++ b/python/yacht/yacht_test.py @@ -0,0 +1,95 @@ +import unittest + +import yacht + +# Tests adapted from `problem-specifications//canonical-data.json` + + +class YachtTest(unittest.TestCase): + def test_yacht(self): + self.assertEqual(yacht.score([5, 5, 5, 5, 5], yacht.YACHT), 50) + + def test_not_yacht(self): + self.assertEqual(yacht.score([1, 3, 3, 2, 5], yacht.YACHT), 0) + + def test_ones(self): + self.assertEqual(yacht.score([1, 1, 1, 3, 5], yacht.ONES), 3) + + def test_ones_out_of_order(self): + self.assertEqual(yacht.score([3, 1, 1, 5, 1], yacht.ONES), 3) + + def test_no_ones(self): + self.assertEqual(yacht.score([4, 3, 6, 5, 5], yacht.ONES), 0) + + def test_twos(self): + self.assertEqual(yacht.score([2, 3, 4, 5, 6], yacht.TWOS), 2) + + def test_fours(self): + self.assertEqual(yacht.score([1, 4, 1, 4, 1], yacht.FOURS), 8) + + def test_yacht_counted_as_threes(self): + self.assertEqual(yacht.score([3, 3, 3, 3, 3], yacht.THREES), 15) + + def test_yacht_of_3s_counted_as_fives(self): + self.assertEqual(yacht.score([3, 3, 3, 3, 3], yacht.FIVES), 0) + + def test_sixes(self): + self.assertEqual(yacht.score([2, 3, 4, 5, 6], yacht.SIXES), 6) + + def test_full_house_two_small_three_big(self): + self.assertEqual(yacht.score([2, 2, 4, 4, 4], yacht.FULL_HOUSE), 16) + + def test_full_house_three_small_two_big(self): + self.assertEqual(yacht.score([5, 3, 3, 5, 3], yacht.FULL_HOUSE), 19) + + def test_two_pair_is_not_a_full_house(self): + self.assertEqual(yacht.score([2, 2, 4, 4, 5], yacht.FULL_HOUSE), 0) + + def test_four_of_a_kind_is_not_a_full_house(self): + self.assertEqual(yacht.score([1, 4, 4, 4, 4], yacht.FULL_HOUSE), 0) + + def test_yacht_is_not_a_full_house(self): + self.assertEqual(yacht.score([2, 2, 2, 2, 2], yacht.FULL_HOUSE), 0) + + def test_four_of_a_kind(self): + self.assertEqual(yacht.score([6, 6, 4, 6, 6], yacht.FOUR_OF_A_KIND), 24) + + def test_yacht_can_be_scored_as_four_of_a_kind(self): + self.assertEqual(yacht.score([3, 3, 3, 3, 3], yacht.FOUR_OF_A_KIND), 12) + + def test_full_house_is_not_four_of_a_kind(self): + self.assertEqual(yacht.score([3, 3, 3, 5, 5], yacht.FOUR_OF_A_KIND), 0) + + def test_little_straight(self): + self.assertEqual(yacht.score([3, 5, 4, 1, 2], yacht.LITTLE_STRAIGHT), 30) + + def test_little_straight_as_big_straight(self): + self.assertEqual(yacht.score([1, 2, 3, 4, 5], yacht.BIG_STRAIGHT), 0) + + def test_four_in_order_but_not_a_little_straight(self): + self.assertEqual(yacht.score([1, 1, 2, 3, 4], yacht.LITTLE_STRAIGHT), 0) + + def test_no_pairs_but_not_a_little_straight(self): + self.assertEqual(yacht.score([1, 2, 3, 4, 6], yacht.LITTLE_STRAIGHT), 0) + + def test_minimum_is_1_maximum_is_5_but_not_a_little_straight(self): + self.assertEqual(yacht.score([1, 1, 3, 4, 5], yacht.LITTLE_STRAIGHT), 0) + + def test_big_straight(self): + self.assertEqual(yacht.score([4, 6, 2, 5, 3], yacht.BIG_STRAIGHT), 30) + + def test_big_straight_as_little_straight(self): + self.assertEqual(yacht.score([6, 5, 4, 3, 2], yacht.LITTLE_STRAIGHT), 0) + + def test_no_pairs_but_not_a_big_straight(self): + self.assertEqual(yacht.score([6, 5, 4, 3, 1], yacht.BIG_STRAIGHT), 0) + + def test_choice(self): + self.assertEqual(yacht.score([3, 3, 5, 6, 6], yacht.CHOICE), 23) + + def test_yacht_as_choice(self): + self.assertEqual(yacht.score([2, 2, 2, 2, 2], yacht.CHOICE), 10) + + +if __name__ == "__main__": + unittest.main()