anchor.diff:              R.button [ClassName (if currentMeth = M3d6 then "selected" else ""); OnClick (fun _ -> pick M3d6)] [R.str "Roll 3d6"]
               R.button [ClassName (if currentMeth = M4d6k3 then "selected" else ""); OnClick (fun _ -> pick M4d6k3)] [R.str "Roll 4d6 drop lowest"]
               ]
  -let statDisplay stats =
  -    let str = sprintf "Str %d\nDex %d\nCon %d\nInt %d\nWis %d\nCha %d%s" stats.Str stats.Dex stats.Con stats.Int stats.Wis stats.Cha (if stats.Special.IsSome then "\n" + stats.Special.Value else "")
  -    str.Split('\n') |> List.ofArray |> List.map (fun txt -> R.div [] [R.str txt])
  -
  -type AbstractDungeon() as this =
  -    inherit Component<obj, ADState>(obj())
  +
  +let statDisplay stats (setState : (Stats -> Stats) -> unit) =
  +    let statNodes =
  +        Stats.Descriptors
  +        |> List.mapi (fun i (descr, getter, setter) ->
  +                        R.div [
  +                                OnDragOver (fun e -> e.preventDefault());
  +                                OnDrop (fun e ->
  +                                        e.preventDefault();
  +                                        match (System.Int32.TryParse (e.dataTransfer.getData("text"))) with
  +                                        | true, v when v < Stats.Descriptors.Length && i <> v ->
  +                                            let (_, otherGetter, otherSetter) = Stats.Descriptors.[v]
  +                                            setState (fun (stats' : Stats) ->
  +                                                            let otherVal = otherGetter stats'
  +                                                            let thisVal = getter stats'
  +                                                            setter(otherSetter(stats', thisVal), otherVal)
  +                                                     )
  +                                        | _ -> ()
  +                                        )
  +                                ]           <|
  +                            [
  +                                R.text [
  +                                        Draggable true :> IProp
  +                                        upcast OnDragStart (fun e -> printfn "Set %d" i; e.dataTransfer.setData("text", i.ToString()) |> ignore)
  +                                        ]          <|
  +                                    [R.str (sprintf "%s %d" descr (getter stats))]
  +                                ])
  +    match stats.Special with
  +    | Some(v) -> statNodes @ [(R.div [] [R.str v])]
  +    | None -> statNodes
  +
  +type AbstractDungeon(p) as this =
  +    inherit Component<obj, ADState>(p)
       let init(prev) =
           let meth = if Option.isSome prev then prev.Value.statMethod else M3d6
           {
  
positive.code:         R.div [] [
              statMethodPicker this.state.statMethod (fun picked -> this.setState (init (Some { this.state with statMethod = picked })))
              R.div [] (List.append
                  (statDisplay (this.state.stats.Add(snd this.state.race)))
                  [
                  racePicker this.state.race (fun picked -> this.setState { this.state with race = picked })
                  R.text [] [R.str descr]
  
negative.code:     R.div [] [
          R.h1 [] [R.str "Abstract Dungeoneering"]
          R.h3 [] [R.str "Advanced character creation"]
          R.com<AbstractDungeon, _, _>(obj()) []
          ],
      Browser.document.getElementById "content")
  |> ignore