5
\$\begingroup\$

I made this Scrabble with care since it will be mainly used by family that will not understand the error codes so that is why I catch basically every possible error, to make it user friendly. I have revised this based on an earlier post and hope to improve its efficiency even more now that I have basically redone the code.


letter_val = {" ": 0, "A": 1, "B": 3, "C": 3, "D": 2, "E": 1, "F": 4, "G": 2, "H": 4, "I": 1, "J": 8, "K": 5, "L": 1, "M": 3, "N": 1, "O": 1, "P": 3, "Q": 10, "R": 1, "S": 1, "T": 1, "U": 1, "V": 4, "W": 4, "X": 8, "Y": 4, "Z": 10}
print("""
___|                 |     |     |           
\___ \   __|  __| _` | __ \  __ \  |  _ \  __| 
  | (    |   (   | |   | |   | |  __/ |    
_____/ \___|_|  \__,_|_.__/ _.__/ _|\___|_|    
""")
players = []


def add_players():
    print("If amount of players is less than 4, press enter after all players are entered.")
    while len(players) < 4:
        new_player = input("Enter player to add>>> ")

        if new_player:
            if new_player not in (stats[0] for stats in players):
                players.append([new_player, 0])
                print("Player {} added.".format(new_player))
            else:
                print("Player already in player list.")
        else:
            break


add_players()


def home():
    option = input('Would you like to [A]dd a score, [V]iew scores, [U]ndo the last change, or [End] the game? > ')
    return option


def add_score():
    score = 0
    global temp_value, temp_player, undo_ind
    player = temp_player = input("Enter player to score>>> ")
    if player:
        if player in (stats[0] for stats in players):
            try:
                word = input("Enter word to score(Enter a space for blank tiles)>>> ")
                if word:
                    value = temp_value = sum(letter_val[i.upper()] for i in word)
                else:
                    add_score()
            except KeyError:
                print("Word must consist of letters only.")
                add_score()
            dbl_or_trip = input("Is word [D]ouble or [T]riple or [N]either?> ")
            if dbl_or_trip.lower() == "d":
                score += value * 2
            elif dbl_or_trip.lower() == "t":
                score += value * 3
            elif dbl_or_trip.lower() == "n":
                score += value
            else:
                print("Please enter a valid option.")
                add_score()
            print("Are there any double or triple letters?(For example, if the letter \"b\" is doubled and \"t\" is tripled, it is entered like this> b2 t3)")
            mult_letters = input("Enter any double or triple letters in the above format or press enter to skip> ")
            spl_mult_letters = mult_letters.split()
            try:
                for letter, multiplier in spl_mult_letters:
                    for stats in players:
                        if stats[0] == player:
                            if 4 > int(multiplier) > 1:
                                stats[1] += letter_val[letter.upper()] * (int(multiplier) - 1) + score
                                break
                            else:
                                print("The multiplier must be either 2 or 3.")
            except ValueError:
                print("Please enter multiplied letters in the above format.")
        else:
            print("Player entered is not in player list.")
            add_score()
    undo_ind = False
    main()


def view_scores():
    for i in players:
        print("Player %s has a score of %d" % (i[0], i[1]))
    main()


def undo():
    global undo_ind
    no_change = "No changes have been made."
    try:
        if temp_value and temp_player and undo_ind is False:
            for stats in players:
                if stats[0] == temp_player:
                    stats[1] -= temp_value
        else:
            print(no_change)
    except NameError:
        print(no_change)
        home()
    undo_ind = True
    main()


def end_game():
    for name, score in players:
        print("Player %s has a score of %d" % (name, score))
        __name__ = "end"


def main():
    option = home()
    if option.lower() == "a":
        add_score()
    elif option.lower() == "v":
        view_scores()
    elif option.lower() == "u":
        undo()
    elif option.lower() == "end":
        end_game()
    else:
        print("Please enter a valid option.")
        main()


if __name__ == "__main__":
    main()

I know I have recursion in the main() function and I would like someone to provide an example specific to this scenario for me to use, as I can't figure out a working solution. Also the reason I set __name__ to "end" in the end_game() function is because if I did not, end game would run the if statement at the end of the code and then repeat main() putting the user in an endless loop that can only be escaped by CTRL+C. If anyone has a better way of the interaction between end_game() and main() I would like to hear and improve on it. I am fairly new to Python, and examples do help a lot for this program and future references. Thanks.

\$\endgroup\$
  • \$\begingroup\$ I tested the code and it does not seem to fit the condition "Your question must contain code that is already working correctly". Trying to add a score without any double or or triple letters fails (nothing is added to the player score), and I sometimes get stuck in an infinite loop with no possibility to exit. Also, it doesn't follow Scrabble's rules, as it is impossible to score a double double word (or triple triple word), and letters should be doubled/tripled before the word, and this code goes the other way around. \$\endgroup\$ – gazoh Nov 26 '19 at 13:18
5
\$\begingroup\$

Style

print("""
___|                 |     |     |           
\___ \   __|  __| _` | __ \  __ \  |  _ \  __| 
  | (    |   (   | |   | |   | |  __/ |    
_____/ \___|_|  \__,_|_.__/ _.__/ _|\___|_|    
""")

What does that even mean? Scrabble?

There are many possible replacements.

For example:

