椿の日記

たぶんプログラムの話をします

Haskellでこの問題を解いてみた②

同じ人が別の問題を作っておられたようなので、解いてみました。

ぷよぷよ19連鎖

import Data.Array
import Data.List

type Point = (Int,Int)
type Field = Array Point Char
type Route = [Point]

isLegalPuyo :: Char -> Bool
isLegalPuyo '\n' = False
isLegalPuyo '\r' = False
isLegalPuyo _ = True

parseField :: String -> Field
parseField cs =
    let numRow = length $ lines $ cs
        numCol = length $ head $ lines $ cs
        puyos = filter isLegalPuyo cs
    in  array ((1,1),(numRow,numCol)) $ zip [(y,x) | y <- [1..numRow], x <- [1..numCol]] puyos

unfoldLine :: Int -> String -> [String]
unfoldLine x cs = case splitAt x cs of
                      (xs,[]) -> [xs]
                      (xs,ys) -> xs : unfoldLine x ys

showField :: Field -> IO ()
showField field = do
    putStr $ unlines $ unfoldLine (snd $ snd $ bounds field) $ elems field

dfs :: (a -> [a]) -> a -> [a]
dfs next node = dfs' node
    where
        dfs' n = n : concatMap dfs' (next n)

inBound :: Field -> Point -> Bool
inBound field (y,x) =
    let ((y1,x1),(y2,x2)) = bounds field
    in  y1 <= y && y <= y2 && x1 <= x && x <= x2

findNextRoutes :: Field -> Route -> [Route]
findNextRoutes field route =
    let pos@(y,x) = head route
        neighbors = filter (inBound field) [(y+1,x),(y-1,x),(y,x+1),(y,x-1)]
        puyo = field ! pos
        same_puyo_poss = filter (\p -> (field ! p) == puyo) neighbors
        not_dupe_poss = filter (\p -> not $ p `elem` route) same_puyo_poss
    in  map (:route) not_dupe_poss

findChainedRoute :: Field -> Point -> Route
findChainedRoute field pos = 
    let routes = dfs (findNextRoutes field) [pos]
    in  nub $ concat $ routes

erasePuyo :: Field -> Maybe Field
erasePuyo field =
    let puyo_poss = filter (\x -> (field ! x) /= ' ') (indices field)
        erase_routes = filter (\x -> length x >= 4) $ map (findChainedRoute field) puyo_poss
        erase_pos = nub $ concat $ erase_routes
    in  case erase_pos of
            [] -> Nothing
            xs -> Just (field // zip xs (repeat ' '))

dropPuyoLine :: Int -> Field -> Field
dropPuyoLine col field =
    let numRow = fst $ snd $ bounds field
        puyos = map (field !) [(p,col) | p <- [numRow,numRow-1 .. 1]]
        packedPuyos = filter (/= ' ') puyos
    in  field // zip [(p,col) | p <- [numRow,numRow-1 .. 1]] (packedPuyos ++ repeat ' ')

dropPuyo :: Field -> Field
dropPuyo field =
    let numCol = snd $ snd $ bounds field
    in  foldr dropPuyoLine field [1..numCol]

enumerateFields :: Field -> [Field]
enumerateFields field =
    case erasePuyo field of
        Just x -> let next_field = dropPuyo x
                  in  next_field : enumerateFields next_field
        Nothing -> []

showNumberedField :: (Int,Field) -> IO ()
showNumberedField (index,field) = do
    putStrLn $ show index
    putStrLn ""
    showField field
    putStrLn "------------------------------------"

main :: IO ()
main = do
    cs <- readFile "source.txt"
    let field = parseField cs
        enumerated_fields = enumerateFields field
    mapM_ showNumberedField (zip [1..] enumerated_fields)

だいたい1時間くらいでした。前の問題もそうだったんですが、慣れたC言語ならば半分くらいの時間で作れた気がするのが関数型言語好きとしては非常に悔しいです。関数脳を鍛えるには…。処理効率もあんまり良くないですし。