hs2048-0.1.0: library/Hs2048/Board.hs
-- | Types and functions for manipulating boards.
module Hs2048.Board
( Board
, canMove
, canShift
, empty
, emptyPoints
, move
, parse
, render
, rotate
, rotateFrom
, rotateTo
, score
, set
, shift
) where
import Data.List (transpose)
import qualified Hs2048.Direction as D
import qualified Hs2048.Point as P
import qualified Hs2048.Tile as T
import qualified Hs2048.Vector as V
{- |
Represents the game board. By convention, it is row-major.
-}
type Board = [V.Vector]
{- |
Determines if the board can be moved in the given direction. See 'move'.
>>> canMove [[Nothing, Just 2]] D.West
True
-}
canMove :: Board -> D.Direction -> Bool
canMove b d = move b d /= b
{- |
Determines if the board can be shifted. See 'shift'.
>>> canShift [[Nothing, Just 2]]
True
-}
canShift :: Board -> Bool
canShift b = shift b /= b
{- |
Returns an empty board of the given size.
>>> empty 2 1
[[Nothing,Nothing]]
-}
empty :: Int -- ^ Width
-> Int -- ^ Height
-> Board
empty = flip replicate . V.empty
{- |
Returns the points that don't contain tiles.
>>> emptyPoints [[Nothing, Just 2]]
[(0,0)]
-}
emptyPoints :: Board -> [P.Point]
emptyPoints b = zip [0 ..] (fmap V.emptyIndexes b) >>= go
where
go (x, ys) = fmap ((,) x) ys
{- |
Moves the board in the given direction.
>>> move [[Nothing, Just 2]] D.West
[[Just 2,Nothing]]
-}
move :: Board -> D.Direction -> Board
move b d = rotateFrom (shift (rotateTo b d)) d
{- |
Parses a string as a board. This is the inverse of 'render'.
>>> parse "- 2\n4 -\n"
[[Nothing,Just 2],[Just 4,Nothing]]
-}
parse :: String -> Board
parse = fmap V.parse . lines
{- |
Renders a board as a string. This is the inverse of 'parse'.
>>> render [[Nothing, Just 2], [Just 4, Nothing]]
"- 2\n4 -\n"
-}
render :: Board -> String
render = unlines . fmap V.render
{- |
Rotate the board 90 degrees clockwise.
>>> rotate [[Nothing, Just 2], [Just 4, Nothing]]
[[Just 4,Nothing],[Nothing,Just 2]]
-}
rotate :: Board -> Board
rotate = fmap reverse . transpose
{- |
Rotates the board so that 'Hs2048.Direction.West' is at the left, assuming
the given direction is currently at the left. This is the inverse of
'rotateTo'.
>>> rotateFrom [[Just 4, Nothing], [Nothing, Just 2]] D.South
[[Nothing,Just 2],[Just 4,Nothing]]
-}
rotateFrom :: Board -> D.Direction -> Board
rotateFrom b d = head (drop n gs)
where
n = 1 + fromEnum (maxBound :: D.Direction) - fromEnum d
gs = iterate rotate b
{- |
Rotates the board so that the given direction is at the left. This is the
inverse of 'rotateFrom'
>>> rotateTo [[Nothing, Just 2], [Just 4, Nothing]] D.South
[[Just 4,Nothing],[Nothing,Just 2]]
-}
rotateTo :: Board -> D.Direction -> Board
rotateTo b d = head (drop n gs)
where
n = fromEnum d
gs = iterate rotate b
{- |
Calculates the score of a board.
>>> score [[Nothing, Just 2], [Just 4, Just 8]]
20
-}
score :: Board -> Int
score = sum . fmap V.score
{- |
Sets a tile at the given point in the board.
>>> set [[Nothing, Just 2], [Just 4, Nothing]] (Just 8) (1, 1)
[[Nothing,Just 2],[Just 4,Just 8]]
-}
set :: Board -> T.Tile -> P.Point -> Board
set b t p = zipWith go [0 ..] b
where
go i v = if i == P.x p then V.set v t (P.y p) else v
{- |
Shifts a board toward the head. See 'Vector.shift'.
>>> shift [[Nothing, Just 2], [Just 4, Nothing]]
[[Just 2,Nothing],[Just 4,Nothing]]
-}
shift :: Board -> Board
shift = fmap V.shift