Skip to content

Commit

Permalink
Add gloss chapter in UI track
Browse files Browse the repository at this point in the history
  • Loading branch information
Manuel Bärenz committed Jun 7, 2024
1 parent b0ce320 commit 5197f79
Show file tree
Hide file tree
Showing 32 changed files with 2,126 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ jobs:
with:
ghc-version: ${{ matrix.ghc }}

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libgl1-mesa-dev libglu1-mesa-dev freeglut3-dev
- name: Configure the build
run: |
cabal configure --enable-tests --enable-benchmarks --disable-documentation
Expand Down
5 changes: 5 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@

* Check cabal outdated
* Check in diffs between solution and problem and make sure it is stable (such that every fix in a solution gets propagated to the problem and vice versa)

## UI

* square that rotates with time
* paint, clear, paintAll: Maybe more complicated program with several paint calls, picture builds up if you forget to call clear
2 changes: 2 additions & 0 deletions diffs/koans/ui/1-gloss/1-circle/diff.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
< constMCl (paintAllIO _) -- paintAllIO clears the drawing canvas and draws the given image
> constMCl (paintAllIO (circleSolid 10)) -- paintAllIO clears the drawing canvas and draws the given image
2 changes: 2 additions & 0 deletions diffs/koans/ui/1-gloss/2-move/diff.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
< rhine = sinceInitS >-> arrMCl (\t -> translate 0 (10 * t) $ paintAllIO $ circleSolid 10) @@ GlossSimClockIO
> rhine = sinceInitS >-> arrMCl (\t -> paintAllIO $ translate 0 (10 * t) $ circleSolid 10) @@ GlossSimClockIO
15 changes: 15 additions & 0 deletions diffs/koans/ui/1-gloss/3-modularize/diff.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
> -- base
> import GHC.Float (double2Float)
>
< movingCircle = sinceInitS >-> arr (\t -> translate 0 (10 * t) $ circleSolid 10) -- realToFrac works as well!
> movingCircle = sinceInitS >-> arr (\t -> translate 0 (10 * double2Float t) $ circleSolid 10) -- realToFrac works as well!
< _ (Millisecond 500)
> GlossConcTClock IO (Millisecond 500)
< gameClock = _ waitClock
> gameClock = glossConcTClock waitClock
< _ _ GlossSimClockIO
> GlossClockUTC IO GlossSimClockIO
< visualizationClock = _ GlossSimClockIO
> visualizationClock = glossClockUTC GlossSimClockIO
< rhine = movingCircle @@ gameClock >-- _ blank --> visualize @@ visualizationClock
> rhine = movingCircle @@ gameClock >-- keepLast blank --> visualize @@ visualizationClock
10 changes: 10 additions & 0 deletions diffs/koans/ui/1-gloss/4-user-input/diff.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
< _ -> _
> (EventKey (SpecialKey KeyRight) Down _ _) -> Just TurnRight
> (EventKey (SpecialKey KeyLeft) Down _ _) -> Just TurnLeft
> _ -> Nothing
< let newDirection = _
< newPosition = _
< in Result _ _
> let newDirection = maybe direction (`changeDirection` direction) turnMaybe
> newPosition = stepPosition newDirection position
> in Result (newPosition, newDirection) newPosition
9 changes: 9 additions & 0 deletions diffs/koans/ui/1-gloss/5-randomness/diff.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
< arr (Just <<< Apple) <<< _ -< (Position (-boardSize) (-boardSize), Position boardSize boardSize)
> arr (Just <<< Apple) <<< getRandomRS -< (Position (-boardSize) (-boardSize), Position boardSize boardSize)
< addedApple <- _ -< ()
> addedApple <- evalRandIOS' newApple -< ()
< game = _
> game = feedback DontEat $ proc (turn, eat) -> do
> snake <- snakeSF -< (turn, eat)
> (apples, eatNext) <- applesSF -< head $ body snake
> returnA -< ((snake, apples), eatNext)
3 changes: 3 additions & 0 deletions diffs/koans/ui/1-gloss/6-control-flow/diff.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
< _
> try $ liftClSF snakeAndApples >>> throwOnCond (fst >>> illegal) () >>> arr Just
> safe $ pure Nothing
50 changes: 50 additions & 0 deletions generic/test-gloss/TestGloss.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
module TestGloss where

-- base
import Control.Concurrent
import Control.Monad
import Data.IORef
import System.Exit

-- rhine-gloss
import FRP.Rhine.Gloss

expectPic :: Picture -> [Picture] -> IO ()
expectPic received expected = expectPics [received] [expected]

expectPics :: [Picture] -> [[Picture]] -> IO ()
expectPics receiveds expecteds = do
forM_ (zip receiveds expecteds) $ \(received, expected) -> do
let flattened = flattenPictures received
when (flattened /= expected) $ do
putStrLn $ "Expected: " ++ show expected
putStrLn $ "Received: " ++ show flattened
exitFailure
putStrLn "Well done!"

flattenPictures :: Picture -> [Picture]
flattenPictures (Pictures ps) = ps >>= flattenPictures
flattenPictures Blank = []
flattenPictures picture = [picture]

stepGlossRhine :: (Clock GlossConc cl, Time cl ~ Time (Out cl), Time cl ~ Time (In cl), GetClockProxy cl) => Rhine GlossConc cl () () -> [Float] -> IO [Picture]

Check failure on line 30 in generic/test-gloss/TestGloss.hs

View workflow job for this annotation

GitHub Actions / Haskell GHC 9.2.8 cabal

• Illegal equational constraint Time cl ~ Time (Out cl)
stepGlossRhine rhine timestamps = stepGlossRhineWithInput rhine timestamps []

stepGlossRhineWithInput :: (Clock GlossConc cl, Time cl ~ Time (Out cl), Time cl ~ Time (In cl), GetClockProxy cl) => Rhine GlossConc cl () () -> [Float] -> [Event] -> IO [Picture]

Check failure on line 33 in generic/test-gloss/TestGloss.hs

View workflow job for this annotation

GitHub Actions / Haskell GHC 9.2.8 cabal

• Illegal equational constraint Time cl ~ Time (Out cl)
stepGlossRhineWithInput rhine timestamps events = do
vars <- makeGlossEnv
void $ forkIO $ forM_ events $ putMVar $ eventVar vars
void $ forkIO $ runGlossConcT (flow rhine) vars
forM timestamps $ \timestamp -> do
putMVar (timeVar vars) timestamp
threadDelay 33333
readIORef (picRef vars)

specialKey :: SpecialKey -> Event
specialKey key = EventKey (SpecialKey key) Down (Modifiers Down Down Down) (0, 0)

keyRight :: Event
keyRight = specialKey KeyRight

keyLeft :: Event
keyLeft = specialKey KeyLeft
135 changes: 135 additions & 0 deletions hie.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,138 @@ cradle:

- path: "generic/reimport-main/Main.hs"
component: "rhine-koans:test:basic-2-9-modularize-test"

- path: "generic/reimport-main/Main.hs"
component: "rhine-koans:exe:ui-1-gloss-1-circle"

- path: "generic/test-gloss"
component: "rhine-koans:test:ui-1-gloss-1-circle-test"

- path: "generic/reimport-main/Main.hs"
component: "rhine-koans:test:ui-1-gloss-1-circle-test"

- path: "koans/ui/1-gloss/1-circle/test/Test.hs"
component: "rhine-koans:test:ui-1-gloss-1-circle-test"

- path: "koans/ui/1-gloss/1-circle"
component: "rhine-koans:test:ui-1-gloss-1-circle-test"

- path: "koans/ui/1-gloss/1-circle/solution"
component: "rhine-koans:test:ui-1-gloss-1-circle-test"

- path: "koans/ui/1-gloss/1-circle"
component: "rhine-koans:exe:ui-1-gloss-1-circle"

- path: "koans/ui/1-gloss/1-circle/solution"
component: "rhine-koans:exe:ui-1-gloss-1-circle"

- path: "generic/reimport-main/Main.hs"
component: "rhine-koans:exe:ui-1-gloss-2-move"

- path: "generic/reimport-main/Main.hs"
component: "rhine-koans:test:ui-1-gloss-2-move-test"

- path: "koans/ui/1-gloss/2-move/test/Test.hs"
component: "rhine-koans:test:ui-1-gloss-2-move-test"

- path: "koans/ui/1-gloss/2-move"
component: "rhine-koans:test:ui-1-gloss-2-move-test"

- path: "koans/ui/1-gloss/2-move/solution"
component: "rhine-koans:test:ui-1-gloss-2-move-test"

- path: "koans/ui/1-gloss/2-move"
component: "rhine-koans:exe:ui-1-gloss-2-move"

- path: "koans/ui/1-gloss/2-move/solution"
component: "rhine-koans:exe:ui-1-gloss-2-move"

- path: "generic/reimport-main/Main.hs"
component: "rhine-koans:exe:ui-1-gloss-3-modularize"

- path: "generic/reimport-main/Main.hs"
component: "rhine-koans:test:ui-1-gloss-3-modularize-test"

- path: "koans/ui/1-gloss/3-modularize/test/Test.hs"
component: "rhine-koans:test:ui-1-gloss-3-modularize-test"

- path: "koans/ui/1-gloss/3-modularize"
component: "rhine-koans:test:ui-1-gloss-3-modularize-test"

- path: "koans/ui/1-gloss/3-modularize/solution"
component: "rhine-koans:test:ui-1-gloss-3-modularize-test"

- path: "koans/ui/1-gloss/3-modularize"
component: "rhine-koans:exe:ui-1-gloss-3-modularize"

- path: "koans/ui/1-gloss/3-modularize/solution"
component: "rhine-koans:exe:ui-1-gloss-3-modularize"

- path: "generic/reimport-main/Main.hs"
component: "rhine-koans:exe:ui-1-gloss-4-user-input"

- path: "generic/reimport-main/Main.hs"
component: "rhine-koans:test:ui-1-gloss-4-user-input-test"

- path: "koans/ui/1-gloss/4-user-input/test/Test.hs"
component: "rhine-koans:test:ui-1-gloss-4-user-input-test"

- path: "koans/ui/1-gloss/4-user-input"
component: "rhine-koans:test:ui-1-gloss-4-user-input-test"

- path: "koans/ui/1-gloss/4-user-input/solution"
component: "rhine-koans:test:ui-1-gloss-4-user-input-test"

- path: "koans/ui/1-gloss/4-user-input"
component: "rhine-koans:exe:ui-1-gloss-4-user-input"

- path: "koans/ui/1-gloss/4-user-input/solution"
component: "rhine-koans:exe:ui-1-gloss-4-user-input"

- path: "generic/reimport-main/Main.hs"
component: "rhine-koans:exe:ui-1-gloss-5-randomness"

- path: "generic/reimport-main/Main.hs"
component: "rhine-koans:test:ui-1-gloss-5-randomness-test"

- path: "koans/ui/1-gloss/5-randomness/test/Test.hs"
component: "rhine-koans:test:ui-1-gloss-5-randomness-test"

- path: "koans/ui/1-gloss/5-randomness"
component: "rhine-koans:test:ui-1-gloss-5-randomness-test"

- path: "koans/ui/1-gloss/5-randomness/solution"
component: "rhine-koans:test:ui-1-gloss-5-randomness-test"

- path: "koans/ui/1-gloss/5-randomness"
component: "rhine-koans:exe:ui-1-gloss-5-randomness"

- path: "koans/ui/1-gloss/5-randomness/solution"
component: "rhine-koans:exe:ui-1-gloss-5-randomness"

- path: "generic/reimport-main/Main.hs"
component: "rhine-koans:exe:ui-1-gloss-6-control-flow"

- path: "generic/reimport-main/Main.hs"
component: "rhine-koans:test:ui-1-gloss-6-control-flow-test"

- path: "koans/ui/1-gloss/6-control-flow/test/Test.hs"
component: "rhine-koans:test:ui-1-gloss-6-control-flow-test"

- path: "koans/ui/1-gloss/6-control-flow"
component: "rhine-koans:test:ui-1-gloss-6-control-flow-test"

- path: "koans/ui/1-gloss/6-control-flow/solution"
component: "rhine-koans:test:ui-1-gloss-6-control-flow-test"

- path: "koans/ui/1-gloss/6-control-flow"
component: "rhine-koans:exe:ui-1-gloss-6-control-flow"

- path: "koans/ui/1-gloss/6-control-flow/solution"
component: "rhine-koans:exe:ui-1-gloss-6-control-flow"

