first commit
It doesn't really work as a game.
This commit is contained in:
commit
1916afcb03
12 changed files with 7797 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
.make.*
|
||||
elm-stuff/
|
||||
error.txt
|
2
.watchmakerc
Normal file
2
.watchmakerc
Normal file
|
@ -0,0 +1,2 @@
|
|||
extensions:
|
||||
- .elm
|
11
Makefile
Normal file
11
Makefile
Normal file
|
@ -0,0 +1,11 @@
|
|||
DIRNAME=$(notdir $(CURDIR))
|
||||
|
||||
ELMS=$(wildcard src/*.elm)
|
||||
|
||||
app.js: src/App.elm $(ELMS)
|
||||
-elm make $< --output=$@ 2> error.txt
|
||||
@cat error.txt
|
||||
|
||||
upload: app.js index.html style.css
|
||||
rsync -avz . clpland:~/domains/somethingorotherwhatever.com/html/$(DIRNAME)
|
||||
@echo "Uploaded to https://somethingorotherwhatever.com/$(DIRNAME)"
|
28
elm.json
Normal file
28
elm.json
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"type": "application",
|
||||
"source-directories": [
|
||||
"src"
|
||||
],
|
||||
"elm-version": "0.19.1",
|
||||
"dependencies": {
|
||||
"direct": {
|
||||
"elm/browser": "1.0.2",
|
||||
"elm/core": "1.0.5",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/random": "1.0.0",
|
||||
"elm/svg": "1.0.1",
|
||||
"elm-community/list-extra": "8.7.0",
|
||||
"elm-community/random-extra": "3.2.0"
|
||||
},
|
||||
"indirect": {
|
||||
"elm/json": "1.1.3",
|
||||
"elm/time": "1.0.0",
|
||||
"elm/url": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.3"
|
||||
}
|
||||
},
|
||||
"test-dependencies": {
|
||||
"direct": {},
|
||||
"indirect": {}
|
||||
}
|
||||
}
|
23
index.html
Normal file
23
index.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Elm app by clp</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<h1>Elm app by clp</h1>
|
||||
</header>
|
||||
<main>
|
||||
<p>This is an app which will either load succesfully, and you'll wonder whether you saw this text at all, or fail ignominiously, showing you only this text.</p>
|
||||
<p>On balance of probabilities: I'm sorry I couldn't be bothered to make this work for you.</p>
|
||||
</main>
|
||||
<footer>Made by <a href="https://somethingorotherwhatever.com">clp</a></footer>
|
||||
|
||||
<script src="app.js"></script>
|
||||
<script src="load-app.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
10
load-app.js
Normal file
10
load-app.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import show_error from './show-error.mjs';
|
||||
async function init_app() {
|
||||
const compilation_error = await show_error;
|
||||
if(compilation_error) {
|
||||
return;
|
||||
}
|
||||
const app = Elm.App.init({node: document.body, flags: {}});
|
||||
}
|
||||
|
||||
init_app();
|
21
show-error.mjs
Normal file
21
show-error.mjs
Normal file
|
@ -0,0 +1,21 @@
|
|||
export default fetch('/error.txt').then(r=>{
|
||||
if(r.ok) {
|
||||
return r.text();
|
||||
} else {
|
||||
throw('');
|
||||
}
|
||||
}).then(text => {
|
||||
if(!text) {
|
||||
return false;
|
||||
}
|
||||
document.body.innerHTML = '';
|
||||
const error_show = document.createElement('pre');
|
||||
error_show.setAttribute('id','build-error');
|
||||
error_show.style.background = 'black';
|
||||
error_show.style.color = 'white';
|
||||
error_show.style.padding = '1em';
|
||||
error_show.style['font-size'] = '16px';
|
||||
error_show.textContent = text;
|
||||
document.body.appendChild(error_show);
|
||||
return true;
|
||||
}).catch(e => false);
|
439
src/App.elm
Normal file
439
src/App.elm
Normal file
|
@ -0,0 +1,439 @@
|
|||
module App exposing (..)
|
||||
|
||||
import Browser
|
||||
import ConstraintLayout as CL
|
||||
import Html as H exposing (Html)
|
||||
import Html.Attributes as HA
|
||||
import Html.Events as HE
|
||||
import List.Extra as LE
|
||||
import Random as R
|
||||
import Random.Extra as RE
|
||||
import Random.List as RL
|
||||
import Svg exposing (Svg)
|
||||
import Svg.Attributes as SA
|
||||
import Svg.Events as SE
|
||||
import Tuple exposing (first, second, pair)
|
||||
import Vector exposing (..)
|
||||
|
||||
type alias Value = Int
|
||||
|
||||
type alias Id = Int
|
||||
|
||||
type alias JunctionFunction = Value -> List Value -> List Value
|
||||
|
||||
type alias Junction =
|
||||
{ id : Id
|
||||
, fn : JunctionFunction
|
||||
, label : String
|
||||
, next : List Id
|
||||
, stack : List Value
|
||||
, position : Vector
|
||||
, solution_stack : List Value
|
||||
}
|
||||
|
||||
type alias Model =
|
||||
{ junctions : List Junction
|
||||
, selected_junction : Maybe Id
|
||||
}
|
||||
|
||||
type Msg
|
||||
= Move (Id, Id)
|
||||
| SetJunctions (List Junction)
|
||||
| ClickJunction Id
|
||||
| RandomMove
|
||||
|
||||
|
||||
lerp : Float -> Float -> Float -> Float
|
||||
lerp a b t = t*b + (1-t)*a
|
||||
|
||||
is_id : Id -> { a | id : Id } -> Bool
|
||||
is_id id = .id >> (==) id
|
||||
|
||||
get_junction : List Junction -> Id -> Maybe Junction
|
||||
get_junction junctions id = junctions |> LE.find (is_id id)
|
||||
|
||||
set_junction : Id -> Junction -> List Junction -> List Junction
|
||||
set_junction id j junctions = LE.setIf (is_id id) j junctions
|
||||
|
||||
iterate : (a -> a) -> Int -> a -> a
|
||||
iterate fn n a =
|
||||
if n > 0 then
|
||||
iterate fn (n-1) (fn a)
|
||||
else
|
||||
a
|
||||
|
||||
layout_junctions : Model -> Model
|
||||
layout_junctions model =
|
||||
let
|
||||
gap = 100
|
||||
|
||||
min_dist_constraints =
|
||||
List.range 0 (List.length model.junctions)
|
||||
|> LE.uniquePairs
|
||||
|> List.map (\(a,b) -> (CL.MinDistance gap, a, b))
|
||||
|
||||
max_dist_constraints =
|
||||
model.junctions
|
||||
|> List.concatMap (\j -> List.map (\i -> (CL.MaxDistance (gap), j.id, i)) j.next)
|
||||
|
||||
same_y_constraints =
|
||||
model.junctions
|
||||
|> List.concatMap (\j -> List.map (\i -> (CL.SameY, j.id, i)) j.next)
|
||||
|
||||
below_constraints =
|
||||
model.junctions
|
||||
|> List.concatMap (\j -> List.map2 (\i k -> (CL.Below (gap/2), i, k)) j.next (List.drop 1 j.next))
|
||||
|
||||
right_of_constraints =
|
||||
model.junctions
|
||||
|> List.concatMap (\j -> List.map (\i -> (CL.RightOf gap, j.id, i)) j.next)
|
||||
|
||||
constraints =
|
||||
[(CL.FixedPosition (0,0),0,0)]
|
||||
++ min_dist_constraints
|
||||
++ max_dist_constraints
|
||||
++ right_of_constraints
|
||||
-- ++ below_constraints
|
||||
++ same_y_constraints
|
||||
|
||||
new_positions =
|
||||
model.junctions
|
||||
|> List.map .position
|
||||
|> iterate (CL.layout constraints) 1000
|
||||
|
||||
junctions = List.map2
|
||||
(\j p -> { j | position = p })
|
||||
model.junctions
|
||||
new_positions
|
||||
in
|
||||
{ model | junctions = junctions }
|
||||
|
||||
path_through_points : List Vector -> String
|
||||
path_through_points points =
|
||||
points
|
||||
|> List.indexedMap (\i (x,y) -> (if i==0 then "M" else "L")++" "++(ff x)++" "++(ff y))
|
||||
|> String.join " "
|
||||
|
||||
bezier_curve : Vector -> Vector -> Vector -> Vector -> String
|
||||
bezier_curve (x1,y1) (x2,y2) (x3,y3) (x4,y4) =
|
||||
"M "++(ff x1)++" "++(ff y1)++" C "++(ff x2)++" "++(ff y2)++" "++(ff x3)++" "++(ff y3)++" "++(ff x4)++" "++(ff y4)
|
||||
translate_to : Vector -> String
|
||||
translate_to (x,y) = "translate("++(ff x)++","++(ff y)++")"
|
||||
|
||||
main = Browser.document
|
||||
{ init = init
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
, view = view
|
||||
}
|
||||
|
||||
blank_junction : Junction
|
||||
blank_junction =
|
||||
{ id = 0
|
||||
, fn = add_to_end
|
||||
, label = ""
|
||||
, stack = []
|
||||
, position = (0,0)
|
||||
, next = []
|
||||
, solution_stack = []
|
||||
}
|
||||
|
||||
add_to_end a list = list++[a]
|
||||
|
||||
add_junction_links : Junction -> Id -> List Id -> Junction
|
||||
add_junction_links j id next = { j | id = id, next = next }
|
||||
|
||||
source_junction stack = { blank_junction | stack = stack, label="S" } |> add_junction_links
|
||||
|
||||
empty_junction = blank_junction |> add_junction_links
|
||||
|
||||
final_junction = add_junction_links { blank_junction | label = "F" }
|
||||
|
||||
unary_junction fn label = { blank_junction | fn = fn >> add_to_end, label = label } |> add_junction_links
|
||||
|
||||
|
||||
random_junctions : R.Generator (List Junction)
|
||||
random_junctions =
|
||||
let
|
||||
sources : R.Generator (List Junction)
|
||||
sources =
|
||||
R.int 1 2 |> R.andThen (\n -> R.list n (R.int 1 3 |> R.andThen (\m -> R.list m (R.int 1 9) |> R.map (source_junction))))
|
||||
|> R.map (List.indexedMap (\i j -> j i []))
|
||||
|
||||
add_op = R.int -4 5 |> R.map (\x -> if x<=0 then x-1 else x) |> R.map (\x -> unary_junction ((+) x) (if x > 0 then "+"++(fi x) else fi x))
|
||||
other_ops =
|
||||
[ R.int 2 5 |> R.map (\x -> unary_junction ((*) x) ("×"++(fi x)))
|
||||
]
|
||||
|
||||
ops : R.Generator (List (Int -> List Id -> Junction))
|
||||
ops = R.int 1 4 |> R.andThen (\n -> R.list n (R.uniform add_op other_ops |> R.andThen identity))
|
||||
|
||||
finals = R.int 1 1 |> R.andThen (\n -> R.list n (R.constant final_junction))
|
||||
|
||||
step : (List Junction, List Junction, List (Int -> List Id -> Junction)) -> R.Generator (List Junction, List Junction)
|
||||
step (last, prev, next) =
|
||||
let
|
||||
q = Debug.log "join" (List.map .label prev, List.map .label id_next)
|
||||
|
||||
maxid = List.maximum (List.map .id prev) |> Maybe.withDefault -1
|
||||
|
||||
-- add IDs to the next junctions
|
||||
id_next : List Junction
|
||||
id_next =
|
||||
next
|
||||
|> List.indexedMap (\i j -> j (i+maxid+1) [])
|
||||
|
||||
add_track : List Junction -> List Junction -> R.Generator (List (Id,Id))
|
||||
add_track ps ns = case (ps,ns) of
|
||||
(p::prest, n::nrest) -> (add_track prest nrest) |> R.map ((::) (p.id,n.id))
|
||||
|
||||
(p::prest, []) ->
|
||||
case id_next of
|
||||
n1::nrest -> R.uniform n1 nrest |> R.map2 (\r n -> (p.id, n.id)::r) (add_track prest [])
|
||||
[] -> R.constant []
|
||||
|
||||
([], n::nrest) ->
|
||||
case prev of
|
||||
p1::prest -> R.uniform p1 prest |> R.map2 (\r p -> (p.id, n.id)::r) (add_track [] nrest)
|
||||
[] -> R.constant []
|
||||
|
||||
([], []) -> R.constant []
|
||||
|
||||
tracks = add_track prev id_next
|
||||
|
||||
add_joins : Junction -> R.Generator Junction
|
||||
add_joins j =
|
||||
tracks
|
||||
|> R.map (List.filter (first >> (==) j.id) >> List.map second)
|
||||
|> R.map (\n -> { j | next = n })
|
||||
|
||||
joined_prev : R.Generator (List Junction)
|
||||
joined_prev = RE.sequence (List.map add_joins prev)
|
||||
in
|
||||
R.map (\p -> (last++p, id_next))
|
||||
joined_prev
|
||||
|
||||
add_level : R.Generator (List (Int -> List Id -> Junction)) -> R.Generator (List Junction, List Junction) -> R.Generator (List Junction, List Junction)
|
||||
add_level rnext rlastprev =
|
||||
R.map2 (\next (last,prev) -> step (last, prev, next))
|
||||
rnext
|
||||
rlastprev
|
||||
|> R.andThen identity
|
||||
in
|
||||
List.foldl add_level (R.map (pair []) sources) ((List.repeat 3 ops)++[finals])
|
||||
|> R.map (\(a,b) -> a++b)
|
||||
|
||||
random_position = R.map2 pair (R.float 0 1) (R.float 0 1)
|
||||
|
||||
scatter_junctions : List Junction -> R.Generator (List Junction)
|
||||
scatter_junctions junctions =
|
||||
R.list (List.length junctions) random_position
|
||||
|> R.map (List.map2 (\j p -> { j | position = p}) junctions)
|
||||
|
||||
available_moves : List Junction -> List (Id,Id)
|
||||
available_moves junctions =
|
||||
junctions
|
||||
|> List.filter (.stack >> List.length >> (<) 0)
|
||||
|> List.concatMap (\j -> List.map (pair j.id) j.next)
|
||||
|
||||
random_solution : List Junction -> R.Generator (List Junction)
|
||||
random_solution junctions =
|
||||
let
|
||||
step : List Junction -> R.Generator (List Junction)
|
||||
step js = case available_moves js of
|
||||
[] -> R.constant js
|
||||
a::rest ->
|
||||
R.uniform a rest
|
||||
|> R.andThen (\move -> step (move_item move js))
|
||||
in
|
||||
(step junctions)
|
||||
|> R.map (List.map .stack)
|
||||
|> R.map (List.map2 (\j s -> { j | solution_stack = s }) junctions)
|
||||
|
||||
|
||||
|
||||
{- ------------------- -}
|
||||
|
||||
init_model =
|
||||
{ junctions =
|
||||
[ source_junction [1,2,3,4,5,6] 0 [1,3]
|
||||
, unary_junction ((*) 2) "×2" 1 [2]
|
||||
, unary_junction (\x -> x - 1) "-1" 2 [4]
|
||||
, unary_junction ((+) 1) "+1" 3 [2,4,5]
|
||||
, final_junction 4 []
|
||||
, final_junction 5 []
|
||||
]
|
||||
, selected_junction = Nothing
|
||||
}
|
||||
|
||||
init : () -> (Model, Cmd Msg)
|
||||
init _ =
|
||||
( init_model
|
||||
, Cmd.batch
|
||||
[ R.generate (SetJunctions) (random_junctions |> R.andThen scatter_junctions |> R.andThen random_solution)
|
||||
]
|
||||
)
|
||||
|
||||
nocmd model = (model, Cmd.none)
|
||||
|
||||
maybeFilter : (a -> Bool) -> Maybe a -> Maybe a
|
||||
maybeFilter predicate = Maybe.andThen (\a -> if predicate a then Just a else Nothing)
|
||||
|
||||
update : Msg -> Model -> (Model, Cmd Msg)
|
||||
update msg model = case msg of
|
||||
Move (from,to) -> { model | junctions = move_item (from,to) model.junctions } |> nocmd
|
||||
|
||||
SetJunctions junctions -> { model | junctions = junctions } |> layout_junctions |> nocmd
|
||||
|
||||
ClickJunction id ->
|
||||
model.selected_junction
|
||||
|> Maybe.andThen (get_junction model.junctions >> maybeFilter (.next >> List.member id))
|
||||
|> Maybe.map (\j -> { model | junctions = move_item (j.id, id) model.junctions })
|
||||
|> Maybe.withDefault model
|
||||
|> (\m -> { m | selected_junction = Just id })
|
||||
|> nocmd
|
||||
|
||||
RandomMove -> case available_moves model.junctions of
|
||||
m::rest -> (model, R.generate Move (R.uniform m rest))
|
||||
[] -> model |> nocmd
|
||||
|
||||
move_item : (Id,Id) -> List Junction -> List Junction
|
||||
move_item (from,to) junctions = case (get_junction junctions from, get_junction junctions to) of
|
||||
(Just jfrom, Just jto) -> case jfrom.stack of
|
||||
v::rest ->
|
||||
let
|
||||
nfrom = { jfrom | stack = rest }
|
||||
nto = { jto | stack = jto.fn v jto.stack }
|
||||
in
|
||||
junctions
|
||||
|> (set_junction from nfrom)
|
||||
|> (set_junction to nto)
|
||||
|
||||
_ -> junctions
|
||||
|
||||
|
||||
_ -> junctions
|
||||
|
||||
subscriptions model = Sub.none
|
||||
|
||||
fi = String.fromInt
|
||||
ff = String.fromFloat
|
||||
tf = toFloat
|
||||
|
||||
view : Model -> Browser.Document Msg
|
||||
view model =
|
||||
let
|
||||
positions = List.map .position model.junctions
|
||||
|
||||
minx = positions |> List.map first |> List.minimum |> Maybe.withDefault 0
|
||||
maxx = positions |> List.map first |> List.maximum |> Maybe.withDefault 0
|
||||
miny = positions |> List.map second |> List.minimum |> Maybe.withDefault 0
|
||||
maxy = positions |> List.map second |> List.maximum |> Maybe.withDefault 0
|
||||
|
||||
margin = 100
|
||||
|
||||
viewbox = (ff <| minx-margin)++" "++(ff <| miny-margin)++" "++(ff <| maxx-minx + 2*margin)++" "++(ff <| (maxy-miny + 2*margin))
|
||||
|
||||
view_junction junction =
|
||||
let
|
||||
(x,y) = junction.position
|
||||
|
||||
can_move_to =
|
||||
model.selected_junction
|
||||
|> Maybe.andThen (get_junction model.junctions)
|
||||
|> Maybe.map (.next >> List.member junction.id)
|
||||
|> Maybe.withDefault False
|
||||
|
||||
selected = model.selected_junction == Just junction.id
|
||||
|
||||
view_stack_item in_solution i n =
|
||||
let
|
||||
path = (junction.id, i)
|
||||
|
||||
selected_item = (not in_solution) && (model.selected_junction == Just junction.id) && i == 0
|
||||
in
|
||||
Svg.g
|
||||
[ SA.transform <| translate_to (0,(tf i)*11 + 18)
|
||||
]
|
||||
[ Svg.circle
|
||||
[ SA.r "5"
|
||||
, SA.fill (if in_solution then "lightgray" else if selected_item then "yellow" else "lightblue")
|
||||
]
|
||||
[]
|
||||
, Svg.text_
|
||||
[ SA.textAnchor "middle"
|
||||
, SA.dominantBaseline "middle"
|
||||
, SA.fontSize "5"
|
||||
]
|
||||
[ Svg.text <| fi n
|
||||
]
|
||||
]
|
||||
|
||||
in
|
||||
Svg.g
|
||||
[ SA.transform <| translate_to junction.position ]
|
||||
[ Svg.g
|
||||
[ SE.onClick (ClickJunction junction.id) ]
|
||||
[ Svg.circle
|
||||
[ SA.r "10"
|
||||
, SA.fill (if selected then "pink" else if can_move_to then "lightgreen" else "grey")
|
||||
, SA.stroke "black"
|
||||
]
|
||||
[]
|
||||
, Svg.text_
|
||||
[ SA.fontSize "10"
|
||||
, SA.textAnchor "middle"
|
||||
, SA.dominantBaseline "middle"
|
||||
]
|
||||
[ Svg.text junction.label ]
|
||||
]
|
||||
, Svg.g
|
||||
[]
|
||||
(List.indexedMap (view_stack_item True) junction.solution_stack)
|
||||
, Svg.g
|
||||
[]
|
||||
(List.indexedMap (view_stack_item False) junction.stack)
|
||||
]
|
||||
|
||||
junctions_by_y =
|
||||
model.junctions
|
||||
|> List.sortBy (.position >> second)
|
||||
|
||||
tracks =
|
||||
junctions_by_y
|
||||
|> List.concatMap (\j -> j.next |> List.map (get_junction model.junctions >> Maybe.map (\j2 -> (j.position, j2.position))) )
|
||||
|> List.filterMap identity
|
||||
|
||||
num_tracks = List.length tracks
|
||||
|
||||
view_track i ((x1,y1),(x2,y2)) =
|
||||
let
|
||||
yo = (tf i) / (tf num_tracks) * 10 - 5
|
||||
in
|
||||
Svg.path
|
||||
[ SA.d <| bezier_curve (x1,y1) (lerp x1 x2 0.5,y1) (lerp x1 x2 0.5,y2) (x2,y2)
|
||||
, SA.fill "none"
|
||||
, SA.stroke "black"
|
||||
, SA.strokeDasharray "2 2"
|
||||
]
|
||||
[]
|
||||
in
|
||||
{
|
||||
title = "Hey",
|
||||
body =
|
||||
[ Svg.svg
|
||||
[ HA.attribute "viewBox" viewbox
|
||||
]
|
||||
[ Svg.g
|
||||
[]
|
||||
(List.indexedMap view_track tracks)
|
||||
, Svg.g
|
||||
[]
|
||||
(List.map view_junction model.junctions)
|
||||
]
|
||||
, H.p
|
||||
[]
|
||||
[ H.button [ HE.onClick RandomMove] [H.text "Random move"]]
|
||||
, H.pre [] [H.text <| Debug.toString <| available_moves model.junctions ]
|
||||
]
|
||||
}
|
72
src/ConstraintLayout.elm
Normal file
72
src/ConstraintLayout.elm
Normal file
|
@ -0,0 +1,72 @@
|
|||
module ConstraintLayout exposing (layout, satisfy, Constraint, ConstraintKind(..))
|
||||
|
||||
import List.Extra as LE
|
||||
import Tuple exposing (first, second)
|
||||
import Vector exposing (..)
|
||||
|
||||
type alias Index = Int
|
||||
|
||||
type alias Constraint = (ConstraintKind, Index, Index)
|
||||
|
||||
type ConstraintKind
|
||||
= MinDistance Float
|
||||
| MaxDistance Float
|
||||
| RightOf Float
|
||||
| Below Float
|
||||
| FixedPosition Vector
|
||||
| SameY
|
||||
|
||||
satisfy : List Vector -> Constraint -> List (Index,Vector)
|
||||
satisfy positions (constraint,ia,ib) =
|
||||
case (LE.getAt ia positions, LE.getAt ib positions) of
|
||||
(Just a, Just b) ->
|
||||
case constraint of
|
||||
MinDistance mind ->
|
||||
let
|
||||
d = distance a b
|
||||
n = sub b a |> normalise
|
||||
f = max 0 ((mind-d)/2)
|
||||
m = smul f n
|
||||
in
|
||||
[ (ia, neg m)
|
||||
, (ib, m)
|
||||
]
|
||||
|
||||
FixedPosition p -> [ (ia, sub p a)]
|
||||
|
||||
RightOf d->
|
||||
let
|
||||
dx = (first a) - ((first b) - d)
|
||||
f = max 0 dx/2
|
||||
in
|
||||
[ (ia, (-f,0))
|
||||
, (ib, (f,0))
|
||||
]
|
||||
|
||||
Below d->
|
||||
let
|
||||
dy = (first a) - ((first b) - d)
|
||||
f = max 0 dy/2
|
||||
in
|
||||
[ (ia, (0,-f))
|
||||
, (ib, (0,f))
|
||||
]
|
||||
|
||||
SameY ->
|
||||
let
|
||||
dy = (second b) - (second a)
|
||||
in
|
||||
[ (ia, (0, dy/10))
|
||||
, (ib, (0,-dy/10))
|
||||
]
|
||||
_ -> []
|
||||
_ -> []
|
||||
|
||||
|
||||
layout : List Constraint -> List Vector -> List Vector
|
||||
layout constraints positions =
|
||||
let
|
||||
f = 0.1
|
||||
changes = List.concatMap (satisfy positions) constraints
|
||||
in
|
||||
List.foldl (\(i,v) -> LE.updateAt i (add (smul f v))) positions changes
|
43
src/Vector.elm
Normal file
43
src/Vector.elm
Normal file
|
@ -0,0 +1,43 @@
|
|||
module Vector exposing (..)
|
||||
|
||||
import Tuple exposing (pair, first, second)
|
||||
|
||||
type alias Vector = (Float, Float)
|
||||
|
||||
type alias LineSegment = (Vector, Vector)
|
||||
|
||||
add (x1,y1) (x2,y2) = (x1+x2, y1+y2)
|
||||
sub (x1,y1) (x2,y2) = (x1-x2, y1-y2)
|
||||
len (x,y) = sqrt (x*x + y*y)
|
||||
smul s (x,y) = (s*x, s*y)
|
||||
neg (x,y) = (-x,-y)
|
||||
distance v1 v2 = sub v1 v2 |> len
|
||||
|
||||
midpoint (x1,y1) (x2,y2) = ((x1+x2)/2, (y1+y2)/2)
|
||||
|
||||
normalise (x,y) =
|
||||
let
|
||||
d = len (x,y)
|
||||
in
|
||||
(x/d, y/d)
|
||||
|
||||
normal (x,y) = normalise (-y,x)
|
||||
|
||||
dot (x1,y1) (x2,y2) = x1*x2 + y1*y2
|
||||
|
||||
sum : List Vector -> Vector
|
||||
sum = List.foldl add (0,0)
|
||||
|
||||
point_line_distance p (p1,p2) =
|
||||
let
|
||||
v1 = sub p2 p1
|
||||
v2 = sub p p1
|
||||
n = normal v1
|
||||
d1 = len v1
|
||||
alpha = (dot v1 v2) / d1
|
||||
d = len (sub p (add p1 (smul alpha v1)))
|
||||
in
|
||||
if alpha<0 then len (sub p p1) else if alpha>d1 then len (sub p p2) else d
|
||||
|
||||
closest : (a -> Vector) -> Vector -> List a -> Maybe (Int, Float, a)
|
||||
closest get_position p1 = List.map (\a -> (a, get_position a |> sub p1 |> len)) >> List.indexedMap (\i (a,d) -> (i,d,a)) >> List.sortBy (\(i,d,a) -> d) >> List.head
|
0
style.css
Normal file
0
style.css
Normal file
Loading…
Add table
Add a link
Reference in a new issue