├── .gitignore ├── Licence ├── Main.elm ├── Readme.md ├── elm-package.json └── src ├── Types.elm ├── Views.elm └── WebGLViews.elm /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff 2 | -------------------------------------------------------------------------------- /Licence: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Thibaut Assus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Main.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (..) 2 | 3 | import Time exposing (Time, second) 4 | import Keyboard.Extra 5 | import Random 6 | import Html 7 | import Types exposing (..) 8 | import WebGLViews 9 | import Views 10 | import Window exposing (Size) 11 | import Task 12 | 13 | 14 | initialKeyboard : Keyboard.Extra.Model 15 | initialKeyboard = 16 | Tuple.first Keyboard.Extra.init 17 | 18 | 19 | init : ( Model, Cmd Msg ) 20 | init = 21 | ( Model 22 | [ ( 0, 0 ) ] 23 | ( 0, 0 ) 24 | ( 0, 0 ) 25 | ( 1, 0 ) 26 | initialKeyboard 27 | (Size 0 0) 28 | , Cmd.batch 29 | [ generateNewApple 30 | , Task.perform Resize Window.size 31 | ] 32 | ) 33 | 34 | 35 | main : Program Never Model Msg 36 | main = 37 | Html.program 38 | { init = init 39 | , view = metaView 40 | , update = update 41 | , subscriptions = subscriptions 42 | } 43 | 44 | 45 | subscriptions : Model -> Sub Msg 46 | subscriptions model = 47 | Sub.batch 48 | [ Sub.map KeyboardMsg Keyboard.Extra.subscriptions 49 | , Time.every (second / config.fps) TickControl 50 | , Time.every (second / config.tps) Tick 51 | , Window.resizes Resize 52 | ] 53 | 54 | 55 | dropLastVertebra = 56 | List.reverse 57 | << List.drop 1 58 | << List.reverse 59 | 60 | 61 | calculateNewVertebra ( x, y ) ( directionX, directionY ) = 62 | ( directionX + x 63 | , directionY + y 64 | ) 65 | 66 | 67 | addNewVertebra : ( Int, Int ) -> List ( Int, Int ) -> List ( Int, Int ) 68 | addNewVertebra direction snake = 69 | let 70 | currentHead = 71 | snake 72 | |> List.head 73 | |> Maybe.withDefault ( 0, 0 ) 74 | 75 | newVertebra = 76 | calculateNewVertebra currentHead direction 77 | in 78 | (newVertebra :: snake) 79 | 80 | 81 | collision = 82 | List.member 83 | 84 | 85 | generateNewApple = 86 | Random.generate NewApple 87 | (Random.pair 88 | (Random.int 1 config.max) 89 | (Random.int 1 config.max) 90 | ) 91 | 92 | 93 | between minimum maximum value = 94 | value >= minimum && value <= maximum 95 | 96 | 97 | out minimum maximum ( x, y ) = 98 | let 99 | betweenBorders = 100 | between minimum maximum 101 | in 102 | (not <| betweenBorders y) 103 | || (not <| betweenBorders x) 104 | 105 | 106 | collisionWithHimselfOrWall snake = 107 | case snake of 108 | head :: tail -> 109 | collision head tail || out 0 config.max head 110 | 111 | [] -> 112 | False 113 | 114 | 115 | moveSnake : Model -> ( Model, Cmd Msg ) 116 | moveSnake ({ direction, snake, apple } as model) = 117 | let 118 | movedSnake = 119 | snake 120 | |> addNewVertebra direction 121 | 122 | appleEaten = 123 | movedSnake 124 | |> collision apple 125 | 126 | finalSnake = 127 | if appleEaten then 128 | movedSnake 129 | else 130 | movedSnake |> dropLastVertebra 131 | in 132 | if collisionWithHimselfOrWall movedSnake then 133 | init 134 | else 135 | ( { model | snake = finalSnake } 136 | , if appleEaten then 137 | generateNewApple 138 | else 139 | Cmd.none 140 | ) 141 | 142 | 143 | applyKeyboard ( arrowX, arrowY ) (( snakeDirectionX, snakeDirectionY ) as snakeDirection) = 144 | if arrowX /= 0 && snakeDirectionX == 0 then 145 | ( arrowX, 0 ) 146 | else if arrowY /= 0 && snakeDirectionY == 0 then 147 | ( 0, -arrowY ) 148 | else 149 | snakeDirection 150 | 151 | 152 | updateDirection : Model -> Model 153 | updateDirection model = 154 | let 155 | newDirection = 156 | model.direction 157 | |> applyKeyboard model.arrows 158 | in 159 | { model | direction = newDirection } 160 | 161 | 162 | handleKeyboard : Model -> Keyboard.Extra.Msg -> ( Model, Cmd Msg ) 163 | handleKeyboard model keyMsg = 164 | let 165 | ( keyboardModel, keyboardCmd ) = 166 | Keyboard.Extra.update keyMsg model.keyboardModel 167 | 168 | arrows = 169 | Keyboard.Extra.arrows keyboardModel 170 | 171 | newArrows : ( Int, Int ) 172 | newArrows = 173 | ( arrows.x, arrows.y ) 174 | in 175 | ( { model 176 | | keyboardModel = keyboardModel 177 | , arrows = newArrows 178 | } 179 | , Cmd.map KeyboardMsg keyboardCmd 180 | ) 181 | 182 | 183 | 184 | -- asciiView model = 185 | -- Html.pre [] 186 | -- [ Html.text "" 187 | -- ] 188 | -- 189 | 190 | 191 | metaView model = 192 | Html.div [] 193 | [ Views.view model 194 | , WebGLViews.view model 195 | 196 | -- , asciiView model 197 | ] 198 | 199 | 200 | addNewApple : ( Int, Int ) -> Model -> ( Model, Cmd Msg ) 201 | addNewApple newApple model = 202 | if collision newApple model.snake then 203 | ( model, generateNewApple ) 204 | else 205 | ( { model | apple = newApple }, Cmd.none ) 206 | 207 | 208 | update : Msg -> Model -> ( Model, Cmd Msg ) 209 | update msg model = 210 | case msg of 211 | KeyboardMsg keyMsg -> 212 | handleKeyboard model keyMsg 213 | 214 | TickControl time -> 215 | ( model |> updateDirection, Cmd.none ) 216 | 217 | Tick time -> 218 | model 219 | |> moveSnake 220 | 221 | NewApple newApple -> 222 | model 223 | |> addNewApple newApple 224 | 225 | Resize size -> 226 | ( { model | size = size }, Cmd.none ) 227 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Elm Lang Snake in WebGL and Html (look at the source for fun and profit) 2 | 3 | [DEMO](http://tibastral.github.io/elm-snake) 4 | 5 | ## Explanation Video of How we did it : 6 | 7 | [part 1](https://youtu.be/FeW9bXZipyg) 8 | [part 2](https://youtu.be/b9l7Jda3fnA) 9 | [part 3](https://youtu.be/z8H79HTm-CE) 10 | 11 | If you want to know more about it, come to [elmeurope.org](https://elmeurope.org), the conference we organize about Elm ! 12 | 13 | ## First iteration 14 | 15 | ![](https://cloud.githubusercontent.com/assets/43472/23854545/258d9124-07f2-11e7-923c-100438432f44.gif) 16 | 17 | ## With the lighting and the sphere 18 | 19 | ![](https://cloud.githubusercontent.com/assets/43472/25152068/22e20a74-2488-11e7-9909-bcab2a41851c.gif) 20 | 21 | ## With planar shadows 22 | 23 | ![](https://cloud.githubusercontent.com/assets/43472/25554879/4040bf3a-2cd9-11e7-897a-adbbd7ab7b06.gif) 24 | 25 | By @tibastral and @w0rm (@unsoundscapes on twitter) 26 | -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "helpful summary of your project, less than 80 characters", 4 | "repository": "https://github.com/user/project.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | ".", 8 | "src" 9 | ], 10 | "exposed-modules": [], 11 | "dependencies": { 12 | "elm-community/linear-algebra": "3.1.0 <= v < 4.0.0", 13 | "elm-community/webgl": "2.0.3 <= v < 3.0.0", 14 | "elm-lang/core": "5.1.1 <= v < 6.0.0", 15 | "elm-lang/html": "2.0.0 <= v < 3.0.0", 16 | "elm-lang/http": "1.0.0 <= v < 2.0.0", 17 | "elm-lang/keyboard": "1.0.1 <= v < 2.0.0", 18 | "elm-lang/mouse": "1.0.1 <= v < 2.0.0", 19 | "elm-lang/window": "1.0.1 <= v < 2.0.0", 20 | "ohanhi/keyboard-extra": "1.2.0 <= v < 2.0.0" 21 | }, 22 | "elm-version": "0.18.0 <= v < 0.19.0" 23 | } 24 | -------------------------------------------------------------------------------- /src/Types.elm: -------------------------------------------------------------------------------- 1 | module Types exposing (..) 2 | 3 | import Keyboard.Extra 4 | import Time exposing (Time, second) 5 | import Window exposing (Size) 6 | 7 | 8 | type alias Position = 9 | ( Int, Int ) 10 | 11 | 12 | type alias Model = 13 | { snake : List Position 14 | , apple : Position 15 | , arrows : Position 16 | , direction : Position 17 | , keyboardModel : Keyboard.Extra.Model 18 | , size : Size 19 | } 20 | 21 | 22 | type Msg 23 | = Tick Time 24 | | TickControl Time 25 | | KeyboardMsg Keyboard.Extra.Msg 26 | | NewApple ( Int, Int ) 27 | | Resize Size 28 | 29 | 30 | config = 31 | { fps = 60 32 | , tps = 5 33 | , max = 10 34 | , spriteSize = 20 35 | } 36 | -------------------------------------------------------------------------------- /src/Views.elm: -------------------------------------------------------------------------------- 1 | module Views exposing (..) 2 | 3 | import Types exposing (..) 4 | import Html exposing (..) 5 | import Html.Attributes exposing (..) 6 | 7 | 8 | toPx val = 9 | (val |> toString) ++ "px" 10 | 11 | 12 | wallsView = 13 | div 14 | [ style 15 | [ ( "width", (config.max + 1) * config.spriteSize |> toPx ) 16 | , ( "height", (config.max + 1) * config.spriteSize |> toPx ) 17 | , ( "background-color", "black" ) 18 | , ( "position", "absolute" ) 19 | ] 20 | ] 21 | [] 22 | 23 | 24 | spriteView val ( x, y ) = 25 | div 26 | [ style 27 | [ ( "position", "absolute" ) 28 | , ( "top", y * config.spriteSize |> toPx ) 29 | , ( "left", x * config.spriteSize |> toPx ) 30 | ] 31 | ] 32 | [ text val ] 33 | 34 | 35 | appleView = 36 | spriteView "🍎" 37 | 38 | 39 | vertebraView = 40 | spriteView "🐍" 41 | 42 | 43 | snakeView = 44 | List.map vertebraView 45 | 46 | 47 | worldView { apple, snake } = 48 | div [] (wallsView :: (appleView apple) :: (snakeView snake)) 49 | 50 | 51 | scoreView { snake } = 52 | div [ style [ ( "position", "absolute" ), ( "color", "white" ) ] ] 53 | [ (text ((List.length snake - 1) |> toString)) ] 54 | 55 | 56 | view : Model -> Html.Html Msg 57 | view model = 58 | div [] 59 | [ worldView model 60 | , scoreView model 61 | ] 62 | -------------------------------------------------------------------------------- /src/WebGLViews.elm: -------------------------------------------------------------------------------- 1 | module WebGLViews exposing (..) 2 | 3 | import Types exposing (..) 4 | import Html exposing (..) 5 | import Html.Attributes exposing (..) 6 | import WebGL exposing (Mesh, Shader, Entity) 7 | import Math.Vector3 as Vec3 exposing (Vec3, vec3) 8 | import Math.Matrix4 exposing (Mat4, mul) 9 | import WebGL.Settings.DepthTest as DepthTest 10 | import WebGL.Settings.StencilTest as StencilTest 11 | 12 | 13 | type alias Attributes = 14 | { position : Vec3 15 | , normal : Vec3 16 | } 17 | 18 | 19 | {-| Direction of the light 20 | -} 21 | light : Vec3 22 | light = 23 | vec3 -1 1 3 |> Vec3.normalize 24 | 25 | 26 | {-| Normal to the floor 27 | -} 28 | floorNormal : Vec3 29 | floorNormal = 30 | vec3 0 0 -1 |> Vec3.normalize 31 | 32 | 33 | {-| Camera projection matrix 34 | -} 35 | camera : Float -> Mat4 36 | camera ratio = 37 | let 38 | c = 39 | toFloat config.max / 2 40 | 41 | eye = 42 | vec3 c -c 15 43 | 44 | center = 45 | vec3 c c 0 46 | in 47 | mul (Math.Matrix4.makePerspective 45 ratio 0.01 100) 48 | (Math.Matrix4.makeLookAt eye center Vec3.j) 49 | 50 | 51 | {-| Adds a normal to the attributes 52 | -} 53 | attributes : Vec3 -> Vec3 -> Vec3 -> ( Attributes, Attributes, Attributes ) 54 | attributes p1 p2 p3 = 55 | let 56 | normal = 57 | Vec3.cross (Vec3.sub p1 p2) (Vec3.sub p1 p3) |> Vec3.normalize 58 | in 59 | ( Attributes p1 normal, Attributes p2 normal, Attributes p3 normal ) 60 | 61 | 62 | {-| A "squash" matrix that smashes things to the ground plane, 63 | defined by a normal, parallel to a given light vector 64 | -} 65 | shadow : Vec3 -> Vec3 -> Mat4 66 | shadow normal light = 67 | let 68 | p = 69 | Vec3.toRecord normal 70 | 71 | l = 72 | Vec3.toRecord light 73 | 74 | d = 75 | Vec3.dot normal light 76 | in 77 | Math.Matrix4.fromRecord 78 | { m11 = p.x * l.x - d 79 | , m21 = p.x * l.y 80 | , m31 = p.x * l.z 81 | , m41 = 0 82 | , m12 = p.y * l.x 83 | , m22 = p.y * l.y - d 84 | , m32 = p.y * l.z 85 | , m42 = 0 86 | , m13 = p.z * l.x 87 | , m23 = p.z * l.y 88 | , m33 = p.z * l.z - d 89 | , m43 = 0 90 | , m14 = 0 91 | , m24 = 0 92 | , m34 = 0 93 | , m44 = -d 94 | } 95 | 96 | 97 | cube : Mesh Attributes 98 | cube = 99 | WebGL.triangles 100 | [ -- back 101 | attributes (vec3 -0.5 0.5 -0.5) (vec3 -0.5 0.5 0.5) (vec3 0.5 0.5 0.5) 102 | , attributes (vec3 0.5 0.5 0.5) (vec3 0.5 0.5 -0.5) (vec3 -0.5 0.5 -0.5) 103 | -- top 104 | , attributes (vec3 -0.5 0.5 0.5) (vec3 -0.5 -0.5 0.5) (vec3 0.5 0.5 0.5) 105 | , attributes (vec3 -0.5 -0.5 0.5) (vec3 0.5 -0.5 0.5) (vec3 0.5 0.5 0.5) 106 | -- right 107 | , attributes (vec3 0.5 0.5 0.5) (vec3 0.5 -0.5 0.5) (vec3 0.5 -0.5 -0.5) 108 | , attributes (vec3 0.5 -0.5 -0.5) (vec3 0.5 0.5 -0.5) (vec3 0.5 0.5 0.5) 109 | -- left 110 | , attributes (vec3 -0.5 0.5 -0.5) (vec3 -0.5 -0.5 0.5) (vec3 -0.5 0.5 0.5) 111 | , attributes (vec3 -0.5 0.5 -0.5) (vec3 -0.5 -0.5 -0.5) (vec3 -0.5 -0.5 0.5) 112 | -- bottom 113 | , attributes (vec3 -0.5 0.5 -0.5) (vec3 0.5 0.5 -0.5) (vec3 0.5 -0.5 -0.5) 114 | , attributes (vec3 -0.5 0.5 -0.5) (vec3 0.5 -0.5 -0.5) (vec3 -0.5 -0.5 -0.5) 115 | -- front 116 | , attributes (vec3 -0.5 -0.5 -0.5) (vec3 0.5 -0.5 0.5) (vec3 -0.5 -0.5 0.5) 117 | , attributes (vec3 0.5 -0.5 0.5) (vec3 -0.5 -0.5 -0.5) (vec3 0.5 -0.5 -0.5) 118 | ] 119 | 120 | 121 | square : Mesh Attributes 122 | square = 123 | WebGL.triangles 124 | [ attributes (vec3 -0.5 0.5 0) (vec3 -0.5 -0.5 0) (vec3 0.5 0.5 0) 125 | , attributes (vec3 -0.5 -0.5 0) (vec3 0.5 -0.5 0) (vec3 0.5 0.5 0) 126 | ] 127 | 128 | 129 | sphere : Mesh Attributes 130 | sphere = 131 | divideSphere 5 octahedron 132 | |> List.map (\( p1, p2, p3 ) -> attributes p1 p2 p3) 133 | |> WebGL.triangles 134 | 135 | 136 | {-| Recursively divide an octahedron to turn it into a sphere 137 | -} 138 | divideSphere : Int -> List ( Vec3, Vec3, Vec3 ) -> List ( Vec3, Vec3, Vec3 ) 139 | divideSphere step triangles = 140 | if step == 0 then 141 | triangles 142 | else 143 | divideSphere (step - 1) (List.concatMap divide triangles) 144 | 145 | 146 | {-| 147 | 1 148 | / \ 149 | b /___\ c 150 | /\ /\ 151 | /__\ /__\ 152 | 0 a 2 153 | -} 154 | divide : ( Vec3, Vec3, Vec3 ) -> List ( Vec3, Vec3, Vec3 ) 155 | divide ( v0, v1, v2 ) = 156 | let 157 | a = 158 | Vec3.add v0 v2 |> Vec3.normalize |> Vec3.scale 0.5 159 | 160 | b = 161 | Vec3.add v0 v1 |> Vec3.normalize |> Vec3.scale 0.5 162 | 163 | c = 164 | Vec3.add v1 v2 |> Vec3.normalize |> Vec3.scale 0.5 165 | in 166 | [ ( v0, b, a ), ( b, v1, c ), ( a, b, c ), ( a, c, v2 ) ] 167 | 168 | 169 | {-| Octahedron 170 | -} 171 | octahedron : List ( Vec3, Vec3, Vec3 ) 172 | octahedron = 173 | [ ( vec3 0.5 0 0, vec3 0 0.5 0, vec3 0 0 0.5 ) 174 | , ( vec3 0 0.5 0, vec3 -0.5 0 0, vec3 0 0 0.5 ) 175 | , ( vec3 -0.5 0 0, vec3 0 -0.5 0, vec3 0 0 0.5 ) 176 | , ( vec3 0 -0.5 0, vec3 0.5 0 0, vec3 0 0 0.5 ) 177 | , ( vec3 0.5 0 0, vec3 0 0 -0.5, vec3 0 0.5 0 ) 178 | , ( vec3 0 0.5 0, vec3 0 0 -0.5, vec3 -0.5 0 0 ) 179 | , ( vec3 -0.5 0 0, vec3 0 0 -0.5, vec3 0 -0.5 0 ) 180 | , ( vec3 0 -0.5 0, vec3 0 0 -0.5, vec3 0.5 0 0 ) 181 | ] 182 | 183 | 184 | type alias Uniforms = 185 | { color : Vec3 186 | , offset : Vec3 187 | , camera : Mat4 188 | , light : Vec3 189 | } 190 | 191 | 192 | vertebraShadowView : Float -> ( Int, Int ) -> Entity 193 | vertebraShadowView ratio ( x, y ) = 194 | WebGL.entityWith 195 | [ StencilTest.test 196 | { ref = 1 197 | , mask = 0xFF 198 | , test = StencilTest.equal 199 | , fail = StencilTest.keep 200 | , zfail = StencilTest.keep 201 | , zpass = StencilTest.keep 202 | , writeMask = 0 203 | } 204 | , DepthTest.default 205 | ] 206 | shadowVertexShader 207 | shadowFragmentShader 208 | cube 209 | (Uniforms 210 | (vec3 0.32 0.16 0.24) 211 | (vec3 (toFloat x) (toFloat (config.max - y)) 0.5) 212 | (Math.Matrix4.mul (camera ratio) (shadow floorNormal light)) 213 | light 214 | ) 215 | 216 | 217 | appleShadowView : Float -> ( Int, Int ) -> Entity 218 | appleShadowView ratio ( x, y ) = 219 | WebGL.entityWith 220 | [ StencilTest.test 221 | { ref = 1 222 | , mask = 0xFF 223 | , test = StencilTest.equal 224 | , fail = StencilTest.keep 225 | , zfail = StencilTest.keep 226 | , zpass = StencilTest.keep 227 | , writeMask = 0 228 | } 229 | , DepthTest.default 230 | ] 231 | shadowVertexShader 232 | shadowFragmentShader 233 | sphere 234 | (Uniforms 235 | (vec3 0.32 0.16 0.24) 236 | (vec3 (toFloat x) (toFloat (config.max - y)) 0.5) 237 | (Math.Matrix4.mul (camera ratio) (shadow floorNormal light)) 238 | light 239 | ) 240 | 241 | 242 | vertebraView : Float -> ( Int, Int ) -> Entity 243 | vertebraView ratio ( x, y ) = 244 | WebGL.entity 245 | vertexShader 246 | fragmentShader 247 | cube 248 | (Uniforms 249 | (vec3 0 1 0) 250 | (vec3 (toFloat x) (toFloat (config.max - y)) 0.5) 251 | (camera ratio) 252 | light 253 | ) 254 | 255 | 256 | appleView : Float -> ( Int, Int ) -> Entity 257 | appleView ratio ( x, y ) = 258 | WebGL.entity 259 | vertexShader 260 | fragmentShader 261 | sphere 262 | (Uniforms 263 | (vec3 1 0 0) 264 | (vec3 (toFloat x) (toFloat (config.max - y)) 0.5) 265 | (camera ratio) 266 | light 267 | ) 268 | 269 | 270 | wallsView : Float -> Entity 271 | wallsView ratio = 272 | WebGL.entityWith 273 | [ StencilTest.test 274 | { ref = 1 275 | , mask = 0xFF 276 | , test = StencilTest.always 277 | , fail = StencilTest.keep 278 | , zfail = StencilTest.keep 279 | , zpass = StencilTest.replace 280 | , writeMask = 0xFF 281 | } 282 | ] 283 | vertexShader 284 | fragmentShader 285 | square 286 | (Uniforms (vec3 0.4 0.2 0.3) 287 | (vec3 0 0 0) 288 | (Math.Matrix4.makeScale3 (toFloat config.max + 1) (toFloat config.max + 1) 1 289 | |> Math.Matrix4.mul (Math.Matrix4.makeTranslate3 (toFloat config.max / 2) (toFloat config.max / 2) 0) 290 | |> Math.Matrix4.mul (camera ratio) 291 | ) 292 | light 293 | ) 294 | 295 | 296 | type alias Varying = 297 | { vlighting : Float 298 | } 299 | 300 | 301 | vertexShader : Shader Attributes Uniforms Varying 302 | vertexShader = 303 | [glsl| 304 | attribute vec3 position; 305 | attribute vec3 normal; 306 | uniform vec3 offset; 307 | uniform mat4 camera; 308 | uniform vec3 light; 309 | varying highp float vlighting; 310 | void main () { 311 | highp float ambientLight = 0.5; 312 | highp float directionalLight = 0.5; 313 | gl_Position = camera * vec4(position + offset, 1.0); 314 | vlighting = ambientLight + max(dot(normal, light), 0.0) * directionalLight; 315 | } 316 | |] 317 | 318 | 319 | fragmentShader : Shader {} Uniforms Varying 320 | fragmentShader = 321 | [glsl| 322 | precision mediump float; 323 | varying highp float vlighting; 324 | uniform vec3 color; 325 | void main () { 326 | gl_FragColor = vec4(color * vlighting, 1.0); 327 | } 328 | |] 329 | 330 | 331 | shadowVertexShader : Shader Attributes Uniforms {} 332 | shadowVertexShader = 333 | [glsl| 334 | attribute vec3 position; 335 | uniform vec3 offset; 336 | uniform mat4 camera; 337 | void main () { 338 | gl_Position = camera * vec4(position + offset, 1.0); 339 | } 340 | |] 341 | 342 | 343 | shadowFragmentShader : Shader {} Uniforms {} 344 | shadowFragmentShader = 345 | [glsl| 346 | precision mediump float; 347 | uniform vec3 color; 348 | void main () { 349 | gl_FragColor = vec4(color, 1.0); 350 | } 351 | |] 352 | 353 | 354 | scoreView : Model -> Html Msg 355 | scoreView { snake } = 356 | div [ style [ ( "position", "relative" ), ( "text-align", "center" ), ( "font", "bold 30px/3 sans-serif" ) ] ] 357 | [ (text ((List.length snake - 1) |> toString)) ] 358 | 359 | 360 | worldView : Model -> Html.Html Msg 361 | worldView { apple, snake, size } = 362 | let 363 | ratio = 364 | (toFloat size.width / toFloat size.height) 365 | in 366 | WebGL.toHtmlWith 367 | [ WebGL.alpha True 368 | , WebGL.antialias 369 | , WebGL.depth 1 370 | , WebGL.stencil 0 371 | ] 372 | [ style 373 | [ ( "display", "block" ) 374 | , ( "position", "absolute" ) 375 | ] 376 | , width size.width 377 | , height size.height 378 | ] 379 | (wallsView ratio 380 | :: appleShadowView ratio apple 381 | :: appleView ratio apple 382 | :: List.map (vertebraShadowView ratio) snake 383 | ++ List.map (vertebraView ratio) snake 384 | ) 385 | 386 | 387 | view : Model -> Html.Html Msg 388 | view model = 389 | div [] 390 | [ worldView model 391 | , scoreView model 392 | ] 393 | --------------------------------------------------------------------------------