- path: "generic/reimport-main/Main.hs"
component: "rhine-koans:exe:ui-1-gloss-snake"

- path: "generic/reimport-main/Main.hs"
component: "rhine-koans:test:ui-1-gloss-snake-test"
10 changes: 5 additions & 5 deletions koans/basic/1/4-compose/Koan.hs
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ produceMessage =

{- | Outputs a message every second.
This component consumes `Text` as input.
|
| But it produces no output.
| |
v v
This component consumes `Text` as input.
|
| But it produces no output.
| |
v v
-}
printMessage :: ClSF IO EverySecond Text ()
printMessage =
Expand Down
41 changes: 41 additions & 0 deletions koans/ui/1-gloss/1-circle/Koan.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{- | Circle.
Let's draw something!
Rhine connects to the famous gloss library for 2d graphics.
Have a look at https://hackage.haskell.org/package/gloss to learn more about it!
The connection between Rhine and gloss is provided by the library https://hackage.haskell.org/package/rhine-gloss,
which encapsulates the effects of drawing pictures in gloss in a monad, 'GlossConcT',
and provides several clocks to interact with the gloss system.
To warm up, let's just draw a circle.
-}
module Koan where

-- rhine
import FRP.Rhine

-- rhine-gloss
import FRP.Rhine.Gloss

{- | The main 'Rhine' of this program.
/--- We use effects in 'GlossConc' to draw images.
|
| /--- This clock ticks whenever an image is drawn on the screen by the gloss backend.
| |
v v
-}
rhine :: Rhine GlossConc GlossSimClockIO () ()
-- Can you create a solid circle of radius 10 here?
-- Have a look at https://hackage.haskell.org/package/gloss/docs/Graphics-Gloss-Data-Picture.html for inspiration.
rhine =
constMCl (paintAllIO _) -- paintAllIO clears the drawing canvas and draws the given image
@@ GlossSimClockIO -- The singleton value of GlossSimClockIO.

main :: IO ()
-- Make sure to keep this definition here as it is: The tests depend on it.
main =
flowGlossIO -- This function can replace 'flow' when you're using the gloss backend.
defaultSettings -- Settings for the gloss window context such as size, title, and background colour.
rhine
41 changes: 41 additions & 0 deletions koans/ui/1-gloss/1-circle/solution/Koan.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{- | Circle.
Let's draw something!
Rhine connects to the famous gloss library for 2d graphics.
Have a look at https://hackage.haskell.org/package/gloss to learn more about it!
The connection between Rhine and gloss is provided by the library https://hackage.haskell.org/package/rhine-gloss,
which encapsulates the effects of drawing pictures in gloss in a monad, 'GlossConcT',
and provides several clocks to interact with the gloss system.
To warm up, let's just draw a circle.
-}
module Koan where

-- rhine
import FRP.Rhine

-- rhine-gloss
import FRP.Rhine.Gloss

{- | The main 'Rhine' of this program.
/--- We use effects in 'GlossConc' to draw images.
|
| /--- This clock ticks whenever an image is drawn on the screen by the gloss backend.
| |
v v
-}
rhine :: Rhine GlossConc GlossSimClockIO () ()
-- Can you create a solid circle of radius 10 here?
-- Have a look at https://hackage.haskell.org/package/gloss/docs/Graphics-Gloss-Data-Picture.html for inspiration.
rhine =
constMCl (paintAllIO (circleSolid 10)) -- paintAllIO clears the drawing canvas and draws the given image
@@ GlossSimClockIO -- The singleton value of GlossSimClockIO.

main :: IO ()
-- Make sure to keep this definition here as it is: The tests depend on it.
main =
flowGlossIO -- This function can replace 'flow' when you're using the gloss backend.
defaultSettings -- Settings for the gloss window context such as size, title, and background colour.
rhine
15 changes: 15 additions & 0 deletions koans/ui/1-gloss/1-circle/test/Test.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Main where

-- rhine-gloss
import FRP.Rhine.Gloss

-- test-gloss
import TestGloss

-- koan
import Koan (rhine)

main :: IO ()
main = do
[pic] <- stepGlossRhine rhine [1]
expectPic pic [circleSolid 10]
Loading

0 comments on commit 5197f79

Please sign in to comment.