I am trying to learn some Common Lisp, except basically all of my computing background is the C family of languages. So, I'm starting small. I have made a Tic-Tac-Toe game, and I am looking for some constructive criticism.
In particular, is this idiomatic Lisp? Is it styled normally? What would an experienced lisper improve about this?
Some concerns I have about this code are:
- The win checking seems repetitive. Can it be simplified?
- The get-player-move is very long. Should it be broken up?
;;;; A simple tic-tac-toe game
(defvar p1 88) ; char code for 'X'
(defvar p2 79) ; char code for 'O'
(defvar tie 49) ; arbitrary constant to represent ties
(defun is-move-free (move board)
(let ((player (elt board move)))
(not (or (= player p1) (= player p2)))))
(defun is-same (a b c)
(and (= a b) (= b c)))
(defun win-on-rows (board)
(defun check-a-row (offset)
(or (is-same (elt board offset) (elt board (+ offset 1)) (elt board (+ offset 2)))))
(or (check-a-row 0) (check-a-row 3) (check-a-row 6)))
(defun win-on-columns (board)
(defun check-a-column (offset)
(or (is-same (elt board offset) (elt board (+ offset 3)) (elt board (+ offset 6)))))
(or (check-a-column 0) (check-a-column 1) (check-a-column 2)))
(defun win-on-diagonals (board)
(or (is-same (elt board 0) (elt board 4) (elt board 8)) (is-same (elt board 2) (elt board 4) (elt board 6))))
;;; This function gets the players move, plays it if possible
;;; then gets the next move. The game will play out in it's
;;; entirety through recursively calling this function.
(defun get-player-move (player board move-num)
(apply #'format t " ~C | ~C | ~C ~%-----------~% ~C | ~C | ~C ~%-----------~% ~C | ~C | ~C~%" (map 'list #'code-char board)) ; Print the board
(if (>= move-num 9) ; if all the moves have been played, and there is no winner
tie ; return the tie constant
(let ((move (- (parse-integer (read-line)) 1))) ; get the move from input, and convert it to list location
(if (is-move-free move board)
(let ((board (substitute player (elt board move) board))) ; apply the move, and get the new board
(if (or (win-on-columns board) (win-on-rows board) (win-on-diagonals board)) ; check if this was the winning move
(elt board move) ; return the winner
(get-player-move (if (= player p1) p2 p1) board (+ move-num 1)))) ; continue the game
(get-player-move player board move-num))))) ; move again, if the move was taken
(let ((result (get-player-move p1 '(49 50 51 52 53 54 55 56 57) 0)))
(if (= result tie)
(format t "It's a tie!")
(format t "The winner is ~C" (code-char result))))