ぷよぷよ19連鎖

ゲーム「ぷよぷよ」で、フィールドの状態がテキストで与えられたとき、消える「ぷよ」を消して次のフィールドの状態を出力するプログラムを書け。
http://okajima.air-nifty.com/b/2011/01/2011-ffac.html

この問題を見たときに、Haskellで解いてみたいと思ったのだが、フラグ等の副作用を使わないうまい書き方が思いつかなくてお蔵入りにしてた。
今回、ふとしたことで方針を思いついたので書いてみた。

import Data.List
input =
  ["  GYRR"
  ,"RYYGYG"
  ,"GYGYRR"
  ,"RYGYRG"
  ,"YGYRYG"
  ,"GYRYRG"
  ,"YGYRYR"
  ,"YGYRYR"
  ,"YRRGRG"
  ,"RYGYGG"
  ,"GRYGYR"
  ,"GRYGYR"
  ,"GRYGYR"]
h = length input
w = length $ head input

near (i,j,c) (i',j',c') = c==c' && abs(i'-i)+abs(j'-j)==1
xyc n cs = [(div i n,mod i n,c) | (i,c) <- zip [0..] cs]
tr xs = [(j,i,c) | (i,j,c) <- xs]
str xs = [c | (_,_,c) <- sort xs]

joins xs = foldr f [] [x | x@(_,_,c) <- xs, c/=' ' && c/='\n']
 where f x r = let (ok,ng) = partition (any $ near x) r in (x : concat ok) : ng

puyo xs = (str $ tr xs) : if xs==ys then [] else puyo ys
 where j4 = concat [x | x <- joins xs, length x > 3]
       ys = xyc h $ str [if elem x j4 then (i,-1,' ') else x | x@(i,_,_) <- xs]

main = mapM_ putStrLn $ puyo $ tr $ xyc (w+1) $ unlines input