Ten Thousand: a dice game for multiple players

A fun little game for as many players as you have friends. This is my first Python project so any advice on how to improve is welcome and appreciated. I'm not concerned with the clunky interface as I'd like to use it for a touch screen device. Have fun playing!

msg = '''
Welcome to 10,000
The Game!
Game objective:
The objective of the game is to have the highest score.
Actually, it's to have fun!
It might take a minute to get used to playing, just relax,
take a few practice runs, and challenge some friends!
To score:
Pick 1's and 5's from the dice you've thrown.
1's are worth 100 points. 5's are worth 50 points.
You may also select any three or four of a kind.
Three of a kind scores the number on the die x 100.
Four of a kind scores double a three of a kind of the same number.
Three ones are worth 1000 points, four ones are worth 2000 points.
Choose any combination of the above. We'll call 'em 'keepers'.
Look carefully on your first roll! The following are worth a lot of
points!:
A Straight 1-6 (chosen in any order) is worth 1500 points.
A Full House (three pairs) is worth 1500 points.
[I'm really proud of the code for these^^ check it out!!]
Six of a kind scores 5000 points!!!
And you get to roll all six dice again!
To play:
For each turn you will start with six dice.
Your dice will appear in [brackets].
It's just old school. You'll be fine. Hang in there.
You must choose at least one keeper from each throw to continue your turn.
To choose a keeper, use the reference number (0-5) located above it.
WTF? Yeah, I know. You'll get used to it and it'll be fun. Trust me.
Enter the reference number and hit Enter.
Repeat until you have all the keepers you want selected.
Then, hit Enter again to submit your selections to scoring.
If all six dice are kept, in one or more turns, all six may be thrown again.
To get on the board you must have a turn worth 500 or more points.
If you would like to stop rolling and keep your score for the
turn, press k and Enter.
If you wish, press Enter and the remaining die will now be thrown again.
You will choose your keepers and whether to throw again in the
same way until...
Your turn ends because you keep your score or your roll has no keepers.
The game continues until one player's score is over 10,000.
The other players will then have one turn to better that player's score.*
The winner is the player with the highest total score!*
*[Not currently functional. I'm working on making sure the winner
actually does have the high score, any advice there, or on anything,
would be greatly appreciated!]
THANK YOU FOR PLAYING !!
Created by:
Paul Kutrich'''

import random
import collections

class Game:
def __init__(self, player_list, round_score = 0):
self.round_score = round_score
self.player_list = player_list

#sets number of players and player names.
def set_player(self, player_list = []):
players = int(input("Enter number of players:""\n",))
x = 0
while x < players:
name = input("Enter your name:""\n",)
self.name = name
self.name = Player(name)
player_list.append(self.name)
x += 1
return player_list

#checks for full house and returns score.
def full_house(self, choice):
pair_count = 0
score = 0
for i in range(0,3):
try:
if choice[0] == choice[1]:
pair_count += 1
del(choice[1], choice[0])
continue
if choice[0] == choice[2]:
pair_count += 1
del(choice[2], choice[0])
continue
if choice[0] == choice[3]:
pair_count += 1
del(choice[3], choice[0])
continue
if choice[0] == choice[4]:
pair_count += 1
del(choice[4], choice[0])
continue
if choice[0] == choice[5]:
pair_count += 1
del(choice[5], choice[0])
except IndexError:
pass
if pair_count == 3:
score += 1500
print("You got a Full House!!\nSix keepers! Roll 'em again!\n")
return score

#checks for straight and returns score.
def straight(self, choice):
score = 0
if len([(x,y) for x in choice for y in choice if x == y]) == 6:
score += 1500
print("You got a Straight!!\n")
return score

#scores choices from Player.pick().
def keep_score(self, choice):
score = 0
if len(choice) == 6:
score += self.full_house(choice)
score += self.straight(choice)
return score
else:
valuedict = {1: {
1: 100,
2: 200,
3: 1000,
4: 2000,
6: 5000
},
2: {
3: 200,
4: 400,
6: 5000
},
3: {
3: 300,
4: 600,
6: 5000
},
4: {
3: 400,
4: 800,
6: 5000
},
5: {
1: 50,
2: 100,
3: 500,
4: 1000,
6: 5000
},
6: {
3: 600,
4: 1200,
6: 5000
}
}
try:
counts = collections.Counter(choice)
score = sum(valuedict[die][count] for die,count
in counts.items())
if score == 0:
print("No keepers\nThat's a bummer\n")
return 0
except KeyError:
print("One of your choices was not a keeper.")
print("Try not cheating next time.\n\n")
return score

