Common Lisp 中的 Alpha-beta 修剪
Alpha-beta pruning in Common Lisp
我尝试使用维基百科中的伪代码对 Alpha-beta 进行编码。在程序达到 (EQ depth 0)
后,它 returns 启发式值但深度继续减少导致循环。现在我的代码如下所示:
(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))))
我应该在我的代码中的某个地方增加深度值吗?为什么递归不自己增加值?
维基百科上的 alpha-beta prunning algorithm 几乎可以原样翻译成 Lisp。因为它使用无限值,所以我们不要使用“999999”,而是定义 min
和 max
可以可靠地处理这些特殊值的函数:
(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 在评论中指出的那样,这可能会导致意外:
One thing to be wary of when using names like α or β is that a typical Unicode-aware lisp implementation will upcase them into Α and Β. Can you tell the difference between A and Α or B and Β?
(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)))
始终使用 let
引入局部变量,永远不要 setq
未在当前范围内定义的变量。您的代码失败是因为您的 Lisp 实现在您第一次对未绑定符号调用 setq
时定义了全局变量。之后,您在递归代码中更改了全局变量,这使其功能失调。
不要有过长的行(大多数语言都是如此),正确缩进,将每个新表格放在自己的行上,从相同的缩进开始.
BREAK
in Lisp enters the debugger. If you want to exit a loop early, use RETURN
(this works because iteration constructs like DO
introduce implicit BLOCK
名为 nil
).
我尝试使用维基百科中的伪代码对 Alpha-beta 进行编码。在程序达到 (EQ depth 0)
后,它 returns 启发式值但深度继续减少导致循环。现在我的代码如下所示:
(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))))
我应该在我的代码中的某个地方增加深度值吗?为什么递归不自己增加值?
维基百科上的 alpha-beta prunning algorithm 几乎可以原样翻译成 Lisp。因为它使用无限值,所以我们不要使用“999999”,而是定义 min
和 max
可以可靠地处理这些特殊值的函数:
(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 在评论中指出的那样,这可能会导致意外:
One thing to be wary of when using names like α or β is that a typical Unicode-aware lisp implementation will upcase them into Α and Β. Can you tell the difference between A and Α or B and Β?
(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)))
始终使用
let
引入局部变量,永远不要setq
未在当前范围内定义的变量。您的代码失败是因为您的 Lisp 实现在您第一次对未绑定符号调用setq
时定义了全局变量。之后,您在递归代码中更改了全局变量,这使其功能失调。不要有过长的行(大多数语言都是如此),正确缩进,将每个新表格放在自己的行上,从相同的缩进开始.
BREAK
in Lisp enters the debugger. If you want to exit a loop early, useRETURN
(this works because iteration constructs likeDO
introduce implicitBLOCK
名为nil
).