Векторы или массивы Java для тетриса? - PullRequest
4 голосов
/ 13 мая 2010

Я пытаюсь создать игру, подобную тетрису, с Clojure, и у меня возникают проблемы с определением структуры данных для игрового поля. Я хочу определить игровое поле как изменяющуюся сетку. Отдельные блоки также являются сетками, но они не должны быть изменяемыми.

Моей первой попыткой было определить сетку как вектор векторов. Например, S-блок выглядит так:

:s-block {
    :grids [
      [ [ 0 1 1 ]
        [ 1 1 0 ] ]

      [ [ 1 0 ]
        [ 1 1 ]
        [ 0 1 ] ] ]
}

Но это оказывается довольно сложно для простых вещей, таких как итерация и рисование (см. Код ниже).

Чтобы сделать сетку изменчивой, моей первоначальной идеей было сделать каждую строку ссылкой. Но тогда я не мог понять, как изменить значение определенной ячейки подряд. Одним из вариантов было бы создание каждой отдельной ячейки ссылки вместо каждой строки. Но это похоже на нечистый подход.

Сейчас я рассматриваю возможность использования массивов Java. Функции aget и aset у Clojure, вероятно, окажутся намного проще.

Однако, прежде чем копаться в более глубоком беспорядке, я хочу спросить идеи / идеи. Как бы вы порекомендовали реализовать изменяемую 2d сетку? Не стесняйтесь также обмениваться альтернативными подходами.

Текущее состояние исходного кода: Tetris.clj (rev452)

Обновление
С помощью ваших предложений и после некоторой работы я придумал следующее:

(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))))

Мне это нравится, потому что это более общее решение. Если это совершенно неправильно или может быть улучшено, не стесняйтесь так говорить.

Ответы [ 3 ]

3 голосов
/ 13 мая 2010

Использование ссылки для каждой ячейки не обязательно является плохой идеей. Бросьте все свои мутации сетки в dosync, и Clojure должен позаботиться о том, чтобы каждое многоклеточное обновление делалось атомарно. (Я не знаю, планируете ли вы, чтобы несколько потоков стучали по вашей сетке одновременно, но это безопасно сделать таким образом.)

Ниже я использую хеш-карты в качестве значений каждой ячейки в сетке, потому что вы можете захотеть, чтобы она была больше, чем просто логическое состояние занято / не занято; Может быть, вы хотите сохранить информацию о цвете или что-то. Я сохранил ваши обозначения для определения блоков, хотя.

(Эта версия indexed в настоящее время работает с передовым Clojure. В более старых версиях вы можете найти indexed в clojure.contrib.)

(def indexed (partial map-indexed vector))

(defn make-grid [x y]
  (let [f #(vec (repeatedly %1 %2))
        r #(ref {:occupied? false})]
    (f y #(f x r))))

(defn draw-block [grid x y block]
  (dosync
   (doseq [[i row] (indexed block)
           [j square] (indexed row)]
     (alter (get-in grid [(+ y i) (+ x j)])
            assoc :occupied? (= 1 square)))))

(defn print-grid [grid]
  (doseq [row grid]
    (doseq [cell row]
      (print (if (cell :occupied?) "X" ".")))
    (println)))

(def *grid* (make-grid 5 5))

user> (draw-block *grid* 2 1 [[1 1 0] 
                              [0 1 1]])
nil
user> (print-grid *grid*)
.....
..XX.
...XX
.....
.....
nil

Java-массивы могут показаться более простыми, но они не являются поточно-ориентированными, и большинство хороших функций Clojure, которые работают с seqs, в любом случае собираются де-массивировать ваши массивы. Отключение множества массивов и объектов определенно не является идиоматическим Clojure. Java-массивы обычно используются для взаимодействия с библиотеками Java.

2 голосов
/ 13 мая 2010

Как насчет вектора векторов (как в вашем первоначальном подходе), хранящегося в одном атоме (или, возможно, Ref, если вам нужны скоординированные параллельные обновления игрового поля и что-то еще ... маловероятно для игры в тетрис) , для использования с update-in? (Если вы используете недавний снимок Clojure (пост-1.1), вы можете рассмотреть возможность использования vector-of для построения ваших векторов. Подробнее см. (doc vector-of).)

Пример кода:

(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, чтобы подключить его к вашему графическому интерфейсу, но это будет полностью отделено от вашей основной логики. В целом, это должно обеспечить более приятное общее впечатление без значительных затрат производительности (я имею в виду, насколько велика ваша игровая площадка и насколько сложными будут обновления ...?).

1 голос
/ 13 мая 2010

Я думаю, что для этого вполне нормально использовать вектор векторов. Поскольку вы, скорее всего, будете делать небольшое количество обновлений в секунду, я не думаю, что будет какая-то обратная сторона в том, чтобы сделать все игровое поле неизменным.

Вам, конечно, нужно будет создать несколько вспомогательных функций для управления этой структурой данных, но вот несколько основных для начала:

(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)))

Вы, вероятно, можете реализовать все остальное, что вам нужно, используя эти или что-то подобное.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...