Проверка на победу в крестики-нолики - PullRequest
4 голосов
/ 30 октября 2010

Хорошо, так что я заканчиваю свой последний проект, (по общему признанию, не очень хороший) реализацию Tic Tac Toe в Common Lisp (вся программа доступна здесь ), но я застрял в одной последней части; Я не могу понять, как заставить мою функцию проверять работоспособность победителя. Функция (и подчиненная ей функция) выглядят так:

(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 ]

6 голосов
/ 30 октября 2010

В операторе defun есть неявное выражение progn, поэтому оно оценивается следующим образом:

  1. инструкции оцениваются один за другим;
  2. значение последнего операторавозвращается как результат функции.

В вашем check-for-win есть 2 оператора: cond и nil.В соответствии с 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)))

:else - это просто ключевое слово, и, как и любое другое ключевое слово, оно оценивается как true.Вы можете использовать любое другое ключевое слово или просто true.Таким образом, если ни один оператор до этого не дал true, результат cond (и всей функции) будет nil.

4 голосов
/ 31 октября 2010

В CHECK-FOR-WIN:

КОНД - плохой выбор для того, для чего он предназначен. Считать об этом: вы хотите, чтобы функция возвращала T, если какая-либо из линий IS-LINE вернуть T и NIL в противном случае. Ну, это в значительной степени определение что ИЛИ делает, поэтому выгрузите COND и соберите вызовы IS-LINE в один ИЛИ. Вы можете использовать НЕКОТОРЫЕ, чтобы сократить его еще больше, но это может оказаться слишком «умным».

В IS-LINE

Давайте возьмем это наизнанку: во-первых, EQL является транзитивным, поэтому, если вы знаете, (EQL A B) и (EQL A C), то для тестирования это избыточно (EQL B C).

Теперь, когда IF, абсолютно непростительно. Это буквально то же самое, что и

if (x)
  return true;
else
  return false;

на языке фигурных скобок. У вас уже есть значение истины, которое вы хотите вернись, просто верни это.

Наконец, это плохой стиль для теневых переменных, как вы делаете с LET. Во всяком случае, я бы сказал, что бросая один EQL, вы в любом случае уменьшите необходимость предварительной обработки ссылок на массив почти до нуля.

В общем

Соглашение в Common Lisp для именования предикатов (функций, которые вернуть либо T, либо NIL) это придумать существительную фразу, которая описывает то, что они проверяют, и нажмите «р». Я так думаю 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 голосов
/ 31 октября 2010

Это также исправляет несколько других проблемных областей в тандеме с исправлением Андрея.

Сначала настройте логический поток в функции 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 всегда сообщал о неверном выборе, поскольку он был не больше самого себя.

1 голос
/ 31 октября 2010

Воспользуйтесь преимуществами функций первого класса и устраните повторение кода (это также побочный эффект решения исходной проблемы:)

(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 ing board-ref, этот общий случай на самом деле довольно прост,

(defun (setf board-ref) (val cell)
  (setf (aref *board* (1- cell)) val))
...