VisualWorks - Find characters in string and replace them

299 Views Asked by At

I have one word. At first, instead of letters, there are only ?. So for example, word 'town' would be shown like this '????'. Then user guesses letter and if he nails it, it changes from ? to actual letter. For example, if he guesses t, it would look like this 't???'.

The problem is, that I have no idea, how to go through string and divide it to characters. And if I somehow do it, I cannot change it in the new string.

Should look somehow like this.

word do: [ :c | 
          c = guessedChar
                ifTrue[mask := mask, guessedChar]
                ifFalse[mask := mask, '?']
            ].

mask is initialized to nil, because the word length can change and word is String. guessedChar is connected to inputField, however it contains only one character at a time.

And would it be better, do it once for every guessedChar or hold collection of all guessed characters and run it over every time?

3

There are 3 best solutions below

9
Leandro Caniglia On

You could use

word doWithIndex: [:c :i | c = guess ifTrue: [mask at: i put: c]]

which is equivalent to:

i := 1.
word do: [:c |
  c = guess ifTrue: [mask at: i put: c].
  i := i + 1]

except that you don't have to initialize and increment i (which is a little bit more error prone, and more verbose)


Addendum

Given that instances of String cannot grow or change their size, which is immutable, I assume that what might change is the variable word. In that case you should initialize mask accordingly, so both strings keep always the same length. Like this:

 word: aString
   word := aString.
   mask := word copy atAllPut: $?

If you also want to preserve the characters already guessed:

 word: aString
   | guessed |
   word := aString.
   guessed := mask reject: [:c | c = $?].
   mask := word copy atAllPut: $?.
   guessed do: [:c | self try: c].

Where #try: is the method we had before

try: aCharacter
  word doWithIndex: [:c :i | c = aCharacter ifTrue: [mask at: i put: c]]

(you may want to uppercase things if required)

6
Karsten On

A String is a Collection of Character objects. So you can use the same methods that apply to other collections, too (like #select:, #collect: or #reject:)

guessedCharacters := 'ts'.
mask := word collect:[:each | (guessedCharacters includes: each)
                                 ifTrue:[each]
                                 ifFalse:[$?]].

Please note that 't' is a String with the Character t. A Character can be written with $ prefix as literal character $t.

As String is a subclass of SequenceableCollection you can concatenate two Strings via ,. You cannot however concatenate a String and a Character.

Instead you could use #copyWith: to append a Character to a String. The result is a new String, it wouldn't modify the existing instance.

0
Jim Sawyer On
"initially"
actualWord := 'phlebotomy'.
actualMask := actualWord collect: [:ch| $?].

"after each guess"
word := actualWord readStream.
mask := actualMask readStream.
newMask := WriteStream on: String new.
[ word atEnd
] whileFalse:
    [ nextCh := word next = guessedCharcter
                  ifTrue: [mask skip. guessedCharacter]
                  ifFalse: [mask next].
      newMask nextPut: nextCh
    ].
actualMask := newMask contents