patternpythonMinor
Yahtzee Code Kata in Python
Viewed 0 times
codeyahtzeekatapython
Problem
I tried a Yahtzee kata in Python the other day. I'm not particularly experienced in Python (C# is my main language) so I'd be interested in any feedback as to how I could have done things better. In particular, I wonder whether my
```
import unittest
#helpers
def Count(dice, number):
return len([y for y in dice if y == number])
def HighestRepeated(dice, minRepeats):
unique = set(dice)
repeats = [x for x in unique if Count(dice, x) >= minRepeats]
return max(repeats) if repeats else 0
def OfAKind(dice, n):
return HighestRepeated(dice,n) * n
def SumOfSingle(dice, selected):
return sum([x for x in dice if x == selected])
#strategies
def Chance(dice):
return sum(dice)
def Pair(dice):
return OfAKind(dice, 2)
def ThreeOfAKind(dice):
return OfAKind(dice, 3)
def FourOfAKind(dice):
return OfAKind(dice, 4)
def SmallStraight(dice):
return 15 if tuple(sorted(dice)) == (1,2,3,4,5) else 0
def LargeStraight(dice):
return 20 if tuple(sorted(dice)) == (2,3,4,5,6) else 0
def Ones(dice):
return SumOfSingle(dice,1)
def Twos(dice):
return SumOfSingle(dice,2)
def Threes(dice):
return SumOfSingle(dice,3)
def Fours(dice):
return SumOfSingle(dice,4)
def Fives(dice):
return SumOfSingle(dice,5)
def Sixes(dice):
return SumOfSingle(dice,6)
def Yahtzee(dice):
return 50 if len(dice) == 5 and len(set(dice)) == 1 else 0
class YahtzeeTest(unittest.TestCase):
testCases = (
((1,2,3,4,5), 1, Ones),
((1,2,3,4,5), 2, Twos),
((3,2,3,4,3), 9, Threes),
((3,2,3,4,3), 0, Sixes),
((1,2,3,4,5), 0, Pair), # no pairs found
((1,5,3,4,5), 10, Pair), # one pair found
((2,2,6,6,4), 12, Pair), # picks highest
((2,3,1,3,3), 6, Pair), # only counts two
((2,2,6,6,6), 18, ThreeOfAKind),
((2,2,4,6,6), 0, ThreeOfAKind), # no threes found
((5,5,5,5,5), 15, ThreeOfAKind), #
Count and HighestRepeated methods could be a little more elegant.```
import unittest
#helpers
def Count(dice, number):
return len([y for y in dice if y == number])
def HighestRepeated(dice, minRepeats):
unique = set(dice)
repeats = [x for x in unique if Count(dice, x) >= minRepeats]
return max(repeats) if repeats else 0
def OfAKind(dice, n):
return HighestRepeated(dice,n) * n
def SumOfSingle(dice, selected):
return sum([x for x in dice if x == selected])
#strategies
def Chance(dice):
return sum(dice)
def Pair(dice):
return OfAKind(dice, 2)
def ThreeOfAKind(dice):
return OfAKind(dice, 3)
def FourOfAKind(dice):
return OfAKind(dice, 4)
def SmallStraight(dice):
return 15 if tuple(sorted(dice)) == (1,2,3,4,5) else 0
def LargeStraight(dice):
return 20 if tuple(sorted(dice)) == (2,3,4,5,6) else 0
def Ones(dice):
return SumOfSingle(dice,1)
def Twos(dice):
return SumOfSingle(dice,2)
def Threes(dice):
return SumOfSingle(dice,3)
def Fours(dice):
return SumOfSingle(dice,4)
def Fives(dice):
return SumOfSingle(dice,5)
def Sixes(dice):
return SumOfSingle(dice,6)
def Yahtzee(dice):
return 50 if len(dice) == 5 and len(set(dice)) == 1 else 0
class YahtzeeTest(unittest.TestCase):
testCases = (
((1,2,3,4,5), 1, Ones),
((1,2,3,4,5), 2, Twos),
((3,2,3,4,3), 9, Threes),
((3,2,3,4,3), 0, Sixes),
((1,2,3,4,5), 0, Pair), # no pairs found
((1,5,3,4,5), 10, Pair), # one pair found
((2,2,6,6,4), 12, Pair), # picks highest
((2,3,1,3,3), 6, Pair), # only counts two
((2,2,6,6,6), 18, ThreeOfAKind),
((2,2,4,6,6), 0, ThreeOfAKind), # no threes found
((5,5,5,5,5), 15, ThreeOfAKind), #
Solution
import unittest
#helpers
def Count(dice, number):The python style guide recommends lowercase_with_underscores for global functions.
return len([y for y in dice if y == number])This can be written as
return dice.count(number)
def HighestRepeated(dice, minRepeats):
unique = set(dice)
repeats = [x for x in unique if Count(dice, x) >= minRepeats]
return max(repeats) if repeats else 0This will be somewhat clearer if you find the highest repeated count and then check it against minRepeats. You can also use the counter class:
counts = collections.Counter(dice)
element, count = counts.most_common()[0]
return count if minRepeats <= count else 0
def OfAKind(dice, n):
return HighestRepeated(dice,n) * n
def SumOfSingle(dice, selected):
return sum([x for x in dice if x == selected])Could be written as
return collections.Counter(dice)[selected]
#strategies
def Chance(dice):
return sum(dice)
def Pair(dice):
return OfAKind(dice, 2)
def ThreeOfAKind(dice):
return OfAKind(dice, 3)
def FourOfAKind(dice):
return OfAKind(dice, 4)
def SmallStraight(dice):
return 15 if tuple(sorted(dice)) == (1,2,3,4,5) else 0Shouldn't a small straight consist of 4 elements in a row?
def LargeStraight(dice):
return 20 if tuple(sorted(dice)) == (2,3,4,5,6) else 0Can't a large straight start with 1?
def Ones(dice):
return SumOfSingle(dice,1)
def Twos(dice):
return SumOfSingle(dice,2)
def Threes(dice):
return SumOfSingle(dice,3)
def Fours(dice):
return SumOfSingle(dice,4)
def Fives(dice):
return SumOfSingle(dice,5)
def Sixes(dice):
return SumOfSingle(dice,6)Writing so many functions that differ only in a parameter they pass to another function is odd.
def Yahtzee(dice):
return 50 if len(dice) == 5 and len(set(dice)) == 1 else 0Can't you assume that there are always 5 dice? Also, why don't you use OfAKind?
class YahtzeeTest(unittest.TestCase):
testCases = (
((1,2,3,4,5), 1, Ones),
((1,2,3,4,5), 2, Twos),
((3,2,3,4,3), 9, Threes),
((3,2,3,4,3), 0, Sixes),
((1,2,3,4,5), 0, Pair), # no pairs found
((1,5,3,4,5), 10, Pair), # one pair found
((2,2,6,6,4), 12, Pair), # picks highest
((2,3,1,3,3), 6, Pair), # only counts two
((2,2,6,6,6), 18, ThreeOfAKind),
((2,2,4,6,6), 0, ThreeOfAKind), # no threes found
((5,5,5,5,5), 15, ThreeOfAKind), # only counts three
((6,2,6,6,6), 24, FourOfAKind),
((2,6,4,6,6), 0, FourOfAKind), # no fours found
((5,5,5,5,5), 20, FourOfAKind), # only counts four
((1,2,5,4,3), 15, SmallStraight),
((1,2,5,1,3), 0, SmallStraight),
((6,2,5,4,3), 20, LargeStraight),
((1,2,5,1,3), 0, LargeStraight),
((5,5,5,5,5), 50, Yahtzee),
((1,5,5,5,5), 0, Yahtzee),
((1,2,3,4,5), 15, Chance),
)
def testRunAll(self):
for (dice, expected, strategy) in self.testCases:
score = strategy(dice)
self.assertEquals(expected, score, "got {0} expected {1}, testing with {2} on {3}".format(score, expected, strategy.__name__, dice))
print 'ran {0} test cases'.format(len(self.testCases))You are duplicating some of the logic of the unit test system here. I recommend using nose tests which would allow this one function to generate a number of tests each of which is considered an independent test by the unit testing system. Then you wouldn't need to spend so much effort.
I'd also consider writing functions for each of your tests rather then putting them in a huge list. The list technique is really nice but its best used for calling the same function. Using it to test so many different functions is odd.
if __name__ == '__main__':
unittest.main()Code Snippets
import unittest
#helpers
def Count(dice, number):return len([y for y in dice if y == number])return dice.count(number)
def HighestRepeated(dice, minRepeats):
unique = set(dice)
repeats = [x for x in unique if Count(dice, x) >= minRepeats]
return max(repeats) if repeats else 0counts = collections.Counter(dice)
element, count = counts.most_common()[0]
return count if minRepeats <= count else 0
def OfAKind(dice, n):
return HighestRepeated(dice,n) * n
def SumOfSingle(dice, selected):
return sum([x for x in dice if x == selected])return collections.Counter(dice)[selected]
#strategies
def Chance(dice):
return sum(dice)
def Pair(dice):
return OfAKind(dice, 2)
def ThreeOfAKind(dice):
return OfAKind(dice, 3)
def FourOfAKind(dice):
return OfAKind(dice, 4)
def SmallStraight(dice):
return 15 if tuple(sorted(dice)) == (1,2,3,4,5) else 0Context
StackExchange Code Review Q#3036, answer score: 8
Revisions (0)
No revisions yet.