From e8b7121deaec5e4485593de1d6236a7ed4a42d83 Mon Sep 17 00:00:00 2001 From: Grzegorz Balcerek Date: Sat, 17 Jan 2015 15:10:44 +0100 Subject: [PATCH] updated for Elm 0.14.1 --- ...ignals.elm => Chapter10KeyboardSignals.elm | 42 ++- Chapter12Paddle.elm => Chapter11Paddle.elm | 35 +- ...r13TicTacToe.elm => Chapter12TicTacToe.elm | 143 ++++---- Chapter14Snake.elm => Chapter13Snake.elm | 165 +++++---- ...visited.elm => Chapter14SnakeRevisited.elm | 36 +- Chapter1HelloWorld.elm | 316 +++++++++++++----- Chapter2FibonacciBars.elm | 226 +++++++------ Chapter3MouseSignals.elm | 97 +++--- Chapter4WindowSignals.elm | 102 +++--- Chapter5Eyes.elm | 66 ++-- Chapter6TimeSignals.elm | 30 +- Chapter7DelayedCircles.elm | 116 +++++-- Chapter9Circles.elm => Chapter8Circles.elm | 296 +++++++++------- Chapter8RandomSignals.elm | 64 ---- ...10Calculator.elm => Chapter9Calculator.elm | 184 +++++----- Introduction.elm | 21 +- LICENSE | 5 + LICENSE.md | 36 -- Lib.elm | 56 ++-- clean.bat | 5 +- code/Calculator.elm | 35 -- code/CalculatorModel.elm | 69 ---- code/CalculatorView.elm | 90 ----- code/CalculatorViewTest1.elm | 5 - code/Circles.elm | 115 ------- code/CirclesModel.elm | 19 -- code/CirclesTest.elm | 29 -- code/CirclesView.elm | 35 -- code/DelayedCircles.elm | 10 - code/DelayedMousePositions.elm | 26 -- code/DrawCircles.elm | 33 -- code/Eyes.elm | 10 - code/EyesModel.elm | 40 --- code/EyesView.elm | 37 -- code/Fibonacci.elm | 15 - code/FibonacciBars.elm | 20 -- code/Foldpm.elm | 25 -- code/GenericList.elm | 8 - code/HelloWorld1.elm | 1 - code/HelloWorld2.elm | 11 - code/HelloWorld3.elm | 15 - code/HelloWorld4.elm | 9 - code/KeyboardSignals1.elm | 4 - code/KeyboardSignals2.elm | 4 - code/KeyboardSignals3.elm | 5 - code/KeyboardSignals4.elm | 4 - code/MouseSignals1.elm | 4 - code/MouseSignals2.elm | 8 - code/MouseSignals3.elm | 31 -- code/Paddle.elm | 120 ------- code/RandomSignals1.elm | 5 - code/RandomSignals2.elm | 5 - code/RandomSignals3.elm | 8 - code/Snake.elm | 9 - code/SnakeModel.elm | 100 ------ code/SnakeRevisited.elm | 7 - code/SnakeSignals.elm | 45 --- code/SnakeState.elm | 47 --- code/SnakeStateRevisited.elm | 66 ---- code/SnakeView.elm | 69 ---- code/TicTacToe.elm | 42 --- code/TicTacToeModel.elm | 131 -------- code/TicTacToeView.elm | 68 ---- code/TimeSignals.elm | 27 -- code/WindowSignals1.elm | 24 -- code/WindowSignals2.html | 13 - elm-package.json | 14 + examples.bat | 17 + extractCode.hs | 39 ++- index.elm | 16 +- make.bat | 142 +++----- prepareChapterSource.hs | 2 +- toc.elm | 23 +- 73 files changed, 1317 insertions(+), 2480 deletions(-) rename Chapter11KeyboardSignals.elm => Chapter10KeyboardSignals.elm (78%) rename Chapter12Paddle.elm => Chapter11Paddle.elm (91%) rename Chapter13TicTacToe.elm => Chapter12TicTacToe.elm (85%) rename Chapter14Snake.elm => Chapter13Snake.elm (82%) rename Chapter15SnakeRevisited.elm => Chapter14SnakeRevisited.elm (94%) rename Chapter9Circles.elm => Chapter8Circles.elm (71%) delete mode 100644 Chapter8RandomSignals.elm rename Chapter10Calculator.elm => Chapter9Calculator.elm (82%) create mode 100644 LICENSE delete mode 100644 LICENSE.md delete mode 100644 code/Calculator.elm delete mode 100644 code/CalculatorModel.elm delete mode 100644 code/CalculatorView.elm delete mode 100644 code/CalculatorViewTest1.elm delete mode 100644 code/Circles.elm delete mode 100644 code/CirclesModel.elm delete mode 100644 code/CirclesTest.elm delete mode 100644 code/CirclesView.elm delete mode 100644 code/DelayedCircles.elm delete mode 100644 code/DelayedMousePositions.elm delete mode 100644 code/DrawCircles.elm delete mode 100644 code/Eyes.elm delete mode 100644 code/EyesModel.elm delete mode 100644 code/EyesView.elm delete mode 100644 code/Fibonacci.elm delete mode 100644 code/FibonacciBars.elm delete mode 100644 code/Foldpm.elm delete mode 100644 code/GenericList.elm delete mode 100644 code/HelloWorld1.elm delete mode 100644 code/HelloWorld2.elm delete mode 100644 code/HelloWorld3.elm delete mode 100644 code/HelloWorld4.elm delete mode 100644 code/KeyboardSignals1.elm delete mode 100644 code/KeyboardSignals2.elm delete mode 100644 code/KeyboardSignals3.elm delete mode 100644 code/KeyboardSignals4.elm delete mode 100644 code/MouseSignals1.elm delete mode 100644 code/MouseSignals2.elm delete mode 100644 code/MouseSignals3.elm delete mode 100644 code/Paddle.elm delete mode 100644 code/RandomSignals1.elm delete mode 100644 code/RandomSignals2.elm delete mode 100644 code/RandomSignals3.elm delete mode 100644 code/Snake.elm delete mode 100644 code/SnakeModel.elm delete mode 100644 code/SnakeRevisited.elm delete mode 100644 code/SnakeSignals.elm delete mode 100644 code/SnakeState.elm delete mode 100644 code/SnakeStateRevisited.elm delete mode 100644 code/SnakeView.elm delete mode 100644 code/TicTacToe.elm delete mode 100644 code/TicTacToeModel.elm delete mode 100644 code/TicTacToeView.elm delete mode 100644 code/TimeSignals.elm delete mode 100644 code/WindowSignals1.elm delete mode 100644 code/WindowSignals2.html create mode 100644 elm-package.json create mode 100644 examples.bat diff --git a/Chapter11KeyboardSignals.elm b/Chapter10KeyboardSignals.elm similarity index 78% rename from Chapter11KeyboardSignals.elm rename to Chapter10KeyboardSignals.elm index 4507af3..fad6245 100644 --- a/Chapter11KeyboardSignals.elm +++ b/Chapter10KeyboardSignals.elm @@ -2,12 +2,14 @@ import Lib (..) import Window +import Signal +import Markdown -main = lift (pageTemplate [content] "Chapter10Calculator" "toc" "Chapter12Paddle") Window.width +main = Signal.map (pageTemplate [content] "Chapter9Calculator" "toc" "Chapter11Paddle") Window.width -content = [markdown| +content = Markdown.toElement """ -# Chapter 11 Keyboard Signals +# Chapter 10 Keyboard Signals The `Keyboard` module defines functions for dealing with keyboard input. Elm defines the `KeyCode` type as an alias for `Int` and @@ -39,10 +41,15 @@ codes. [Try](KeyboardSignals1.html) the action: % KeyboardSignals1.elm + module KeyboardSignals1 where + + import Keyboard + import Signal ((<~)) + import Text (asText) - main = lift asText Keyboard.keysDown + main = asText <~ Keyboard.keysDown The `lastPressed` function returns a signal which provides the code of the last pressed key — even when the key is not currently being @@ -51,10 +58,15 @@ pressed any more. Check it out using the [here](KeyboardSignals2.html): % KeyboardSignals2.elm + module KeyboardSignals2 where + + import Keyboard + import Signal ((<~)) + import Text (asText) - main = lift asText Keyboard.lastPressed + main = asText <~ Keyboard.lastPressed The `isDown` function takes a key code as its argument and returns a boolean signal indicating whether the given key is currently being @@ -65,11 +77,16 @@ for certain special keys: `shift`, `ctrl`, `space` and pressing the *A* key: % KeyboardSignals3.elm - import Keyboard + module KeyboardSignals3 where + + import Char + import Keyboard + import Signal ((<~)) + import Text (asText) - main = lift asText (Keyboard.isDown (Char.toCode 'A')) + main = asText <~ (Keyboard.isDown (Char.toCode 'A')) The `directions` function is useful for building games. It takes four key codes as arguments and returns a signal of `{ x: Int, y: Int @@ -88,15 +105,20 @@ member in a similar way. [Run](KeyboardSignals4.html) the pressing the *Q*, *A*, *O* and *P* keys in various combinations: % KeyboardSignals4.elm + module KeyboardSignals4 where + + import Keyboard + import Signal ((<~)) + import Text (asText) - main = lift asText (Keyboard.directions 81 65 79 80) + main = asText <~ (Keyboard.directions 81 65 79 80) There are also two helper functions defined in terms of `directions`: `wasd` and `arrows`. -The [next](Chapter12Paddle.html) chapter presents a game which uses +The [next](Chapter11Paddle.html) chapter presents a game which uses keyboard as input. -|] +""" diff --git a/Chapter12Paddle.elm b/Chapter11Paddle.elm similarity index 91% rename from Chapter12Paddle.elm rename to Chapter11Paddle.elm index 1efc423..71161e3 100644 --- a/Chapter12Paddle.elm +++ b/Chapter11Paddle.elm @@ -2,14 +2,16 @@ import Lib (..) import Window +import Signal +import Markdown content w = pageTemplate [content1] - "Chapter11KeyboardSignals" "toc" "Chapter13TicTacToe" w -main = lift content Window.width + "Chapter10KeyboardSignals" "toc" "Chapter12TicTacToe" w +main = Signal.map content Window.width -content1 = [markdown| +content1 = Markdown.toElement """ -# Chapter 12 Paddle +# Chapter 11 Paddle Now that we know keyboard signals, we will use them to create a game. The *[Paddle.elm](Paddle.elm)* program is a game. The player uses the @@ -23,9 +25,14 @@ The code starts with the `Paddle` module declaration and a list of imports. module Paddle where - import Window - import Text as T + import Color (blue, green, orange, red, white) + import Graphics.Collage (Form, circle, collage, filled, group, move, moveY, rect) + import Graphics.Element (Element, container, empty, layers, middle) import Keyboard + import Signal ((<~), Signal, foldp, merge) + import Text as T + import Time (Time, fps) + import Window After the imports, four functions which draw various elements of the game view are defined. The `borders` function draws the blue wall by @@ -63,7 +70,7 @@ to the user when the game is over. gameOver : Element gameOver = - T.toText "Game Over" + T.fromString "Game Over" |> T.color red |> T.bold |> T.height 60 @@ -73,7 +80,7 @@ to the user when the game is over. The `State` type represents the state of the game. % Paddle.elm - type State = + type alias State = { x: Float, y: Float, @@ -128,7 +135,7 @@ a data type representing events from both signals. The `Event` data type represents the events. % Paddle.elm - data Event = Tick Time | PaddleDx Int + type Event = Tick Time | PaddleDx Int The `Tick` constructor takes a `Time` value as parameter and creates an event representing a time-based tick. The `PaddleDx` constructor @@ -140,7 +147,7 @@ standard library `fps` function. % Paddle.elm clockSignal : Signal Event - clockSignal = lift Tick <| fps 100 + clockSignal = Tick <~ fps 100 The `keyboardSignal` function uses the `Keyboard.arrows` signal to create a signal of keyboard-based events. Only the `x` members from @@ -151,7 +158,7 @@ events: `-1`, `0`, or `1`. % Paddle.elm keyboardSignal : Signal Event - keyboardSignal = lift (.x >> PaddleDx) Keyboard.arrows + keyboardSignal = (.x >> PaddleDx) <~ Keyboard.arrows The `eventSignal` merges both signal into one combined signal. % Paddle.elm @@ -232,7 +239,7 @@ producing the final game signal that is rendered on the screen. % Paddle.elm main : Signal Element - main = lift view gameSignal + main = view <~ gameSignal In order to transform the game state, we have used a monolitic `step` function, that reacts to each possible combination of input event and @@ -240,7 +247,7 @@ current state. The solution works, but it has the disadvantage that the function which transforms the state may become big and difficult to maintain for larger programs. We will explore alternatives to that approach in the subsequent chapters. The -[next](Chapter13TicTacToe.html) chapter presents a program which uses +[next](Chapter12TicTacToe.html) chapter presents a program which uses an alternative approach. -|] +""" diff --git a/Chapter13TicTacToe.elm b/Chapter12TicTacToe.elm similarity index 85% rename from Chapter13TicTacToe.elm rename to Chapter12TicTacToe.elm index c3c5639..f73b9df 100644 --- a/Chapter13TicTacToe.elm +++ b/Chapter12TicTacToe.elm @@ -2,16 +2,20 @@ import Lib (..) import Window +import Signal (..) +import Markdown +import Graphics.Element (..) +import Graphics.Collage (..) content w = pageTemplate [ content1 , container w 510 middle picture1 , content2 - ] "Chapter12Paddle" "toc" "Chapter14Snake" w -main = lift content Window.width + ] "Chapter11Paddle" "toc" "Chapter13Snake" w +main = content <~ Window.width -content1 = [markdown| +content1 = Markdown.toElement """ -# Chapter 13 Tic Tac Toe +# Chapter 12 Tic Tac Toe This chapter presents a program implementing the Tic Tac Toe game. You can play it [here](TicTacToe.html) using your mouse to select the @@ -27,30 +31,34 @@ The code is divided into three modules: * `TicTacToe` We start our analysis with the `TicTacToeModel` module defined in the -*[TicTacToeModel.elm](TicTacToeModel.elm)* file. The module -declaration is followed by several data type declarations. +*[TicTacToeModel.elm](TicTacToeModel.elm)* file. The module contains +several data type declarations. % TicTacToeModel.elm module TicTacToeModel where - data Player = O | X + import List ((::), all, filter, head, isEmpty, length, map, tail) - data Result = Draw | Winner Player + type Player = O | X - type Field = { col: Int, row: Int } + type Result = Draw | Winner Player - type Move = (Field,Player) + type alias Field = { col: Int, row: Int } - type Moves = [Move] + type alias Move = (Field,Player) - data GameState = FinishedGame Result Moves - | NotFinishedGame Player Moves + type alias Moves = List Move + + + type GameState = + FinishedGame Result Moves + | NotFinishedGame Player Moves The `Player` data type represents the two players. The `Result` data type represents the result of the game — either a draw or a victory of @@ -94,7 +102,7 @@ occupied, given the list of moves made so far. % TicTacToeModel.elm isFieldEmpty : Moves -> Field -> Bool - isFieldEmpty moves field = all (\move -> not (fst move == field)) moves + isFieldEmpty moves field = all (\\move -> not (fst move == field)) moves The function uses the `all` function, which takes a predicate function (a function that takes an argument and returns a boolean value) and a @@ -113,24 +121,24 @@ The `subsequences` function is a helper function that works on lists, but is not available in the standard library `List` module. % TicTacToeModel.elm - subsequences : [a] -> [[a]] + subsequences : List a -> List (List a) subsequences lst = case lst of [] -> [[]] h::t -> let st = subsequences t in - st ++ map (\x -> h::x) st + st ++ map (\\x -> h::x) st It returns all subsequences of a given list. > subsequences [] - [[]] : [[a]] + [[]] : List (List a) > subsequences [1] - [[],[1]] : [[number]] + [[],[1]] : List (List number) > subsequences [1,2] - [[],[2],[1],[1,2]] : [[number]] + [[],[2],[1],[1,2]] : List (List number) > subsequences [1,2,4] - [[],[4],[2],[2,4],[1],[1,4],[1,2],[1,2,4]] : [[number]] + [[],[4],[2],[2,4],[1],[1,4],[1,2],[1,2,4]] : List (List number) Notice how the empty list `[]` and a non-empty list consisting of the head `h` and tail `t` are used as patterns in the `case` expression. @@ -142,17 +150,17 @@ considering the list of moves made so far. playerWon : Player -> Moves -> Bool playerWon player = let fieldsAreInLine fields = - all (\{col} -> col == 1) fields || - all (\{col} -> col == 2) fields || - all (\{col} -> col == 3) fields || - all (\{row} -> row == 1) fields || - all (\{row} -> row == 2) fields || - all (\{row} -> row == 3) fields || - all (\{col,row} -> col == row) fields || - all (\{col,row} -> col + row == 4) fields + all (\\{col} -> col == 1) fields || + all (\\{col} -> col == 2) fields || + all (\\{col} -> col == 3) fields || + all (\\{row} -> row == 1) fields || + all (\\{row} -> row == 2) fields || + all (\\{row} -> row == 3) fields || + all (\\{col,row} -> col == row) fields || + all (\\{col,row} -> col + row == 4) fields in subsequences - >> filter (\x -> length x == 3) - >> filter (all (\(_,p) -> p == player)) + >> filter (\\x -> length x == 3) + >> filter (all (\\(_,p) -> p == player)) >> map (map fst) >> filter fieldsAreInLine >> isEmpty @@ -351,8 +359,14 @@ functions. module TicTacToeView where + import Color (black, white) + import Graphics.Collage (circle, collage, filled, group, move, rect, rotate) + import Graphics.Element (Element, container, down, flow, layers, middle, right, spacer) + import Graphics.Input (button) + import List (map) + import Signal (Channel, channel, send) + import Text (plainText) import TicTacToeModel (..) - import Graphics.Input (..) The `drawLines` function draws the two vertical and two horizontal lines of the game board. @@ -397,32 +411,37 @@ sees below the board. stateDescription state = case state of FinishedGame Draw _ -> "Game Over. Draw" - FinishedGame (Winner p) _ -> "Game Over. Winner: " ++ show p - NotFinishedGame p _ -> "Next move: " ++ show p - -The `newGameInput` function creates an `Input` element related to the -“New Game” button. The `newGameButton` uses that input’s `handle` and -the `button` function from the `Graphics.Input` module, to create a -clickable button. The signal associated with the input will output a -`()` event each time the button is clicked. + FinishedGame (Winner p) _ -> "Game Over. Winner: " ++ toString p + NotFinishedGame p _ -> "Next move: " ++ toString p + +The `newGameChannel` function creates a channel for sending messages +in response to clicking the “New Game” button. The `newGameButton` +creates a clickable button using the `button` function from the +`Graphics.Input` module. + + button : Message -> String -> Element + +The `Message` object is created using the `send` function, which takes +the channel as one of its arguments + % TicTacToeView.elm - newGameInput : Input () - newGameInput = input () + newGameChannel : Channel () + newGameChannel = channel () newGameButton : Element - newGameButton = button newGameInput.handle () "New Game" + newGameButton = button (send newGameChannel ()) "New Game" There are also two similar functions related to the “Undo” button. % TicTacToeView.elm - undoInput : Input () - undoInput = input () + undoChannel : Channel () + undoChannel = channel () undoButton : Element - undoButton = button undoInput.handle () "Undo" + undoButton = button (send undoChannel ()) "Undo" The `view` function collects the complete view, by drawing the board lines and the player moves on top of each other, and the state @@ -443,15 +462,17 @@ The [`TicTacToe`](TicTacToe.elm) module implements the remainder of the game. module TicTacToe where + import Graphics.Element (Element) + import Mouse + import Signal ((<~), Signal, foldp, mergeMany, sampleOn, subscribe) import TicTacToeModel (..) import TicTacToeView (..) - import Mouse The module creates several signals and combines them together. The following figure presents how the individual signals are combined together to produce the main game signal. -|] +""" sigBox a b c w x line = signalFunctionBox 14 18 50 a b c w x (line*100-300) sigVertU line x = sigVerticalLine 25 x (line*100-238) @@ -462,9 +483,9 @@ sigArr line x = sigDownwardArrow x (line*100-265) sigVertArr line x = group [sigVert line x, sigArr line x ] picture1 = collage 700 510 - [ sigBox "Signal ()" "newGameButtonSignal" "newGameInput.signal" 180 -240 5 + [ sigBox "Signal ()" "newGameButtonSignal" "subscribe newGameChannel" 180 -240 5 , sigBox "Signal (Int,Int)" "clickSignal" "sampleOn Mouse.clicks Mouse.position" 240 0 5 - , sigBox "Signal ()" "undoButtonSignal" "undoInput.signal" 180 240 5 + , sigBox "Signal ()" "undoButtonSignal" "subscribe undoChannel" 180 240 5 , sigVertArr 4 -240 , sigVertArr 4 0 @@ -490,14 +511,14 @@ picture1 = collage 700 510 , sigBox "Signal Element" "main" "" 140 0 1 ] -content2 = [markdown| +content2 = Markdown.toElement """ The first three functions create the basic input signals that are then transformed into other signals. The `clickSignal` function creates a signal which outputs the mouse pointer position on every click. The `newGameButtonSignal` function returns the signal associated with -`newGameInput`. The `undoButtonSignal` function returns the signal -associated with `undoInput`. +`newGameChannel`. The `undoButtonSignal` function returns the signal +associated with `undoChannel`. % TicTacToe.elm clickSignal : Signal (Int,Int) @@ -505,11 +526,11 @@ associated with `undoInput`. newGameButtonSignal : Signal () - newGameButtonSignal = newGameInput.signal + newGameButtonSignal = subscribe newGameChannel undoButtonSignal : Signal () - undoButtonSignal = undoInput.signal + undoButtonSignal = subscribe undoChannel The next three functions transform each of the above signals into a signal of state-transforming functions. Those signals have the type of @@ -559,7 +580,7 @@ into one. % TicTacToe.elm inputSignal : Signal (GameState -> GameState) - inputSignal = merges [ moveSignal, newGameSignal, undoSignal ] + inputSignal = mergeMany [ moveSignal, newGameSignal, undoSignal ] The `gameStateSignal` uses the `foldp` function to modify the game state. % TicTacToe.elm @@ -569,8 +590,7 @@ The `gameStateSignal` uses the `foldp` function to modify the game state. Recall the `foldp` signature. - > foldp - : (a -> b -> b) -> b -> Signal a -> Signal b + foldp : (a -> b -> b) -> b -> Signal a -> Signal b In our case the input signal has the `Signal (GameState -> GameState)` type, thus `a` is `GameState -> GameState`. The output signal has the @@ -583,8 +603,7 @@ argument to `foldp` needs to have the following signature: Well, that signature looks like a function application operator `(<|)` — we can thus use it in the `foldp` call. - > (<|) - : (a -> b) -> a -> b + (<|) : (a -> b) -> a -> b So, the way how the `foldp` function works in our game is that it receives state-transforming functions as input, and applies each of @@ -595,8 +614,8 @@ the `view` function from the `TicTacToeView` module. % TicTacToe.elm main : Signal Element - main = lift view gameStateSignal + main = view <~ gameStateSignal -The [next](Chapter14Snake.html) chapter presents another game. +The [next](Chapter13Snake.html) chapter presents another game. -|] +""" diff --git a/Chapter14Snake.elm b/Chapter13Snake.elm similarity index 82% rename from Chapter14Snake.elm rename to Chapter13Snake.elm index cbb257d..d8d27fb 100644 --- a/Chapter14Snake.elm +++ b/Chapter13Snake.elm @@ -2,14 +2,18 @@ import Lib (..) import Window +import Signal +import Markdown +import Graphics.Element (..) +import Graphics.Collage (..) content w = pageTemplate [content1,container w 620 middle picture1,content2] - "Chapter13TicTacToe" "toc" "Chapter15SnakeRevisited" w -main = lift content Window.width + "Chapter12TicTacToe" "toc" "Chapter14SnakeRevisited" w +main = Signal.map content Window.width -content1 = [markdown| +content1 = Markdown.toElement """ -# Chapter 14 Snake +# Chapter 13 Snake The *[Snake.elm](Snake.elm)* program is a game, in which the player uses the keyboard arrows to choose the direction that the snake goes @@ -35,31 +39,31 @@ usual module declaration and imports. module SnakeModel where + import List ((::), head, isEmpty, map, reverse, tail) import Set - import Maybe (maybe) -Then it defines the following data types: +Then it defines the following type aliases: % SnakeModel.elm - type Position = + type alias Position = { x: Int , y: Int } - type Delta = + type alias Delta = { dx: Int , dy: Int } - type Snake = - { front: [Position] - , back: [Position] + type alias Snake = + { front: List Position + , back: List Position } - type SnakeState = + type alias SnakeState = { snake: Snake , delta: Delta , food: Maybe Position @@ -67,8 +71,8 @@ Then it defines the following data types: , gameOver: Bool } -The `SnakeState` represents the state of the game. The `snake` member, -of type `Snake`, contains the snake positions (the `Position` type) on +`SnakeState` represents the state of the game. The `snake` member, of +type `Snake`, contains the snake positions (the `Position` type) on the board, stored in two lists (it will be explained below why it is convenient to store it that way). The `delta` member, of type `Delta`, stores the current direction of the snake. At any given point in time, @@ -105,7 +109,7 @@ The game state is changed in reaction to events represented by the following data type: % SnakeModel.elm - data Event = Tick Position | Direction Delta | NewGame | Ignore + type Event = Tick Position | Direction Delta | NewGame | Ignore The `Tick` event is periodically generated based on a time signal and contains a potential, new, randomly-generated position of the @@ -147,7 +151,10 @@ the move. moveSnakeForward : Snake -> Delta -> Maybe Position -> Snake moveSnakeForward snake delta food = let next = nextPosition snake delta - tailFunction = maybe tail (\f -> if next == f then identity else tail) food + tailFunction = + case food of + Nothing -> tail + Just f -> if next == f then identity else tail in if isEmpty snake.back then { front = [next] @@ -179,10 +186,10 @@ snake positions (using the `Set.fromList` function) and uses the isInSnake : Snake -> Position -> Bool isInSnake snake position = - let frontSet = Set.fromList <| map show snake.front - backSet = Set.fromList <| map show snake.back + let frontSet = Set.fromList <| map toString snake.front + backSet = Set.fromList <| map toString snake.back in - Set.member (show position) frontSet || Set.member (show position) backSet + Set.member (toString position) frontSet || Set.member (toString position) backSet The `collision` function detects the collision state, that is a state in which the next position of the snake belongs to the snake or is outside @@ -206,31 +213,13 @@ block of imports. module SnakeView where - import Text - import Maybe (maybe) + import Color (Color, black, blue, green, red, white) + import Graphics.Collage (Form, collage, filled, move, rect) + import Graphics.Element (Element, container, empty, midBottom, middle, layers) + import List (map) + import Maybe import SnakeModel (..) - import SnakeModel - - - type Position = SnakeModel.Position - -The `import SnakeModel (..)` line imports the members of the -`SnakeModel` module. The two lines following it are needed to -disambiguate the import of the `SnakeModel.Position` type. - -By default, members of several standard modules are imported by Elm -programs. One of those modules is `Graphics.Element`, which defines a -`Position` type. Without the disambiguation, that type would conflict -with the `Position` type imported from `SnakeModel`. That would result in a -compilation error. - - Error in definition drawPosition: - Ambiguous usage of type 'Position'. - Disambiguate between: Graphics.Element.Position, SnakeModel.Position - -To disambiguate, we create an alias (using the `type` keyword) for the -`SnakeModel.Position` type. Such local type declaration takes -precedence over the imported declarations. + import Text The snake and the food are drawn using filled squares. The actual size of the squares and the size of the board boundaries are @@ -265,7 +254,7 @@ The `drawPosition` function draws a single square on a given position. The `drawPositions` function draws squares representing positions from a list. % SnakeView.elm - drawPositions : Color -> [Position] -> Element + drawPositions : Color -> List Position -> Element drawPositions color positions = collage outerSize outerSize (map (drawPosition color) positions) @@ -281,7 +270,7 @@ game is over. gameOver : Element gameOver = - Text.toText "Game Over" + Text.fromString "Game Over" |> Text.color red |> Text.bold |> Text.height 60 @@ -294,7 +283,7 @@ board. instructions : Element instructions = - plainText "Press the arrows to change the snake move direction.\nPress N to start a new game." + Text.plainText "Press the arrows to change the snake move direction.\nPress N to start a new game." |> container outerSize (outerSize+3*unit) midBottom The `view` function combines the above functions into one that draws @@ -306,7 +295,7 @@ the whole game based on the state given in the argument. , instructions , drawPositions blue state.snake.front , drawPositions blue state.snake.back - , maybe empty drawFood state.food + , Maybe.withDefault empty <| Maybe.map drawFood state.food , if state.gameOver then gameOver else empty ] @@ -330,27 +319,20 @@ defined the functions showed on the figure, except for the `stateSignal` and `main` functions, which are defined in different modules. -|] +""" -sigBox a b c w x line = signalFunctionBox 14 18 50 a b c w x (line*100-300-50) -sigVertU line x = sigVerticalLine 25 x (line*100-238-50) -sigVertD line x = sigVerticalLine 25 x (line*100-238-25-50) -sigVert line x = sigVerticalLine 50 x (line*100-250-50) -sigHoriz w line x = sigHorizontalLine w x (line*100-250-50) -sigArr line x = sigDownwardArrow x (line*100-265-50) +sigBox a b c w x line = signalFunctionBox 14 18 50 a b c w x (line*100-300) +sigVertU line x = sigVerticalLine 25 x (line*100-238) +sigVertD line x = sigVerticalLine 25 x (line*100-238-25) +sigVert line x = sigVerticalLine 50 x (line*100-250) +sigHoriz w line x = sigHorizontalLine w x (line*100-250) +sigArr line x = sigDownwardArrow x (line*100-265) sigVertArr line x = group [sigVert line x, sigArr line x ] -picture1 = collage 600 610 - [ sigBox "Signal Int" "timeSignal" "fps" 100 0 6 - - , sigVertArr 5 -35 - , sigVertArr 5 35 - - , sigBox "Signal Float" "xSignal" "Random.range" 100 -70 5 - , sigBox "Signal Float" "ySignal" "Random.range" 100 70 5 +picture1 = collage 600 510 + [ sigBox "Signal Int" "timeSignal" "fps" 100 0 5 - , sigVertArr 4 -35 - , sigVertArr 4 35 + , sigVertArr 4 0 , sigBox "Signal Event" "directionSignal" "Keyboard.arrows" 170 -200 4 , sigBox "Signal Event" "tickSignal" "" 170 0 4 @@ -378,45 +360,50 @@ picture1 = collage 600 610 module SnakeSignals where - import SnakeModel (..) - import SnakeView (..) + import Char import Keyboard import Random - import Char + import Signal ((<~), Signal, keepIf, mergeMany) + import SnakeModel (..) + import SnakeView (..) + import Time (Time, fps) -} -content2 = [markdown| +content2 = Markdown.toElement """ The `timeSignal` function uses the `fps` function to produce a signal -of `Int` values ticking with the approximate rate of 50 events per +of `Time` values ticking with the approximate rate of 50 events per second. + % SnakeSignals.elm - timeSignal : Signal Float + timeSignal : Signal Time timeSignal = fps 50 -The `xSignal` and `ySignal` functions use the `Random.range` function -together with the `timeSignal` to produce a signal of random `Int` -values from the range of `-boardSize` to `boardSize`. % SnakeSignals.elm - xSignal : Signal Int - xSignal = Random.range -boardSize boardSize timeSignal +The `makeTick` function creates a `Tick` event given a `Time` +value. Each such event carries a `Position` value representing the +potential new food position. +% SnakeSignals.elm + + makeTick : Time -> Event + makeTick time = + let seed1 = Random.initialSeed (round time) + (x,seed2) = Random.generate (Random.int -boardSize boardSize) seed1 + (y,_) = Random.generate (Random.int -boardSize boardSize) seed2 + in + Tick { x = x, y = y } - ySignal : Signal Int - ySignal = Random.range -boardSize boardSize timeSignal +The `tickSignal` function maps `makeTick` over the time signal, +producing a signal of `Tick` events. -The `tickSignal` function combines the `xSignal` and `ySignal` signals -and produces a signal of `Tick` events. Each such event carries a -`Position` value representing the potential new food position. % SnakeSignals.elm tickSignal : Signal Event - tickSignal = - let combine x y = Tick { x = x, y = y } - in combine <~ xSignal ~ ySignal + tickSignal = makeTick <~ timeSignal The `directionSignal` function uses the `Keyboard.arrows` function and produces a signal of the directions the snake should move to. @@ -429,7 +416,7 @@ produces a signal of the directions the snake should move to. | x /= 0 -> Direction { dx = x, dy = 0 } | otherwise -> Direction { dx = 0, dy = y } in - lift arrowsToDelta Keyboard.arrows + arrowsToDelta <~ Keyboard.arrows The `newGameSignal` function produces a signal of `NewGame` events. The events are generated when the player presses the “N” key @@ -449,7 +436,7 @@ the input signals have the same signature. % SnakeSignals.elm eventSignal : Signal Event - eventSignal = merges [tickSignal, directionSignal, newGameSignal] + eventSignal = mergeMany [tickSignal, directionSignal, newGameSignal] The `stateSignal` function is defined in the `StateState` module. The module obviously starts with the module declaration and imports. @@ -458,6 +445,8 @@ module obviously starts with the module declaration and imports. module SnakeState where + import List (head) + import Signal (Signal, foldp) import SnakeModel (..) import SnakeSignals (..) @@ -559,6 +548,8 @@ The `Snake` module implements the `main` function. module Snake where + import Graphics.Element (Element) + import Signal ((<~), Signal) import SnakeState (..) import SnakeView (..) @@ -574,7 +565,7 @@ function, that reacts to each possible combination of input event and current state. The solution works, but it has the disadvantage that the function which transforms the state may become big and difficult to maintain for larger programs. The -[next](Chapter15SnakeRevisited.html) chapter presents an alternative +[next](Chapter14SnakeRevisited.html) chapter presents an alternative solution for implementing the same game. -|] +""" diff --git a/Chapter15SnakeRevisited.elm b/Chapter14SnakeRevisited.elm similarity index 94% rename from Chapter15SnakeRevisited.elm rename to Chapter14SnakeRevisited.elm index 6392730..d695a05 100644 --- a/Chapter15SnakeRevisited.elm +++ b/Chapter14SnakeRevisited.elm @@ -2,31 +2,39 @@ import Lib (..) import Window +import Signal +import Markdown content w = pageTemplate [content1] - "Chapter14Snake" "toc" "" w -main = lift content Window.width + "Chapter13Snake" "toc" "" w +main = Signal.map content Window.width {- % SnakeStateRevisited.elm module SnakeStateRevisited where - import SnakeModel (..) - import SnakeSignals (..) import Foldpm import Foldpm (..) + import List (head) + import Signal ((<~), Signal) + import SnakeModel (..) + import SnakeSignals (..) + -} {- % Foldpm.elm module Foldpm where + + import Signal (Signal, foldp) + -} -content1 = [markdown| +content1 = Markdown.toElement """ -# Chapter 15 Snake Revisited +# Chapter 14 Snake Revisited The previous chapter presented a program which implemented the game of snake. That program used a monolitic `step` function, that reacted to @@ -99,7 +107,7 @@ received. It returns `Nothing` otherwise. Just { state | delta <- if abs newDelta.dx /= abs state.delta.dx then newDelta else state.delta } - _ -> Nothing + _ -> Nothing The `handleTick` function handles the `Tick` events, returning the updated state wrapped in `Just` if that event is being processed, and @@ -159,11 +167,11 @@ we compose the functions using an auxiliary function `compose`: The `compose` function is defined as follows: % Foldpm.elm - compose : [a -> b -> Maybe b] -> (a -> b -> Maybe b) + compose : List (a -> b -> Maybe b) -> (a -> b -> Maybe b) compose steps = case steps of - [] -> \_ _ -> Nothing - f::fs -> \a b -> + [] -> \\_ _ -> Nothing + f::fs -> \\a b -> case f a b of Nothing -> (compose fs) a b Just x -> Just x @@ -215,18 +223,22 @@ The revised game has its own `main` function defined in the % SnakeRevisited.elm module SnakeRevisited where + + import Graphics.Element (Element) + import Signal ((<~), Signal) import SnakeStateRevisited (..) import SnakeView (..) + main : Signal Element main = view <~ stateSignal You can see that program in action [here](SnakeRevisited.html). From the user point of view it is analogous to the [*Snake.elm*](Snake.html) -program presented in [Chapter 11](Chapter11Snake.html). +program presented in [Chapter 13](Chapter13Snake.html). The `foldpm`, `when` and `compose` functions are more general and not specific to the snake program. They are defined in a separate module called [`Foldpm`](Foldpm.elm). -|] +""" diff --git a/Chapter1HelloWorld.elm b/Chapter1HelloWorld.elm index a95ceeb..e582ce8 100644 --- a/Chapter1HelloWorld.elm +++ b/Chapter1HelloWorld.elm @@ -1,11 +1,14 @@ -- -*- coding: utf-8; -*- +module Chapter1HelloWorld where import Lib (..) import Window +import Signal +import Markdown -main = lift (pageTemplate [content] "Introduction" "toc" "Chapter2FibonacciBars") Window.width +main = Signal.map (pageTemplate [content] "Introduction" "toc" "Chapter2FibonacciBars") Window.width -content = [markdown| +content = Markdown.toElement """ # Chapter 1 Hello World @@ -13,7 +16,14 @@ The first example will display the “Hello World” message. The program — *[HelloWorld1.elm](HelloWorld1.elm)* — is presented below. % HelloWorld1.elm - main = plainText "Hello World" + import Text + main = Text.plainText "Hello World" + +The first line — `import Text` — imports the `Text` module, allowing +us to reference its functions in our program. We need the import, +since our program uses the `plainText` function that is defined in +that module. We reference a function by prefixing the function name +with the module name and the dot. The meaning of an Elm program is defined by the `main` function. Every program needs to define one. To define a function, we provide its name @@ -21,57 +31,125 @@ followed by the `=` character and the function body. Functions may have parameters, which are declared between the function name and the equals sign. The `main` function does not have parameters. -The body of our function contains a call to the `plainText` function +The body of our function contains a call to the `Text.plainText` function with one argument — the “Hello World” string. The string is delimited with the quotation marks. The function argument is separated from the function name by a single space (more spaces would do as well). Elm does not require enclosing function arguments in parenthesis. -Before the program can be run, it needs to be compiled. The following -command compiles it: - - elm HelloWorld1.elm - -The compiler builds the *HelloWorld.html* file in the *build* -folder. You can now open that file using your favorite browser. Click -[here](HelloWorld1.html) to see the program output. +Before the program can be run, it needs to be compiled. If you create +a directory with the *HelloWorld1.elm* program in it, you can use the +following command to compile: + + elm-make HelloWorld1.elm --output HelloWorld1.html + +When you run that command the first time in that directory, it needs +to perform certain setup steps: download, configure and compile +standard library files. Before doing that, it asks for +permission. You should answer `y` to proceed. The command will create +a file called *elm-package.json* and a folder called *elm-stuff* in +the current directory. + + $ elm-make HelloWorld1.elm --output HelloWorld1.html + Some new packages are needed. Here is the upgrade plan. + + Install: + elm-lang/core 1.0.0 + + Do you approve of this plan? (y/n) y + Downloading elm-lang/core + Packages configured successfully! + Compiled 33 files + Successfully generated HelloWorld1.html + +When the program is compiled again in the same folder, the setup is +not performed again. + + $ elm-make HelloWorld1.elm --output HelloWorld1.html + Successfully generated HelloWorld1.html + +The compiler builds the *HelloWorld1.html* file. You can now open that +file using your favorite browser. Click [here](HelloWorld1.html) to +see the program output. + +The *elm-package.json* file contains information related to the Elm +project. Its content may look like this: + + { + "version": "1.0.0", + "summary": "helpful summary of your project, less than 80 characters", + "repository": "https://github.com/USER/PROJECT.git", + "license": "BSD3", + "source-directories": [ + "." + ], + "exposed-modules": [], + "dependencies": { + "elm-lang/core": "1.0.0 <= v < 2.0.0" + } + } + +You may change its content manually. It can also be modified by elm +tools: `elm-make`, that we have just met, and `elm-package`, that we +will meet soon. Our program shows the “Hello World” message using the default -font. What if we want to use a different font? The *[HelloWorld2.elm](HelloWorld2.elm)* -program, presented below, shows one way styling the message in a -custom way. You can see it in action [here](HelloWorld2.html). +font. What if we want to use a different font? The +*[HelloWorld2.elm](HelloWorld2.elm)* program, presented below, shows +one way of styling the message in a custom way. You can see it in +action [here](HelloWorld2.html). % HelloWorld2.elm + module HelloWorld2 where + + + import Color (blue) + import Graphics.Element (..) import Text main : Element main = - Text.toText "Hello World" + Text.fromString "Hello World" |> Text.color blue |> Text.italic |> Text.bold |> Text.height 60 |> Text.leftAligned -In order to style to our message, we use functions from the `Text` -module from Elm’s standard library. The first line: `import Text` -imports the `Text` module, allowing us to reference its functions in -our program. We reference a function by prefixing the function name with -the module name and the dot. - -The second line is the `main` function type declaration (or in other -words, its signature). Signatures are optional and we did not have it -in our first program. In fact, the first versions of Elm did not even -provide a way of declaring function signatures. It is however often a -good idea to add them to our programs, as a way of documenting its -functions. The type declaration is given in the line preceding its +The program begins with a module declaration. The declaration consists +of the `module` keyword followed by the module name and the `where` +keyword, which marks the beginning of the module body. If a program +does not start with the module declaration — like our +*HelloWorld1.elm* program — the module called `Main` is implicitly +assumed. + +In order to add style to our message, we use functions from the `Text` +module from Elm’s standard library. To make that possible, we import +the `Text` module. + +Instead of importing a module, we can alternatively import its +members, which introduces those members to the current namespace. We +do this by following the module name with a list of imported values +enclosed in parentheses. The second import statement imports one member +(`blue`) from the `Color` module. Alternatively, we can replace the +list with two dot characters, thus imporing everything that the module +exported. The third import is using that possiblity, importing +everything that the `Graphics.Element` exported, including the +`Element` type. + +The `main` function in this program has a type declaration (or in +other words, its signature). Signatures are optional and we did not +have it in our first program. In fact, the first versions of Elm did +not even provide a way of declaring function signatures. It is however +often a good idea to add them to our programs, as a way of documenting +its functions. The type declaration is given in the line preceding its definition, and it consists of the function name followed by the colon character and the function type. Our declaration states, that the `main` function does not take any parameters and returns a value of the `Element` type. Elements are “things” that Elm’s runtime know how to display. The `Element` type name starts with a capital letter. In -fact, all type names in Elm must start with a capital letters. +fact, all type names in Elm must start with a capital letter. The function definition must be consistent with its signature. Otherwise Elm will complain by printing a compilation @@ -80,17 +158,23 @@ declaration to `main : String` in our program (such a declaration implies that the result of the `main` function is a string), the compiler would complain: - $ elm HelloWorld2.elm - [1 of 1] Compiling Main ( HelloWorld2.elm ) - Type error between lines 6 and 11: - (((((Text.toText "Hello World") |> (Text.color blue)) |> - Text.italic) |> - Text.bold) |> - (Text.height 60)) |> - Text.leftAligned + $ elm-make HelloWorld2.elm --output HelloWorld2.html + + Error in HelloWorld2.elm: + + Type mismatch between the following types between lines 8 and 13: + + Graphics.Element.Element + + String - Expected Type: Graphics.Element.Element - Actual Type: String + It is related to the following expression: + + (((((Text.fromString "Hello World") |> (Text.color blue)) + |> Text.italic) + |> Text.bold) + |> (Text.height 60)) + |> Text.leftAligned The `main` function body contains a set of expressions separated with the `|>` operators. What are they? They are in fact a way of function @@ -100,26 +184,27 @@ the function name and add the `|>` operator, like so: a |> f -For greater readability, we have also added new line characters before -the `|>` operators. +For greater readability, the program includes new line characters +before the `|>` operators. The body of our `main` function consists of several function applications chained together. In fact, it could alternatively have been written in the following way: - main = Text.leftAligned (Text.height 60 (Text.bold (Text.italic (Text.color blue (Text.toText "Hello World"))))) + main = Text.leftAligned (Text.height 60 (Text.bold (Text.italic (Text.color blue (Text.fromString "Hello World"))))) Let’s now analyze what the function does. It starts with a call to the -`toText` function, which transforms a string into a value of type +`fromString` function, which transforms a string into a value of type `Text`. It’s type looks like this: - toText : String -> Text + fromString : String -> Text The `->` arrow separates the function argument type from the result -type. The `Text` value created by the `toText` function has certain -default properties, like the color or font size. The function calls -following the `toText` call modify those properties, creating new -`Text` values which differ in some respect from the old value. +type. The `Text` value created by the `fromString` function has +certain default properties, like color or font size. The function +calls following the `fromString` call modify those properties, +creating new `Text` values, which differ in some respect from the old +value. It is important to keep in mind, that Elm functions do not modify their arguments in place. So, whenever I write about “modifying” the @@ -144,7 +229,7 @@ function: height : Float -> Text -> Text -Finally, the `leftAligned` function turns a `Text` value to an +Finally, the `leftAligned` function turns a `Text` value into an `Element` that can be displayed: leftAligned : Text -> Element @@ -152,32 +237,37 @@ Finally, the `leftAligned` function turns a `Text` value to an Let us get back to the signatures of the `color` and `height` functions. Why is it, that the same operator — the `->` arrow — is used for what might seem to be two different things: to separate one -function argument from the other one and in the same time to separate -the arguments from the function result type? In fact, stricly -speaking, all Elm functions can only take at most one argument. The -`color` function, which can in many circumstances be treated just as a +function argument from the other and in the same time to separate the +arguments from the function result type? In fact, stricly speaking, +all Elm functions can only take at most one argument. The `color` +function, which can in many circumstances be treated just as a function taking two arguments, is more formally speaking a one-argument function taking an argument of the `Color` type, that returns another one-argument function, taking an argument of the -`Text` type, which in turn returns the result of type `Text`. It -might seem to be a small technical distinction only and in many -situtions it can be ignored. However, it also allows us to use a -useful programming technique of partially applying a -function. Consider the *[HelloWorld3.elm](HelloWorld3.elm)* program, which displays the -same text that *[HelloWorld2.elm](HelloWorld2.elm)* does, but is written in a slightly -different way. +`Text` type, which in turn returns the result of type `Text`. It might +seem to be a small technical distinction only and in many situtions it +can be ignored. However, it also allows us to use a useful programming +technique of partially applying a function. Consider the +*[HelloWorld3.elm](HelloWorld3.elm)* program, which displays the same +text that *[HelloWorld2.elm](HelloWorld2.elm)* does, but is written in +a slightly different way. % HelloWorld3.elm + module HelloWorld3 where + + + import Color (blue) + import Graphics.Element (..) import Text as T - makeBlue : Text -> Text + makeBlue : T.Text -> T.Text makeBlue = T.color blue main : Element main = - T.toText "Hello World" + T.fromString "Hello World" |> makeBlue |> T.italic |> T.bold @@ -187,8 +277,8 @@ different way. The first difference is the use of a *qualified* import. By suffixing the import statement for the `Text` module with the `as T` clause, we make the `Text` module available with the qualified name `T` instead -of the full name `Text`. The references to the functions defined in -that module must now be prefixed with `T.`. +of the full name `Text`. The references to symbols defined in that +module must now be prefixed with `T.`. The `main` function differs from its equivalent in the previous program by using the auxiliary function `makeBlue` instead of directly @@ -210,36 +300,94 @@ The `Text` module is part of the Elm standard library. We have so far only used a small subset of its functions, but it contains more of them. If you want to verify what are the functions that a module provide, try to find the module on the -[library.elm-lang.org](http://library.elm-lang.org/) web site. +[package.elm-lang.org/](http://package.elm-lang.org/) web site. + +The final example presented in this chapter — +*[HelloWorld4.elm](HelloWorld4.elm)* — shows a web page, the content +of which is specified using the +[Markdown](http://daringfireball.net/projects/markdown/) format. See +the program output [here](HelloWorld4.html). + +% HelloWorld4.elm + module HelloWorld4 where -Let us now turn to the last example in this chapter. We will display -text specified using the -[Markdown](http://daringfireball.net/projects/markdown/) format. The -*[HelloWorld4.elm](HelloWorld4.elm)* program, presented below, shows a -small web page. You can see the program output -[here](HelloWorld4.html). - main = [markdown| + import Markdown + + + main = Markdown.toElement \"\"\" # Hello World This is the output of the *HelloWorld4.elm* program. --- - -
  |]
- -The body of the `main` function defines a markdown block delimited by -the `[markdown|` and |] tags. The line starting with -a single hash `#` character is displayed as a header (using the `h1` -HTML tag) line. The words enclosed in asterisks are displayed in -italic. Finally, the three consecutive dash characters are showed as -a horizontal line. Our program only uses a few selected Markdown -features. You can read about others on the [Markdown -Syntax](http://daringfireball.net/projects/markdown/syntax) web page. + \"\"\" + +The body of the `main` function contains a call to the +Markdown.toElement function with one argument — a string containing +markdown syntax. The string is delimited with triple quotation mark +characters. + +The line starting with a single hash `#` character is displayed as a +header (using the `h1` HTML tag) line. The words enclosed in +asterisks are displayed in italic. Finally, the three consecutive +dash characters are showed as a horizontal line. Our program only uses +a few selected Markdown features. You can read about others on the +[Markdown Syntax](http://daringfireball.net/projects/markdown/syntax) +web page. + +The `Markdown` module does not belong to the Elm standard library. It +belongs to the *evancz/elm-markdown* package. We can install the +package with the following command: + + + $ elm-package install evancz/elm-markdown + To install evancz/elm-markdown I would like to add the following + dependency to elm-package.json: + + "evancz/elm-markdown": "1.1.1 <= v < 2.0.0" + + May I add that to elm-package.json for you? (y/n) y + + Some new packages are needed. Here is the upgrade plan. + + Install: + evancz/elm-html 1.0.0 + evancz/elm-markdown 1.1.1 + evancz/virtual-dom 1.0.0 + + Do you approve of this plan? (y/n) y + Downloading evancz/elm-html + Downloading evancz/elm-markdown + Downloading evancz/virtual-dom + Packages configured successfully! + +The command asked two questions and installed the package. It also +modified the *elm-package.json* file from the current folder: + + { + "version": "1.0.0", + "summary": "helpful summary of your project, less than 80 characters", + "repository": "https://github.com/USER/PROJECT.git", + "license": "BSD3", + "source-directories": [ + "." + ], + "exposed-modules": [], + "dependencies": { + "elm-lang/core": "1.0.0 <= v < 2.0.0", + "evancz/elm-markdown": "1.1.1 <= v < 2.0.0" + } + } + +Notice the additional line in the *dependencies* section. We can now +compile our program using `elm-make`: + + elm-make HelloWorld4.elm --output HelloWorld4.html The [next](Chapter2FibonacciBars.html) chapter will show you how to make arithmetic calculations and draw simple pictures. It will also present another tool provided by Elm — the REPL. -|] +""" diff --git a/Chapter2FibonacciBars.elm b/Chapter2FibonacciBars.elm index 7d14e5f..9770d1e 100644 --- a/Chapter2FibonacciBars.elm +++ b/Chapter2FibonacciBars.elm @@ -2,10 +2,12 @@ import Lib (..) import Window +import Signal +import Markdown -main = lift (pageTemplate [content] "Chapter1HelloWorld" "toc" "Chapter3MouseSignals") Window.width +main = Signal.map (pageTemplate [content] "Chapter1HelloWorld" "toc" "Chapter3MouseSignals") Window.width -content = [markdown| +content = Markdown.toElement """ # Chapter 2 Fibonacci Bars @@ -19,7 +21,7 @@ Elm — like many other functional languages — provides. The `elm-repl` command starts it, like so: $ elm-repl - Elm REPL 0.2.2 + Elm REPL 0.4 Type :help for help, :exit to exit > @@ -58,12 +60,18 @@ the function takes a floating point number and returns an integer. Let’s now add something to `x`. > x + 1.0 - [1 of 1] Compiling Repl ( repl-temp-000.elm ) - Type error on line 3, column 3 to 10: - x + 1.0 + + Error in repl-temp-000.elm: + + Type mismatch between the following types on line 3, column 3 to 10: + + Int + + Float - Expected Type: Int - Actual Type: Float + It is related to the following expression: + + x + 1.0 What is that? We got a compilation error. We tried to add `x` of type `Int` to `1.0`, which is a floating-point number of type `Float`. Elm @@ -137,6 +145,10 @@ Here are examples of using those operators: > 10 % 3 1 : Int +To exit the REPL, use the `:exit` command. + + > :exit + Let’s now go back to our program displaying colorful bars. The program consists of two files. The *[FibonacciBars.elm](FibonacciBars.elm)* file defines the `main` function. But let’s first a look at the @@ -147,7 +159,10 @@ module. Here is its content: module Fibonacci where - fibonacci : Int -> [Int] + import List ((::), head, map2, reverse, tail) + + + fibonacci : Int -> List Int fibonacci n = let fibonacci' n acc = if n <= 2 @@ -157,30 +172,28 @@ module. Here is its content: fibonacci' n [1,1] |> reverse - fibonacciWithIndexes : Int -> [(Int,Int)] - fibonacciWithIndexes n = zip [0..n] (fibonacci n) + fibonacciWithIndexes : Int -> List (Int,Int) + fibonacciWithIndexes n = map2 (,) [0..n] (fibonacci n) -It begins with a module declaration. The declaration consists of the -`module` keyword followed by the module name and the `where` keyword, -which marks the beginning of the module body. +It begins with a module declaration and an import statement. Since +`::` is an operator, it needs to be enclosed in parenthesis in the +import statement. Next comes the `fibonacci` function for calculating the first `n` Fibonacci numbers. Its type declaration specifies, that it takes one -argument and returns a list of integers. +argument of type `Int` and returns a list of integers `List Int`. -In Elm, the list type is specified by enclosing the type of the list -elements in brackets, so for example a list of integers is denoted as -`[Int]`. List elements are also enclosed in square brackets, and are -separated by commas: +A list literal is a sequence of the list elements, enclosed in square +brackets, and separated by commas: > ["a","b","c","d"] - ["a","b","c","d"] : [String] + ["a","b","c","d"] : List String The `fibonacci` function uses the auxiliary `fibonacci'` function (yes, the aphostrophe character `'` can be used in identifiers; by convention identifiers ending with `'` are somehow related to similar identifiers without the `'` character). The auxiliary function is -defined withing the `let` expression, which has the following +defined within the `let` expression, which has the following structure: let in @@ -211,18 +224,18 @@ the expression after the `then` keyword. Otherwise, it is the result of evaluating the expression after the `else` keyword. In our case, when `n` is less then or equal to `2`, the `fibonacci'` function returns the value of the accumulator `acc`. Otherwise, it calls itself -with the new values of `n` and `acc`, which are calculated using -another, nested `let` expression. +with the new values of `n` and `acc` calculated by the expression: + + (head acc + (tail >> head) acc) :: acc -The nexted `let` expression defines the `cadr` function, which returns -the second element of the list. The `head` function returns the first -element of a list. The `tail` function returns a list without its -first element. +The `head` function returns the first element of a list. The `tail` +function returns a list without its first element. + > import List (..) > head [1,2,3,4] 1 : number > tail [1,2,3,4] - [2,3,4] : [number] + [2,3,4] : List number The composition of `tail` and `head` returns the second element from the list. Two functions can be composed in Elm using the `>>` or `<<` @@ -238,51 +251,35 @@ of arguments. 2 : number The next fibonacci number is calculated as the sum of the first and -the second element of the current list of resuts (the accumulator +the second element of the current list of results (the accumulator `acc`) and is stored as the value `next`. -The new `n` value for the recursive call to `fibonacci'` is equal to -the old `n` decremeted by 1. The new accumulator is calculated by -prepending its current value with the next value. The `::` operator -yields a new list with its left operand (an element) prepended to its -right operand (a list). +The new accumulator is calculated by prepending its current value with +the next value. The `::` operator yields a new list with its left +operand (an element) prepended to its right operand (a list). > 7 :: [1,2,3,4] - [7,1,2,3,4] : [number] + [7,1,2,3,4] : List number +The new `n` value for the recursive call to `fibonacci'` is equal to +the old `n` decremeted by 1. The result of calling the `fibonacci'` function is reversed using the `reverse` function, since `fibonacci'` returns the list of numbers in reversed order. > reverse [1,2,3,4] - [4,3,2,1] : [number] - -We can test our `fibonacci` function in the REPL. But before calling -the function, we import all functions from the `Fibonacci` module. + [4,3,2,1] : List number - > import Fibonacci - -The import statement informs the REPL, that we want to import the -`Fibonacci` module. We can now reference the `fibonacci` function from -that module by prefixing it with the module name: - - > Fibonacci.fibonacci 10 - [1,1,2,3,5,8,13,21,34,55] : [Int] - -Beside importing the module, we can also “open” it, which introduces -certain module functions to the current namespace. We do this by -following the module name with a list of imported values enclosed in -parentheses. Alternatively, we can replace the list with two dot -characters, thus imporing everything that the module exported. +We can test our `fibonacci` function in the REPL. First, we import +functions from the `Fibonacci` module. > import Fibonacci (..) -We can now directly reference the function without prefixing it with -the module name. +We can now call the `fibonacci` function. > fibonacci 10 - [1,1,2,3,5,8,13,21,34,55] : [Int] + [1,1,2,3,5,8,13,21,34,55] : List Int The bars displayed by our program have the width proportional to the first ten Fibonacci numbers. But their colors are calculated @@ -290,9 +287,7 @@ differently. They are selected from a list of seven colors based on the index (position) of each number. We will need a function that returns a list of pairs containing the Fibonacci numbers paired with their positions. The `fibonacciWithIndexes` function calculates such -pairs by zipping (using the `zip` function) a list of consecutive -numbers with the list of ten first Fibonacci numbers calculated by -the `fibonacci 10` call. +pairs. > fibonacciWithIndexes 10 [(0,1),(1,1),(2,2),(3,3),(4,5),(5,8),(6,13),(7,21),(8,34),(9,55)] : [(Int, Int)] @@ -311,28 +306,56 @@ elements. > (1,"a",4.0,6) (1,"a",4,6) : (number, String, Float, number) -The list of consecutive numbers from 1 to 20 is generated by the `[1..20]` expression. +There is another way we can use to create tuples: - > [1..20] - [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20] : [number] + > (,) 1 "a" + (1,"a") : ( number, String ) + > (,,) 1 "a" 4.0 + (1,"a",4) : ( number, String, Float ) + > (,,,) 1 "a" 4.0 6 + (1,"a",4,6) : ( number, String, Float, number ) -The `zip` function takes two lists, and returns a list of pairs -consisting of consecutive elements from both lists. +`(,)`, `(,,)` and `(,,,)` are actually functions returning tuples. - > zip ["a","b","c","d"] [1..20] - [("a",1),("b",2),("c",3),("d",4)] : [(String, number)] + > (,) + : a -> b -> ( a, b ) + > (,,) + : a -> b -> c -> ( a, b, c ) + > (,,,) + : a -> b -> c -> d -> ( a, b, c, d ) -Notice, that the first list is shorter than the second one. That is -fine, since `zip` will only produce a list as long as the shorter of its -arguments. +Those functions are *generic*, or *polimorphic* — the letters `a`, +`b`, `c` and `d` denote type parameters. + +The list of consecutive numbers from 1 to 20 is generated by the +`[1..20]` expression. + + > [1..20] + [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20] : List number -Let’s now look at the contents of the *[FibonacciBars.elm](FibonacciBars.elm)* file. +The `map2` function takes a two-argument function and two lists, and +returns a list of values calculated by applying the function to +consecutive elements of both lists, skipping the excessive elements of +the longer list. + + > map2 (+) [9,8,7,6,5] [4,4,7,7] + [13,12,14,13] : List number + > map2 (,) [9,8,7,6,5] [4,4,7,7] + [(9,4),(8,4),(7,7),(6,7)] : List ( number, number ) + +Let’s now look at the contents of the +*[FibonacciBars.elm](FibonacciBars.elm)* file. % FibonacciBars.elm module FibonacciBars where + import Color (blue, brown, green, orange, purple, red, yellow) import Fibonacci (fibonacci, fibonacciWithIndexes) + import Graphics.Collage (collage, filled, rect) + import Graphics.Element (down, flow, right) + import List (drop, head, length, map) + import Text (asText) color n = @@ -351,27 +374,23 @@ Let’s now look at the contents of the *[FibonacciBars.elm](FibonacciBars.elm)* main = flow down <| map bar (fibonacciWithIndexes 10) Again, we start with the module declaration. We then proceed by -importing both functions from the `Fibonacci` module. We explicitly -enumerate, in parenthesis, the functions to be imported. Next, we have -three function declarations. Let’s analyze them one by one. +importing both functions from the `Fibonacci` module and several +members from the standard library modules. Next, we have three +function declarations. Let’s analyze them one by one. The `color` function returns one of seven colors based on the value of its parameter. The seven colors are defined in the `let` expression, -in a list. The color names used are all available by default in Elm -programs. The function calculates its result by dropping (using the +in a list. The color names used are all available in the `Color` +module. The function calculates its result by dropping (using the `drop` function) a number of elements from the list of colors, and -then taking the first element of the remaining list. The drop function +then taking the first element of the remaining list. The `drop` function takes two arguments: the number of elements to be dropped and a list. > drop 2 [1,2,3,4,5] - [3,4,5] : [number] + [3,4,5] : List number The number of elements to be dropped is calculated by taking a modulo -of `n` and the length of the list of colors. The `%` operator -calculates the modulo of two numbers: - - > 10 % 3 - 1 : Int +of `n` and the length of the list of colors. The `bar` function produces a colored rectangle together with a number corresponding to its length. Since it is our first function which @@ -386,12 +405,9 @@ the width and height of the rectangle, and returns a value of type rect : Float -> Float -> Shape There are other functions creating shapes in the `Graphics.Collage` -module: `oval`, `circle`, `square`, `ngon`, `polygon`. The functions -of that module are imported by default in Elm programs, thus are -accessible without additional imports. - -The shape created by the `rect` function is used as an argument to the -`filled` function in the following expression: +module: `oval`, `circle`, `square`, `ngon`, `polygon`. The shape +created by the `rect` function is used as an argument to the `filled` +function in the following expression: filled (color index) (rect (toFloat n * 20) 20) @@ -402,15 +418,12 @@ of type `Form` representing the filled (colored) rectangle: Other functions creating forms from shapes are available as well: `textured`, `gradient`, `outlined`. The `Color` argument is calculated -by the `color` function, described above. You may remember, that we -used a function called `color` in the previous chapter. By defining -our own `color` function, we have “hidden” the `color` function -provided by default by Elm. +by the `color` function, described above. The next step is transforming a `Form` into an `Element`. This is the job of the `collage` function: - collage : Int -> Int -> [Form] -> Element + collage : Int -> Int -> List Form -> Element The `collage` function takes two integer values and a list of forms and turns them into an `Element`. The two integer values represent the @@ -423,18 +436,19 @@ rectangular form that it contains: The `flow` function, defined in the `Graphics.Element` module, creates an `Element` from a list of elements: - flow : Direction -> [Element] -> Element + flow : Direction -> List Element -> Element The `Direction` parameter represents the direction of how the elements from the list are placed in relation to each other. The following functions return various `Direction` values: `left`, `right`, `up`, `down`, `inward`, `outward`. -In the `bar` function, `flow` is used to create an element -consisting of the rectangular bar and a numeric value to its right -(thus the `right` function used for the `Direction` argument). The -numeric value is turned into an `Element` using the `asText` -function. That function can be used to turn any value into an element: +In the `bar` function, `flow` is used to create an element consisting +of the rectangular bar and a numeric value to its right (thus the +`right` function used for the `Direction` argument). The numeric value +is turned into an `Element` using the `asText` function. That function +can be used to turn any value into a value of type `Element` (not into +a value of type `Text`!): asText : a -> Element @@ -465,7 +479,7 @@ The expression used as the second argument of the `flow` function uses the `map` function, which has the following signature: > map - : (a -> b) -> [a] -> [b] + : (a -> b) -> List a -> List b Notice, that we have used the REPL to verify the signature. We just entered the name of the function, and REPL responded with its @@ -479,17 +493,17 @@ applying the function to each element of the input list. Here are three examples of using the `map` function with various arguments. > map round [1.2,4.5,7.8,98] - [1,5,8,98] : [Int] + [1,5,8,98] : List Int > map fibonacci [3,4] - [[1,1,2],[1,1,2,3]] : [[Int]] - > map (\x -> x+1) [1,2,3,4] - [2,3,4,5] : [number] + [[1,1,2],[1,1,2,3]] : List (List Int) + > map (\\x -> x+1) [1,2,3,4] + [2,3,4,5] : List number In the second example we used the `fibonacci` function, obtaining a list of lists. The third example introduces a new concept — an anonymous function. It -is a function without a name. It starts with the `\` character, which +is a function without a name. It starts with the `\\` character, which is followed by the name (or names) of function arguments. The arguments are followed by the `->` arrow and the function body. In the example, the anonymous function has one parameter `x`, and returns the @@ -500,4 +514,4 @@ calculations as well as show some graphics. However, our program is static. In the [next](Chapter3MouseSignals.html) chapter we will show how Elm lets write dynamic programs by means of signals. -|] +""" diff --git a/Chapter3MouseSignals.elm b/Chapter3MouseSignals.elm index 3743678..9edb008 100644 --- a/Chapter3MouseSignals.elm +++ b/Chapter3MouseSignals.elm @@ -2,10 +2,12 @@ import Lib (..) import Window +import Signal +import Markdown -main = lift (pageTemplate [content] "Chapter2FibonacciBars" "toc" "Chapter4WindowSignals") Window.width +main = Signal.map (pageTemplate [content] "Chapter2FibonacciBars" "toc" "Chapter4WindowSignals") Window.width -content = [markdown| +content = Markdown.toElement """ # Chapter 3 Mouse Signals @@ -18,10 +20,15 @@ let’s see them in action. Take a look at the working example is available [here](MouseSignals1.html). % MouseSignals1.elm + module MouseSignals1 where + + import Mouse + import Signal (map) + import Text (asText) - main = lift asText Mouse.x + main = map asText Mouse.x Try running that program and notice what happens as you move your mouse pointer. The program shows the `x` coordinate of the mouse @@ -42,15 +49,15 @@ a signal of `Int` values. We cannot show it directly on the screen, that is we cannot write `main = Mouse.x`, because such program would not compile. All our programs so far assigned to `main` values of type `Element`, but it is not the only possible type of the `main` -function. The other possible type is `Signal Element`. In other words, -Elm can display a static element, or a dynamic signal of elements. +function. Another possible type is `Signal Element`. In other words +Elm can display a dynamic signal of elements. We can use the `asText` function to turn an `Int` into an `Element`. But what we need is to turn a `Signal Int` into a `Signal -Element`. How can we do that? By using the `lift` function! Here is -its signature: +Element`. How can we do that? By using the `map` function from the +`Signal` module! Here is its signature: - lift : (a -> b) -> Signal a -> Signal b + map : (a -> b) -> Signal a -> Signal b It takes a function and a signal, and applies the function to the values “carried” by the signal. In other words, it applies the @@ -61,10 +68,11 @@ Elm allows using functions as operators, that is in the infix notation, by enclosing them in backsticks. We could thus define the `main` function in an alternative way as follows: - main = asText `lift` Mouse.x + main = asText `map` Mouse.x However, Elm provides already the `<~` operator that is equivalent to -the `lift` function. Thus, we can also write: +the `map` function. Thus, we can also write (provided we also import +the `<~` operator from the `Signal` module): main = asText <~ Mouse.x @@ -73,32 +81,38 @@ two signals combined together and displayed as a pair of mouse pointer coordinates. You can see it in action [here](MouseSignals2.html). % MouseSignals2.elm + module MouseSignals2 where + + + import Graphics.Element (Element) import Mouse + import Signal (map2) + import Text (asText) combine : a -> b -> Element combine a b = asText (a,b) - main = lift2 combine Mouse.x Mouse.y + main = map2 combine Mouse.x Mouse.y -The `lift` function is not enough when we want to combine two signals -into one. Luckily Elm provides the `lift2` function that let us do +The `map` function is not enough when we want to combine two signals +into one. Luckily Elm provides the `map2` function that let us do that. It has the following signature: - lift2 : (a -> b -> c) -> Signal a -> Signal b -> Signal c + map2 : (a -> b -> c) -> Signal a -> Signal b -> Signal c Our program merges the `Mouse.x` and `Mouse.y` (which obviously -represents the mouse pointer *y* coordinates) signals using the -`lift2` function. Elm also provides other similar functions: `lift3`, -`lift4`, …, up to `lift8`. However, it also provides an alternative -way of combining several signals into one. The last line of our -program could have been written as follows: +represents the mouse pointer *y* coordinates) signals using the `map2` +function. Elm also provides other similar functions: `map3`, `map4` +and `map5`. However, it also provides an alternative way of combining +several signals into one. The last line of our program could have been +written as follows (again, importing `~` would be necessary): main = combine <~ Mouse.x ~ Mouse.y What is going on here? We already know the `<~` operator, which is -equivalent to the `lift` function. The `~` operator has the following +equivalent to the `map` function. The `~` operator has the following signature: (~) : Signal (a -> b) -> Signal a -> Signal b @@ -121,24 +135,30 @@ look at yet another program — *[MouseSignals3.elm](MouseSignals3.elm)* (the working program is available [here](MouseSignals3.html)): % MouseSignals3.elm + module MouseSignals3 where + + + import Graphics.Element (down, flow) + import List (map) import Mouse + import Signal ((~), (<~), sampleOn) + import Text (plainText) - showsignals a b c d e f g h = + showsignals a b c d e f g = flow down <| map plainText [ - "Mouse.position: " ++ show a, - "Mouse.x: " ++ show b, - "Mouse.y: " ++ show c, - "Mouse.clicks: " ++ show d, - "Mouse.isDown: " ++ show e, - "count Mouse.isDown: " ++ show f, - "sampleOn Mouse.clicks Mouse.position: " ++ show g, - "sampleOn Mouse.isDown Mouse.position: " ++ show h + "Mouse.position: " ++ toString a, + "Mouse.x: " ++ toString b, + "Mouse.y: " ++ toString c, + "Mouse.clicks: " ++ toString d, + "Mouse.isDown: " ++ toString e, + "sampleOn Mouse.clicks Mouse.position: " ++ toString f, + "sampleOn Mouse.isDown Mouse.position: " ++ toString g ] @@ -149,19 +169,18 @@ look at yet another program — *[MouseSignals3.elm](MouseSignals3.elm)* ~ Mouse.y ~ Mouse.clicks ~ Mouse.isDown - ~ count Mouse.isDown ~ sampleOn Mouse.clicks Mouse.position ~ sampleOn Mouse.isDown Mouse.position -The `showsignals` function presents a list of several (eight) values -with descriptions. Each item on that list represents a signal. Signal +The `showsignals` function presents a list of several values with +descriptions. Each item on that list represents a signal. Signal values are “feeded” into the function using the `<~` and `~` operators. -The `show` function used as the argument of the `map` function, +The `toString` function used as the argument of the `map` function, converts any value to a `String`. - show : a -> String + toString : a -> String The first signal, `Mouse.position`, represents the mouse pointer coordinates as a pair of values. We have already seen the `Mouse.x` @@ -180,10 +199,6 @@ the mouse button is released). The `Mouse.isDown` signal is a signal of boolean values indicating whether the mouse button is being pressed. -The next signal uses the `count` function which converts a signal into -a signal of `Int` values indicating how many events from the original -signal have occurred. - The last two signals use the `sampleOn` function, which samples the second signal whenever the first signal changes its value. @@ -194,11 +209,7 @@ moments of when the mouse button was released, while the last signal represents mouse positions from the moments of both when the mouse button was pressed and relesed. -The `count` and `sampleOn` functions are defined in the `Signal` -module. The module provides various functions for signal -manipulations. - The [next](Chapter4WindowSignals.html) chapter presents signals defined in the `Window` module. -|] +""" diff --git a/Chapter4WindowSignals.elm b/Chapter4WindowSignals.elm index 04a5468..e13294a 100644 --- a/Chapter4WindowSignals.elm +++ b/Chapter4WindowSignals.elm @@ -2,18 +2,20 @@ import Lib (..) import Window +import Signal +import Markdown -main = lift (pageTemplate [content] "Chapter3MouseSignals" "toc" "Chapter5Eyes") Window.width +main = Signal.map (pageTemplate [content] "Chapter3MouseSignals" "toc" "Chapter5Eyes") Window.width -content = [markdown| +content = Markdown.toElement """ # Chapter 4 Window Signals The `Window` module from the standard library defines signals which provide information about the dimensions of the container in which the Elm program lives. The container may be the whole browser window, but -does not have to be — it may be a `div` element where an Elm program -is embeded on an HTML page. +does not have to be — it may for example be a `div` element where an +Elm program is embeded on an HTML page. Our first example is a standalone program, and thus the signals represent the dimensions of the browser window. The @@ -24,6 +26,10 @@ and its working instance is available [here](WindowSignals1.html). module WindowSignals1 where + import Graphics.Element (down, flow) + import List (map) + import Signal ((~), (<~)) + import Text (plainText) import Window @@ -34,9 +40,9 @@ and its working instance is available [here](WindowSignals1.html). map plainText [ - "Window.dimensions: " ++ show a, - "Window.width: " ++ show b, - "Window.height: " ++ show c + "Window.dimensions: " ++ toString a, + "Window.width: " ++ toString b, + "Window.height: " ++ toString c ] @@ -53,42 +59,39 @@ Three signals are presented by the program: * `Window.height` represents its height. We will now embed our program in an HTML page. The -*WindowSignals2.html* page shows how an Elm program can be -embedded. Here is the page source: +*WindowSignals2.html.txt* page shows how an Elm program can be +embedded. % WindowSignals2.html - - - - - - -
- - - - -The page includes the Elm runtime file *elm-runtime.js*. In fact, -every compiled Elm program also includes that file. You can for -example verify, that *elm-runtime.js* is included by the -*WindowSignal1.html* page produced by the Elm compiler as the result -of compiling the *WindowSignals1.elm* program. If you cannot find the -*elm-runtime.js* in your Elm installation, compile any Elm program and -verify, in the HTML page produced by the compiler, where the runtime -file is included from. - -The next line includes the *WindowSignals1.js* file. The + ‹/script› + ‹body› + ‹/html› + +The page includes the *WindowSignals1.js* file. The following command can be used to produce that file: - elm --only-js WindowSignals1.elm + $ elm-make WindowSignals1.elm --output WindowSignals1.js + Successfully generated WindowSignals1.js + +So far, we have been providing file names with the *.html* extention +as the argument to the `--output` option. Here, we provide a file with +the *.js* extention, and the compiler is producing a JavaScript file. +In fact, producing JavaScript is the default. If w omit the `--output` +option, the compiler outputs a file called *elm.js*. -The `--only-js` flag instructs the Elm compiler to only generate the -JavaScript file. No html file is generated. + $ elm-make WindowSignals1.elm + Successfully generated elm.js -The *[WindowSignals2.html](WindowSignals2.txt)* page contains a `div` +The *[WindowSignals2.html](WindowSignals2.html.txt)* page contains a `div` element where the Elm program will be embedded. In order to easily distinguish visually the `div` element on the page, a simple border is added to it. @@ -101,17 +104,38 @@ placed after the `div` element in the page source. The `script` element contains two statements. The first one finds the `
` element and stores it in the `div` variable. The second statement actually embeds the Elm program. The `Elm.embed` function is -called with two arguments. The first one is `Elm.WindowSignals1`. The -`WindowSignals1` is the name of the module defined in the -*WindowSignals1.elm* source file. The second argument is our `div` +called with two arguments. The first one is `Elm.WindowSignals1`, +where the `WindowSignals1` part is the name of the module defined in +the *WindowSignals1.elm* source file. The second argument is our `div` element. You can open the HTML page [here](WindowSignals2.html) and notice that the dimensions reported by the program correspond to the dimensions of the `div` element, not the dimensions of the whole window. +We can also embed the compiled Elm program as a fullscreen +application. The *[WindowSignals3.html](WindowSignals3.html)* page +shows how how to do it. + +% WindowSignals3.html + ‹html› + ‹head› + ‹script src="WindowSignals1.js"›‹/script› + ‹/head› + ‹body› + ‹script› + Elm.fullscreen(Elm.WindowSignals1) + ‹/script› + ‹body› + ‹/html› + +The *[WindowSignals3.html](WindowSignals3.html.txt)* page code is similar +to the *[WindowSignals2.html](WindowSignals2.html.txt)* code, but there is +no `div` element, and the one-argument `Elm.fullscreen` function is +used instead of `Elm.embed`. + In this and the previous chapters we have learned about mouse and window related signals. The [next](Chapter5Eyes.html) chapter presents an example that uses those signals. -|] +""" diff --git a/Chapter5Eyes.elm b/Chapter5Eyes.elm index f71c858..5302e8b 100644 --- a/Chapter5Eyes.elm +++ b/Chapter5Eyes.elm @@ -3,12 +3,19 @@ import Lib (..) import Window import Text +import Text (..) +import Signal +import Markdown +import Graphics.Element (..) +import Graphics.Collage (..) +import Signal ((<~)) +import Color (white, black) content w = pageTemplate [content1,container w 490 middle picture1,content2,container w 310 middle picture2,content3] "Chapter4WindowSignals" "toc" "Chapter6TimeSignals" w -main = lift content Window.width +main = Signal.map content Window.width -content1 = [markdown| +content1 = Markdown.toElement """ # Chapter 5 Eyes @@ -36,6 +43,11 @@ We start our analysis with the `EyesView` module defined in the module EyesView where + import Color (black, white) + import Graphics.Collage (Form, collage, filled, group, move, moveX, oval) + import Graphics.Element (Element) + + eyeBorder : Float -> Float -> Form eyeBorder w h = group [ @@ -112,7 +124,7 @@ and let’s solve the problem of calculating the pupil coordinate under the assumption, that the mouse pointer is located within that quoter. Consider the following picture. -|] +""" picture1 = collage 680 480 [ outlined defaultLine (rect 600 400) @@ -120,29 +132,29 @@ picture1 = collage 680 480 , filled white (rect 680 80) |> moveY -241 , filled white (rect 80 480) |> moveX -341 , outlined (dotted black) (rect 500 300) |> move (-50,-50) - , toForm (toText "R’" |> Text.height 30 |> leftAligned) |> move (200,-220) - , toForm (toText "R’’" |> Text.height 30 |> leftAligned) |> move (-320,100) - , toForm (toText "R" |> Text.height 30 |> leftAligned) |> move (210,100) + , toForm (fromString "R’" |> Text.height 30 |> leftAligned) |> move (200,-220) + , toForm (fromString "R’’" |> Text.height 30 |> leftAligned) |> move (-320,100) + , toForm (fromString "R" |> Text.height 30 |> leftAligned) |> move (210,100) , filled black (circle 4) |> move (-300,-200) - , toForm (toText "O" |> Text.height 30 |> leftAligned) |> move (-320,-200) + , toForm (fromString "O" |> Text.height 30 |> leftAligned) |> move (-320,-200) , filled black (circle 4) |> move (300,200) - , toForm (toText "C" |> Text.height 30 |> leftAligned) |> move (320,200) + , toForm (fromString "C" |> Text.height 30 |> leftAligned) |> move (320,200) , filled black (circle 4) |> move (300,-200) - , toForm (toText "C’" |> Text.height 30 |> leftAligned) |> move (320,-200) + , toForm (fromString "C’" |> Text.height 30 |> leftAligned) |> move (320,-200) , filled black (circle 4) |> move (-300,200) - , toForm (toText "C’’" |> Text.height 30 |> leftAligned) |> move (-320,200) + , toForm (fromString "C’’" |> Text.height 30 |> leftAligned) |> move (-320,200) , traced defaultLine (path [(-300,-200),(300,0)]) , filled black (circle 4) |> move (300,0) - , toForm (toText "A" |> Text.height 30 |> leftAligned) |> move (320,0) + , toForm (fromString "A" |> Text.height 30 |> leftAligned) |> move (320,0) , filled black (circle 4) |> move (225,-25) - , toForm (toText "M" |> Text.height 30 |> leftAligned) |> move (225,-43) + , toForm (fromString "M" |> Text.height 30 |> leftAligned) |> move (225,-43) , filled black (circle 4) |> move (137,-54) - , toForm (toText "B" |> Text.height 30 |> leftAligned) |> move (137,-74) + , toForm (fromString "B" |> Text.height 30 |> leftAligned) |> move (137,-74) , filled black (circle 4) |> move (60,-80) - , toForm (toText "P" |> Text.height 30 |> leftAligned) |> move (60,-100) + , toForm (fromString "P" |> Text.height 30 |> leftAligned) |> move (60,-100) ] -content2 = [markdown| +content2 = Markdown.toElement """ Point *O* is the center of the eye, and the *R’R’’* arc is the internal eye border. The *OC’CC’’* rectangle is the area where we @@ -254,7 +266,7 @@ area. Each eye will fill half of the available area. We will thus have 8 quaters of an eye that we have to handle, as illustrated on the following picture. -|] +""" -- xB²×yR² + yB²×xR² = xR²×yR² ① -- xB²×yR² + xB²×xR²×yM²/xM² = xR²×yR² @@ -276,7 +288,7 @@ picture2 = collage 502 302 , traced (dotted black) (path [(125,-150),(125,150)]) ] -content3 = [markdown| +content3 = Markdown.toElement """ Thus, the *xC* size will be equal to one-fourth of the window width, and *yC* will be equal to half of the window height. The *xR* and *yR* @@ -299,15 +311,15 @@ which area of the screen the mouse pointer is. The function returns a (xPr,yPr) = if xM >= 3*xC then calculateP (xR,yR) (xC,yC) (xM-3*xC,yM) - |> \(xP,yP) -> (xP+xC, yP * sign yM) + |> \\(xP,yP) -> (xP+xC, yP * sign yM) else calculateP (xR,yR) (3*xC,yC) (3*xC-xM,yM) - |> \(xP,yP) -> (xC-xP, yP * sign yM) + |> \\(xP,yP) -> (xC-xP, yP * sign yM) (xPl,yPl) = if xM >= xC then calculateP (xR,yR) (3*xC,yC) (xM-xC,yM) - |> \(xP,yP) -> (xP-xC, yP * sign yM) + |> \\(xP,yP) -> (xP-xC, yP * sign yM) else calculateP (xR,yR) (xC,yC) (-xM+xC,yM) - |> \(xP,yP) -> (-xP-xC, yP * sign yM) + |> \\(xP,yP) -> (-xP-xC, yP * sign yM) in (xPl,yPl,xPr,yPr) @@ -315,13 +327,17 @@ What is left is the `Eyes` module that assembles the program modules together: % Eyes.elm + module Eyes where + + + import EyesModel (..) + import EyesView (..) import Mouse + import Signal import Window - import EyesView (..) - import EyesModel (..) - main = lift2 eyes Window.dimensions Mouse.position + main = Signal.map2 eyes Window.dimensions Mouse.position eyes (w,h) (x,y) = eyesView (w,h) (pupilsCoordinates (w,h) (x,y)) @@ -336,4 +352,4 @@ calculations for given window dimensions and mouse positions. The [next](Chapter6TimeSignals.html) chapter introduces time related signals. -|] +""" diff --git a/Chapter6TimeSignals.elm b/Chapter6TimeSignals.elm index 00fd611..02968e6 100644 --- a/Chapter6TimeSignals.elm +++ b/Chapter6TimeSignals.elm @@ -2,10 +2,12 @@ import Lib (..) import Window +import Signal +import Markdown -main = lift (pageTemplate [content] "Chapter5Eyes" "toc" "Chapter7DelayedCircles") Window.width +main = Signal.map (pageTemplate [content] "Chapter5Eyes" "toc" "Chapter7DelayedCircles") Window.width -content = [markdown| +content = Markdown.toElement """ # Chapter 6 Time Signals @@ -45,8 +47,15 @@ program (a working example is available [here](TimeSignals.html)), presents a few examples of their use. % TimeSignals.elm - import Time + module TimeSignals where + + + import Graphics.Element (down, flow) + import List (map) import Mouse + import Signal ((~), (<~)) + import Text (plainText) + import Time (delay, every, fps, fpsWhen, second, since, timestamp) showsignals a b c d e f = @@ -56,12 +65,12 @@ presents a few examples of their use. map plainText [ - "every (5*second): " ++ show a, - "since (2*second) Mouse.clicks: " ++ show b, - "timestamp Mouse.isDown: " ++ show c, - "delay second Mouse.position: " ++ show d, - "fps 200: " ++ show e, - "fpsWhen 200 Mouse.isDown: " ++ show f + "every (5*second): " ++ toString a, + "since (2*second) Mouse.clicks: " ++ toString b, + "timestamp Mouse.isDown: " ++ toString c, + "delay second Mouse.position: " ++ toString d, + "fps 200: " ++ toString e, + "fpsWhen 200 Mouse.isDown: " ++ toString f ] @@ -102,5 +111,4 @@ when the mouse button is pressed. The [next](Chapter7DelayedCircles.html) chapter presents an example program that is using one of the functions presented above. -|] - +""" diff --git a/Chapter7DelayedCircles.elm b/Chapter7DelayedCircles.elm index ae7535d..043658b 100644 --- a/Chapter7DelayedCircles.elm +++ b/Chapter7DelayedCircles.elm @@ -2,12 +2,17 @@ import Lib (..) import Window +import Signal +import Markdown +import Graphics.Element (..) +import Graphics.Collage (..) +import Signal ((<~)) content w = pageTemplate [text1,container w 520 middle picture,text2] - "Chapter6TimeSignals" "toc" "Chapter8RandomSignals" w -main = lift content Window.width + "Chapter6TimeSignals" "toc" "Chapter8Circles" w +main = Signal.map content Window.width -text1 = [markdown| +text1 = Markdown.toElement """ # Chapter 7 Delayed Circles @@ -26,14 +31,20 @@ draws the circles given a list of their radiuses. import Array as A + import Color (Color, blue, brown, green, orange, purple, red, yellow) + import Graphics.Collage (Form, circle, collage, filled, move) + import Graphics.Element (Element) + import List (map) + import Maybe color : Int -> Color color n = let colors = A.fromList [ green, red, blue, yellow, brown, purple, orange ] + maybeColor = A.get (n % (A.length colors)) colors in - A.getOrElse black (n % (A.length colors)) colors + Maybe.withDefault green maybeColor circleForm : (Int, (Int, Int)) -> Form @@ -43,7 +54,7 @@ draws the circles given a list of their radiuses. |> move (toFloat x,toFloat y) - drawCircles : [(Int, (Int, Int))] -> (Int, Int) -> Element + drawCircles : List (Int, (Int, Int)) -> (Int, Int) -> Element drawCircles d (w, h) = collage w h <| map circleForm d main = @@ -60,11 +71,48 @@ The `color` function takes a number and returns one of the colors from a predefined list. The argument modulo the length of the list gives the index of the returned color. In order to retrieve an element from a given index, the list is transformed into an array using the -`fromList` function of the `Array` module. The `getOrElse` function -returns the element from the given index. Its first argument is a -fallback value, in case the index provided in the second argument does -not exist in the array (it is never used in our case, since we always -calculate a correct index value). +`fromList` function of the `Array` module. The `get` function is used +to retrieve the element from the given index. + + get : Int -> Array a -> Maybe a + +Its first argument is the index, and the second argument is the +array. However, the result type is not `a` — the type of array +elements — but `Maybe a`. Let’s see what the `get` function returns +depending on the value of the index. + + > import Array as A + > arr = A.fromList ['a', 'b', 'c', 'd'] + Array.fromList ['a','b','c','d'] : Array.Array Char + > A.get 0 arr + Just 'a' : Maybe.Maybe Char + > A.get 9 arr + Nothing : Maybe.Maybe Char + + The `Maybe` data type is defined in the `Maybe` module and it is a so +called *union type* and it is a union of two distinct cases. It +represents optional values. An existing value is represented by the +`Just` case, while a non-existing value by `Nothing`. Array elements +are indexed starting with 0, thus 0 is a valid index and the return +value is the first element of our array “wrapped” in `Just`. However, +calling `get` with the index of 9 returns `Nothing`. To get the value +out of the `Maybe` type we can use the `Maybe.withDefault` function. + + > import Maybe (withDefault) + > withDefault + : a -> Maybe.Maybe a -> a + > withDefault 'z' (A.get 0 arr) + 'a' : Char + > withDefault 'z' (A.get 9 arr) + 'z' : Char + +The first argument of `withDefault` is a fallback value — to be used +in case the `Maybe` value is `Nothing`. + +The `color` function in the `DrawCircles` module uses `withDefault` to +“unwrap” the color value from the `Maybe` value returned by `get`, but +the fallback value is never used, since the code always calculate a +correct index value when calling `get. The `circleForm` function returns a `Form` representing a circle drawn according to the data provided in the first argument. The first @@ -90,22 +138,32 @@ each pair is the delayed mouse position. module DelayedMousePositions where + import List + import List ((::), foldr, length, repeat) import Mouse + import Signal + import Signal (Signal, (~), (<~), constant) + import Text (asText) + import Time (delay) import Window - delayedMousePositions : [Int] -> Signal [(Int, (Int, Int))] + combine : List (Signal a) -> Signal (List a) + combine = foldr (Signal.map2 (::)) (constant []) + + + delayedMousePositions : List Int -> Signal (List (Int, (Int, Int))) delayedMousePositions rs = let adjust (w, h) (x, y) = (x-w//2,h//2-y) n = length rs position = adjust <~ Window.dimensions ~ Mouse.position - positions = repeat n position -- [Signal (Int, Int)] - delayedPositions = -- [Signal (Int, (Int, Int))] - zipWith - (\r pos -> + positions = repeat n position -- List (Signal (Int, Int)) + delayedPositions = -- List (Signal (Int, (Int, Int)) + List.map2 + (\\r pos -> let delayedPosition = delay (toFloat r*100) pos in - lift (\pos -> (r,pos)) delayedPosition) + (\\pos -> (r,pos)) <~ delayedPosition) rs positions in @@ -119,7 +177,7 @@ representing the delays and returns the signal. The following figure presents how the signal is built by combining and transforming other signals. -|] +""" sigBox a b c w x line = signalFunctionBox 14 18 50 a b c w x (line*100-300) sigVertU line x = sigVerticalLine 25 x (line*100-238) @@ -134,7 +192,7 @@ picture = collage 600 510 , sigBox "Signal (Int,Int)" "Mouse.position" "" 170 100 5 , sigVertArr 4 -45 , sigVertArr 4 45 - , sigBox "Signal (Int,Int)" "position" "lift adjust" 140 0 4 + , sigBox "Signal (Int,Int)" "position" "adjust" 140 0 4 , sigVertArr 3 0 , sigBox "[Signal (Int,Int)]" "positions" "repeat" 140 0 3 , sigVertArr 2 0 @@ -143,7 +201,7 @@ picture = collage 600 510 , sigBox "Signal [(Int,(Int,Int))]" "mousePositions" "combine" 140 0 1 ] -text2 = [markdown| +text2 = Markdown.toElement """ The `Window.dimensions` and `Mouse.positions` signals are the basic signals from the standard library, that are transformed into the @@ -160,7 +218,7 @@ right and upwards. The `positions` function returns the `position` signal repeated `n` times, where `n` is the length of the input list. Thus the `positions` -function returns a list of type `[Signal (Int,Int)]`. +function returns a list of type `List (Signal (Int,Int))`. The `delayedPositions` returns a list of signals, each of which carries pairs of values — the function return type is `[Signal @@ -176,9 +234,6 @@ a delayed signal. The returned signal is produced using the `combine` function, which turns a list of signals into a signal of a list of values. - > combine - : [Signal.Signal a] -> Signal.Signal [a] - Again, the `main` function is used for testing. You can see its result [here](DelayedMousePositions.html). @@ -188,10 +243,15 @@ is a series of circles, each of which follow the mouse pointer, but with a time lag proportional to the size of the circle. % DelayedCircles.elm - import Window - import Fibonacci (fibonacci) - import DrawCircles (drawCircles) + module DelayedCircles where + + import DelayedMousePositions (delayedMousePositions) + import DrawCircles (drawCircles) + import Fibonacci (fibonacci) + import List (reverse, tail) + import Signal ((~), (<~)) + import Window main = @@ -203,7 +263,7 @@ The sizes of the circles are calculated by the `fibonacci` function from the `Fibonacci` module described in [Chapter 2](Chapter2FibonacciBars.html). -The [next](Chapter8RandomSignals.html) chapter presents signals used +The [next](Chapter8Circles.html) chapter presents signals used for generating random numbers. -|] +""" diff --git a/Chapter9Circles.elm b/Chapter8Circles.elm similarity index 71% rename from Chapter9Circles.elm rename to Chapter8Circles.elm index 5bc44b3..db468dd 100644 --- a/Chapter9Circles.elm +++ b/Chapter8Circles.elm @@ -2,14 +2,19 @@ import Lib (..) import Window +import Signal +import Markdown +import Graphics.Element (..) +import Graphics.Collage (..) +import Signal ((<~)) content w = pageTemplate [text1,container w 820 middle picture1,text2] - "Chapter8RandomSignals" "toc" "Chapter10Calculator" w -main = lift content Window.width + "Chapter7DelayedCircles" "toc" "Chapter9Calculator" w +main = Signal.map content Window.width -text1 = [markdown| +text1 = Markdown.toElement """ -# Chapter 9 Circles +# Chapter 8 Circles The next example, *[Circles.elm](Circles.elm)*, is a program which maintains state. Initially, the program only shows an empty @@ -27,16 +32,21 @@ The code is divided into three modules: We start our analysis with the `CirclesModule` module, defined in the *[CirclesModel.elm](CirclesModel.elm)*, which starts with the usual -module declaration followed by three type declarations: +module declaration followed by imports and three type declarations: % CirclesModel.elm module CirclesModel where - type Position = { x: Int, y: Int } + import Color (Color, rgb) + import Time (Time) + import Random (generate, int, initialSeed) - type CircleSpec = { + type alias Position = { x: Int, y: Int } + + + type alias CircleSpec = { radius: Int, xv: Int, yv: Int, @@ -45,15 +55,16 @@ module declaration followed by three type declarations: } - type Circle = { + type alias Circle = { position: Position, circleSpec: CircleSpec } The module defines three data types, that we will use in our -program. Their definitions start with the `type` keyword followed by -the type name. The `type` statement creates a type alias, that is, the -type on the right hand side of the equals sign acquires a new name. +program. Their definitions start with the `type alias` keywords +followed by the type name. The `type alias` statement creates, a type +alias, that is, the type on the right hand side of the equals sign +acquires a new name. All of the data types are records. A record is a data structure consisting of one or more values. Each value in a record has a name @@ -95,19 +106,127 @@ specifying a circle: its radius, its vertical and horizontal velocity `Circle` data type contains the circle specification (`CircleSpec`) and its position. -The `CirclesModel` module defines a few functions, but instead of -analyzing them now, we will now turn our attention to the -`CirclesView` module. We will get back to the `CirclesModel` functions -later. The `CirclesView` module, defined in the -*[CirclesView.elm](CirclesView.elm)* file, starts with the module -declaration and the import of the data types declared in the -`CirclesModel` module. +The `makeCircleSpec` function creates a new `CircleSpec` record given +a `Time` value. + +% CirclesModel.elm + + makeCircleSpec : Time -> CircleSpec + makeCircleSpec time = + let seed1 = initialSeed (round time) + (radius,seed2) = generate (int 10 30) seed1 + (xv,seed3) = generate (int 10 50) seed2 + (yv,seed4) = generate (int 10 50) seed3 + (r,seed5) = generate (int 10 220) seed4 + (g,seed6) = generate (int 10 220) seed5 + (b,_) = generate (int 10 220) seed6 + in + { radius = radius + , xv = xv + , yv = yv + , col = rgb r g b + , creationTime = time + } + +The `makeCircleSpec` function is using several functions from the +`Random` module, which contains functions related to generating random +values. The function generates random values using the `generate` +function, which has the following type: + + generate : Generator a -> Seed -> (a, Seed) + +Its first argument is generator, which produces random values of a +certain type `a`. The `makeCircleSpec` function uses generators +created by the `int` function, which takes two `Int` arguments +defining a range of values and returns a generator of `Int` values +from that range. + + int : Int -> Int -> Generator Int + +Besides a generator, the `generator` function needs a `Seed` value to +produce a result. A `Seed` value can be created by the `initialSeed` +function, which takes an `Int` value. + + initialSeed : Int -> Seed + +The `generate` function returns a random value produced by the +generator and a new seed. If we try to call the `generate` function +with the same generator and seed, we will always get the same return +value (the REPL output is a little verbose, showing the internals of +the seed): + + > generate (int 10 18) (initialSeed 12345) + (12,{ next = , range = , split = , state = State 494012844 40692 }) + : ( Int + , { next : Random.State -> ( Int, Random.State ) + , range : Random.State -> ( Int, Int ) + , split : Random.State -> ( Random.State, Random.State ) + , state : Random.State + } + ) + > generate (int 10 18) (initialSeed 12345) + (12,{ next = , range = , split = , state = State 494012844 40692 }) + : ( Int + , { next : Random.State -> ( Int, Random.State ) + , range : Random.State -> ( Int, Int ) + , split : Random.State -> ( Random.State, Random.State ) + , state : Random.State + } + ) + +That is not very useful if we really need randomness. We can, however, +use the seed returned by `generate` as the input seed in the next call +to `generate`: + + > result = generate (int 10 18) (initialSeed 12345) + (12,{ next = , range = , split = , state = State 494012844 40692 }) + : ( Int + , { next : Random.State -> ( Int, Random.State ) + , range : Random.State -> ( Int, Int ) + , split : Random.State -> ( Random.State, Random.State ) + , state : Random.State + } + ) + > generate (int 10 18) (snd result) + (18,{ next = , range = , split = , state = State 1991225964 1655838864 }) + : ( Int + , { next : Random.State -> ( Int, Random.State ) + , range : Random.State -> ( Int, Int ) + , split : Random.State -> ( Random.State, Random.State ) + , state : Random.State + } + ) + +The `makeCircleSpec` function uses a time value (recall that `Time` is +an alias of `Float`) to get the initial seed. It then calls `generate` +several times in order to calculate the circle specification elements, +passing previously calculated seeds as input to the subsequent +`generate` calls. In order to calculate the circle color, it uses the +`rgb` function, which takes three `Int` values representing the +primary colors: red, green and blue. + + > makeCircleSpec 12345.0 + { col = RGBA 34 33 99 1, creationTime = 12345, radius = 18, xv = 10, yv = 34 } + : { col : Color.Color + , creationTime : Float + , radius : Int + , xv : Int + , yv : Int + } + +We will now turn our attention to the `CirclesView` module. It is +defined in the *[CirclesView.elm](CirclesView.elm)* file and it starts +as usual with the module declaration and a list of imports. % CirclesView.elm module CirclesView where - import CirclesModel (Position, CircleSpec, Circle) + import CirclesModel (Circle, CircleSpec, Position) + import Color (black, red, green) + import Graphics.Collage (circle, collage, filled, move, outlined, rect, solid) + import Graphics.Element (layers) + import List (map) The `boundingBox` function draws a square of a given width and height. The `outlined` function draws the square border using the line @@ -190,18 +309,18 @@ its beginning: module Circles where - import Color - import Time (..) - import Mouse - import Random (range) import CirclesModel (..) import CirclesView (..) + import List ((::), map) + import Mouse + import Signal (Signal, (<~), (~), foldp, keepIf, sampleOn) + import Time (Time, fps, timestamp) The module creates several signals and combines them together. The following figure presents the relations between the individual signals. -|] +""" sigBox a b c w x line = signalFunctionBox 14 18 50 a b c w x (line*100-300-50) sigVertU line x = sigVerticalLine 25 x (line*100-238-50) @@ -212,40 +331,31 @@ sigArr line x = sigDownwardArrow x (line*100-265-50) sigVertArr line x = group [sigVert line x, sigArr line x ] picture1 = collage 900 810 - [ sigBox "Signal (Int,Int)" "clickPositionsSignal" "sampleOn Mouse.clicks Mouse.position" 240 -100 7 - - , sigVertArr 6 -100 - - , sigBox "Signal (Int,Int)" "inBoxClickPositionsSignal" "Signal.keepIf" 200 -100 6 - , sigBox "Signal Time" "clockSignal" "Time.timestamp, Time.fps" 200 300 6 - - , sigVertArr 5 -100 - , sigVertArr 5 300 - , sigVertD 5 -300, sigArr 5 -300 - , sigVertD 5 100, sigArr 5 100 - , sigVertD 5 250, sigArr 5 250 - , sigHoriz 650 5 -75 - , sigHoriz 100 5 350 - - , sigBox "Signal Int" "radiusSignal" "Random.range" 170 -300 5 - , sigBox "Signal Int" "velocitySignal" "Random.range" 170 -100 5 - , sigBox "Signal Color" "colorSignal" "Random.range" 170 100 5 - , sigBox "Signal Time" "creationTimeSignal" "Signal.sampleOn" 170 300 5 - - , sigVertU 4 -300 - , sigVertU 4 -100 - , sigVertU 4 100 - , sigVertU 4 300 - , sigVertD 4 0, sigArr 4 0 - , sigHoriz 600 4 0 - , sigVerticalLine 200 -400 (4*100-250-50) + [ sigBox "Signal (Int,Int)" "clickPositionsSignal" "sampleOn Mouse.clicks Mouse.position" 240 -150 7 + + , sigVertArr 6 -150 + + , sigBox "Signal (Int,Int)" "inBoxClickPositionsSignal" "Signal.keepIf" 200 -150 6 + , sigBox "Signal Time" "clockSignal" "Time.timestamp, Time.fps" 200 150 6 + + , sigVertU 5 -150 + , sigVertU 5 150 + , sigHoriz 100 5 -100 + , sigHoriz 100 5 100 + , sigVertD 5 -50, sigArr 5 -50 + , sigVertD 5 50, sigArr 5 50 + + , sigBox "Signal Time" "creationTimeSignal" "Signal.sampleOn" 170 0 5 + + , sigVertArr 4 0 + , sigVerticalLine 200 -150 (4*100-250-50) , sigBox "Signal CircleSpec" "newCircleSpecSignal" "" 200 0 4 , sigVertArr 3 0 - , sigHoriz 350 3 -225 + , sigHoriz 100 3 -100 , sigVertD 3 -50, sigArr 3 -50 - , sigVerticalLine 400 400 (3*100-250-50) + , sigVerticalLine 400 150 (3*100-250-50) , sigBox "Signal Circle" "newCircleSignal" "" 200 0 3 @@ -254,7 +364,7 @@ picture1 = collage 900 810 , sigBox "Signal [Circle]" "allCirclesSpecSignal" "foldp" 200 0 2 , sigVertArr 1 0 - , sigHoriz 350 1 225 + , sigHoriz 100 1 100 , sigVertD 1 50, sigArr 1 50 , sigBox "Signal [Circle]" "circlesSignal" "" 200 0 1 @@ -265,7 +375,7 @@ picture1 = collage 900 810 ] -text2 = [markdown| +text2 = Markdown.toElement """ The first signal from the `Circles` module is created by the `clockSignal` function. It periodically outputs a timestamp. The rate @@ -274,7 +384,7 @@ second) from the `Time` module. % Circles.elm clockSignal : Signal Time - clockSignal = lift fst (timestamp (fps 50)) + clockSignal = fst <~ timestamp (fps 50) The signal created by the `clickPositionsSignal` function outputs the mouse pointer position on every click. @@ -287,7 +397,7 @@ The `inBoxClickPositionsSignal` function takes the width and height as arguments and creates a signal, that filters the events from the `clickPositionsSignal` to only output those that represent positions inside the bounding box of the given width and height. The `keepIf` -function from the standard `Signals` module is used for filtering the +function from the standard `Signal` module is used for filtering the events. % Circles.elm @@ -297,47 +407,10 @@ events. in keepIf positionInBox (0, 0) clickPositionsSignal -When the user clicks inside the bounding box, the `Circles` program is -supposed to create a new circle. Several circle properties are -generated randomly using the `Random.range` function. The next couple -of signals represent those various circle properties. - -The `radiusSignal` function produces a signal of values, each of which -represent the radius of a new, to-be-created circle. -% Circles.elm - - radiusSignal : Int -> Int -> Signal Int - radiusSignal w h = - range 10 30 (inBoxClickPositionsSignal w h) - -The `velocitySignal` function generates a signal of values representing the -vertical or horizontal velocity of the circle. -% Circles.elm - - velocitySignal : Int -> Int -> Signal Int - velocitySignal w h = - range 10 50 (inBoxClickPositionsSignal w h) - -The `colorSignal` function generates a signal of values representing the circle -color. It combines three auxiliary signals, each of which represent -one of the three basic colors (red, green, blue), by means of the -`rgb` function from the `Color` module. The `rgb` function takes three -`Int` values and returns a color. -% Circles.elm - - colorSignal : Int -> Int -> Signal Color - colorSignal w h = - let - redSignal = range 0 220 (inBoxClickPositionsSignal w h) - greenSignal = range 0 220 (inBoxClickPositionsSignal w h) - blueSignal = range 0 220 (inBoxClickPositionsSignal w h) - in - lift3 rgb redSignal greenSignal blueSignal - -The `creationTimeSignal` function produces a signal representing the creation -times of the circles. This is not a random signal. It is created by -sampling (using the `sampleOn` function) the signal produced by the -`clockSignal` function on the events carried on by the signal from the +The `creationTimeSignal` function produces a signal representing the +creation times of the circles. It is created by sampling (using the +`sampleOn` function) the signal produced by the `clockSignal` function +on the events carried on by the signal from the `inBoxClickPositionsSignal` function. % Circles.elm @@ -351,14 +424,7 @@ a signal representing the specifications of the new circles. newCircleSpecSignal : Int -> Int -> Signal CircleSpec newCircleSpecSignal w h = - let makeCircleSpec r xv yv c t = { radius = r, xv = xv, yv = yv, col = c, creationTime = t } - in - makeCircleSpec - <~ radiusSignal w h - ~ velocitySignal w h - ~ velocitySignal w h - ~ colorSignal w h - ~ creationTimeSignal w h + makeCircleSpec <~ creationTimeSignal w h A full circle representation consists of its specification and its position. The `newCircleSignal` function produces a signal @@ -382,7 +448,7 @@ the user clicked inside the bounding box. After each such click, we want to add the new circle to a list of previous circles (our list is empty at the beginning). New circles are represented by a signal of type `Signal Circle`. We now want to have a signal of type `Signal -[Circle]`, which contains a list of all circles created so far. +(List Circle)`, which contains a list of all circles created so far. We can use the `foldp` function from the `Signal` module to maintain state in Elm programs. The signature of that function looks as follows: @@ -419,17 +485,17 @@ and a list — and returns a new list consisting of the new value prepended to the list. > 1 :: [] - [1] : [number] + [1] : List number > 2 :: [1] - [2,1] : [number] + [2,1] : List number > 3 :: [2,1] - [3,2,1] : [number] + [3,2,1] : List number We can thus use the `::` operator as the first argument to our `foldp` call: % Circles.elm - allCirclesSpecSignal : Int -> Int -> Signal [Circle] + allCirclesSpecSignal : Int -> Int -> Signal (List Circle) allCirclesSpecSignal w h = foldp (::) [] (newCircleSignal w h) @@ -510,7 +576,7 @@ partially applied with the appropriate parameters, through a list of `Circle` values. % Circles.elm - positionedCircles : Int -> Int -> Float -> [Circle] -> [Circle] + positionedCircles : Int -> Int -> Float -> List Circle -> List Circle positionedCircles w h time circles = map (positionedCircle w h time) circles @@ -527,7 +593,7 @@ positions change while time passes. This is the job of the `clockSignal` and `allCirclesSpecSignal`. % Circles.elm - circlesSignal : Int -> Int -> Signal [Circle] + circlesSignal : Int -> Int -> Signal (List Circle) circlesSignal w h = positionedCircles w h <~ clockSignal ~ allCirclesSpecSignal w h @@ -543,7 +609,7 @@ program signal, that is rendered by Elm’s runtime. So far we have used signals from the `Mouse` module to get mouse -related input. The [next](Chapter10Calculator.html) chapter presents an +related input. The [next](Chapter9Calculator.html) chapter presents an alternative way of processing mouse events. -|] +""" diff --git a/Chapter8RandomSignals.elm b/Chapter8RandomSignals.elm deleted file mode 100644 index 6570f34..0000000 --- a/Chapter8RandomSignals.elm +++ /dev/null @@ -1,64 +0,0 @@ --- -*- coding: utf-8; -*- - -import Lib (..) -import Window - -main = lift (pageTemplate [content] "Chapter7DelayedCircles" "toc" "Chapter9Circles") Window.width - -content = [markdown| - -# Chpater 8 Random Signals - -The `Random` module defines functions producing signals carrying -random numbers. - -The `float` function creates a signal of float numbers between 0 -(including) and 1 (excluding). The signal returned by this, and the -two other functions presented below, outputs a new event for each -event from the input signal, which is passed as the function -argument. Observe [here](RandomSignals1.html) the values produced for -each mouse click by the *[RandomSignals1.elm](RandomSignals1.elm)* -program, presented below. - -% RandomSignals1.elm - import Random - import Mouse - - - main = asText <~ Random.float Mouse.clicks - -The `range` function creates a signal of integer numbers from within a -range specifed by the first two arguments. Observe -[here](RandomSignals2.html) the values produced for each mouse click -by the *[RandomSignals2.elm](RandomSignals2.elm)* program. - -% RandomSignals2.elm - import Random - import Mouse - - - main = asText <~ Random.range 10 20 Mouse.clicks - -The `floatList` function creates a signal of lists of floating point -numbers. The input signal passed to the function as its first argument -must be a signal of type `Signal Int`. The length of the output list -is equal to the value carried by the input -signal. Observe [here](RandomSignals3.html) the values produced by the -*[RandomSignals3.elm](RandomSignals3.elm)* program as you move your -mouse. - -% RandomSignals3.elm - import Random - import Mouse - - - lengthSignal = lift (\x -> 1 + x // 100) Mouse.x - - - main = asText <~ Random.floatList lengthSignal - -Our programs so far did not have any state. Yet state is -important. The [next](Chapter9Circles.html) chapter shows how to deal -with state in Elm programs. - -|] diff --git a/Chapter10Calculator.elm b/Chapter9Calculator.elm similarity index 82% rename from Chapter10Calculator.elm rename to Chapter9Calculator.elm index 2adbf2d..9f21e78 100644 --- a/Chapter10Calculator.elm +++ b/Chapter9Calculator.elm @@ -2,13 +2,20 @@ import Lib (..) import Window +import Signal +import Markdown +import Signal ((<~)) +import Graphics.Element (..) +import Graphics.Collage (..) +import Color (red) +import List (map) -content w = pageTemplate [content1,container w 200 middle picture1,content2,container w 100 middle picture2,content3] "Chapter9Circles" "toc" "Chapter11KeyboardSignals" w -main = lift content Window.width +content w = pageTemplate [content1,container w 200 middle picture1,content2,container w 100 middle picture2,content3] "Chapter8Circles" "toc" "Chapter10KeyboardSignals" w +main = Signal.map content Window.width -content1 = [markdown| +content1 = Markdown.toElement """ -# Chapter 10 Calculator +# Chapter 9 Calculator The *[Calculator.elm](Calculator.elm)* program implements a simple calculator that can be used to perform arithmetic operations. You can @@ -23,34 +30,34 @@ The code is divided into three modules: We start our analysis with the `CalculatorModel` module defined in the *[CalculatorModel.elm](CalculatorModel.elm)* file. The module starts with -thdeclaration and a list of imports: +the declaration and a list of imports: % CalculatorModel.elm module CalculatorModel where import Char + import Maybe (withDefault) + import Result import Set import String - import Maybe (maybe) The following line defines a new data type called `ButtonType`: % CalculatorModel.elm - data ButtonType = Regular | Large + type ButtonType = Regular | Large -The definition starts with the `data` keyword followed by the type -name, the equals sign and the type definition. The `data` keyword is -used for defining so called Algebraic Data Types (ADT). Such types -consist of a number of alternatives which are separated with the `|` -character. In our case, we have two alternatives: `Regular` and -`Large`. +The definition starts with the `type` keyword followed by the type +name, the equals sign and the type definition. The `type` keyword is +used for defining so called *Union Types*. Such types consist of a +number of alternatives which are separated with the `|` character. In +our case, we have two alternatives: `Regular` and `Large`. -Our data type is very simple. However, using the `data` keyword, it is +Our data type is very simple. However, using the `type` keyword, it is possible to define more complex data types as well. For example, the following data type represents a list of integers: - data ListOfInts = Nil | Cons Int ListOfInts + type ListOfInts = Nil | Cons Int ListOfInts The alternatives are sometimes called *type constructors*. Our `ListOfInts` data type defines two of them. The first one is called @@ -68,12 +75,11 @@ As an example, let us create a two-element list, containing the values > Cons 1 (Cons 2 Nil) Cons 1 (Cons 2 Nil) : ListOfInts -A data type defined with the `data` keyword can have type -parameters. The following data type represents lists of elements of an -arbitrary type. The type of the list elements is represeted by the `a` -parameter: +Union types may have type parameters. The following data type +represents lists of elements of an arbitrary type. The type of the +list elements is represeted by the `a` parameter: - data GenericList a = Nil | Cons a (GenericList a) + type GenericList a = Nil | Cons a (GenericList a) Here we create a list of characters containing the characters ‘a’, ‘b’ and ‘c’: @@ -140,7 +146,7 @@ The `CalculatorModel` module defines a record type representing the calculator state. % CalculatorModel.elm - type CalculatorState = { + type alias CalculatorState = { input: String, operator: String, number: Float @@ -175,7 +181,7 @@ and the button clicked by the user and calculates the new state. if | (state.input == "" || state.input == "0") && btn == "." -> "0." | state.input == "" || state.input == "0" -> btn | String.length state.input >= 18 -> state.input - | btn == "." && any (\c -> c == '.') (String.toList state.input) -> state.input + | btn == "." && String.any (\\c -> c == '.') state.input -> state.input | otherwise -> state.input ++ btn } The `step` function uses an alternative form of the `if` @@ -230,7 +236,7 @@ The function uses two functions from the `Set` module. The function verifies if its first argument belongs to the set represented by the second argument. -If the user selected one of the operators, but there is already an +If the user selects one of the operators, but there is already an input value present in the `input` field, then a whole new state is calculated and returned as follows: @@ -244,7 +250,7 @@ The `calculate` function is defined as follows: calculate : Float -> String -> String -> Float calculate number op input = - let number2 = maybe 0.0 identity (String.toFloat input) + let number2 = withDefault 0.0 (Result.toMaybe (String.toFloat input)) in if | op == "+" -> number + number2 | op == "-" -> number - number2 @@ -259,32 +265,18 @@ return a `Float` value however, as showed by the repl: > String.toFloat : String -> Maybe Float -The return value is of type `Maybe Float`. `Maybe` is an algebraic -data type defined in the `Maybe` module as follows: +The return value is of type `Maybe Float`. We have already met `Maybe` +in one of earlier chapters. `Maybe` is a union type defined in the +`Maybe` module as follows: - data Maybe a = Just a | Nothing + type Maybe a = Just a | Nothing Thus the `String.toFloat` function may return one of two values: `Just Float` or `Nothing`. The first one is returned if the conversion succeeds, the second one otherwise. The `calculate` function could -pattern match on the result using a `case` expression, but it does not -have to do it, since the `Maybe` module provides the `maybe` function -which already implements the pattern matching on `Maybe` values. The -function expects three arguments: - - > import Maybe - > Maybe.maybe - : a -> (b -> a) -> Maybe b -> a - -The first one is a value to be returned if the third argument is -`Nothing`. The second is a function, which is applied to the value -contained in the `Just` constructor. The result of that function -application becomes the result of the `maybe` function call. - -The `calculate` function uses the `id` function, which just returns its -argument, as the second argument to `maybe`, since it only wants to -exctract the `Just` value without transforming it. The `0.0` value is -used as a fallback in case the conversion fails. +pattern match on the result using a `case` expression, but it uses the +the `withDefault` function instead, to retrieve the result, with the +`0.0` value used as a fallback in case the conversion fails. After converting the input value to `Float`, the `calculate` function performs the appropriate (based on the value of the `operator` member) @@ -311,7 +303,7 @@ converted to a string and returned. showState : CalculatorState -> String showState {number,input} = if input == "" - then show number + then toString number else input We can now turn our analysis to the `CalculatorView` module, which is @@ -323,7 +315,11 @@ definition starts as follows: import CalculatorModel (..) - import Graphics.Input (Input, input, clickable) + import Color (rgb) + import Graphics.Collage (LineCap(Padded), collage, defaultLine, filled, outlined, rect, toForm) + import Graphics.Element (Element, container, down, flow, layers, midRight, middle, right, spacer) + import Graphics.Input (clickable) + import Signal (Signal, channel, send, subscribe) import Text After the imports, the `makeButton` function is defined. That function @@ -343,7 +339,7 @@ that will be the button label, and a `ButtonType` value as arguments. filled buttonColor <| rect (toFloat (xSize-8)) 52, outlined { defaultLine | width <- 2, cap <- Padded } <| rect (toFloat (xSize-8)) 52, - Text.toText label |> Text.height 30 |> Text.bold |> Text.centered |> toForm + Text.fromString label |> Text.height 30 |> Text.bold |> Text.centered |> toForm ] A button is composed of a filled rectangle, which forms the button @@ -375,7 +371,7 @@ The `cap` member can be set to values `Flat` (default), `Round` or `Sharp Float` (`Sharp 10` is the default). The following figure illustrates the various line caps and joins: -|] +""" picture1 = let myShape = path [(-50,-50),(50,-50),(50,50),(0,0)] @@ -395,7 +391,7 @@ picture1 = let map (\form -> collage 170 170 [form,dot,dot50]) |> flow right -content2 = [markdown| +content2 = Markdown.toElement """ The first one has the `cap` set to `Flat` and the `join` set to `Sharp 10`. The second one has the `cap` set to `Flat` and the `join` @@ -407,7 +403,7 @@ The following figure illustrates the `dashing`. It presents three lines. The first one has `dashing` set to `[]` (the default). The second, to `[40,10]` and the third one to `[40,10,40]`. -|] +""" picture2 = let myShape = path [(-200,0),(200,0)] @@ -425,13 +421,16 @@ picture2 = let map (\form -> collage 420 30 [form]) |> flow down -content3 = [markdown| +content3 = Markdown.toElement """ The *CalculatorViewTest1.elm* program (showed below) can be used to visually test the `makeButton` function (try it [here](CalculatorViewTest1.html)). % CalculatorViewTest1.elm + module CalculatorViewTest1 where + + import CalculatorModel (..) import CalculatorView (..) @@ -446,35 +445,43 @@ signal associated with it. We create such buttons using the makeButtonAndSignal : String -> ButtonType -> (Element, Signal String) makeButtonAndSignal label btnSize = - let buttonInput = input label - button = makeButton label btnSize - clickableButton = clickable buttonInput.handle label button + let button = makeButton label btnSize + buttonChannel = channel "" + message = send buttonChannel label + clickableButton = clickable message button in - (clickableButton, buttonInput.signal) + (clickableButton, subscribe buttonChannel) + +To create a clickable element, we first need a *channel*. The +`Channel` type is defined in the `Signal` module and it represents a +place where messages can be sent to. You can also *subscribe* to a +channel, getting a signal. To create a channel, we use the `channel` +function, providing the default value of that channel’s signal as its +argument: + + channel : a -> Channel a + +In our function we use a `String` value as the argument to the +`channel` function. Thus the `buttonChannel` value has the `Channel +String` type. -To create a clickable element, we first need an `Input` value. The -`Input` type is defined in the `Graphics.Input` module and is a record -with two members: `handle` and `signal`. To create such a value, we -use the `input` function: +To create a message, that can be sent to the channel, we use the +`send` function. We need to give it two arguments: the channel and a +value to be sent through it. - input : a -> Input a + send : Channel a -> a -> Message -In our function we use a `String` value as the argument to the `input` -function. Thus the `buttonInput` value has the `Input String` -type. The next step is to take a regular element and pass it as an -argument to the `clickable` method, which has the following signature: +We can now use the `clickable` function to turn a regular button into +a clickable one. The `clickable` function takes a message and an +element, and returns a *clickable* version of that element. - clickable : Handle a -> a -> Element -> Element + clickable : Message -> Element -> Element -The first argument is a handle, that can be obtained from the `handle` -member of the `Input` value. The second one is the value of the events -that will be emmitted by the signal associated with the clickable -element. Finally, the third argument is the regular element (created -using the `makeButton` function in our case). The function returns an -“enchanced” element (`clickableButton` in our function). Once we have -it, the `signal` member of the `Input` value represents the signal of -events. Our `makeButtonAndSignal` function returns a pair of values: -the clickable button and the signal. +Our `makeButtonAndSignal` function returns a pair of values: the +clickable button and the signal associated with the channel, which is +obtained using the `subscribe` function. + + subscribe : Channel a -> Signal a Next, we use the `makeButtonAndSignal` function to create all the calculator buttons and the associated signals. @@ -508,7 +515,7 @@ of the calculation as well as the user input will be shown. The display state = collage 240 60 [ outlined { defaultLine | width <- 2, cap <- Padded } <| rect 232 50, - toForm (container 220 50 midRight (plainText <| showState state)) + toForm (container 220 50 midRight (Text.plainText <| showState state)) ] It takes the calculator state as argument and uses the `showState` @@ -549,8 +556,13 @@ Finally, the `view` function combines the components and draws the calculator. The `view` function takes two arguments: the calculator state, and a pair representing the window sizes. The `CalculatorView` module -defines a `main` method for testing purposes. You can see it in action -[here](CalculatorView.html). +defines a `main` method for testing purposes. + +% CalculatorView.elm + + main = view initialState (600,600) + +You can see it in action [here](CalculatorView.html). The `Calculator` module is the main module of our calculator program: @@ -560,11 +572,12 @@ The `Calculator` module is the main module of our calculator program: import CalculatorModel (..) import CalculatorView (..) + import Signal (foldp, map2, mergeMany) import Window lastButtonClicked = - merges [ + mergeMany [ button0Signal, button1Signal, button2Signal, @@ -589,15 +602,13 @@ The `Calculator` module is the main module of our calculator program: stateSignal = foldp step initialState lastButtonClicked - main = lift2 view stateSignal Window.dimensions + main = map2 view stateSignal Window.dimensions The `lastButtonClicked` function combines individual signals associated with the calculator buttons into one signal using the -`merges` function from the `Signal` standard library module. - -The `merges` function has the following signature: +`mergeMany` function from the `Signal` standard library module. - merges : [Signal a] -> Signal a + mergeMany : List (Signal a) -> Signal a As the signature shows, all the signals in the input list need to have the same type. @@ -611,8 +622,7 @@ Finally, the `main` function combines the `stateSignal` and `CalculatorView` module. So far, we have only used the mouse to interact with our programs. In -the [next](Chapter11KeyboardSignals.html) chapter we will learn how to +the [next](Chapter10KeyboardSignals.html) chapter we will learn how to use keyboard releated signals. -|] - +""" diff --git a/Introduction.elm b/Introduction.elm index 6adeecd..2502f02 100644 --- a/Introduction.elm +++ b/Introduction.elm @@ -2,10 +2,12 @@ import Lib (..) import Window +import Signal +import Markdown -main = lift (pageTemplate [content] "toc" "toc" "Chapter1HelloWorld") Window.width +main = Signal.map (pageTemplate [content] "toc" "toc" "Chapter1HelloWorld") Window.width -content = [markdown| +content = Markdown.toElement """ # Introduction @@ -23,7 +25,7 @@ cost. With new Elm releases certain parts of this tutorial may become obsolete. I am planning to update the tutorial as new Elm versions appear, however you may expect a time lag between new Elm versions and new versions of this tutorial. This version of the tutorial targets -Elm 0.13. +Elm 0.14.1. I am not treating this tutorial as a finished work. You may expect its content to evolve and to be updated. Partly because of the Elm @@ -35,7 +37,18 @@ the tutorial is located on [github](http://github.com/grzegorzbalcerek/elm-by-example) and you can open an issue there. +The examples are available separately from +[github](http://github.com/grzegorzbalcerek/ElmByExample). + The [first](Chapter1HelloWorld.html) chapter will show you how to make simple Elm programs. -|] +""" +{- + and as an +Elm package. To install the package, you may run: + + elm-package install grzegorzbalcerek/ElmByExample + +You should have the files installed inside the `elm-stuff` folder. +-} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6640ef2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,5 @@ +Copyright (c) 2015, Grzegorz Balcerek + +This work is licensed under a [Creative Commons Attribution-ShareAlike +4.0 International +License](http://creativecommons.org/licenses/by-sa/4.0/). diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index ffff555..0000000 --- a/LICENSE.md +++ /dev/null @@ -1,36 +0,0 @@ - -This work is licensed under a [Creative Commons Attribution-ShareAlike -4.0 International -License](http://creativecommons.org/licenses/by-sa/4.0/). - -The files from the `code` folder are additionally licensed using the -following license (that means that you can choose which license to use -for the files in that folder): - - Copyright (c) 2014, Grzegorz Balcerek - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - diff --git a/Lib.elm b/Lib.elm index b2865a5..fee946f 100644 --- a/Lib.elm +++ b/Lib.elm @@ -4,38 +4,42 @@ module Lib where import Char import Text import String +import Markdown +import Graphics.Collage (..) +import Graphics.Element (..) +import Color (..) +import List (..) margin = 200 headerHeight = 70 -pageTemplate : [Element] -> String -> String -> String -> Int -> Element +pageTemplate : List Element -> String -> String -> String -> Int -> Element pageTemplate content leftHref upHref rightHref w = let line = collage w 5 [ filled black (rect (toFloat w) 1) ] --- linkArrow href code = if href == "" --- then empty --- else String.fromList [ Char.fromCode code ] |> --- toText |> --- Text.height 60 |> --- Text.link (href ++ ".html") |> --- Text.style Text.defaultStyle |> --- Text.line Text.Under |> --- centered --- leftArrow = linkArrow leftHref 8678 --- upArrow = linkArrow upHref 8679 --- rightArrow = linkArrow rightHref 8680 - upArrowForm = group [ filled black (rect 10 20) |> moveY -9 - , filled black (path [(0,15), (-15,0),(15,0)]) ] - linkArrow href form = if href == "" + linkArrow href code = if href == "" then empty - else collage headerHeight headerHeight [form] |> link (href ++ ".html") - leftArrow = linkArrow leftHref (upArrowForm |> rotate (degrees 90)) - upArrow = linkArrow upHref upArrowForm - rightArrow = linkArrow rightHref (upArrowForm |> rotate (degrees -90)) - footerContent = [markdown| -
Elm by Example. Copyright © [Grzegorz Balcerek](http://www.grzegorzbalcerek.net) 2014.
+ else String.fromList [ Char.fromCode code ] |> + Text.fromString |> + Text.height 60 |> + Text.link (href ++ ".html") |> + Text.style Text.defaultStyle |> + Text.centered + leftArrow = linkArrow leftHref 8678 + upArrow = linkArrow upHref 8679 + rightArrow = linkArrow rightHref 8680 +-- upArrowForm = group [ filled black (rect 10 20) |> moveY -9 +-- , filled black (path [(0,15), (-15,0),(15,0)]) ] +-- linkArrow href form = if href == "" +-- then empty +-- else collage headerHeight headerHeight [form] |> link (href ++ ".html") +-- leftArrow = linkArrow leftHref (upArrowForm |> rotate (degrees 90)) +-- upArrow = linkArrow upHref upArrowForm +-- rightArrow = linkArrow rightHref (upArrowForm |> rotate (degrees -90)) + footerContent = Markdown.toElement """ +
Elm by Example. Copyright © [Grzegorz Balcerek](http://www.grzegorzbalcerek.net) 2015.
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
-|] +""" footerContentSize = sizeOf footerContent in flow down [ @@ -55,8 +59,8 @@ sigVerticalLine h x y = outlined defaultLine <| polygon [(x,y-h/2),(x,y+h/2)] sigDownwardArrow x y = ngon 3 10 |> filled black |> rotate (degrees 30) |> move (x,y) signalFunctionBox f1 f2 h t1 t2 t3 w x y = group [ outlined defaultLine (rect w h) |> move (x,y) - , toForm (toText t1 |> Text.height f1 |> centered) |> moveY (h/3) |> move (x,y) - , toForm (toText t2 |> Text.height f2 |> bold |> centered) |> move (x,y) - , toForm (toText t3 |> Text.height f1 |> centered) |> moveY (-h/3) |> move (x,y) + , toForm (Text.fromString t1 |> Text.height f1 |> Text.centered) |> moveY (h/3) |> move (x,y) + , toForm (Text.fromString t2 |> Text.height f2 |> Text.bold |> Text.centered) |> move (x,y) + , toForm (Text.fromString t3 |> Text.height f1 |> Text.centered) |> moveY (-h/3) |> move (x,y) ] diff --git a/clean.bat b/clean.bat index e8dd233..bdebfb8 100644 --- a/clean.bat +++ b/clean.bat @@ -3,7 +3,4 @@ del *.exe del *.hi del *.o rmdir /q/s target -rmdir /q/s code\build -rmdir /q/s code\cache -rmdir /q/s build -rmdir /q/s cache +rmdir /q/s src diff --git a/code/Calculator.elm b/code/Calculator.elm deleted file mode 100644 index a6ac1ba..0000000 --- a/code/Calculator.elm +++ /dev/null @@ -1,35 +0,0 @@ -module Calculator where - - -import CalculatorModel (..) -import CalculatorView (..) -import Window - - -lastButtonClicked = - merges [ - button0Signal, - button1Signal, - button2Signal, - button3Signal, - button4Signal, - button5Signal, - button6Signal, - button7Signal, - button8Signal, - button9Signal, - buttonEqSignal, - buttonPlusSignal, - buttonMinusSignal, - buttonDivSignal, - buttonMultSignal, - buttonDotSignal, - buttonCSignal, - buttonCESignal - ] - - -stateSignal = foldp step initialState lastButtonClicked - - -main = lift2 view stateSignal Window.dimensions diff --git a/code/CalculatorModel.elm b/code/CalculatorModel.elm deleted file mode 100644 index 55bdef8..0000000 --- a/code/CalculatorModel.elm +++ /dev/null @@ -1,69 +0,0 @@ -module CalculatorModel where - - -import Char -import Set -import String -import Maybe (maybe) - - -data ButtonType = Regular | Large - - -buttonSize : ButtonType -> Int -buttonSize size = - case size of - Regular -> 60 - Large -> 120 - - -type CalculatorState = { - input: String, - operator: String, - number: Float - } - - -step : String -> CalculatorState -> CalculatorState -step btn state = - if | btn == "C" -> initialState - | btn == "CE" -> { state | input <- "0" } - | state.input == "" && isOper btn -> { state | operator <- btn } - | isOper btn -> { - number = calculate state.number state.operator state.input, - operator = btn, - input = "" - } - | otherwise -> - { state | - input <- - if | (state.input == "" || state.input == "0") && btn == "." -> "0." - | state.input == "" || state.input == "0" -> btn - | String.length state.input >= 18 -> state.input - | btn == "." && any (\c -> c == '.') (String.toList state.input) -> state.input - | otherwise -> state.input ++ btn } - - -initialState = { number = 0.0, input = "", operator = "" } - - -isOper : String -> Bool -isOper btn = Set.member btn (Set.fromList ["+","-","*","/","="]) - - -calculate : Float -> String -> String -> Float -calculate number op input = - let number2 = maybe 0.0 identity (String.toFloat input) - in - if | op == "+" -> number + number2 - | op == "-" -> number - number2 - | op == "*" -> number * number2 - | op == "/" -> number / number2 - | otherwise -> number2 - - -showState : CalculatorState -> String -showState {number,input} = - if input == "" - then show number - else input diff --git a/code/CalculatorView.elm b/code/CalculatorView.elm deleted file mode 100644 index 6207d11..0000000 --- a/code/CalculatorView.elm +++ /dev/null @@ -1,90 +0,0 @@ -module CalculatorView where - - -import CalculatorModel (..) -import Graphics.Input (Input, input, clickable) -import Text - - -makeButton : String -> ButtonType -> Element -makeButton label size = - let xSize = buttonSize size - buttonColor = rgb 199 235 243 - in - collage - xSize - 60 - [ - filled buttonColor <| rect (toFloat (xSize-8)) 52, - outlined { defaultLine | width <- 2, cap <- Padded } - <| rect (toFloat (xSize-8)) 52, - Text.toText label |> Text.height 30 |> Text.bold |> Text.centered |> toForm - ] - - -makeButtonAndSignal : String -> ButtonType -> (Element, Signal String) -makeButtonAndSignal label btnSize = - let buttonInput = input label - button = makeButton label btnSize - clickableButton = clickable buttonInput.handle label button - in - (clickableButton, buttonInput.signal) - - -(button0, button0Signal) = makeButtonAndSignal "0" Regular -(button1, button1Signal) = makeButtonAndSignal "1" Regular -(button2, button2Signal) = makeButtonAndSignal "2" Regular -(button3, button3Signal) = makeButtonAndSignal "3" Regular -(button4, button4Signal) = makeButtonAndSignal "4" Regular -(button5, button5Signal) = makeButtonAndSignal "5" Regular -(button6, button6Signal) = makeButtonAndSignal "6" Regular -(button7, button7Signal) = makeButtonAndSignal "7" Regular -(button8, button8Signal) = makeButtonAndSignal "8" Regular -(button9, button9Signal) = makeButtonAndSignal "9" Regular -(buttonEq, buttonEqSignal) = makeButtonAndSignal "=" Regular -(buttonPlus, buttonPlusSignal) = makeButtonAndSignal "+" Regular -(buttonMinus, buttonMinusSignal) = makeButtonAndSignal "-" Regular -(buttonDiv, buttonDivSignal) = makeButtonAndSignal "/" Regular -(buttonMult, buttonMultSignal) = makeButtonAndSignal "*" Regular -(buttonDot, buttonDotSignal) = makeButtonAndSignal "." Regular -(buttonC, buttonCSignal) = makeButtonAndSignal "C" Large -(buttonCE, buttonCESignal) = makeButtonAndSignal "CE" Large - - -display : CalculatorState -> Element -display state = - collage 240 60 [ - outlined { defaultLine | width <- 2, cap <- Padded } <| rect 232 50, - toForm (container 220 50 midRight (plainText <| showState state)) - ] - - -view : CalculatorState -> (Int, Int) -> Element -view value (w, h) = - container - w - h - middle - <| layers - [ - collage - 250 - 370 - [ - rect - 248 - 368 - |> outlined { defaultLine | width <- 3, cap <- Padded } - ], - flow - down - [ - spacer 250 5, - flow right [ spacer 5 60, display value ], - flow right [ spacer 5 60, buttonCE, buttonC ], - flow right [ spacer 5 60, buttonPlus, button1, button2, button3 ], - flow right [ spacer 5 60, buttonMinus, button4, button5, button6 ], - flow right [ spacer 5 60, buttonMult, button7, button8, button9 ], - flow right [ spacer 5 60, buttonDiv, button0, buttonDot, buttonEq ] - ] - ] diff --git a/code/CalculatorViewTest1.elm b/code/CalculatorViewTest1.elm deleted file mode 100644 index d329fa7..0000000 --- a/code/CalculatorViewTest1.elm +++ /dev/null @@ -1,5 +0,0 @@ -import CalculatorModel (..) -import CalculatorView (..) - - -main = makeButton "test" Large diff --git a/code/Circles.elm b/code/Circles.elm deleted file mode 100644 index 05650f2..0000000 --- a/code/Circles.elm +++ /dev/null @@ -1,115 +0,0 @@ -module Circles where - - -import Color -import Time (..) -import Mouse -import Random (range) -import CirclesModel (..) -import CirclesView (..) - - -clockSignal : Signal Time -clockSignal = lift fst (timestamp (fps 50)) - - -clickPositionsSignal : Signal (Int, Int) -clickPositionsSignal = sampleOn Mouse.clicks Mouse.position - - -inBoxClickPositionsSignal : Int -> Int -> Signal (Int, Int) -inBoxClickPositionsSignal w h = - let positionInBox pos = fst pos <= w && snd pos <= h - in - keepIf positionInBox (0, 0) clickPositionsSignal - - -radiusSignal : Int -> Int -> Signal Int -radiusSignal w h = - range 10 30 (inBoxClickPositionsSignal w h) - - -velocitySignal : Int -> Int -> Signal Int -velocitySignal w h = - range 10 50 (inBoxClickPositionsSignal w h) - - -colorSignal : Int -> Int -> Signal Color -colorSignal w h = - let - redSignal = range 0 220 (inBoxClickPositionsSignal w h) - greenSignal = range 0 220 (inBoxClickPositionsSignal w h) - blueSignal = range 0 220 (inBoxClickPositionsSignal w h) - in - lift3 rgb redSignal greenSignal blueSignal - - -creationTimeSignal : Int -> Int -> Signal Time -creationTimeSignal w h = - sampleOn (inBoxClickPositionsSignal w h) clockSignal - - -newCircleSpecSignal : Int -> Int -> Signal CircleSpec -newCircleSpecSignal w h = - let makeCircleSpec r xv yv c t = { radius = r, xv = xv, yv = yv, col = c, creationTime = t } - in - makeCircleSpec - <~ radiusSignal w h - ~ velocitySignal w h - ~ velocitySignal w h - ~ colorSignal w h - ~ creationTimeSignal w h - - -newCircleSignal : Int -> Int -> Signal Circle -newCircleSignal w h = - let makeCircle (x,y) spec = { position = { x = x, y = y }, circleSpec = spec } - in - makeCircle - <~ inBoxClickPositionsSignal w h - ~ newCircleSpecSignal w h - - -allCirclesSpecSignal : Int -> Int -> Signal [Circle] -allCirclesSpecSignal w h = - foldp (::) [] (newCircleSignal w h) - - -computeCoordinate : Int -> Int -> Float -> Float -> Int -computeCoordinate startingPointCoordinate boxSize velocity time = - let distance = startingPointCoordinate + round(velocity * time / 1000) - distanceMod = distance % boxSize - distanceDiv = distance // boxSize - in - if (distanceDiv % 2 == 0) - then distanceMod - else boxSize - distanceMod - - -positionedCircle : Int -> Int -> Float -> Circle -> Circle -positionedCircle w h time circle = - let {position, circleSpec} = circle - {radius, xv, yv, creationTime} = circleSpec - relativeTime = time - creationTime - boxSizeX = w - radius*2 - boxSizeY = h - radius*2 - x = radius + computeCoordinate (position.x-radius) boxSizeX (toFloat xv) relativeTime - y = radius + computeCoordinate (position.y-radius) boxSizeY (toFloat yv) relativeTime - in - { position = { x=x, y=y }, circleSpec = circleSpec } - - -positionedCircles : Int -> Int -> Float -> [Circle] -> [Circle] -positionedCircles w h time circles = - map (positionedCircle w h time) circles - - -circlesSignal : Int -> Int -> Signal [Circle] -circlesSignal w h = positionedCircles w h <~ clockSignal - ~ allCirclesSpecSignal w h - - -main = - let main' w h = view w h <~ circlesSignal w h - in - main' 400 400 diff --git a/code/CirclesModel.elm b/code/CirclesModel.elm deleted file mode 100644 index bee2290..0000000 --- a/code/CirclesModel.elm +++ /dev/null @@ -1,19 +0,0 @@ -module CirclesModel where - - -type Position = { x: Int, y: Int } - - -type CircleSpec = { - radius: Int, - xv: Int, - yv: Int, - col: Color, - creationTime: Time - } - - -type Circle = { - position: Position, - circleSpec: CircleSpec - } diff --git a/code/CirclesTest.elm b/code/CirclesTest.elm deleted file mode 100644 index 9f6f5cc..0000000 --- a/code/CirclesTest.elm +++ /dev/null @@ -1,29 +0,0 @@ -module CirclesTest where - -import Circles (..) - -show a b c d e f g h i j k = flow down - [ asText a - , asText b - , asText c - , asText d - , asText e - , asText f - , asText g - , asText h - , asText i - , asText j - , asText k - ] - -main = show <~ clockSignal - ~ clickPositionsSignal - ~ inBoxClickPositionsSignal 400 400 - ~ radiusSignal 400 400 - ~ velocitySignal 400 400 - ~ colorSignal 400 400 - ~ creationTimeSignal 400 400 - ~ newCircleSpecSignal 400 400 - ~ newCircleSignal 400 400 - ~ allCirclesSpecSignal 400 400 - ~ circlesSignal 400 400 \ No newline at end of file diff --git a/code/CirclesView.elm b/code/CirclesView.elm deleted file mode 100644 index af3782b..0000000 --- a/code/CirclesView.elm +++ /dev/null @@ -1,35 +0,0 @@ -module CirclesView where - - -import CirclesModel (Position, CircleSpec, Circle) - - -boundingBox w h = - collage - w - h - [ - outlined (solid black) <| rect (toFloat w) (toFloat h), - outlined (solid black) <| rect (toFloat (w-2)) (toFloat (h-2)) - ] - - -drawCircle w h {position, circleSpec} = - filled circleSpec.col (circle (toFloat circleSpec.radius)) - |> move (toFloat position.x - (toFloat w)/2, (toFloat h)/2 - toFloat position.y) - - -drawCircles w h circles = collage w h (map (drawCircle w h) circles) - - -view w h circles = layers [ boundingBox w h, drawCircles w h circles ] - - -main = - view - 400 - 400 - [ - { circleSpec = { col = red, radius = 26 } , position = { x = 0, y = 0 } }, - { circleSpec = { col = green, radius = 43 } , position = { x = 200, y = 200 } } - ] diff --git a/code/DelayedCircles.elm b/code/DelayedCircles.elm deleted file mode 100644 index 3db0806..0000000 --- a/code/DelayedCircles.elm +++ /dev/null @@ -1,10 +0,0 @@ -import Window -import Fibonacci (fibonacci) -import DrawCircles (drawCircles) -import DelayedMousePositions (delayedMousePositions) - - -main = - drawCircles - <~ delayedMousePositions (fibonacci 8 |> tail |> reverse) - ~ Window.dimensions diff --git a/code/DelayedMousePositions.elm b/code/DelayedMousePositions.elm deleted file mode 100644 index 59f8fcd..0000000 --- a/code/DelayedMousePositions.elm +++ /dev/null @@ -1,26 +0,0 @@ -module DelayedMousePositions where - - -import Mouse -import Window - - -delayedMousePositions : [Int] -> Signal [(Int, (Int, Int))] -delayedMousePositions rs = - let adjust (w, h) (x, y) = (x-w//2,h//2-y) - n = length rs - position = adjust <~ Window.dimensions ~ Mouse.position - positions = repeat n position -- [Signal (Int, Int)] - delayedPositions = -- [Signal (Int, (Int, Int))] - zipWith - (\r pos -> - let delayedPosition = delay (toFloat r*100) pos - in - lift (\pos -> (r,pos)) delayedPosition) - rs - positions - in - combine delayedPositions - - -main = asText <~ delayedMousePositions [0,10,25] diff --git a/code/DrawCircles.elm b/code/DrawCircles.elm deleted file mode 100644 index bb770b3..0000000 --- a/code/DrawCircles.elm +++ /dev/null @@ -1,33 +0,0 @@ -module DrawCircles where - - -import Array as A - - -color : Int -> Color -color n = - let colors = - A.fromList [ green, red, blue, yellow, brown, purple, orange ] - in - A.getOrElse black (n % (A.length colors)) colors - - -circleForm : (Int, (Int, Int)) -> Form -circleForm (r, (x, y)) = - circle (toFloat r*5) - |> filled (color r) - |> move (toFloat x,toFloat y) - - -drawCircles : [(Int, (Int, Int))] -> (Int, Int) -> Element -drawCircles d (w, h) = collage w h <| map circleForm d - -main = - drawCircles [ - (3, (-200, 0)), - (4, (-100, 0)), - (5, (0, 0)), - (7, (100, 0)), - (9, (200, 0)) - ] - (600, 400) diff --git a/code/Eyes.elm b/code/Eyes.elm deleted file mode 100644 index 46c91d7..0000000 --- a/code/Eyes.elm +++ /dev/null @@ -1,10 +0,0 @@ -import Mouse -import Window -import EyesView (..) -import EyesModel (..) - - -main = lift2 eyes Window.dimensions Mouse.position - - -eyes (w,h) (x,y) = eyesView (w,h) (pupilsCoordinates (w,h) (x,y)) diff --git a/code/EyesModel.elm b/code/EyesModel.elm deleted file mode 100644 index 700ec1a..0000000 --- a/code/EyesModel.elm +++ /dev/null @@ -1,40 +0,0 @@ -module EyesModel where - - -calculateP : (Float, Float) -> (Float, Float) -> (Float, Float) -> (Float, Float) -calculateP (xR, yR) (xC, yC) (xM, yM) = - let (xA, yA) = - if (yM/xM < yC/xC) - then (xC,xC*yM/xM) - else (xM*yC/yM,yC) - xB = xR*yR / sqrt (yR^2+(xR*yM/xM)^2) - yB = xR*yR / sqrt (xR^2+(yR*xM/yM)^2) - xP = xB*xM/xA - yP = yB*yM/yA - in - (xP,yP) - - -pupilsCoordinates : (Int, Int) -> (Int, Int) -> (Float, Float, Float, Float) -pupilsCoordinates (w, h) (x, y) = - let xC = (toFloat w)/4 - yC = (toFloat h)/2 - xM = toFloat x - yM = (toFloat (h-y)) - yC - xR = xC*9/10 - yR = yC*9/10 - sign x = x / (abs x) - (xPr,yPr) = - if xM >= 3*xC - then calculateP (xR,yR) (xC,yC) (xM-3*xC,yM) - |> \(xP,yP) -> (xP+xC, yP * sign yM) - else calculateP (xR,yR) (3*xC,yC) (3*xC-xM,yM) - |> \(xP,yP) -> (xC-xP, yP * sign yM) - (xPl,yPl) = - if xM >= xC - then calculateP (xR,yR) (3*xC,yC) (xM-xC,yM) - |> \(xP,yP) -> (xP-xC, yP * sign yM) - else calculateP (xR,yR) (xC,yC) (-xM+xC,yM) - |> \(xP,yP) -> (-xP-xC, yP * sign yM) - in - (xPl,yPl,xPr,yPr) diff --git a/code/EyesView.elm b/code/EyesView.elm deleted file mode 100644 index cc0ffcf..0000000 --- a/code/EyesView.elm +++ /dev/null @@ -1,37 +0,0 @@ -{- - This module contains functions which draw - the eyes. --} -module EyesView where - - -eyeBorder : Float -> Float -> Form -eyeBorder w h = - group [ - filled black <| oval w h, - filled white <| oval (w*9/10) (h*9/10) - ] - - -eyePupil : Float -> Float -> Form -eyePupil w h = filled black <| oval w h - - -eyesView : (Int, Int) -> (Float, Float, Float, Float) -> Element -eyesView (w, h) (xPl, yPl, xPr, yPr) = - let xC = (toFloat w) / 4 - yC = (toFloat h) / 2 - in - collage - w - h - [ - eyeBorder (2*xC) (2*yC) |> moveX (-xC), - eyeBorder (2*xC) (2*yC) |> moveX xC, - eyePupil (xC/5) (yC/5) |> move (xPl,yPl), - eyePupil (xC/5) (yC/5) |> move (xPr,yPr) - ] - - --- test -main = eyesView (700, 500) (-50, -50, 100, 100) diff --git a/code/Fibonacci.elm b/code/Fibonacci.elm deleted file mode 100644 index 8cbcc65..0000000 --- a/code/Fibonacci.elm +++ /dev/null @@ -1,15 +0,0 @@ -module Fibonacci where - - -fibonacci : Int -> [Int] -fibonacci n = - let fibonacci' n acc = - if n <= 2 - then acc - else fibonacci' (n-1) ((head acc + (tail >> head) acc) :: acc) - in - fibonacci' n [1,1] |> reverse - - -fibonacciWithIndexes : Int -> [(Int,Int)] -fibonacciWithIndexes n = zip [0..n] (fibonacci n) diff --git a/code/FibonacciBars.elm b/code/FibonacciBars.elm deleted file mode 100644 index c5d9ef7..0000000 --- a/code/FibonacciBars.elm +++ /dev/null @@ -1,20 +0,0 @@ -module FibonacciBars where - - -import Fibonacci (fibonacci, fibonacciWithIndexes) - - -color n = - let colors = [ red, orange, yellow, green, blue, purple, brown ] - in - drop (n % (length colors)) colors |> head - - -bar (index, n) = - flow right [ - collage (n*20) 20 [ filled (color index) (rect (toFloat n * 20) 20) ], - asText n - ] - - -main = flow down <| map bar (fibonacciWithIndexes 10) diff --git a/code/Foldpm.elm b/code/Foldpm.elm deleted file mode 100644 index be7b62d..0000000 --- a/code/Foldpm.elm +++ /dev/null @@ -1,25 +0,0 @@ -module Foldpm where - - -when : Bool -> a -> Maybe a -when p result = if p then Just result else Nothing - - -compose : [a -> b -> Maybe b] -> (a -> b -> Maybe b) -compose steps = - case steps of - [] -> \_ _ -> Nothing - f::fs -> \a b -> - case f a b of - Nothing -> (compose fs) a b - Just x -> Just x - - -foldpm : (a -> b -> Maybe b) -> b -> Signal a -> Signal b -foldpm stepm b sa = - let step event state = - case stepm event state of - Nothing -> state - Just x -> x - in - foldp step b sa diff --git a/code/GenericList.elm b/code/GenericList.elm deleted file mode 100644 index 1a01e8b..0000000 --- a/code/GenericList.elm +++ /dev/null @@ -1,8 +0,0 @@ -module GenericList where - -data GenericList a = Nil | Cons a (GenericList a) - -listSize : GenericList a -> Int -listSize lst = case lst of - Nil -> 0 - Cons _ tail -> 1 + listSize tail diff --git a/code/HelloWorld1.elm b/code/HelloWorld1.elm deleted file mode 100644 index 7672c7d..0000000 --- a/code/HelloWorld1.elm +++ /dev/null @@ -1 +0,0 @@ -main = plainText "Hello World" diff --git a/code/HelloWorld2.elm b/code/HelloWorld2.elm deleted file mode 100644 index 4362bdc..0000000 --- a/code/HelloWorld2.elm +++ /dev/null @@ -1,11 +0,0 @@ -import Text - - -main : Element -main = - Text.toText "Hello World" - |> Text.color blue - |> Text.italic - |> Text.bold - |> Text.height 60 - |> Text.leftAligned diff --git a/code/HelloWorld3.elm b/code/HelloWorld3.elm deleted file mode 100644 index 0505537..0000000 --- a/code/HelloWorld3.elm +++ /dev/null @@ -1,15 +0,0 @@ -import Text as T - - -makeBlue : Text -> Text -makeBlue = T.color blue - - -main : Element -main = - T.toText "Hello World" - |> makeBlue - |> T.italic - |> T.bold - |> T.height 60 - |> T.leftAligned diff --git a/code/HelloWorld4.elm b/code/HelloWorld4.elm deleted file mode 100644 index 992adb5..0000000 --- a/code/HelloWorld4.elm +++ /dev/null @@ -1,9 +0,0 @@ -main = [markdown| - -# Hello World - -This is the output of the *HelloWorld4.elm* program. - ---- - -|] diff --git a/code/KeyboardSignals1.elm b/code/KeyboardSignals1.elm deleted file mode 100644 index 439aa85..0000000 --- a/code/KeyboardSignals1.elm +++ /dev/null @@ -1,4 +0,0 @@ -import Keyboard - - -main = lift asText Keyboard.keysDown diff --git a/code/KeyboardSignals2.elm b/code/KeyboardSignals2.elm deleted file mode 100644 index 5923597..0000000 --- a/code/KeyboardSignals2.elm +++ /dev/null @@ -1,4 +0,0 @@ -import Keyboard - - -main = lift asText Keyboard.lastPressed diff --git a/code/KeyboardSignals3.elm b/code/KeyboardSignals3.elm deleted file mode 100644 index 8b3782d..0000000 --- a/code/KeyboardSignals3.elm +++ /dev/null @@ -1,5 +0,0 @@ -import Keyboard -import Char - - -main = lift asText (Keyboard.isDown (Char.toCode 'A')) diff --git a/code/KeyboardSignals4.elm b/code/KeyboardSignals4.elm deleted file mode 100644 index fe333c0..0000000 --- a/code/KeyboardSignals4.elm +++ /dev/null @@ -1,4 +0,0 @@ -import Keyboard - - -main = lift asText (Keyboard.directions 81 65 79 80) diff --git a/code/MouseSignals1.elm b/code/MouseSignals1.elm deleted file mode 100644 index 1e19351..0000000 --- a/code/MouseSignals1.elm +++ /dev/null @@ -1,4 +0,0 @@ -import Mouse - - -main = lift asText Mouse.x diff --git a/code/MouseSignals2.elm b/code/MouseSignals2.elm deleted file mode 100644 index 7c1bec0..0000000 --- a/code/MouseSignals2.elm +++ /dev/null @@ -1,8 +0,0 @@ -import Mouse - - -combine : a -> b -> Element -combine a b = asText (a,b) - - -main = lift2 combine Mouse.x Mouse.y diff --git a/code/MouseSignals3.elm b/code/MouseSignals3.elm deleted file mode 100644 index f22b66d..0000000 --- a/code/MouseSignals3.elm +++ /dev/null @@ -1,31 +0,0 @@ -import Mouse - - -showsignals a b c d e f g h = - flow - down - <| - map - plainText - [ - "Mouse.position: " ++ show a, - "Mouse.x: " ++ show b, - "Mouse.y: " ++ show c, - "Mouse.clicks: " ++ show d, - "Mouse.isDown: " ++ show e, - "count Mouse.isDown: " ++ show f, - "sampleOn Mouse.clicks Mouse.position: " ++ show g, - "sampleOn Mouse.isDown Mouse.position: " ++ show h - ] - - -main = - showsignals - <~ Mouse.position - ~ Mouse.x - ~ Mouse.y - ~ Mouse.clicks - ~ Mouse.isDown - ~ count Mouse.isDown - ~ sampleOn Mouse.clicks Mouse.position - ~ sampleOn Mouse.isDown Mouse.position diff --git a/code/Paddle.elm b/code/Paddle.elm deleted file mode 100644 index 4ffb61d..0000000 --- a/code/Paddle.elm +++ /dev/null @@ -1,120 +0,0 @@ -module Paddle where - - -import Window -import Text as T -import Keyboard - - -borders : Form -borders = - group [ - rect 440 440 |> filled blue, - rect 400 420 |> filled white |> moveY -10 - ] - - -paddle : Float -> Form -paddle x = - rect (toFloat 100) (toFloat 20) - |> filled green - |> move (x,-210) - - -ball : Float -> Float -> Form -ball x y = filled orange (circle 10) |> move (x,y) - - -gameOver : Element -gameOver = - T.toText "Game Over" - |> T.color red - |> T.bold - |> T.height 60 - |> T.centered - |> container 440 440 middle - - -type State = - { - x: Float, - y: Float, - dx: Float, - dy: Float, - paddlex: Float, - paddledx: Float, - isOver: Bool - } - - -initialState : State -initialState = - { - x = 0, - y = 0, - dx = 0.14, - dy = 0.2, - paddlex = 0, - paddledx = 0, - isOver = False - } - - -view : State -> Element -view s = - layers [ - collage 440 440 [ - borders, - paddle s.paddlex, - ball s.x s.y - ], - if s.isOver then gameOver else empty - ] - - -data Event = Tick Time | PaddleDx Int - - -clockSignal : Signal Event -clockSignal = lift Tick <| fps 100 - - -keyboardSignal : Signal Event -keyboardSignal = lift (.x >> PaddleDx) Keyboard.arrows - - -eventSignal : Signal Event -eventSignal = merge clockSignal keyboardSignal - - -gameSignal : Signal State -gameSignal = foldp step initialState <| eventSignal - - -step : Event -> State -> State -step event s = - if s.isOver - then s - else case event of - Tick time -> - { s | - x <- s.x + s.dx*time, - y <- s.y + s.dy*time, - dx <- if (s.x >= 190 && s.dx > 0) || - (s.x <= -190 && s.dx < 0) - then -1*s.dx - else s.dx, - dy <- if (s.y >= 190 && s.dy > 0) || - (s.y <= -190 && s.dy < 0 && - s.x >= s.paddlex - 50 && - s.x <= s.paddlex + 50) - then -1*s.dy - else s.dy, - paddlex <- ((s.paddlex + s.paddledx*time) `max` -150) `min` 150, - isOver <- s.y < -200 - } - PaddleDx dx -> { s | paddledx <- 0.1 * toFloat dx } - - -main : Signal Element -main = lift view gameSignal diff --git a/code/RandomSignals1.elm b/code/RandomSignals1.elm deleted file mode 100644 index d52fe47..0000000 --- a/code/RandomSignals1.elm +++ /dev/null @@ -1,5 +0,0 @@ -import Random -import Mouse - - -main = asText <~ Random.float Mouse.clicks diff --git a/code/RandomSignals2.elm b/code/RandomSignals2.elm deleted file mode 100644 index a4b3ece..0000000 --- a/code/RandomSignals2.elm +++ /dev/null @@ -1,5 +0,0 @@ -import Random -import Mouse - - -main = asText <~ Random.range 10 20 Mouse.clicks diff --git a/code/RandomSignals3.elm b/code/RandomSignals3.elm deleted file mode 100644 index c6e09ef..0000000 --- a/code/RandomSignals3.elm +++ /dev/null @@ -1,8 +0,0 @@ -import Random -import Mouse - - -lengthSignal = lift (\x -> 1 + x // 100) Mouse.x - - -main = asText <~ Random.floatList lengthSignal diff --git a/code/Snake.elm b/code/Snake.elm deleted file mode 100644 index 98f3ce9..0000000 --- a/code/Snake.elm +++ /dev/null @@ -1,9 +0,0 @@ -module Snake where - - -import SnakeState (..) -import SnakeView (..) - - -main : Signal Element -main = view <~ stateSignal diff --git a/code/SnakeModel.elm b/code/SnakeModel.elm deleted file mode 100644 index ab4980c..0000000 --- a/code/SnakeModel.elm +++ /dev/null @@ -1,100 +0,0 @@ -module SnakeModel where - - -import Set -import Maybe (maybe) - - -type Position = - { x: Int - , y: Int - } - - -type Delta = - { dx: Int - , dy: Int - } - - -type Snake = - { front: [Position] - , back: [Position] - } - - -type SnakeState = - { snake: Snake - , delta: Delta - , food: Maybe Position - , ticks: Int - , gameOver: Bool - } - - -initialSnake = - { front = [{x = 0, y = 0}, {x = 0, y = -1}, {x = 0, y = -2}, {x = 0, y = -3}] - , back = [] - } - - -initialDelta = { dx = 0, dy = 1 } - - -initialFood = Nothing - - -initialState = - { snake = initialSnake - , delta = initialDelta - , food = initialFood - , ticks = 0 - , gameOver = False - } - - -data Event = Tick Position | Direction Delta | NewGame | Ignore - - -boardSize = 15 - - -boxSize = boardSize + boardSize + 1 - - -velocity = 5 - - -nextPosition : Snake -> Delta -> Position -nextPosition snake {dx,dy} = - let headPosition = head snake.front - in { x = headPosition.x + dx, y = headPosition.y + dy } - - -moveSnakeForward : Snake -> Delta -> Maybe Position -> Snake -moveSnakeForward snake delta food = - let next = nextPosition snake delta - tailFunction = maybe tail (\f -> if next == f then identity else tail) food - in - if isEmpty snake.back - then { front = [next] - , back = (tailFunction << reverse) snake.front } - else { front = next :: snake.front - , back = tailFunction snake.back } - - -isInSnake : Snake -> Position -> Bool -isInSnake snake position = - let frontSet = Set.fromList <| map show snake.front - backSet = Set.fromList <| map show snake.back - in - Set.member (show position) frontSet || Set.member (show position) backSet - - -collision : SnakeState -> Bool -collision state = - let next = nextPosition state.snake state.delta - in - if abs next.x > boardSize || abs next.y > boardSize || isInSnake state.snake next - then True - else False diff --git a/code/SnakeRevisited.elm b/code/SnakeRevisited.elm deleted file mode 100644 index e57f99c..0000000 --- a/code/SnakeRevisited.elm +++ /dev/null @@ -1,7 +0,0 @@ -module SnakeRevisited where - -import SnakeStateRevisited (..) -import SnakeView (..) - -main : Signal Element -main = view <~ stateSignal diff --git a/code/SnakeSignals.elm b/code/SnakeSignals.elm deleted file mode 100644 index 32a915d..0000000 --- a/code/SnakeSignals.elm +++ /dev/null @@ -1,45 +0,0 @@ -module SnakeSignals where - - -import SnakeModel (..) -import SnakeView (..) -import Keyboard -import Random -import Char - - -timeSignal : Signal Float -timeSignal = fps 50 - - -xSignal : Signal Int -xSignal = Random.range -boardSize boardSize timeSignal - - -ySignal : Signal Int -ySignal = Random.range -boardSize boardSize timeSignal - - -tickSignal : Signal Event -tickSignal = - let combine x y = Tick { x = x, y = y } - in combine <~ xSignal ~ ySignal - - -directionSignal : Signal Event -directionSignal = - let arrowsToDelta {x,y} = - if | x == 0 && y == 0 -> Ignore - | x /= 0 -> Direction { dx = x, dy = 0 } - | otherwise -> Direction { dx = 0, dy = y } - in - lift arrowsToDelta Keyboard.arrows - - -newGameSignal : Signal Event -newGameSignal = - always NewGame <~ (keepIf identity False <| Keyboard.isDown (Char.toCode 'N')) - - -eventSignal : Signal Event -eventSignal = merges [tickSignal, directionSignal, newGameSignal] diff --git a/code/SnakeState.elm b/code/SnakeState.elm deleted file mode 100644 index 9728903..0000000 --- a/code/SnakeState.elm +++ /dev/null @@ -1,47 +0,0 @@ -module SnakeState where - - -import SnakeModel (..) -import SnakeSignals (..) - - -stateSignal : Signal SnakeState -stateSignal = foldp step initialState eventSignal - - -step : Event -> SnakeState -> SnakeState -step event state = - case (event,state.gameOver) of - (NewGame,_) -> initialState - (_,True) -> state - (Direction newDelta,_) -> - { state | delta <- if abs newDelta.dx /= abs state.delta.dx - then newDelta - else state.delta } - (Tick newFood, _) -> - let state1 = if state.ticks % velocity == 0 - then { state | gameOver <- collision state } - else state - in if state1.gameOver - then state1 - else let state2 = { state1 - | snake <- - if state1.ticks % velocity == 0 - then moveSnakeForward state1.snake state1.delta state1.food - else state1.snake - } - state3 = { state2 - | food <- - case state2.food of - Just f -> - if state2.ticks % velocity == 0 && - head state2.snake.front == f - then Nothing - else state2.food - Nothing -> - if isInSnake state2.snake newFood - then Nothing - else Just newFood - } - in { state3 | ticks <- state3.ticks + 1 } - (Ignore,_) -> state diff --git a/code/SnakeStateRevisited.elm b/code/SnakeStateRevisited.elm deleted file mode 100644 index e391062..0000000 --- a/code/SnakeStateRevisited.elm +++ /dev/null @@ -1,66 +0,0 @@ -module SnakeStateRevisited where - - -import SnakeModel (..) -import SnakeSignals (..) -import Foldpm -import Foldpm (..) - -handleNewGame : Event -> SnakeState -> Maybe SnakeState -handleNewGame event _ = when (event == NewGame) initialState - - -handleGameOver : Event -> SnakeState -> Maybe SnakeState -handleGameOver _ state = when (state.gameOver) state - - -handleDirection : Event -> SnakeState -> Maybe SnakeState -handleDirection event state = - case event of - Direction newDelta -> - Just { state | delta <- if abs newDelta.dx /= abs state.delta.dx - then newDelta - else state.delta } - _ -> Nothing - - -handleTick : Event -> SnakeState -> Maybe SnakeState -handleTick event state = - case event of - Tick newFood -> - let state1 = if state.ticks % velocity == 0 - then { state | gameOver <- collision state } - else state - in - if state1.gameOver - then Just state1 - else let state2 = { state1 - | snake <- - if state1.ticks % velocity == 0 - then moveSnakeForward state1.snake state1.delta state1.food - else state1.snake - } - state3 = { state2 - | food <- - case state2.food of - Just f -> - if state2.ticks % velocity == 0 && - head state2.snake.front == f - then Nothing - else state2.food - Nothing -> - if isInSnake state2.snake newFood - then Nothing - else Just newFood - } - in - Just { state3 | ticks <- state3.ticks + 1 } - _ -> Nothing - - -step : Event -> SnakeState -> Maybe SnakeState -step = Foldpm.compose [handleNewGame, handleGameOver, handleDirection, handleTick] - - -stateSignal : Signal SnakeState -stateSignal = foldpm step initialState eventSignal diff --git a/code/SnakeView.elm b/code/SnakeView.elm deleted file mode 100644 index 6bb6d06..0000000 --- a/code/SnakeView.elm +++ /dev/null @@ -1,69 +0,0 @@ -module SnakeView where - - -import Text -import Maybe (maybe) -import SnakeModel (..) -import SnakeModel - - -type Position = SnakeModel.Position - - -unit = 15 - - -innerSize = unit * boxSize - - -outerSize = unit * (boxSize+1) - - -box = - collage outerSize outerSize [ - filled black <| rect outerSize outerSize, - filled white <| rect innerSize innerSize ] - - -drawPosition : Color -> Position -> Form -drawPosition color position = - filled color (rect unit unit) |> - move (toFloat (unit*position.x), toFloat (unit*position.y)) - - -drawPositions : Color -> [Position] -> Element -drawPositions color positions = - collage outerSize outerSize (map (drawPosition color) positions) - - -drawFood : Position -> Element -drawFood position = drawPositions green [position] - - -gameOver : Element -gameOver = - Text.toText "Game Over" - |> Text.color red - |> Text.bold - |> Text.height 60 - |> Text.centered - |> container outerSize outerSize middle - - -instructions : Element -instructions = - plainText "Press the arrows to change the snake move direction.\nPress N to start a new game." - |> container outerSize (outerSize+3*unit) midBottom - - -view state = - layers [ box - , instructions - , drawPositions blue state.snake.front - , drawPositions blue state.snake.back - , maybe empty drawFood state.food - , if state.gameOver then gameOver else empty - ] - - -main = view initialState diff --git a/code/TicTacToe.elm b/code/TicTacToe.elm deleted file mode 100644 index ba4726f..0000000 --- a/code/TicTacToe.elm +++ /dev/null @@ -1,42 +0,0 @@ -module TicTacToe where - - -import TicTacToeModel (..) -import TicTacToeView (..) -import Mouse - - -clickSignal : Signal (Int,Int) -clickSignal = sampleOn Mouse.clicks Mouse.position - - -newGameButtonSignal : Signal () -newGameButtonSignal = newGameInput.signal - - -undoButtonSignal : Signal () -undoButtonSignal = undoInput.signal - - -newGameSignal : Signal (GameState -> GameState) -newGameSignal = always (always initialState) <~ newGameButtonSignal - - -undoSignal : Signal (GameState -> GameState) -undoSignal = always undoMoves <~ undoButtonSignal - - -moveSignal : Signal (GameState -> GameState) -moveSignal = processClick <~ clickSignal - - -inputSignal : Signal (GameState -> GameState) -inputSignal = merges [ moveSignal, newGameSignal, undoSignal ] - - -gameStateSignal : Signal GameState -gameStateSignal = foldp (<|) initialState inputSignal - - -main : Signal Element -main = lift view gameStateSignal diff --git a/code/TicTacToeModel.elm b/code/TicTacToeModel.elm deleted file mode 100644 index cd365e1..0000000 --- a/code/TicTacToeModel.elm +++ /dev/null @@ -1,131 +0,0 @@ -module TicTacToeModel where - - -data Player = O | X - - -data Result = Draw | Winner Player - - -type Field = { col: Int, row: Int } - - -type Move = (Field,Player) - - -type Moves = [Move] - - -data GameState = FinishedGame Result Moves - | NotFinishedGame Player Moves - - -other : Player -> Player -other player = - case player of - X -> O - O -> X - - -moves : GameState -> Moves -moves state = - case state of - (NotFinishedGame _ moves) -> moves - (FinishedGame _ moves) -> moves - - -initialState : GameState -initialState = NotFinishedGame X [] - - -isFieldEmpty : Moves -> Field -> Bool -isFieldEmpty moves field = all (\move -> not (fst move == field)) moves - - -subsequences : [a] -> [[a]] -subsequences lst = - case lst of - [] -> [[]] - h::t -> let st = subsequences t - in - st ++ map (\x -> h::x) st - - -playerWon : Player -> Moves -> Bool -playerWon player = - let fieldsAreInLine fields = - all (\{col} -> col == 1) fields || - all (\{col} -> col == 2) fields || - all (\{col} -> col == 3) fields || - all (\{row} -> row == 1) fields || - all (\{row} -> row == 2) fields || - all (\{row} -> row == 3) fields || - all (\{col,row} -> col == row) fields || - all (\{col,row} -> col + row == 4) fields - in subsequences - >> filter (\x -> length x == 3) - >> filter (all (\(_,p) -> p == player)) - >> map (map fst) - >> filter fieldsAreInLine - >> isEmpty - >> not - - -addMove : Move -> GameState -> GameState -addMove move state = - let newMoves = move :: (moves state) - player = snd move - in - if | playerWon player newMoves -> FinishedGame (Winner player) newMoves - | length newMoves == 9 -> FinishedGame Draw newMoves - | otherwise -> NotFinishedGame (other player) newMoves - - -makeComputerMove : GameState -> GameState -makeComputerMove state = case state of - FinishedGame _ _ -> state - NotFinishedGame player moves -> - let fields = [ - {col=2,row=2}, - {col=1,row=1}, - {col=3,row=3}, - {col=1,row=3}, - {col=3,row=1}, - {col=1,row=2}, - {col=2,row=1}, - {col=2,row=3}, - {col=3,row=2} - ] - newField = head <| filter (isFieldEmpty moves) fields - newMoves = (newField, player) :: moves - in - addMove (newField, player) state - - -makeHumanAndComputerMove : Field -> GameState -> GameState -makeHumanAndComputerMove field state = - case state of - FinishedGame _ _ -> state - NotFinishedGame player moves -> - if isFieldEmpty moves field - then addMove (field,player) state |> makeComputerMove - else state - - -undoMoves : GameState -> GameState -undoMoves state = - case state of - NotFinishedGame _ [] -> state - NotFinishedGame player moves -> - NotFinishedGame player (moves |> tail |> tail) - FinishedGame _ _ -> state - - -processClick : (Int,Int) -> GameState -> GameState -processClick (x,y) = - let col = 1 + x // 100 - row = 1 + y // 100 - in - if col >= 1 && col <= 3 && row >= 1 && row <= 3 - then makeHumanAndComputerMove {col=col,row=row} - else identity diff --git a/code/TicTacToeView.elm b/code/TicTacToeView.elm deleted file mode 100644 index 441791d..0000000 --- a/code/TicTacToeView.elm +++ /dev/null @@ -1,68 +0,0 @@ -module TicTacToeView where - - -import TicTacToeModel (..) -import Graphics.Input (..) - - -drawLines : Element -drawLines = - collage 300 300 [ - filled black (rect 3 300) |> move (-50,0), - filled black (rect 3 300) |> move (50,0), - filled black (rect 300 3) |> move (0,-50), - filled black (rect 300 3) |> move (0,50) - ] - - -drawMoves : GameState -> Element -drawMoves state = - let moveSign player = - group (case player of - X -> - let xline = filled black (rect 5 64) - in [ rotate (degrees 45) xline - , rotate (degrees 135) xline - ] - O -> - [ filled black <| circle 30 - , filled white <| circle 25 - ] - ) - playerMove ({col,row}, player) = - moveSign player |> move (toFloat <| 100*col-200,toFloat <| -100*row+200) - in - collage 300 300 (map playerMove <| moves state) - - -stateDescription : GameState -> String -stateDescription state = - case state of - FinishedGame Draw _ -> "Game Over. Draw" - FinishedGame (Winner p) _ -> "Game Over. Winner: " ++ show p - NotFinishedGame p _ -> "Next move: " ++ show p - - -newGameInput : Input () -newGameInput = input () - - -newGameButton : Element -newGameButton = button newGameInput.handle () "New Game" - - -undoInput : Input () -undoInput = input () - - -undoButton : Element -undoButton = button undoInput.handle () "Undo" - - -view : GameState -> Element -view state = - flow down [ - layers [drawLines, drawMoves state], - container 300 60 middle <| plainText <| stateDescription state, - container 300 60 middle <| flow right [undoButton, spacer 20 20, newGameButton] - ] diff --git a/code/TimeSignals.elm b/code/TimeSignals.elm deleted file mode 100644 index c3e6b31..0000000 --- a/code/TimeSignals.elm +++ /dev/null @@ -1,27 +0,0 @@ -import Time -import Mouse - - -showsignals a b c d e f = - flow - down - <| - map - plainText - [ - "every (5*second): " ++ show a, - "since (2*second) Mouse.clicks: " ++ show b, - "timestamp Mouse.isDown: " ++ show c, - "delay second Mouse.position: " ++ show d, - "fps 200: " ++ show e, - "fpsWhen 200 Mouse.isDown: " ++ show f - ] - - -main = showsignals - <~ every (5*second) - ~ since (2*second) Mouse.clicks - ~ timestamp Mouse.isDown - ~ delay second Mouse.position - ~ fps 200 - ~ fpsWhen 200 Mouse.isDown diff --git a/code/WindowSignals1.elm b/code/WindowSignals1.elm deleted file mode 100644 index 8ea88bc..0000000 --- a/code/WindowSignals1.elm +++ /dev/null @@ -1,24 +0,0 @@ -module WindowSignals1 where - - -import Window - - -showsignals a b c = - flow - down - <| - map - plainText - [ - "Window.dimensions: " ++ show a, - "Window.width: " ++ show b, - "Window.height: " ++ show c - ] - - -main = - showsignals - <~ Window.dimensions - ~ Window.width - ~ Window.height diff --git a/code/WindowSignals2.html b/code/WindowSignals2.html deleted file mode 100644 index 858138c..0000000 --- a/code/WindowSignals2.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - -
- - - diff --git a/elm-package.json b/elm-package.json new file mode 100644 index 0000000..57725f4 --- /dev/null +++ b/elm-package.json @@ -0,0 +1,14 @@ +{ + "version": "1.0.0", + "summary": "Elm by Example", + "repository": "https://github.com/grzegorzbalcerek/elm-by-example.git", + "license": "Creative Commons Attribution-ShareAlike 4.0 International License", + "source-directories": [ + "src" + ], + "exposed-modules": [], + "dependencies": { + "elm-lang/core": "1.1.0 <= v < 2.0.0", + "evancz/elm-markdown": "1.1.1 <= v < 2.0.0" + } +} diff --git a/examples.bat b/examples.bat new file mode 100644 index 0000000..90f8388 --- /dev/null +++ b/examples.bat @@ -0,0 +1,17 @@ +@echo off + +cd ../elm-by-example +extractCode Chapter1HelloWorld.elm ../ElmByExample/src +extractCode Chapter2FibonacciBars.elm ../ElmByExample/src +extractCode Chapter3MouseSignals.elm ../ElmByExample/src +extractCode Chapter4WindowSignals.elm ../ElmByExample/src +extractCode Chapter5Eyes.elm ../ElmByExample/src +extractCode Chapter6TimeSignals.elm ../ElmByExample/src +extractCode Chapter7DelayedCircles.elm ../ElmByExample/src +extractCode Chapter8Circles.elm ../ElmByExample/src +extractCode Chapter9Calculator.elm ../ElmByExample/src +extractCode Chapter10KeyboardSignals.elm ../ElmByExample/src +extractCode Chapter11Paddle.elm ../ElmByExample/src +extractCode Chapter12TicTacToe.elm ../ElmByExample/src +extractCode Chapter13Snake.elm ../ElmByExample/src +extractCode Chapter14SnakeRevisited.elm ../ElmByExample/src diff --git a/extractCode.hs b/extractCode.hs index 1159a93..22e7eb1 100644 --- a/extractCode.hs +++ b/extractCode.hs @@ -1,9 +1,14 @@ +-- -*- coding: utf-8; -*- + import System.Environment(getArgs) import qualified Data.Map.Strict as M import Control.Monad (forM_) import Data.Char (isSpace) import Data.List (groupBy) import Debug.Trace (traceShow, traceShowId) +import System.IO +import GHC.IO.Encoding +import Control.Monad.Trans.State type Acc = (M.Map String [String], Maybe String) @@ -16,24 +21,42 @@ step (sources, Nothing) line = step (sources, Just fileName) line = if (null line || isSpace (head line)) then (M.alter (\maybeLines -> case maybeLines of - Just lines -> Just (drop 6 line:lines) - Nothing -> Just [drop 6 line] + Just lines -> Just (extractLine line:lines) + Nothing -> Just [extractLine line] ) fileName sources, Just fileName) else (sources, Nothing) +extractLine :: String -> String +extractLine = drop 6 . foldr (\c acc -> + if not (null acc) && head acc == '\\' && c == '\\' then acc + else if not (null acc) && head acc == '"' && c == '\\' then acc + else if c == '‹' then '<':acc + else if c == '›' then '>':acc + else c:acc + ) "" + processLines :: [String] -> Acc processLines = foldl step (M.empty, Nothing) process = fst . processLines . lines -writeSources :: M.Map String [String] -> IO () -writeSources sources = +writeSources :: M.Map String [String] -> String -> IO () +writeSources sources destDir = forM_ (M.toList sources) (\(fileName,sourceLines) -> - writeFile ("code/" ++ fileName) (unlines $ reverse $ dropWhile null sourceLines)) + do + houtput <- openFile (destDir ++ "/" ++ fileName) WriteMode + hSetEncoding houtput utf8 + hPutStr houtput (unlines $ reverse $ dropWhile null sourceLines) + hClose houtput + ) main = do args <- getArgs - let srcFile = head args - content <- readFile srcFile + let sourceFile = head args + let destDir = head (tail args) + hinput <- openFile sourceFile ReadMode + hSetEncoding hinput utf8 + content <- hGetContents hinput let sources = process content - writeSources sources + writeSources sources destDir + hClose hinput diff --git a/index.elm b/index.elm index e966781..85287ea 100644 --- a/index.elm +++ b/index.elm @@ -1,4 +1,5 @@ -- -*- coding: utf-8; -*- +module Index where import Lib (..) import Window @@ -11,12 +12,17 @@ import CirclesView import Text (..) import Text import Graphics.Element +import Graphics.Element (..) +import Graphics.Collage (..) +import Color (..) import CalculatorView import CalculatorModel +import Signal +import Markdown -version = "Built on 2014-11-12 using Elm 0.13." +version = "Built on 2015-01-16 using Elm 0.14.1." -main = lift content Window.width +main = Signal.map content Window.width content w = pageTemplate [ spacer 30 30 , title @@ -28,18 +34,18 @@ content w = pageTemplate [ spacer 30 30 , container w 30 middle (plainText version) ] "" "" "toc" w -title = toText "Elm by Example" |> +title = fromString "Elm by Example" |> bold |> Text.height 60 |> centered -author = toText "Grzegorz Balcerek" |> +author = fromString "Grzegorz Balcerek" |> bold |> Text.height 40 |> centered tocLink : Element -tocLink = [markdown| [Continue to the Table of Contents](toc.html) |] +tocLink = Markdown.toElement """ [Continue to the Table of Contents](toc.html) """ snakeState = { delta = { dx = 1, dy = 0 }, food = Just { x = 11, y = 1 }, gameOver = False, snake = { back = [{ x = -5, y = 1 },{ x = -6, y = 1 },{ x = -7, y = 1 },{ x = -8, y = 1 },{ x = -9, y = 1 },{ x = -10, y = 1 },{ x = -11, y = 1 },{ x = -11, y = 0 },{ x = -11, y = -1 },{ x = -11, y = -2 },{ x = -11, y = -3 },{ x = -11, y = -4 },{ x = -11, y = -5 },{ x = -11, y = -6 },{ x = -10, y = -6 },{ x = -9, y = -6 },{ x = -8, y = -6 }], front = [{ x = 13, y = -6 },{ x = 12, y = -6 },{ x = 11, y = -6 },{ x = 10, y = -6 },{ x = 9, y = -6 },{ x = 8, y = -6 },{ x = 7, y = -6 },{ x = 6, y = -6 },{ x = 5, y = -6 },{ x = 4, y = -6 },{ x = 3, y = -6 },{ x = 2, y = -6 },{ x = 1, y = -6 },{ x = 0, y = -6 },{ x = -1, y = -6 },{ x = -2, y = -6 },{ x = -3, y = -6 },{ x = -4, y = -6 },{ x = -5, y = -6 },{ x = -6, y = -6 },{ x = -7, y = -6 }] }, ticks = 2076 } diff --git a/make.bat b/make.bat index 568897a..a6c54c3 100644 --- a/make.bat +++ b/make.bat @@ -1,30 +1,30 @@ -@echo off +@echo on -set RUNTIME=--set-runtime=elm-runtime.js -rem set RUNTIME= +rem elm-package install elm-lang/core +rem elm-package install evancz/elm-markdown -rem rmdir /q/s target +rmdir /q/s src +rmdir /q/s target +mkdir src mkdir target -mkdir target\Chess +del /q ..\ElmByExample\src\* -extractCode Chapter1HelloWorld.elm -extractCode Chapter2FibonacciBars.elm -extractCode Chapter3MouseSignals.elm -extractCode Chapter4WindowSignals.elm -extractCode Chapter5Eyes.elm -extractCode Chapter6TimeSignals.elm -extractCode Chapter7DelayedCircles.elm -extractCode Chapter8RandomSignals.elm -extractCode Chapter9Circles.elm -extractCode Chapter10Calculator.elm -extractCode Chapter11KeyboardSignals.elm -extractCode Chapter12Paddle.elm -extractCode Chapter13TicTacToe.elm -extractCode Chapter14Snake.elm -extractCode Chapter15SnakeRevisited.elm +cmd /c examples.bat -copy /y code\* target -copy /y *.elm target +cd ..\ElmByExample +cmd /c compile.bat +cd ..\elm-by-example + +copy /y ..\ElmByExample\src\* target +copy /y ..\ElmByExample\*.html target +copy /y ..\ElmByExample\WindowSignals2.html target\WindowSignals2.html.txt +copy /y ..\ElmByExample\WindowSignals3.html target\WindowSignals3.html.txt +copy /y ..\ElmByExample\*.js target +copy /y Lib.elm src +copy /y index.elm src +copy /y toc.elm src +copy /y Introduction.elm src +copy target\*.elm src prepareChapterSource Chapter1HelloWorld.elm prepareChapterSource Chapter2FibonacciBars.elm @@ -33,80 +33,32 @@ prepareChapterSource Chapter4WindowSignals.elm prepareChapterSource Chapter5Eyes.elm prepareChapterSource Chapter6TimeSignals.elm prepareChapterSource Chapter7DelayedCircles.elm -prepareChapterSource Chapter8RandomSignals.elm -prepareChapterSource Chapter9Circles.elm -prepareChapterSource Chapter10Calculator.elm -prepareChapterSource Chapter11KeyboardSignals.elm -prepareChapterSource Chapter12Paddle.elm -prepareChapterSource Chapter13TicTacToe.elm -prepareChapterSource Chapter14Snake.elm -prepareChapterSource Chapter15SnakeRevisited.elm - -cd target - -echo /* > elm-runtime.js -type ..\..\Elm\LICENSE >> elm-runtime.js -echo */ >> elm-runtime.js -type C:\Users\grzes\AppData\Roaming\cabal\i386-windows-ghc-7.8.3\Elm-0.13\elm-runtime.js >> elm-runtime.js - -elm %RUNTIME% -m index.elm -elm %RUNTIME% -m toc.elm -elm %RUNTIME% -m Introduction.elm -elm %RUNTIME% -m Chapter1HelloWorld.elm -elm %RUNTIME% -m HelloWorld1.elm -elm %RUNTIME% -m HelloWorld2.elm -elm %RUNTIME% -m HelloWorld3.elm -elm %RUNTIME% -m HelloWorld4.elm -elm %RUNTIME% -m Chapter2FibonacciBars.elm -elm %RUNTIME% -m FibonacciBars.elm -elm %RUNTIME% -m Chapter3MouseSignals.elm -elm %RUNTIME% -m MouseSignals1.elm -elm %RUNTIME% -m MouseSignals2.elm -elm %RUNTIME% -m MouseSignals3.elm -elm %RUNTIME% -m Chapter4WindowSignals.elm -elm %RUNTIME% -m WindowSignals1.elm -elm %RUNTIME% --only-js WindowSignals1.elm -elm %RUNTIME% -m Chapter5Eyes.elm -elm %RUNTIME% -m EyesView.elm -elm %RUNTIME% -m EyesModel.elm -elm %RUNTIME% -m Eyes.elm -elm %RUNTIME% -m Chapter6TimeSignals.elm -elm %RUNTIME% -m TimeSignals.elm -elm %RUNTIME% -m Chapter7DelayedCircles.elm -elm %RUNTIME% -m DrawCircles.elm -elm %RUNTIME% -m DelayedMousePositions.elm -elm %RUNTIME% -m DelayedCircles.elm -elm %RUNTIME% -m Chapter8RandomSignals.elm -elm %RUNTIME% -m RandomSignals1.elm -elm %RUNTIME% -m RandomSignals2.elm -elm %RUNTIME% -m RandomSignals3.elm -elm %RUNTIME% -m Chapter9Circles.elm -elm %RUNTIME% -m CirclesView.elm -elm %RUNTIME% -m Circles.elm -elm %RUNTIME% -m CirclesTest.elm -elm %RUNTIME% -m Chapter10Calculator.elm -elm %RUNTIME% -m CalculatorViewTest1.elm -elm %RUNTIME% -m CalculatorView.elm -elm %RUNTIME% -m Calculator.elm -elm %RUNTIME% -m Chapter11KeyboardSignals.elm -elm %RUNTIME% -m KeyboardSignals1.elm -elm %RUNTIME% -m KeyboardSignals2.elm -elm %RUNTIME% -m KeyboardSignals3.elm -elm %RUNTIME% -m KeyboardSignals4.elm -elm %RUNTIME% -m Chapter12Paddle.elm -elm %RUNTIME% -m Paddle.elm -elm %RUNTIME% -m Chapter13TicTacToe.elm -elm %RUNTIME% -m TicTacToe.elm -elm %RUNTIME% -m Chapter14Snake.elm -elm %RUNTIME% -m Snake.elm -elm %RUNTIME% -m SnakeView.elm -elm %RUNTIME% -m Chapter15SnakeRevisited.elm -elm %RUNTIME% -m SnakeRevisited.elm +prepareChapterSource Chapter8Circles.elm +prepareChapterSource Chapter9Calculator.elm +prepareChapterSource Chapter10KeyboardSignals.elm +prepareChapterSource Chapter11Paddle.elm +prepareChapterSource Chapter12TicTacToe.elm +prepareChapterSource Chapter13Snake.elm +prepareChapterSource Chapter14SnakeRevisited.elm -cd .. +elm-make src/index.elm --output target/index.html +elm-make src/toc.elm --output target/toc.html +elm-make src/Introduction.elm --output target/Introduction.html +elm-make src/Chapter1HelloWorld.elm --output target/Chapter1HelloWorld.html +elm-make src/Chapter2FibonacciBars.elm --output target/Chapter2FibonacciBars.html +elm-make src/Chapter3MouseSignals.elm --output target/Chapter3MouseSignals.html +elm-make src/Chapter4WindowSignals.elm --output target/Chapter4WindowSignals.html +elm-make src/Chapter5Eyes.elm --output target/Chapter5Eyes.html +elm-make src/Chapter6TimeSignals.elm --output target/Chapter6TimeSignals.html +elm-make src/Chapter7DelayedCircles.elm --output target/Chapter7DelayedCircles.html +elm-make src/Chapter8Circles.elm --output target/Chapter8Circles.html +elm-make src/Chapter9Calculator.elm --output target/Chapter9Calculator.html +elm-make src/Chapter10KeyboardSignals.elm --output target/Chapter10KeyboardSignals.html +elm-make src/Chapter11Paddle.elm --output target/Chapter11Paddle.html +elm-make src/Chapter12TicTacToe.elm --output target/Chapter12TicTacToe.html +elm-make src/Chapter13Snake.elm --output target/Chapter13Snake.html +elm-make src/Chapter14SnakeRevisited.elm --output target/Chapter14SnakeRevisited.html -copy /y target\elm-runtime.js target\build -copy /y code\* target\build -copy /y code\WindowSignals2.html target\build\WindowSignals2.txt +rem copy /y code\WindowSignals2.html target\WindowSignals2.txt echo target\build\index.html diff --git a/prepareChapterSource.hs b/prepareChapterSource.hs index 100e575..1adfce8 100644 --- a/prepareChapterSource.hs +++ b/prepareChapterSource.hs @@ -11,6 +11,6 @@ process = unlines . filter (not . startsWithPercent) . lines main = do args <- getArgs let srcFile = head args - let targetFile = "target/" ++ srcFile + let targetFile = "src/" ++ srcFile content <- readFile srcFile writeFile targetFile (process content) diff --git a/toc.elm b/toc.elm index 45e8929..1985c4d 100644 --- a/toc.elm +++ b/toc.elm @@ -3,10 +3,12 @@ import Lib (..) import Window +import Signal +import Markdown -main = lift (pageTemplate [content] "index" "index" "Introduction") Window.width +main = Signal.map (pageTemplate [content] "index" "index" "Introduction") Window.width -content = [markdown| +content = Markdown.toElement """ # Table of contents @@ -18,13 +20,12 @@ content = [markdown| #### [Chapter 5 Eyes](Chapter5Eyes.html) #### [Chapter 6 Time Signals](Chapter6TimeSignals.html) #### [Chapter 7 Delayed Circles](Chapter7DelayedCircles.html) -#### [Chapter 8 Random Signals](Chapter8RandomSignals.html) -#### [Chapter 9 Circles](Chapter9Circles.html) -#### [Chapter 10 Calculator](Chapter10Calculator.html) -#### [Chapter 11 Keyboard Signals](Chapter11KeyboardSignals.html) -#### [Chapter 12 Paddle](Chapter12Paddle.html) -#### [Chapter 13 Tic Tac Toe](Chapter13TicTacToe.html) -#### [Chapter 14 Snake](Chapter14Snake.html) -#### [Chapter 15 Snake Revisited](Chapter15SnakeRevisited.html) +#### [Chapter 8 Circles](Chapter8Circles.html) +#### [Chapter 9 Calculator](Chapter9Calculator.html) +#### [Chapter 10 Keyboard Signals](Chapter10KeyboardSignals.html) +#### [Chapter 11 Paddle](Chapter11Paddle.html) +#### [Chapter 12 Tic Tac Toe](Chapter12TicTacToe.html) +#### [Chapter 13 Snake](Chapter13Snake.html) +#### [Chapter 14 Snake Revisited](Chapter14SnakeRevisited.html) -|] +"""