はじめに
この記事の続き。
公式チュートリアルの「Wrapping Up」の章(最後)まで進めてみました。history
に保存した盤面を再生できるようになりました。
全ソースはこちら。
1. 実装
(defn square [{:keys [on-click value]}]
[:button.square
{:on-click #(on-click)}
value])
(defn board [& {:keys [squares on-click]}]
(letfn
[(render-square [i]
[square {
:value (squares i)
:on-click #(on-click i)
}])]
[:div
[:div.board-row
(render-square 0)
(render-square 1)
(render-square 2)]
[:div.board-row
(render-square 3)
(render-square 4)
(render-square 5)]
[:div.board-row
(render-square 6)
(render-square 7)
(render-square 8)]]))
(defn game []
(let [state (r/atom {:history (vec [{:squares (vec (repeat 9 ""))}])
:step-number 0
:x-is-next? true})]
(fn []
(letfn
[(handle-click [i]
(let [history (vec (take (inc (get @state :step-number)) (get @state :history)))
current (last history)
squares (get current :squares)
x-is-next? (get @state :x-is-next?)]
(when (and (= (calculate-winner squares) nil) (= (squares i) ""))
(swap! state
assoc :history
(conj history
(assoc-in current [:squares i] (if x-is-next? "X" "O"))))
(swap! state assoc :step-number (count history))
(swap! state assoc :x-is-next? (not x-is-next?)))))
(jump-to [step]
(swap! state assoc :step-number step)
(swap! state assoc :x-is-next? (if (= (mod step 2) 0) true false)))]
(let [history (get @state :history)
current (get history (get @state :step-number))
squares (get current :squares)
winner (calculate-winner squares)
moves (map-indexed (fn [move _]
(let [desc (if (= move 0)
(str "Go to game start")
(str "Go to move #" move))]
[:li {:key move} [:button {:on-click #(jump-to move)} desc]]))
history)
status (if (= winner nil)
(str "Next player: " (if (get @state :x-is-next?) "X" "O"))
(str "Winner: " winner))]
[:div.game
[:div.game-board
[board :squares squares
:on-click handle-click]
]
[:div.game-info
[:div status]
[:ol
moves
]]])))))
#2. ClojureScriptコーディングあれこれ
##2.1. take, inc
(vec (take (inc (get @state :step-number)) (get @state :history))
Vanilla JSではslice
で配列の部分取得ができますが、cljsではtake
で先頭の要素を個数分取得できます。また、inc
で1インクリメントできます。
##2.2. map-indexed
moves (map-indexed (fn [move _]
(let [desc (if (= move 0)
(str "Go to game start")
(str "Go to move #" move))]
[:li {:key move} [:button {:on-click #(jump-to move)} desc]]))
history)
cljsでmap
のindexを取得するためには、map-indexed
を使用します。第1引数に設定した即時関数の第1引数がindex, 第2引数がitemです。この処理でitemは使用しないため、_
で無効化しています。
##2.3. count
(count history)
count
でcollectionの要素数を取得しています。
#3. まとめ
なんだかReactというよりはVanilla JSとcljsの仕様差異を吸収するほうが大変でした。
書いてみて以下のことを感じました。
- 括弧だらけに見えたけど書く分にはあまり気にならない。
- 配列操作の豊富な関数群がスマートな書き方を実現できる。ある程度どういうラインナップがあるか事前に勉強要。
- 名前空間、
let
スコープなどにより、汚染の少ない書き方が可能。 - HicCup記法はタグの閉じ忘れが起きにくいのでJSX記法より書きやすいかもしれない。
- 書き方がある程度制約されている事によって、誰が書いても同じになる→誰のコードでも読みやすいような仕組みになっている。
- Collectionの種類を理解せず雰囲気でやってしまっているのでしっかりと把握要。
次は
- Clojure自体の基礎知識を本で補強する。
- Reduxに相当するre-frameを使ってみる。
あたりをやってみましょうかね。