So, I came up with a solution that seems to work. As reference (and for 
others), I did reference work on Entity-Component Systems mentioned in this 
group before.

https://gist.github.com/TheSeamau5/8d7fac9f1937f0bab317
https://gist.github.com/TheSeamau5/ec9695232ae919185f4d

And I found Chris Granger's post quite 
helpful: www.chris-granger.com/2012/12/11/anatomy-of-a-knockout/

I decided to model a generic entity as:

// entity.elm

type alias ID = Int

type alias Model = {
    id : ID
  , components : List Component
  }

With the components as a type:

// entity.elm
type Component =
    Spatial Component.Spatial.Model
  | Corporeal Component.Corporeal.Model
  | Label Component.Label.Model
  | Viewable ComponentView
  | Gravitate Component.Gravitate.Model

type alias ComponentView = {
    func : Model -> Form
  }

And the Spatial component model would contain fields that had to do with 
spatial orientation of the entity, modeled as:

// components/spatial.elm
type alias Model = {
    mass : Float
  , forces : List Vec.Vec
  , pos : Vec.Vec    -- in units
  , vel : Vec.Vec    -- in units / centiseconds
  , acc : Vec.Vec    -- in units / centiseconds ** 2
  , heading : Float
  }

So in declaring an instance entity, such as a cat, I modeled it as:

// entity/cat.elm
init : Entity.ID -> Entity.Model
init id = {
    id = id
  , components = [
      Entity.spatial 50 (100, 0)
    , Entity.corporeal (45, 45) Color.orange
    , Entity.viewable view
    , Entity.gravitate Component.Gravitate.ToEarth
    ]
  }

where view() was another method with the signature view : Model -> Form

What is Entity.spatial? Well, it's just:

//entity.elm
spatial : Float -> Vec -> Component
spatial mass pos =
  Spatial <| Component.Spatial.init mass po

So entity.elm has some functions that delegate to the specific component 
being created, and just tacks on the type in front.

How do we get at a particular component type in a list of components?

getSpatial : Model -> Maybe Component.Spatial.Model
getSpatial model =
  List.filterMap (\component ->
    case component of
      Spatial spaceModel ->
        Just spaceModel
      _ ->
        Nothing
  ) model.components
  |> List.head

Unfortunately, this needed to be repeated for all other components. I don't 
know of a way to pass in a type and use it in the branches of a case 
expression.

But sometimes, you don't just want to get the component, you want to 
replace it. Learning the lesson from functors, I added a filterMap for 
entity, treating it as a container.

filterMapSpatial : (Component.Spatial.Model -> Component.Spatial.Model) -> 
Model -> Model
filterMapSpatial func entity =
  { entity |
    components = List.map (\component ->
        case component of
          Spatial space ->
            Spatial (func space)
          _ ->
            component
      ) entity.components
  }

So when I update and step through the model with a dt tick, I do:

//main.elm
step : Float -> Model -> Model
step dt model =
  model
  |> boundFloor
  |> gravity dt
  |> newtonian dt
  |> clearForces

where each one of those functions changes the model somehow. In newtonian, 
it maps over all entities and calls newtonian on each entity.

// main.elm
newtonian : Float -> Model -> Model
newtonian dt model =
  map (Entity.newtonian dt) model

it calculates the new position based on the velocity. It calculates 
velocity based on the total acceleration. And it calculate the total 
acceleration based on all the forces acting on it. So even though all the 
entities have different components, I'm able to call newtonian on each 
entity, and filterMapSpatial will act on a spatial component, if it can 
find one, and update it.

// entity.elm
newtonian : Float -> Model -> Model
newtonian dt entity =
  filterMapSpatial (\space ->
    let
      acc = Component.Spatial.totalAcc space
      space2 = Component.Spatial.vel (space.vel |+ acc .* (dt / 10)) space
    in
      Component.Spatial.pos (space2.pos |+ ((space.vel |+ space2.vel) .* 
(0.5 * (dt / 10)))) space2
  ) entity

And in that way, I can call newtonian on every entity, regardless of what 
components it has, because filterMapSpatial looks for a spatial component 
in an entity's component list, and only acts to replace it if it can find 
one. 

And that's how I got around overloading and polymorphism. The full code 
(work in progress, as of this posting) is here 
<https://github.com/iamwilhelm/bayes-cat/tree/796542f8789726a797a268bf521c2a8c5e56b64a>.
 
The downside is that every time you add a component, you have to add 
accessor and filterMap functions for each component.

Hope that helps someone.

Wil

On Tuesday, May 10, 2016 at 11:56:44 PM UTC-7, Wil C wrote:
>
> Thanks so far for the help that elm community has given lately. Really 
> appreciated. :)
>
> I've been in the middle of writing a simple game in Elm. However, I'm 
> running into a roadblock that makes me feel unproductive in elm. Here's the 
> problem:
>
> In a game, I got it in my head that the way to do this is to have a list 
> of entities (game objects), where you'd map over the entities to call 
> update on them to have them change. Because they're all in the list, they 
> should all have the same interface, like update(). However, an entity may 
> have different abilities due to them playing a different role: player 
> character is controlled by keyboard, whereas AI characters is controlled by 
> computer. So in a list when updating them, they should have the same 
> interface--the same type, whereas in other computational contexts, they 
> should be treated differently. Polymorphism is the tool that comes to mind, 
> given my background.
>
> I know polymorphism has been done to death on this forum, and there are no 
> typeclasses in Elm. But I didn't see a solution that I understood. It seems 
> like case statements or functors might be a way to go. Modeling it after 
> the composability of the Html startapp, I figured having a container called 
> Entity, that can contain different types of entities with different 
> components was a way to go.
>
> module Component where
>
> type alias Spatial = {
>     pos: Vec.Vec
>   , vel: Vec.Vec
>   , acc: Vec.Vec
>   , heading: Float
>   }
>
> module Entity.Egg where
>
> type alias Model =
>   { space: Component.Spatial
>   , corp: Component.Corporeal -- a record for dimension and color
>   , label: Component.Label -- a record for labeling the entity for display
>   }
>
> view : Model -> Form
> view model =
>   group [
>     filled model.corp.color <| circle ((fst model.corp.dim) / 2)
>   ]
>
> Each entity "subclass" has a view method that we call that shows the right 
> view when we pass in the model. 
> module Entity where
>
> type Entity a =
>     Egg a
>   | Cat a
>
> I figured I could use the Entity container as a functor, so I can map 
> functions over it, like how Maybe and Result do it.
>
> map : (a -> b) -> Entity a -> Entity b
> map func model =
>   case model of
>     Role.Egg a ->
>       Role.Egg <| func a
>     Role.Cat a ->
>       Role.Cat <| func a
>
> So that way, I can ask an entity to apply a function to the entity model. 
> That's all well and good, but if I want to call the view() method across 
> all entities, I'm not sure how to do that. If I have a list of entities, I 
> could map across the List of entities, but how do I know which view() to 
> call for each? Would I have to have a view() method at the entity level?
>
> mapView : Entity a -> Form
> mapView model =
>   case model of
>     Role.Egg a ->
>       Role.Egg <| Entity.Egg.view a
>     Role.Cat a ->
>       Role.Cat <| Entity.Cat.view a
>
> That seems like a lot of repetition, especially if I have other methods, 
> and other entities...wouldn't i get a combinatorial explosion? Is there a 
> better way?
>
>

-- 
You received this message because you are subscribed to the Google Groups "Elm 
Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to elm-discuss+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to