print("""

       _/_/_/_/    _/_/_/_/   _/_/_/_/     _/       _/_/_/_/    _/_/_/_/    _/        _/_/_/_/
      _/         _/         _/     _/    _/ _/     _/     _/   _/     _/   _/        _/
     _/_/_/_/   _/         _/_/_/_/    _/   _/    _/_/_/_/    _/_/_/_/    _/        _/_/_/_/
           _/  _/         _/ _/       _/_/_/_/   _/     _/   _/     _/   _/        _/
    _/_/_/_/  _/_/_/_/   _/    _/    _/     _/  _/_/_/_/    _/_/_/_/    _/_/_/_/  _/_/_/_/


""")

But it's your wish if you don't want to change it!


add_scores

Always add a return statement when you don't want the upcoming code to be executed

except KeyError:
    print("Word must consist of letters only.")
    add_score()

dbl_or_trip = input("Is word [D]ouble or [T]riple or [N]either?> ")

Regardless of whether there was KeyError or not, dbl_or_trip = input(... is executed.
If there was a KeyError, then after add_score() was executed, it moves on to the next line.

value is declared inside the scope of try. That's bound to create some errors. declare value = None before executing the try statement


add_players

There's not much improvement here, except that you can use
print("Player {} added.".format(new_player)) instead of print(f"Player {new_player} added.")

Also, you can add print() statement after taking the input to make the text easier to read.


view_scores

Again, you can use print(f'{something}') instead of print('%s' % 'something')


end_game

And yet again, you can use f'{something}'

Also, why do you do __name__ = "end"? It works even if you remove that.

Also the reason I set name to "end" in the end_game() function is because if I did not, end game would run the if statement at the end of the code and then repeat main() putting the user in an endless loop that can only be escaped by CTRL+C.

That is not true. After end_game() is called, the code automatically returns to the line that was being run.

def foo():        # Line 1
    return 'bar'  # Line 2
                  # Line 3
foo()             # Line 4
print('1')        # Line 5

This does not create an endless recursion. After 'bar' was returned, The function ends, but the code does not continue from line 3. It goes on to the next line that was being run while calling the function which was line 4. So the code proceeds to line 5. In other words, functions are not a part of the executing code.


undo

Again, always add a return statement when you don't want the upcoming code to be executed

    except NameError:
        print(no_change)
        home()

    undo_ind = True
    main()

After home() is executed, the code will proceed to execute undo_ind = True and main(), which we don't want to happen.

undo_ind is False should always be replaced with not undo_ind.


Misc

  • You should not run add_players() outside if __name__ == '__main__'. If the module was imported, It would automatically ask for player names.

  • Also, you can create a function begin which calls add_players() and then main()

\$\endgroup\$
  • 2
    \$\begingroup\$ That "Scrabble" ascii art looks amazing. \$\endgroup\$ – Linny Nov 21 '19 at 16:45
  • 2
    \$\begingroup\$ @Linny Thank you so much! My family said I were just wasting my time, but I was sure someone would appreciate it! :D \$\endgroup\$ – Srivaths Nov 21 '19 at 17:07
  • \$\begingroup\$ @Srivaths also, when I add the score to stats[1] it's in the for loop that each list in the format of [player_name, score] is represented by stats, so it is updating the score in player, I just don't get how the way you did it is much different than mine. But then again, maybe I need some more explanation of what your code is doing better. \$\endgroup\$ – pythonier500 Nov 21 '19 at 22:37
  • \$\begingroup\$ @Srivaths For the points for the letters, I have to make a custom dictionary because there is no pattern in the points for each letter, unless I do some webscraping or something along the lines to retrieve point values, which is needless to say unnecessary. \$\endgroup\$ – pythonier500 Nov 21 '19 at 22:53
  • 1
    \$\begingroup\$ @Srivaths it is quite alright, I was wrong about the end_game function as I had an invalid placement of code, thank you for all the help. I took your advice for a begin function that works quite well. \$\endgroup\$ – pythonier500 Nov 24 '19 at 3:50
2
\$\begingroup\$

Bugs

I think the calculation is incorrect when there's both letter multipliers and word multipliers in play - the letter multipliers should be computed first, so that they get multiplied up by the word multiplier, too.

It also doesn't handle words that span more than one double-word or triple-word square, or claiming the 50-point bonus for playing all tiles.

Improvements

This line, as well as being unwieldy (use some line breaks!) restricts this program to English scrabble:

letter_val = {" ": 0, "A": 1, "B": 3, "C": 3, "D": 2, "E": 1, "F": 4, "G": 2, "H": 4, "I": 1, "J": 8, "K": 5, "L": 1, "M": 3, "N": 1, "O": 1, "P": 3, "Q": 10, "R": 1, "S": 1, "T": 1, "U": 1, "V": 4, "W": 4, "X": 8, "Y": 4, "Z": 10}

If you want to be useful to all players of the game, it would be worth loading the points values from a config file.

The current code is going to have problems in languages where not all tiles are single letters (e.g. DD₁ or LL₅ in a Welsh set).

If you ever try Super Scrabble, you'll find that there are quad-letter and quad-word spaces, too; it would be a nice enhancement to support those, and not too difficult to implement.

\$\endgroup\$
  • \$\begingroup\$ Thank you for that feedback, I will try to implement the 50 point bonus and words that span more than one double-word or triple-word. I do not plan to spend too much more time on this project so I may keep it restricted to the English language since this is more of a project for my family and we only play the English version. \$\endgroup\$ – pythonier500 Nov 26 '19 at 16:05
  • \$\begingroup\$ You might be able to implement extra word-multipliers by allowing the user to provide a string of multipliers (e.g. 'dd' for two double-word scores) and parsing with a loop. That might be a good way to handle the bonus, too (e.g. enter an x in that input to indicate all tiles played). \$\endgroup\$ – Toby Speight Nov 26 '19 at 16:28

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.