基于web的Dice of Doom
发表于 02 July 2012
基于web的Dice of Doom游戏
眼看最后两章了,虽然AI那里云山雾罩,然而看到web又精神为之一震,真是看得高潮迭起啊。作者也很给力,直接用html5来编写web应用,把svg直接嵌入到html中。可惜作者用的是firefox3.7 alpha,为了在我的archlinux x86_64的firefox12上运行良好,还是多废了些心机。这章看着很爽。
绘制游戏元素
毋庸置疑,所有游戏界面要用svg画出来,于是我们之前写得 svg库 要派上用场了。什么?已经没有了?那么从该书主页上下载 svg.lisp.
由于要创建个基于web的应用程序,之前的 webserver 也需要加载。
当然还有我们的游戏新引擎 Dice of Doom v2
(cd "~/Documents/lisp")
(load "dice_of_doom_v2")
(load "webserver")
(load "svg.lisp")
然后需要先设定一些常数
(defparameter *board-width* 900);面板宽度
(defparameter *board-height* 500);面板高度
(defparameter *board-scale* 64);面板缩放比例
(defparameter *top-offset* 3);两摞起来的骰子相对位移
(defparameter *dice-scale* 40);骰子缩放比例
(defparameter *dot-size* 0.05);骰子上点的大小
接下来画骰子
(defun draw-die-svg (x y col)
(labels ((calc-pt (pt)
(cons (+ x (* *dice-scale* (car pt)))
(+ y (* *dice-scale* (cdr pt)))))
(f (pol col)
(polygon (mapcar #'calc-pt pol) col)))
(f '((0 . -1) (-0.6 . -0.75) (0 . -0.5) (0.6 . -0.75))
(brightness col 40))
(f '((0 . -0.5) (-0.6 . -0.75) (-0.6 . 0) (0 . 0.25))
col)
(f '((0 . -0.5) (0.6 . -0.75) (0.6 . 0) (0 . 0.25))
(brightness col -40))
(mapc (lambda (x y)
(polygon (mapcar (lambda (xx yy)
(calc-pt (cons (+ x (* xx *dot-size*))
(+ y (* yy *dot-size*)))))
'(-1 -1 1 1)
'(-1 1 1 -1))
'(255 255 255)))
'(-0.05 0.125 0.3 -0.3 -0.125 0.05 0.2 0.2 0.45 0.45 -0.45 -0.2)
'(-0.875 -0.80 -0.725 -0.775 -0.70 -0.625 -0.35 -0.05 -0.45 -0.15 -0.45 -0.05))))
;(with-open-file (*standard-output* "die.svg" :direction :output :if-exists :supersede) (svg 100 100 (draw-die-svg 50 50 '(255 0 0))))
再画一小块面板
(defun draw-tile-svg (x y pos hex xx yy col chosen-tile)
(loop for z below 2
do (polygon (mapcar (lambda (pt)
(cons (+ xx (* *board-scale* (car pt)))
(+ yy (* *board-scale*
(+ (cdr pt) (* (- 1 z) 0.1))))))
'((-1 . -0.2) (0 . -0.5) (1 . -0.2)
(1 . 0.2) (0 . 0.5) (-1 . 0.2)))
(if (eql pos chosen-tile)
(brightness col 100)
col)))
(loop for z below (second hex)
do (draw-die-svg (+ xx
(* *dice-scale*
0.3
(if (oddp (+ x y z))
-0.3
0.3)))
(- yy (* *dice-scale* z 0.8)) col)))
;(with-open-file (*standard-output* "tile.svg" :direction :output) (svg 300 300 (draw-tile-svg 0 0 0 '(0 3) 100 150 '(255 0 0) nil)))
接下来是整个面板
;Draw the Board
(defparameter *die-colors* '((255 63 63) (63 63 255)))
(defun draw-board-svg (board chosen-tile legal-tiles)
(loop for y below *board-size*
do (loop for x below *board-size*
for pos = (+ x (* *board-size* y))
for hex = (aref board pos)
for xx = (* *board-scale* (+ (* 2 x) (- *board-size* y)))
for yy = (* *board-scale* (+ (* y 0.7) *top-offset*))
for col = (brightness (nth (first hex) *die-colors*)
(* -15 (- *board-size* y)))
do (if (member pos legal-tiles)
(tag g ()
(tag a ("xlink:href" (make-game-link pos))
(draw-tile-svg x y pos hex xx yy col chosen-tile)))
(draw-tile-svg x y pos hex xx yy col chosen-tile)))))
(defun make-game-link (pos)
(format nil "/game.html?chosen=~a" pos))
;(with-open-file (*standard-output* "board.svg" :direction :output :if-exists :supersede) (svg *board-width* *board-height* (draw-board-svg (gen-board) nil nil)))
这有个问题,之前我们svg宏不是像上文中那样定义的,svg宏需要稍加修改,具体参见本书的 errata
建立Web服务器
先写游戏的驱动
(defparameter *cur-game-tree* nil);初始化当前游戏树
(defparameter *from-tile* nil);选择的一片面板
(defun dod-request-handler (path header params)
(if (equal path "game.html")
(progn (princ "HTTP/1.1 200 OK
<!doctype html>
<html><head></head><body>")
(tag center ()
(princ "Welcome to DICE OF DOOM!")
(tag br ())
(let ((chosen (assoc 'chosen params)))
(when (or (not *cur-game-tree*) (not chosen))
(setf chosen nil)
(web-initialize))
(cond ((lazy-null (caddr *cur-game-tree*))
(web-announce-winner (cadr *cur-game-tree*)))
((zerop (car *cur-game-tree*))
(web-handle-human
(when chosen
(read-from-string (cdr chosen)))))
(t (web-handle-computer))))
(tag br ())
(draw-dod-page *cur-game-tree* *from-tile*))
(princ "</body></html>"))
(princ "Sorry... I don't know that page.")))
在这里我稍作修改,更改了服务器返回给浏览器的信息,使它更加符合标准能在firefox12中运行正常。
我们只做了一个超级简化的服务器,功能弱爆了,但作者许诺能在最后一章很轻易的扩展。之后我们处理些基于webserver的函数
(defun web-initialize ()
(setf *from-tile* nil)
(setf *cur-game-tree* (game-tree (gen-board) 0 0 t)))
;宣布胜者web version
(defun web-announce-winner (board)
(fresh-line)
(let ((w (winners board)))
(if (> (length w ) 1)
(format t "The game is a tie between ~a" (mapcar #'player-letter w))
(format t "The winner is ~a" (player-letter (car w)))))
(tag a (href "game.html")
(princ " play again")))
;处理人类玩家
(defun web-handle-human (pos)
(cond ((not pos) (princ "Please choose a hex to move from:"))
((eq pos 'pass) (setf *cur-game-tree*
(cadr (lazy-car (caddr *cur-game-tree*))))
(princ "Your reinforcements have been placed.")
(tag a (href (make-game-link nil))
(princ "continue")))
((not *from-tile*) (setf *from-tile* pos)
(princ "Now choose a destination:"))
((eq pos *from-tile*) (setf *from-tile* nil)
(princ "Move cancelled."))
(t (setf *cur-game-tree*
(cadr (lazy-find-if (lambda (move)
(equal (car move)
(list *from-tile* pos)))
(caddr *cur-game-tree*))))
(setf *from-tile* nil)
(princ "You may now ")
(tag a (href (make-game-link 'pass))
(princ "pass"))
(princ " or make another move:"))))
;处理计算机玩家
(defun web-handle-computer ()
(setf *cur-game-tree* (handle-computer *cur-game-tree*))
(princ "The computer has moved. ")
(tag script ()
(princ
"window.setTimeout('window.location=\"game.html?chosen=NIL\"',3000)")));javascript,改成3秒了,感觉5秒在我这里太慢了
然后在html中绘制svg游戏面板时有些tweak,首先是svg,你遵照之前errata提到的修正svg宏后才能正常显示,否则只显示一小块或完全看不到时可别吃惊。另外是作者定义两次点击同一片游戏面板代表取消,但它的legal-tile(合法面板)中却没有包含本身,以致于不能正常取消选中。我做了如下修改,把当前选中的游戏面板添加进合法面板中。
;caXXXXXXXdr……以后我要用firstsecond改写
(defun draw-dod-page (tree selected-tile)
(svg *board-width* ;svg要更改!!
*board-height*
(draw-board-svg (cadr tree)
selected-tile
(cons selected-tile;what I add
(take-all (if selected-tile
(lazy-mapcar
(lambda (move)
(when (eql (caar move)
selected-tile)
(cadar move)))
(caddr tree))
(lazy-mapcar #'caar (caddr tree))))))))
然后很显然,enjoy我们基于网络的游戏吧
(serve #'dod-request-handler)
游戏截图
截个图看看效果
blog comments powered by Disqus