I know you said you didn't want code, but I think this will be helpful. Here is an example of what the very top of your logic might reasonably look like.
main :: IO ()
main = do
someInitialization
config <- someConfigurator
result <- gameLoop $ makeInitialState config
print result
gameLoop :: GameState -> IO GameResult
gameLoop gameState =
let maybeEndState = discoverEndState gameState
in case maybeEndState of
Just endState -> return endState
Nothing -> do
playerMove <- fetchInput gameState
gameLoop $ makeNextState playerMove