Fork me on GitHub
#specter
<
2017-12-16
>
jrheard19:12:56

is there a natural way to use srange, like, backward?

jrheard19:12:04

begin unreasonably long context dump for which i am greatly sorry: again, i have a 2d grid of values that are integers or nil

jrheard19:12:40

i need to start at a particular x,y point on the grid and get the values seen as you walk four steps in each of the cardinal directions from that x,y point

jrheard19:12:05

i call these “runs”, they’re related to the logic of this game i’m programming

jrheard19:12:18

so far, getting the right run and the down run is pretty straightforward:

jrheard19:12:29

(let [x 6
        y 8]
    {:right-run (select [(srange (inc x) (+ x MAX-RUN-LENGTH)) ALL
                         y]
                        (@app-state :grid))
     :down-run (select [x
                        (srange (inc y) (+ y MAX-RUN-LENGTH)) ALL]
                       (@app-state :grid))
     })

jrheard19:12:29

but i’m having trouble figuring out how to do the left and up runs

jrheard19:12:36

i could do it by hand in each of those cases - eg, if you’re going left, then in that situation you do (srange (- x MAX-RUN-LENGTH) x)

jrheard19:12:05

maybe that’s just the sanest thing to do here

jrheard19:12:03

my preexisting clojure code involved a function (find-runs [x y xdir ydir]), with invocations like (find-runs x y 0 -1), and so inside that function you’ve got a single bit of run-finding code that can handle any of the four cardinal directions

jrheard19:12:50

but it looks like if i want to specterize this code, the sanest thing is to write four specter select calls by hand, rather than reimplement (find-runs x y xdir ydir) in a way that uses a single select call whose path is heavily parameterized? apologies if any of this is foolish / obvious / doesn’t make any sense

jrheard19:12:19

preexisting implementation of find-runs in case it’s helpful:

jrheard19:12:23

(reduce (fn [[run-length run-sum] num-steps-in-direction]
     ; Find the position of the cell we're currently examining.
     (let [run-x (+ x (* xdir num-steps-in-direction))
           run-y (+ y (* ydir num-steps-in-direction))]
       (if (or (not (cell-is-on-grid grid run-x run-y))
               (nil? (get-in grid [run-x run-y])))
         ; If the cell's value is nil or this position is off the grid, the run is over.
         (reduced [run-length run-sum])
         ; Otherwise, record this cell's value and continue following the run.
         [(inc run-length)
          (+ run-sum (get-in grid [run-x run-y]))])))
   [0 0]
   (map inc (range)))

jrheard19:12:38

the end goal here is to come up with a “run”, which is essentially [(count run-values) (apply + run-values)]

jrheard19:12:03

and again, the main problem i’m having trouble with is figuring out whether it’s sanely possible to write a single select call that’s parameterized on x, y, xdir, and ydir, or if that select call would be too grody and i should instead write four single-purpose select calls by hand

jrheard19:12:34

i’ll do the by-hand approach for now, this is a toy project and doesn’t matter, but thanks in advance for your time and advice! 🙂

jrheard19:12:29

here’s what i ended up with:

(defn grid-range [start end direction]
  (srange (max start 0)
          (min end (if (= direction :horizontal)
                     GRID-WIDTH
                     GRID-HEIGHT))))

(let [x 6
      y 4]
  {:right-run (select [(grid-range (inc x) (+ x MAX-RUN-LENGTH) :horizontal) ALL
                       y]
                      (@app-state :grid))
   :down-run  (select [x
                       (grid-range (inc y) (+ y MAX-RUN-LENGTH) :vertical) ALL]
                      (@app-state :grid))
   :left-run  (select [(grid-range (- x (dec MAX-RUN-LENGTH)) x :horizontal) ALL
                       y]
                      (@app-state :grid))
   :up-run    (select [x
                       (grid-range (- y (dec MAX-RUN-LENGTH)) y :vertical) ALL]
                      (@app-state :grid))})
the sanest way i can think to clean it up is to make a function like eg (get-run-selector-path x y :left), which is implemented with just a cond with four branches, and the :left branch looks like [(grid-range (- x (dec MAX-RUN-LENGTH)) x :horizontal) ALL y], etc; maybe there’s a saner thing to do here though?

jrheard19:12:36

all this is further complicated by the fact that i’d like all the runs’ values to be in order based on their direction from the origin cell because a “run” just means “a series of values in a row, in each cardinal direction, starting at this cell but excluding this cell’s value, with maximum length 4, stopping as soon as you see your first nil”.

jrheard19:12:22

so i’ll need to end up reversing left-run and up-run and then call (take-until nil the-run) on each of my four runs. and at the end of the day i’m like, maybe the end result in specter will be insane and i should just stick with my preexisting implementation, which is looking way better now that i’m comparing the two? and if so, that’s totally cool. again, sorry for the wall of text!

jrheard20:12:56

in case it’s helpful, the game board looks like this: http://jrheard.com/quinto/ the “make a move” button has the AI detect the highest-scoring possible move and make it. there isn’t yet a way for the user to make a move, and there isn’t yet a visible score or any explanation of how the game works. the game’s basically scrabble but for numbers, the goal is to make “runs” of numbers that sum up to a multiple of five, it’s called quinto, my friend found it at goodwill and it seems like basically zero people in the world have ever played it

nathanmarz21:12:35

@jrheard your use case is probably best handled with matrix-specific navigators

nathanmarz21:12:20

here's an excerpt from some code I have:

(defnav matrix-elem [row col]
  (select* [this structure next-fn]
    (next-fn (-> structure :rows (nth row) (nth col)))
    )
  (transform* [this structure next-fn]
    (let [rows (:rows structure)
          r (nth rows row)
          new-elem (next-fn (nth r col))]
      (->Matrix (assoc rows row (assoc r col new-elem)))
      )))

nathanmarz21:12:58

you could also make a "submat" navigator that navigates you to a submatrix

nathanmarz21:12:31

then you could do something like (select [(submat 4 2 8 2) ALL ALL] mat) to get the "down run"

nathanmarz21:12:13

for a less flexible approach you could have a submat-elems function like so:

(defnav matrix-elem [row col]
  (select* [this structure next-fn]
    (next-fn (-> structure(nth row) (nth col)))
    )
  (transform* [this structure next-fn]
    (let [rows (:rows structure)
          r (nth rows row)
          new-elem (next-fn (nth r col))]
      (assoc rows row (assoc r col new-elem))
      )))

(defn ^:direct-nav submat-elems [row col row2 col2]
  (reduce
    multi-path
    (for [r (range row (inc row2))
          c (range col (inc col2))]
      (matrix-elem r c)
      )))

(def data
  [[1   2   3   4]
   [5   6   7   8]
   [9   :a  :b  :c]
   [:d  :e  :f  :g]])

(select (submat-elems 1 1 3 1) data)
;; => [6 :a :e]

nathanmarz21:12:30

technically you don't really need matrix-elem and can just use nthpath