Dice of Doom
发表于 23 May 2012
照例前面来些废话
前一阵忙着看通信原理,近来各科都陆续结课,又接连有这么几科让写个什么论文,忙忙碌碌之后发现lisp就没怎么看了,以致于这一章都看快一星期了还没看完。不过回头多看几次后,因为对游戏要生成个什么样的game tree有了整体上的把握,理解起来简单多了。所以奉劝其它读者先搞清楚要生成个什么样的game tree和作者的设计方案,再来看后面的代码部分。
昨天还激动终于看到用lisp来实现人工智能了,结果一看几行代码实现个minmax算法的问题……失望了。
Dice of Doom
游戏规则
这是个怎么样的游戏?看看这里就知道了。我们现在就要用函数式编程风格写一个这样的游戏,当然,从简单的开始。简单到只有两个玩家,只以骰子数目多少来决定能否“占领”对方的领地。
本想举个例子,不过画图挺麻烦的,现在没什么空。有空再补上。
有这么一些规则:
- 两个玩家占据着六边形阵列上的各个六边形。每个六边形都有一些立方体骰子,被两个玩家分别占有。
- 在一个回合内,玩家可以执行任意数目的移动,但至少移动一次。如果他不能移动那么他就输了。
- 移动包括攻击邻近的对方六边形。玩家发动攻击的六边形必须比要攻击的对方的六边形拥有更多骰子。将来可能我们会真的“转动”这些骰子。
- 攻击结束后,输者在被攻击六边形的骰子被移除,发动攻击的六边形留下一个骰子,将其它所有骰子移到占领的新地方。
- 当玩家完成他所有的移动,将获得他消灭的骰子总数减一的增援。增援从左上角开始给每个己方六角形增加一个骰子。若达到每个六边形的最大骰子数则跳过。
- 如果玩家都不能移动,游戏结束,占据最多六边形的玩家获胜。
然后是漫长的源码……
;;Defining Some Global Variables
;玩家数二
(defparameter *num-players* 2)
;一个六边形最多3个骰子
(defparameter *max-dice* 3)
(defparameter *board-size* 2)
;2x2的board
(defparameter *board-hexnum* (* *board-size* *board-size*))
;clean
;将list转化为array,为今后优化做准备
(defun board-array (lst)
(make-array *board-hexnum* :initial-contents lst))
;dirty
;生成随机的玩家分布和力量
(defun gen-board ()
(board-array (loop for n below *board-hexnum*
collect (list (random *num-players*)
(1+ (random *max-dice*))))))
;clean
;将玩家名由数字转换成字母
(defun player-letter (n)
(code-char (+ 97 n)))
;dirty
;通过两层循环控制输出,绘制board
(defun draw-board (board)
(loop for y below *board-size*
do (progn (fresh-line)
(loop repeat (- *board-size* y)
do (princ " "))
(loop for x below *board-size*
for hex = (aref board (+ x (* *board-size* y)))
do (format t "~a-~a " (player-letter (first hex))
(second hex))))))
;;;Decoupling Dice of Doom’s Rules from the Rest of the Game
;;Generating a Game Tree
;clean
;产生一个game-tree,形如(player board (moves))
(defun game-tree (board player spare-dice first-move)
(list player
board
(add-passing-move board
player
spare-dice
first-move
(attacking-moves board player spare-dice))))
;;Calculating Passing Moves
;clean
;如果是第一次移动则返回原tree的moves,如果不是则添加nil表示
;无法移动,并递归调用game-tree添加玩家移动树
(defun add-passing-move (board player spare-dice first-move moves)
(if first-move
moves
(cons (list nil
(game-tree (add-new-dice board player (1- spare-dice))
(mod (1+ player) *num-players*)
0
t))
moves)))
;clean
;Calculating Attacking Moves
;生成攻击可行的攻击game-tree中的moves
(defun attacking-moves (board cur-player spare-dice)
(labels ((player (pos)
(car (aref board pos)))
(dice (pos)
(cadr (aref board pos))))
(mapcan (lambda (src)
(when (eq (player src) cur-player)
(mapcan (lambda (dst)
(when (and (not (eq (player dst) cur-player))
(> (dice src) (dice dst)))
(list
(list (list src dst)
(game-tree (board-attack board cur-player src dst (dice src))
cur-player
(+ spare-dice (dice dst))
nil)))))
(neighbors src))))
(loop for n below *board-hexnum*
collect n))))
;Finding the Neighbors
;clean
;返回邻近的pos列表
(defun neighbors (pos)
(let ((up (- pos *board-size*))
(down (+ pos *board-size*)))
(loop for p in (append (list up down)
(unless (zerop (mod pos *board-size*))
(list (1- up) (1- pos)))
(unless (zerop (mod (1+ pos) *board-size*))
(list (1+ pos) (1+ down))))
when (and (>= p 0) (< p *board-hexnum*))
collect p)))
;Attacking
;clean
;生成attack后的board
(defun board-attack (board player src dst dice)
(board-array (loop for pos;第一个for为位置
for hex across board;数组用across
collect (cond ((eq pos src) (list player 1))
((eq pos dst) (list player (1- dice)))
(t hex)))))
;Reinforcements
;clean
;根据spare-dice添加骰子,返回新的board(数组)
(defun add-new-dice (board player spare-dice)
(labels ((f (lst n)
(cond ((null lst) nil)
((zerop n) lst)
(t (let ((cur-player (caar lst))
(cur-dice (cadar lst)))
(if (and (eq cur-player player) (< cur-dice *max-dice*))
(cons (list cur-player (1+ cur-dice))
(f (cdr lst) (1- n)))
(cons (car lst) (f (cdr lst) n))))))))
(board-array (f (coerce board 'list) spare-dice))))
;;Playing Dice of Doom Against Another Human
;The Main Loop
;dirty
;人人对战主函数
(defun play-vs-human (tree)
(print-info tree)
(if (caddr tree)
(play-vs-human (handle-human tree))
(announce-winner (cadr tree))))
;Giving Information About the State of the Game
;dirty
;打印当前信息,包括当前玩家和board
(defun print-info (tree)
(fresh-line)
(format t "current player = ~a" (player-letter (car tree)))
(draw-board (cadr tree)))
;Handling Input from Human Players
;dirty
;处理人的行动的函数,根据人的输入返回不同move的子树
(defun handle-human (tree)
(fresh-line)
(princ "choose your move:")
(let ((moves (caddr tree)))
(loop for move in moves
for n from 1
do (let ((action (car move)))
(fresh-line)
(format t "~a. " n)
(if action
(format t "~a -> ~a" (car action) (cadr action))
(princ "end turn"))))
(fresh-line)
(cadr (nth (1- (read)) moves))))
;Determining the Winner
(defun winners (board)
(let* ((tally (loop for hex across board
collect (car hex)))
(totals (mapcar (lambda (player)
(cons player (count player tally)))
(remove-duplicates tally)))
(best (apply #'max (mapcar #'cdr totals))))
(mapcar #'car
(remove-if (lambda (x)
(not (eq (cdr x) best)))
totals))))
现在可以试试新游戏:
(play-vs-human (game-tree (gen-board) 0 0 t))
其中0代表玩家0,第二个0代表刚开始获得的骰子为0,t代表第一次移动。
写在最后
尼玛这怎么设计出来的,看起来倒容易,设计者真值得膜拜……
着一个函数接着一个函数真是超级强烈的函数式编程风格好不好,我又想起来C语言这种强烈的面向过程风格,难道函数式就是功能式,其实就是过程式?
要说有什么收获,什么cddrcdar搞得更清楚了,我觉得作者用one two three什么的看起来更方便些。loop for什么for什么的across什么算是开了眼界了。其它,只让我体会到强烈的面向过程风格,作为不能完美计划所有过程的小白我还是继续看c++去比较简单…………
blog comments powered by Disqus