2

I'm trying to program a vocabulary game. Let me say at the outset that I'm a completely rookie programmer!

I am using a regular expression to hide a word, which I have to guess.

Take for example the chosen_word:

'TO CRANK (STH) UP'

With RegExs I manage to hide the keywords and I have hidden_word as follows:

TO _ _ _ _ _ (STH) _ _

With the RegEx findall() and the filter() methods I get a list of hidden_letters:

['C', 'R', 'A', 'N', 'K', 'U', 'P']

The idea is that we now choose one letter from this list randomly to reveal to the user:

chosen_letter = random.choice(hidden_letters)

n = hidden_letters.index(chosen_letter) + 1

Now the chosen letter's n maps beautifully onto its corresponding underscores in the hidden_word. For example, if the game has randomly chosen the letter 'R', then n will be '2', and in the hidden_word this letter's position is at the 2nd underscore.

I am stuck at this stage now. I want to replace the nth underscore of hidden_word with the chosen_letter. I'm thinking of using either the .replace() method, or the RegEx re.sub() method, but I don't know how to start off.

Could someone please give me hints?

Many thanks in advance!!

  • what is the desired behavior if there are repeated letters in the hidden word? Also, in your example, what is the hidden word? – Pierre D Jan 22 '21 at 17:23
  • the hidden word is ' TO _ _ _ _ _ (STH) _ _ ', which is what is displayed to the user. And yes, I've thought about the problem with repeated letters - still haven't got there yet though haha but the desired behaviour is that it replaces the first occurrence of the repeated letter, and that that one is no longer considered in subsequent iterations. – Xavier Villà Aguilar Jan 22 '21 at 17:31
  • perhaps some examples with the desired behavior would help. It would also help you describe the exact desired behavior in various corner cases. – Pierre D Jan 22 '21 at 17:36
  • You need two lists: one with the full phrase and one with underscores. When an index is requested then replace the underscore in the second list with the value from the first list and output to user. – MonkeyZeus Jan 22 '21 at 17:42

2 Answers2

2

I would approach this a bit differently:

The state at each point in time is defined by:

  • the full phrase (to be guessed),
  • a set of hidden words,
  • letters guessed so far (or to be revealed to the user).

The you can define a show() function with these three quantities:

def show(phrase, hidden_words, letters_guessed):
    parts = [
        ''.join([
            c if c in letters_guessed else '-' for c in w
        ]) if w in hidden_words else w
        for w in phrase.split()
    ]
    return ' '.join(parts)

With this, you can write tests, including doctests. That will make your code much easier to document, debug, test and use.

Some examples (which could be included as doctests in the docstring of show):

phrase = 'TO PIPE (STH) UP AND PIPE DOWN'
hidden_words = {'PIPE', 'UP'}

>>> show(phrase, hidden_words, {})
'TO ---- (STH) -- AND ---- DOWN'

>>> show(phrase, hidden_words, set('I'))
'TO -I-- (STH) -- AND -I-- DOWN'

>>> show(phrase, hidden_words, set('PI'))
'TO PIP- (STH) -P AND PIP- DOWN'
Pierre D
  • 24,012
  • 7
  • 60
  • 96
1

Given you're a rookie, I'd thought throw my hat in and do it without regex and attempt to explain. Maybe only read this once you've had your go. Your question title can be answered with:

def replace_nth_char(string, n, replacement):
    n -= 1
    l_string = list(string)
    l_string[n] = replacement
    return ''.join(l_string)

It converts the string to a list of letters which you can replace by index n and then joins it back up.

I've also given the rest a go to show you more python options.

As you're managing the state, you might want to think about using a class. It helps you wrap all the functions and attributes together into one object with a purpose - playing your Vocabulary Game. I recommend looking into them. Here is one for the game:

import random


