Common Lisp中的Alpha-beta修剪

时间:2018-04-17 18:51:13

标签: artificial-intelligence common-lisp minimax alpha-beta-pruning

我尝试使用维基百科中的伪代码编码Alpha-beta。程序到达(EQ depth 0)后,它返回启发式值,但深度继续解除干扰,从而导致循环。现在我的代码看起来像这样:

(defun ab(tab node depth a b)
(cond ((EQ depth 0) (calculaH tab))
        ((eq (mod depth 2) 0) (setq v -999999) (setq movimiento (sigMov depth node tab))  (loop while (not(null movimiento))  
                                                        do (setq v (max v (ab (nth 3 movimiento) movimiento (- depth 1) a b)))
                                                           (setq a (max a v))
                                                           (cond((<= b a) (break))
                                                                (t (setq movimiento (sigMov depth movimiento tab))))) (return v))

        (t (setq v 999999) (setq movimiento (sigMov depth node tab)) (loop while (not(null movimiento))   
                                                        do (setq v (min v (ab (nth 3 movimiento) movimiento (- depth 1) a b)))
                                                           (setq a (min b v))
                                                           (cond((<= b a) (break))
                                                                (t (setq movimiento (sigMov depth movimiento tab))))) (return v))))

我应该在代码中增加深度值吗?为什么递归本身不会增加值?

1 个答案:

答案 0 :(得分:3)

维基百科上的alpha-beta prunning algorithm几乎可以按原样翻译成Lisp。由于它使用无限值​​,所以我们不要使用“999999”,而是定义minmax函数,这些函数可以使用这些特殊值可靠地运行:

(defpackage :alphabeta
  (:use :cl)
  ;; custom min/max functions that support infinity
  (:shadow min max))

(in-package :alphabeta)

(defvar -∞ '-∞ "Negative infinity symbol")
(defvar +∞ '+∞ "Positive infinity symbol")

(defun min (a b)
  (cond
    ((eql a +∞) b)
    ((eql b +∞) a)
    ((eql a -∞) -∞)
    ((eql b -∞) -∞)
    (t (cl:min a b))))

(defun max (a b)
  (cond
    ((eql a -∞) b)
    ((eql b -∞) a)
    ((eql a +∞) +∞)
    ((eql b +∞) +∞)
    (t (cl:max a b))))

代码还依赖于辅助功能,我在此声明这些功能以避免警告:

 ;; You need to implement the followning functions
(declaim (ftype function terminal-node-p heuristic-value children))

然后,伪代码几乎可以相同地写入。为了这个问题,我保留了相同的希腊变量,但正如Dan Robertson在评论中指出的那样,这可能会带来惊喜:

  

使用像α或β这样的名称时要注意的一件事是,典型的支持Unicode的lisp实现会将它们改为Α和Β。你能说出A和Α或B和Β之间的区别吗?

(defun alphabeta (node depth α β maximizing-player-p)
  (when (or (= depth 0) (terminal-node-p node))
    (return-from alphabeta (heuristic-value node)))
  (if maximizing-player-p
      (let ((value -∞))
        (dolist (child (children node))
          (setf value (max value (alphabeta child (1- depth) α β nil)))
          (setf α (max α value))
          (when (<= β α)
            ;; β cut-off
            (return)))
        value)
      (let ((value +∞))
        (dolist (child (children node))
          (setf value (min value (alphabeta child (1- depth) α β t)))
          (setf α (min α value))
          (when (<= β α)
            ;; α cut-off
            (return)))
        value)))
  • 绝对不要将数字与EQ 进行比较。如果您希望仅比较数字,请使用=

  • 始终使用let 引入局部变量,从不setq引入当前作用域中未定义的变量。您的代码失败,因为您的Lisp实现在您第一次在未绑定的符号上调用setq时定义全局变量。之后,您会在递归代码中改变全局变量,这会使其失效。

  • 不要有超长行(大多数语言都是这样),正确缩进,将每个新表单放在同一个缩进处开始。

  • Lisp 中的
  • BREAK进入调试器 。如果您想提前退出循环,请使用RETURN(这是有效的,因为像DO这样的迭代构造会引入名为nil的隐式BLOCK