A simple rule of thumb is that you should never use readS_to_Prec and readS_to_P. Consider that once you've converted a parser to a ReadS to run it, there is no way back. (One handwavy explanation is that those functions are really dirty hacks, which is also why gather is undefined when they are used.)
Under those constraints, the only place where you can ...
step does two things: (1) step to the next state, and (2) check that the state is valid. Its name suggests only the first meaning. The repetition of wrapValidInJust suggests that they can be separated.
Instead of == you can use pattern-matching.
isgood :: String -> Bool
isgood = isValidFinal . foldM (check . step) (0, 0)
step (x, y) 'R' = (...
I think a simple way to address the problem is by building the board first then printing it:
empty_row :: String
empty_row = "+---+---+---+"
board_repr :: type of b -> String
board_repr b = empty_row + concat ( map (\x -> (build_row x b) + empty_row ) [0, 1, 2] )
main = print (board_repr b)
Take this as pseudo-code pointing in an ...