首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >一个简单的Tic Tac脚趾游戏

一个简单的Tic Tac脚趾游戏
EN

Code Review用户
提问于 2018-08-25 03:15:18
回答 2查看 530关注 0票数 3

我正在尝试学习一些通用的Lisp,除了基本上我所有的计算背景都是C语言家族。所以,我开始很小。我做了一场Tic-Tac-Toe的比赛,我正在寻求一些建设性的批评.

特别是,这是一种惯用的Lisp吗?它的样式正常吗?一个有经验的语言学家会在这方面做些什么?

我对此代码的一些关注是:

  • 赢的检查似乎是重复的。可以简化吗?
  • 玩家的移动时间很长。它应该被打破吗?
代码语言:javascript
复制
;;;; 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))))
EN

回答 2

Code Review用户

回答已采纳

发布于 2018-08-25 08:42:15

以下是一些注意事项。

DEFPARAMETER与DEFVAR

前三个定义应该用defparameter而不是defvar来定义。第一个运算符用于不更改的值,除非它们在源文件中被修改,然后重新编译。第二个变量可以在运行时修改,但在重新加载和重新编译时,不会修改当前的运行时值(即不重新初始化)。

多个值

上的

比较运算符

在Common中,使用所有带有多个参数的比较运算符的可能性非常方便。例如,如果要检查数字abc是否都相等,只需编写(= a b c);如果要检查它们之间是否都不同(也就是说,不是两个值相等),则可以编写(\= a b c)。这既可以简化函数is-move-free,也可以使函数is-same成为不必要的。

半乘的win函数

实际上,它们都有相同的模式:比较三个值,从一个特定的索引开始,然后用相同的值递增两次。出于这个原因,我建议一个更简单的函数,包括所有这三个:

代码语言:javascript
复制
(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))))

注意:

  1. 使用flet来定义内部函数:defun应该只用于顶级函数。对于内部递归函数,您应该使用labels而不是flet
  2. incf的使用,它执行一个副作用递增变量的值(在这种情况下是安全的,因为在公共Lisp参数是通过值传递的)。
  3. 如果某个谓词在应用于一个(或多个)列表时至少一次为true,则使用some是一个函数,返回一个真值。在本例中,谓词是check,它应用于起始索引列表和增量列表。

最终代码

下面是我修改的全部代码:

代码语言:javascript
复制
(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的递归调用的应用样式,不如使用一种简单的迭代方式,例如:

代码语言:javascript
复制
(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)这样的常量。

票数 4
EN

Code Review用户

发布于 2018-08-27 20:41:03

其他一些改进:

  • 使用有用名称的更多函数使代码更好地记录下来。可以选择使用文档字符串。
  • 全局变量以*name*形式编写。
  • DEFUN替换为局部函数的FLET
  • 对谓词使用?-p
  • 不要使用字符值,直接使用符号和数字
  • 打印后,确保输出完成,然后再读取->完成输出。
  • 将代码分段以改进可视化导航

代码:

代码语言:javascript
复制
(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)))

赢了?

代码语言:javascript
复制
(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)))

游戏逻辑:

代码语言:javascript
复制
(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))))
票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/202449

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档