module Games.TicTacToe exposing (..) {-| This module exposes a library for the simple game of tic-tac-toe. -} import Color import Element exposing (Element) import Json.Decode as D import Layout import Pixels exposing (Pixels) import Quantity exposing (Quantity) import Svg import Svg.Attributes import Theme -- MODEL type Field = X | O | Empty type alias TicTacToe = { field_1 : Field , field_2 : Field , field_3 : Field , field_4 : Field , field_5 : Field , field_6 : Field , field_7 : Field , field_8 : Field , field_9 : Field } decoder : D.Decoder TicTacToe decoder = D.map3 (\( a, b, c ) ( d, e, f ) ( g, h, i ) -> TicTacToe a b c d e f g h i ) (D.map3 (\a b c -> ( a, b, c )) fieldDecoder fieldDecoder fieldDecoder) (D.map3 (\a b c -> ( a, b, c )) fieldDecoder fieldDecoder fieldDecoder) (D.map3 (\a b c -> ( a, b, c )) fieldDecoder fieldDecoder fieldDecoder) empty : TicTacToe empty = { field_1 = Empty , field_2 = Empty , field_3 = Empty , field_4 = Empty , field_5 = Empty , field_6 = Empty , field_7 = Empty , field_8 = Empty , field_9 = Empty } fieldDecoder : D.Decoder Field fieldDecoder = D.andThen (\s -> case s of "X" -> D.succeed X "O" -> D.succeed O "" -> D.succeed Empty _ -> D.fail "Unknown field type" ) D.string -- VIEW view : { flavor : Theme.Flavor , game : TicTacToe , height : Quantity Int Pixels , width : Quantity Int Pixels } -> Element msg view data = Layout.svg { aspectRatio = 1 / 1 , height = Pixels.inPixels data.height , width = Pixels.inPixels data.width , viewMinX = 0 , viewMaxX = 300 , viewMinY = 0 , viewMaxY = 300 , svg = Svg.g [ Svg.Attributes.strokeLinecap "round" ] [ svgField { field = data.game.field_1, flavor = data.flavor, offsetX = 0, offsetY = 0 } , svgField { field = data.game.field_2, flavor = data.flavor, offsetX = 1, offsetY = 0 } , svgField { field = data.game.field_3, flavor = data.flavor, offsetX = 2, offsetY = 0 } , svgField { field = data.game.field_4, flavor = data.flavor, offsetX = 0, offsetY = 1 } , svgField { field = data.game.field_5, flavor = data.flavor, offsetX = 1, offsetY = 1 } , svgField { field = data.game.field_6, flavor = data.flavor, offsetX = 2, offsetY = 1 } , svgField { field = data.game.field_7, flavor = data.flavor, offsetX = 0, offsetY = 2 } , svgField { field = data.game.field_8, flavor = data.flavor, offsetX = 1, offsetY = 2 } , svgField { field = data.game.field_9, flavor = data.flavor, offsetX = 2, offsetY = 2 } , Svg.g [ Svg.Attributes.fill <| Color.toCssString <| Theme.text data.flavor , Svg.Attributes.strokeWidth "7.5" ] [ Svg.line [ Svg.Attributes.x1 "100" , Svg.Attributes.x1 "100" , Svg.Attributes.y1 "20" , Svg.Attributes.y2 "280" ] [] , Svg.line [ Svg.Attributes.x1 "200" , Svg.Attributes.x1 "200" , Svg.Attributes.y1 "20" , Svg.Attributes.y2 "280" ] [] , Svg.line [ Svg.Attributes.x1 "20" , Svg.Attributes.x1 "280" , Svg.Attributes.y1 "100" , Svg.Attributes.y2 "100" ] [] , Svg.line [ Svg.Attributes.x1 "20" , Svg.Attributes.x1 "280" , Svg.Attributes.y1 "200" , Svg.Attributes.y2 "200" ] [] ] ] } svgField : { field : Field , flavor : Theme.Flavor , offsetX : Int , offsetY : Int } -> Svg.Svg svg svgField data = let radius = 35 in Svg.g [ Svg.Attributes.fill <| Color.toCssString <| Theme.subtext0 data.flavor , Svg.Attributes.strokeWidth "10" ] [ case data.field of Empty -> Svg.g [] [] O -> Svg.circle [ Svg.Attributes.cx (String.fromInt (50 + 100 * data.offsetX)) , Svg.Attributes.cy (String.fromInt (50 + 100 * data.offsetY)) , Svg.Attributes.r (String.fromInt radius) ] [] X -> Svg.g [] [ Svg.line [ Svg.Attributes.x1 (String.fromInt (50 - radius)) , Svg.Attributes.x2 (String.fromInt (50 + radius)) , Svg.Attributes.y1 (String.fromInt (50 - radius)) , Svg.Attributes.y2 (String.fromInt (50 + radius)) ] [] , Svg.line [ Svg.Attributes.x1 (String.fromInt (50 - radius)) , Svg.Attributes.x2 (String.fromInt (50 + radius)) , Svg.Attributes.y1 (String.fromInt (50 + radius)) , Svg.Attributes.y2 (String.fromInt (50 - radius)) ] [] ] ]