import Html exposing (Html, button, text, div, hr, span)
import Html.Events exposing (onClick)
import Html.Attributes exposing (style)
import List exposing (..)
import Color exposing (..)
import Time exposing (Time, second)
main = Html.program
{ init = (reset 0, Cmd.none)
, view = view
, update = update
, subscriptions = subscription
}
-- MODEL
type alias Index = Int
type alias Model =
{ state : List Int
, goto : List Int
, score : Int
, history : List (List Int)
}
type alias Problem =
{ from : List Int
, to : List Int
}
-- non empty
type NonEmptyList a = NonEmptyList a (List a)
problems : NonEmptyList Problem
problems = NonEmptyList
{ from = [0,0,0,0,3,0,0,0,0], to = [2,2,2,2,0,2,2,2,2] }
[ { from = [2,1,2,1,0,1,2,1,2], to = [0,3,0,3,1,3,0,3,0] }
, { from = [2,1,2,1,1,1,2,1,2], to = [2,1,2,1,0,1,2,1,2] }
, { from = [3,3,3,3,0,3,3,3,3], to = [0,3,0,3,0,3,0,3,0] }
, { from = [0,2,2,0,0,3,0,0,3], to = [3,0,1,0,3,2,2,2,3] }
, { from = [0,2,2,3,2,2,1,1,0], to = [0,2,2,3,1,0,2,2,3] }
, { from = [3,0,1,1,0,0,1,3,0], to = [1,1,1,0,2,2,3,1,2] }
, { from = [3,0,2,3,3,3,1,1,2], to = [2,3,2,0,2,2,1,1,1] }
]
(!!) : NonEmptyList a -> Int -> a
(!!) ls n = case ls of
NonEmptyList x [] -> x
NonEmptyList x (y::xs) -> if n <= 0
then x
else (NonEmptyList y xs) !! (n-1)
mod : Int -> List a -> List (List a)
mod n ls =
let go n ls = case (take n ls, drop n ls) of
(l1, []) -> [l1]
(l1, l2) -> l1 :: go n l2
in go n ls
reset : Int -> Model
reset n = let prob = problems !! n
in { state = prob.from
, goto = prob.to
, score = 0
, history = [prob.from]
}
-- UPDATE
type Msg = Reset Int | AddAt Int | Fix | Undo
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Reset n -> (reset n, Cmd.none)
AddAt m ->
let
l1 = take m model.state
l1b = drop m model.state
x = take 1 l1b
l2 = drop 1 l1b
newL = l1 ++ (map (\n -> n+1) x) ++ l2
newH = model.state :: model.history
in ({ model | state = newL, score = model.score + 1, history = newH }, Cmd.none)
Fix ->
let
rems = map (\v -> v % 4) model.state
divs = map (\v -> v // 4) model.state
dividx = map (\(v, i) -> scale v (neighbours i)) <| zip divs idxs
adds = foldr add zero dividx
newState = add rems adds
in ({ model | state = newState }, Cmd.none)
Undo -> case model.history of
[] -> ({ model | history = [model.state]}, Cmd.none)
[x] -> ({ model | state = x }, Cmd.none)
x::xs -> ({ model | state = x, history = xs }, Cmd.none)
subscription : Model -> Sub Msg
subscription _ = Time.every (0.2*second) (always Fix)
zip = map2 (,)
idxs = range 0 8
zero = List.repeat 9 0
add l1 l2 = map2 (+) l1 l2
scale n ls = map (\v -> n*v) ls
neighbours n = case n of
0 -> [0,1,0,1,0,0,0,0,0]
1 -> [1,0,1,0,1,0,0,0,0]
2 -> [0,1,0,0,0,1,0,0,0]
3 -> [1,0,0,0,1,0,1,0,0]
4 -> [0,1,0,1,0,1,0,1,0]
5 -> [0,0,1,0,1,0,0,0,1]
6 -> [0,0,0,1,0,0,0,1,0]
7 -> [0,0,0,0,1,0,1,0,1]
8 -> [0,0,0,0,0,1,0,1,0]
_ -> []
correct model = model.state == model.goto
-- VIEW
color n = case n of
0 -> rgb 255 255 255
1 -> rgb 255 255 100
2 -> rgb 255 200 50
3 -> rgb 255 100 0
_ -> rgb 100 255 255
toRgbaString : Color -> String
toRgbaString color =
let {red, green, blue, alpha} = Color.toRgb color
in "rgba(" ++ toString red ++ ", " ++ toString green ++ ", " ++ toString blue ++ ", " ++ toString alpha ++ ")"
fst (a, b) = a
view : Model -> Html Msg
view model =
let
intro = div [] [ text "Can you transform the top grid into the bottom grid?" ]
but n = button [ onClick (Reset n) ] [ text (toString (n+1))]
puzzleSelect = div [] ( [ text "Choose puzzle " ] ++ map but (List.range 0 7) )
undoButton = button [ onClick Undo ] [ text "undo move" ]
finTxt = case model.state == model.goto of
True -> ". Yay!"
False -> ""
scoreView = div [] [ undoButton, text ("Taps: " ++ toString model.score), text finTxt ]
tile c f n = div [ style
[ ("backgroundColor", c n)
, ("width", "37px")
, ("height", "37px")
, ("display", "inline-block")
, ("textAlign", "center")
, ("padding", "30px")
]
] [f n]
row c f r = div [] (map (tile c f) r)
grid c f g = div [] (map (row c f) g)
pile c f p = grid c f (mod 3 p)
tstr = text << toString
tstr2 (n, m) = button [ style
[ ("backgroundColor", c n)
, ("width", "37px")
, ("height", "37px")
], onClick (AddAt m) ] [ tstr n ]
c = toRgbaString << color
c2 = if correct model
then always "#00FF00"
else toRgbaString << color << fst
in
div [] [ intro
, puzzleSelect
, hr [] []
, scoreView
, pile c2 tstr2 (map2 (,) model.state idxs)
, hr [] []
, pile c tstr model.goto
]