检查Tic-Tac-Toe的胜利

时间:2010-10-30 19:10:43

标签: lisp common-lisp

好的,所以我已经完成了我的最新项目,在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) 。如何使等式检查有效?

4 个答案:

答案 0 :(得分:6)

progn语句中存在隐式defun,因此以这种方式进行评估:

  1. 语句一个接一个地进行评估;
  2. 最后一个语句的值作为函数结果返回。
  3. check-for-win您有2条陈述:condnil。根据{{​​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))
相关问题