5
\$\begingroup\$

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")
                print("Your total_score", self.total_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")
                            print("Your total_score", self.total_score,"\n")
                            False
                            return round_score
        False
        return round_score

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

print(msg)
main()
\$\endgroup\$
  • 3
    \$\begingroup\$ Is this a duplicate of your previous question? \$\endgroup\$ – Peilonrayz Jan 14 '18 at 20:11
  • \$\begingroup\$ I always thought that a full house was 3+2 instead of 2+2+2 \$\endgroup\$ – Mathias Ettinger Jan 15 '18 at 12:35
  • \$\begingroup\$ @MathiasEttinger It is in poker. This however doesn't seem to be poker. \$\endgroup\$ – Peilonrayz Jan 15 '18 at 15:03
  • \$\begingroup\$ It really should be called something other than a full house. But it's traditionally called that, at least in these parts. \$\endgroup\$ – Paul K Jan 15 '18 at 21:12
  • \$\begingroup\$ Huh. Not three pairs? \$\endgroup\$ – David K Jan 16 '18 at 1:15
3
\$\begingroup\$
  • 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")
\$\endgroup\$
  • \$\begingroup\$ 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! \$\endgroup\$ – Paul K Jan 16 '18 at 19:54
  • \$\begingroup\$ 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. \$\endgroup\$ – Paul K Jan 17 '18 at 19:30
  • \$\begingroup\$ @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. \$\endgroup\$ – Peilonrayz Jan 17 '18 at 20:14
  • \$\begingroup\$ Ah, Okay. I was thinking along the lines of; a game has players. I see your point. \$\endgroup\$ – Paul K Jan 17 '18 at 20:26
  • 1
    \$\begingroup\$ @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). \$\endgroup\$ – Raimund Krämer Jan 19 '18 at 11:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.