1
Fork 0
This repository has been archived on 2023-12-08. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
2022FuncProg-project2-patience/lib/PatienceBoard.hs
2022-11-15 09:28:10 +01:00

245 lines
8.8 KiB
Haskell

module PatienceBoard
( Game (..)
, Board (..)
, amountOfGameStacks
, amountOfEndingStacks
, gameStacksCoord
, endingStacksCoord
, pileCoord
, handleInputs
, initGame
, isInGame
, isInEnding
, isInPile
) where
import CardDeck
import Selector
import InputHandler
----------------------------- Constants ------------------------------
-- Prepresentation of a Patience game
data Game = Game {
-- The playboard
board :: Board,
-- The selector
selector :: Selector
} deriving (Show)
-- Representation of a Patience board
data Board = Board {
-- 7 colums of cards (the 'playing field')
gameStacks :: [Stack],
-- 4 ending stacks (top right corner, usually)
endingStacks :: [Stack],
-- Stack of cards that are not yet on the board
pile :: Stack
} deriving (Show)
amountOfGameStacks :: Int
amountOfGameStacks = 7
amountOfEndingStacks :: Int
amountOfEndingStacks = 4
-- Coordinate of the GameStacks
gameStacksCoord :: Coordinate
gameStacksCoord = (0, 0)
-- Coordinate of the EndingStacks
endingStacksCoord :: Coordinate
endingStacksCoord = (x, 1)
where x = amountOfGameStacks - amountOfEndingStacks
-- Coordinate of the Pile
pileCoord :: Coordinate
pileCoord = (0, 1)
-- Step size to rotate the pile of the game
rotateStep :: Int
rotateStep = 3
------------------------------- Init ---------------------------------
-- Split a full deck into 7 gameStacks and one pile of unused cards.
splitDeck :: Stack -> [Stack]
splitDeck = reverse . splitDeck' amountOfGameStacks
where splitDeck' :: Int -> Stack -> [Stack]
splitDeck' 0 cs = [cs]
splitDeck' n cs = let (stack,rest) = splitAt n cs
in showFirst stack : splitDeck' (n - 1) rest
-- The initial board consisting of a stack of yet-to-be-turned cards
-- and n stacks of increasingly large amount of cards (1, ..., n)
initBoard :: Board
initBoard = Board {
gameStacks = stacks,
endingStacks = replicate amountOfEndingStacks [],
pile = map showCard pile
}
where pile:stacks = splitDeck generateShuffledDeck
-- The initial state of the playboard, with a board and a cursor.
initGame :: Game
initGame = Game {
board = initBoard,
selector = initSelector
}
------------------- Coordinate to Card conversion --------------------
-- Check if a coordinate is in the pile.
isInPile :: Coordinate -> Bool
isInPile = (pileCoord == )
-- Check if a coordinate is in an endingStack.
isInEnding :: Coordinate -> Bool
isInEnding (x, y) = leftBound && rightBound && yCheck
where leftBound = fst endingStacksCoord <= x
rightBound = x < amountOfGameStacks
yCheck = y == snd endingStacksCoord
-- Check if a coordinate is in a GameStack.
isInGame :: Coordinate -> Game -> Bool
isInGame (x, y) g = horizontalCheck && verticalCheck
where horizontalCheck = leftBound && rightBound
verticalCheck = upBound && downBound
leftBound = fst gameStacksCoord <= x
rightBound = x < amountOfGameStacks
upBound = y <= snd gameStacksCoord
downBound = negate y < length (gameStacks (board g) !! x)
-- Based on a coordinate, return a stack.
getStackFromCoord :: Game -> Coordinate -> Stack
getStackFromCoord game (x, y)
| isInPile (x, y) = pile $ board game
| isInEnding (x, y) = endingStacks (board game) !! (x - (amountOfGameStacks - amountOfEndingStacks))
| isInGame (x, y) game = gameStacks (board game) !! x
| otherwise = pile $ board game
-- Based on a coordinate, return a card.
getCardFromCoord :: Game -> Coordinate -> Card
getCardFromCoord game (x, y)
| isInPile (x, y) = head $ pile (board game)
| isInEnding (x, y) = head $ endingStacks (board game) !! (x - (amountOfGameStacks - amountOfEndingStacks))
| isInGame (x, y) game = (gameStacks (board game) !! x ) !! negate y
| otherwise = (NoneType, NoneValue, Hidden)
--------------------------- Change cards -----------------------------
-- Show the first of a stack of cards.
showFirst :: Stack -> Stack
showFirst [] = []
showFirst (c:cs) = showCard c : cs
-- Rotate the pile n times.
rotatePile :: Game -> Game
rotatePile g@Game{ board = b } = g{ board = rotatedBoard }
where rotatedBoard = b{ pile = tail ++ head }
(head, tail) = splitAt rotateStep $ pile b
-- Check if a card can be placed ontop of a gameStack.
canPlayOn :: Card -> Stack -> Bool
canPlayOn (_,King,_) [] = True
canPlayOn (t1,v1,_) ((t2,v2,_):cs) = differentColor && predValue
where differentColor = t1 /= t2 && fromEnum t1 + fromEnum t2 `elem` [1,2,4,5]
predValue = succ v1 == v2
canPlayOn _ _ = False
-- Check if a card can be played ontop of an EndingStack.
canFinishOn :: Card -> Stack -> Bool
canFinishOn (_,Ace,_) [] = True
canFinishOn (t1,v1,_) ((t2,v2,_):cs) = sameType && succValue
where sameType = t1 == t2
succValue = v1 == succ v2
canFinishOn _ _ = False
-- Move a card to a GameStack. Move all the cards below the given card
-- on the 'from' stack as well.
moveToGS :: Stack -> Int -> Stack -> (Stack,Stack)
moveToGS from index to
| canPlayOn (from !! index) to = (showFirst removed, added)
| otherwise = (from,to)
where (diff,removed) = splitAt (index + 1) from
added = diff ++ to
-- Move a card to an EndingStack. This can only be a single card at once.
moveToES :: Stack -> Stack -> (Stack,Stack)
moveToES from to
| canFinishOn (head from) to = (showFirst removed, added)
| otherwise = (from,to)
where (diff,removed) = splitAt 1 from
added = diff ++ to
-- Switch a stack for another stack in a list of stacks.
switchStack :: [Stack] -> Int -> Stack -> [Stack]
switchStack ss index new = front ++ new:back
where (front, back') = splitAt index ss
back = tail back'
-- Swap a stack in the Game for another stack.
updateStack :: Game -> Coordinate -> Stack -> Game
updateStack game (x, y) new
| isInEnding (x, y) = let originalBoard = board game
index = x - (amountOfGameStacks - amountOfEndingStacks)
newStack = switchStack (endingStacks originalBoard) index new
updatedBoard = originalBoard{ endingStacks = newStack }
in game{ board = updatedBoard }
| isInPile (x, y) = let originalBoard = board game
updatedBoard = originalBoard{ pile = new }
in game{ board = updatedBoard }
| otherwise = let originalBoard = board game
stackNr = x
index = negate y
newGameStacks = switchStack (gameStacks originalBoard) stackNr new
updatedBoard = originalBoard{ gameStacks = newGameStacks }
in game{ board = updatedBoard }
-- Move a card from Coordinate to Coordinate.
moveCard :: Game -> Coordinate -> Coordinate -> Game
moveCard game (x, y) (a, b)
| isInEnding (a, b) = let fromStack = getStackFromCoord game (x, y)
toStack = getStackFromCoord game (a, b)
(removed, added) = moveToES fromStack toStack
-- Swapping to old stack.
applyFirst = updateStack game (x, y) removed
-- Swapping the new stack.
result = updateStack applyFirst (a, b) added
in result
| otherwise = game
------------------------------ Input ---------------------------------
-- Check if moving in a direction is legal.
isLegalMove :: Direction -> Game -> Bool
isLegalMove dir g = isInPile coord || isInEnding coord || isInGame coord g
where coord = position $ move dir $ selector g
-- Move the selector of the game. (Wrapper)
moveSelector :: Direction -> Game -> Game
moveSelector dir g@Game{ selector = s }
| isLegalMove dir g = g{ selector = move dir s }
| otherwise = g
-- Toggle selector. If a card was already selected, try to move it.
toggleSelector :: Game -> Game
toggleSelector g@Game{ selector = s@Selector{ selected = Nothing } } = toggled
where toggled = g{ selector = toggleSelection s }
toggleSelector g@Game{ selector = s@Selector{ selected = Just coord } } = moved
where moved = moveCard g{ selector = toggleSelection s } (getSelected s) (position s)
-- Handle all the inputs necessary for patience.
handleInputs :: Event -> Game -> Game
handleInputs = composeInputHandler [
handleUp (moveSelector U),
handleDown (moveSelector D),
handleLeft (moveSelector L),
handleRight (moveSelector R),
handleSpace toggleSelector,
handleEnter rotatePile
]