├── .gitignore ├── LICENSE.md ├── LazyLambda.hsproj ├── Actions.hs ├── Bird-01.png ├── Bird-02.png ├── Bird-03.png ├── Constants.hs ├── Convenience.hs ├── GameState.hs ├── LICENSE.md ├── Land.png ├── LazyLambda.cabal ├── LazyLambda.hs ├── PipeDown.png ├── PipeUp.png ├── Pipes.hs ├── Random.hs ├── Scenery.hs ├── Setup.hs ├── Sky.png ├── _Actions.hsplay ├── _Constants.hsplay ├── _Convenience.hsplay ├── _GameState.hsplay ├── _LazyLambda.hsplay ├── _Pipes.hsplay ├── _Random.hsplay ├── _Scenery.hsplay ├── scoreboard.png └── stack.yaml ├── README.md └── images └── LazyLambdaLoop.gif /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | dist-* 3 | cabal-dev 4 | *.o 5 | *.hi 6 | *.chi 7 | *.chs.h 8 | *.dyn_o 9 | *.dyn_hi 10 | .hpc 11 | .hsenv 12 | .cabal-sandbox/ 13 | cabal.sandbox.config 14 | *.prof 15 | *.aux 16 | *.hp 17 | *.eventlog 18 | .stack-work/ 19 | cabal.project.local 20 | .uuid 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © 2016 Manuel M T Chakravarty & Applicative 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | 28 | The graphics assets are mostly from 29 | 30 | https://github.com/fullstackio/FlappySwift 31 | 32 | which is distributed under the following license. 33 | 34 | 35 | The MIT License (MIT) 36 | ===================== 37 | 38 | Copyright © 2015 Nate Murray & Fullstack.io 39 | 40 | Permission is hereby granted, free of charge, to any person 41 | obtaining a copy of this software and associated documentation 42 | files (the “Software”), to deal in the Software without 43 | restriction, including without limitation the rights to use, 44 | copy, modify, merge, publish, distribute, sublicense, and/or sell 45 | copies of the Software, and to permit persons to whom the 46 | Software is furnished to do so, subject to the following 47 | conditions: 48 | 49 | The above copyright notice and this permission notice shall be 50 | included in all copies or substantial portions of the Software. 51 | 52 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 53 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 54 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 55 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 56 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 57 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 58 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 59 | OTHER DEALINGS IN THE SOFTWARE. 60 | 61 | -------------------------------------------------------------------------------- /LazyLambda.hsproj/Actions.hs: -------------------------------------------------------------------------------- 1 | module Actions where 2 | 3 | import Graphics.SpriteKit 4 | 5 | import GameState 6 | 7 | 8 | -- The bump used to lift Lambda on key press. 9 | -- 10 | bumpAction :: LambdaNode -> TimeInterval -> LambdaNode 11 | bumpAction sprite@Sprite{ nodePhysicsBody = Just body } _dt 12 | = sprite 13 | { nodePhysicsBody 14 | = Just body 15 | { bodyVelocity = vectorZero 16 | , bodyForcesAndImpulses = [ApplyImpulse (Vector 0 20) Nothing] 17 | } 18 | } 19 | bumpAction node _dt = node 20 | 21 | -- Tilt Lambda in dependence on its vertical velocity vector. 22 | -- 23 | tiltAction :: LambdaNode -> TimeInterval -> LambdaNode 24 | tiltAction sprite@Sprite{ nodePhysicsBody = Just body } _dt 25 | = sprite 26 | { nodeZRotation = (-1) `max` zRotation `min` 0.5 } 27 | where 28 | zRotation = dY * (if dY < 0 then 0.003 else 0.001 ) 29 | dY = vectorDy . bodyVelocity $ body 30 | tiltAction node _dt = node 31 | -------------------------------------------------------------------------------- /LazyLambda.hsproj/Bird-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchakravarty/lazy-lambda/ccdde0d3e54525ef580d483a28562985519224aa/LazyLambda.hsproj/Bird-01.png -------------------------------------------------------------------------------- /LazyLambda.hsproj/Bird-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchakravarty/lazy-lambda/ccdde0d3e54525ef580d483a28562985519224aa/LazyLambda.hsproj/Bird-02.png -------------------------------------------------------------------------------- /LazyLambda.hsproj/Bird-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchakravarty/lazy-lambda/ccdde0d3e54525ef580d483a28562985519224aa/LazyLambda.hsproj/Bird-03.png -------------------------------------------------------------------------------- /LazyLambda.hsproj/Constants.hs: -------------------------------------------------------------------------------- 1 | module Constants where 2 | 3 | import Graphics.SpriteKit 4 | 5 | import Data.Bits 6 | import Data.Word 7 | 8 | 9 | -- Scene dimensions 10 | -- 11 | width, height :: GFloat 12 | width = 667 13 | height = 750 14 | 15 | -- The size of the gap to fly through. 16 | -- 17 | verticalPipeGap :: GFloat 18 | verticalPipeGap = 150 19 | 20 | -- Categorisation of the various items in the physics world. 21 | -- 22 | data PhysicsCategory = Bird -- Lambda 23 | | World -- Pipes & the ground 24 | | Score -- Scoring nodes 25 | deriving (Enum) 26 | 27 | categoryBitMask :: [PhysicsCategory] -> Word32 28 | categoryBitMask = foldl setCategoryBit zeroBits 29 | where 30 | setCategoryBit bits cat = bits .|. bit (fromEnum cat) 31 | 32 | isInCategory :: PhysicsCategory -> Node u -> Bool 33 | isInCategory cat node 34 | = case nodePhysicsBody node of 35 | Just body -> testBit (bodyCategoryBitMask body) (fromEnum cat) 36 | Nothing -> False 37 | 38 | isWorld :: Node u -> Bool 39 | isWorld = isInCategory World 40 | 41 | isScore :: Node u -> Bool 42 | isScore = isInCategory Score 43 | 44 | -- Background colour of the sky 45 | -- 46 | skyColour :: Color 47 | skyColour = colorWithRGBA (81.0/255.0) 48 | (192.0/255.0) 49 | (201.0/255.0) 50 | 1.0 51 | -------------------------------------------------------------------------------- /LazyLambda.hsproj/Convenience.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NamedFieldPuns, RecordWildCards #-} 2 | 3 | module Convenience where 4 | 5 | import Graphics.SpriteKit 6 | 7 | 8 | -- Define a texture based on the image data in the given file, while also 9 | -- determining its width and height. 10 | -- 11 | defineTexture :: FilePath -> (Texture, GFloat, GFloat) 12 | defineTexture path = (tex, sizeWidth, sizeHeight) 13 | where 14 | tex = textureWithImageNamed path 15 | Size{..} = textureSize tex 16 | 17 | -- Run an action on the specified child node. 18 | -- 19 | runActionOn :: String -> Action children -> SDirective node children 20 | runActionOn childName action 21 | = runAction $ runActionOnChildWithName action childName 22 | 23 | -- Run a custom action on the specified child node. 24 | -- 25 | runCustomActionOn :: String -> TimedUpdate children -> SDirective node children 26 | runCustomActionOn childName actionFun 27 | = runActionOn childName $ customAction actionFun 28 | 29 | -- Run the given action in an endless loop. 30 | -- 31 | runActionForever :: SAction node children -> SDirective node children 32 | runActionForever = runAction . repeatActionForever 33 | 34 | -- Run the given sequence of action in an endless loop. 35 | -- 36 | runActionSequenceForever :: [SAction n c] -> SDirective n c 37 | runActionSequenceForever = runActionForever . sequenceActions -------------------------------------------------------------------------------- /LazyLambda.hsproj/GameState.hs: -------------------------------------------------------------------------------- 1 | module GameState where 2 | 3 | import Graphics.SpriteKit 4 | 5 | 6 | data NodeState = PipesState [Int] -- unbound sequence of random numbers 7 | | NoState 8 | 9 | type LambdaNode = Node NodeState 10 | 11 | randomInt :: NodeState -> (NodeState, Int) 12 | randomInt NoState = (NoState, 0) 13 | randomInt (PipesState (i:is)) = (PipesState is, i) 14 | 15 | 16 | data GameState = Running | Crash | Over 17 | deriving Eq 18 | 19 | data SceneState = SceneState 20 | { sceneScore :: Int 21 | , keyPressed :: Bool 22 | , bumpScore :: Bool 23 | , gameState :: GameState 24 | } 25 | 26 | initialSceneState 27 | = SceneState 28 | { sceneScore = 0 29 | , keyPressed = False 30 | , bumpScore = False 31 | , gameState = Running 32 | } 33 | 34 | type LambdaScene = Scene SceneState NodeState 35 | -------------------------------------------------------------------------------- /LazyLambda.hsproj/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © 2016 Manuel M T Chakravarty & Applicative 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | 28 | The graphics assets are mostly from 29 | 30 | https://github.com/fullstackio/FlappySwift 31 | 32 | which is distributed under the following license. 33 | 34 | 35 | The MIT License (MIT) 36 | ===================== 37 | 38 | Copyright © 2015 Nate Murray & Fullstack.io 39 | 40 | Permission is hereby granted, free of charge, to any person 41 | obtaining a copy of this software and associated documentation 42 | files (the “Software”), to deal in the Software without 43 | restriction, including without limitation the rights to use, 44 | copy, modify, merge, publish, distribute, sublicense, and/or sell 45 | copies of the Software, and to permit persons to whom the 46 | Software is furnished to do so, subject to the following 47 | conditions: 48 | 49 | The above copyright notice and this permission notice shall be 50 | included in all copies or substantial portions of the Software. 51 | 52 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 53 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 54 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 55 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 56 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 57 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 58 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 59 | OTHER DEALINGS IN THE SOFTWARE. 60 | 61 | -------------------------------------------------------------------------------- /LazyLambda.hsproj/Land.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchakravarty/lazy-lambda/ccdde0d3e54525ef580d483a28562985519224aa/LazyLambda.hsproj/Land.png -------------------------------------------------------------------------------- /LazyLambda.hsproj/LazyLambda.cabal: -------------------------------------------------------------------------------- 1 | name: LazyLambda 2 | version: 1.0 3 | cabal-version: 1.11 4 | build-type: Simple 5 | license: MIT 6 | license-file: LICENSE.md 7 | copyright: 2016 Applicative 8 | stability: experimental 9 | synopsis: A Flappy Bird clone 10 | description: 11 | This project was inspired by https://github.com/fullstackio/FlappySwift and is also based on the graphics assets of the Swift version. 12 | category: Games 13 | author: Manuel M T Chakravarty 14 | data-files: 15 | scoreboard.png 16 | Sky.png 17 | PipeUp.png 18 | Land.png 19 | Bird-01.png 20 | Bird-02.png 21 | Bird-03.png 22 | LICENSE.md 23 | PipeDown.png 24 | x-ghc-framework-version: 7.10.3-5.9-2 25 | x-last-ide-version: HfM1.3.0 26 | Executable LazyLambda 27 | main-is: LazyLambda.hs 28 | buildable: True 29 | build-depends: 30 | base -any, 31 | spritekit -any, 32 | random -any, 33 | pipes -any 34 | other-modules: 35 | Constants 36 | Random 37 | GameState 38 | Actions 39 | Pipes 40 | Convenience 41 | Scenery 42 | -------------------------------------------------------------------------------- /LazyLambda.hsproj/LazyLambda.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NamedFieldPuns, RecordWildCards #-} 2 | 3 | import Graphics.SpriteKit 4 | 5 | import Actions 6 | import Constants 7 | import Convenience 8 | import GameState 9 | import Pipes 10 | import Random 11 | import Scenery 12 | 13 | 14 | lazyLambda :: LambdaScene 15 | lazyLambda 16 | = (sceneWithSize (Size width height)) 17 | { sceneBackgroundColor = skyColour 18 | , sceneChildren = [bird, movingNodes, groundPhysics, score] 19 | , sceneData = initialSceneState 20 | , sceneUpdate = Just update 21 | , scenePhysicsWorld = physicsWorld 22 | { worldGravity = Vector 0 (-5) 23 | , worldContactDidBegin = Just contact 24 | } 25 | , sceneHandleEvent = Just handleEvent 26 | } 27 | 28 | (bird1Texture, birdWidth, birdHeight) = defineTexture "Bird-01.png" 29 | (bird2Texture, _, _) = defineTexture "Bird-02.png" 30 | (bird3Texture, _, _) = defineTexture "Bird-03.png" 31 | 32 | bird :: LambdaNode 33 | bird = (spriteWithTexture bird1Texture) 34 | { nodeName = Just "Lambda" 35 | , nodePosition = Point (width * 0.35) (height * 0.6) 36 | , nodeActionDirectives = [runActionForever flap] 37 | , nodePhysicsBody 38 | = Just $ 39 | (bodyWithCircleOfRadius (birdHeight / 2) Nothing) 40 | { bodyCategoryBitMask = categoryBitMask [Bird] 41 | , bodyCollisionBitMask = categoryBitMask [World] 42 | , bodyContactTestBitMask = categoryBitMask [World, Score] 43 | } 44 | } 45 | where 46 | flap = animateWithTextures 47 | [bird1Texture, bird2Texture, bird3Texture, bird2Texture] 0.1 48 | 49 | movingNodes :: LambdaNode 50 | movingNodes = (node $ pipes : groundSprites ++ skySprites) 51 | { nodeName = Just "Moving" } 52 | 53 | groundPhysics :: LambdaNode 54 | groundPhysics = (node []) 55 | { nodePosition = Point 0 (groundTileHeight / 2) 56 | , nodePhysicsBody = Just $ 57 | (bodyWithEdgeFromPointToPoint (Point 0 (groundTileHeight / 2)) 58 | (Point width (groundTileHeight / 2))) 59 | { bodyCategoryBitMask = categoryBitMask [World] } 60 | } 61 | 62 | pipes :: LambdaNode 63 | pipes = (node []) 64 | { nodeActionDirectives = [runActionSequenceForever 65 | [ customAction (spawnPipePair birdWidth) 66 | , waitForDuration{ actionDuration = 1.5 } 67 | ] ] 68 | , nodeUserData = PipesState randomNums 69 | } 70 | 71 | score :: LambdaNode 72 | score = (labelNodeWithFontNamed "MarkerFelt-Wide") 73 | { nodeName = Just "Score" 74 | , nodePosition = Point (width / 2) (3 * height / 4) 75 | , nodeZPosition = 100 76 | , labelText = "0" 77 | } 78 | 79 | update :: LambdaScene -> TimeInterval -> LambdaScene 80 | update scene@Scene{ sceneData = sceneState@SceneState{..} } _dt 81 | = case gameState of 82 | Running 83 | | keyPressed -> bumpLambda scene{ sceneData = sceneState{ keyPressed = False } } 84 | | bumpScore -> incScore scene{ sceneData = sceneState{ bumpScore = False } } 85 | | otherwise -> tiltLambda scene 86 | Crash -> crash scene{ sceneData = sceneState{ gameState = Over } } 87 | Over -> scene 88 | 89 | bumpLambda :: LambdaScene -> LambdaScene 90 | bumpLambda scene 91 | = scene { sceneActionDirectives = [runCustomActionOn "Lambda" bumpAction] } 92 | 93 | tiltLambda :: LambdaScene -> LambdaScene 94 | tiltLambda scene 95 | = scene{ sceneActionDirectives = [runCustomActionOn "Lambda" tiltAction] } 96 | 97 | crash :: LambdaScene -> LambdaScene 98 | crash scene 99 | = scene { sceneActionDirectives = [ runActionOn "Lambda" crashAction 100 | , runActionOn "Moving" stopMoving 101 | ] } 102 | where 103 | crashAction = sequenceActions 104 | [ (rotateByAngle (-pi * 4)){ actionDuration = 1 } 105 | , fadeOut{ actionDuration = 0.2 } 106 | ] 107 | stopMoving = sequenceActions 108 | [ waitForDuration{ actionDuration = 1 } 109 | , customAction $ \node _ -> node{ nodeSpeed = 0 } 110 | ] 111 | 112 | incScore :: LambdaScene -> LambdaScene 113 | incScore scene@Scene{ sceneData = sceneState } 114 | = scene 115 | { sceneActionDirectives = [runCustomActionOn "Score" setScore] 116 | , sceneData = sceneState{ sceneScore = newScore } 117 | } 118 | where 119 | newScore = sceneScore sceneState + 1 120 | 121 | setScore label@Label{} _dt = label{ labelText = show newScore } 122 | setScore node _ = node 123 | 124 | contact :: SceneState 125 | -> PhysicsContact u 126 | -> (Maybe SceneState, Maybe (Node u), Maybe (Node u)) 127 | contact state@SceneState{..} PhysicsContact{..} 128 | | (isWorld contactBodyA || isWorld contactBodyB) && gameState == Running 129 | = (Just state{ gameState = Crash }, Nothing, Nothing) 130 | | isScore contactBodyA || isScore contactBodyB 131 | = (Just state{ bumpScore = True }, Nothing, Nothing) 132 | | otherwise 133 | = (Nothing, Nothing, Nothing) 134 | 135 | handleEvent :: Event -> SceneState -> Maybe SceneState 136 | handleEvent KeyEvent{ keyEventType = KeyDown } state = Just state{ keyPressed = True } 137 | handleEvent _ _ = Nothing 138 | -------------------------------------------------------------------------------- /LazyLambda.hsproj/PipeDown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchakravarty/lazy-lambda/ccdde0d3e54525ef580d483a28562985519224aa/LazyLambda.hsproj/PipeDown.png -------------------------------------------------------------------------------- /LazyLambda.hsproj/PipeUp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchakravarty/lazy-lambda/ccdde0d3e54525ef580d483a28562985519224aa/LazyLambda.hsproj/PipeUp.png -------------------------------------------------------------------------------- /LazyLambda.hsproj/Pipes.hs: -------------------------------------------------------------------------------- 1 | module Pipes where 2 | 3 | import Graphics.SpriteKit 4 | 5 | import Actions 6 | import Constants 7 | import Convenience 8 | import GameState 9 | 10 | 11 | (pipeUpTexture, pipeUpWidth, pipeUpHeight) = defineTexture "PipeUp.png" 12 | (pipeDownTexture, pipeDownWidth, pipeDownHeight) = defineTexture "PipeDown.png" 13 | 14 | spawnPipePair :: GFloat -> LambdaNode -> TimeInterval -> LambdaNode 15 | spawnPipePair birdWidth pipes _dt 16 | = pipes 17 | { nodeChildren = pipePair birdWidth pipeUpY : nodeChildren pipes 18 | , nodeUserData = pipeState' 19 | } 20 | where 21 | heightVariation = round (height / 4) 22 | (pipeState', i) = randomInt (nodeUserData pipes) 23 | pipeUpY = fromIntegral $ heightVariation + (i `mod` heightVariation) `div` 2 24 | 25 | pipePair :: GFloat -> GFloat -> LambdaNode 26 | pipePair birdWidth pipeUpY 27 | = (node [pipeDown pipeUpY, scoreContact birdWidth, pipeUp pipeUpY]) 28 | { nodePosition = Point (width + pipeUpWidth) 0 29 | , nodeZPosition = -10 30 | , nodeActionDirectives = [movePipesAndRemove] 31 | } 32 | where 33 | movePipesAndRemove = runAction $ 34 | sequenceActions [ (moveBy $ Vector (-distanceToMove) 0) 35 | { actionDuration = 0.005 * distanceToMove } 36 | , removeFromParent 37 | ] 38 | distanceToMove = width + pipeUpWidth 39 | pipeDown pipeUpY = pipe pipeDownTexture 40 | (pipeUpY + pipeDownHeight + verticalPipeGap) 41 | pipeDownWidth 42 | pipeDownHeight 43 | pipeUp pipeUpY = pipe pipeUpTexture 44 | pipeUpY 45 | pipeUpWidth 46 | pipeUpHeight 47 | 48 | pipe :: Texture -> GFloat -> GFloat -> GFloat -> LambdaNode 49 | pipe texture y width height 50 | = (spriteWithTexture texture) 51 | { nodePosition = Point 0 y 52 | , nodePhysicsBody = Just $ (bodyWithRectangleOfSize (Size width height) Nothing) 53 | { bodyIsDynamic = False 54 | , bodyCategoryBitMask = categoryBitMask [World] 55 | , bodyContactTestBitMask = categoryBitMask [Bird] 56 | } 57 | } 58 | 59 | scoreContact :: GFloat -> LambdaNode 60 | scoreContact birdWidth 61 | = (node []) 62 | { nodePosition = Point (pipeDownWidth / 2 + birdWidth / 2) (height / 2) 63 | , nodePhysicsBody = Just $ (bodyWithEdgeFromPointToPoint (Point 0 0) (Point 0 height)) 64 | { bodyCategoryBitMask = categoryBitMask [Score] 65 | , bodyContactTestBitMask = categoryBitMask [Bird] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /LazyLambda.hsproj/Random.hs: -------------------------------------------------------------------------------- 1 | module Random where 2 | 3 | import System.Random 4 | import System.IO.Unsafe 5 | 6 | 7 | randomNums :: (Random a, Num a) => [a] 8 | randomNums = unsafePerformIO $ randoms <$> newStdGen 9 | -------------------------------------------------------------------------------- /LazyLambda.hsproj/Scenery.hs: -------------------------------------------------------------------------------- 1 | module Scenery where 2 | 3 | import Graphics.SpriteKit 4 | 5 | import Actions 6 | import Constants 7 | import Convenience 8 | import GameState 9 | 10 | 11 | (groundTexture, groundTileWidth, groundTileHeight) = defineTexture "Land.png" 12 | 13 | groundSprites :: [LambdaNode] 14 | groundSprites = [ (spriteWithTexture groundTexture) 15 | { nodePosition = Point x (groundTileHeight / 2) 16 | , nodeActionDirectives = [groundMovement] 17 | } 18 | | x <- [0, groundTileWidth..width + groundTileWidth]] 19 | where 20 | movementDuration = 0.005 * groundTileWidth 21 | groundMovement = runActionSequenceForever 22 | [ (moveBy $ Vector (-groundTileWidth) 0) -- move 23 | { actionDuration = movementDuration } 24 | , (moveBy $ Vector groundTileWidth 0) -- reset 25 | { actionDuration = 0 } 26 | ] 27 | 28 | (skyTexture, skyTileWidth, skyTileHeight) = defineTexture "Sky.png" 29 | 30 | skySprites :: [LambdaNode] 31 | skySprites = [ (spriteWithTexture skyTexture) 32 | { nodePosition = Point x skyHeight 33 | , nodeZPosition = -20 34 | , nodeXScale = 2 35 | , nodeYScale = 2 36 | , nodeActionDirectives = [skyMovement] 37 | } 38 | | x <- [0, skyTileWidth..width + skyTileWidth]] 39 | where 40 | skyHeight = (skyTileHeight / 2) + groundTileHeight 41 | movementDuration = 0.025 * skyTileWidth 42 | skyMovement = runActionSequenceForever 43 | [ (moveBy $ Vector (-skyTileWidth) 0) -- move 44 | { actionDuration = movementDuration } 45 | , (moveBy $ Vector skyTileWidth 0) -- reset 46 | { actionDuration = 0 } 47 | ] 48 | 49 | -------------------------------------------------------------------------------- /LazyLambda.hsproj/Setup.hs: -------------------------------------------------------------------------------- 1 | -- Generated by Haskell for Mac for standalone builds 2 | import Distribution.Simple 3 | main = defaultMain 4 | -------------------------------------------------------------------------------- /LazyLambda.hsproj/Sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchakravarty/lazy-lambda/ccdde0d3e54525ef580d483a28562985519224aa/LazyLambda.hsproj/Sky.png -------------------------------------------------------------------------------- /LazyLambda.hsproj/_Actions.hsplay: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchakravarty/lazy-lambda/ccdde0d3e54525ef580d483a28562985519224aa/LazyLambda.hsproj/_Actions.hsplay -------------------------------------------------------------------------------- /LazyLambda.hsproj/_Constants.hsplay: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchakravarty/lazy-lambda/ccdde0d3e54525ef580d483a28562985519224aa/LazyLambda.hsproj/_Constants.hsplay -------------------------------------------------------------------------------- /LazyLambda.hsproj/_Convenience.hsplay: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchakravarty/lazy-lambda/ccdde0d3e54525ef580d483a28562985519224aa/LazyLambda.hsproj/_Convenience.hsplay -------------------------------------------------------------------------------- /LazyLambda.hsproj/_GameState.hsplay: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchakravarty/lazy-lambda/ccdde0d3e54525ef580d483a28562985519224aa/LazyLambda.hsproj/_GameState.hsplay -------------------------------------------------------------------------------- /LazyLambda.hsproj/_LazyLambda.hsplay: -------------------------------------------------------------------------------- 1 | -- Haskell Playground 1.0 2 | lazyLambda 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | bird1Texture 17 | 18 | 19 | 20 | bird2Texture 21 | 22 | 23 | 24 | bird3Texture 25 | 26 | 27 | 28 | spriteWithTexture groundTexture 29 | 30 | 31 | 32 | 33 | 34 | spriteWithTexture skyTexture 35 | 36 | 37 | 38 | node skySprites 39 | 40 | 41 | 42 | spriteWithTexture pipeDownTexture 43 | spriteWithTexture pipeUpTexture -------------------------------------------------------------------------------- /LazyLambda.hsproj/_Pipes.hsplay: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchakravarty/lazy-lambda/ccdde0d3e54525ef580d483a28562985519224aa/LazyLambda.hsproj/_Pipes.hsplay -------------------------------------------------------------------------------- /LazyLambda.hsproj/_Random.hsplay: -------------------------------------------------------------------------------- 1 | -- Haskell Playground 1.0 2 | -------------------------------------------------------------------------------- /LazyLambda.hsproj/_Scenery.hsplay: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchakravarty/lazy-lambda/ccdde0d3e54525ef580d483a28562985519224aa/LazyLambda.hsproj/_Scenery.hsplay -------------------------------------------------------------------------------- /LazyLambda.hsproj/scoreboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchakravarty/lazy-lambda/ccdde0d3e54525ef580d483a28562985519224aa/LazyLambda.hsproj/scoreboard.png -------------------------------------------------------------------------------- /LazyLambda.hsproj/stack.yaml: -------------------------------------------------------------------------------- 1 | # Generated by Haskell for Mac for standalone builds 2 | flags: {} 3 | packages: 4 | - '.' 5 | extra-deps: [] 6 | resolver: lts-5.9 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lazy Lambda — a Flappy Bird clone in Haskell with SpriteKit 2 | 3 | ![Lazy Lambda Loop](https://raw.githubusercontent.com/mchakravarty/lazy-lambda/master/images/LazyLambdaLoop.gif) 4 | 5 | Lazy Lambda is a clone of Flappy Bird in Haskell based on the [Haskell SpriteKit binding](https://github.com/mchakravarty/HaskellSpriteKit). Check out the video and slides of my [Compose :: Melbourne 2016](http://www.composeconference.org/2016-melbourne/day-one-program/) keynote, where I outline the concepts behind the SpriteKit binding and live code Lazy Lambda: 6 | 7 | > https://speakerdeck.com/mchakravarty/playing-with-graphics-and-animations-in-haskell 8 | 9 | For the best Haskell SpriteKit development experience, check out [Haskell for Mac](http://haskellformac.com). The Lazy Lambda code in this repository already comes in the form of a Haskell for Mac project. 10 | -------------------------------------------------------------------------------- /images/LazyLambdaLoop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchakravarty/lazy-lambda/ccdde0d3e54525ef580d483a28562985519224aa/images/LazyLambdaLoop.gif --------------------------------------------------------------------------------