<commit_before>(ns xom.server
  (:require
    [taoensso.sente :as sente]
    [taoensso.sente.server-adapters.aleph :refer (get-sch-adapter)]
    [compojure.core :refer [defroutes GET POST]]
    [ring.middleware.resource]
    [ring.middleware.keyword-params]
    [ring.middleware.params]
    [datomic.api :as d]
    [om.next.server :as om]
    )
  )

(defn log [& m] (spit "server.log" (apply str m "\n") :append true))

(def schema [{:db/ident :xom/game-id
              :db/valueType :db.type/uuid
              :db/cardinality :db.cardinality/one
              :db/unique :db.unique/identity}
             {:db/ident :xom/winner
              :db/valueType :db.type/keyword
              :db/cardinality :db.cardinality/one}
             {:db/ident :xom/positions
              :db/valueType :db.type/ref
              :db/isComponent true
              :db/cardinality :db.cardinality/many} ; coll-of :pos/{row,col,mark}
             {:db/ident :pos/row
              :db/valueType :db.type/long
              :db/cardinality :db.cardinality/one}
             {:db/ident :pos/col
              :db/valueType :db.type/long
              :db/cardinality :db.cardinality/one}
             {:db/ident :pos/mark
              :db/valueType :db.type/keyword
              :db/cardinality :db.cardinality/one} ; :x or :o
             ])

(defn create-game
  "Create a new game"
  []
  {:xom/game-id (java.util.UUID/randomUUID)
   :xom/winner :none
   :xom/positions (for [col (range 0 3)
                        row (range 0 3)]
                    {:pos/col col :pos/row row})})

(defn player-turn
  "Return whose turn it is (x goes first)."
  [game]
  (let [turns (count (keep :pos/mark (:xom/positions game)))]
    (if (even? turns)
      :x
      :o)))

(defn board
  "Return a 2d vector representing the board"
  [game]
  (reduce (fn [b {:keys [pos/row pos/col pos/mark]}]
            (assoc-in b [row col] mark)) [[nil nil nil] [nil nil nil] [nil nil nil]] (:xom/positions game)))

(defn winner
  "Return the winner for the given board Returns :none, :cat, :x or :o"
  [board]
  (let [check-win (fn [[p1 p2 p3]]
                    (if (= (get-in board p1) (get-in board p2) (get-in board p3))
                      (get-in board p1)))]
    (or
     (some check-win [[[0 0] [0 1] [0 2]] ; top
                      [[1 0] [1 1] [1 2]] ; middle
                      [[2 0] [2 1] [2 2]] ; bottom
                      [[0 0] [1 0] [2 0]] ; left
                      [[0 1] [1 1] [2 1]] ; center
                      [[0 2] [1 2] [2 2]] ; right
                      [[0 0] [1 1] [2 2]] ; diag
                      [[2 0] [1 1] [0 2]] ; diag
                      ])
     (if (= 9 (->> board flatten (remove nil?) count))
       :cat)
     :none)))

(defn valid-move?
  [game {:keys [pos/row pos/col pos/mark]}]
  (log "valid-move? " row col (player-turn game) mark)
  (and game
       (= mark (player-turn game))
       (< row 3)
       (< col 3)
       (empty? (filter #(and
                          (:pos/mark %)
                          (= row (:pos/row %))
                          (= col (:pos/col %)))
                       (:xom/positions game)))))

(defn game
  "return the game entity for the game id or nil if the game does not exist"
  [db id]
  (d/entity db [:xom/game-id id]))

(defn move
  "Return a transaction to apply this move or nil if the move is invalid"
  [db new-pos]
  (let [m (d/entity db (:db/id new-pos))
        g (:xom/_positions m) ]
    (log "move " m g new-pos)
    (if (and m g (valid-move? g new-pos))
      (let [new-winner (-> (d/with db [new-pos])
                           :db-after
                           (d/entity (:db/id g))
                           board
                           winner)]
        (log "new-winner " new-winner)
        [{:db/id (:db/id g) :xom/winner new-winner}
         new-pos])
      (log "illegal move " new-pos))))

(comment
 (even? 0)

 (player-turn (create-game "x" "o"))

 (valid-move? (create-game "x" "o") {:pos/row 0 :pos/col 0 :pos/mark :o})
 (valid-move? (create-game "x" "o") {:pos/row 0 :pos/col 0 :pos/mark :x})
 (valid-move? (create-game "x" "o") {:pos/row 4 :pos/col 3 :pos/mark :x})

 (winner [[:x :x :o] [:o :x :x] [:x :o :o]])
 (winner [[:x :x :x]  [:x :x :o]  [:o :o :o]] )

 (winner (board {:xom/positions [{:pos/row 0 :pos/col 0 :pos/mark :x}]}))
(->> (board {:xom/positions [{:pos/row 0 :pos/col 0 :pos/mark :x}]}) flatten (remove nil?) count)
 )

(defmulti om-read om/dispatch)
(defmulti om-mutate om/dispatch)

