Java vectors or arrays for tetris?

I am trying to create a game like Tetris with Clojure, and I am having problems choosing the data structure for the playing field. I want to define the playing field as a resizable grid. Individual blocks are also grids, but they do not need to be changed.

My first attempt was to define a grid as a vector of vectors. For example, an S-block is as follows:

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

But this turns out to be quite complicated for simple things like iteration and drawing (see code below).

To make the grid volatile, my initial idea was to make each line a link. But then I could not understand how to change the value of a specific cell in a row. One option is to create each individual cell instead of each row. But this seems like an unclean approach.

I am now considering using Java arrays. Clojure The aget and aset functions are likely to be much simpler.

However, before delving into a deeper mess, I want to ask ideas / ideas. How would you recommend implementing a mutable 2d mesh? Feel free to share alternative approaches.

Current Status of the source code: Tetris.clj (rev452)

Update
With the help of your suggestions and after some mastering, I came up with the following:

 (defstruct grid :width :height) (defn create-grid [wh initial-value] (struct-map grid :width w :height h :data (ref (vec (repeat (* wh) initial-value))))) (defn create-grid-with-data [wh gdata] (struct-map grid :width w :height h :data (ref gdata))) (defn get-grid [gxy] (let [gdata (g :data) idx (+ x (* (g :width) y)) ] (gdata idx))) (defn set-grid [gxy 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)))) 

I like it because it is a more general solution. If this is completely wrong or can be improved, feel free to talk about it.

+4
source share
3 answers

Using ref for each cell is not necessarily a bad idea. Drop all mesh mutations in dosync and Clojure, make sure that every multi-cell update is performed atomically. (I don’t know if you plan to have multiple threads on your grid at once, but this is safe for this.)

Below, I use hash maps as the values ​​of each cell in the grid, because you might want it to be more than just logical busy / not busy; maybe you want to save color information or something like that. I saved your notation for block definitions.

(This version of indexed works with the edge of Clojure’s bloodstream at the moment. In older versions, you can find indexed in clojure.contrib.)

 (def indexed (partial map-indexed vector)) (defn make-grid [xy] (let [f #(vec (repeatedly %1 %2)) r #(ref {:occupied? false})] (fy #(fxr)))) (defn draw-block [grid xy block] (dosync (doseq [[i row] (indexed block) [j square] (indexed row)] (alter (get-in grid [(+ yi) (+ xj)]) 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 arrays may seem simpler, but they are not thread safe, and most of the good Clojure functions that run on seq are going to massage the arrays anyway. Muting multiple arrays and objects is definitely not idiomatic clojure. Java arrays are commonly used to interact with Java libraries.

+3
source

How about a vector of vectors (as in your original approach) stored in one Atom (or maybe Ref if you need coordinated parallel updates to the playing field and something else ... not very likely to play Tetris), to use with update-in ? (If you are using the recent Clojure snapshot (post-1.1), you can use vector-of to create your vectors. See (doc vector-of) more details.)

Code example:

 (def field (atom (vec (doall (for [_ (range 10)] (vec (repeat 10 false))))))) (defn set-occupied! [xy] (swap! field #(update-in % [xy] (constantly true)))) (defn set-unoccupied! [xy] (swap! field #(update-in % [xy] (constantly false)))) (defn toggle-occupied! [xy] (swap! field #(update-in % [xy] not))) 

In fact, the above are just meant to illustrate how you can manipulate the board. However, the real benefit of this approach is that you do not need these destructive (side effects) in your main logic . Instead, you can write it as a bunch of pure functions that occupy the current state of the playing field, perhaps with something to represent a player’s input (or lack thereof).

In the end, you just need to wrap it in some Java interaction code to connect it to your GUI, but that will be completely separate from your main logic. In general, this should do for a more enjoyable overall experience without any significant overhead (I mean, how big is your playground and how complex are the updates that will be ...?).

+2
source

I think that fully use vector vectors for this. Since you are likely to make a small number of updates per second, I don’t think there will be a drawback in making the entire playing field unchanged.

Of course, you will need to create helper functions to manage this data structure, but here are some basic ones to help you get started:

 (defn make-row [w] (vec (for [x (range w)] 0))) (defn make-grid [wh] (vec (for [y (range h)] (make-row w)))) (defn gget [grid xy] ((grid y) x)) (defn gset [grid xyv] (assoc grid y (assoc (grid y) xv))) 

You can possibly implement everything you need using these or something similar.

+1
source

Source: https://habr.com/ru/post/1309604/


All Articles