|
module VM where
|
|
|
|
import Data.Hashable (hash, Hashable(..))
|
|
import qualified Data.List as List
|
|
import Data.Set (Set)
|
|
import qualified Data.Set as Set
|
|
|
|
import Datatypes
|
|
|
|
collides :: Unit -> Board -> Bool
|
|
collides u b = not . Set.null $ Set.intersection (unitMembers u) (boardFilled b)
|
|
|
|
isInvalidFor :: Unit -> Board -> Bool
|
|
isInvalidFor u b = any isOutside (Set.toList $ unitMembers u) where
|
|
isOutside (x, y) = x < 0 || x >= boardWidth b || y < 0 || y >= boardHeight b
|
|
|
|
lockUnit :: Game -> Game
|
|
lockUnit (Game board (u:us) _) = Game newBoard us (Set.fromList []) where
|
|
newBoard = board { boardFilled = Set.union (unitMembers u) (boardFilled board) }
|
|
|
|
checkSpawn :: Game -> Game
|
|
checkSpawn game@(Game _ [] _) = game
|
|
checkSpawn game@(Game board (u:us) oldPos) = if u `collides` board
|
|
then Game board [] oldPos
|
|
else game
|
|
|
|
isCompleted :: Game -> Bool
|
|
isCompleted game@(Game _ [] _) = True
|
|
isCompleted _ = False
|
|
|
|
step :: Game -> Command -> (Game, Notes)
|
|
step game@(Game _ [] _) command = (game, ErrorZero)
|
|
step game@(Game board (unit:us) oldPositions) command =
|
|
if unit `collides` board || newUnit `isInvalidFor` board
|
|
then let final = checkSpawn (lockUnit game) in
|
|
(if isCompleted final then (final, Ended) else (final, OK))
|
|
else
|
|
if Set.member (hash newUnit) oldPositions
|
|
then (Game board (newUnit:us) oldPositions, ErrorZero)
|
|
else (Game board (newUnit:us) newOldPositions, OK)
|
|
where
|
|
newUnit = applyCommand unit command
|
|
newOldPositions = Set.insert (hash newUnit) oldPositions
|
|
|
|
applyWholeUnit :: Unit -> (Cell -> Cell) -> Unit
|
|
applyWholeUnit (Unit members pivot) f = Unit (Set.map f members) (f pivot)
|
|
|
|
applyCommand :: Unit -> Command -> Unit
|
|
applyCommand unit MoveW = applyWholeUnit unit move where
|
|
move (x, y) = (x - 1, y)
|
|
applyCommand unit MoveE = applyWholeUnit unit move where
|
|
move (x, y) = (x + 1, y)
|
|
applyCommand unit MoveSW = applyWholeUnit unit move where
|
|
move (x, y) = (x - ((y + 1) `mod` 2), y + 1)
|
|
applyCommand unit MoveSE = applyWholeUnit unit move where
|
|
move (x, y) = (x + (y `mod` 2), y + 1)
|
|
applyCommand (Unit members pivot) RotateClockwise = Unit (Set.map transform members) pivot where
|
|
transform cell = rotateCell pivot cell
|
|
applyCommand (Unit members pivot) RotateCounterclockwise = Unit (Set.map transform members) pivot where
|
|
transform cell = counterRotateCell pivot cell
|
|
|
|
rotateCell :: Cell -> Cell -> Cell
|
|
rotateCell (px, py) (x, y) = (px - ddiag + (doriz + py `mod` 2) `div` 2, py + doriz) where
|
|
(ddiag, doriz) = relativePosition (px, py) (x, y)
|
|
|
|
counterRotateCell :: Cell -> Cell -> Cell
|
|
counterRotateCell pivot = rp . rp . rp . rp . rp where
|
|
rp = rotateCell pivot
|
|
counterRotateCell2 :: Cell -> Cell -> Cell
|
|
counterRotateCell2 (px, py) (x, y) = (px + (ddiag + doriz + py `mod` 2) `div` 2, py + ddiag - doriz) where
|
|
(ddiag, doriz) = relativePosition (px, py) (x, y)
|
|
|
|
relativePosition :: Cell -> Cell -> (Int, Int)
|
|
relativePosition (px, py) (x, y) = (diagDir, horizDir) where
|
|
diagDir = y - py
|
|
horizDir = x - px + (if py `mod` 2 == 0 then diagDir + diagDir `mod` 2 else diagDir - diagDir `mod` 2) `div` 2
|