Haskell로 만드는 콘솔 Life Game

15957 단어 lifegame하스켈
Fortran이라는 고대 언어를 쓰는 날마다 혐오감을 느꼈기 때문에 Haskell의 힘을 빌리기 위해 부작용을 털어 냈습니다.
지금도 하스켈 공부 중이지만, 결국 모나드는 함수와 값을 감싸고<<= 같은 연산자나 특정 함수로 조작을 하는 것을 명시하는 OOP에서 말하는 캡슐화의 개념에 가까운 것 그럴까?
어쨌든 프로그래밍을 배우는 것보다는 쓰기가 가장 좋기 때문에 콘솔에서 작동하는 간단한 LifeGame을 작성해 보았습니다.

게임 상태 정의



필드의 상태는 셀을 좌표 Pos로 표시하고 전체적으로 Board로 관리합니다.Board는 살아있는 셀을 요소로하는 Pos 형식의 목록입니다. 필요한 라이브러리도 가져옵니다.
import Control.Monad
import System.Random

type Pos = (Int, Int)
type Board = [Pos]

필드 크기



쉽게 변경할 수 있도록 가로 너비 width와 세로 너비 height도 함수에 정의되어 있습니다.
width :: Int
width = 50

height :: Int
height = 50

각 셀의 상태 확인



그런 다음 Pos 형식의 좌표에서 Board 내에 해당 셀이 있는지 확인하고 해당 셀이 살아 있는지 여부를 Bool 형식으로 반환하는 함수 isAlive를 준비합니다. ) 함수는 셀에 생명이 있는지 여부를 반환합니다.
isAlive :: Board -> Pos -> Bool
isAlive b p = p `elem` b

isEmpty :: Board -> Pos -> Bool
isEmpty b p = not (isAlive b p)

주위의 셀 취득


isEmpty 함수는 셀 좌표 neighbers에서 그 주위 8 방향의 셀을 가져옵니다. 주변의 셀 획득에 해당합니다.
wrap :: Pos -> Pos
wrap (x, y) = (((x-1) `mod` width) + 1,
               ((y-1) `mod` height)+ 1)

neighbers :: Pos -> [Pos]
neighbers (x, y) = map wrap [(x-1, y-1), (x, y-1),
                             (x+1, y-1), (x-1, y),
                             (x+1, y),   (x-1, y+1),
                             (x, y+1),   (x+1, y+1)]

차세대 생활


Pos 함수는 주변의 살아있는 셀 수를 "wrap 유형의 인수에서 반환하는 함수"를 반환하는 함수입니다.
그리고 liveneighbers는 현세대에서 차세대에서 살아남는 셀 목록을 반환합니다.
반면에 Pos는 차세대에서 새롭게 생명이 생성되는 셀 목록을 반환합니다.survivors에서 사용되는 births는 목록에서 중복 요소를 제외하고 하나로 만듭니다.
그리고 마지막으로 birthsrmdups에서 survivors로 차세대 생활을 얻습니다.
liveneighbers :: Board -> Pos -> Int
liveneighbers b = length . filter (isAlive b) . neighbers

survivors :: Board -> [Pos]
survivors b = [p | p <- b, liveneighbers b p `elem` [2, 3]]

rmdups :: Eq a => [a] -> [a]
rmdups [] = []
rmdups (x:xs) = x : rmdups (filter (/= x) xs)

births :: Board -> [Pos]
births b = [p | p <- rmdups (concatMap neighbers b),
                isEmpty b p,
                liveneighbers b p == 3]

nextgen :: Board -> Board
nextgen b = survivors b ++ births b

그리기 함수



그런 다음 콘솔에 표시하는 기능을 제공합니다.births 콘솔 화면을 지우고. 요소 작업을 순차적으로 수행하는 nextgen 함수를 사용하여 그립니다.
cls :: IO ()
cls = putStr "\ESC[2J"

goto :: Pos -> IO ()
goto (x, y) = putStr ("\ESC[" ++ show y ++ ";" ++ show x ++ "H")

writeat :: Pos -> String -> IO ()
writeat p xs = do goto p
                  putStr xs

seqn :: [IO a] -> IO ()
seqn [] = return ()
seqn (a:as) = do a
                 seqn as

showcells :: Board -> IO ()
showcells b = seqn [writeat p "O" | p <- b]

게임 사이클



마지막으로 게임 본문을 정의합니다.cls는 세대간에 그리기 사이를 취하는 기능입니다.
lifegame :: Board -> IO ()
lifegame b = do cls
                showcells b
                wait 5000
                lifegame (nextgen b)

초기 상태


goto 함수를 호출하려면 초기 상태 writeat를 제공하지 않으면 갈 수 없으므로 반환하는 함수를 준비하십시오.showcells에서 난수를 사용하여 임의의 위치의 셀 좌표를 반환합니다.
그런 다음 seqn에 지정된 수의 수명을 가진 wait를 가져옵니다.
makeLife :: IO Pos
makeLife = do
        x <- getStdRandom $ randomR(1, width) :: IO Int
        y <- getStdRandom $ randomR(1, height) :: IO Int
        return (x, y)

initBoard :: IO Board
initBoard = replicateM (width * height `div` 5) makeLife

완성!!!


main :: IO ()
main = lifegame =<< initBoard

자 실행해보자!!!!!



...........
네! ! ! 훌륭합니다! ! ! 훌륭합니다! ! !

..........
한화휴제

함수형으로 제대로 코드 쓴 것은 처음입니다만, haskell의 형태->형의 형태를 강하게 의식시키는 사양은 함수의 세분화를 촉진해, 그 때문에 함수 각각의 독립성이 강하게 느꼈습니다. 성・안전성이 높다고 하는 것도 어쩐지 알 수 있다고 생각합니다. 무엇보다 단순한 함수의 편성으로 복잡한 처리를 간결하게 쓸 수 있는 것은 기분 좋네요. 내가 생각했던 Project Euler를 Haskell로 써 보자.

좋은 웹페이지 즐겨찾기