Here's something I tried putting together as I'm learning. Critiques on anything are welcome. There's also a logic bug in the Plane module I can't identify.
The long and the short are that it takes the URI "some float/some float/some float/some float", and makes the first 2 (x,y) coord where something is, and the second 2 (x,y) coord where it wants to go. If there are no things overlapping the desired space, it will return the coords of the new location. If there is an overlap, it will return the original coords.
Extra points if you can find the logic bug I've been trying to find where it's thinking all coords are a collision with something.
plane.hs:
module Plane where
type X = Float
type Y = Float
type Direction = Float
type Location = (X, Y)
type Size = (Float, Float)
type TopLeftCorner = Location
type TopRightCorner = Location
type BottomLeftCorner = Location
type BottomRightCorner = Location
data Shape = Rectangle deriving (Eq, Show)
data Corner = RectangleCorners {
topLeftCorner :: TopLeftCorner,
topRightCorner :: TopRightCorner,
bottomRightCorner :: BottomRightCorner,
bottomLeftCorner :: BottomLeftCorner}
data Artifact = Artifact {
shape :: Shape,
location :: Location,
size :: Size } deriving (Eq, Show)
type Plane = [Artifact]
moveArtifact :: Plane -> Artifact -> Location -> Artifact
moveArtifact plane originalArtifact (moveToX, moveToY)
| artifactCanGoToLoc = Artifact Rectangle (moveToX, moveToY) $ size originalArtifact
| otherwise = originalArtifact
where artifactCorners = corners originalArtifact
artifactCanGoToLoc = not $
topLeftCorner artifactCorners `inside` plane ||
topRightCorner artifactCorners `inside` plane ||
bottomRightCorner artifactCorners `inside` plane ||
bottomLeftCorner artifactCorners `inside` plane
corners :: Artifact -> Corner
corners (Artifact Rectangle (artifactX, artifactY) (artifactW,artifactH)) =
RectangleCorners
((-) artifactX $ artifactW / 2, (+) artifactY $ artifactH / 2)
((+) artifactX $ artifactW / 2, (+) artifactY $ artifactH / 2)
((+) artifactX $ artifactW / 2, (-) artifactY $ artifactH / 2)
((-) artifactX $ artifactW / 2, (-) artifactY $ artifactH / 2)
inside :: Location -> Plane -> Bool
inside x y = insideAcc False x y
insideAcc :: Bool -> Location -> Plane -> Bool
insideAcc False (locToCheckX, locToCheckY) (Artifact Rectangle (artifactX, artifactY) (artifactW,artifactH):artifacts) =
insideAcc
(upperRightX > locToCheckX &&
lowerLeftX < locToCheckX &&
upperRightY > locToCheckY &&
lowerLeftY < locToCheckY)
(locToCheckX, locToCheckY) artifacts
where
upperRightX = (+) artifactX $ artifactW / 2
upperRightY = (+) artifactY $ artifactH / 2
lowerLeftX = (-) artifactY $ artifactH / 2
lowerLeftY = (-) artifactX $ artifactW / 2
insideAcc _ _ _ = True
main.hs:
{-# LANGUAGE OverloadedStrings #-}
module Main (
main
) where
import Plane
import Network.Wai
import Network.Wai.Handler.Warp
import Network.HTTP.Types (status200, status404)
import Blaze.ByteString.Builder (copyByteString)
import qualified Data.ByteString.UTF8 as BU
import Data.Monoid
import Data.Text (Text, unpack)
import Control.Applicative ((<*>),
(*>),
(<$>),
(<|>),
pure)
import qualified Data.Attoparsec.Text as A
import qualified Data.Attoparsec.Combinator as AC
import Data.Attoparsec.Text (Parser)
import Control.Monad.Trans.Resource
import Network.HTTP.Types
main :: IO ()
main = do
let port = 3000
putStrLn $ "Listening on port " ++ show port
run port app
app :: Request -> ResourceT IO Response
app req = do
return $ moveArtifactResponse path
where path = pathInfo req
moveArtifactResponse :: [Text] -> Response
moveArtifactResponse splitPath@(oldX:oldY:newX:newY:_) =
case (maybeArtifact, maybeX, maybeY) of
(Just artifact, Just x, Just y) -> createJsonResponse $ show $ location $ moveArtifact examplePlane (artifact) (x, y)
(_, _, _) -> notFoundResponse splitPath
where maybeArtifact = textToArtifact oldX oldY
maybeX = textToFloat newX
maybeY = textToFloat newY
moveArtifactResponse splitPath = notFoundResponse splitPath
notFoundResponse :: [Text] -> Response
notFoundResponse path =
createErrorResponse status200 $ "404 NOT FOUND LOCATION READ AS: " ++ (show $ fmap textToFloatString path) ++ "<br/>" ++ (concat $ fmap show examplePlane)
createResponse :: BU.ByteString -> Status -> (String -> Response)
createResponse contentType status response = do
ResponseBuilder status [("Content-Type", contentType)] . mconcat . fmap copyByteString $ [BU.fromString response]
createErrorResponse = createResponse "text/html"
createJsonResponse = createResponse "text/javascript" status200
createHtmlResponse = createResponse "text/html" status200
textToFloat :: Text -> Maybe Float
textToFloat x
| (length $ textReads x) /= 1 = Nothing
| (snd $ head $ textReads x) /= [] = Nothing
| otherwise = Just $ fst $ head $ textReads x
where textReads = reads . unpack :: Text -> [(Float, String)]
textToArtifact :: Text -> Text -> Maybe Artifact
textToArtifact textX textY =
case (maybeX, maybeY) of
(Just x, Just y) -> Just $ Artifact Rectangle (x, y) (1,1)
(_, _) -> Nothing
where maybeX = textToFloat textX
maybeY = textToFloat textY
textToFloatString :: Text -> Maybe String
textToFloatString x
| textReads x == [] = Nothing
| (snd $ head $ textReads x) /= [] = Nothing
| otherwise = Just $ unpack x
where textReads = reads . unpack :: Text -> [(Float, String)]
examplePlane :: Plane
examplePlane = [
Artifact Rectangle (3, 3) (2,2),
Artifact Rectangle (3, 8) (2,2),
Artifact Rectangle (8, 3) (2,2),
Artifact Rectangle (8, 8) (2,2)]