2010-10-30 2 views
4

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

(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), а, б и будет (в выигрышной ситуации) устанавливается на символ (или :X или :O). Как я могу получить проверки равенства?

ответ

6

Там в неявной progn в defun заявление, так что оценивается в таком виде:

  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. Итак, если никакое утверждение ранее не дано верно, результатом cond (и всей функции) будет nil.

+0

Это работает, если вы помещаете круглые скобки вокруг ': else nil'. Благодаря! – Andy

+0

Да, извините, исправлено. – ffriend

2

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

Сначала отрегулируйте логический поток в функции 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)))) 

Второй настроить чек-выбор функции,() ...

;;; 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 всегда сообщал о недопустимом выборе из-за того, что он был не больше, чем он сам.

4

В проверки на наличие-WIN:

COND является плохим выбором для того, что он имел в виду сделать. Подумайте об этом : вы хотите, чтобы функция возвращала T, если какая-либо из IS-LINE возвращает T и NIL в противном случае. Ну, это в значительной степени определение того, что делает OR, поэтому сбрасываем COND и собираем вызовы IS-LINE в одном OR. Вы можете использовать SOME, чтобы сократить его еще больше, но что может оказаться слишком «умным»."

В IS-ЛАЙН

Давайте эту изнанку: первый, EQL транзитивно, так что если вы знаете (EQL AB) и (EQL AC), то это избыточно тест (EQL до н.э.).

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

if (x) 
    return true; 
else 
    return false; 

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

И, наконец, это плохой стиль для переменных тени, как вы делаете с 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))) 
+0

Большое вам спасибо за конструктивную критику. Я действительно новичок в Lisp, и я все еще пытаюсь склонить голову, думая в стиле Lisp-y, поэтому я очень признателен, что вы указываете мне эти вещи. :) – Andy

+0

@Cirno как я могу сделать '(setf (выбор платы-ref) * marker *)' работа, если вы не возражаете? – Andy

+0

@Andrew: вам нужно 'defsetf' ot [нечто подобное] (http://www.gnu.org/software/emacs/manual/html_node/cl/Customizing-Setf.html). Но это не тривиальная задача - вам нужно перейти на нижний уровень абстракции - к понятию «место». Другим подходом является использование классов и объектов, имеющих аксессоры. – ffriend

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 ING board-ref, это общее дело на самом деле довольно просто,

(defun (setf board-ref) (val cell) 
    (setf (aref *board* (1- cell)) val)) 
Смежные вопросы