├── .gitignore ├── README.md ├── Main.elm ├── elm-package.json ├── MultiGame.elm ├── Tests.elm └── LightsGame.elm /.gitignore: -------------------------------------------------------------------------------- 1 | /elm-stuff/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | https://twitch.tv/avh4 2 | 3 | ## Future work 4 | 5 | - Randomly generate boards instead of having a hardcoded defaultBoard 6 | - Make a nicer "You Win" screen 7 | - Add an initial menu screen 8 | -------------------------------------------------------------------------------- /Main.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (..) 2 | 3 | import Html.App 4 | import MultiGame 5 | 6 | 7 | main : Program Never 8 | main = 9 | Html.App.beginnerProgram 10 | { model = MultiGame.init 11 | , update = MultiGame.update 12 | , view = MultiGame.view 13 | } 14 | -------------------------------------------------------------------------------- /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 | ], 9 | "exposed-modules": [], 10 | "dependencies": { 11 | "eeue56/elm-flat-matrix": "3.0.0 <= v < 4.0.0", 12 | "elm-community/elm-test": "2.1.0 <= v < 3.0.0", 13 | "elm-lang/core": "4.0.3 <= v < 5.0.0", 14 | "elm-lang/html": "1.1.0 <= v < 2.0.0", 15 | "rtfeldman/html-test-runner": "1.0.0 <= v < 2.0.0" 16 | }, 17 | "elm-version": "0.17.1 <= v < 0.18.0" 18 | } 19 | -------------------------------------------------------------------------------- /MultiGame.elm: -------------------------------------------------------------------------------- 1 | module MultiGame exposing (..) 2 | 3 | import LightsGame 4 | import Html 5 | import Html.App 6 | 7 | 8 | type alias Model = 9 | { boards : List LightsGame.Model } 10 | 11 | 12 | init = 13 | { boards = 14 | [ LightsGame.initWithDefaultBoard "orange" 15 | , LightsGame.initWithDefaultBoard "blue" 16 | , LightsGame.initWithDefaultBoard "green" 17 | ] 18 | } 19 | 20 | 21 | 22 | -- Update 23 | 24 | 25 | type Msg 26 | = BoardMsg { boardNumber : Int, msg : LightsGame.Msg } 27 | 28 | 29 | update msg model = 30 | case msg of 31 | BoardMsg { boardNumber, msg } -> 32 | { model 33 | | boards = 34 | updateBoard boardNumber msg model.boards 35 | } 36 | 37 | 38 | updateBoard : 39 | Int 40 | -> LightsGame.Msg 41 | -> List LightsGame.Model 42 | -> List LightsGame.Model 43 | updateBoard indexToUpdate msg boards = 44 | List.indexedMap 45 | (\i board -> 46 | if i == indexToUpdate then 47 | LightsGame.update msg board 48 | else 49 | board 50 | ) 51 | boards 52 | 53 | 54 | view : Model -> Html.Html Msg 55 | view model = 56 | model.boards 57 | |> List.indexedMap 58 | (\i board -> 59 | LightsGame.view board 60 | |> Html.App.map 61 | (\msg -> 62 | BoardMsg { boardNumber = i, msg = msg } 63 | ) 64 | ) 65 | |> Html.div [] 66 | -------------------------------------------------------------------------------- /Tests.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (..) 2 | 3 | import Test exposing (describe, test) 4 | import Expect 5 | import LightsGame 6 | import Matrix 7 | import Test.Runner.Html 8 | 9 | 10 | singleLightBoard = 11 | Matrix.fromList [ [ Just True ] ] 12 | |> Maybe.withDefault Matrix.empty 13 | 14 | 15 | singleRowBoard = 16 | Matrix.fromList [ [ Just False, Just False, Just False ] ] 17 | |> Maybe.withDefault Matrix.empty 18 | 19 | 20 | squareBoard = 21 | Matrix.fromList 22 | [ [ Just False, Just False, Just False ] 23 | , [ Just False, Just False, Just False ] 24 | , [ Just False, Just False, Just False ] 25 | ] 26 | |> Maybe.withDefault Matrix.empty 27 | 28 | 29 | all = 30 | describe "LightsGame" 31 | [ test "can toggle a light" <| 32 | \() -> 33 | LightsGame.init singleLightBoard 34 | |> LightsGame.update (LightsGame.Toggle { x = 0, y = 0 }) 35 | |> .isOn 36 | |> Matrix.get 0 0 37 | |> Expect.equal (Just <| Just False) 38 | , test "toggling a light toggles its right neighbor" <| 39 | \() -> 40 | LightsGame.init singleRowBoard 41 | |> LightsGame.update (LightsGame.Toggle { x = 0, y = 0 }) 42 | |> .isOn 43 | |> Matrix.get 1 0 44 | |> Expect.equal (Just <| Just True) 45 | , test "toggling a light toggles its left neighbor" <| 46 | \() -> 47 | LightsGame.init singleRowBoard 48 | |> LightsGame.update (LightsGame.Toggle { x = 2, y = 0 }) 49 | |> .isOn 50 | |> Matrix.get 1 0 51 | |> Expect.equal (Just <| Just True) 52 | , test "toggling a light doesn't toggle non-neighbors" <| 53 | \() -> 54 | LightsGame.init squareBoard 55 | |> LightsGame.update (LightsGame.Toggle { x = 1, y = 1 }) 56 | |> .isOn 57 | |> Matrix.get 0 0 58 | |> Expect.equal (Just <| Just False) 59 | , test "toggling a light toggles its upper neighbor" <| 60 | \() -> 61 | LightsGame.init squareBoard 62 | |> LightsGame.update (LightsGame.Toggle { x = 1, y = 2 }) 63 | |> .isOn 64 | |> Matrix.get 1 1 65 | |> Expect.equal (Just <| Just True) 66 | , test "toggling a light toggles its lower neighbor" <| 67 | \() -> 68 | LightsGame.init squareBoard 69 | |> LightsGame.update (LightsGame.Toggle { x = 1, y = 0 }) 70 | |> .isOn 71 | |> Matrix.get 1 1 72 | |> Expect.equal (Just <| Just True) 73 | , describe "isSolved" 74 | [ test "True when all lights are off" <| 75 | \() -> 76 | LightsGame.init squareBoard 77 | |> LightsGame.isSolved 78 | |> Expect.equal True 79 | , test "False when some lights are on" <| 80 | \() -> 81 | LightsGame.init squareBoard 82 | |> LightsGame.update (LightsGame.Toggle { x = 0, y = 0 }) 83 | |> LightsGame.isSolved 84 | |> Expect.equal False 85 | ] 86 | ] 87 | 88 | 89 | main = 90 | Test.Runner.Html.run all 91 | -------------------------------------------------------------------------------- /LightsGame.elm: -------------------------------------------------------------------------------- 1 | module LightsGame 2 | exposing 3 | ( Model 4 | , init 5 | , initWithDefaultBoard 6 | , isSolved 7 | , Msg(..) 8 | , update 9 | , view 10 | ) 11 | 12 | import Array 13 | import Html 14 | import Html.Attributes 15 | import Html.Events 16 | import Matrix exposing (Matrix) 17 | 18 | 19 | type alias Model = 20 | { color : String 21 | , isOn : Matrix (Maybe Bool) 22 | } 23 | 24 | 25 | init : Matrix (Maybe Bool) -> Model 26 | init startingBoard = 27 | { color = "orange" 28 | , isOn = startingBoard 29 | } 30 | 31 | 32 | initWithDefaultBoard : String -> Model 33 | initWithDefaultBoard color = 34 | { color = color 35 | , isOn = 36 | Matrix.repeat 6 6 (Just False) 37 | |> Matrix.set 4 3 Nothing 38 | |> Matrix.set 2 2 Nothing 39 | } 40 | |> update (Toggle { x = 0, y = 0 }) 41 | |> update (Toggle { x = 2, y = 0 }) 42 | |> update (Toggle { x = 1, y = 4 }) 43 | |> update (Toggle { x = 4, y = 2 }) 44 | |> update (Toggle { x = 5, y = 0 }) 45 | |> update (Toggle { x = 0, y = 2 }) 46 | |> update (Toggle { x = 1, y = 1 }) 47 | 48 | 49 | isSolved : Model -> Bool 50 | isSolved model = 51 | let 52 | isOn maybeIsOn = 53 | maybeIsOn 54 | |> Maybe.withDefault False 55 | 56 | onLights = 57 | Matrix.filter isOn model.isOn 58 | in 59 | Array.isEmpty onLights 60 | 61 | 62 | 63 | -- Update 64 | 65 | 66 | type alias LightIndex = 67 | { x : Int, y : Int } 68 | 69 | 70 | type Msg 71 | = Toggle LightIndex 72 | 73 | 74 | update : Msg -> Model -> Model 75 | update msg model = 76 | case msg of 77 | Toggle indexToToggle -> 78 | { model | isOn = toggleLight indexToToggle model.isOn } 79 | 80 | 81 | toggleLight : LightIndex -> Matrix (Maybe Bool) -> Matrix (Maybe Bool) 82 | toggleLight indexToToggle matrix = 83 | let 84 | toggleMaybeBool maybeBool = 85 | Maybe.map not maybeBool 86 | in 87 | matrix 88 | |> Matrix.update indexToToggle.x indexToToggle.y toggleMaybeBool 89 | |> Matrix.update (indexToToggle.x + 1) indexToToggle.y toggleMaybeBool 90 | |> Matrix.update (indexToToggle.x - 1) indexToToggle.y toggleMaybeBool 91 | |> Matrix.update indexToToggle.x (indexToToggle.y + 1) toggleMaybeBool 92 | |> Matrix.update indexToToggle.x (indexToToggle.y - 1) toggleMaybeBool 93 | 94 | 95 | 96 | -- View 97 | 98 | 99 | view : Model -> Html.Html Msg 100 | view model = 101 | Html.div [] 102 | [ if isSolved model then 103 | Html.text "You're a winner!" 104 | else 105 | gameView model 106 | ] 107 | 108 | 109 | gameView : Model -> Html.Html Msg 110 | gameView model = 111 | model.isOn 112 | |> Matrix.indexedMap (lightButton model.color) 113 | |> matrixToDivs 114 | 115 | 116 | matrixToDivs : Matrix (Html.Html Msg) -> Html.Html Msg 117 | matrixToDivs matrix = 118 | let 119 | makeRow y = 120 | Matrix.getRow y matrix 121 | |> Maybe.map (Array.toList) 122 | |> Maybe.withDefault [] 123 | |> Html.div [] 124 | 125 | height = 126 | Matrix.height matrix 127 | in 128 | [0..height] 129 | |> List.map makeRow 130 | |> Html.div [] 131 | 132 | 133 | lightButton : String -> Int -> Int -> Maybe Bool -> Html.Html Msg 134 | lightButton color x y maybeIsOn = 135 | case maybeIsOn of 136 | Nothing -> 137 | Html.div 138 | [ Html.Attributes.style 139 | [ ( "width", "40px" ) 140 | , ( "height", "40px" ) 141 | , ( "border-radius", "4px" ) 142 | , ( "margin", "2px" ) 143 | , ( "display", "inline-block" ) 144 | ] 145 | ] 146 | [] 147 | 148 | Just isOn -> 149 | Html.div 150 | [ Html.Attributes.style 151 | [ ( "background-color" 152 | , if isOn then 153 | color 154 | else 155 | "grey" 156 | ) 157 | , ( "width", "40px" ) 158 | , ( "height", "40px" ) 159 | , ( "border-radius", "4px" ) 160 | , ( "margin", "2px" ) 161 | , ( "display", "inline-block" ) 162 | ] 163 | , Html.Events.onClick (Toggle { x = x, y = y }) 164 | ] 165 | [] 166 | --------------------------------------------------------------------------------