I'm making a cryptogram word game (decipher a famous quote) for my portfolio. I'm down to the last major hurdle: confirming that the puzzle is solved.
I don't have the reputation points to post a picture that would really help clarify, but think Wheel of Fortune where each white box is a separate input. I don't have them attached to any state because there can easily be over 100 inputs per puzzle. I've been manipulating them through className to autofill the inputs accordingly (i.e. if a player enters an 'e' in one input all similar inputs will be filled with 'e')
I thought I had completion confirmation figured out but quickly realized how flawed my solution was. I was creating a pendingSolution state that was a string and trying to populate it appropriately through an OnChange function in the inputs. I was checking this every time the value changed to see if it matched with the solution string, but it turns out I'm not setting the state of the pendingSolution string correctly to update the player's progress in the puzzle. I'm not sure if this is even the best approach, so I'm looking for either some help with fixing my updatePendingSolution function, or advice on a better way to approach this problem.
Example:
- "*h*** *o* *o* *o** h***!" <-- pendingSolution
- "Thank you for your help!" <-- solution
These are the functions to handle the OnChange of inputs and also to autofill up to three random letters as onClick hints.
const handleChange = (e) => {
let letter = document.getElementsByClassName(e.target.className)
for (let i=0; i < letter.length; i++) {
letter[i].value = e.target.value
letter[i].style.color = "darkblue"
}
setPendingSolution(pendingSolution.split('').map((char, i) => char.replace(/\*/, updatePendingSolution(char, e.target.value, i))).join(''))
}
const handleClick = () => {
const rand = Math.floor(Math.random() * 26)
if(solution === pendingSolution) return
if (randLetters.includes(rand)) {
handleClick()
} else {
setRandLetters([...randLetters, rand])
let letter = document.getElementsByClassName(`char_input ${shuffledAlphabet[rand].toLowerCase()}`)
if (letter.length === 0) {
letter = document.getElementsByClassName(`char_input ${shuffledAlphabet[rand]}`)
if(letter.length === 0) handleClick()
} else {
if (letter[0].value.toUpperCase() === alphabet[rand]) {
handleClick()
} else {
for (let i=0; i < letter.length; i++) {
letter[i].value = alphabet[rand]
letter[i].disabled = true
letter[i].style.color = 'green';
setPendingSolution(pendingSolution.split('').map((char, i) => char.replace('*', updatePendingSolution(char, alphabet[rand], i))).join(''))
}
}
}
setHints([...hints, alphabet[rand]])
}
}
This is the part that I need the help with. It is the function I'm working on to update the pendingSolution state whenever a player uses a hint or changes an input.
useEffect(() => {
if (solution && pendingSolution) {
if (solution === pendingSolution) {
alert("You won!!!")
}
}
}, [pendingSolution])
const updatePendingSolution = (char, value, i) => {
if (solution[i].match(value.toUpperCase())) {
return value.toUpperCase()
} else if (solution[i].match(value.toLowerCase())) {
return value.toLowerCase()
} else return char
}
And this is the code that renders the quote and name components:
function EncryptedText({ divName, onChange, words } ) {
return (
<div className={divName}>{words.split(" ").map((word, i) => {
return (
<div className="word_div" key={i}> {word.split("").map((char, j) => {
return (
<div key={j} className="char_container">
{char.match(/[A-Za-z]/) ?
char.match(/[A-Z]/) ?
<div>
<p className="puzzle_p uppercase"><input className={`char_input ${char.toLowerCase()}`} maxLength="1" onChange={onChange} type="text" /></p>
</div>
:<div>
<p className="puzzle_p lowercase"><input className={`char_input ${char}`} maxLength="1" onChange={onChange} type="text" /></p>
</div>
: <p className="puzzle_p">{char}</p>
}
<p className="puzzle_p">{char}</p>
</div>
)
})}</div>
)
})}</div>
)
}
export default EncryptedText