Skip to content

C22 Phoenix Class - Luqi Xie#35

Open
shiqipaper wants to merge 6 commits into
Ada-C22:mainfrom
shiqipaper:main
Open

C22 Phoenix Class - Luqi Xie#35
shiqipaper wants to merge 6 commits into
Ada-C22:mainfrom
shiqipaper:main

Conversation

@shiqipaper

Copy link
Copy Markdown

No description provided.

@anselrognlie anselrognlie left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks good! Please review my comments, and let me know if you have any questions. Slack, our next 1:1, or office hours are preferred (rather than replying to my comments in GitHub) as I don't have email notifications turned on. Nice job!

Comment thread adagrams/game.py

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎉 Nice job doing commits regularly as you finished each wave. A commit after each wave is a good target to aim for with projects. When working on your own code, it will be up to you to decide when it's a good time to commit, but the more we practice now, the more we'll be in the habit of committing. Otherwise, it's easy to forget.

Comment thread adagrams/game.py
Comment on lines +31 to +39
SCORE_CHART = {
1 : ["A", "E", "I", "O", "U", "L", "N", "R", "S", "T"],
2 : ["D", "G"],
3 : ["B", "C", "M", "P"],
4 : ["F", "H", "V", "W", "Y"],
5 : ["K"],
8 : ["J", "X"],
10 : ["Q", "Z"]
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

👍 I like that you represented the score chart with a global CONSTANT. Moving large data objects out of a function can improve the readability of the function by focusing on the logic.

However, we should be sure to store the data in a way that's convenient for our code to use. While the table in the README listed the scores as we see here, in practice, we need to look up the score for a letter, not which letters are associated with a score. With a dictionary, using the value we want to look up by as the key will lead to more efficient utilization of the dictionary. This would mean storing the scores more like

SCORE_CHART = {
    'A': 1,
    'B': 3,
    'C': 3,
    ...
}

Comment thread adagrams/game.py
Comment on lines +42 to +45
letter_pool = []
for letter, count in LETTER_POOL.items():
for _ in range(count):
letter_pool.append(letter)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This code does a nice job of building up a list of all the available tiles. We could make this a little clearer by moving the logic to a helper function and giving it a good descriptive name, maybe build_tile_pile.

Having the variable here be letter_pool when we already have the global LETTER_POOL could be confusing. It's even a little more confusing in this case than in general because the variables are related. Consider a name such as tile_pile or maybe all_tiles.

Comment thread adagrams/game.py
letter_pool.append(letter)

hand_of_letters = []
while len(hand_of_letters) < 10:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This line effectively has the meaning "run until we've picked 10 tiles." Some approaches to picking a tile aren't guaranteed to pick a tile each time through the loop, so phrasing the loop that way can be necessary. In your case, your loop block will always pick a tile each iteration, and so another way we think of this loop is, run the code 10 times. We could write that as

    for _ in range(10):

similarly to how you did the pile building a little earlier.

We could also consider giving a name to 10, like HAND_SIZE so that it's clear what this number represents here. For a small project like this, we probably know what all the numbers represent, but for larger systems, having unnamed numbers showing up all over the place makes the code more difficult to understand.

Comment thread adagrams/game.py
while len(hand_of_letters) < 10:
letter_index = random.randint(0, len(letter_pool)-1)
hand_of_letters.append(letter_pool[letter_index])
letter_pool.pop(letter_index)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

👍 Using pop (rather than remove) lets us extract precisely the letter that was randomly picked. However, we'll see that popping from an aribtrary location has the same performance as using remove (it is related to the length of the list). But if we know exactly which position we want to pop, and the order of things in the list doesn't really matter, we can use a trick to pop from an aribitrary location in a way that doesn't depend on the length of the list. We can swap the value we want to pop to the end of the list, then pop from the end (popping from the end doesn't depend on the length of the list). Consider something like

        last_pos = len(letter_pool) - 1
        letter_pool[last_pos], letter_pool[letter_index] = letter_pool[letter_index], letter_pool[last_pos]
        letter_pool.pop()

Comment thread adagrams/game.py
Comment on lines +92 to +94
if score > highest_score:
highest_score = score
best_word = [word, score]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

👍 A higher score word always replaces the best word so far.

Comment thread adagrams/game.py
score = score_word(word)
if score > highest_score:
highest_score = score
best_word = [word, score]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Since we're always replacing the best_word value (not changing a value within it) we could use a tuple throughout

            best_word = (word, score)  # parens are optional

But persoanlly, since we only ever update the score part here (where there's a new high score), but we replace the word in several palces, I prefer using two separate variaables until we reach the end, and only put them together at the return.

Comment thread adagrams/game.py
Comment on lines +96 to +99
if len(word) == 10 and len(best_word[0])!= 10:
best_word = [word, score]
elif len(word) < len(best_word[0]) and len(best_word[0]) != 10:
best_word = [word, score]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Notice that both of these conditions check that the winning length isn't already 10 (another possible CONSTANT). A word can only beat another tied word if the word we already found wasn't length 10 already. So we could move the and len(best_word[0]) != 10 for both of these up to the elif score == highest_score line, basically encoding the check as "if the new word is tied and the old word can possibly be beaten".

But also, notice how complicated all of these checks are!

A different approach is to break up the parts of the logic into different chunks rather than trying to do it all in one loop. We can think of another approach like:

  1. Find the max score (any winning word must have this score)
  2. Make a list of all the words that have the max score
  3. If we found only a single word, that's the winner
  4. Otherwise, look at each word
  5. If we find a 10 letter word, that's the winner (nothing would be able to beat it)
  6. Otherwise, find the shortest word, that's the winner

The tie-breaking logic becomes a little easier thinking of the problem this way! Plus, each of those steps might be able to be written in their own helper function, making it easier to read the intention behind the logic, rather than needing to work backwards from the code to the intent, by which I mean it would be challenging to deduce what the rules to pick the winning word are solely from reading this code.

Comment thread adagrams/game.py
Comment on lines +100 to +101
elif len(word) == len(best_word[0]):
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Since this check doesn't do anything (it just returns to the top of the loop), but that's what would happen even without this condition, it's safe to remove this condition from the logic entirely.

Comment thread adagrams/game.py
highest_score = score
best_word = [word, score]
elif score == highest_score:
if len(word) == 10 and len(best_word[0])!= 10:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nit: spacing around binary operator !=

            if len(word) == 10 and len(best_word[0]) != 10:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants