俄罗斯方块的向量还是Java数组?
我正试图用Clojure创建一个类似俄罗斯方块的游戏,但在决定游戏场的数据结构时遇到了一些问题。我想把运动场定义为一个可变网格。单个块也是网格,但不需要是可变的 我的第一次尝试是将网格定义为向量向量。例如,S形块如下所示:俄罗斯方块的向量还是Java数组?,java,arrays,data-structures,vector,clojure,Java,Arrays,Data Structures,Vector,Clojure,我正试图用Clojure创建一个类似俄罗斯方块的游戏,但在决定游戏场的数据结构时遇到了一些问题。我想把运动场定义为一个可变网格。单个块也是网格,但不需要是可变的 我的第一次尝试是将网格定义为向量向量。例如,S形块如下所示: :s-block { :grids [ [ [ 0 1 1 ] [ 1 1 0 ] ] [ [ 1 0 ] [ 1 1 ] [ 0 1 ] ] ] } 但是对于像迭代和绘制这样的简单事情来说,
:s-block {
:grids [
[ [ 0 1 1 ]
[ 1 1 0 ] ]
[ [ 1 0 ]
[ 1 1 ]
[ 0 1 ] ] ]
}
但是对于像迭代和绘制这样的简单事情来说,这是相当棘手的(参见下面的代码)
为了使网格可变,我最初的想法是使每一行都成为一个引用。但是,我真的不知道如何更改行中特定单元格的值。一个选项是为每个单元格创建一个ref,而不是每行。但这感觉像是一种不干净的做法
我现在正在考虑使用Java数组。Clojure的aget和aset函数可能会变得更简单
然而,在把自己陷入更深的困境之前,我想问问自己的想法/见解。您建议如何实现可变二维网格?也可以随意分享其他方法
源代码当前状态:
更新在你的建议的帮助下,经过我自己的一些修补,我得出了以下结论:
(defstruct grid :width :height)
(defn create-grid [w h initial-value]
(struct-map grid
:width w
:height h
:data (ref (vec (repeat (* w h) initial-value)))))
(defn create-grid-with-data [w h gdata]
(struct-map grid
:width w
:height h
:data (ref gdata)))
(defn get-grid [g x y]
(let [gdata (g :data)
idx (+ x (* (g :width) y)) ]
(gdata idx)))
(defn set-grid [g x y value]
(let [data (deref (g :data))
idx (+ x (* (g :width) y)) ]
(dosync (alter (g :data) (fn [_] (assoc data idx value))))))
(defn get-grid-rows [g]
(partition (g :width) (deref (g :data))))
我喜欢它,因为它是一个更通用的解决方案。如果它是完全错误的,或者可以改进,请随意说。一个向量向量(如您最初的方法)如何存储在一个原子中(或者如果您需要对游戏场和其他东西进行协调的并发更新,可能是Ref…对于俄罗斯方块游戏来说不太可能),与
中的更新一起使用?(如果您使用的是Culjure(StP1.1)的近期快照,您可以考虑使用<代码> <代码>向量来构建您的向量。详见<代码>(DOC向量)< /C> >详细信息。
示例代码:
(def field (atom (vec (doall (for [_ (range 10)] (vec (repeat 10 false)))))))
(defn set-occupied! [x y]
(swap! field #(update-in % [x y] (constantly true))))
(defn set-unoccupied! [x y]
(swap! field #(update-in % [x y] (constantly false))))
(defn toggle-occupied! [x y]
(swap! field #(update-in % [x y] not)))
事实上,上面的内容只是为了说明如何操作电路板。然而,这种方法的真正好处是,在核心逻辑中不需要那些破坏性(副作用)功能。相反,您可以将其编写为一组纯粹的函数,以获取游戏场的当前状态,可能还包括一些表示玩家输入(或缺少输入)的内容
最终,您只需要将其封装在一些Java互操作代码中,以将其插入GUI,但这将与您的核心逻辑完全解耦。总而言之,它应该在没有显著性能成本的情况下提供更愉快的总体体验(我的意思是,您的竞争环境可能有多大,更新将有多复杂…?)。对每个单元格使用ref并不一定是个坏主意。将所有网格突变放入dosync
中,Clojure应该注意每个多单元更新都是以原子方式完成的。(我不知道您是否计划同时在网格上运行多个线程,但这样做是安全的。)
下面我使用散列映射作为网格中每个单元格的值,因为您可能希望它不仅仅是一个布尔值被占用/未被占用;也许你想储存颜色信息或其他东西。但我保留了定义块的符号
(这个版本的indexed
目前适用于最前沿的Clojure。在旧版本中,您可以在Clojure.contrib中找到indexed
。)
Java数组看起来可能更简单,但它们不是线程安全的,而且大多数在seqs上运行的优秀Clojure函数都将以任何方式取消数组。改变一堆数组和对象绝对不是惯用的Clojure。Java数组通常用于与Java库的互操作。我认为使用向量向量完全可以。因为你可能每秒只会做少量的更新,所以我不认为让整个游戏环境一成不变会有任何负面影响
当然,您需要构建一些帮助器函数来管理此数据结构,但以下是一些基本函数:
(defn make-row [w]
(vec (for [x (range w)] 0)))
(defn make-grid [w h]
(vec (for [y (range h)] (make-row w))))
(defn gget [grid x y]
((grid y) x))
(defn gset [grid x y v]
(assoc grid y (assoc (grid y) x v)))
您可能可以使用这些或类似的工具来实现所需的所有其他功能。我不完全理解,但它工作得很好:)(我看到set-Occessed用一个总是返回true的函数替换网格单元的内容,但我仍在努力弄清楚结构中的更新实际上是如何工作的。)总之,谢谢!很乐意帮忙。:-)实际上设置已被占用
将给定网格单元的内容替换为将(始终为true)
应用于其当前内容的结果(这就是中的更新所做的;它有点类似于统一更新模型中的交换!
,更改
&friends)。您还可以使用#(assoc in%[x y]true)
进行简单的替换(我想这会更有意义)<代码>切换已占用代码>对我来说似乎是最有趣、最有用的函数,而且最好使用updatein
编写。(哦,还有一个提取数据的get in
函数。)另外,我可能会将整个棋盘状态包装在一个ref中。如果您需要协调访问游戏状态的任何事务/同步线程,例如显示渲染循环或分数计算等,这将非常有用。这不完全是对您问题的回答,但是,您可能想看看clojure中的俄罗斯方块是否在不借助可变网格的情况下实现。
(defn make-row [w]
(vec (for [x (range w)] 0)))
(defn make-grid [w h]
(vec (for [y (range h)] (make-row w))))
(defn gget [grid x y]
((grid y) x))
(defn gset [grid x y v]
(assoc grid y (assoc (grid y) x v)))