(defmethod om-read :default
  [e k p]
  (log "default read: " k (keys e) p))

(defmethod om-read :xom/my-uid
  [{:keys [uid] :as e} k p]
  {:value uid}
  )

(defmethod om-read :xom/game
  [{:keys [conn query]} k p]
  (let [game (d/q '[:find (pull ?e query) .
                    :in $ query
                    :where
                    [?e :xom/game-id]
                    [?e :xom/winner :none]] (d/db conn) query)]
    (log "got game" game)
    {:value game}))

(defmethod om-mutate 'xom/new-game
  [{:keys [conn uid send-fn connected-uids]} k p]
  (log "xom/new-game " p uid)
  {:action (fn []
             @(d/transact conn [(create-game)])
             nil)})

(defmethod om-mutate 'xom/mark
  [{:keys [conn uid send-fn connected-uids]} k p]
  (log "xom/mark " p uid)
  {:action
   (fn []
     (try
       (if-let [tx (move (d/db conn) (assoc p :pos/mark uid))]
         (let [{:keys [db-after]} @(d/transact conn tx)]
           (doseq [uid (:ws @connected-uids)]
             (send-fn uid
                      [:xom/data
                       (into {} (map (fn [m] [[:db/id (:db/id m)] m])) tx)]))))
       (catch Exception e (log e))))})

(defmethod om-mutate :default
  [e k p]
  (log "default mutate: " k (keys e) p))

(def om-parser (om/parser {:read om-read :mutate om-mutate}))
(defn conn [] (d/connect "datomic:mem://xom"))