#sets winning score, updates total scores,
#switches between players until winning score met.
#this is essentially the game script.
def take_turns(self, player_list):
self.player_list = Game.set_player(Game, [])
scores_list = [self.name.total_score for self.name in self.player_list
if self.name.total_score >= 10000]
while len(scores_list) == 0:
for x in range(0,len(self.player_list)):
self.player_list[x].turn(self.player_list[x].name)
scores_list = [self.name.total_score for self.name
in self.player_list if self.name.total_score >= 10000]
for x in range(0,len(self.player_list)):
print("\n",self.player_list[x].name,"'s total score is"
,self.player_list[x].total_score,"\n")
winner_list = [self.name for self.name in self.player_list
if  self.name.total_score >=10000]
if len(winner_list) > 0:
winner_list.sort(key=lambda Player: self.name.total_score)
print("The winner is",winner_list[-1].name,"!! With a score of:"
,winner_list[-1].total_score,"\n")
else:
print("The winner is",winner_list[0].name,"wins!! With a score of:"
,winner_list[0].total_score,"\n")
return False

class Player(Game):
def __init__(self, name, total_score = 0):
self.total_score = total_score
self.name = name

#rolls six dice and puts rolls into a list. Returns list of dice.
def throw(self, throw_count):
dice = 5
list = []
i = 0
while i <= dice - len(throw_count):
i +=1
list.append(random.randint(1,6))
return list

#takes user input to choose dice by index. Returns list of choices.
def pick(self, list):
choice_list = []
choose = []
choice = []
i = 0
while i <= len(list):
i += 1
try:
choose = int(input('''
Choose which die to keep by position 0-5
Type choice, then enter, repeat for all choices.
Press enter when finished
''',))
if choose >= len(list):
print("Choice not available\nGo ahead and try again.\n")
else:
choice_list.append(choose)
except ValueError:
Idx = choice_list
choice = [list[i] for i in Idx]
if choice == None:
choice = []
return choice

#one player turn.
def turn(self, player):
round_score = 0
list = self.throw([])
print("\n",player,", your dice in []\n  0  1  2  3  4  5\n",list,"\n")
throw_list = []
throw_list.append(list)
choice = self.pick(throw_list[0])
print("Here are your choices",choice,"\n")
score = self.keep_score(choice)
print("Your score for this throw is:",score,"\n")
if score == 0:
round_score = 0
return round_score
else:
round_score += score
keepers_list = []
keepers_list += choice
if len(keepers_list) == 6:
print("Six keepers! Roll 'em again!\n")
keepers_list = []
throw_list = []
throw_count = -1
else:
throw_count = 0
again = input("Roll again or keep?\nEnter = roll, K = keep\n",)
if again is r"k":
self.total_score += round_score
if self.total_score < 500:
round_score = 0
print("Must score 500 to get on the board.\n")
print("Your score this turn:",round_score,"\n")
False
self.total_score = 0
return round_score
else:
print("Your score this turn:",round_score,"\n")
False
return round_score
elif again != "q":
while True:
for i in range(0,4):
throw_count += 1
list = self.throw(keepers_list)
print("\n",player,", your dice in []\n  0  1  2  3  4  5\n"
,list,"\n")
throw_list.append(list)
choice = self.pick(throw_list[throw_count])
print("Here are your choices:",choice,"\n")
score = self.keep_score(choice)
print("Your score this throw is:",score,"\n")
if score == 0:
round_score = 0
False
return round_score
else:
round_score += score
print("Turn score:",round_score,"\n")
keepers_list += choice
if len(keepers_list) ==6:
print("Six keepers! Roll 'em again!\n")
keepers_list = []
throw_list = []
throw_count = -1
again = (input(
'''
Roll again or keep?
Enter = roll K = keep''',))
if again == r"k":
self.total_score += round_score
if self.total_score < 500:
print("Must score 500 to get on the board. \n")
print("Your score this turn:",round_score,"\n")
False
self.total_score = 0
round_score = 0
return round_score
else:
print("Your score this turn:",round_score,"\n")
False
return round_score
False
return round_score

def main():
while True:
Game.take_turns(Game, [])

print(msg)
main()
• Jan 14, 2018 at 20:11
• I always thought that a full house was 3+2 instead of 2+2+2 Jan 15, 2018 at 12:35
• @MathiasEttinger It is in poker. This however doesn't seem to be poker. Jan 15, 2018 at 15:03
• It really should be called something other than a full house. But it's traditionally called that, at least in these parts. Jan 15, 2018 at 21:12
• Huh. Not three pairs? Jan 16, 2018 at 1:15

• You should move most of the functions out of the classes. Leaving as little as possible in each class.
• You should have Player not inherit from Game. As a Player is not a child of Game.
• You should change throw to take an amount to throw. And make it a simple comprehension.
• You should make a select_range function that generates none to $N$ numbers, in the range zero to $N$. This is to help simplify pick.
• You should change pick to only select choices, by iterating over select_range.
• I'd change input_players to make, and store Player objects.
• is_full_house should sort the choices, and check if the pairs are the same.
• is_straight should sort and check that the choices are 1 to 6.
• score can be simplified if you move valuedict out of the function.
• You can drastically simplify take_turn, by merging all the loops into one.

I also removed some other aspects, that seemed unneeded. Which overcomplicate the core of the function.

• In take_turns you can reduce the while loop if you use any, rather than using a list comprehension.

You can also use max, rather than sorting the list. Which can allow you to merge all your ifs together.

I don't fully get how you start the Game, and so I've left that out. But below is how I'd change the code:

import random
import collections

valuedict = {
1: {
1: 100,
2: 200,
3: 1000,
4: 2000,
6: 5000
},
2: {
3: 200,
4: 400,
6: 5000
},
3: {
3: 300,
4: 600,
6: 5000
},
4: {
3: 400,
4: 800,
6: 5000
},
5: {
1: 50,
2: 100,
3: 500,
4: 1000,
6: 5000
},
6: {
3: 600,
4: 1200,
6: 5000
}
}

def throw(amount):
return [
random.randint(Game._DICE_MIN, Game._DICE_MAX)
for _ in range(amount)
]

def select_range(stop):
for _ in range(stop):
try:
while True:
choice = int(input())
if 0 <= choice < stop:
break
yield choice
except ValueError:
break

def pick(choices):
return [
choices[choice]
for choice in set(select_range(len(choices)))
]

def input_players():
players = int(input("Enter number of players:""\n",))
player_list = []
for _ in range(players):
name = input("Enter your name:""\n",)
player_list.append(Player(name))
return player_list

def is_full_house(choices):
return len(choices) == 6 and all(a == b for a, b in zip(*[iter(sorted(choices))]*2))

def is_straight(choices):
return sorted(choices) == list(range(1, 7))

def score(choices):
score = 0
if len(choices) == 6:
if is_full_house(choices):
score += 1500
print("You got a Full House!!\nSix keepers! Roll 'em again!\n")
if is_straight(choices):
score += 1500
print("You got a Straight!!\n")
else:
counts = collections.Counter(choice)
try:
score = sum(valuedict[die][count] for die, count
in counts.items())
except KeyError:
print("One of your choices was not a keeper.")
print("Try not cheating next time.\n\n")
else:
if score == 0:
print("No keepers\nThat's a bummer\n")
return score

class Player:
def __init__(self, name):
self.name = name
self.score = 0

def take_turn(self):
total_score = 0
choices = []
while True:
choices += throw(Game._DICE_MAX - len(choices))
print("\n{self.name}, you rolled\n  0  1  2  3  4  5\n {choices} \n")
choices = pick(choices)
print("Here are your choices {choices}\n")
score = score(choices)
if score == 0:
return 0

total_score += score
print("Your score this throw is: {score}\n")
if len(choices) == 6:
print("Six keepers! Roll 'em again!\n")

again = input("Roll again or keep?\nEnter = roll, K = keep\n")
if again in "qk":
break
self.score += total_score

class Game:
_DICE_MIN = 1
_DICE_MAX = 6

def __init__(self, player_list, round_score=0):
self.round_score = round_score
self.player_list = player_list

@staticmethod
def take_turns(players):
while not any(player.score >= 10000 for player in players):
for player in players:
player.take_turn()
for player in players:
print("\n{player.name}'s score is {player.score}'")

winner = max(players, lambda player: player.score)
print("The winner is {winner.name}!! With a score of: {winner.score}\n")
• Thank you! I'm going to implement a lot of this! There are some tools here I wasn't aware of and simplifications I didn't think to try. I really appreciate you taking the time to answer! Jan 16, 2018 at 19:54
• Could you further explain your reasoning for moving functions out of classes and for Player not inheriting from Game. My understanding is that encapsulating is generally better. I have written a computer player (a very dumb one) that also inherits from Game and that seems useful. Jan 17, 2018 at 19:30
• @PaulK I find inheritance easier to understand if you think it's like taxonomic rank. A Felis silvestris catus (domestic cat) is a subclass of Felis silvestris (wildcat). This is as domestic cats have everything a wild cat does, but it changes some aspects - it's domesticated. However a person isn't a game, or a slightly changed one. Finally I moved the functions out of the class as name is easier to use than class.name. And means you don't have to wrap them all in static_methods. Jan 17, 2018 at 20:14
• Ah, Okay. I was thinking along the lines of; a game has players. I see your point. Jan 17, 2018 at 20:26
• @PaulK that is actually a good point to lead towards is a vs has a relationships. Inheritance creates is a relationships (e. g. John is a Person, so John inherits from Person). Even where inheritance makes sense (with Player and Game it does not), has a relationships are often prefered, which is called composition (e. g. Game has Players, for example a list of them). Jan 19, 2018 at 11:12