├── .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 | 
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 |
--------------------------------------------------------------------------------