好的,所以我已经完成了我的最新项目,在Common Lisp中完成了Tic Tac Toe(一般来说不是很好)(整个程序可用here),但是我坚持了一个最后部分;我无法弄清楚如何获得检查胜利者工作的功能。函数(及其从属函数)如下所示:
(defun check-for-win ()
(cond ((is-line 1 2 3) t)
((is-line 1 4 7) t)
((is-line 1 5 9) t)
((is-line 2 5 8) t)
((is-line 3 6 9) t)
((is-line 3 5 7) t)
((is-line 4 5 6) t)
((is-line 7 8 9) t))
nil)
(defun is-line (a b c)
(let ((a (aref *board* (- a 1)))
(b (aref *board* (- b 1)))
(c (aref *board* (- c 1))))
(if (and
(eql a b)
(eql a c)
(eql b c))
t
nil)))
(尽管没有缩进这么深),并且在(is-line)
中,a,b和c将(在获胜场景中)设置为符号(:X
或:O
) 。如何使等式检查有效?
答案 0 :(得分:6)
progn
语句中存在隐式defun
,因此以这种方式进行评估:
在check-for-win
您有2条陈述:cond
和nil
。根据{{1}}评估规则,对于任何调用都将返回progn
的值,而nil
的结果将被忽略。
试试这段代码:
cond
(defun check-for-win ()
(cond ((is-line 1 2 3) t)
((is-line 1 4 7) t)
((is-line 1 5 9) t)
((is-line 2 5 8) t)
((is-line 3 6 9) t)
((is-line 3 5 7) t)
((is-line 4 5 6) t)
((is-line 7 8 9) t)
(:else nil)))
只是一个关键字,任何关键字都会被评估为true。您可以使用任何其他关键字,也可以只使用:else
。因此,如果之前没有任何陈述为真,则true
(以及所有函数)的结果将为cond
。
答案 1 :(得分:4)
在CHECK-FOR-WIN中:
COND对于它的目标是一个糟糕的选择。认为 关于它:如果有任何IS-LINE,你希望函数返回T. 返回T,否则返回NIL。那就是定义 或者做什么,所以转储COND并收集IS-LINE电话 一个OR。您可以使用SOME来进一步缩短它,但那样做 可能最终过于“聪明。”
在IS-LINE
让我们从内到外:首先,EQL是可传递的,所以如果你知道的话 (EQL A B)和(EQL A C),那么测试是多余的(EQL B C)。
现在IF,绝对是不可原谅的。从字面上看,它与做
相同if (x)
return true;
else
return false;
用括号语言表达。你已经拥有了自己想要的真值 返回,所以只需返回。
最后,阴影变量就像你正在做的那样糟糕 LET。无论如何,我会争辩说你扔掉一个EQL 无论如何,减少预计算数组refs的必要性几乎为零。
一般
Common Lisp中用于命名谓词的约定(函数 返回T或NIL)是想出一个名词短语 描述了他们正在测试的内容,以及“p”。所以我认为 WINNING-POSITION-P和CELLS-MATCH-P会是更好的名字。
我认为编写一个函数来获取内容可能是个好主意 因为后者暴露,所以与使用AREF相反的板块广场 其实施细节。即使这是一个相对较小的问题 在这种情况下,这是一个很好的习惯。
遵循这些建议会产生以下代码:
(defun winning-position-p () (or (cells-match-p 1 2 3) (cells-match-p 1 4 7) (cells-match-p 1 5 9) (cells-match-p 2 5 8) (cells-match-p 3 6 9) (cells-match-p 3 5 7) (cells-match-p 4 5 6) (cells-match-p 7 8 9))) (defun cells-match-p (a b c) (and (eql (board-ref a) (board-ref b)) (eql (board-ref a) (board-ref c))) (defun board-ref (cell) ;; Could check for errors here. (aref *board* (1- cell)))
答案 2 :(得分:2)
这也解决了一些其他问题领域与Andrei的修复。
首先,调整play()功能中的逻辑流程。
;;; Play the game
(defun play (&optional switch-p)
(when switch-p (switch-player))
(check-choice (read-choice))
;;; Check if we should continue playing.
(when (and
(not (check-for-win))
(not (stalemate)))
(play t))
;;; Check for win FIRST (last move in possible stalemate may be a win)
(when (check-for-win)
(format t "~a has won! " *player*)
(if (y-or-n-p "Play again? ")
(play-again)
(quit)))
;;; Check for stalemate.
(when (stalemate)
(if (y-or-n-p "~%~%Stalemate! Play again? ")
(play-again)
(quit))))
其次,调整check-choice()函数......
;;; Changed (> choice 1) to (> choice 0) otherwise square 1 is always invalid.
(defun check-choice (choice)
(if (and
(numberp choice)
(> choice 0)
(< choice 10))
(select choice)
(progn
(format t "~%Invalid choice.~%")
(check-choice (read-choice)))))
第一部分的问题是,如果最后一次移动是唯一的移动并且获胜的话,那么该计划将会在胜利前报告僵局。
第二部分的问题是,方1总是报告无效选择,因为它不比自身大。
答案 3 :(得分:1)
利用一流功能的强大功能,消除代码重复(这也有解决原始问题的副作用):
(defun check-for-win ()
(some (lambda (x) (apply #'is-line x)
'((1 2 3) (1 4 7) (1 5 9) (2 5 8) (3 6 9) (3 5 7) (4 5 6) (7 8 9))))
关于setf
board-ref
,这种常见情况实际上相当简单,
(defun (setf board-ref) (val cell)
(setf (aref *board* (1- cell)) val))