但请记住,人工智能尚未完成,不久的将来还会添加其他启发式方法。目前,它只关心连接的硬币(而不是例如这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-let
与and
结合使用,而不是显式返回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