我正在尝试学习一些通用的Lisp,除了基本上我所有的计算背景都是C语言家族。所以,我开始很小。我做了一场Tic-Tac-Toe的比赛,我正在寻求一些建设性的批评.
特别是,这是一种惯用的Lisp吗?它的样式正常吗?一个有经验的语言学家会在这方面做些什么?
我对此代码的一些关注是:
;;;; 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))))发布于 2018-08-25 08:42:15
以下是一些注意事项。
前三个定义应该用defparameter而不是defvar来定义。第一个运算符用于不更改的值,除非它们在源文件中被修改,然后重新编译。第二个变量可以在运行时修改,但在重新加载和重新编译时,不会修改当前的运行时值(即不重新初始化)。
多个值
比较运算符
在Common中,使用所有带有多个参数的比较运算符的可能性非常方便。例如,如果要检查数字a、b和c是否都相等,只需编写(= a b c);如果要检查它们之间是否都不同(也就是说,不是两个值相等),则可以编写(\= a b c)。这既可以简化函数is-move-free,也可以使函数is-same成为不必要的。
win函数实际上,它们都有相同的模式:比较三个值,从一个特定的索引开始,然后用相同的值递增两次。出于这个原因,我建议一个更简单的函数,包括所有这三个:
(defun win (board)
(flet ((check (start increment)
(= (elt board start) (elt board (incf start increment)) (elt board (incf start increment)))))
(some #'check '(0 3 6 0 1 2 0 2) '(1 1 1 3 3 3 4 2))))注意:
flet来定义内部函数:defun应该只用于顶级函数。对于内部递归函数,您应该使用labels而不是flet。incf的使用,它执行一个副作用递增变量的值(在这种情况下是安全的,因为在公共Lisp参数是通过值传递的)。some是一个函数,返回一个真值。在本例中,谓词是check,它应用于起始索引列表和增量列表。下面是我修改的全部代码:
(defparameter p1 88) ; char code for 'X'
(defparameter p2 79) ; char code for 'O'
(defparameter tie 49) ; arbitrary constant to represent ties
(defun is-move-free (move board)
(let ((player (elt board move)))
(/= p1 p2 player)))
(defun win (board)
(flet ((check (start increment)
(= (elt board start) (elt board (incf start increment)) (elt board (incf start increment)))))
(some #'check '(0 3 6 0 1 2 0 2) '(1 1 1 3 3 3 4 2))))
;;; 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~%" (mapcar #'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 (win 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))))请注意,if使用了更常见的缩进约定,以及format的半细化。
通用Lisp的设计既适用于应用(即功能性)风格,也适用于命令式(即副作用)风格。因此,与其使用每次使用substitute创建新板并将其传递给函数get-player-move的递归调用的应用样式,不如使用一种简单的迭代方式,例如:
(defun get-player-move (player board move-num)
(loop
(apply #'format t " ~C | ~C | ~C ~%-----------~% ~C | ~C | ~C ~%-----------~% ~C | ~C | ~C~%" (mapcar #'code-char board)) ; Print the board
(let ((move (- (parse-integer (read-line)) 1)))
(when (is-move-free move board)
(setf (elt board move) player)
(when (win board) (return player))
(incf move-num)
(when (= move-num 9) (return tie))
(setf player (if (= player p1) p2 p1))))))请注意,在这种情况下,由于板是修改的,应该首先使用(list 49 50 51 52 53 54 55 56 57)创建它,而不是使用像'(49 50 51 52 53 54 55 56 57)这样的常量。
发布于 2018-08-27 20:41:03
其他一些改进:
*name*形式编写。DEFUN替换为局部函数的FLET?或-p代码:
(defparameter *p1* 'X "Player 1")
(defparameter *p2* 'O "Player 2")
(defparameter *tie* 'tie "Tie")
(defun is-move-free? (move board)
(let ((player (elt board move)))
(not (or (eql player *p1*)
(eql player *p2*)))))
(defun is-same? (a b c)
(and (eql a b) (eql b c)))赢了?
(defun win-on-rows? (board)
(flet ((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)
(flet ((check-a-column? (offset)
(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))))
(defun win? (board)
(or (win-on-columns? board)
(win-on-rows? board)
(win-on-diagonals? board)))游戏逻辑:
(defun next-player (player)
(if (eql player *p1*) *p2* *p1*))
(defun get-move ()
(- (parse-integer (read-line)) 1))
(defun print-board (board)
(apply #'format t
" ~A | ~A | ~A ~%-----------~% ~A | ~A | ~A ~%-----------~% ~A | ~A | ~A~%"
board)
(finish-output))
;;; 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)
(print-board board)
(if (>= move-num 9)
*tie*
(let ((move (get-move)))
(if (is-move-free? move board)
(let ((board (substitute player (elt board move) board)))
(if (win? board)
(elt board move)
(get-player-move (next-player player)
board
(+ move-num 1))))
(get-player-move player board move-num)))))
(defun game ()
(let ((result (get-player-move *p1* (list 1 2 3 4 5 6 7 8 9) 0)))
(if (eql result *tie*)
(format t "It's a tie!")
(format t "The winner is ~C" result))))https://codereview.stackexchange.com/questions/202449
复制相似问题