Created
June 27, 2015 14:42
-
-
Save luochen1990/2b9f7cc5c0e194b53a73 to your computer and use it in GitHub Desktop.
2048 game in Haskell
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# LANGUAGE ForeignFunctionInterface #-} | |
import System.Console.ANSI (clearScreen) | |
import Data.Char | |
import Foreign.C.Types | |
import Prelude hiding (Left, Right) | |
import GHC.Exts (groupWith, sortWith) | |
import Control.Arrow | |
import System.Random | |
import System.IO | |
import Data.Hashable | |
import Data.Maybe | |
import Data.List | |
import System.Process | |
getHiddenChar = fmap (chr.fromEnum) c_getch | |
foreign import ccall unsafe "conio.h getch" | |
c_getch :: IO CInt | |
(boardW, boardH) = (boardSize, boardSize) where boardSize = 4 | |
data Direction = Right | Top | Left | Bottom deriving (Eq, Show, Enum) | |
data Hole = Hole {getPos :: (Int, Int), getValue :: Int} deriving (Eq, Show) | |
newtype Board = Board {getHoles :: [Hole]} | |
instance Hashable Hole where hashWithSalt s (Hole p v) = hashWithSalt s (p, v) | |
instance Hashable Board where hashWithSalt s (Board hs) = hashWithSalt s hs | |
instance Show Board where | |
show (Board hs) = (++ "\n") . intercalate "\n\n" . map (intercalate "\t") $ ls where | |
ls = do | |
y <- [boardH-1,boardH-2..0] | |
return $ do | |
x <- [0..boardW-1] | |
return $ case find ((== (x, y)) . getPos) hs of | |
Nothing -> "_" | |
Just h -> show $ getValue h | |
sink :: (Eq a, Num a) => [a] -> [a] | |
sink [] = [] | |
sink (x : []) = (x : []) | |
sink (x : y : xs) | |
| x == y = (x + y) : sink xs | |
| otherwise = x : sink (y : xs) | |
toOneLine :: Direction -> [Hole] -> (Int, [Int]) | |
fromOneLine :: Direction -> (Int, [Int]) -> [Hole] | |
varOnX dir = even (fromEnum dir) | |
isDesc dir = fromEnum dir >= 2 | |
nthOnDirection i dir = if isDesc dir then i else dirLimit - i where | |
dirLimit = (if varOnX dir then boardW else boardH) - 1 | |
toOneLine dir = (f' . getPos . head &&& map getValue) . (sortWith (sig . f . getPos)) where | |
(f, f') = if varOnX dir then (fst, snd) else (snd, fst) | |
sig = if isDesc dir then id else negate | |
fromOneLine dir (cv, vs) = do | |
(v, i) <- zip vs [0..] | |
let vv = i `nthOnDirection` dir | |
return $ Hole (if varOnX dir then (vv, cv) else (cv, vv)) v | |
sinkBoard :: Direction -> Board -> Board | |
sinkBoard dir = Board . concat . map sinkOneLine . groupWith (field . getPos) . getHoles | |
where | |
sinkOneLine = fromOneLine dir . (id *** sink) . toOneLine dir | |
field = if dir == Left || dir == Right then snd else fst | |
zeroBoard = Board [] | |
randomHoles :: Int -> [Hole] | |
randomHoles seed = map f (randoms (mkStdGen seed)) where | |
f r = Hole (x, y) 2 where | |
xy = r `mod` (boardW * boardH) | |
(x, y) = ((uncurry mod) &&& (uncurry div)) (xy, boardW) | |
incBoard :: Board -> Maybe Board | |
incBoard (Board hs) | length hs >= boardW * boardH = Nothing | |
incBoard (Board hs) = Just (Board (h : hs)) where | |
ps = map getPos hs | |
seed = hash (Board hs) | |
h = fromMaybe undefined $ find (not . (`elem` ps) . getPos) $ randomHoles seed | |
incBoard' = fromMaybe zeroBoard . incBoard | |
main = do | |
hSetBuffering stdin NoBuffering | |
let loop board = do | |
c <- getHiddenChar | |
let dir = toEnum $ (`mod` 4) $ fromMaybe (-1) $ findIndex (== c) "dwaslkhj" :: Direction | |
let board' = incBoard' $ sinkBoard dir $ board | |
clearScreen | |
print board' | |
putStr "\n\n\n" | |
loop board' | |
print (incBoard' zeroBoard) | |
loop (incBoard' zeroBoard) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment