worldturtle 0.1.1.0 → 0.2.0.0
raw patch · 13 files changed
+391/−261 lines, 13 filesdep ~basebinary-addedPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base
API changes (from Hackage documentation)
+ Graphics.WorldTurtle: (>/>) :: Turtle -> TurtleCommand a -> WorldCommand a
+ Graphics.WorldTurtle: data WorldCommand a
+ Graphics.WorldTurtle: empty :: Alternative f => f a
+ Graphics.WorldTurtle: infixl 4 >/>
+ Graphics.WorldTurtle: run :: TurtleCommand a -> Turtle -> WorldCommand a
+ Graphics.WorldTurtle: runWorld :: WorldCommand () -> IO ()
+ Graphics.WorldTurtle.Commands: arc :: Float -> Float -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: branch :: TurtleCommand a -> TurtleCommand a
- Graphics.WorldTurtle.Commands: backward :: Float -> Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: backward :: Float -> TurtleCommand ()
- Graphics.WorldTurtle.Commands: bk :: Float -> Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: bk :: Float -> TurtleCommand ()
- Graphics.WorldTurtle.Commands: circle :: Float -> Float -> Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: circle :: Float -> TurtleCommand ()
- Graphics.WorldTurtle.Commands: clear :: TurtleCommand ()
+ Graphics.WorldTurtle.Commands: clear :: WorldCommand ()
- Graphics.WorldTurtle.Commands: fd :: Float -> Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: fd :: Float -> TurtleCommand ()
- Graphics.WorldTurtle.Commands: forward :: Float -> Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: forward :: Float -> TurtleCommand ()
- Graphics.WorldTurtle.Commands: goto :: Point -> Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: goto :: Point -> TurtleCommand ()
- Graphics.WorldTurtle.Commands: heading :: Turtle -> TurtleCommand Float
+ Graphics.WorldTurtle.Commands: heading :: TurtleCommand Float
- Graphics.WorldTurtle.Commands: home :: Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: home :: TurtleCommand ()
- Graphics.WorldTurtle.Commands: left :: Float -> Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: left :: Float -> TurtleCommand ()
- Graphics.WorldTurtle.Commands: lt :: Float -> Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: lt :: Float -> TurtleCommand ()
- Graphics.WorldTurtle.Commands: makeTurtle :: TurtleCommand Turtle
+ Graphics.WorldTurtle.Commands: makeTurtle :: WorldCommand Turtle
- Graphics.WorldTurtle.Commands: makeTurtle' :: Point -> Float -> Color -> TurtleCommand Turtle
+ Graphics.WorldTurtle.Commands: makeTurtle' :: Point -> Float -> Color -> WorldCommand Turtle
- Graphics.WorldTurtle.Commands: penColor :: Turtle -> TurtleCommand Color
+ Graphics.WorldTurtle.Commands: penColor :: TurtleCommand Color
- Graphics.WorldTurtle.Commands: penDown :: Turtle -> TurtleCommand Bool
+ Graphics.WorldTurtle.Commands: penDown :: TurtleCommand Bool
- Graphics.WorldTurtle.Commands: penSize :: Turtle -> TurtleCommand Float
+ Graphics.WorldTurtle.Commands: penSize :: TurtleCommand Float
- Graphics.WorldTurtle.Commands: position :: Turtle -> TurtleCommand Point
+ Graphics.WorldTurtle.Commands: position :: TurtleCommand Point
- Graphics.WorldTurtle.Commands: representation :: Turtle -> TurtleCommand Picture
+ Graphics.WorldTurtle.Commands: representation :: TurtleCommand Picture
- Graphics.WorldTurtle.Commands: right :: Float -> Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: right :: Float -> TurtleCommand ()
- Graphics.WorldTurtle.Commands: rotationSpeed :: Turtle -> TurtleCommand Float
+ Graphics.WorldTurtle.Commands: rotationSpeed :: TurtleCommand Float
- Graphics.WorldTurtle.Commands: rt :: Float -> Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: rt :: Float -> TurtleCommand ()
- Graphics.WorldTurtle.Commands: setHeading :: Float -> Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: setHeading :: Float -> TurtleCommand ()
- Graphics.WorldTurtle.Commands: setPenColor :: Color -> Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: setPenColor :: Color -> TurtleCommand ()
- Graphics.WorldTurtle.Commands: setPenDown :: Bool -> Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: setPenDown :: Bool -> TurtleCommand ()
- Graphics.WorldTurtle.Commands: setPenSize :: Float -> Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: setPenSize :: Float -> TurtleCommand ()
- Graphics.WorldTurtle.Commands: setPosition :: Point -> Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: setPosition :: Point -> TurtleCommand ()
- Graphics.WorldTurtle.Commands: setRepresentation :: Picture -> Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: setRepresentation :: Picture -> TurtleCommand ()
- Graphics.WorldTurtle.Commands: setRotationSpeed :: Float -> Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: setRotationSpeed :: Float -> TurtleCommand ()
- Graphics.WorldTurtle.Commands: setSpeed :: Float -> Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: setSpeed :: Float -> TurtleCommand ()
- Graphics.WorldTurtle.Commands: setVisible :: Bool -> Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: setVisible :: Bool -> TurtleCommand ()
- Graphics.WorldTurtle.Commands: sleep :: Float -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: sleep :: Float -> WorldCommand ()
- Graphics.WorldTurtle.Commands: speed :: Turtle -> TurtleCommand Float
+ Graphics.WorldTurtle.Commands: speed :: TurtleCommand Float
- Graphics.WorldTurtle.Commands: stamp :: Turtle -> TurtleCommand ()
+ Graphics.WorldTurtle.Commands: stamp :: TurtleCommand ()
- Graphics.WorldTurtle.Commands: visible :: Turtle -> TurtleCommand Bool
+ Graphics.WorldTurtle.Commands: visible :: TurtleCommand Bool
Files
- ChangeLog.md +13/−2
- Graphics/WorldTurtle.hs +181/−81
- Graphics/WorldTurtle/Color.hs +7/−4
- Graphics/WorldTurtle/Commands.hs +108/−114
- Graphics/WorldTurtle/Internal/Commands.hs +65/−47
- Graphics/WorldTurtle/Internal/Coords.hs +5/−1
- Graphics/WorldTurtle/Internal/Sequence.hs +4/−4
- Graphics/WorldTurtle/Internal/Turtle.hs +3/−3
- Graphics/WorldTurtle/Shapes.hs +2/−2
- docs/images/drawtriangle.gif binary
- docs/images/parallel_serial_turtles.gif binary
- docs/images/parallel_serial_turtles_2.gif binary
- worldturtle.cabal +3/−3
ChangeLog.md view
@@ -1,5 +1,18 @@ # Changelog for turtle-haskell +## v0.2.0 + +* Split `TurtleCommand` into `TurtleCommand` and `WorldCommand` to help reduce + boilerplate of having to apply a turtle to a command for every stage of a + command block. +* Added `runWorld`, `runTurtle`, `run`, and `(>/>)` functions. +* `circle` command split into `arc` and `circle` commands. +* Fixed `shiftHue` as function did not match documentation. + +## v0.1.2 + +* Added the `branch` function. + ## v0.1.1 * Added `sleep` function. @@ -13,5 +26,3 @@ ## v0.1.0 Initial release. - -## Unreleased changes
Graphics/WorldTurtle.hs view
@@ -1,36 +1,45 @@-{-| -Module : Graphics.WorldTurtle -Description : WorldTurtle -Copyright : (c) Archibald Neil MacDonald, 2020 -License : BSD3 -Maintainer : FortOyer@hotmail.co.uk -Stability : experimental -Portability : POSIX - -"Graphics.WorldTurtle" is a module for writing and rendering turtle graphics -in Haskell. - -Take a look at the - [examples](https://github.com/FortOyer/worldturtle-haskell#examples) on -Github! - +{- | + Module : Graphics.WorldTurtle + Description : WorldTurtle + Copyright : (c) Archibald Neil MacDonald, 2020 + License : BSD3 + Maintainer : FortOyer@hotmail.co.uk + Stability : experimental + Portability : POSIX + + "Graphics.WorldTurtle" is a module for writing and rendering turtle graphics + in Haskell. + + Take a look at the + [examples](https://github.com/FortOyer/worldturtle-haskell#examples) on + Github! -} module Graphics.WorldTurtle - ( - -- * Running the turtle + ( + -- * Running a WorldTurtle simulation. + -- * Running on a single turtle. -- $running - TurtleCommand - , runTurtle + runTurtle + , TurtleCommand + -- * Running a world of turtles. + -- $multiturtle + , runWorld + , WorldCommand + , run + , (>/>) -- * Parallel animation -- $parallel , (<|>) + -- * Stop an animation + -- $empty + , empty -- * Further documentation , module Graphics.WorldTurtle.Commands , module Graphics.WorldTurtle.Shapes , module Graphics.WorldTurtle.Color ) where -import Control.Applicative ((<|>)) +import Control.Applicative (empty, (<|>)) import Graphics.Gloss.Data.Display (Display (..)) import qualified Graphics.Gloss.Data.ViewState as G @@ -40,50 +49,52 @@ import Graphics.WorldTurtle.Color import Graphics.WorldTurtle.Commands import Graphics.WorldTurtle.Internal.Sequence (renderTurtle) -import Graphics.WorldTurtle.Internal.Commands (TurtleCommand, seqT) +import Graphics.WorldTurtle.Internal.Commands (TurtleCommand, seqT + , WorldCommand (..), seqW) import Graphics.WorldTurtle.Shapes -data World = World { elapsedTime :: !Float - , running :: !Bool - , state :: !G.ViewState - } - -{- | `runTurtle` takes a `TurtleCommand` and produces the animation in a new - window! - - The simplest way to run `runTurtle` is to execute it directly from - your main function like so: - - @ - main :: IO () - main = runTurtle yourOwnCoolCommand - @ +-- | Takes a `TurtleCommand` and executes the command on an implicitly created +-- turtle that starts at position @(0, 0)@ with heading `north`. +-- +-- This is a convenience function written in terms of `runWorld` as: +-- +-- > runTurtle c = runWorld $ makeTurtle >>= run c +-- +-- See also: `Graphics.WorldTurtle.Commands.makeTurtle`. +runTurtle :: TurtleCommand () -- ^ Command sequence to execute. + -> IO () +runTurtle c = runWorld $ makeTurtle >>= run c - While running, you can interact with the window in the following way: +{- | `runWorld` takes a `WorldCommand` and produces the animation in a new + window! - +------------------------------------------+-------------------+ - | Action | Interaction | - +==========================================+===================+ - | Pan the viewport. | Click and drag | - +------------------------------------------+-------------------+ - | Zoom in/out. |Mousewheel up/down | - +------------------------------------------+-------------------+ - | Reset the viewport to initial position. | Spacebar | - +------------------------------------------+-------------------+ - | Reset the animation. | @R@ key | - +------------------------------------------+-------------------+ - | Pause the animation. | @P@ key | - +------------------------------------------+-------------------+ - | Quit | Escape key | - +------------------------------------------+-------------------+ + == Interacting with the window. + + While running, you can interact with the window in the following way: + + +------------------------------------------+-------------------+ + | Action | Interaction | + +==========================================+===================+ + | Pan the viewport. | Click and drag | + +------------------------------------------+-------------------+ + | Zoom in/out. |Mousewheel up/down | + +------------------------------------------+-------------------+ + | Reset the viewport to initial position. | Spacebar | + +------------------------------------------+-------------------+ + | Reset the animation. | @R@ key | + +------------------------------------------+-------------------+ + | Pause the animation. | @P@ key | + +------------------------------------------+-------------------+ + | Quit | Escape key | + +------------------------------------------+-------------------+ -} -runTurtle :: TurtleCommand () -- ^ Command sequence to execute +runWorld :: WorldCommand () -- ^ Command sequence to execute -> IO () -runTurtle tc = G.play display white 30 defaultWorld iterateRender input timePass +runWorld tc = G.play display white 30 defaultWorld iterateRender input timePass where display = InWindow "World Turtle" (800, 600) (400, 300) iterateRender w = G.applyViewPortToPicture (G.viewStateViewPort $ state w) - $! renderTurtle (seqT tc) (elapsedTime w) + $! renderTurtle (seqW tc) (elapsedTime w) input e w -- Reset key resets sim state (including unpausing). We -- deliberately keep view state the same. @@ -97,6 +108,51 @@ | running w = w { elapsedTime = f + elapsedTime w } | otherwise = w +-- | `run` takes a `TurtleCommand` and a `Turtle` to execute the command on. +-- The result of the computation is returned wrapped in a `WorldCommand`. +-- +-- For example, to create a turtle and get its @x@ `position` one might +-- write: +-- +-- > myCommand :: Turtle -> WorldCommand Float +-- > myCommand t = do +-- > (x, _) <- run position t +-- > return x +-- +-- Or to create a command that accepts a turtle and draws a right angle: +-- +-- > myCommand :: Turtle -> WorldCommand () +-- > myCommand = run $ forward 10 >> right 90 >> forward 10 +run :: TurtleCommand a -- ^ Command to execute + -> Turtle -- ^ Turtle to apply the command upon. + -> WorldCommand a -- ^ Result as a `WorldCommand` +run c = WorldCommand . seqT c + +-- | This is an infix version of `run` where the arguments are swapped. +-- +-- We take a turtle and a command to execute on the turtle. +-- The result of the computation is returned wrapped in a `WorldCommand`. +-- +-- To create a turtle and draw a right-angle: +-- +-- > myCommand :: WorldCommand () +-- > myCommand = do +-- > t <- makeTurtle +-- > t >/> do +-- > forward 10 +-- > right 90 +-- > forward 10 +(>/>) :: Turtle -- ^ Turtle to apply the command upon. + -> TurtleCommand a -- ^ Command to execute + -> WorldCommand a -- ^ Result as a `WorldCommand` +(>/>) = flip run +infixl 4 >/> + +data World = World { elapsedTime :: !Float + , running :: !Bool + , state :: !G.ViewState + } + defaultWorld :: World defaultWorld = World 0 True $ G.viewStateInitWithConfig @@ -119,41 +175,85 @@ {- $running -It is easy to create and animate your turtle. You just pass your commands to -`runTurtle` like so: + To start animating a single turtle, you just pass your commands to + `runTurtle` like so: -@ - import Control.Monad (replicateM_) - import Graphics.WorldTurtle + > import Control.Monad (replicateM_) + > import Graphics.WorldTurtle + > + > drawSquare :: Float -> TurtleCommand () + > drawSquare size = replicateM_ 4 $ forward size >> right 90 + > + > main :: IO () + > main = runTurtle $ drawSquare 100 - myCommand :: TurtleCommand () - myCommand = do - t <- makeTurtle - replicateM_ 4 $ forward 90 t >> right 90 t + Which will produce this animation. - main :: IO () - main = runTurtle myCommand -@ +  +-} -Which will produce this animation +{- $multiturtle + + For executing commands on multiple turtles, we use `runWorld` which + executes on `WorldCommand`s. Here is an example where 2 turtles draw a + circle independently: - --} + > import Graphics.WorldTurtle + > + > main :: IO () + > main = runWorld $ do + > t1 <- makeTurtle + > t2 <- makeTurtle + > + > t1 >/> circle 90 + > t2 >/> circle (-90) + Notice that in a `WorldCommand` context we must create our own turtles with + `makeTurtle`! We them apply the `TurtleCommand` + on our turtles using the run operator `(>/>)`. +-} {- $parallel - We already know that `TurtleCommand`s can be combined with `(>>)`, but the - alternative operation `(<|>)` can alo be used to combine two - `TurtleCommand`s. This has a special meaning: do both animations at the - same time! + #parallel# -  + While `WorldCommand`s can be combined with `(>>)` to produce sequential + instructions, we can also use the + alternative operator `(<|>)` to achieve parallel instructions. That is: + animate two turtles at time! - Note that the result of @a \<|\> b@ is: + Here is an example: + + > import Graphics.WorldTurtle + > + > main :: IO () + > main = runWorld $ do + > t1 <- makeTurtle' (0, 0) north green + > t2 <- makeTurtle' (0, 0) north red + > + > -- Draw the anticlockwise and clockwise circles in sequence. + > t1 >/> circle 90 >> t2 >/> circle (-90) + > + > clear + > + > -- Draw the anticlockwise and clockwise circles in parallel. + > t1 >/> circle 90 <|> t2 >/> circle (-90) + + Which would produce an animation like this + +  + + Note that the result of @x \<|\> y@ is: - >>> a <|> b - a + >>> x <|> y + x - when /a/ is not `Control.Monad.mzero`. --}+ when @x@ is not `Control.Applicative.empty`, otherwise the result is @y@. +-} + +{- $empty + If a `WorldCommand` is `Control.Applicative.empty`, then this stops this + section of animation and it does not progress. To this end + `Control.Monad.guard` can be used to calculate when to stop part of an + animation sequence. +-}
Graphics/WorldTurtle/Color.hs view
@@ -19,11 +19,14 @@ import Graphics.Gloss.Data.Color +import Graphics.WorldTurtle.Internal.Coords (degToRad) + -- | Rotates a given color's hue between [0, 360) degrees. shiftHue :: Float -- ^ Degrees to change hue. -> Color -- ^ Color to shift. -> Color -- ^ Resultant color with hue shifted. -shiftHue d c = let hMatrix = hueMatrix d +shiftHue d c = let d' = degToRad d -- Radians to degrees + hMatrix = hueMatrix d' (r, g, b, a) = rgbaOfColor c cMatrix = fromList 1 3 [r, g, b] cMatrix' = cMatrix * hMatrix @@ -33,7 +36,7 @@ -- Haskell form of solution posted here: -- https://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color hueMatrix :: Float -> Matrix Float -hueMatrix degrees = matrix 3 3 (flip calcForIndex degrees) +hueMatrix degrees = matrix 3 3 (`calcForIndex` degrees) calcForIndex :: (Int, Int) -> Float -> Float calcForIndex (1, 1) = diag_ @@ -45,7 +48,7 @@ calcForIndex (3, 1) = perm1_ calcForIndex (3, 2) = perm2_ calcForIndex (3, 3) = diag_ -calcForIndex _ = error $ "We only work with 3x3 matrices!" +calcForIndex _ = error "We only work with 3x3 matrices!" diag_ :: Float -> Float diag_ d = cos d + 1/3 * (1.0 - cos d) @@ -54,4 +57,4 @@ perm1_ d = 1/3 * (1 - cos d) - sqrt (1/3) * sin d perm2_ :: Float -> Float -perm2_ d = 1/3 * (1 - cos d) + sqrt (1/3) * sin d+perm2_ d = 1/3 * (1 - cos d) + sqrt (1/3) * sin d
Graphics/WorldTurtle/Commands.hs view
@@ -15,12 +15,18 @@ -} module Graphics.WorldTurtle.Commands ( - -- * Creating a turtle. + -- * Types Turtle + , P.Point + -- * WorldCommand commands. + -- ** Creating a turtle. , makeTurtle , makeTurtle' - -- * Movement commands. - , P.Point + -- ** Canvas commands. + , clear + , sleep + -- * TurtleCommand commands. + -- ** Movement commands. , forward , fd , backward @@ -30,6 +36,7 @@ , right , rt , Graphics.WorldTurtle.Commands.circle + , Graphics.WorldTurtle.Commands.arc , goto , setPosition , home @@ -38,8 +45,7 @@ , setRotationSpeed -- * Styling commands. , stamp - , representation - -- * Query turtle's state. + -- ** Query turtle's state. , position , heading , speed @@ -47,16 +53,15 @@ , penColor , penDown , penSize + , representation , visible - -- * Set rendering state. + -- ** Mutate turtle's state. + , branch , setPenColor , setPenDown , setPenSize , setRepresentation , setVisible - -- * Canvas commands. - , clear - , sleep -- * Common constants , east , north @@ -85,37 +90,32 @@ Creates a new `Turtle` and displays it on the canvas. This turtle can then be manipulated! For example, to create a turtle and then move the turtle forward: - @ - main:: IO () - main = runTurtle $ do - t <- makeTurtle - forward 90 t - @ - -The default turtle starts at position (0, 0) and is orientated `north`. + > main:: IO () + > main = runWorld $ do + > t <- makeTurtle + > t >/> forward 90 +The default turtle starts at position @(0, 0)@ and is orientated `north`. -} -makeTurtle :: TurtleCommand Turtle -makeTurtle = TurtleCommand generateTurtle +makeTurtle :: WorldCommand Turtle +makeTurtle = WorldCommand generateTurtle {-| This variant of `makeTurtle` takes a starting position, a starting orientation, and a color to apply to the turtle and the turtle's pen. - @ - myCommand :: TurtleCommand () - myCommand = do - t1 <- makeTurtle' (0, 0) 0 green - t2 <- makeTurtle' (0, 0) 90 red - forward 90 t1 \<|\> forward 90 t2 - @ + > myCommand :: WorldCommand () + > myCommand = do + > t1 <- makeTurtle' (0, 0) 0 green + > t2 <- makeTurtle' (0, 0) 90 red + > t1 >/> forward 90 <|> t2 >/> forward 90 See `makeTurtle`. -} makeTurtle' :: Point -- ^ Initial position of the turtle. -> Float -- ^ Initial heading of the turtle. -> Color -- ^ Color of the turtle and the turtle's pen. - -> TurtleCommand Turtle -- ^ The generated turtle. -makeTurtle' p f c = TurtleCommand $ do + -> WorldCommand Turtle -- ^ The generated turtle. +makeTurtle' p f c = WorldCommand $ do turtle <- generateTurtle let ts = turtLens_ turtle ts . T.position .= p @@ -127,12 +127,11 @@ -- | Move the turtle backward by the specified @distance@, in the direction the -- turtle is headed. backward :: Float -- ^ Distance to move the turtle. - -> Turtle -- ^ The turtle to move. -> TurtleCommand () backward !d = forward (-d) -- | Shorthand for `backward`. -bk :: Float -> Turtle -> TurtleCommand () +bk :: Float -> TurtleCommand () bk = backward calculateNewPointF_ :: P.Point -- ^ Starting point @@ -147,16 +146,16 @@ -- | Move the turtle forward by the specified @distance@, in the direction the -- turtle is headed. forward :: Float -- ^ Distance to move the turtle. - -> Turtle -- ^ The turtle to move. -> TurtleCommand () -forward !d turtle = TurtleCommand $ do +forward !d = TurtleCommand $ \ turtle -> do t <- tData_ turtle -- Get origin point animate' d (t ^. T.speed) $ \ q -> do -- Get new endpoint via percentage let !startP = t ^. T.position let !midP = calculateNewPointF_ startP d (t ^. T.heading) q - when (t ^. T.penDown) $ do -- don't draw if pen isn't in down state + -- don't draw if pen isn't in down state + when (t ^. T.penDown) $ addPicture $ color (t ^. T.penColor) $ thickLine startP midP (t ^. T.penSize) -- Draw line from startPoint to midPoint. @@ -164,40 +163,36 @@ -- Update the turtle to a new position -- | Shorthand for `forward`. -fd :: Float -> Turtle -> TurtleCommand () +fd :: Float -> TurtleCommand () fd = forward -- | Stamp a copy of the turtle shape onto the canvas at the current turtle -- position. -stamp :: Turtle -- ^ The turtle with the shape to be copied. - -> TurtleCommand () -stamp turtle = TurtleCommand $ tData_ turtle >>= addPicture . T.drawTurtle +stamp :: TurtleCommand () +stamp = TurtleCommand $ tData_ >=> (addPicture . T.drawTurtle) -- | Turn a turtle right by the given degrees amount. right :: Float -- ^ Rotation amount to apply to turtle. - -> Turtle -- ^ The turtle to rotate. -> TurtleCommand () right = rotateTo_ True -- | Shorthand for `right`. -rt :: Float -> Turtle -> TurtleCommand () +rt :: Float -> TurtleCommand () rt = right -- | Turn a turtle left by the given degrees amount. left :: Float -- ^ Rotation amount to apply to turtle. - -> Turtle -- ^ The turtle to rotate. -> TurtleCommand () left = rotateTo_ False -- | Shorthand for `left`. -lt :: Float -> Turtle -> TurtleCommand () +lt :: Float -> TurtleCommand () lt = left rotateTo_ :: Bool -- ^ Bias decides in which direction rotation happens. -> Float -- ^ Amount to rotate by - -> Turtle -- Turtle to modify. -> TurtleCommand () -rotateTo_ rightBias !r turtle = TurtleCommand $ do +rotateTo_ rightBias !r = TurtleCommand $ \ turtle -> do t <- tData_ turtle let r' = P.normalizeHeading r animate' (P.degToRad r') (t ^. T.rotationSpeed) $ \q -> do @@ -207,6 +202,18 @@ -- Get new heading via percentage turtLens_ turtle . T.heading .= newHeading +-- | Draw a circle with a given @radius@. The center is @radius@ units left of +-- the turtle if positive. Otherwise @radius@ units right of the turtle +-- if negative. +-- +-- The circle is drawn in an anticlockwise direction if the radius is +-- positive, otherwise, it is drawn in a clockwise direction. +-- +-- Circle is an alias for @circle r = arc r 360@. +circle :: Float -- ^ Radius of the circle. + -> TurtleCommand () +circle radius = Graphics.WorldTurtle.Commands.arc radius 360 + -- | Draws an arc starting from a given starting point on the edge of the -- circle. drawCircle_ :: P.Point -- ^ Point on edge of circle to start from @@ -217,11 +224,11 @@ -> Color -- ^ Color of circle -> Picture -- ^ Resulting circle drawCircle_ !p !radius !startAngle !endAngle !pSize !pColor = - translate (fst p) (snd p) $ rotate (180 - startAngle) - $ translate (-radius) 0 - $ color pColor - $ scale (if radius >= 0 then 1 else -1) 1 - $ thickArc 0 (endAngle) (abs radius) pSize + uncurry translate p $ rotate (180 - startAngle) + $ translate (-radius) 0 + $ color pColor + $ scale (if radius >= 0 then 1 else -1) 1 + $ thickArc 0 endAngle (abs radius) pSize -- Calculates the next position of a turtle on a circle. calculateNewPointC_ :: P.Point -- ^ Point on edge of circle @@ -237,27 +244,27 @@ else startAngle - angle -- | Draw an arc with a given @radius@. The center is @radius@ units left of the --- @turtle@ if positive. Otherwise @radius@ units right of the @turtle@ if +-- turtle if positive. Otherwise @radius@ units right of the turtle if -- negative. -- -- The arc is drawn in an anticlockwise direction if the radius is positive, -- otherwise, it is drawn in a clockwise direction. -circle :: Float -- ^ Radius of the circle. - -> Float -- ^ Angle to travel in degrees. +arc :: Float -- ^ Radius of the circle. + -> Float -- ^ Angle to travel in degrees. -- For example: @360@ for a full circle or @180@ for a -- semicircle. - -> Turtle -- ^ Turtle to move in a circle. - -> TurtleCommand () -circle !radius !r turtle = TurtleCommand $ do + -> TurtleCommand () +arc !radius !r = TurtleCommand $ \turtle -> do t <- tData_ turtle let !r' = P.normalizeHeading r animate' (abs radius * P.degToRad r') (t ^. T.speed) $ \ q -> do let !startAngle = t ^. T.heading + 90 let !p = t ^. T.position let !angle = r' * q - when (t ^. T.penDown) $ do -- don't draw if pen isn't in down state + -- don't draw if pen isn't in down state + when (t ^. T.penDown) $ addPicture $! drawCircle_ p radius startAngle angle - (t ^. T.penSize) (t ^. T.penColor) + (t ^. T.penSize) (t ^. T.penColor) -- Update the turtle with the new values. let ts = turtLens_ turtle @@ -271,15 +278,13 @@ -- | Returns the turtle's current position. -- Default (starting) position is @(0, 0)@. -position :: Turtle -- ^ Turtle to query. - -> TurtleCommand P.Point -- ^ Returned current point. +position :: TurtleCommand P.Point -- ^ Returned current point. position = getter_ (0, 0) T.position -- | Warps the turtle to its starting position @(0, 0)@ and resets the --- orientation to @North@ (90 degrees). No line is drawn moving the turtle. -home :: Turtle - -> TurtleCommand () -home turtle = TurtleCommand $ do +-- orientation to `north` (@90@ degrees). No line is drawn moving the turtle. +home :: TurtleCommand () +home = TurtleCommand $ \ turtle -> do let ts = turtLens_ turtle ts . T.position .= (0, 0) ts . T.heading .= 90 @@ -290,9 +295,8 @@ -- -- This does not affect the turtle's heading. goto :: P.Point -- ^ Position to warp to. - -> Turtle -- ^ Turtle to modify. -> TurtleCommand () -goto point turtle = TurtleCommand $ do +goto point = TurtleCommand $ \ turtle -> do t <- tData_ turtle let startP = t ^. T.position when (t ^. T.penDown) $ addPicture @@ -301,7 +305,7 @@ turtLens_ turtle . T.position .= point -- | Alias of `goto`. -setPosition :: P.Point -> Turtle -> TurtleCommand () +setPosition :: P.Point -> TurtleCommand () setPosition = goto -- | Returns the turtle's heading. @@ -314,136 +318,126 @@ -- * South is @270@ degrees. -- -- The default heading is North (@90@ degrees). -heading :: Turtle -- ^ Turtle to query. - -> TurtleCommand Float -- ^ Returned heading as angle in degrees. +heading :: TurtleCommand Float -- ^ Returned heading as angle in degrees. heading = getter_ 0 T.heading -- | Sets the turtle's heading. See `heading`. setHeading :: Float -- ^ Heading to apply. - -> Turtle -- ^ Turtle to modify. -> TurtleCommand () setHeading = setter_ T.heading -- | Returns the turtle's pen color. -- The color of the turtle's pen.The default color is @black@. -penColor :: Turtle -- ^ Turtle to query. - -> TurtleCommand Color -- ^ Returned current pen color. +penColor :: TurtleCommand Color -- ^ Returned current pen color. penColor = getter_ black T.penColor -- | Set the turtle's pen color. -- See `penColor`. setPenColor :: Color -- ^ New pen color to apply - -> Turtle -- ^ Turtle to modify. -> TurtleCommand () setPenColor = setter_ T.penColor -- | Returns whether the turtle's pen is down. -- When the turtle's pen is down it will draw a line when it moves. -- The default value is @True@. -penDown :: Turtle -- ^ Turtle to query. - -> TurtleCommand Bool -- ^ True if pen is down, false if not. +penDown :: TurtleCommand Bool -- ^ True if pen is down, false if not. penDown = getter_ False T.penDown -- | Sets the turtle's pen to down or up. -- See `penDown`. setPenDown :: Bool -- ^ New state for pen flag. True for down. False for up. - -> Turtle -- ^ Turtle to modify. -> TurtleCommand () setPenDown = setter_ T.penDown -- | Returns the turtle's pen size. -- Defaults to @2@. -penSize :: Turtle -- ^ Turtle to query. - -> TurtleCommand Float -- ^ Size of turtle's pen. +penSize :: TurtleCommand Float -- ^ Size of turtle's pen. penSize = getter_ 0 T.penSize -- | Sets the turtle's pen size. -- See `penSize`. setPenSize :: Float -- ^ New size for turtle's pen. - -> Turtle -- ^ Turtle to modify. -> TurtleCommand () setPenSize = setter_ T.penSize -- | Returns whether the turtle is visible. -- The default value is @True@. -visible :: Turtle -- ^ Turtle to query. - -> TurtleCommand Bool -- ^ @True@ if turtle is visible, @False@ if not. +visible :: TurtleCommand Bool -- ^ @True@ if turtle is visible, @False@ if not. visible = getter_ False T.visible -- | Sets the turtle's visibility. -- See `visible`. setVisible :: Bool -- ^ New state for visible flag. - -> Turtle -- ^ Turtle to modify. -> TurtleCommand () setVisible = setter_ T.visible -- | Returns the turtle's current speed. -- Speed is is @distance@ per second. --- A speed of 0 is equivalent to no animation being performed and instant +-- A speed of @0@ is equivalent to no animation being performed and instant -- movement. -- The default value is @200@. -speed :: Turtle -- ^ Turtle to query. - -> TurtleCommand Float -- ^ Speed of turtle. +speed :: TurtleCommand Float -- ^ Speed of turtle. speed = getter_ 0 T.speed -- | Sets the turtle's speed. -- See `speed`. setSpeed :: Float -- ^ New speed. - -> Turtle -- ^ Turtle to modify. -> TurtleCommand () setSpeed = setter_ T.speed -- | Returns the turtle's current rotation speed. -- Rotation speed is is the speed in seconds it takes to do a full revolution. --- A speed of 0 is equivalent to no animation being performed and instant +-- A speed of @0@ is equivalent to no animation being performed and instant -- rotation. -- The default value is @20@. -rotationSpeed :: Turtle -- ^ Turtle to query. - -> TurtleCommand Float -- ^ Rotation speed of turtle. +rotationSpeed :: TurtleCommand Float -- ^ Rotation speed of turtle. rotationSpeed = getter_ 0 T.rotationSpeed -- | Sets the turtle's rotation speed. -- See `rotationSpeed`. setRotationSpeed :: Float -- ^ New rotation speed. - -> Turtle -- ^ Turtle to modify. -> TurtleCommand () setRotationSpeed = setter_ T.rotationSpeed -- | Gets the turtle's representation as a `Picture`. -representation :: Turtle -- ^ Turtle to query. - -> TurtleCommand Picture +representation :: TurtleCommand Picture representation = getter_ blank T.representation {- | Sets the turtle's representation to a `Picture`. See `representation`. For example, to set the turtle as a red circle: - @ - import Graphics.WorldTurtle - import qualified Graphics.Gloss.Data.Picture as G - - myCommand :: TurtleCommand () - myCommand = do - t <- makeTurtle - setPenColor red t - setRepresentation (G.color red $ G.circleSolid 10) t - forward 90 t - @ + > import Graphics.WorldTurtle + > import qualified Graphics.Gloss.Data.Picture as G + > + > myCommand :: TurtleCommand () + > myCommand = do + > setPenColor red + > setRepresentation (G.color red $ G.circleSolid 10) + > forward 90 -} setRepresentation :: Picture -- ^ Picture to apply. - -> Turtle -- ^ Turtle to modify. -> TurtleCommand () setRepresentation = setter_ T.representation -- | Clears all drawings form the canvas. Does not alter any turtle's state. -clear :: TurtleCommand () -clear = TurtleCommand $ pics .= [] +clear :: WorldCommand () +clear = WorldCommand $ pics .= [] -- | Sleep for a given amount of time in seconds. When sleeping no animation -- runs. A negative value will be clamped to @0@. -sleep :: Float -> TurtleCommand () -sleep = TurtleCommand . decrementSimTime . max 0 +sleep :: Float -> WorldCommand () +sleep = WorldCommand . decrementSimTime . max 0 +-- | Given a command, runs the command, then resets the turtle's state back to +-- what the state was before the command was run. +branch :: TurtleCommand a -> TurtleCommand a +branch (TurtleCommand p ) = TurtleCommand $ \ turtle -> do + t <- tData_ turtle + output <- p turtle + turtLens_ turtle .= t + return output + -- | @90@ degrees. north :: Float north = 90 @@ -471,24 +465,24 @@ -> (T.TurtleData -> f T.TurtleData) -> TSC b -> f (TSC b) -turtLens_ t = turtles . at t . _Just +turtLens_ t = turtles . ix t {-# INLINE turtLens_ #-} -- | This is a helper function for our getter commands. -- It takes a default value, the lense to compose, and the turtle to inspect. -getter_ :: a -> Lens' T.TurtleData a -> Turtle -> TurtleCommand a -getter_ def l t = - TurtleCommand $ fromMaybe def <$> preuse (turtLens_ t . l) +getter_ :: a -> Lens' T.TurtleData a -> TurtleCommand a +getter_ def l = + TurtleCommand $ \ t -> fromMaybe def <$> preuse (turtLens_ t . l) {-# INLINE getter_ #-} -- | This is a helper function that extracts the turtle data for a given turtle. tData_ :: Turtle -> SeqC T.TurtleData -tData_ = seqT <$> getter_ T.defaultTurtle id +tData_ = seqT $ getter_ T.defaultTurtle id {-# INLINE tData_ #-} -- | This is a helper function for our setter commands -- It takes a lens, the value to apply, and the turtle to modify. -setter_ :: Lens' T.TurtleData b -> b -> Turtle -> TurtleCommand () -setter_ l val t = - TurtleCommand $ turtLens_ t . l .= val +setter_ :: Lens' T.TurtleData b -> b -> TurtleCommand () +setter_ l val = + TurtleCommand $ \ t -> turtLens_ t . l .= val {-# INLINE setter_ #-}
Graphics/WorldTurtle/Internal/Commands.hs view
@@ -2,6 +2,7 @@ module Graphics.WorldTurtle.Internal.Commands ( SeqC , TurtleCommand (..) + , WorldCommand (..) ) where import Control.Applicative @@ -13,74 +14,91 @@ type SeqC a = SequenceCommand (AlmostVal ()) a -{-| A `TurtleCommand` represents an instruction to execute. It could be as - simple as "draw a line" or more complicated like "draw 300 circles." - +{-| A `TurtleCommand` represents an instruction to execute on a turtle. + It could be as simple as "draw a line" or more complicated like + "draw 300 circles." + `TurtleCommand`s can be executed in order by combining them using the monadic operator `(>>)`. - Here is an example of how to write a function that when given a - @size@ and a @turtle@, will return a new `TurtleCommand` which - will draw a square with a length and breadth of @size@ using @turtle@. - - @ - drawSquare :: Float -> Turtle -> TurtleCommand () - drawSquare size t = replicateM_ 4 $ forward size t >> right 90 t - @ - - This draws a square by doing the following in order: - - [@(1/4)@]: + For example, to draw an equilateral triangle + using [do notation](https://en.wikibooks.org/wiki/Haskell/do_notation): - * Move forward by @size@ amount. + > drawTriangle :: TurtleCommand () + > drawTriangle = do + > setHeading east + > forward 100 + > left 120 + > forward 100 + > left 120 + > forward 100 - * Turn right by @90@ degrees + Which would produce: - [@(2/4)@]: +  +-} +newtype TurtleCommand a = TurtleCommand + { + seqT :: Turtle -> SeqC a + } - * Move forward by @size@ amount. +instance Functor TurtleCommand where + fmap f (TurtleCommand a) = TurtleCommand $ \ t -> fmap f (a t) - * Turn right by @90@ degrees +instance Applicative TurtleCommand where + pure a = TurtleCommand $ \ _ -> pure a + liftA2 f (TurtleCommand a) (TurtleCommand b) = + TurtleCommand $ \ t -> liftA2 f (a t) (b t) - [@(3/4)@]: +instance Monad TurtleCommand where + (TurtleCommand a) >>= f = TurtleCommand $ \ t -> a t >>= \s -> seqT (f s) t - * Move forward by @size@ amount. +instance MonadFail TurtleCommand where + fail t = TurtleCommand $ \ _ -> do + addPicture $ text t + failSequence - * Turn right by @90@ degrees +{- | A `WorldCommand` represents an instruction that affects the entire + animation canvas. + + This could be as simple as "make a turtle" or more complicated like + "run these 5 turtles in parallel." - [@(4/4)@]: + Like `TurtleCommand`s, `WorldCommand`s can be executed in order by + combining commands in order using the monadic operator `(>>)`. - * Move forward by @size@ amount. + To execute a `TurtleCommand` in a `WorldCommand`, use either the + `Graphics.WorldTurtle.run` function or the + `Graphics.WorldTurtle.>/>` operator. - * Turn right by @90@ degrees + For how to achieve parallel animations + see "Graphics.WorldTurtle#parallel". -} -newtype TurtleCommand a = TurtleCommand +newtype WorldCommand a = WorldCommand { - seqT :: SeqC a + seqW :: SeqC a } -instance Functor TurtleCommand where - fmap f (TurtleCommand a) = TurtleCommand $ fmap f a +instance Functor WorldCommand where + fmap f (WorldCommand a) = WorldCommand $ fmap f a -instance Applicative TurtleCommand where - pure a = TurtleCommand $ pure a - liftA2 f (TurtleCommand a) (TurtleCommand b) = TurtleCommand $ liftA2 f a b +instance Applicative WorldCommand where + pure a = WorldCommand $ pure a + liftA2 f (WorldCommand a) (WorldCommand b) = WorldCommand $ liftA2 f a b -instance Monad TurtleCommand where - (TurtleCommand a) >>= f = TurtleCommand $ a >>= \s -> seqT (f s) +instance Monad WorldCommand where + (WorldCommand a) >>= f = WorldCommand $ a >>= \s -> seqW (f s) -instance Alternative TurtleCommand where - empty = TurtleCommand failSequence - (<|>) (TurtleCommand a) (TurtleCommand b) = - TurtleCommand $ alternateSequence a b +instance Alternative WorldCommand where + empty = WorldCommand failSequence + (<|>) (WorldCommand a) (WorldCommand b) = WorldCommand $ alternateSequence a b -instance Semigroup a => Semigroup (TurtleCommand a) where - (TurtleCommand a) <> (TurtleCommand b) = - TurtleCommand $ combineSequence a b - -instance MonadPlus TurtleCommand +instance Semigroup a => Semigroup (WorldCommand a) where + (WorldCommand a) <> (WorldCommand b) = WorldCommand $ combineSequence a b -instance MonadFail TurtleCommand where - fail t = TurtleCommand $ do +instance MonadPlus WorldCommand + +instance MonadFail WorldCommand where + fail t = WorldCommand $ do addPicture $ text t - failSequence+ failSequence
Graphics/WorldTurtle/Internal/Coords.hs view
@@ -17,7 +17,11 @@ import Graphics.Gloss.Data.Vector as GVector import Graphics.Gloss.Geometry.Angle as GAngle -lerp :: Float -> Point -> Point -> Point +-- | What it says on the tin. A lerp function. +lerp :: Float -- Coefficient between 0 and 1. + -> Point -- Point /a/. + -> Point -- Point /b/. + -> Point -- new point some percentage value between /a/ and /b/. lerp !l !a !b = ((1 P.- l) `mulSV` a) + (l `mulSV` b) -- | Return a valid heading value between (0, 360].
Graphics/WorldTurtle/Internal/Sequence.hs view
@@ -103,7 +103,7 @@ -- See `setSimTime`. decrementSimTime :: Float -- ^ Value to subtract from store simulation time. -> SequenceCommand b () -decrementSimTime duration = simTime >>= setSimTime . (flip (-) duration) +decrementSimTime duration = simTime >>= setSimTime . flip (-) duration -- | Given a picture, adds it to the picture list. addPicture :: Picture -- ^ Picture to add to our animation @@ -138,7 +138,7 @@ in pictures $ s ^. pics ++ drawTurtles (s ^. turtles) drawTurtles :: Map Turtle TurtleData -> [Picture] -drawTurtles m = fmap drawTurtle $ Map.elems m +drawTurtles m = drawTurtle <$> Map.elems m generateTurtle :: SequenceCommand b Turtle generateTurtle = do @@ -246,7 +246,7 @@ -- animating is concerned is the same as not succeeding at all! decrementSimTime 0 - return $! (aVal, bVal) + return (aVal, bVal) -- | Calls our early exit and fails the callback. No calculations will be -- performed beyond this call. @@ -257,4 +257,4 @@ -- We can never reach this point with our call to `ex`. So the return type -- can be whatever we want it to be. Let's go crazy! let (Just x) = (Nothing :: Maybe Void) - in absurd x+ in absurd x
Graphics/WorldTurtle/Internal/Turtle.hs view
@@ -58,10 +58,10 @@ drawTurtle :: TurtleData -> Picture drawTurtle t - | t ^. visible == False = blank + | not (t ^. visible) = blank | otherwise = let (x, y) = _position t s = _scale t in translate x y - $ rotate (360 - t ^. heading) + $ rotate (360 - t ^. heading) -- Anticlockwise to clockwise. $ G.scale s s - $ (t ^. representation) + (t ^. representation)
Graphics/WorldTurtle/Shapes.hs view
@@ -33,7 +33,7 @@ -> Point -- ^ Ending point. -> Float -- ^ Line thickness. -> Picture -- ^ Produced line. -thickLine a b t = polygon $ [a1, a2, b2, b1] +thickLine a b t = polygon [a1, a2, b2, b1] where !v = b P.- a !angle = P.argV v !perpAngle = angle - (pi/2) @@ -46,7 +46,7 @@ !b2 = b P.+ t'' P.+ t' outline_ :: Color -> Picture -outline_ !c = color c $ translate (0) (-1) $ scale 1.4 1.4 $ fill_ c +outline_ !c = color c $ translate 0 (-1) $ scale 1.4 1.4 $ fill_ c fill_ :: Color -> Picture fill_ !c = color c $ translate (-4) (-2)
+ docs/images/drawtriangle.gif view
binary file changed (absent → 4517 bytes)
− docs/images/parallel_serial_turtles.gif
binary file changed (103400 → absent bytes)
+ docs/images/parallel_serial_turtles_2.gif view
binary file changed (absent → 32929 bytes)
worldturtle.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.0 name: worldturtle -version: 0.1.1.0 +version: 0.2.0.0 synopsis: Turtle graphics. category: teaching homepage: https://github.com/FortOyer/worldturtle-haskell#readme @@ -22,7 +22,7 @@ Turtle graphics are a fantastic introduction to the world of programming and to the syntax of a new programming language. . -  +  . This module is a framework built on top of [gloss](https://hackage.haskell.org/package/gloss) to render turtles @@ -49,7 +49,7 @@ Graphics.WorldTurtle.Internal.Turtle Graphics.WorldTurtle.Shapes build-depends: - base >=4.7 && <5 + base >=4.13 && <5 , containers >=0.6.2 && < 0.7 , gloss >=1.13.1 && < 1.14 , lens >=4.18.1 && < 4.20