Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 161 additions & 4 deletions adagrams/game.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,168 @@
import random

# Dictionary of the letters with their quantities
LETTER_POOL = {
"A": 9,
"B": 2,
"C": 2,
"D": 4,
"E": 12,
"F": 2,
"G": 3,
"H": 2,
"I": 9,
"J": 1,
"K": 1,
"L": 4,
"M": 2,
"N": 6,
"O": 8,
"P": 2,
"Q": 1,
"R": 6,
"S": 4,
"T": 6,
"U": 4,
"V": 2,
"W": 2,
"X": 1,
"Y": 2,
"Z": 1,
}

# Dictionary assigning point values to the letters
LETTERS_VALUE = {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both of these global dictionaries look great!

"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,
}


def draw_letters():
pass

# Draw 10 letters randomly, considering the quantity limits
letter_list = list(LETTER_POOL.keys())
tiles = {} # Dictionary with letter and count to avoid nested loops
letter_bank = []

while len(letter_bank) < 10:
random_number = random.randint(0, len(letter_list) - 1)
random_letter = letter_list[random_number]

if random_letter in tiles:
if tiles[random_letter] >= LETTER_POOL[random_letter]:
continue
tiles[random_letter] += 1
else:
tiles[random_letter] = 1

letter_bank.append(random_letter)

return letter_bank

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, this function looks good! It's a different approach than most that I've seen so far and it's quite clever! The one thing to point out is that while you are taking into account quantity limits, your code currently treats every letter as if they have the same chance of being picked! In reality though, there are more "A"s than "Q"s so the probability of picking an A would be much higher and your current algorithm doesn't take that into account! I will say this wasn't required and it wasn't a test case, so you are not required to include that, but if you have some extra time and would like to try an implementation that weights the letters, feel free to do so!



# Count occurrences of a letter in an iterable
def count_items(item, iterable):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the way you built your own count method here! It is a builtin method, but being able to write it yourself can help you better understand how to use it! Helper functions always encouraged!

count = 0
for x in iterable:
if x == item:
count += 1
return count


def uses_available_letters(word, letter_bank):
pass
capital_word = word.upper()

for letter in set(capital_word):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice move to loop through a set as opposed to the entire capital word! Good catch to avoid duplicates!

if letter not in letter_bank or count_items(letter, capital_word) > count_items(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great use of your helper functions!

letter, letter_bank
):
return False
return True


def score_word(word):
pass
# Calculate score
score = 0
for letter in word.upper():
score += LETTERS_VALUE[letter]

# Add bonus points for words of length 7-10
if len(word) >= 7 and len(word) <= 10:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good! Remember that we have a slightly more concise way to write a conditional to figure out if a value falls into some range. What might that look like?

score += 8
return score


# Function to find the maximum value in a list of numbers
def calculate_max(iterable):
max = 0
for value in iterable:
if value > max:
max = value
return max


# Function to find the minimum length of words in a list
def calculate_min_length(list_of_words):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love these helpers!

if not list_of_words:
return None

min_length = len(list_of_words[0])
for word in list_of_words:
if len(word) < min_length:
min_length = len(word)
return min_length


def get_highest_word_score(word_list):
pass
# Calculate scores of each word
word_score = {}
for word in word_list:
score = score_word(word)
word_score[word] = score

# Calculate max score
max_score = calculate_max(word_score.values())

# Find all candidate words with max scores
candidates = []
for word, score in word_score.items():
if score == max_score:
candidates.append(word)

# Find minimum length in candidates
min_length = calculate_min_length(candidates)

# Determine a winner considering rules
current_winner = ""
for word in candidates:
if len(word) == 10:
current_winner = word
break
if len(word) == min_length and not current_winner:
current_winner = word

return current_winner, max_score

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this function works and passes all the tests, so great job! Specifically, this is a really great way to separate out each section of data and then parse through it one at a time. There is nothing inherently wrong with that and it's a great way to learn and make sure you understand each step that you are doing!

Once challenge I have for you is to remember that when it comes to these sorts of algorithms, it can be super helpful to keep in mind what information you need to store as well as what information you don't! For example, we really only need to hold onto words that could be or are the highest scoring word. We don't really care about any of the other words! The dictionary you create maps every word to a score but we likely won't need most of those scores and the words that map to them.

Your logic is super sound for how to discover which word and score to update and I think you could leverage that logic to limit the number of extra dictionaries and lists that you create here! No need to make the actual change, but it is something to think about moving forward!

41 changes: 26 additions & 15 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import sys
from adagrams.ui_helper import *
from adagrams.game import draw_letters, uses_available_letters, score_word, get_highest_word_score
from adagrams.game import (
draw_letters,
uses_available_letters,
score_word,
get_highest_word_score,
)


def wave_1_run_game():
display_welcome_message()
Expand All @@ -9,13 +15,14 @@ def wave_1_run_game():
print("Let's draw 10 letters from the letter pool...")
letter_bank = draw_letters()
display_drawn_letters(letter_bank)

display_retry_instructions()
continue_input = input()
game_continue = continue_input == "y"

display_goodbye_message()


def wave_2_run_game():
display_welcome_message()
game_continue = True
Expand All @@ -26,16 +33,17 @@ def wave_2_run_game():
display_game_instructions()
user_input_word = input()

