λ在Chito的第一個CSS配置[原主題:Earthy]
小企鵝輸入法(FCITX)·仿極點五筆特殊符號輸入編碼表

[CLisp]井字過三關代碼

λ posted @ 2011年11月24日 19:44 in Mixture with tags exercise lisp robot , 4195 阅读

做完《Common Lisp: A Gentle Introduction to Symbolic Computation》第 10.6 節中的練習後得到的成果。

TIC-TAC-TOE代碼

遊戲設定數據

;; 設定人類玩家和電腦玩家的棋子數值
(setf *opponent* 1 *computer* 5)

;; 所有連線位置
(setf *triplets*
   '((1 2 3) (4 5 6) (7 8 9)
     (1 4 7) (2 5 8) (3 6 9)
     (1 5 9) (3 5 7)))

;; 邊與角的位置
(setf *corners* '(1 3 7 9) *sides* '(2 4 6 8))

棋盤相關函數

;; 構造新棋盤
(defun make-board ()
  (list 'board 0 0 0 0 0 0 0 0 0))
  ;不能直接用 '(board 0 0 0 0 0 0 0 0 0)
  ;否則下次用的是使用過的同一個棋盤。

;; 將數值轉換成棋子
(defun convert-to-chess (val)
  (cond ((eql 1 val) "O") ; 人類玩家
        ((eql 5 val) "X") ; 電腦玩家
        (t " ")))

;; 打印一行棋子
(defun print-row (x y z)
  (format t "~& ~A | ~A | ~A"
            (convert-to-chess x)
            (convert-to-chess y)
            (convert-to-chess z)))

;; 打印棋盤
(defun print-board (board)
  (format t "~&~%")
  (print-row (nth 1 board) (nth 2 board) (nth 3 board))
  (format t "~&___________")
  (print-row (nth 4 board) (nth 5 board) (nth 6 board))
  (format t "~&___________")
  (print-row (nth 7 board) (nth 8 board) (nth 9 board))
  (format t "~&~%"))

玩家操作函數

;; 進行單次遊戲
(defun play-one-game ()
  (if (y-or-n-p "Would you like to play first? ")
    (opponent-move (make-board))
    (computer-move (make-board))))

;; 開始遊戲
(defun tic-tac-toe ()
  (format t "~&Welcome to play TIC-TAC-TOE!~%~%")
  (play-one-game)
  (format t "~&~%")
  (cond ((y-or-n-p "Play again? ")
         (format t "~%~%")
         (tic-tac-toe))
        (t (format t "~%~%Thanks for playing! Bye."))))

;; 玩家下子
(defun make-move (player pos board)
  (setf (nth pos board) player)
  board)
  
;; 人類玩家輸入有效下子位置
(defun valid-input (board)
  (format t "~&Your move: ")
  (let ((pos (read)))
    (cond ((not (and (integerp pos) (<= 1 pos 9)))
           (format t "~&Invalid input.")
           (valid-input board))
          ((not (zerop (nth pos board)))
           (format t "~&That space is already occupied.")
           (valid-input board))
          (t pos))))

;; 人類玩家下子
(defun opponent-move (board)
  (let* ((pos (valid-input board))
         (new-board (make-move *opponent* pos board)))
    (print-board new-board)
    (cond ((winner-p new-board) (format t "~&You win!."))
          ((full-board-p new-board) (format t "~&Tie game."))
          (t (computer-move new-board)))))

;; 隨機取得可下子位置
(defun pick-random-position (board)
  (let* ((empty-space (all-empty-positions board))
         (range (length empty-space)))
    (nth (random range) empty-space)))

;; 電腦玩家下子
(defun computer-move (board)
  (let* ((best-move (choose-best-move board))
         (pos (first best-move)) ; 最佳下子位置
         (strategy (second best-move)) ; 策略信息
         (new-board (make-move *computer* pos board)))
    (format t "~&My move: ~S" pos)
    (format t "~&My strategy: ~A" strategy)
    (print-board new-board)
    (cond ((winner-p new-board) (format t "~&I win!."))
          ((full-board-p new-board) (format t "~&Tie game."))
          (t (opponent-move new-board)))))

;; 電腦玩家最佳下子位置及相關策略
(defun choose-best-move (board)
  (or (make-three board)
      (block-win board)
      (block-squeeze-play board)
      (block-two-on-one board)
      (squeeze-trap board)
      (two-on-one-trap board)
      (make-two board)
      (random-move board)))

;; 策略:直接連線成功
(defun make-three (board)
  (let ((pos (win-or-block board (* 2 *computer*))))
    (and pos (list pos "make-three"))))

;; 策略:阻止人類玩家下一回合連線成功
(defun block-win (board)
  (let ((pos (win-or-block board (* 2 *opponent*))))
    (and pos (list pos "block-win"))))

;; 策略:規避斜線上 O-X-O 陷阱
(defun block-squeeze-play (board)
  (let ((block-sum (+ *computer* (* 2 *opponent*))))
    (and (or (eql block-sum (sum-of-triplet board '(1 5 9)))
             (eql block-sum (sum-of-triplet board '(3 5 7))))
         (eql *computer* (nth 5 board))
         (let ((pos (find-empty-postion board *sides*)))
           (and pos (list pos "block-squeeze-play"))))))

;; 策略:規避斜線上 O-O-X 陷阱
(defun block-two-on-one (board)
  (let ((block-sum (+ *computer* (* 2 *opponent*))))
    (and (or (eql block-sum (sum-of-triplet board '(1 5 9)))
             (eql block-sum (sum-of-triplet board '(3 5 7))))
         (eql *opponent* (nth 5 board))
         (let ((pos (find-empty-postion board *corners*)))
           (and pos (list pos "block-two-on-one"))))))

;; 策略:製造斜線上 X-O-X 陷阱
(defun squeeze-trap (board)
  (if (and (find-if #'(lambda (pos)
                        (eql *opponent* (nth pos board)))
                    *corners*)
           (eql 7 (length (all-empty-positions board))))
    (let ((pos (find-if #'integerp
                        (mapcar #'(lambda (triplet)
                                    (find-empty-postion board triplet))
                                '((1 5 9) (3 5 7))))))
      (and pos (list pos "squeeze-trap")))))

;; 策略:製造斜線上 X-X-O 陷阱
(defun two-on-one-trap (board)
  (if (and (find-if #'(lambda (pos)
                        (eql *opponent* (nth pos board)))
                    *sides*)
           (eql 7 (length (all-empty-positions board))))
    (cond ((eql *computer* (nth 5 board))
           (list (find-empty-postion board *corners*) "two-on-one-trap"))
          (t (list 5 "two-on-one-trap")))))

;; 策略:在線上放兩子
(defun make-two (board)
  (let ((pos (win-or-block board *computer*)))
    (and pos (list pos "make-two"))))

;; 策略:隨機下子
(defun random-move (board)
  (list (pick-random-position board) "random-move"))

遊戲信息函數

;; 是否勝利
(defun winner-p (board)
  (let ((sums (sum-of-board board)))
    (or (member (* 3 *opponent*) sums)
        (member (* 3 *computer*) sums))))

;; 棋盤是否已滿
(defun full-board-p (board)
  (not (member 0 board)))

;; 取得指定連線上棋子數值之和
(defun sum-of-triplet (board triplet)
  (apply #'+ (mapcar #'(lambda (pos)
                         (nth pos board))
                     triplet)))

;; 取得棋盤各連線上棋子數值之和
(defun sum-of-board (board)
  (mapcar #'(lambda (triplet)
              (sum-of-triplet board triplet))
          *triplets*))

;; 取得連線成功或能進行阻擋的第一個位置
(defun win-or-block (board target-sum)
  (let ((triplet (find-if #'(lambda (trip)
                             (eql target-sum
                                  (sum-of-triplet board trip)))
                          *triplets*)))
    (when triplet
      (find-empty-postion board triplet))))

;; 取得所有有效下子位置
(defun all-empty-positions (board)
  (remove-if-not #'(lambda (pos)
                     (zerop (nth pos board)))
                 '(1 2 3 4 5 6 7 8 9)))

;; 取得一個有效下子位置
(defun find-empty-postion (board pos-list)
  (find-if #'(lambda (pos)
               (zerop (nth pos board)))
           pos-list))

 


執行(tic-tac-toe)就能開始遊戲了,電腦不是無敵的,哈。

這些代碼的主要體現如下所述:

  • LISP的賦值語句很少,得益於遞歸函數尾調用及函數返回值。
  • 由下至上及由上至下的構造方法,將遊戲不斷分解,接著通過小模塊逐漸建立整個遊戲。
  • 函數式編程及表結構提供的強大批處理功能。

本網站無註明「轉載」的著作均由Jak Wings製作 CC BY-NC-SA 2.5
Creative Commons 保持署名-相同方式分享 2.5

Avatar_small
DeathKing 说:
Nov 25, 2011 12:46:00 AM

自从用了Maxima这个神器(显然我不是Emacs党),真心觉得Lisp强大,也打算以后有时间深入研究。(Ruby可也是很受Lisp启发的啊。)

Avatar_small
λ 说:
Nov 25, 2011 10:58:07 AM

嗯,用過Ruby肯定對Lisp也容易上手的。推薦先看《Common Lisp》,再看《Practical Common Lisp》,最後《On Lisp》。

《Common Lisp》主要系統地講一些基礎的東西,例子講得很生動,練習題比較多,但聯系比較緊密,啟發性很強。

《Practical Common Lisp》我還沒看,小翻了一下,包括了很多進階內容,內容組織得有點分散,強調實踐,實例占的比重比較大,不懂編程的新手看了估計會暈。

《On Lisp》主要講Lisp強大的宏。

Avatar_small
λ 说:
Nov 25, 2011 11:03:00 AM

再補充一本吧:《LISP Quick Reference》,查看各種函數、宏、邏輯指令、內置全局常量的定義。

以上四本都是Common Lisp的書。 Scheme、Haskel、Clojure啥的不知教程多不多。

DeathKing 说:
Nov 25, 2011 10:50:09 PM

Common Lisp评价很高啊,当时只是随手一翻,也没细看。

edutec.in 说:
Apr 26, 2023 01:46:26 AM

I had included my contact number when I applied for internet banking. However my account has not been changed. My e-banking account was momentarily frozen, and to change the password.Indian Overseas Bank Updating my cellphone number; my account has online banking enabled. I had included my contact number when I applied for internet banking. However my account has not been changed. My e-banking account edutec.in was momentarily frozen, and to change the password, Indian Overseas Bank Updating my cellphone number; my account has online banking enabled.

uburt.in 说:
Apr 26, 2023 01:47:47 AM

Following verification, you must provide a copy of your self-attested Aadhar card.When making an account on any KRA’s eKYC site, you must provide your personal information, such as your Aadhar card number, as well as your registered phone number, to which you will receive an OTP. Following uburt.in verification, you must provide a copy of your self-attested Aadhar card. When making an account on any KRA’s eKYC site, you must provide your personal information, such as your Aadhar card number, as well as your registered phone number, to which you will receive an OTP.

netflix descuento e 说:
Aug 13, 2023 09:21:39 AM

Literalmente, no hay descuento para estudiantes de Netflix para estudiantes universitarios. Pero no te decepciones todavía. Hay algunos métodos que puede usar para obtener Netflix para una prueba gratuita.Aunque netflix descuento estudiante si el acceso ilimitado a Netflix cuesta $ 7.99 por mes, es posible que los estudiantes con un presupuesto limitado no puedan pagar dicho paquete de membresía Netflix no ofrece descuento para estudiantes. Lamentablemente, Netflix ofrece con frecuencia ofertas especiales y descuentos a clientes nuevos y actuales, por lo que es concebible un descuento para estudiantes en el futuro.


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter