preritdas / wordle

A backend, deployable recreation of Wordle.
https://wordle.preritdas.com
4 stars 5 forks source link

Wordle hint response logic #1

Closed qlitre closed 2 years ago

qlitre commented 2 years ago

bug

Hello. This photo was taken when I actually played Wordle. The answer at this time was "ULTRA". In this case, the second letter A of "MAMMA" seems to be judged as gray. Similarly, for example, when the answer is ROBIN and WORRY is entered, the fourth R seems to be a gray judgment.

I forked your project and tried to write a code like this.

so,I checked green and yellow in order and tried to remove the hit words.

class Wordle:
    """Main Wordle game taking arguments of the answer and whether to check against the dictionary."""

    def __init__(self, word: str, real_words: bool):
        self.word = word.upper()
        self.real_words = real_words

    def response(self, guess: str):
        response = []
        answer = [c for c in self.word]
        guess = [c for c in guess]

        # correct(green) check
        for pos, char in enumerate(guess):
            if answer[pos] == char:
                # append hint
                hint = f"*{guess[pos]}*   "
                response.append({'pos': pos, 'hint': hint})
                # remove char
                answer[pos] = ''
                guess[pos] = ''

        # present(yellow) check
        for pos, char in enumerate(guess):
            if not char:
                continue
            if char in answer:
                hint = char + "   "
                response.append({'pos': pos, 'hint': hint})
                # remove char
                guess[pos] = ''
                for _pos, _char in enumerate(answer):
                    if _char == char:
                        answer[_pos] = ''
                        break

        # remains all absent(gray)
        for pos, char in enumerate(guess):
            if not char:
                continue
            hint = guess[pos].lower() + "   "
            response.append({'pos': pos, 'hint': hint})

        # sort by pos
        response.sort(key=lambda x: x['pos'])

        return [r['hint'] for r in response]
...
import wordle

game = wordle.Wordle(word='WRYYY', real_words=False)
game.run()
>>>
Attempt 1 >>> yyaaa
Y   Y   a   a   a   
Attempt 2 >>> rrryy
r   *R*   r   *Y*   *Y*   
Attempt 3 >>> yyyaa
Y   Y   *Y*   a   a   
Attempt 4 >>> yyayy
Y   y   a   *Y*   *Y*   
Attempt 5 >>> wryyy
*W*   *R*   *Y*   *Y*   *Y*   
Congratulations, you passed the wordle in 5 tries.
preritdas commented 2 years ago

Hello, I've studied your code and it works as expected. And you're absolutely right about the 2+ letter condition. Currently, the script can't even handle a case in which a user guesses two of a letter when the word has only one. It will capitalize both instances as if there are two of that letter in the word.

I appreciate you taking the time to rewrite the Wordle class. I will be heading back to the drawing board to see if there's a simpler modification to the code as it stands to fix the issue you brought up, possibly via the implementation of an occurrences list/dictionary. If that's successful I'll resort to your Wordle.response() structure.

Thanks again!

qlitre commented 2 years ago

thank you for your reply. There may be a smarter implementation than using list / dictionary I wrote. I'm looking forward to the update!

preritdas commented 2 years ago

Hi,

An update: I fixed the issue. Here's how:

A new class attribute, self.word_dup, defined as list(self.word) in every iteration attempt. Within each attempt, if a letter is guessed which is in self.word_dup, the letter is removed. I tested and it works.

I'll push the code to GitHub and PyPi in a couple hours. Appreciate your code! I'm still a newbie so it was great to learn a few things from it.

Edit: Code is pushed on master branch and PyPi. Version 1.8.0.

qlitre commented 2 years ago

Thank you for updating the code. I git cloned the code and tried it to work, but it seems to have a bug.

# test.py
import wordle

wordle.Wordle(word='games', real_words=True).run()
>>>
Attempt 1 >>> trees
Traceback (most recent call last):
  File ".../wordle/test.py", line 3, in <module>
    wordle.Wordle(word='games', real_words=True).run()
  File "...\wordle\wordle\wordle.py", line 48, in run
    self.word_dup.remove(guess[j])  # Duplicates
ValueError: list.remove(x): x not in list

I printed the variable in the middle of the loop.

response = []
for j in range(len(guess)):
    print(j, guess[j], self.word_dup)
    ...
>>>
Attempt 1 >>> trees
0 T ['G', 'A', 'M', 'E', 'S']
1 R ['G', 'A', 'M', 'E', 'S']
2 E ['G', 'A', 'M', 'E', 'S']
3 E ['G', 'A', 'M', 'S']
Traceback (most recent call last):
....

The reason is that the 3rd character E was judged as present first, and the 4th character E of "GAMES" was removed from self.word_dup.

Sorry,I figured out how to implement the logic in a single loop, but so far I haven't come up with a good idea...

preritdas commented 2 years ago

Ah. Simple error on my part. The first checker line (45, where the error was raised) was:

if guess[j] in self.word and guess[j] == self.word[j]:

It needed to be:

if guess[j] in self.word_dup and guess[j] == self.word[j]:

Where self.word is replaced with self.word_dup.. I tested it at runtime attempting trees with the answer games and it no longer breaks.

Attempt 1 >>> trees
t   r   E   e   *S*   

Pushing this to GitHub and PyPi as version 1.8.2.

Thanks!

qlitre commented 2 years ago

Thank you for the update. The games and trees pattern solved the problem. However, I think there is still a problem with the ultra and mamma patterns. This is the example I gave in the image.

import wordle

wordle.Wordle(word='ultra', real_words=False).run()
>>>
Attempt 1 >>> mamma
m   A   m   m   a   
...

This is because the second character a is evaluated first and the last a is not included in self.word_dup.

The number of loops will be 2 times, but it seems that it can be solved by doing the following.

# prepare 5 length list
response = ['', '', '', '', '']

# first correct check
for j in range(len(guess)):
    if guess[j] in self.word_dup and guess[j] == self.word[j]:
        response[j] = f"*{guess[j]}*   "
        self.word_dup.remove(guess[j])  # Duplicates

# next present and absent check
for j in range(len(guess)):
    # already response skip
    if response[j]:
        continue
    # it's present(yellow)
    if guess[j] in self.word_dup:
        response[j] = guess[j] + "   "
        self.word_dup.remove(guess[j])  # Duplicates
    # other absent
    else:
        response[j] = guess[j].lower() + "   "
wordle.Wordle(word='games', real_words=False).run()
>>>
Attempt 1 >>> trees
t   r   e   *E*   *S*   

wordle.Wordle(word='ultra', real_words=False).run()
>>>
Attempt 1 >>> mamma
m   a   m   m   *A*   

wordle.Wordle(word='robin', real_words=False).run()
Attempt 1 >>> worry
w   *O*   R   r   y   
preritdas commented 2 years ago

I see what you're saying. The strategy of having response be a len = 5 array of strings makes the most sense in this case and that should've been the structure from the get-go. And I like your solution to the problem––checking for absolute correctness in a separate iterative loop than checking for 'yellow' as you called it and then 'grey'. I used the structure you provided above in the latest release 1.9.3 which is on PyPi and GitHub.

Setup

import wordle

wordle.Wordle(word = 'ultra', real_words = False).run()

Result

Attempt 1 >>> mamma
m   a   m   m   *A*   

Thank you.