Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C19 Sapphire - Linh Huynh #116

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
fe37811
Added project credit source, name, and date to README.md file.
remitly-linh Mar 18, 2023
ea1c8a6
Iterated through LETTER_POOL dictionary to append each letter quantit…
remitly-linh Mar 18, 2023
d7183c0
Added code to randomly select 10 letters. Assigned these letters as a…
remitly-linh Mar 18, 2023
741ab2d
Wrote function that checks whether the letters in a word exist within…
remitly-linh Mar 19, 2023
7295e64
Updated uses_available_letters() function to account for instances wh…
remitly-linh Mar 19, 2023
ea5cd6c
Added code under the uses_available_letters to account for instances …
remitly-linh Mar 22, 2023
1cc20db
Added code to capitalize any lowercase letter input from player.
remitly-linh Mar 22, 2023
a72b4b7
Added code under the score_word function to accurately score the tota…
remitly-linh Mar 22, 2023
8f2f693
Made word scoring process case insensitive.
remitly-linh Mar 23, 2023
454cdea
Accounted for empty word input scenarios in the score calculation pro…
remitly-linh Mar 23, 2023
9279bb5
Unified indentation across all functions.
remitly-linh Mar 24, 2023
761597d
Corrected score_word(word) function to properly add points for letter…
remitly-linh Mar 24, 2023
76a5651
Added code underget_highest_word_score function to determine and outp…
remitly-linh Mar 24, 2023
d431e32
Refactored score_word(word) function to approach word case sensitivit…
remitly-linh Mar 24, 2023
aab9721
Updated get_highest_word_score function to account for scenarios with…
remitly-linh Mar 24, 2023
9ae92f6
Updated get_highest_word_score function to select highest scoring wor…
remitly-linh Mar 24, 2023
67c23d0
Updated get_highest_word_score to prefer the first word with highest …
remitly-linh Mar 24, 2023
fae638d
Practice forking the URL for my Adagrams submission for grading
remitly-linh Mar 24, 2023
90bd3d2
Updated draw_letters function to remove randomly selected letters in …
remitly-linh Mar 24, 2023
9ea3722
Removed all print statements.
remitly-linh Mar 24, 2023
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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# AdaGrams
Project Credit: Ada Developers Academy <br />
Name: Linh Huynh <br />
Date: March 16, 2023 <br />

## Skills Assessed

Expand Down
169 changes: 164 additions & 5 deletions adagrams/game.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,170 @@
import random
from collections import Counter

LETTER_POOL = {
Copy link

Choose a reason for hiding this comment

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

Great call making these constants

'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
}

SCORE_CHART = {
'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
}

LETTER_POOL_LIST = []
temp_list = []

def draw_letters():
pass
Comment on lines +62 to -2
Copy link

Choose a reason for hiding this comment

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

Your solution would benefit if we moved these two variables to inside the draw_letters function, so instead:

def draw_letters():
    LETTER_POOL_LIST = []
    temp_list = []
    # ...

The first sign that we should move these variables is because they are only used/modified/accessed within one function: the draw_letters function. We want our variables to have the smallest scope as possible-- so if other functions don't need access to LETTER_POOL_LIST and temp_list, then let's make them local variables.

As a note, if we move these variables, all of your tests pass! Please let me know if you'd like to talk together about why this fix works.


def uses_available_letters(word, letter_bank):
pass
# Creates a comprehensive list which includes the total quanity of each available letter from the LETTER_POOL dictionary as individual elements.
for letter, quantity in LETTER_POOL.items():
temp_list.append([letter] * quantity)

for sublist in temp_list:
for letter in sublist:
Comment on lines +71 to +72
Copy link

Choose a reason for hiding this comment

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

For me, it's hard to understand what sublist represents by its name. I would suggest renaming sublist to letter_list or something else more specific, and if you're having trouble coming up with a name, to revisit what it's used for

LETTER_POOL_LIST.append(letter)

# Chooses 10 random letters as hand, excluding the ones that are already chosen.
#hand = random.sample(LETTER_POOL_LIST, 10)

hand = []
while len(hand) < 10:
random_letter = random.choice(LETTER_POOL_LIST)
random_letter_index = LETTER_POOL_LIST.index(random_letter)
LETTER_POOL_LIST.pop(random_letter_index)
hand.append(random_letter)

return hand

draw_letters()
Copy link

Choose a reason for hiding this comment

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

This line doesn't do anything meaningful right now, since it's not in a test file or a function! This line is safe to delete.


def uses_available_letters(word, letter_bank):
word_verification = []
hand_letter_count = {}
word_letter_count = {}
count = 0
capitalized_word = word.upper()

for letter in letter_bank:
if letter not in hand_letter_count:
count = 1
hand_letter_count[letter] = count
else:
count += 1
hand_letter_count[letter] = count
Comment on lines +96 to +102
Copy link

Choose a reason for hiding this comment

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

Your logic is sound here, but there are a couple of ways we can make it clearer by focusing on count. Firstly, count is only used in one place: inside this single for loop. Therefore, we could move the definition of count:

    for letter in letter_bank:
        count = 0
        if letter not in hand_letter_count:
            count = 1
            hand_letter_count[letter] = count
        else: 
            count += 1
            hand_letter_count[letter] = count

Afterwards, we can really see how count is being used. We could even find a way to refactor it out and avoid it:

    for letter in letter_bank:
        if letter not in hand_letter_count:
            hand_letter_count[letter] = 1
        else: 
            hand_letter_count[letter] += 1

This code has the exact same logic as your original project, and three less lines and one less variable!


for letter in capitalized_word:
letter_quantity = capitalized_word.count(letter)
Comment on lines +104 to +105
Copy link

Choose a reason for hiding this comment

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

Reinforcing a comment I've made, word_letter_count is only used in one place: within this for loop. We can move the definition of word_letter_count from above, closer to where it's being used.

word_letter_count[letter] = letter_quantity

if letter in letter_bank and word_letter_count[letter] <= hand_letter_count[letter]:
word_verification.append("True")
else:
word_verification.append("False")
Comment on lines +109 to +111
Copy link

Choose a reason for hiding this comment

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

word_verification is a list, and lists can contain Booleans. With the way that you're using word_verification, I think you could use the booleans True and False instead of the strings "True" and "False"


if "False" in word_verification:
return False
else:
return True
Comment on lines +113 to +116
Copy link

Choose a reason for hiding this comment

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

This logic says "If we've come across even one False is inside word_verification, then we'll return False." Using the same logic, could we refactor this if conditional? One thing to notice is that we're using False/"False" and word_verification in two of the same places. Could we return False in the case that we find the False case?

We can move return False, and assume that we return True otherwise.

I'd encourage you to work through this refactoring and see if any questions come up. After refactoring that, I think it's worth asking "Do I need the word_verification variable? Can I get rid of it? Why/why not?"



def score_word(word):
pass
word_score = {}

if word == "":
word_score["empty"] = 0
Copy link

Choose a reason for hiding this comment

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

[nit] Your indentation on this line is off by one space

Comment on lines +122 to +123
Copy link

Choose a reason for hiding this comment

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

This code says to me "if word is empty, then the total score is 0". I think that adding "empty" into word_score isn't as straightforward as we can be, although I see that you're trying to give the score 0 even if the word has a length of 0 (an empty string). An alternative solution is to return 0 inside this conditional:

    if word == "":
      return 0


for letter in word.upper():
if letter in SCORE_CHART:
if letter not in word_score:
word_score[letter] = SCORE_CHART[letter]
else:
word_score[letter] += SCORE_CHART[letter]

if 7 <= len(word) <= 10:
word_score["bonus"] = 8

return sum(word_score.values())
Comment on lines +120 to +135
Copy link

Choose a reason for hiding this comment

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

I think your approach to this problem is interesting and extensible-- I think that adding "bonus" key and summing word_score.values() is really smart for an expanded project. In this situation, if you wanted to refactor and make word_score an integer, instead of a {}, then that would also pass the tests.


def get_highest_word_score(word_list):
pass
score_dict = {}
score_list = []
matching_scores_dict = {}
highest_score = 0

for word in word_list:
score = score_word(word)
score_dict[word] = score

for word, score in score_dict.items():
score_list.append(score)
if score > highest_score:
highest_score = score
winning_word = word

matching_scores = [k for k,v in Counter(score_list).items() if v>1]

for word, score in score_dict.items():
if score in matching_scores:
matching_scores_dict[word] = score
Comment on lines +153 to +157
Copy link

Choose a reason for hiding this comment

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

From what I can tell, this code is finding all times that there is a non-unique score, and collects them in a dictionary between word and score. However, do we need to keep a dictionary of matching scores to find the best word?


for word, score in matching_scores_dict.items():
Copy link

Choose a reason for hiding this comment

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

Instead of iterating through matching_scores.dict.items(), what if we iterate through score_dict instead? score_dict is a dictionary that already keeps each word and its score. We already have a value for highest_score and winning_word, too!

If we refactor this loop so it iterates through score_dict, we can also delete the code around matching scores. If we make these changes, then the tests still pass!

if score == highest_score and len(word) == 10:
highest_score = score
winning_word = word
break
elif score == highest_score and len(word) < len(winning_word):
highest_score = score
winning_word = word
break

best_word = (winning_word, highest_score)
return best_word
3 changes: 3 additions & 0 deletions tests/test_wave_01.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def test_draw_letters_draws_ten():
# Assert
assert len(letters) == 10

#@pytest.mark.skip(reason="no way of currently testing this")
Copy link

Choose a reason for hiding this comment

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

Would be good to delete these lines in a commit

def test_draw_letters_is_list_of_letter_strings():
# Arrange/Act
letters = draw_letters()
Expand All @@ -49,6 +50,7 @@ def test_draw_letters_is_list_of_letter_strings():
assert type(elem) == str
assert len(elem) == 1

#@pytest.mark.skip(reason="no way of currently testing this")
def test_letter_not_selected_too_many_times():

for i in range(1000):
Expand All @@ -66,6 +68,7 @@ def test_letter_not_selected_too_many_times():
for letter in letters:
assert letter_freq[letter] <= LETTER_POOL[letter]

#@pytest.mark.skip(reason="no way of currently testing this")
def test_draw_letters_returns_different_hands():
# Arrange/Act
hand1 = draw_letters()
Expand Down
4 changes: 4 additions & 0 deletions tests/test_wave_02.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def test_uses_available_letters_true_word_in_letter_bank():
# Assert
assert is_valid == True

#@pytest.mark.skip(reason="no way of currently testing this")
def test_uses_available_letters_false_word_in_letter_bank():
# Arrange
letters = ["D", "O", "X", "X", "X", "X", "X", "X", "X", "X"]
Expand All @@ -24,6 +25,7 @@ def test_uses_available_letters_false_word_in_letter_bank():
# Assert
assert is_valid == False

#@pytest.mark.skip(reason="no way of currently testing this")
def test_uses_available_letters_false_word_overuses_letter():
# Arrange
letters = ["A", "X", "X", "X", "X", "X", "X", "X", "X", "X"]
Expand All @@ -35,6 +37,7 @@ def test_uses_available_letters_false_word_overuses_letter():
# Assert
assert is_valid == False

#@pytest.mark.skip(reason="no way of currently testing this")
def test_uses_available_letters_does_not_change_letter_bank():
# Arrange
letters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]
Expand All @@ -48,6 +51,7 @@ def test_uses_available_letters_does_not_change_letter_bank():
assert is_valid == True
assert letters == letters_copy

#pytest.mark.skip(reason="no way of currently testing this")
def test_uses_available_letters_ignores_case():
# Arrange
letters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]
Expand Down
3 changes: 3 additions & 0 deletions tests/test_wave_03.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@ def test_score_word_accurate():
assert score_word("DOG") == 5
assert score_word("WHIMSY") == 17

#@pytest.mark.skip(reason="no way of currently testing this")
def test_score_word_accurate_ignores_case():
# Assert
assert score_word("a") == 1
assert score_word("dog") == 5
assert score_word("wHiMsY") == 17

#@pytest.mark.skip(reason="no way of currently testing this")
def test_score_zero_for_empty():
# Assert
assert score_word("") == 0

#@pytest.mark.skip(reason="no way of currently testing this")
def test_score_extra_points_for_seven_or_longer():
# Assert
assert score_word("XXXXXXX") == 64
Expand Down
9 changes: 9 additions & 0 deletions tests/test_wave_04.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def test_get_highest_word_score_accurate():
assert best_word[0] == "XXXX"
assert best_word[1] == 32

#@pytest.mark.skip(reason="no way of currently testing this")
def test_get_highest_word_score_accurate_unsorted_list():
# Arrange
words = ["XXX", "XXXX", "XX", "X"]
Expand All @@ -25,6 +26,7 @@ def test_get_highest_word_score_accurate_unsorted_list():
assert best_word[0] == "XXXX"
assert best_word[1] == 32

#@pytest.mark.skip(reason="no way of currently testing this")
def test_get_highest_word_tie_prefers_shorter_word():
# Arrange
words = ["MMMM", "WWW"]
Expand All @@ -38,6 +40,7 @@ def test_get_highest_word_tie_prefers_shorter_word():
assert best_word[0] == "WWW"
assert best_word[1] == 12

#@pytest.mark.skip(reason="no way of currently testing this")
def test_get_highest_word_tie_prefers_shorter_word_unsorted_list():
# Arrange
words = ["WWW", "MMMM"]
Expand All @@ -51,6 +54,7 @@ def test_get_highest_word_tie_prefers_shorter_word_unsorted_list():
assert best_word[0] == "WWW"
assert best_word[1] == 12

#@pytest.mark.skip(reason="no way of currently testing this")
def test_get_highest_word_tie_prefers_ten_letters():
# Arrange
words = ["AAAAAAAAAA", "BBBBBB"]
Expand All @@ -62,6 +66,7 @@ def test_get_highest_word_tie_prefers_ten_letters():
assert best_word[0] == "AAAAAAAAAA"
assert best_word[1] == 18

#@pytest.mark.skip(reason="no way of currently testing this")
def test_get_highest_word_tie_prefers_ten_letters_unsorted_list():
# Arrange
words = ["BBBBBB", "AAAAAAAAAA"]
Expand All @@ -73,6 +78,7 @@ def test_get_highest_word_tie_prefers_ten_letters_unsorted_list():
assert best_word[0] == "AAAAAAAAAA"
assert best_word[1] == 18

#@pytest.mark.skip(reason="no way of currently testing this")
def test_get_highest_word_tie_same_length_prefers_first():
# Arrange
words = ["AAAAAAAAAA", "EEEEEEEEEE"]
Expand All @@ -86,6 +92,7 @@ def test_get_highest_word_tie_same_length_prefers_first():
assert best_word[0] == words[0]
assert best_word[1] == 18

#@pytest.mark.skip(reason="no way of currently testing this")
def test_get_highest_word_many_ties_pick_first_ten_letters():
# Arrange
words = ["JQ", "FHQ", "AAAAAAAAAA", "BBBBBB", "TTTTTTTTTT"]
Expand All @@ -97,6 +104,7 @@ def test_get_highest_word_many_ties_pick_first_ten_letters():
assert best_word[0] == "AAAAAAAAAA"
assert best_word[1] == 18

#@pytest.mark.skip(reason="no way of currently testing this")
def test_get_highest_word_many_ties_pick_shortest():
# Arrange
words = ["BBBBBB", "AAAAAAAAD", "JQ", "KFHK"]
Expand All @@ -108,6 +116,7 @@ def test_get_highest_word_many_ties_pick_shortest():
assert best_word[0] == "JQ"
assert best_word[1] == 18

#@pytest.mark.skip(reason="no way of currently testing this")
def test_get_highest_word_does_not_return_early_after_first_tiebreaker():
# Arrange
words = ["WWW", "MMMM", "BBBBBB", "AAAAAAAAD", "JQ", "KFHK"]
Expand Down