I have a vector of entities (maps), I have filtered this vector to those entities with a particular key. Then I apply a function to update some values in those entities, and now have a subset that I'd like to merge with the original superset.
world [{a} {b} {c} {d} {e}]
a [{b} {d} {e}] ; (filter matches? world)
b [{b'} {d'} {e'}] ; (map func a)
new-world [{a} {b'} {c} {d'} {e'}] ; (merge world b)
How can I merge my update b
with the original world
?
My map structure is:
{:id identity
:attrs {:property1 {:param1 value
:param2 value}
:property2 {}}
There can be a variable number of properties. Properties can have 0 ({}
) or more params. The only part of the map that is changing are the values, and only some of them.
I have to peer into each map to identify it, and then merge accordingly? What is an idiomatic way to do this?
Clojure's map function can take multiple sequences, but won't compare my subset b
to the whole superset world
, only b
number of world entities, and stop once b
is exhausted.
> (map #(str %1 " " %2) [1 2 3 4 5] [6 7 8])
("1 6" "2 7" "3 8")
Have I chosen the wrong structure for my data in the first place? Would I be better off without a vector and just one map?
{:entid1
{:property1 {:param1 value :param2 value}
:property2 {}}
:entid2
{:property1 {:param1 value :param2 value}
:property2 {:param1 value}
:property3 {:param1 value :param2 value :param3 value}}}
This question looks similar but does not merge my vectors correctly.
Real world implementation
My actual code is part of a game I'm writing to familiarise myself with Clojure (ClojureScript in this instance).
The game state (world) is as follows:
[{:id :player,
:attrs
{:gravity {:weight 10},
:jump {:height 60, :falling false, :ground true},
:renderable {:width 37, :height 35},
:position {:x 60, :y 565},
:walk {:step 4, :facing :right},
:input {},
:solid {:blocked false}}}
{:id wall1,
:attrs
{:solid {},
:position {:x 0, :y 0},
:renderable {:width 20, :height 600}}}
{:id wall2,
:attrs
{:solid {},
:position {:x 780, :y 0},
:renderable {:width 20, :height 600}}}
{:id platform3,
:attrs
{:solid {},
:position {:x 20, :y 430},
:renderable {:width 600, :height 20}}}]
I update the world on each animation frame, then re-render the canvas.
(defn game-loop []
(ui/request-frame game-loop)
(-> world
system/update!
ui/render))
system/update!
is:
(defn update! [world]
(let [new-world (->> @world
(phys/move @player/input-cmd))]
(player/clear-cmd)
(reset! world new-world)
@world))
My plan was to have a chain of systems updating the world in the thread last macro. I am in the process of writing phys/move
.
This means that systems have to update the world, instead of just returning their effect on the world (vector of changes).
I'm contemplating if it's more manageable to have the system/update!
function manage the effects on the world (applying the updates). So systems only return their list of changes. Something like:
(defn update! [world]
(let [updates []
game @world]
(conj updates (phys/move @player/input-cmd game))
(conj updates (camera/track :player game))
; etc.
(reset! world
(map #(update-world updates %) world))
@world))
Repeated (conj updates (func world))
feels clunky though. This is possibly more work than just merging updates (or returning an modified entity) in a system function.
How to elegantly pass state changes between my system functions (phys/move
, camera/track
), and subsystem functions (walk
, jump
)?
My stab at applying Joaquin's map only approach to my move
system:
(ns game.phys)
(def step 4)
(def height 50)
(defn walk?
"is this a walk command?"
[cmd]
(#{:left :right} cmd))
(defn walks?
"update? equivalent"
[entity]
(contains? (:attrs entity) :position))
(defn walk
[cmd entity]
(if (walks? entity)
(let [x (get-in entity [:attrs :position :x]
op (if (= cmd :left) - +)
update {:attrs {:position {:x (op x step)}
:walk {:facing cmd}}}]
(merge entity update))
entity))
; cmd is a deref'ed atom that holds player input commands
; e.g. :left :right: :jump
(defn move
[cmd world]
(cond
(walk? cmd) (map #(walk input-cmd %) world)
(jump? cmd) (map #(jump height %) world)
:else world))
[key value]
pairs.) – Thumbnailwhen-let
in functionmove
will always succeed -()
is truthy. You probably want to wrap the expression formovables
in aseq
. – Thumbnail