 ;; necessary for the player to make the first move.
 
 (defn initial-board
-  "Returns a board with two (random) tiles, where tile-fn is a function that
-   returns a new tile."
+  "Returns a board with two tiles returned by tile-fn, which will be called
+   twice."
   [tile-fn]
   (let [f (partial add-tile tile-fn)]
     (-> empty-board f f)))

                                (filter (set (keys key-map)))
                                (map key-map))))))

;; Finally we arrived to the part where we put everything together, the game
;; loop. The loop's bindings will serve us to keep the state we need, that is,
;; the board and the action we want to perform. On each iteration, we either
;; render the board or wait for a move event. Rendering the board means:
;; - render the board in the translation phase,
;; - wait for the transition animations,
;; - render the board in the reveal phase, and
;; - wait for the appearence animations.
;; - If the game didn't end, start another iteration for the board and the :wait
;;   action, but
;; - if it did, stop looping.
;; Wait for a move event means:
;; - wait for a key event,
;; - if the move was valid, loop again with the new board and the :render
;;   action, but
;; - if it wasn't valid, keep waiting for a valid move event with the current
;;   board.
;; The loop is started with a random initial board and the :render action. Even
;; though there's nothing to show in the first translation phase, it happens so
;; dast that it doesn't matters.

(let [keys (keys-chan)]
  (go-loop [board (initial-board build-tile)

  [board]
  (some (complement -synth?) (compact board)))

;; The player loses the game when there are no more possible moves, that is,
;; sliding and synthesizing the board on every direction yields the same
;; original board.

(defn lost?
  "Returns true when applying slide-synth to board and every possible direction
   always returns board."
  [board]
  (apply = board (map (partial slide-synth board) [:left :right :up :down])))

;; The game ends when the player wins or loses.

(defn ended?
  "Returns true if won? or lost? is truthy for board."
  [board]
  (or (won? board) (lost? board)))

;; # Rendering

;; Because manually manipulating the DOM is messy, we'll let
;; [Quiescent](https://github.com/levand/quiescent) do it for us instead.
;; Quiescent is an abstraction over [React](http://facebook.github.io/react)
;; that leverages immutability. We are responsible only for supplying a
;; specification of what the UI should look like. Quiescent will rely on React
;; to generate the minimal set of changes that need to be applied to the DOM.

;; Elements are placed on the board on well established positions by wrapping
;; them in a div with the "pos" and "pos-i-j" classes, where i and j are numbers
;; from 0 to 3 that stand for the row and the column that defines the position.
;; For instance, the following markup places an element on the bottom-left
;; corner.

;;    <div class="pos pos-3-0">
;;      <!-- some element -->
;;    </div>

;; While that is enough to position an element, a problem arises if we want to
;; rely on CSS transitions to animate the translation of the element as its
;; position changes over time. Without getting into much detail, when React
;; updates the DOM it tries to do it in the most efficiently manner by recycling
;; what is already in the DOM. It does not provide (by default) any guarantee
;; that the same DOM element will be used for our example div across render
;; passes. Fortunately, the only reason React can't keep a mapping between the
;; elements of our specification and the elements of the DOM is because it
;; doesn't have a way to identify the elements of the specification. We can
;; provide React the information it needs by assigning a key to our divs. The
;; [official documentation](http://facebook.github.io/react/docs/reconciliation.html)
;; is a great place to learn more about React's reconciliation process.

(defn pos-view
  "Returns a div wrapping content with positioning classes derived from pos,
   which is a two-element vector containing a row and a column. Optionally, a
   React key can be provided."
  ([pos content] (pos-view pos nil content))
  ([[i j] key content]
     (d/div {:className (str "pos pos-" i "-" j)
             :key key}
       content)))

;; A square is just a div with the square class.

;;    <div class="square"></div>

(defn square-view
  "Returns a div with a square class."
  []
  (d/div {:className "square"}))

;; To render the board we need to draw a square in every position.

(defn squares-view
  "Returns a div containing a square for each position of a board with a given
   order."
  [order]
  (apply d/div {:className "squares"}
    (for [i (range order) j (range order)]
      (pos-view [i j] (square-view)))))

;; Tiles are styled differently according to their value. We'll use the
;; class "tile" to apply styles shared by all tiles and "tile-val", where val is
;; the tile's value, to apply the right color, font size, shadow, etc. However,
;; a tile might have another classes.

;;    <div class="tile tile-2 fade-in">2</div>

;; The previous markup represents a tile with a value of two which has been just
;; added to the board. Extra classes, such as "fade-in", are needed to animate
;; the appearence of new tiles.

(defn tile-view
  "Returns a div with tile classes whose content is val. Another classes can be
   provided."
  ([val] (tile-view val ""))
  ([val classes] (d/div {:className (str classes " " (str "tile tile-" val))}
                   val)))

;; Of course there are a lot of tiles that need to be rendered.

(defn tiles-view
  "Returns a div with the tiles class containing the tiles provided. Tiles are
   wrapped in a div with their position. Each tile is a map that must contain
   :val (something with a str value) and :pos (two-element vector containing a
   row and a column number). Optionally, it can also contain :key (a React key)
   and :classes (string to be concatenated with the default tile classes)."
  [tiles]
  (apply d/div {:className "tiles"}
    (map (fn [{:keys [val pos key classes]}]
           (pos-view pos key (tile-view val classes)))
         tiles)))

;; Finally, we can put toghether the board with the squares and the tiles in a
;; Quiescent component. To learn more about Quiescent components you can read
;; the
;; [official documentation](https://github.com/levand/quiescent/blob/master/docs.md).

(q/defcomponent SquareBoard
  [tiles order]
