The state of things (part 1)

Posted on September 5, 2025 by Alex

So I got to thinking, how would I do something like this in the browser:

    import System.IO
    
    main :: IO ()
    main = do
      putStrLn "let's play Hangman by creating a word to guess:"
      secretWord <- getSpecialLine
      putStrLn "Now try to guess it. Yeah I know you know it!"
      play secretWord
    
    
    -- 1. Use getCh  to build our string
    getSpecialLine :: IO String
    getSpecialLine = do
      c <- getSpecialChar
      if  c == '\n' then do
        putChar c
        return []
      else
        do putChar '-'
           cs <- getSpecialLine
           return (c:cs)
    
    getSpecialChar :: IO Char
    getSpecialChar = do
      hSetEcho stdin False
      c <- getChar
      hSetEcho stdin True
      return c
    
    play :: String -> IO ()
    play word = putStrLn "A games loop begins"

What AI had to say about it:

    <!DOCTYPE html>
    <html>
    <head>
        <title>Hangman Game</title>
        <style>
            body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; }
            .hidden-input { position: absolute; left: -9999px; }
            .dashes { letter-spacing: 5px; font-size: 24px; margin: 10px 0; }
        </style>
    </head>
    <body>
        <div id="game">
            <div id="message"></div>
            <input type="text" id="hiddenInput" class="hidden-input" maxlength="1">
            <div id="dashes" class="dashes"></div>
        </div>
    
        <script>
            let secretWord = '';
            let currentDashes = '';
    
            async function getSpecialChar() {
                return new Promise((resolve) => {
                    const input = document.getElementById('hiddenInput');
                    input.value = '';
                    input.focus();
                    
                    const handler = (e) => {
                        if (e.key.length === 1) {
                            e.preventDefault();
                            input.removeEventListener('keydown', handler);
                            resolve(e.key);
                        }
                    };
                    
                    input.addEventListener('keydown', handler);
                });
            }
    
            async function getSpecialLine() {
                let word = '';
                currentDashes = '';
                document.getElementById('dashes').textContent = currentDashes;
                
                updateMessage('Type the secret word (press Enter when done):');
                
                while (true) {
                    const char = await getSpecialChar();
                    
                    if (char === 'Enter') {
                        updateMessage('');
                        document.getElementById('dashes').textContent = '';
                        break;
                    } else if (char.length === 1) {
                        word += char;
                        currentDashes += '-';
                        document.getElementById('dashes').textContent = currentDashes;
                    }
                }
                
                return word;
            }
    
            function updateMessage(text) {
                document.getElementById('message').textContent = text;
            }
    
            function play(word) {
                updateMessage("A games loop begins! The secret word was: " + word);
                // Game logic would go here
            }
    
            async function main() {
                updateMessage("let's play Hangman by creating a word to guess:");
                secretWord = await getSpecialLine();
                
                updateMessage("Now try to guess it. Yeah I know you know it!");
                play(secretWord);
            }
    
            // Start the game
            main();
        </script>
    </body>
    </html>

Conclusion

Like the Haskell code, the JavaScript code is relatively straighforward. We are not mutating any variables in our code.

The next step will be to introduce some kind of state, basically game state. I will keep it simple, and put it in a game loop which recursively passes the updated game state, namely the word back to itself.

main = do
    staticDirectory "images"
    staticDirectory "css"
    staticDirectory "js"