diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..387ee11282f86b22b65fe0476e62a23638e9c6d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +**/.idea/ +*.html +**/elm-stuff/ diff --git a/elm-examples/sudoku/.gitignore b/elm-examples/sudoku/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..aee981063c664d20bd7bed367b6ebbe69b44819e --- /dev/null +++ b/elm-examples/sudoku/.gitignore @@ -0,0 +1 @@ +/elm-stuff/ diff --git a/elm-examples/sudoku/README.md b/elm-examples/sudoku/README.md index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e52afa0498bd53be58e43ff1abf4b2cd860ec3cd 100644 --- a/elm-examples/sudoku/README.md +++ b/elm-examples/sudoku/README.md @@ -0,0 +1,41 @@ +# Sudoku + +An implementation of Sudoku (9x9) in elm. This project aims to run in Chromium based browsers and in Firefox. + +## Building and Testing + +In order to build the project type + +``` sh +elm make src/Main.elm +``` + +This will download the libraries mentioned in the `elm.json` and create the `index.html` with the javascript. + +For testing purposes type +``` sh +elm reactor +``` +For this to work the mentionend libraries are needed and therefore `elm make` should be run at least once first. + +## Project structure + +- `elm.json` contains basic elm settings and the needed dependencies +- `src/` + - `Main.elm` + Entry point + - `Update.elm` + The update function for the page + - `View.elm` + Functions to show the sudoku and create the buttons + - `Types.elm` + The basic types for handling the sudoku and the `Msg` type for the browser module + - `Sudoku.elm` + A module for the sudoku logic. This contains functions to check the validity of the sudoku and functions to adjust the sudoku. + - `GenSudoku.elm` + A module with functions to generate a sudoku by solving it first and removing values afterwards. + The solving capabilities can/are also used to solve sudokus, but seem to be incomplete (). This only affects sudoku solving but not the generation of sudokus. + - `List/Maybe.elm` + Contains a function like foldl for lists, but with Maybe which aborts if Nothing is returned even once + - `Seq/Extra.elm` + Two additional functions for Seq (lazy lists). diff --git a/elm-examples/sudoku/elm.json b/elm-examples/sudoku/elm.json new file mode 100644 index 0000000000000000000000000000000000000000..d5774ca5e40da2afa5a82edca641434303bdbaaa --- /dev/null +++ b/elm-examples/sudoku/elm.json @@ -0,0 +1,33 @@ +{ + "type": "application", + "source-directories": [ + "src" + ], + "elm-version": "0.19.0", + "dependencies": { + "direct": { + "elm/browser": "1.0.1", + "elm/core": "1.0.2", + "elm/json": "1.1.2", + "elm/html": "1.0.0", + "elm/http": "2.0.0", + "elm/random": "1.0.0", + "elm-community/list-extra": "8.2.0", + "elm-community/random-extra": "3.1.0", + "the-sett/lazy-list": "1.1.1", + "elm-community/maybe-extra": "5.0.0" + }, + "indirect": { + "elm/bytes": "1.0.8", + "elm/file": "1.0.5", + "elm/time": "1.0.0", + "elm/url": "1.0.0", + "elm/virtual-dom": "1.0.2", + "owanturist/elm-union-find": "1.0.0" + } + }, + "test-dependencies": { + "direct": {}, + "indirect": {} + } +} diff --git a/elm-examples/sudoku/src/GenSudoku.elm b/elm-examples/sudoku/src/GenSudoku.elm new file mode 100644 index 0000000000000000000000000000000000000000..b552633acb46a02d8ec3a3e9ba3996cb2c8c3ac7 --- /dev/null +++ b/elm-examples/sudoku/src/GenSudoku.elm @@ -0,0 +1,359 @@ +module GenSudoku exposing (gen_full_sudoku, gen_partially_filled, solve_sudoku) + +import Basics exposing ((<<), (>>), (||)) +import List exposing (all, any, concatMap, filter, foldr, head, indexedMap, isEmpty, length, map, range, sortBy) +import List.Extra exposing (getAt, remove, updateAt, zip) +import List.Maybe exposing (maybeFoldl) +import Maybe exposing (andThen, withDefault) +import Maybe.Extra exposing (unwrap) +import Random exposing (Generator, constant) +import Random.List exposing (shuffle) +import Sudoku exposing (possible_values) +import Tuple exposing (pair) +import Types exposing (Entry(..), EntryType(..), EntryValue, Position, Row, Sudoku) + + + +-- alias of different functions who's names are already in use + + +rmap : (a -> b) -> Generator a -> Generator b +rmap = + Random.map + + +randThen : (a -> Generator b) -> Generator a -> Generator b +randThen = + Random.andThen + + +mmap : (a -> b) -> Maybe a -> Maybe b +mmap = + Maybe.map + + + +-- SmartSudoku is used in Solving a Sudoku + + +type alias SmartSudoku = + List SmartRow + + +type alias SmartRow = + List SmartEntry + + +type alias SmartEntry = + List EntryValue + + +list_of_all_positions : List Position +list_of_all_positions = + concatMap (\row -> map (pair row) <| range 0 8) <| range 0 8 + + +gen_partially_filled : Generator (Maybe Sudoku) +gen_partially_filled = + randThen + (\sudoku -> + rmap (pair sudoku) <| + shuffle list_of_all_positions + ) + gen_full_sudoku + |> rmap + (\( sudoku, posList ) -> + mmap (\s -> erase_where_obvious s posList) + sudoku + ) + + +erase_where_obvious : Sudoku -> List Position -> Sudoku +erase_where_obvious sudoku list = + case list of + [] -> + sudoku + + x :: xs -> + let + next = + update_entry_at (\_ -> Empty) x sudoku + in + case possible_values next x of + [ _ ] -> + erase_where_obvious next xs + + _ -> + erase_where_obvious sudoku xs + + + +-- generates a sudoku that is only filled in the top left section + + +gen_start_sudoku : Generator Sudoku +gen_start_sudoku = + --generate a random first row + range 1 9 + |> map Just + |> shuffle + |> rmap (zip (Sudoku.area_coordinates ( 0, 0 ))) + -- insert Row into Empty Sudoku + |> rmap + (\list -> + foldr + (\( pos, value ) -> set_entry_at (to_entry value) pos) + Sudoku.empty_sudoku + list + ) + + + +-- converts an int the corresponding Fixed Entry + + +to_entry : Maybe Int -> Entry +to_entry = + Maybe.map (Entry Fixed) >> Maybe.withDefault Empty + + + +-- generate a filled out sudoku by solving a start_sudoku + + +gen_full_sudoku : Generator (Maybe Sudoku) +gen_full_sudoku = + -- solve remaining Sudoku + Random.andThen solve_sudoku gen_start_sudoku + + + +-- solve sudoku in all empty fields + + +solve_sudoku : Sudoku -> Generator (Maybe Sudoku) +solve_sudoku = + to_smart >> solve_sudoku_smart >> rmap (mmap from_smart) + + + +-- converts a Sudoku to a SmartSudoku + + +to_smart : Sudoku -> SmartSudoku +to_smart sudoku = + sudoku + |> indexedMap + (\rowIndex -> + indexedMap + (\columnIndex entry -> + case entry of + Empty -> + possible_values sudoku ( rowIndex, columnIndex ) + + Entry _ num -> + [ num ] + ) + ) + + + +-- converts a SmartSudoku to a Sudoku + + +from_smart : SmartSudoku -> Sudoku +from_smart = + map (map options_to_entry) + + + +-- converts a List of Ints to an Entry + + +options_to_entry : List Int -> Entry +options_to_entry seq = + case seq of + -- no possible entry remaining, leaving entry as this is the best we can do + [] -> + Empty + + -- only one entry remaining using that + [ a ] -> + Entry Fixed a + + -- more than one possibility leaving empty as this is not for us to decide + _ :: _ -> + Empty + + + +-- tries to solve a SmartSudoku + + +solve_sudoku_smart : SmartSudoku -> Generator (Maybe SmartSudoku) +solve_sudoku_smart sudoku = + if any (any isEmpty) sudoku then + -- a field has no entries remaining, no solution to be found + constant Nothing + + else if all (all (length >> (==) 1)) sudoku then + -- all fields have one entry remaining we are done + constant <| Just sudoku + + else + -- find a position with least options, but more than one remaining and try them + -- generate a list containing all position (0,0) to (8,8) + concatMap (\row -> map (\column -> pair row column) <| range 0 8) (range 0 8) + -- remove positions with 1 (or less entries) + |> filter (\pos -> get_entry_at pos sudoku |> mmap length |> withDefault 0 |> (<) 1) + -- the default should never be needed + |> sortBy (\pos -> get_entry_at pos sudoku |> mmap length |> withDefault 9) + -- get a position with least options + |> head + -- try all options lazy + |> mmap (try_all_options sudoku) + |> withDefault (constant Nothing) + + + +-- tries all options still possible at the position in a random order + + +try_all_options : SmartSudoku -> Position -> Generator (Maybe SmartSudoku) +try_all_options sudoku position = + get_entry_at position sudoku + |> mmap shuffle + |> withDefault (constant []) + |> randThen + (\list -> + first_just_result + list + (\entry -> + try_entry_at position + entry + sudoku + |> unwrap (constant Nothing) solve_sudoku_smart + ) + ) + + +first_just_result : List a -> (a -> Generator (Maybe b)) -> Generator (Maybe b) +first_just_result list fun = + case list of + [] -> + constant Nothing + + x :: xs -> + fun x + |> randThen + (\result -> + case result of + Just _ -> + constant result + + Nothing -> + first_just_result xs fun + ) + + + +-- tries to solve the sudoku with entry at pos + + +try_entry_at : Position -> EntryValue -> SmartSudoku -> Maybe SmartSudoku +try_entry_at pos entry sudoku = + set_entry_at [ entry ] pos sudoku + |> update_surrounding entry pos + + + +-- updates the surrounding of pos after inserting entry, this might cascade + + +update_surrounding : EntryValue -> Position -> SmartSudoku -> Maybe SmartSudoku +update_surrounding entry pos = + update_surrounding_row entry pos + >> (unwrap Nothing <| update_surrounding_column entry pos) + >> (unwrap Nothing <| update_surrounding_section entry pos) + + + +-- updates the pos after inserting entry in a surrounding field , this might cascade + + +update_surrounding_entry : EntryValue -> Position -> SmartSudoku -> Maybe SmartSudoku +update_surrounding_entry entry pos sudoku = + let + old = + get_entry_at pos sudoku |> withDefault [] + in + case remove entry old of + [] -> + Nothing + + [ x ] -> + if [ x ] /= old then + -- we reduced the list to one entry we need to recurse + try_entry_at pos x sudoku + + else + -- nothing changed + Just sudoku + + value -> + -- we still have choices to make here in the future + Just <| set_entry_at value pos sudoku + + + +-- updates the surrounding row of pos after inserting entry, this might cascade + + +update_surrounding_row : EntryValue -> Position -> SmartSudoku -> Maybe SmartSudoku +update_surrounding_row entry ( row, column ) sudoku = + range 0 8 |> remove column |> maybeFoldl (update_surrounding_entry entry << pair row) sudoku + + + +-- updates the surrounding column of pos after inserting entry, this might cascade + + +update_surrounding_column : EntryValue -> Position -> SmartSudoku -> Maybe SmartSudoku +update_surrounding_column entry ( row, column ) sudoku = + range 0 8 |> remove row |> maybeFoldl (update_surrounding_entry entry << (\r -> ( r, column ))) sudoku + + + +-- updates the surrounding section of pos after inserting entry, this might cascade + + +update_surrounding_section : EntryValue -> Position -> SmartSudoku -> Maybe SmartSudoku +update_surrounding_section entry pos sudoku = + Sudoku.area_coordinates pos |> maybeFoldl (update_surrounding_entry entry) sudoku + + + +-- gets the value at (row, column) in the 2d list if it exists + + +get_entry_at : Position -> List (List a) -> Maybe a +get_entry_at ( row, column ) = + getAt row >> andThen (getAt column) + + + +-- sets the entry at (row, column) in the 2d list should that position exits + + +set_entry_at : a -> Position -> List (List a) -> List (List a) +set_entry_at entry = + update_entry_at (always entry) + + + +-- updates the entry at (row, column) in the 2d list should that position exits, by passing the old value to the function to get the new value + + +update_entry_at : (a -> a) -> Position -> List (List a) -> List (List a) +update_entry_at updater ( row, column ) = + updateAt row <| updateAt column updater diff --git a/elm-examples/sudoku/src/List/Maybe.elm b/elm-examples/sudoku/src/List/Maybe.elm new file mode 100644 index 0000000000000000000000000000000000000000..77835cae1897749ce491003fb0f9823fc6971abb --- /dev/null +++ b/elm-examples/sudoku/src/List/Maybe.elm @@ -0,0 +1,22 @@ +module List.Maybe exposing (maybeFoldl) + +import Maybe exposing (Maybe(..)) + + + +-- maybeFoldl acts like foldl and aborts if the accumulation function returns Nothing resulting in Nothing otherwise in the final Just value + + +maybeFoldl : (a -> b -> Maybe b) -> b -> List a -> Maybe b +maybeFoldl fun init list = + case list of + [] -> + Just init + + x :: xs -> + case fun x init of + Nothing -> + Nothing + + Just next -> + maybeFoldl fun next xs diff --git a/elm-examples/sudoku/src/Main.elm b/elm-examples/sudoku/src/Main.elm new file mode 100644 index 0000000000000000000000000000000000000000..054c2f24b5cf4047586e4a56335ec2f071ba9d41 --- /dev/null +++ b/elm-examples/sudoku/src/Main.elm @@ -0,0 +1,17 @@ +module Main exposing (main) + +import Browser +import Model exposing (init, subs) +import Types exposing (Model, Msg) +import Update exposing (update) +import View exposing (view) + + +main : Program () Model Msg +main = + Browser.element + { init = init + , update = update + , view = view + , subscriptions = subs + } diff --git a/elm-examples/sudoku/src/Model.elm b/elm-examples/sudoku/src/Model.elm new file mode 100644 index 0000000000000000000000000000000000000000..138ce0e565c529f702d786eb953a8e367552c52b --- /dev/null +++ b/elm-examples/sudoku/src/Model.elm @@ -0,0 +1,14 @@ +module Model exposing (init, subs) + +import Sudoku exposing (empty_sudoku) +import Types exposing (Model, Msg) + + +init : a -> ( Model, Cmd Msg ) +init _ = + ( ( empty_sudoku, "Empty" ), Cmd.none ) + + +subs : Model -> Sub Msg +subs _ = + Sub.none diff --git a/elm-examples/sudoku/src/Seq/Extra.elm b/elm-examples/sudoku/src/Seq/Extra.elm new file mode 100644 index 0000000000000000000000000000000000000000..c66cf05977fec5bc556ba831ebe7b41a3779e0c7 --- /dev/null +++ b/elm-examples/sudoku/src/Seq/Extra.elm @@ -0,0 +1,40 @@ +module Seq.Extra exposing (all, remove) + +import Seq exposing (Seq(..)) + + + +-- like all for List, returns true iff all elements fulfill the predicate + + +all : (a -> Bool) -> Seq a -> Bool +all predicate seq = + case seq of + Nil -> + True + + Cons a fun -> + case predicate a of + True -> + all predicate <| fun () + + False -> + False + + + +-- removed the first occurrence the value from the Sequence + + +remove : a -> Seq a -> Seq a +remove sentinel seq = + case seq of + Nil -> + Nil + + Cons a tail -> + if a == sentinel then + tail () + + else + Cons a (\_ -> remove sentinel <| tail ()) diff --git a/elm-examples/sudoku/src/Sudoku.elm b/elm-examples/sudoku/src/Sudoku.elm new file mode 100644 index 0000000000000000000000000000000000000000..aeec318a17f20b2945a3fb7ca06bb69488a3318d --- /dev/null +++ b/elm-examples/sudoku/src/Sudoku.elm @@ -0,0 +1,230 @@ +module Sudoku exposing (area_coordinates, empty_sudoku, possible_values, update_sudoku, validate_sudoku) + +import List exposing (all, append, drop, filter, filterMap, foldr, map, member, range, repeat, take) +import List.Extra exposing (notMember, transpose) +import Random exposing (Generator) +import Types exposing (Entry(..), EntryType(..), EntryValue, Msg(..), Position, Row, Sudoku) + + +empty_sudoku : Sudoku +empty_sudoku = + repeat 9 (repeat 9 Empty) + + +validate_sudoku : Sudoku -> Bool +validate_sudoku sudoku = + all (validate_feature sudoku) + [ extract_rows, extract_columns, extract_areas ] + + +validate_feature : Sudoku -> (Sudoku -> List (List Entry)) -> Bool +validate_feature sudoku extractor = + all (validate_list << map entry_to_maybe) (extractor sudoku) + + + +{- validates if a sudoku with the value at the position would be valid -} + + +validate_entry : Sudoku -> Position -> Int -> Bool +validate_entry s p e = + all (notMember (Entry Fixed e)) <| + map (\a -> a s p) + [ extract_row, extract_column, extract_area ] + + + +{- validate_sudoku = let + res_list = + map (validate_feature sudoku) [ extract_rows, extract_columns, extract_areas ] + in + List.foldl (&&) True res_list +-} +{- creates a list of possible entries for a position -} + + +possible_values : Sudoku -> Position -> List Int +possible_values s p = + filter (validate_entry s p) (range 1 9) + + +entry_to_maybe : Entry -> Maybe EntryValue +entry_to_maybe v = + case v of + Empty -> + Nothing + + Entry _ m -> + Just m + + +validate_list : List (Maybe EntryValue) -> Bool +validate_list l = + case l of + [] -> + True + + Nothing :: tail -> + validate_list tail + + m :: tail -> + not (member m tail) && validate_list tail + + + +-- Creates a list of area entry lists + + +extract_areas : Sudoku -> List (List Entry) +extract_areas sudoku = + map + (\n -> extract_area sudoku ( n // 3, remainderBy 3 n )) + (range 0 8) + + +area_coordinates : Position -> List Position +area_coordinates ( row, column ) = + let + base_row = + row // 3 * 3 + + base_column = + column // 3 * 3 + + base = + ( base_row, base_column ) + + add ( x, y ) ( t, s ) = + ( x + t, y + s ) + + offsets = + List.range 0 2 + |> List.concatMap (\r -> List.map (Tuple.pair r) <| List.range 0 2) + in + List.map (add base) offsets + + + +-- Drops the first 3*n elements of a list and returns 3 elements of the remainder + + +td3 : Int -> List a -> List a +td3 n list = + take 3 (drop (n * 3) list) + + + +-- Returns a list of 9 entries that form the area defined by the position + + +extract_area : Sudoku -> Position -> List Entry +extract_area s ( r, c ) = + foldr (\n -> append (td3 c n)) [] (td3 r s) + + +extract_rows : Sudoku -> List (List Entry) +extract_rows = + identity + + +extract_row : Sudoku -> Position -> List Entry +extract_row sudoku ( row, _ ) = + Maybe.withDefault [] <| element row sudoku + + +extract_columns : Sudoku -> List (List Entry) +extract_columns = + transpose + + +extract_column : Sudoku -> Position -> List Entry +extract_column sudoku ( _, column ) = + filterMap (element column) sudoku + + + +-- Returns the nth element of a list + + +element : Int -> List a -> Maybe a +element = + List.Extra.getAt + + + +-- Generate Sudoku ----------------------------------------------------------------------------------------------------- + + +update_sudoku : Sudoku -> Position -> Entry -> Sudoku +update_sudoku sudoku ( row, column ) entry = + exchange_entry sudoku row <| + update_sudoku_row (List.head (drop row sudoku)) + column + entry + + +update_sudoku_row : Maybe Row -> Int -> Entry -> Row +update_sudoku_row row = + exchange_entry (Maybe.withDefault (repeat 9 Empty) row) + + +exchange_entry : List a -> Int -> a -> List a +exchange_entry list index replacement = + List.Extra.setAt index replacement list + + + +--Random.andThen (try_insert ( 0, 0 ) empty_sudoku) (Random.int 1 9) + + +try_insert : Position -> Sudoku -> Int -> Generator (Maybe Sudoku) +try_insert p s int = + let + new = + update_sudoku s p (Entry Fixed int) + + ( n, done ) = + next p + + possibleMaybeValues = + possible_values new n + in + case done of + True -> + Random.constant <| Just new + + False -> + Random.andThen + (\may -> + case may of + Just a -> + try_insert n new a + + Nothing -> + Random.constant Nothing + ) + <| + Random.lazy (\_ -> mayUniform possibleMaybeValues) + + +mayUniform : List a -> Generator (Maybe a) +mayUniform list = + case list of + [] -> + Random.constant Nothing + + x :: xs -> + Random.uniform (Just x) <| map (\y -> Just y) xs + + +next : ( Int, Int ) -> ( ( Int, Int ), Bool ) +next ( x, y ) = + case ( x, y ) of + ( 8, 8 ) -> + ( ( 8, 8 ), True ) + + ( l, 8 ) -> + ( ( l + 1, 0 ), False ) + + ( l, s ) -> + ( ( l, s + 1 ), False ) diff --git a/elm-examples/sudoku/src/Types.elm b/elm-examples/sudoku/src/Types.elm new file mode 100644 index 0000000000000000000000000000000000000000..5fd8e5eab6b48d0c21cac377c38e62fdfe4abb09 --- /dev/null +++ b/elm-examples/sudoku/src/Types.elm @@ -0,0 +1,47 @@ +module Types exposing + (Entry(..), EntryType(..), EntryValue, Model, Msg(..), Position, + Row, Sudoku, all_options) + +import List exposing (map, range) + + +type Entry + = Empty + | Entry EntryType EntryValue + + +type EntryType + = Fixed + | User + + +type alias EntryValue = + Int + + +all_options : List Entry +all_options = + Empty :: map (Entry User) (range 1 9) + + +type alias Row = + List Entry + + +type alias Sudoku = + List Row + + +type alias Model = + ( Sudoku, String ) + + +type alias Position = + ( Int, Int ) + + +type Msg + = Msg Position Entry + | Random (Maybe Sudoku) + | Generate + | Solve diff --git a/elm-examples/sudoku/src/Update.elm b/elm-examples/sudoku/src/Update.elm new file mode 100644 index 0000000000000000000000000000000000000000..2eb57898e77fe726a9d0309eecbc9b337249ee5f --- /dev/null +++ b/elm-examples/sudoku/src/Update.elm @@ -0,0 +1,61 @@ +module Update exposing (no_emptys_sudoku, update, won_sudoku) + +import GenSudoku exposing (gen_partially_filled, solve_sudoku) +import List exposing (all) +import Platform.Cmd +import Random exposing (generate) +import Sudoku exposing (validate_sudoku) +import Types exposing (Entry(..), Model, Msg(..), Sudoku) + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg (( sudoku, _ ) as model) = + case msg of + Msg position entry -> + let + sudoku2 = + Sudoku.update_sudoku sudoku position entry + + resp = + case Sudoku.validate_sudoku sudoku2 of + True -> + if won_sudoku sudoku2 then + "Yay you have made it!" + + else + "Incomplete" + + False -> + "Your Sudoku is inconsistent!" + in + ( ( sudoku2, resp ), Cmd.none ) + + Generate -> + ( model, generate Random gen_partially_filled ) + + Solve -> + ( model, generate Random <| solve_sudoku sudoku ) + + Random Nothing -> + ( ( sudoku, "Failed to generate/solve Sudoku" ), Cmd.none ) + + Random (Just s) -> + ( ( s, "Sudoku generated" ), Cmd.none ) + + + +-- checks if the sudoku was solved + + +won_sudoku : Sudoku -> Bool +won_sudoku field = + no_emptys_sudoku field && validate_sudoku field + + + +-- checks if all fields are non Empty + + +no_emptys_sudoku : Sudoku -> Bool +no_emptys_sudoku field = + all (all <| (/=) Empty) field diff --git a/elm-examples/sudoku/src/View.elm b/elm-examples/sudoku/src/View.elm new file mode 100644 index 0000000000000000000000000000000000000000..e5a8fa09a7f76a14fe5c0b1abf6863f17d9915a9 --- /dev/null +++ b/elm-examples/sudoku/src/View.elm @@ -0,0 +1,138 @@ +module View exposing (conv_to_msg, gen_entry, gen_option, gen_row, gen_sudoku, parse, show, view) + +import Html exposing (Html, br, button, div, node, option, select, table, td, text, tr) +import Html.Attributes exposing (selected) +import Html.Events exposing (onClick, onInput) +import List exposing (map, range, repeat) +import List.Extra exposing (zip) +import Types exposing (Entry(..), EntryType(..), EntryValue, Model, Msg(..), Position, Row, Sudoku, all_options) + + +view : Model -> Html Msg +view ( sudoku, msg ) = + div [] + [ css_style + , gen_sudoku sudoku + , button [ onClick Generate ] [ text "Generate New Sudoku" ] + , button [ onClick Solve ] [ text "Solve Sudoku" ] + , br [] [] + , text msg + ] + + +css_style = + node "style" [] <| + (\c -> [ c ]) <| + text """ + table { + border: 5px double black; + border-collapse: collapse; + } + + select { + width: 100%; + } + + td { + width: 30px; + height: 30px; + border: 1px solid black; + text-align: center; + } + + td:nth-child(3n):not(:last-child) { + border-style: solid double solid solid; + border-width: 1px 5px 1px 1px; + } + + tr:nth-child(3n):not(:last-child) > td { + border-style: solid solid double solid; + border-width: 1px 1px 5px 1px; + } + + tr:nth-child(3n):not(:last-child) > td:nth-child(3n):not(:last-child) { + border-style: solid double double solid; + border-width: 1px 5px 5px 1px; + } + + select { + -webkit-appearance: none; + } + + """ + + + +-- this could be done withing css to simpplify view generation, +-- but that is "geschmackssache" + + +gen_sudoku : Sudoku -> Html Msg +gen_sudoku model = + table [] <| map gen_row <| zip (range 0 8) model + + +gen_row : ( Int, Row ) -> Html Msg +gen_row ( index, row ) = + tr [] <| map gen_entry <| zip (zip (repeat 9 index) <| range 0 8) row + + +gen_entry : ( Position, Entry ) -> Html Msg +gen_entry ( position, entry ) = + case entry of + Entry Fixed _ -> + td [] [ text <| show_entry entry ] + + _ -> + td [] + [ select [ onInput <| conv_to_msg position ] <| + map (gen_option position entry) + all_options + ] + + +conv_to_msg : Position -> String -> Msg +conv_to_msg pos = + parse >> Msg pos + + +gen_option : Position -> Entry -> Entry -> Html Msg +gen_option position select entry = + option + [ selected <| select == entry + , onClick <| Msg position select + ] + [ text <| show_entry entry ] + + +parse : String -> Entry +parse e = + case String.toInt e of + Just n -> + if n >= 1 && n <= 9 then + Entry User n + + else + Empty + + Nothing -> + Empty + + +show_entry : Entry -> String +show_entry entry = + case entry of + Empty -> + "" + + Entry _ e -> + show e + + +show : EntryValue -> String +show e = + if e >= 1 && e <= 9 then + String.fromInt e + + else + "" diff --git a/elm-examples/sudoku/sudoku.iml b/elm-examples/sudoku/sudoku.iml new file mode 100644 index 0000000000000000000000000000000000000000..f939a9dd7fb5bff4645730452a7dbcb67985a557 --- /dev/null +++ b/elm-examples/sudoku/sudoku.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file