我在Clojure中编写了一个包含AI的Connect四游戏,并且由于我不是Clojure的新人,所以对它的一些评论将非常感激。它可以包括所有内容,编码样式,简化等。
但请记住,人工智能尚未完成,不久的将来还会添加其他启发式方法。目前,它只关心连接的硬币(而不是例如这3个连接的硬币是否实际可以产生4个)。

整个源代码可以在GitHub上找到。

我我专门询问AI,但如果您对board.clj,check_board.clj或core.clj有任何建议,我也很高兴听到他们的建议。

ai_minimax.clj:

 (ns clj-connect-four.ai-minimax
  (:require [clj-connect-four.board       :as board]
            [clj-connect-four.check-board :as check]))

;;; HEURISTIC FUNCTIONS ;;;

;; consider connected coins
; define score points for 2, 3 and 4 connected coins
(def CC4  1048576)
(def CC3  32)
(def CC2  4)

(defn bit-count
  "Checks how many bits are set on given bitboard."
  [board]
  (map #(bit-test board %) board/board-bits))

(defn get-score
  "Determines score of a bitboard manipulated by check-board-*."
  [check points]
  (* points
     (apply + (map #(count (filter true? (bit-count %))) check))))

(defn heuristic
  "Calculates the main heuristic value of given bitboards for a player."
  [boards player-num]
  (apply +
    (map (fn [[c p]] (get-score c p))
         [[(check/check-board-4 (boards player-num)) CC4]
          [(check/check-board-3 (boards player-num)) CC3]
          [(check/check-board-2 (boards player-num)) CC2]
          [(check/check-board-4 (boards (- 3 player-num))) (- 1 CC4)]
          [(check/check-board-3 (boards (- 3 player-num))) (- 1 CC3)]
          [(check/check-board-2 (boards (- 3 player-num))) (- 1 CC2)]])))

;; consider possible winning combinations
;; (only when first heuristic returns equal values)
(defn get-diags
  "Generates diagonals of given starting position using given step-f."
  [step-fn start-pos]
  (for [pos start-pos]
    (take 4 (iterate step-fn pos))))

(def win-combos
  "All 69 possible winning combinations."
  (let [rows (for [y (range 6), j (range 4)]
               (for [i (range 4)]
                 [y (+ i j)]))
        columns (for [x (range 7), j (range 3)]
                  (for [i (range 4)]
                    [(+ i j) x]))
        diagonals (concat
                   ; descending diagonals \
                   (get-diags (partial mapv inc)
                              (for [y (range 3), x (range 4)]
                                [y x]))
                   ; ascending diagonals /
                   (get-diags (fn [[y x]] [(inc y) (dec x)])
                              (for [y (range 3), x (range 3 7)]
                                [y x])))]
    (concat rows columns diagonals)))

(defn filter-current-move [y x coll]
  "Filter win-combos for coords including given [y x]."
  (if (nil? y)
    (some #{[0 x]} coll)
    (some #{[(inc y) x]} coll)))

(defn filter-open-combos [player-num boards coll]
  "Filter for combos which are still open."
  (some (fn [[y x]] (or (not (bit-test (boards 0) (+ y (* 7 x))))
                        (bit-test (boards player-num) (+ y (* 7 x)))))
        coll))

(defn heuristic2
  "Calculate second heuristic value."
  [boards player-num x]
  (count (filter
          #(and
            (filter-current-move (board/get-y (boards 0) x) x %)
            (filter-open-combos player-num boards %))
          win-combos)))

;;; MINIMAX ALGORITHM ;;;

(defn not-nil? [x]
  (not (nil? x)))

(defn get-max [coll]
  (if (empty? coll) 0
    (apply max coll)))

(defn minimax
  "Minimax algorithm using only main heuristic."
  [boards player-num x depth]
  (if (or (nil? boards)
          (nil? (board/insert boards x player-num))) nil
    (if (= depth 0)
      (heuristic (board/insert boards x player-num) player-num)
      (- (heuristic (board/insert boards x player-num) player-num)
         (get-max (filter not-nil?
                          (map #(minimax
                                 (board/insert boards x player-num)
                                 (- 3 player-num)
                                 %
                                 (- depth 1))
                               [0 1 2 3 4 5 6])))))))

(defn get-highest-index [coll]
  (apply max-key second
         (filter #(not-nil? (second %)) coll)))

(defn make-move
  "Generate next move using minimax
  and second heuristic if needed."
  [boards player-num depth]
  (let [heuristics (map #(minimax boards player-num % depth)
                        [0 1 2 3 4 5 6])
        highest (get-highest-index (map-indexed vector heuristics))]
    (println heuristics)
    (if (> (count (filter #{(second highest)} heuristics)) 1)
      ; equal values from first heuristics - look at second
      (first (get-highest-index
              (map #(vector (first %) (heuristic2 boards player-num (first %)))
                   ; only consider the highest and equal values
                   (filter #(= (second highest) (second %))
                           (map-indexed vector heuristics)))))
      (first highest))))
 


用于检查电路板的算法是对该算法的略微修改,在“算法”段落中进行了说明2:“位板”。

#1 楼

这已经两年半了,但是为了将来的观众:

总体而言,您的编码风格看起来不错,尤其是对于Clojure的新手。我对AI引擎本身没有任何评论,但是这里有一些结构性注释:

not-nil?已经以clojure.core的形式存在于some?中。 :例如“返回..的结果”或“基于...生成y”

在某些地方可以使用->->>宏代替深层嵌套的形式。您执行此操作的程度很大程度上取决于个人喜好。我喜欢将函数作为管道读取,因此,例如,get-score可以写为:

 (defn get-score
  [check points]
  (let [count-true-bits #(count (filter true? (bit-count %)))]
    (->> check
         (map count-true-bits)
         (apply +)
         (* points))))
 


minimax可以从let中受益,避免重复四次(board/insert boards x player-num),并且可以将when-letand结合使用,而不是显式返回nil

我将重命名函数(defn minimax "Minimax algorithm using only main heuristic." [boards player-num x depth] (when-let [boards' (and boards (board/insert boards x player-num))] (let [heuristic-val (heuristic boards' player-num)] (if (zero? depth) heuristic-val (- heuristic-val (->> (range 7) (map #(minimax boards' (- 3 player-num) % (dec depth))) (filter some?) get-max)))))) filter-current-move,因为它们似乎更像谓词而不是过滤器-filter-open-combos将返回some的第一个逻辑真值,否则返回(pred item-in-coll)。同样,它们的文档字符串应在其参数列表之前。幻想:

 nil