Tic-Tac-Toe Victory Test

Ok, so I finished my last project, (though not very good), implementing Tic Tac Toe in Common Lisp (all the available program here ), but I was stuck on one last part; I canโ€™t figure out how to get my function, which checks the work of the winner. A function (and its subordinate function) looks like this:

(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 (abc) (let ((a (aref *board* (- a 1))) (b (aref *board* (- b 1))) (c (aref *board* (- c 1)))) (if (and (eql ab) (eql ac) (eql bc)) t nil))) 

(although they are not so far behind), but in (is-line) , a, b and c will (in the winning scenario) set the symbol (either :X or :O ). How to get equality checks?

+4
source share
4 answers

There is an implicit progn in defun , so it is evaluated like this:

  • Operators
  • evaluated one by one;
  • the value of the last statement is returned as the result of the function.

In your check-for-win you have 2 statements: cond and nil . According to progn evaluation rules, nil will be returned for any call, and cond will simply be ignored.

Try this code:

 (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 is just a keyword, and for any keyword it evaluates to true. You can use any other keyword or just true . So, if none of the above instructions gave true, the result of cond (and the whole function) will be nil .

+6
source

In CHECK-FOR-WIN:

COND is a poor choice for what it should have done. Think about it: you want the function to return T if any of the IS-LINE returns T and NIL otherwise. Well, that pretty much determines what OR is doing, so drop the COND and put the IS-LINE calls into one OR. You can use SOME to cut it even further, but it may be too smart.

In IS-LINE

Let's take it inside out: firstly, EQL is transitive, so if you know (EQL AB) and (EQL AC), then it is redundant for validation (EQL BC).

Now that IF is absolutely unforgivable. This, literally, is the same as doing

 if (x) return true; else return false; 

in the language of figures. You already have a truth value that you want return, so just return it.

Finally, this is a bad style for shadow variables, as you do with LET. In any case, I would say that by dropping one EQL, you will reduce the need to pre-compute the refs array to almost zero anyway.

Generally

A convention in Common Lisp to denote predicates (functions that return either T or NIL) to come up with a noun that describes what they are testing, and stick with "p". I think WINNING-POSITION-P and CELLS-MATCH-P will be the best names.

I think it would be nice to write a function to get the content of the square of the board, as opposed to using AREF, since the latter provides details of its implementation. Even if this is a relatively minor problem, in this case it is a good habit to join.

Following these suggestions, you will receive the following code:

  (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 (abc)
   (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)))
+4
source

It also captures several other problem areas in tandem with Andrei's fix.

First adjust the logical flow in the play () function.

 ;;; 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-np "Play again? ") (play-again) (quit))) ;;; Check for stalemate. (when (stalemate) (if (y-or-np "~%~%Stalemate! Play again? ") (play-again) (quit)))) 

Secondly, adjust the check-choice () function ...

 ;;; 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))))) 

The problem in the first section was that if the last move, which was the only move on the left and the winning move, the program reports a dead end before victory.

The problem in the second section was that square 1 always reported an invalid choice due to the fact that he was no more than himself.

+2
source

Take advantage of first-class features and prevent code duplication (this also affects fixing the original problem :)

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

Regarding setf ing board-ref , this general case is actually quite simple,

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

All Articles