while( not uses_available_letters(user_input_word, letter_bank)):
while not uses_available_letters(user_input_word, letter_bank):
display_needs_valid_input_message()
user_input_word = input()

display_retry_instructions()
continue_input = input()
game_continue = continue_input == "y"

display_goodbye_message()


def wave_3_run_game():
display_welcome_message()
game_continue = True
Expand All @@ -46,10 +54,10 @@ def wave_3_run_game():
display_game_instructions()
user_input_word = input()

while( not uses_available_letters(user_input_word, letter_bank)):
while not uses_available_letters(user_input_word, letter_bank):
display_needs_valid_input_message()
user_input_word = input()

score = score_word(user_input_word)
display_score(score)

Expand All @@ -58,6 +66,7 @@ def wave_3_run_game():
game_continue = continue_input == "y"
display_goodbye_message()


def wave_4_run_game():
display_welcome_message()
game_continue = True
Expand All @@ -69,10 +78,10 @@ def wave_4_run_game():
display_game_instructions()
user_input_word = input()

while( not uses_available_letters(user_input_word, letter_bank)):
while not uses_available_letters(user_input_word, letter_bank):
display_needs_valid_input_message()
user_input_word = input()

score = score_word(user_input_word)
display_score(score)
played_words.append(user_input_word)
Expand All @@ -83,22 +92,24 @@ def wave_4_run_game():
display_highest_score(get_highest_word_score(played_words))
display_goodbye_message()


def main(wave):
if(wave == 1):
if wave == 1:
wave_1_run_game()
elif(wave == 2):
elif wave == 2:
wave_2_run_game()
elif(wave == 3):
elif wave == 3:
wave_3_run_game()
elif(wave == 4):
elif wave == 4:
wave_4_run_game()
else:
print("Please input a wave number. Valid wave numbers are 1, 2, 3, 4.")


if __name__ == "__main__":
args = sys.argv
if(len(args) >= 2 and args[1].isnumeric()):
if len(args) >= 2 and args[1].isnumeric():
wave = int(args[1])
else:
wave = "ERROR"
main(wave)
main(wave)
10 changes: 10 additions & 0 deletions tests/test_wave_04.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from adagrams.game import score_word, get_highest_word_score


def test_get_highest_word_score_accurate():
# Arrange
words = ["X", "XX", "XXX", "XXXX"]
Expand All @@ -14,6 +15,7 @@ def test_get_highest_word_score_accurate():
assert best_word[0] == "XXXX"
assert best_word[1] == 32


def test_get_highest_word_score_accurate_unsorted_list():
# Arrange
words = ["XXX", "XXXX", "XX", "X"]
Expand All @@ -25,6 +27,7 @@ def test_get_highest_word_score_accurate_unsorted_list():
assert best_word[0] == "XXXX"
assert best_word[1] == 32


def test_get_highest_word_tie_prefers_shorter_word():
# Arrange
words = ["MMMM", "WWW"]
Expand All @@ -38,6 +41,7 @@ def test_get_highest_word_tie_prefers_shorter_word():
assert best_word[0] == "WWW"
assert best_word[1] == 12


def test_get_highest_word_tie_prefers_shorter_word_unsorted_list():
# Arrange
words = ["WWW", "MMMM"]
Expand All @@ -51,6 +55,7 @@ def test_get_highest_word_tie_prefers_shorter_word_unsorted_list():
assert best_word[0] == "WWW"
assert best_word[1] == 12


def test_get_highest_word_tie_prefers_ten_letters():
# Arrange
words = ["AAAAAAAAAA", "BBBBBB"]
Expand All @@ -62,6 +67,7 @@ def test_get_highest_word_tie_prefers_ten_letters():
assert best_word[0] == "AAAAAAAAAA"
assert best_word[1] == 18


def test_get_highest_word_tie_prefers_ten_letters_unsorted_list():
# Arrange
words = ["BBBBBB", "AAAAAAAAAA"]
Expand All @@ -73,6 +79,7 @@ def test_get_highest_word_tie_prefers_ten_letters_unsorted_list():
assert best_word[0] == "AAAAAAAAAA"
assert best_word[1] == 18


def test_get_highest_word_tie_same_length_prefers_first():
# Arrange
words = ["AAAAAAAAAA", "EEEEEEEEEE"]
Expand All @@ -86,6 +93,7 @@ def test_get_highest_word_tie_same_length_prefers_first():
assert best_word[0] == words[0]
assert best_word[1] == 18


def test_get_highest_word_many_ties_pick_first_ten_letters():
# Arrange
words = ["JQ", "FHQ", "AAAAAAAAAA", "BBBBBB", "TTTTTTTTTT"]
Expand All @@ -97,6 +105,7 @@ def test_get_highest_word_many_ties_pick_first_ten_letters():
assert best_word[0] == "AAAAAAAAAA"
assert best_word[1] == 18


def test_get_highest_word_many_ties_pick_shortest():
# Arrange
words = ["BBBBBB", "AAAAAAAAD", "JQ", "KFHK"]
Expand All @@ -108,6 +117,7 @@ def test_get_highest_word_many_ties_pick_shortest():
assert best_word[0] == "JQ"
assert best_word[1] == 18


def test_get_highest_word_does_not_return_early_after_first_tiebreaker():
# Arrange
words = ["WWW", "MMMM", "BBBBBB", "AAAAAAAAD", "JQ", "KFHK"]
Expand Down