(comment (d/q '[:find ?e :where [?e :db/type ]] (d/db (conn))))

(defmulti event-msg-handler :id)

(declare uid-fn)

(let [{:keys [ch-recv send-fn connected-uids
              ajax-post-fn ajax-get-or-ws-handshake-fn]}
       (sente/make-channel-socket!
         (get-sch-adapter)
         {:user-id-fn uid-fn})]

  (def ring-ajax-post                ajax-post-fn)
  (def ring-ajax-get-or-ws-handshake ajax-get-or-ws-handshake-fn)
  (def ch-chsk                       ch-recv) ; ChannelSocket's receive channel
  (def connected-uids                connected-uids)
)

(defn uid-fn [_]<commit_msg>Use var ref to allow uid-fn to be declared after sente websocket.<commit_after>(ns xom.server
  (:require
    [taoensso.sente :as sente]
    [taoensso.sente.server-adapters.aleph :refer (get-sch-adapter)]
    [compojure.core :refer [defroutes GET POST]]
    [ring.middleware.resource]
    [ring.middleware.keyword-params]
    [ring.middleware.params]
    [datomic.api :as d]
    [om.next.server :as om]
    )
  )

(defn log [& m] (spit "server.log" (apply str m "\n") :append true))

(def schema [{:db/ident :xom/game-id
              :db/valueType :db.type/uuid
              :db/cardinality :db.cardinality/one
              :db/unique :db.unique/identity}
             {:db/ident :xom/winner
              :db/valueType :db.type/keyword
              :db/cardinality :db.cardinality/one}
             {:db/ident :xom/positions
              :db/valueType :db.type/ref
              :db/isComponent true
              :db/cardinality :db.cardinality/many} ; coll-of :pos/{row,col,mark}
             {:db/ident :pos/row
              :db/valueType :db.type/long
              :db/cardinality :db.cardinality/one}
             {:db/ident :pos/col
              :db/valueType :db.type/long
              :db/cardinality :db.cardinality/one}
             {:db/ident :pos/mark
              :db/valueType :db.type/keyword
              :db/cardinality :db.cardinality/one} ; :x or :o
             ])

(defn create-game
  "Create a new game"
  []
  {:xom/game-id (java.util.UUID/randomUUID)
   :xom/winner :none
   :xom/positions (for [col (range 0 3)
                        row (range 0 3)]
                    {:pos/col col :pos/row row})})

(defn player-turn
  "Return whose turn it is (x goes first)."
  [game]
  (let [turns (count (keep :pos/mark (:xom/positions game)))]
    (if (even? turns)
      :x
      :o)))

(defn board
  "Return a 2d vector representing the board"
  [game]
  (reduce (fn [b {:keys [pos/row pos/col pos/mark]}]
            (assoc-in b [row col] mark)) [[nil nil nil] [nil nil nil] [nil nil nil]] (:xom/positions game)))

(defn winner
  "Return the winner for the given board Returns :none, :cat, :x or :o"
  [board]
  (let [check-win (fn [[p1 p2 p3]]
                    (if (= (get-in board p1) (get-in board p2) (get-in board p3))
                      (get-in board p1)))]
    (or
     (some check-win [[[0 0] [0 1] [0 2]] ; top
                      [[1 0] [1 1] [1 2]] ; middle
                      [[2 0] [2 1] [2 2]] ; bottom
                      [[0 0] [1 0] [2 0]] ; left
                      [[0 1] [1 1] [2 1]] ; center
                      [[0 2] [1 2] [2 2]] ; right
                      [[0 0] [1 1] [2 2]] ; diag
                      [[2 0] [1 1] [0 2]] ; diag
                      ])
     (if (= 9 (->> board flatten (remove nil?) count))
       :cat)
     :none)))

(defn valid-move?
  [game {:keys [pos/row pos/col pos/mark]}]
  (log "valid-move? " row col (player-turn game) mark)
  (and game
       (= mark (player-turn game))
       (< row 3)
       (< col 3)
       (empty? (filter #(and
                          (:pos/mark %)
                          (= row (:pos/row %))
                          (= col (:pos/col %)))
                       (:xom/positions game)))))

(defn game
  "return the game entity for the game id or nil if the game does not exist"
  [db id]
  (d/entity db [:xom/game-id id]))

(defn move
  "Return a transaction to apply this move or nil if the move is invalid"
  [db new-pos]
  (let [m (d/entity db (:db/id new-pos))
        g (:xom/_positions m) ]
    (log "move " m g new-pos)
    (if (and m g (valid-move? g new-pos))
      (let [new-winner (-> (d/with db [new-pos])
                           :db-after
                           (d/entity (:db/id g))
                           board
                           winner)]
        (log "new-winner " new-winner)
        [{:db/id (:db/id g) :xom/winner new-winner}
         new-pos])
      (log "illegal move " new-pos))))

(comment
 (even? 0)

 (player-turn (create-game "x" "o"))

 (valid-move? (create-game "x" "o") {:pos/row 0 :pos/col 0 :pos/mark :o})
 (valid-move? (create-game "x" "o") {:pos/row 0 :pos/col 0 :pos/mark :x})
 (valid-move? (create-game "x" "o") {:pos/row 4 :pos/col 3 :pos/mark :x})

 (winner [[:x :x :o] [:o :x :x] [:x :o :o]])
 (winner [[:x :x :x]  [:x :x :o]  [:o :o :o]] )

 (winner (board {:xom/positions [{:pos/row 0 :pos/col 0 :pos/mark :x}]}))
(->> (board {:xom/positions [{:pos/row 0 :pos/col 0 :pos/mark :x}]}) flatten (remove nil?) count)
 )

(defmulti om-read om/dispatch)
(defmulti om-mutate om/dispatch)

(defmethod om-read :default
  [e k p]
  (log "default read: " k (keys e) p))

(defmethod om-read :xom/my-uid
  [{:keys [uid] :as e} k p]
  {:value uid}
  )

(defmethod om-read :xom/game
  [{:keys [conn query]} k p]
  (let [game (d/q '[:find (pull ?e query) .
                    :in $ query
                    :where
                    [?e :xom/game-id]
                    [?e :xom/winner :none]] (d/db conn) query)]
    (log "got game" game)
    {:value game}))

(defmethod om-mutate 'xom/new-game
  [{:keys [conn uid send-fn connected-uids]} k p]
  (log "xom/new-game " p uid)
  {:action (fn []
             @(d/transact conn [(create-game)])
             nil)})

(defmethod om-mutate 'xom/mark
  [{:keys [conn uid send-fn connected-uids]} k p]
  (log "xom/mark " p uid)
  {:action
   (fn []
     (try
       (if-let [tx (move (d/db conn) (assoc p :pos/mark uid))]
         (let [{:keys [db-after]} @(d/transact conn tx)]
           (doseq [uid (:ws @connected-uids)]
             (send-fn uid
                      [:xom/data
                       (into {} (map (fn [m] [[:db/id (:db/id m)] m])) tx)]))))
       (catch Exception e (log e))))})

(defmethod om-mutate :default
  [e k p]
  (log "default mutate: " k (keys e) p))

(def om-parser (om/parser {:read om-read :mutate om-mutate}))
(defn conn [] (d/connect "datomic:mem://xom"))

(comment (d/q '[:find ?e :where [?e :db/type ]] (d/db (conn))))

(defmulti event-msg-handler :id)

(declare uid-fn)

(let [{:keys [ch-recv send-fn connected-uids
              ajax-post-fn ajax-get-or-ws-handshake-fn]}
       (sente/make-channel-socket!
         (get-sch-adapter)
         {:user-id-fn #'uid-fn})]

  (def ring-ajax-post                ajax-post-fn)
  (def ring-ajax-get-or-ws-handshake ajax-get-or-ws-handshake-fn)
  (def ch-chsk                       ch-recv) ; ChannelSocket's receive channel
  (def connected-uids                connected-uids)
)

(defn uid-fn [_]
{'fraction_non_alphanumeric': 0.17142416293781823, 'fraction_numerical': 0.01141799105076377, 'mean_word_length': 1.7865434221840069, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '307088', 'n_tokens_mistral': 4941, 'n_tokens_neox': 4706, 'n_words': 1337}