Arantium Maestum

プログラミング、囲碁、読書の話題

Reactで書かれた囲碁ボードをReagentで作ってみる(Views編)

前回からの続き。

Reagentコンポーネントを定義するviews.cljsの説明。

まずは点の大きさを表すGRID_SIZEを定義し、盤上の「点」をコンポーネント化する。

(def GRID_SIZE 40)

(defn BoardIntersection [[row col] color click-fn]
  (let [style {:top (* row GRID_SIZE)
               :left (* col GRID_SIZE)}
        classes (str "intersection" 
                     (cond (= color :black) " black"
                           (= color :white) " white"
                           :else            ""))]
    [:div {:className classes 
           :style style 
           :onClick #(click-fn [row col])}]))

クリックされた時の挙動は完全に外から渡されたclick-fnに準拠する。ゲームのロジックは全く組み込まれていない。のでこのコンポーネントを全く変えずに使ってオセロも作れる。

一つ気がかりなのは、盤上の端を特に考慮していないこと。できれば端は見た目を変えて表現したい。一から作る時にはこれもやってみよう。

BoardIntersectionコンポーネントを合わせてBoardViewコンポーネントを作る:

(defn BoardView [game-state click-fn]
  (let [size (:size game-state)
        style {:width (* size GRID_SIZE)
               :height (* size GRID_SIZE)}
        main-div [:div {:id "board"
                        :style style}]
        coords (for [x (range size) y (range size)] [x y])
        get-color (fn [coord] (get-in game-state [:board coord]))
        intersections (for [coord coords]
                            ^{:key coord} [BoardIntersection 
                              coord (get-color coord) click-fn])]
    (into main-div intersections)))

game-stateから盤の大きさと、各点の状態を取得して、その情報を元にforループで全ての点のIntersectionコンポーネントを作成し、親Divのベクトルにintoで挿入している。ここら辺のコンポジションはReact/Reagentの面目躍如と言ったところか。

get-color関数をここでも定義してしまっているのは残念。game-stateの実装の知識がここで漏れ出てしまっている。修正するときはgame-state関連の関数として定義したものをインポートして使うべきか。

パス用のボタン:

(defn PassView [pass-fn]
  [:input {:id "pass-btn"
           :type "button"
           :value "Pass"
           :onClick pass-fn}])

クリックされた時の挙動は完全に外部からのpass-fn次第。

特殊な状態を表示するAlertView:

(defn AlertView [game-state]
  (let [text (cond (:game-over game-state)
                   "GAME OVER"
                   (:in-atari game-state)
                   "ATARI"
                   (:attempted-suicide game-state)
                   "SUICIDE!"
                   :else
                   "")]
    [:div {:id "alerts"} text]))

React版をベースにしているのだけど、インターフェイスとしては少しいじりたくなる気がする。例えばアタリと自殺手が重複してたりすると自殺手のワーニングがでないのを、ゲームロジックの方で重複しないようにしている点。ここも要変更。

上記のコンポーネントを組み合わせてGameコンポーネントができる:

(defn Game [game-state click-fn pass-fn]
  [:div
   [AlertView game-state]
   [PassView pass-fn]
   [BoardView game-state click-fn]])

ゲーム状態やクリック時の挙動は外部から渡されるだけ。というわけでviews.cljsで定義されるコンポーネントはあくまで渡されたデータを元に見た目を構築するコードのみを記述したものになる。

views.cljsとlogic.cljsをcore.cljsで組み合わせて出来上がり。

core.cljsについては次回。