August 4, 2018

Tic-tac on my toe

Because Clojurians don't let Clojurians use Jekyll.

Live self-hosted Clojurescript

Cryogen makes KLIPSE integration a breeze:

(require '[reagent.core :as r])

And now we can use Reagent!

Here's Timothy Pratley's tic-tac-toe (game rendered below):

(defn new-board [n]
  (vec (repeat n (vec (repeat n "B")))))

(def board-size 3)

(def win-length 3)

(defonce app-state
  (r/atom {:text "Tic tac on my toe"
         :board (new-board board-size)
         :game-status :in-progress}))

(defn computer-move [board]
  (let [remaining-spots (for [i (range board-size)
                              j (range board-size)
                              :when (= (get-in board [j i]) "B")]
                          [j i])
        move (when (seq remaining-spots)
               (rand-nth remaining-spots))]
    (if move
      (assoc-in board move "C")
      board)))

(defn straight [owner board [x y] [dx dy] n]
  (every? true?
         (for [i (range n)]
           (= (get-in board [(+ (* dx i) x)
                             (+ (* dy i) y)])
              owner))))

(defn win? [owner board n]
  (some true?
        (for [i (range board-size)
              j (range board-size)
              dir [[1 0] [0 1] [1 1] [1 -1]]]
          (straight owner board [i j] dir n))))

(defn full? [board]
  (every? #{"P" "C"} (apply concat board)))

(defn game-status [board]
  (cond
    (win? "P" board win-length) :player-victory
    (win? "C" board win-length) :computer-victory
    (full? board) :draw
    :else :in-progress))

(defn update-status [state]
  (assoc state :game-status (game-status (:board state))))

(defn check-game-status [state]
  (-> state
      (update-in [:board] computer-move)
      (update-status)))

(defn blank [i j]
  [:rect
   {:width 0.9
    :height 0.9
    :fill "grey"
    :x (+ 0.05 i)
    :y (+ 0.05 j)
    :on-click
    (fn blank-click [e]
      (when (= (:game-status @app-state) :in-progress)
        (swap! app-state assoc-in [:board j i] "P")
        (if (win? "P" (:board @app-state) win-length)
          (swap! app-state assoc :game-status :player-victory)
          (swap! app-state check-game-status))))}])

(defn circle [i j]
  [:circle
   {:r 0.35
    :stroke "green"
    :stroke-width 0.12
    :fill "none"
    :cx (+ 0.5 i)
    :cy (+ 0.5 j)}])

(defn cross [i j]
  [:g {:stroke "darkred"
       :stroke-width 0.4
       :stroke-linecap "round"
       :transform
       (str "translate(" (+ 0.5 i) "," (+ 0.5 j) ") "
            "scale(0.3)")}
   [:line {:x1 -1 :y1 -1 :x2 1 :y2 1}]
   [:line {:x1 1 :y1 -1 :x2 -1 :y2 1}]])

(defn tictactoe []
  [:center
   [:h1 (:text @app-state)]
   [:h2
    (case (:game-status @app-state)
      :player-victory "You won! "
      :computer-victory "Computer wins "
      :draw "The cat got it... "
      "")
    [:button
     {:on-click
      (fn new-game-click [e]
        (swap! app-state assoc
               :board (new-board board-size)
               :game-status :in-progress))}
     "New Game"]]
   (into
    [:svg
     {:view-box (str "0 0 " board-size " " board-size)
      :width 500
      :height 500}]
    (for [i (range board-size)
          j (range board-size)]
      (case (get-in @app-state [:board j i])
        "B" [blank i j]
        "P" [circle i j]
        "C" [cross i j])))])

[tictactoe]
Tags: Reagent KLIPSE games Cryogen Clojure Clojurescript