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