mediumReact Native#135

Word Guessing Game (Adivinar Pokemon)

Prompt

Implement the logic for a Hangman-style word guessing game.

Write three pure functions:

  1. getDisplay(word, guessed) — returns the word with unguessed letters as underscores (e.g. "P _ K A _ H U")
  2. processGuess(letter, state) — processes a letter guess, returns new { guessed, wrong, gameOver, won }
  3. isGameOver(state) — returns { won: boolean, lost: boolean }

The word is "PIKACHU", max 6 wrong guesses.

Solution

const POKEMON = 'PIKACHU'
const MAX_WRONG = 6

function getDisplay(word, guessed) {
  return word.split('').map(l => guessed.includes(l) ? l : '_').join(' ')
}
function processGuess(letter, { guessed, wrong }) {
  if (guessed.includes(letter)) return { guessed, wrong, gameOver: false, won: false }
  const newGuessed = [...guessed, letter]
  const correct = POKEMON.includes(letter)
  const newWrong = correct ? wrong : wrong + 1
  const display = getDisplay(POKEMON, newGuessed)
  const won = !display.includes('_')
  const gameOver = won || newWrong >= MAX_WRONG
  return { guessed: newGuessed, wrong: newWrong, gameOver, won }
}
function isGameOver({ wrong, won }) {
  return { won, lost: wrong >= MAX_WRONG }
}
Mentor's take

processGuess(letter, state) → newState is a reducer in disguise, and that's the point: the whole game is a state-transition function over { guessed, wrong }. The component becomes a thin shell — onPress={l => setState(s => processGuess(l, s))} — or drops straight into useReducer unchanged. Pure transitions mean every rule below is a one-line unit test, no renderer involved.

The rules encoded in the transition:

  • Repeat guesses are a no-op — the early return keeps a double-tap on the same key from burning a wrong guess, and returns without cloning since nothing changed.
  • Wrong only increments on a miss, compared against MAX_WRONG, a named constant — the same number the UI uses to draw remaining lives, defined once.
  • Win is derived, not tracked: getDisplay with the new guesses has no underscores left ⇒ won. Reusing the display function as the win predicate guarantees the screen and the game state can never disagree — there is no second bookkeeping structure to drift.
  • New state is built with spread ([...guessed, letter]); the incoming state object is never touched.

isGameOver stays trivial because the transition already did the work — it just projects { won, lost } for the end screen.

Red flag: maintaining a separate won boolean updated in a different code path from guessed. Two writes for one fact is exactly how a game shows the win banner while the display still has underscores.

Say it: "processGuess is a pure reducer — repeat guesses no-op, wrong counts against a named max, and the win is derived from the display so UI and state can't diverge."