RosettaCodeData/Task/Langtons-ant/Elm/langtons-ant.elm
2023-07-01 13:44:08 -04:00

132 lines
3.8 KiB
Elm

import Maybe as M
import Matrix
import Time exposing (Time, every, second)
import List exposing (..)
import String exposing (join)
import Html exposing (div, h1, text)
import Html.App exposing (program)
import Svg
import Svg.Attributes exposing (version, viewBox, cx, cy, r, x, y, x1, y1, x2, y2, fill,style, width, height, preserveAspectRatio)
w = 700
h = 700
dt = 0.0001
type Direction = North | West | South | East
type alias Model =
{ rows : Int
, cols : Int
, boxes : Matrix.Matrix Bool
, location : Matrix.Location
, direction : Direction
}
initModel : Int -> Int -> Model
initModel cols rows =
{ rows = rows
, cols = cols
, boxes = Matrix.matrix rows cols (\location -> False)
, location = (rows//2,cols//2)
, direction = North
}
view model =
let
borderLineStyle = style "stroke:black;stroke-width:0.3"
x1Min = x1 <| toString 0
y1Min = y1 <| toString 0
x1Max = x1 <| toString model.cols
y1Max = y1 <| toString model.rows
x2Min = x2 <| toString 0
y2Min = y2 <| toString 0
x2Max = x2 <| toString model.cols
y2Max = y2 <| toString model.rows
borders = [ Svg.line [ x1Min, y1Min, x2Max, y2Min, borderLineStyle ] []
, Svg.line [ x1Max, y1Min, x2Max, y2Max, borderLineStyle ] []
, Svg.line [ x1Max, y1Max, x2Min, y2Max, borderLineStyle ] []
, Svg.line [ x1Min, y1Max, x2Min, y2Min, borderLineStyle ] []
]
circleInBox (row,col) color =
Svg.circle [ r "0.25"
, fill (color)
, cx (toString (toFloat col + 0.5))
, cy (toString (toFloat row + 0.5))
] []
showUnvisited location box =
if box then [circleInBox location "black" ]
else []
unvisited = model.boxes
|> Matrix.mapWithLocation showUnvisited
|> Matrix.flatten
|> concat
maze = [ Svg.g [] <| borders ++ unvisited ]
in
div
[]
[ h1 [] [text "Langton's Ant"]
, Svg.svg
[ version "1.1"
, width (toString w)
, height (toString h)
, viewBox (join " "
[ 0 |> toString
, 0 |> toString
, model.cols |> toString
, model.rows |> toString ])
]
maze
]
updateModel : Model -> Model
updateModel model =
let current = model.location
inBox = snd current >= 0 && snd current < model.cols
&& fst current >= 0 && fst current < model.rows
in if not inBox then
model
else
let currentValue = Matrix.get current model.boxes |> M.withDefault False
dir = case (model.direction, currentValue) of
(North, True) -> East
(East, True) -> South
(South, True) -> West
(West, True) -> North
(North, False) -> West
(East, False) -> North
(South, False) -> East
(West, False) -> South
next = case dir of
North -> (fst current+1, snd current)
South -> (fst current-1, snd current)
East -> (fst current, snd current+1)
West -> (fst current, snd current-1)
boxes = Matrix.set current (not currentValue) model.boxes
in {model | boxes=boxes, location=next, direction=dir}
type Msg = Tick Time
subscriptions model = every (dt * second) Tick
main =
let
update msg model = (updateModel model, Cmd.none)
init = (initModel 100 100 , Cmd.none)
in program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}