class VocabularyGame():
    def __init__(self, chosen_words, hidden_words):
        self.chosen_words = chosen_words
        self.hidden_words = hidden_words
        self.hidden_letters = list(set(''.join(words)))
        self.masked_sentence = self.mask_words(chosen_words, hidden_words)
        print(f"Game start: {self.masked_sentence}")
        
    def mask_words(self, sentence, masks):
        return ' '.join(['_'*len(w) if w in masks else w for w in sentence.split(' ')])
    
    def try_letter(self, letter=None):
        if letter is None:
            letter = random.choice(self.hidden_letters)
        self.masked_sentence = ''.join(
            [c if c== letter else m for m, c in zip(self.masked_sentence, self.chosen_words)]
        )
        self.hidden_letters = [l for l in self.hidden_letters if l != letter]
        print(f"Trying letter {letter}...\nRemaining letters: {self.masked_sentence}")

The __init__ section runs whenever we make new game instances and takes three arguments, (self, chosen_words, hidden_words). The use of self references the current class instance (or game) and we can use it to set and retrieve attributes to the class, in this case, to remember our words and sentences.

    def __init__(self, chosen_words, hidden_words):
        self.chosen_words = chosen_words
        self.hidden_words = hidden_words
        self.hidden_letters = list(set(''.join(hidden_words)))
        self.masked_sentence = self.mask_words(chosen_words, hidden_words)
        print(f"Game start: {self.masked_sentence}")

list(set(''.join(words))) gets all unique letters in the hidden words by joining them into one string and using sets to convert them into the unique letters. I convert this back into a list for ease of use later.

We then apply a function to mask the words with '_'.

    def mask_words(self, sentence, masks):
        return ' '.join(['_'*len(w) if w in masks else w for w in sentence.split(' ')])

This goes through each word in the sentence and replaces it with '_' for the length of the word, if it exists in hidden words. Then it joins it back up. Now we have our start state.

The last thing to do is to try a letter. We do this by defining a method (function on a class) def try_letter(self, letter=None):. If no letter is provided, we pick a random one from the unique missing letters we defined earlier. The we go through each letter in the original sentence and masked sentence together using zip and when the original letter is our chosen letter, we replace the one in our masked sentence.

        self.masked_sentence = ''.join(
            [c if c==letter else m for m, c in zip(self.masked_sentence, self.chosen_words)]
        )

Then remove the letter form our hidden letters list:

self.hidden_letters = [l for l in self.hidden_letters if l != letter]

Finally, we print result out using f-strings. Now, we can play the game!

chosen_word = "TO CRANK (STH) UP"
words = ['CRANK', 'UP']

game = VocabularyGame(chosen_word, words)

Outputs: Game start: TO _____ (STH) __

Trying a letter 7 times for i in range(7): game.try_letter() Outputs:

Trying letter N...
Remaining letters: TO ___N_ (STH) __
Trying letter K...
Remaining letters: TO ___NK (STH) __
Trying letter R...
Remaining letters: TO _R_NK (STH) __
Trying letter P...
Remaining letters: TO _R_NK (STH) _P
Trying letter C...
Remaining letters: TO CR_NK (STH) _P
Trying letter U...
Remaining letters: TO CR_NK (STH) UP
Trying letter A...
Remaining letters: TO CRANK (STH) UP
chsws
  • 403
  • 2
  • 6
  • Many thanks for the comprehensive reply chsws! I'm digesting all the Class stuff, but in the meantime, the more basic point (your definition of the replace_nth_char() method)... I feed the function a value 'n', which I have worked out, but doesn't map well to the [n] index. Let's consider the word 'APPLE', which is all hidden as (_ _ _ _ _ ). I've chosen to reveal the letter 'L', which is in n=4. The hidden string passed to the function, after turning it into a list, is ['_', ' ', '_', ' ', '_', ' ', '_', ' ', '_', ' '], so l_string[4] is not the 4th underscore... – Xavier Villà Aguilar Jan 31 '21 at 21:20
  • Hi @XavierVillàAguilar, you'll have to supply some code for me to comment. The function works fine: `apple_masked = '_'*5 replace_nth_char(apple_masked,3,'L')` Outputs: '____L_' Indexes in python start at 0 so the 4th character is position 3. I amended the function above. – chsws Feb 02 '21 at 21:56