├── .gitignore ├── images ├── logo_left.png ├── logo_right.png ├── rex │ ├── dead_0.png │ ├── dead_1.png │ ├── duck_0.png │ ├── duck_1.png │ ├── duck_2.png │ ├── duck_3.png │ ├── duck_4.png │ ├── duck_5.png │ ├── idle.png │ ├── jump_0.png │ ├── jump_1.png │ ├── run_0.png │ ├── run_1.png │ ├── run_2.png │ ├── run_3.png │ ├── run_4.png │ └── run_5.png ├── screenshot.png ├── cacti │ ├── cactus_0.png │ ├── cactus_1.png │ ├── cactus_2.png │ ├── cactus_3.png │ ├── cactus_4.png │ └── cactus_5.png └── clouds │ └── cloud_0.png ├── save-commands.json ├── src ├── WindowSize.elm ├── Cloud.elm ├── Cactus.elm ├── Main.elm ├── Background.elm ├── CactusGenerator.elm ├── Hud.elm ├── Rex.elm └── Game.elm ├── README.md ├── index.html ├── css └── style.css ├── deploy └── elm-package.json /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff 2 | target 3 | 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /images/logo_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/logo_left.png -------------------------------------------------------------------------------- /images/logo_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/logo_right.png -------------------------------------------------------------------------------- /images/rex/dead_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/rex/dead_0.png -------------------------------------------------------------------------------- /images/rex/dead_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/rex/dead_1.png -------------------------------------------------------------------------------- /images/rex/duck_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/rex/duck_0.png -------------------------------------------------------------------------------- /images/rex/duck_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/rex/duck_1.png -------------------------------------------------------------------------------- /images/rex/duck_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/rex/duck_2.png -------------------------------------------------------------------------------- /images/rex/duck_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/rex/duck_3.png -------------------------------------------------------------------------------- /images/rex/duck_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/rex/duck_4.png -------------------------------------------------------------------------------- /images/rex/duck_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/rex/duck_5.png -------------------------------------------------------------------------------- /images/rex/idle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/rex/idle.png -------------------------------------------------------------------------------- /images/rex/jump_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/rex/jump_0.png -------------------------------------------------------------------------------- /images/rex/jump_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/rex/jump_1.png -------------------------------------------------------------------------------- /images/rex/run_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/rex/run_0.png -------------------------------------------------------------------------------- /images/rex/run_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/rex/run_1.png -------------------------------------------------------------------------------- /images/rex/run_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/rex/run_2.png -------------------------------------------------------------------------------- /images/rex/run_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/rex/run_3.png -------------------------------------------------------------------------------- /images/rex/run_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/rex/run_4.png -------------------------------------------------------------------------------- /images/rex/run_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/rex/run_5.png -------------------------------------------------------------------------------- /images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/screenshot.png -------------------------------------------------------------------------------- /images/cacti/cactus_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/cacti/cactus_0.png -------------------------------------------------------------------------------- /images/cacti/cactus_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/cacti/cactus_1.png -------------------------------------------------------------------------------- /images/cacti/cactus_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/cacti/cactus_2.png -------------------------------------------------------------------------------- /images/cacti/cactus_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/cacti/cactus_3.png -------------------------------------------------------------------------------- /images/cacti/cactus_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/cacti/cactus_4.png -------------------------------------------------------------------------------- /images/cacti/cactus_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/cacti/cactus_5.png -------------------------------------------------------------------------------- /images/clouds/cloud_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelchelliah/elm-rex/HEAD/images/clouds/cloud_0.png -------------------------------------------------------------------------------- /save-commands.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeout": 5000, 3 | "commands" : [ "src/**/*.elm : elm make src/Main.elm --output=target/elm.js" ] 4 | } 5 | -------------------------------------------------------------------------------- /src/WindowSize.elm: -------------------------------------------------------------------------------- 1 | module WindowSize exposing (windowHeight, windowWidth) 2 | 3 | 4 | type alias Model = 5 | { width : Float 6 | , height : Float 7 | } 8 | 9 | 10 | windowHeight : Float 11 | windowHeight = 12 | size.height 13 | 14 | 15 | windowWidth : Float 16 | windowWidth = 17 | size.width 18 | 19 | 20 | size : Model 21 | size = 22 | Model 1000 400 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Elm-rex 2 | > Chrome's T-rex runner game, written in Elm. 3 | 4 | :dragon_face: ... . .. :cactus: .... .. :cactus: . .... .. 5 | 6 | ## RAWЯ! 7 | A fun litte hobby project to learn Elm! Game is inspired by Google Chrome's offline T-rex runner. 8 | 9 | **Check out the live demo here: [RAWЯ!](https://joelchelliah.github.io/elm-rex/)** 10 | 11 | 12 | ## Preview 13 | ![screenshot](images/screenshot.png) 14 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Elm-rex 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | text-align: center; 3 | } 4 | 5 | h1 { 6 | border-bottom: 1px solid lightgray; 7 | } 8 | 9 | h1 img { 10 | margin: 10px 10px 0; 11 | width: 25px; 12 | vertical-align: top; 13 | } 14 | 15 | h5 { 16 | color: gray; 17 | font-size: 0.9em; 18 | font-weight: normal; 19 | margin: 12px 0; 20 | } 21 | 22 | .info { 23 | border-top: 1px solid lightgray; 24 | padding-top: 8px; 25 | margin-top: 8px; 26 | } 27 | 28 | .info a { 29 | padding: 7px 0px 0px 7px; 30 | display: inline-block; 31 | vertical-align: top; 32 | } 33 | -------------------------------------------------------------------------------- /deploy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Deploy the application by pushing files to gh-pages 4 | 5 | CSS_PATH='css' 6 | IMG_PATH='images' 7 | TAR_PATH='target' 8 | 9 | # 1. Build the application (This is done automaticall by Atom) 10 | # elm-make src/Main.elm --output $OUT_PATH 11 | 12 | # 2. Push to gh-pages 13 | mkdir tmp 14 | cp -r $CSS_PATH tmp/ 15 | cp -r $IMG_PATH tmp/ 16 | cp -r $TAR_PATH tmp/ 17 | 18 | git checkout gh-pages 19 | cp -rf tmp/* . 20 | 21 | git add $CSS_PATH $IMG_PATH $TAR_PATH 22 | git commit -m "Deployed new version to gh-pages!" 23 | git push origin gh-pages 24 | 25 | # 3. Clean up 26 | rm -rf tmp 27 | git checkout master 28 | -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "Chrome's offline T-rex runner game, written in Elm.", 4 | "repository": "https://github.com/joelchelliah/elm-rex.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | ".", 8 | "src" 9 | ], 10 | "exposed-modules": [], 11 | "dependencies": { 12 | "elm-lang/animation-frame": "1.0.0 <= v < 2.0.0", 13 | "elm-lang/core": "5.0.0 <= v < 6.0.0", 14 | "elm-lang/html": "2.0.0 <= v < 3.0.0", 15 | "elm-lang/keyboard": "1.0.0 <= v < 2.0.0", 16 | "elm-lang/svg": "2.0.0 <= v < 3.0.0", 17 | "evancz/elm-graphics": "1.0.0 <= v < 2.0.0", 18 | "elm-community/list-extra": "6.0.0 <= v < 6.2.0", 19 | "jystic/elm-font-awesome": "2.0.0 <= v <= 2.0.1" 20 | }, 21 | "elm-version": "0.18.0 <= v < 0.19.0" 22 | } 23 | -------------------------------------------------------------------------------- /src/Cloud.elm: -------------------------------------------------------------------------------- 1 | module Cloud exposing (Model, init, update, view) 2 | 3 | import WindowSize exposing (windowWidth) 4 | import Svg exposing (Svg) 5 | import Svg.Attributes exposing (..) 6 | 7 | 8 | type alias Model = 9 | { xPos : Float 10 | , speed : Float 11 | } 12 | 13 | 14 | init : Model 15 | init = 16 | { xPos = 700 17 | , speed = 0.02 18 | } 19 | 20 | 21 | update : Float -> Model -> Model 22 | update delta ({ xPos, speed } as cloud) = 23 | let 24 | ( leftEdge, rightEdge ) = 25 | ( 0, windowWidth - size.width ) 26 | in 27 | if xPos < leftEdge then 28 | { cloud | xPos = leftEdge, speed = -speed } 29 | else if xPos > rightEdge then 30 | { cloud | xPos = rightEdge, speed = -speed } 31 | else 32 | { cloud | xPos = cloud.xPos - cloud.speed * delta } 33 | 34 | 35 | view : Model -> Svg {} 36 | view cloud = 37 | Svg.image 38 | [ x <| toString cloud.xPos 39 | , y <| toString 50 40 | , width <| toString size.width 41 | , height <| toString size.height 42 | , opacity "0.3" 43 | , xlinkHref "images/clouds/cloud_0.png" 44 | ] 45 | [] 46 | 47 | 48 | size : { width : Float, height : Float } 49 | size = 50 | { width = 152 51 | , height = 84 52 | } 53 | -------------------------------------------------------------------------------- /src/Cactus.elm: -------------------------------------------------------------------------------- 1 | module Cactus exposing (Model, init, update, view, isVisible, bounds) 2 | 3 | import WindowSize exposing (..) 4 | import Svg exposing (Svg) 5 | import Svg.Attributes exposing (..) 6 | 7 | 8 | type alias Model = 9 | { xPos : Float 10 | , yPos : Float 11 | , speed : Float 12 | , width : Float 13 | , height : Float 14 | , img : String 15 | } 16 | 17 | 18 | init : Float -> Int -> Float -> Model 19 | init x i speedInc = 20 | { xPos = x 21 | , yPos = 42 22 | , speed = 0.4 + 0.02 * speedInc 23 | , width = 64 24 | , height = 84 25 | , img = "images/cacti/cactus_" ++ toString i ++ ".png" 26 | } 27 | 28 | 29 | update : Float -> Model -> Model 30 | update delta cactus = 31 | { cactus | xPos = cactus.xPos - cactus.speed * delta } 32 | 33 | 34 | view : Model -> Svg {} 35 | view cactus = 36 | let 37 | { exMin, eyMin } = 38 | bounds cactus 39 | in 40 | Svg.image 41 | [ x <| toString exMin 42 | , y <| toString eyMin 43 | , width <| toString cactus.width 44 | , height <| toString cactus.height 45 | , xlinkHref cactus.img 46 | ] 47 | [] 48 | 49 | 50 | isVisible : Model -> Bool 51 | isVisible cactus = 52 | cactus.xPos >= -cactus.width 53 | 54 | 55 | bounds : Model -> { exMin : Float, exMax : Float, eyMin : Float, eyMax : Float } 56 | bounds cactus = 57 | let 58 | offsetFromBottomEdge = 59 | 100 60 | 61 | ( offsetX, offsetY ) = 62 | ( cactus.xPos, windowHeight - offsetFromBottomEdge + cactus.yPos ) 63 | in 64 | { exMin = offsetX 65 | , exMax = offsetX + cactus.width 66 | , eyMin = offsetY - cactus.height 67 | , eyMax = offsetY 68 | } 69 | -------------------------------------------------------------------------------- /src/Main.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (..) 2 | 3 | import Game exposing (Msg) 4 | import Html exposing (Html, programWithFlags, h1, h5, div, map, a, text, img) 5 | import Html.Attributes exposing (href, src) 6 | import Svg.Attributes exposing (..) 7 | import Random exposing (Seed, initialSeed) 8 | import FontAwesome as Icon 9 | import Color 10 | 11 | 12 | type alias Model = 13 | { game : Game.Model } 14 | 15 | 16 | type alias Flags = 17 | { randomSeed : Int } 18 | 19 | 20 | main : Program Flags Model Msg 21 | main = 22 | programWithFlags 23 | { init = init 24 | , view = view 25 | , update = update 26 | , subscriptions = subscriptions 27 | } 28 | 29 | 30 | init : Flags -> ( Model, Cmd Msg ) 31 | init { randomSeed } = 32 | let 33 | seed = 34 | initialSeed randomSeed 35 | in 36 | { game = Game.init seed } ! [ Cmd.none ] 37 | 38 | 39 | update : Msg -> Model -> ( Model, Cmd Msg ) 40 | update msg model = 41 | { model | game = Game.update msg model.game } ! [ Cmd.none ] 42 | 43 | 44 | subscriptions : Model -> Sub Msg 45 | subscriptions { game } = 46 | Game.subscriptions game 47 | 48 | 49 | view : Model -> Html Msg 50 | view ({ game } as model) = 51 | div [] 52 | [ viewHeader 53 | , Game.view game 54 | , viewInfo 55 | ] 56 | 57 | 58 | viewHeader : Html Msg 59 | viewHeader = 60 | div [] 61 | [ h1 [] 62 | [ img [ src "images/logo_right.png" ] [] 63 | , text "ELM-REX" 64 | , img [ src "images/logo_left.png" ] [] 65 | ] 66 | , h5 [] [ text "- An Elm port of Chrome's T-rex runner game -" ] 67 | ] 68 | 69 | 70 | viewInfo : Html Msg 71 | viewInfo = 72 | let 73 | url = 74 | "https://github.com/joelchelliah/elm-rex" 75 | in 76 | div 77 | [ class "info" ] 78 | [ Icon.github (Color.black) 30 79 | , a [ href url ] [ text "Find it on github" ] 80 | ] 81 | -------------------------------------------------------------------------------- /src/Background.elm: -------------------------------------------------------------------------------- 1 | module Background exposing (view) 2 | 3 | import WindowSize exposing (..) 4 | import List exposing (map, map2) 5 | import List.Extra exposing (iterate) 6 | import Svg exposing (Svg) 7 | import Svg.Attributes exposing (..) 8 | 9 | 10 | view : Svg {} 11 | view = 12 | Svg.svg [] 13 | [ renderSky 14 | , renderGround 15 | ] 16 | 17 | 18 | renderSky : Svg {} 19 | renderSky = 20 | let 21 | fillSkyRow = 22 | fillRow "0.08" "#4575FF" 23 | 24 | rows = 25 | map fillSkyRow skyRowPositions 26 | in 27 | Svg.svg [] rows 28 | 29 | 30 | renderGround : Svg {} 31 | renderGround = 32 | let 33 | fillGroundRow = 34 | fillRow "1.0" 35 | 36 | outline = 37 | fillGroundRow "#EEC39A" 90 38 | 39 | makeRow = 40 | map fillGroundRow groundRowColors 41 | 42 | rows = 43 | map2 identity makeRow groundRowPositions 44 | in 45 | Svg.svg [] <| outline :: rows 46 | 47 | 48 | fillRow : String -> String -> Float -> Svg {} 49 | fillRow alpha col yPos = 50 | Svg.rect 51 | [ fill col 52 | , x "0" 53 | , y <| toString <| windowHeight - yPos 54 | , width <| toString windowWidth 55 | , height <| toString yPos 56 | , opacity alpha 57 | ] 58 | [] 59 | 60 | 61 | groundRowColors : List String 62 | groundRowColors = 63 | [ "#D9A066" 64 | , "#CE975F" 65 | , "#C18C57" 66 | , "#B58452" 67 | , "#A87A4C" 68 | , "#9A7046" 69 | , "#8C6545" 70 | , "#825E41" 71 | , "#78563C" 72 | ] 73 | 74 | 75 | skyRowPositions : List Float 76 | skyRowPositions = 77 | let 78 | nextLine h = 79 | if h < 90 then 80 | Nothing 81 | else 82 | Just <| h - 15 83 | in 84 | iterate nextLine windowHeight 85 | 86 | 87 | groundRowPositions : List Float 88 | groundRowPositions = 89 | let 90 | nextLine h = 91 | if h < 0 then 92 | Nothing 93 | else 94 | Just (h - 10) 95 | in 96 | iterate nextLine 88 97 | -------------------------------------------------------------------------------- /src/CactusGenerator.elm: -------------------------------------------------------------------------------- 1 | module CactusGenerator exposing (Model, init, update) 2 | 3 | import Cactus 4 | import WindowSize exposing (..) 5 | import Random exposing (Seed, initialSeed, step) 6 | import List exposing (length, range, map, map2, filter) 7 | import Dict exposing (Dict, fromList, get) 8 | import Maybe exposing (withDefault) 9 | 10 | 11 | type alias Model = 12 | { seed : Seed 13 | , cacti : List Cactus.Model 14 | , spawnTimer : Int 15 | } 16 | 17 | 18 | init : Seed -> Model 19 | init seed0 = 20 | let 21 | startPosition = 22 | windowWidth - 200 23 | 24 | ( cactus, seed1 ) = 25 | generateCactusAt startPosition seed0 26 | in 27 | { seed = seed1 28 | , cacti = [ cactus ] 29 | , spawnTimer = 50 30 | } 31 | 32 | 33 | update : Float -> Int -> Model -> Model 34 | update delta score ({ seed, cacti, spawnTimer } as model) = 35 | let 36 | ( i, nextSeed ) = 37 | generateIndex seed 38 | 39 | speedInc = 40 | toFloat <| score // 500 41 | 42 | updatedCacti = 43 | map (Cactus.update delta) <| filter Cactus.isVisible cacti 44 | in 45 | if spawnTimer == 0 then 46 | { model 47 | | cacti = (Cactus.init windowWidth i speedInc) :: updatedCacti 48 | , seed = nextSeed 49 | , spawnTimer = getSpawnTime i <| speedInc 50 | } 51 | else 52 | { model 53 | | cacti = updatedCacti 54 | , seed = nextSeed 55 | , spawnTimer = spawnTimer - 1 56 | } 57 | 58 | 59 | generateCactusAt : Float -> Seed -> ( Cactus.Model, Seed ) 60 | generateCactusAt position seed0 = 61 | let 62 | ( i, seed1 ) = 63 | generateIndex seed0 64 | in 65 | ( Cactus.init position i 0, seed1 ) 66 | 67 | 68 | generateIndex : Seed -> ( Int, Seed ) 69 | generateIndex = 70 | step <| Random.int 0 maxCactusIndex 71 | 72 | 73 | getSpawnTime : Int -> Float -> Int 74 | getSpawnTime index inc = 75 | let 76 | timeDec = 77 | floor <| inc * 1.8 78 | 79 | withSpawnTime i = 80 | ( i, 45 + 6 * i - timeDec ) 81 | 82 | indices = 83 | range 0 maxCactusIndex 84 | in 85 | withDefault 0 << get index << fromList << map withSpawnTime <| indices 86 | 87 | 88 | maxCactusIndex : Int 89 | maxCactusIndex = 90 | 5 91 | -------------------------------------------------------------------------------- /src/Hud.elm: -------------------------------------------------------------------------------- 1 | module Hud exposing (Model, Msg(..), init, update, view) 2 | 3 | import WindowSize exposing (..) 4 | import Svg exposing (Svg, Attribute) 5 | import Svg.Attributes exposing (..) 6 | 7 | 8 | type alias Model = 9 | { score : Int 10 | , highScore : Int 11 | , state : State 12 | } 13 | 14 | 15 | type State 16 | = Normal 17 | | Highlighted 18 | 19 | 20 | init : Model 21 | init = 22 | { score = 0 23 | , highScore = 0 24 | , state = Normal 25 | } 26 | 27 | 28 | type Msg 29 | = IncScore 30 | | Reset 31 | | Highlight 32 | 33 | 34 | update : Msg -> Model -> Model 35 | update msg ({ score, highScore } as model) = 36 | case msg of 37 | IncScore -> 38 | { model | score = score + 100 } 39 | 40 | Reset -> 41 | { model 42 | | score = 0 43 | , state = Normal 44 | } 45 | 46 | Highlight -> 47 | let 48 | newHighScore = 49 | if score > highScore then 50 | score 51 | else 52 | highScore 53 | in 54 | { model 55 | | highScore = newHighScore 56 | , state = Highlighted 57 | } 58 | 59 | 60 | view : Model -> Svg {} 61 | view hud = 62 | Svg.svg [] 63 | [ renderOutline hud 64 | , renderScore hud 65 | , renderHighScore hud 66 | ] 67 | 68 | 69 | renderOutline : Model -> Svg {} 70 | renderOutline { state } = 71 | let 72 | ( col, alpha ) = 73 | highlightStyles state 74 | in 75 | Svg.rect 76 | [ style <| "fill:none;stroke:" ++ col ++ ";stroke-width:5;opacity:" ++ alpha 77 | , x "0" 78 | , y "0" 79 | , width (toString <| windowWidth) 80 | , height (toString <| windowHeight) 81 | ] 82 | [] 83 | 84 | 85 | renderScore : Model -> Svg {} 86 | renderScore { score, state } = 87 | let 88 | ( col, alpha ) = 89 | highlightStyles state 90 | 91 | attrs = 92 | [ x "20" 93 | , y "40" 94 | , fill col 95 | , opacity alpha 96 | , fontSize "25" 97 | , textAnchor "start" 98 | ] 99 | in 100 | Svg.text_ attrs [ Svg.text <| "Score: " ++ (toString score) ] 101 | 102 | 103 | renderHighScore : Model -> Svg {} 104 | renderHighScore { highScore, state } = 105 | let 106 | ( col, alpha ) = 107 | highlightStyles state 108 | 109 | attrs = 110 | [ x "980" 111 | , y "40" 112 | , fill col 113 | , opacity alpha 114 | , fontSize "25" 115 | , textAnchor "end" 116 | ] 117 | in 118 | Svg.text_ attrs [ Svg.text <| "High Score: " ++ (toString highScore) ] 119 | 120 | 121 | highlightStyles : State -> ( String, String ) 122 | highlightStyles state = 123 | if state == Highlighted then 124 | ( "red", "0.85" ) 125 | else 126 | ( "black", "0.6" ) 127 | -------------------------------------------------------------------------------- /src/Rex.elm: -------------------------------------------------------------------------------- 1 | module Rex exposing (Model, Msg(..), init, update, hasLandedFromJumping, hitDetected, view) 2 | 3 | import Cactus 4 | import WindowSize exposing (..) 5 | import Svg exposing (Svg, Attribute) 6 | import Svg.Attributes as Attributes exposing (..) 7 | import Time exposing (Time) 8 | 9 | 10 | type alias Model = 11 | { state : State 12 | , yPos : Float 13 | , yVel : Float 14 | , width : Float 15 | , height : Float 16 | , runCount : Int 17 | , frameInc : Int 18 | } 19 | 20 | 21 | type State 22 | = Idle 23 | | Running 24 | | Jumping 25 | | Ducking 26 | | Dead 27 | 28 | 29 | init : Model 30 | init = 31 | { state = Idle 32 | , yPos = 0 33 | , yVel = 0 34 | , width = sizeRunning.width 35 | , height = sizeRunning.height 36 | , runCount = 1 37 | , frameInc = 0 38 | } 39 | 40 | 41 | type Msg 42 | = Run 43 | | Jump 44 | | Duck 45 | | Kill 46 | | Tick Float 47 | 48 | 49 | update : Msg -> Model -> Model 50 | update msg model = 51 | case msg of 52 | Run -> 53 | case model.state of 54 | Jumping -> 55 | model 56 | 57 | _ -> 58 | animate Running model 59 | 60 | Jump -> 61 | case model.state of 62 | Jumping -> 63 | model 64 | 65 | _ -> 66 | initJump 1.3 <| animate Jumping model 67 | 68 | Duck -> 69 | case model.state of 70 | Jumping -> 71 | model 72 | 73 | _ -> 74 | animate Ducking model 75 | 76 | Kill -> 77 | initJump 0.7 <| animate Dead model 78 | 79 | Tick delta -> 80 | case model.state of 81 | Jumping -> 82 | updateAirbourne delta model 83 | 84 | Dead -> 85 | updateAirbourne delta model 86 | 87 | Running -> 88 | animate Running model 89 | 90 | Ducking -> 91 | animate Ducking model 92 | 93 | _ -> 94 | model 95 | 96 | 97 | hasLandedFromJumping : Model -> Bool 98 | hasLandedFromJumping rex = 99 | rex.yPos >= 0 && rex.state == Jumping 100 | 101 | 102 | initJump : Float -> Model -> Model 103 | initJump force rex = 104 | { rex 105 | | yPos = -force 106 | , yVel = -force 107 | } 108 | 109 | 110 | updateAirbourne : Time -> Model -> Model 111 | updateAirbourne delta ({ yPos, yVel, state } as rex) = 112 | let 113 | gravity = 114 | 0.005 115 | 116 | ( state_, yPos_, yVel_ ) = 117 | if (hasLandedFromJumping rex) then 118 | ( Running, 0, 0 ) 119 | else 120 | ( state, yPos + yVel * delta, yVel + gravity * delta ) 121 | in 122 | { rex 123 | | yPos = yPos_ 124 | , yVel = yVel_ 125 | , state = state_ 126 | } 127 | 128 | 129 | animate : State -> Model -> Model 130 | animate state ({ runCount, frameInc } as model) = 131 | let 132 | size = 133 | case state of 134 | Ducking -> 135 | sizeDucking 136 | 137 | Dead -> 138 | sizeDucking 139 | 140 | _ -> 141 | sizeRunning 142 | in 143 | { model 144 | | state = state 145 | , width = size.width 146 | , height = size.height 147 | , runCount = runCount + frameInc 148 | , frameInc = 1 - frameInc 149 | } 150 | 151 | 152 | hitDetected : Model -> List Cactus.Model -> Bool 153 | hitDetected rex obstacles = 154 | case obstacles of 155 | [] -> 156 | False 157 | 158 | elem :: rest -> 159 | let 160 | { xMin, xMax, yMin, yMax } = 161 | bounds rex 162 | 163 | { exMin, exMax, eyMin, eyMax } = 164 | Cactus.bounds elem 165 | 166 | ( margin, marginAfter ) = 167 | if rex.state == Jumping then 168 | ( 25, 20 ) 169 | else 170 | ( 4, 10 ) 171 | 172 | xInBounds = 173 | margin < xMax - exMin && marginAfter < exMax - xMin 174 | 175 | yInBounds = 176 | margin < yMax - eyMin && margin < eyMax - yMin 177 | in 178 | if xInBounds && yInBounds then 179 | True 180 | else 181 | hitDetected rex rest 182 | 183 | 184 | view : Model -> Svg Msg 185 | view rex = 186 | let 187 | { xMin, yMin } = 188 | bounds rex 189 | in 190 | Svg.image 191 | [ x <| toString xMin 192 | , y <| toString yMin 193 | , width <| toString rex.width 194 | , height <| toString rex.height 195 | , xlinkHref <| render rex 196 | ] 197 | [] 198 | 199 | 200 | render : Model -> String 201 | render { state, yVel, runCount } = 202 | let 203 | runningIndex = 204 | toString <| runCount % 6 205 | 206 | airbourneIndex = 207 | toString <| 208 | if yVel < 0.2 then 209 | 0 210 | else 211 | 1 212 | 213 | toImg action = 214 | "images/rex/" ++ action ++ ".png" 215 | in 216 | case state of 217 | Idle -> 218 | toImg <| "idle" 219 | 220 | Running -> 221 | toImg <| "run_" ++ runningIndex 222 | 223 | Jumping -> 224 | toImg <| "jump_" ++ airbourneIndex 225 | 226 | Ducking -> 227 | toImg <| "duck_" ++ runningIndex 228 | 229 | Dead -> 230 | toImg <| "dead_" ++ airbourneIndex 231 | 232 | 233 | sizeRunning : { width : Float, height : Float } 234 | sizeRunning = 235 | { width = 92, height = 84 } 236 | 237 | 238 | sizeDucking : { width : Float, height : Float } 239 | sizeDucking = 240 | { width = 108, height = 60 } 241 | 242 | 243 | bounds : Model -> { xMin : Float, xMax : Float, yMin : Float, yMax : Float } 244 | bounds rex = 245 | let 246 | ( offsetX, offsetY ) = 247 | ( offsetFromLeftEdge, windowHeight - offsetFromBottomEdge + rex.yPos ) 248 | in 249 | { xMin = offsetX 250 | , xMax = offsetX + rex.width 251 | , yMin = offsetY - rex.height 252 | , yMax = offsetY 253 | } 254 | 255 | 256 | offsetFromLeftEdge : Float 257 | offsetFromLeftEdge = 258 | 50 259 | 260 | 261 | offsetFromBottomEdge : Float 262 | offsetFromBottomEdge = 263 | 60 264 | -------------------------------------------------------------------------------- /src/Game.elm: -------------------------------------------------------------------------------- 1 | module Game exposing (..) 2 | 3 | import Hud 4 | import Rex 5 | import Cactus 6 | import CactusGenerator as CactusGen 7 | import Cloud 8 | import Background 9 | import WindowSize exposing (..) 10 | import Html exposing (Html, programWithFlags, h1, h5, div, map, a, text) 11 | import Time exposing (Time) 12 | import Keyboard exposing (KeyCode) 13 | import Svg exposing (Svg) 14 | import Svg.Attributes exposing (..) 15 | import AnimationFrame 16 | import Random exposing (Seed, step) 17 | 18 | 19 | type GameState 20 | = New 21 | | Playing 22 | | Paused 23 | | GameOver 24 | 25 | 26 | type alias Model = 27 | { state : GameState 28 | , hud : Hud.Model 29 | , rex : Rex.Model 30 | , cactusGen : CactusGen.Model 31 | , cloud : Cloud.Model 32 | , seed : Seed 33 | } 34 | 35 | 36 | init : Seed -> Model 37 | init seed = 38 | { state = New 39 | , hud = Hud.init 40 | , rex = Rex.init 41 | , cactusGen = CactusGen.init seed 42 | , cloud = Cloud.init 43 | , seed = nextSeed seed 44 | } 45 | 46 | 47 | restart : Model -> Model 48 | restart game = 49 | { state = New 50 | , hud = Hud.update Hud.Reset game.hud 51 | , rex = Rex.init 52 | , cactusGen = CactusGen.init game.seed 53 | , cloud = game.cloud 54 | , seed = nextSeed game.seed 55 | } 56 | 57 | 58 | type Msg 59 | = Tick Time 60 | | KeyPressed KeyCode 61 | | KeyReleased 62 | | SubMsg 63 | 64 | 65 | update : Msg -> Model -> Model 66 | update msg game = 67 | case game.state of 68 | Playing -> 69 | updatePlaying msg game 70 | 71 | GameOver -> 72 | updateGameOver msg game 73 | 74 | _ -> 75 | updatePaused msg game 76 | 77 | 78 | updatePlaying : Msg -> Model -> Model 79 | updatePlaying msg ({ hud, rex, cactusGen, cloud } as game) = 80 | if (spacePressed msg) then 81 | { game | state = Paused } 82 | else 83 | case msg of 84 | KeyPressed code -> 85 | { game | rex = Rex.update (codeToRexMsg code) rex } 86 | 87 | KeyReleased -> 88 | { game | rex = Rex.update Rex.Run rex } 89 | 90 | Tick delta -> 91 | if Rex.hitDetected rex cactusGen.cacti then 92 | { game 93 | | state = GameOver 94 | , hud = Hud.update Hud.Highlight hud 95 | , rex = Rex.update Rex.Kill rex 96 | , cloud = Cloud.update delta cloud 97 | } 98 | else 99 | { game 100 | | rex = Rex.update (Rex.Tick delta) rex 101 | , cactusGen = CactusGen.update delta hud.score cactusGen 102 | , cloud = Cloud.update delta cloud 103 | , hud = 104 | if (Rex.hasLandedFromJumping rex) then 105 | Hud.update Hud.IncScore hud 106 | else 107 | hud 108 | } 109 | 110 | SubMsg -> 111 | game 112 | 113 | 114 | updatePaused : Msg -> Model -> Model 115 | updatePaused msg game = 116 | if (spacePressed msg) then 117 | { game | state = Playing } 118 | else 119 | game 120 | 121 | 122 | updateGameOver : Msg -> Model -> Model 123 | updateGameOver msg game = 124 | if (spacePressed msg) then 125 | restart game 126 | else 127 | case msg of 128 | Tick delta -> 129 | { game 130 | | rex = Rex.update (Rex.Tick delta) game.rex 131 | , cloud = Cloud.update delta game.cloud 132 | } 133 | 134 | _ -> 135 | game 136 | 137 | 138 | codeToRexMsg : KeyCode -> Rex.Msg 139 | codeToRexMsg code = 140 | case code of 141 | 40 -> 142 | Rex.Duck 143 | 144 | 38 -> 145 | Rex.Jump 146 | 147 | _ -> 148 | Rex.Run 149 | 150 | 151 | spacePressed : Msg -> Bool 152 | spacePressed msg = 153 | msg == KeyPressed 32 154 | 155 | 156 | subscriptions : Model -> Sub Msg 157 | subscriptions _ = 158 | Sub.batch 159 | [ AnimationFrame.diffs Tick 160 | , Keyboard.downs KeyPressed 161 | , Keyboard.ups (\_ -> KeyReleased) 162 | ] 163 | 164 | 165 | view : Model -> Html Msg 166 | view { state, hud, rex, cactusGen, cloud } = 167 | let 168 | ( w, h ) = 169 | ( toString windowWidth, toString windowHeight ) 170 | 171 | attributes = 172 | [ width w 173 | , height h 174 | , viewBox <| "0 0 " ++ w ++ " " ++ h 175 | , version "1.1" 176 | ] 177 | 178 | sceneElements = 179 | [ viewBackground 180 | , viewCacti cactusGen.cacti 181 | , viewCloud cloud 182 | , viewRex rex 183 | , viewAlert state hud.score 184 | , viewHud hud 185 | ] 186 | in 187 | Svg.svg attributes sceneElements 188 | 189 | 190 | viewAlert : GameState -> Int -> Svg Msg 191 | viewAlert state score = 192 | let 193 | yMiddle = 194 | windowHeight / 2 195 | 196 | attrBase = 197 | [ x << toString <| windowWidth / 2 198 | , textAnchor "middle" 199 | , fill "#C12" 200 | ] 201 | 202 | attrLarge yPos = 203 | [ y << toString <| yMiddle + yPos 204 | , fontSize "60" 205 | ] 206 | ++ attrBase 207 | 208 | attrSmall yPos = 209 | [ y << toString <| yMiddle + yPos 210 | , fontSize "18" 211 | ] 212 | ++ attrBase 213 | in 214 | case state of 215 | New -> 216 | Svg.svg [] 217 | [ Svg.text_ (attrLarge -50) [ Svg.text "RAWЯ!" ] 218 | , Svg.text_ (attrSmall 0) [ Svg.text "Play using the arrow keys: ↑ ↓" ] 219 | , Svg.text_ (attrSmall 35) [ Svg.text "Press SPACE to start / pause" ] 220 | ] 221 | 222 | Paused -> 223 | Svg.svg [] 224 | [ Svg.text_ (attrLarge -50) [ Svg.text "Paused!" ] 225 | , Svg.text_ (attrSmall 0) [ Svg.text "Play using the arrow keys: ↑ ↓" ] 226 | , Svg.text_ (attrSmall 35) [ Svg.text "Press SPACE to continue" ] 227 | ] 228 | 229 | GameOver -> 230 | Svg.svg [] 231 | [ Svg.text_ (attrLarge -40) [ Svg.text "Game Ovər!" ] 232 | , Svg.text_ (attrSmall 25) [ Svg.text "Press SPACE to try again" ] 233 | ] 234 | 235 | Playing -> 236 | Svg.svg [] [] 237 | 238 | 239 | viewBackground : Svg Msg 240 | viewBackground = 241 | map (\_ -> SubMsg) Background.view 242 | 243 | 244 | viewCacti : List Cactus.Model -> Svg Msg 245 | viewCacti elems = 246 | let 247 | render elem = 248 | map (\_ -> SubMsg) (Cactus.view elem) 249 | in 250 | Svg.svg [] <| List.map render elems 251 | 252 | 253 | viewCloud : Cloud.Model -> Svg Msg 254 | viewCloud cloud = 255 | map (\_ -> SubMsg) (Cloud.view cloud) 256 | 257 | 258 | viewRex : Rex.Model -> Svg Msg 259 | viewRex rex = 260 | map (\_ -> SubMsg) (Rex.view rex) 261 | 262 | 263 | viewHud : Hud.Model -> Svg Msg 264 | viewHud hud = 265 | map (\_ -> SubMsg) (Hud.view hud) 266 | 267 | 268 | nextSeed : Seed -> Seed 269 | nextSeed seed = 270 | Tuple.second <| step Random.bool seed 271 | --------------------------------------------------------------------------------