我为我正在制作的游戏编写了以下代码。但似乎有点慢。如果您尚未检查代码,则它是A *搜索/路径查找算法。对于100x100网格,它需要大约100-600毫秒,具体取决于所使用的启发式(以及随后访问的磁贴数量)。
没有反射警告。但是,我怀疑拳击可能是一个问题。但我不知道在这种情况下如何摆脱拳击,因为计算分为几个函数。另外,我将tile / coordinates保存为两个数字的向量,如下所示:[x y]
。但是这些数字会被装箱,对吧?如果您不想全部阅读,典型的代码是:(def add-pos (partial mapv + pos))
其中pos
是上述类型的双数字向量。有一些地方的数字以类似于上面add-pos
的方式被操纵,然后放回到矢量中。有没有办法优化这样的代码?任何其他提示也是受欢迎的,与性能相关或其他。
(ns game.server.pathfinding
(:use game.utils)
(:require [clojure.math.numeric-tower :as math]
[game.math :as gmath]
[clojure.data.priority-map :as pm]))
(defn walkable? [x]
(and x (= 1 x)))
(defn point->tile
([p] (apply point->tile p))
([x y] [(int x) (int y)]))
(defn get-tile [m v]
"Gets the type of the tile at the point v in
the grid m. v is a point in R^2, not grid indices."
(get-in m (point->tile v)))
(defn integer-points
"Given an equation: x = start + t * step, returns a list of the
values for t that make x an integer between start and stop,
or nil if there is no such value for t."
[start stop step]
(if-not (zero? step)
(let [first-t (-> start ((if (neg? step) math/floor math/ceil))
(- start) (/ step))
t-step (/ 1 (math/abs step))]
(take-while #((if (neg? step) > <) (+ start (* step %)) stop)
(iterate (partial + t-step) first-t)))))
(defn crossed-tiles [[x y :as p] p2 m]
(let [[dx dy :as diff-vec] (map - p2 p)
ipf (fn [getter]
(integer-points (getter p) (getter p2) (getter diff-vec)))
x-int-ps (ipf first)
y-int-ps (ipf second)
get-tiles (fn [[x-indent y-indent] t]
(->> [(+ x-indent x (* t dx)) (+ y-indent y (* t dy))]
(get-tile m)))]
(concat (map (partial get-tiles [0.5 0]) x-int-ps)
(map (partial get-tiles [0 0.5]) y-int-ps))))
(defn clear-line?
"Returns true if the line between p and p2 passes over only
walkable? tiles in m, otherwise false."
[p p2 m]
(every? walkable? (crossed-tiles p p2 m)))
(defn clear-path?
"Returns true if a circular object with radius r can move
between p and p2, passing over only walkable? tiles in m,
otherwise false.
Note: Does not currently work for objects with a radius >= 0.5."
[p p2 r m]
(let [diff-vec (map (partial * r) (gmath/normalize (map - p2 p)))
ortho1 ((fn [[x y]] (list (- y) x)) diff-vec)
ortho2 ((fn [[x y]] (list y (- x))) diff-vec)]
(and (clear-line? (map + ortho1 p) (map + ortho1 p2) m)
(clear-line? (map + ortho2 p) (map + ortho2 p2) m))))
(defn straighten-path
"Given a path in the map m, remove unnecessary nodes of
the path. A node is removed if one can pass freely
between the previous and the next node."
([m path]
(if (> (count path) 2) (straighten-path m path nil) path))
([m [from mid to & tail] acc]
(if to
(if (clear-path? from to 0.49 m)
(recur m (list* from to tail) acc)
(recur m (list* mid to tail) (conj acc from)))
(reverse (conj acc from mid)))))
(defn to-mid-points [path]
(map (partial map (partial + 0.5)) path))
(defn to-tiles [path]
(map (partial map int) path))
(defn a*
"A* search for a grid of squares, mat. Tries to find a
path from start to goal using only walkable? tiles.
start and goal are vectors of indices into the grid,
not points in R^2."
[mat start goal factor]
(let [width (count mat)
height (count (first mat))]
(letfn [(h [{pos :pos}] (* factor (gmath/distance pos goal)))
(g [{:keys [pos parent]}]
(if parent
(+ (:g parent) (gmath/distance pos (parent :pos)))
0))
(make-node [parent pos]
(let [node {:pos pos :parent parent}
g (g node) h (h node)
f (+ g h)]
(assoc node :f f :g g :h h)))
(get-path
([node] (get-path node ()))
([{:keys [pos parent]} path]
(if parent
(recur parent (conj path pos))
(conj path pos))))
(free-tile? [tile]
(let [type (get-in mat (vec tile))]
(and type (walkable? type))))
(expand [closed pos]
(let [adj [[1 0] [0 1] [-1 0] [0 -1]]
add-pos (partial mapv + pos)]
(->> (take 4 (partition 2 1 (cycle adj)))
(map (fn [[t t2]]
(list* (map + t t2) (map add-pos [t t2]))))
(map (fn [[d t t2]]
(if (every? free-tile? [t t2]) d nil)))
(remove nil?)
(concat adj)
(map add-pos)
(remove (fn [[x y :as tile]]
(or (closed tile) (neg? x) (neg? y)
(>= x width) (>= y height)
(not (walkable? (get-in mat tile)))))))))
(add-to-open [open tile->node [{:keys [pos f] :as node} & more]]
(if node
(if (or (not (contains? open pos))
(< f (open pos)))
(recur (assoc open pos f)
(assoc tile->node pos node)
more)
(recur open tile->node more))
{:open open :tile->node tile->node}))]
(let [start-node (make-node nil start)]
(loop [closed #{}
open (pm/priority-map start (:f start-node))
tile->node {start start-node}]
(let [[curr _] (peek open) curr-node (tile->node curr)]
(when curr
(if (= curr goal)
(get-path curr-node)
(let [exp-tiles (expand closed curr)
exp-nodes (map (partial make-node curr-node) exp-tiles)
{:keys [open tile->node]}
(add-to-open (pop open) tile->node exp-nodes)]
(recur (conj closed curr) open tile->node))))))))))
(defn find-path [mat start goal]
(let [start-tile (point->tile start)
goal-tile (point->tile goal)
path (a* mat start-tile goal-tile)
point-path (to-mid-points path)
full-path (concat [start] point-path [goal])
final-path (rest (straighten-path mat full-path))]
final-path))
答案 0 :(得分:3)
我建议使用Clojure High Performance Programming一本书来解决像你这样的问题。
有解包原语的功能(byte
,short
,int
,long
,float
,double
)。
反射警告不适用于数字类型反射/失败以优化数字代码。有一个lib强制警告数字反射 - primitive-math。
您可以声明函数参数和函数返回值(defn ^Integer foo [^Integer x ^Integer y] (+ x y))
的类型。
如果你想要表现,请避免申请。
如果您想要表现,请避免使用varargs(需要申请的常见原因)。 Varargs函数在每次调用时都会创建垃圾(为了构造args映射,通常不在函数体外使用)。 partial
总是构造一个varargs函数。考虑用#(* x%)替换varargs(partial * x),后者可以更积极地进行优化。
使用原始的jvm单一类型数组有一个权衡(它们很长并且固定长度,这会导致更复杂和更脆弱的代码),但它们的性能会比标准的clojure顺序类型更好,并且可用如果所有其他方法都无法获得所需的性能。
此外,使用criterium来比较代码的各种实现,它有一堆技巧来帮助排除影响执行时间的随机事物,这样你就可以看到在紧密循环中真正表现最佳的东西。 / p>
此外,关于您将某个点表示为[xy] - 您可以减少用它们保存的集合的空间和查找开销(defrecord point [xy])(只要您知道它们将仅保留两个元素,你不介意改变你的代码来要求(:x点)或(:y点))。你可以通过制作或使用一个简单的两个数字的java类来进一步优化(通过权衡失去免疫力)。