Wordladder Journal

Oct 05, 2025
Episode 1
[ball] -> [[ball,tall],[ball,bull],[ball,fall]...]`
[ball,tall] -> [ball,tall,fall], [ball,tall,till], [ball,tall,tale], ...and so on
walk :: Set String -> String -> Array (Array String) -> Set String -> Maybe (Array String)
walk dictionary target queue visited = ??
getAllPossibilities :: Set String -> String -> Array String
getAllPossibilities dictionary wrd =
  let
    alphabets = split (Pattern "") "abcdefghijklmnopqrstuvwxyz"
    wrdAsArray = split (Pattern "") wrd
    indices = 0 .. (length wrd - 1)
    possibilities = do
      idx <- indices
      c <- alphabets
      let newWord = mapWithIndex (\i ch -> if i == idx then c else ch) wrdAsArray
      guard (isValidWord dictionary (joinWith "" newWord) && (joinWith "" newWord) /= wrd)
      pure (joinWith "" newWord)
  in
    possibilities
getShortestPath :: Set String -> String -> String -> Maybe (Tuple Int (Array String))
getShortestPath dictionary source target =
  if length source /= length target then
    Nothing
  else
    let
      res = walk dictionary target [ [ source ] ] (Set.singleton source)
    in
      map (\path -> Tuple (A.length path - 1) path) res
Episode 2
type GameState =
  { gameStatus :: GameStatus
  , dictionary :: Set String
  , gameWords :: Tuple String String
  , lastPlayedWord :: String
  , gameType :: GameType
  , playedWords :: Array String
  }

data GameType = PvC | PvP

data GameStatus = Play Player | Win Player | Over String
data Player = User | Computer
gameLoop :: GameState -> Aff GameState
rankByClosest :: String -> Array String -> Array String
rankByClosest target wrds = sortBy (comparing (hammingDistance target)) wrds

hammingDistance :: String -> String -> Int
hammingDistance wrd1 wrd2 =
  let
    chars1 = split (Pattern "") wrd1
    chars2 = split (Pattern "") wrd2
    differences = zipWith (\c1 c2 -> if c1 == c2 then 0 else 1) chars1 chars2
  in
    sum differences

Episode 3
type GameState = { ... }
data GameEffect = Effect1 | Effect2 | ...

updateGame :: GameState -> Tuple GameState (Array GameEffect)

handleEffect :: GameState -> GameEffect -> Aff (Tuple GameState (Array GameEffect))
handleEffects :: GameState -> Array GameEffect -> Aff GameState
handleEffects = -- some fold function that reduces Array GameEffect into a GameState with side-effects run correctly

gameLoop :: GameState -> Aff Unit
gameLoop gameState = do
	let (Tuple newState effects) = updateGame gameState
	finalState <- handleEffects newState effects
	gameLoop finalState
handleEffects :: GameState -> Array GameEffect -> Aff GameState
handleEffects state effects = go effects state
  where
  go :: Array GameEffect -> GameState -> Aff GameState
  go effs s = case A.head effs of
    Nothing -> pure $ s
    Just e -> do
      Tuple s' newEffs <- handleEffect s e
      go (newEffs <> (fromMaybe [] $ A.tail effs)) s'
data GameEffect
  = Log String
  | Exit Int
  | AskUserToChooseDifficulty -- <- this one
  -- | others
handleEffect :: GameState -> GameEffect -> Aff (Tuple GameState (Array GameEffect))
handleEffect state AskUserToChooseDifficulty = do
  input <- readLine $ colorInfo "Choose word length (3, 4, or 5)"
  if (elem input [ "3", "4", "5" ]) then
    pure $ Tuple (state { currentState = DifficultySet (fromMaybe 3 (fromString input)) }) []
  else do
    log $ colorError "Invalid input. Please enter 3, 4, or 5."
    pure $ Tuple state [ AskUserToChooseDifficulty ]
updateGameState :: GameState -> Tuple GameState (Array GameEffect)

Update:

Episode 4
data Free f a = Pure a | Free (f (Free f a))
updateGameState :: GameState -> Writer (Array GameEffect) GameState
updateGameState state = case state.currentState of
  NotInitialized -> do
    tell [ AskUserToChooseDifficulty ]
    pure state

  DifficultySet int -> do
    tell [ InitializeGame ]
    pure (state { dictionary = dict, wordLength = int })
    where
    dict = getAllWordsByLen int

  -- and more
loop state = do
        let Tuple newState effects = runWriter (updateGameState state)
        finalState <- handleEffects newState effects
        loop finalState
    loop initialState

-- where runWriter is:
runWriter :: Writer w a -> Tuple a w
-- where
-- w is Array GameEffect
-- a is GameState

Update

handleEffects :: GameState -> Array GameEffect -> Aff GameState
handleEffects initialState initialEffects =
  tailRecM go (Tuple initialState initialEffects)
  where
  go :: (Tuple GameState (Array GameEffect)) -> Aff (Step (Tuple GameState (Array GameEffect)) GameState)
  go (Tuple state effects) = case A.uncons effects of
    Nothing -> pure $ Done state
    Just { head: eff, tail: rest } -> do
      Tuple state' newEffects <- handleEffect state eff
      pure $ Loop (Tuple state' (newEffects <> rest))

data Step a b = Done b | Loop a