问题
I'm playing around with a crafty tutorial here:
http://buildnewgames.com/introduction-to-crafty/
and am wondering how this particular function be implemented in clojurescript/clojure
var max_villages = 5;
for (var x = 0; x < Game.map_grid.width; x++) {
for (var y = 0; y < Game.map_grid.height; y++) {
if (Math.random() < 0.02) {
Crafty.e('Village').at(x, y);
if (Crafty('Village').length >= max_villages) {
return;
}
}
}
}
I know that we can have the (for [])
construct but how would you get it to stop when max_villages hits 5?
回答1:
Here's one approach:
(def max-villages 5)
(->> (for [x (range map-width)
y (range map-height)]
[x y])
(filter (fn [_] (< (rand) 0.02)))
(take max-villages))
Then perhaps add (map make-village-at)
or something similar as the next stage of the pipeline; if it's meant to perform side effects, add a dorun
or doall
as the final stage to force them to happen at once (choosing one or the other depending on whether the return values are interesting).
NB. some extra vectors and random numbers may be generated due to seq chunking, it'll be less than 32 though.
A more imperative approach with a counter for comparison:
(let [counter (atom 0)]
(doseq [x (range map-width)
:while (< @counter max-villages)
y (range map-height)
:while (< @counter max-villages)
:when (< (rand) 0.02)]
(swap! counter inc)
(prn [x y]))) ; call make-village-at here
:while
terminates the loop at the current nesting level when its test expression fails; :when
moves on to the next iteration immediately. doseq
supports chunking too, but :while
will prevent it from performing unnecessary work.
回答2:
Using recursion it would be something like:
(letfn [(op [x y]
(if (= (rand) 0.02)
(do
(village-at x y)
(if (>= (village-length) max-villages) true))))]
(loop [x 0 y 0]
(when (and (< x width) (not (op x y)))
(if (= (inc y) height)
(recur (inc x) 0)
(recur x (inc y))))))
回答3:
That's a great tutorial!
A variation on Michael's approach (I would have just commented to his answer but I don't have enough stack power yet) would be to use Cartesian products rather than nested for loops:
;; some stub stuff to get the example to run
(ns example.core
(:use clojure.math.combinatorics))
(def max-villages 5)
(def map-width 10)
(def map-height 10)
(defn crafty-e [x y z] (print z))
;; the example, note I use doseq rather than map to empasize the fact that the loop
;; is being performed for its side effects not its return value.
(doseq [coord (take max-villages
(filter
(fn [_] (< (rand) 0.02))
(cartesian-product (range map-width) (range map-height))))]
(crafty-e :village :at coord))
来源:https://stackoverflow.com/questions/16599191/how-would-a-loop-with-a-nested-return-be-implemented-in-clojure