EMACS-DOCUMENT

=============>随便,谢谢

Part 7: 移动,醉鬼漫步,键盘控制

文件处理

要开始编写代码,需要先创建一个编辑文件的emacs缓冲区。比如你可以输入 c-x c-f ~/Desktop/motion.el. ~/Desktop/ 导航到您的桌面(在OS X上),然后 motion.el 是文件名。你可以选择任何名称,但是得以 .el 结尾好让Emacs知道这是一个Emacs Lisp程序。

第一次使用 c-x c-f 导航到该文件时,Emacs将创建该文件。然后通过 c-x c-s 保存工作。 下次启动Emacs时,执行 c-x c-f ~/Desktop/motion.el (或其他名称)将加载该文件。

在指定文件名或目录时,按下 TAB Emacs将补完目录/文件名。如果没有找到补全内容,按 TAB 2此就会显示所有可能的补全项。 要在闪存上编辑文件,请使用类似 /volume/=drivename=/filename 这样的路径名(在OS X上)。

快捷键 意义
c-x c-f 查找文件
c-x c-s 保存文件
TAB 尝试补全目录/文件名称

Test 缓冲区

要在相同Emacs窗口中显示代码和test缓冲区,输入 c-x 3 来分割Emacs窗口。然后就可以通过 c-x o 在左右窗格之间切换光标。 在右侧窗格中,输入 c-x b test <return> 创建一个test缓冲区。然后=c-x o=返回左窗格并编辑代码。将窗口加宽,以便有足够的空间来查看左边的代码和右边的测试缓冲区。

emacs-two-pane.jpg

Now in the left pane, you can type m-x eval-buffer <return> to load your Lisp code into Emacs. Then type c-x o to move to the right pane (test buffer), and type m-: (=defunname)= to run a defun you've created. If you need to quit your defun (because it is looping forever), type c-g. Then c-x o will bring you back to you Emacs code in the left pane. 现在在左侧窗格中,可以键入=m-x eval-buffer <return>=将Lisp代码加载到Emacs中。然后type c-x o=移动到右边的窗格(测试缓冲区),type =m-:(=defunname)=运行您创建的defun。如果需要退出defun(因为它会一直循环下去),输入=c-g=。然后=c-x o=将带您回到左窗格中的Emacs代码。

If you accidentally run your Lisp program in the same buffer as you Lisp code, it'll probably overwrite your code. To get your program back, type c-/ which performs an undo. If the Lisp code has hidden the cursor, typing m-: (setq cursor-type t) <return> will bring back the cursor. 如果您不小心在与Lisp代码相同的缓冲区中运行Lisp程序,它可能会覆盖您的代码。要取回程序,键入=c-/=执行撤消操作。如果Lisp代码隐藏了光标,那么键入=m-:(setq指针类型t) <return>=将返回光标。

key command meaning 关键命令的意思 c-x 3 = 3 cx = split window horizontally 水平分割窗口 c-x o = cx o = move cursor to other pane 将光标移到其他窗格 c-x b = cx b = switch to buffer 切换到缓冲区 m-x eval-buffer = m x eval-buffer = load the Lisp code in the current buffer 在当前缓冲区中加载Lisp代码 m-: m - = = = = prompt for and evaluate a Lisp expression 提示并计算Lisp表达式 c-g = c g = abort running Lisp program (or pretty much anything else) 中止运行Lisp程序(或其他任何程序) c-/ c - / = = = = undo 撤销 m-: (setq cursor-type t) m-:(setq指针型t) bring back cursor 让光标

注释

Lisp会忽略一行中分号(;)后面所有内容。这允许您用非lisp语言对代码进行注释。习惯上,整行注释以两个分号(;;)开头:

;; set some variables
(setq i 23)
(setq j "hello")

辅助函数和全局变量

我们将设置此页中的代码为全局变量(在 defun 之外的通过 setq 进行赋值,这样所有函数都可以看到它们),并以此来绘制网格大小和背景。 然后我们将创建函数来绘制网格、绘制字符和清除字符。需要运行这段代码,才能使其他代码正常工作。

;; global variables

(setq width 50)
(setq height 35)
(setq background-char ?.)

;; helper routines (little elves)

(defun make-grid ()
  (erase-buffer)
  (dotimes (i height)
    (insert-char background-char width)
    (newline)))

(defun draw-char (x y char)
  (goto-char (+ x (* (1- y) (1+ width))))
  (delete-char 1)
  (insert-char char 1))

(defun clear-char (x y)
  (draw-char x y background-char))

1+ 返回比它的参数多1的值, 1- 返回少1的值。因此 (1+ width)(+ 1 width) 的缩写,而 (1- y)(- y 1) 的缩写。

注意, clear-char 是一个函数,它使用了另一个函数 draw-char 来完成工作,同时引用了全局变量 background-char.(就像 draw-char 使用了Emacs内置函数 goto-char, delete-charinsert-char 来完成工作一样。)

让字符向右下方移动

(defun move-char ()
  (make-grid)
  (let ((x 1) (y 1))
    (dotimes (i 30)
      (draw-char x y ?*)
      (sit-for 0.2)
      (clear-char x y)
      (setq x (+ x (random 3))
            y (+ y (random 3))))))

let 设置 xy 来跟踪字符的位置. (random 3) 返回0、1或2。因此, setq 设置的 xy 值是随机的,他们要么相同,要么多一点。 注意,=setq= 可以设置多个变量。

两个字符的动画

(defun move-two-chars ()
  (make-grid)
  (let ((x1 1) (y1 1)
        (x2 1) (y2 1))
    (dotimes (i 30)
      (draw-char x1 y1 ?*)
      (draw-char x2 y2 ?o)
      (sit-for 0.2)
      (clear-char x1 y1)
      (clear-char x2 y2)
      (setq x1 (+ x1 (random 3))
            y1 (+ y1 (random 3))
            x2 (+ x2 (random 4))
            y2 (+ y2 (random 2))))))

这是一个很有特色的动画代码. let 设置两个字符。每次循环,代码首先绘制字符,然后暂停以便这两个字符显示出来,然后清除该字符,再更新它们的位置,然后重复。 (在slicker的代码中,计算机可以在暂停期间更新字符位置数据,以节省时间并消除闪烁。)

醉鬼漫步

(defun drunkard ()
  (setq cursor-type nil)
  (make-grid)
  (let ((x (/ width 2))
        (y (/ height 2)))
    (while t
      (draw-char x y ?*)
      (sit-for 0.2)
      (clear-char x y)
      (setq x (+ x (1- (random 3)))
            y (+ y (1- (random 3)))))))

字符交错出现在屏幕上. (setq cursor-type nil) 会隐藏光标。=(1- (random 3))= 返回-1、0或1。 while t 表示酒鬼将永远游荡(实际上不是永远,当角色离开网格时,此代码将中断)。

两个醉鬼

(defun two-drunks ()
  (setq cursor-type nil)
  (make-grid)
  (let ((x1 (/ width 2)) (y1 (/ height 2))
        (x2 (/ width 2)) (y2 (/ height 2)))
    (while t
      (draw-char x1 y1 ?\*)
      (draw-char x2 y2 ?\#)
      (sit-for 0.2)
      (make-grid)
      (setq x1 (+ x1 (1- (random 3)))
            y1 (+ y1 (1- (random 3)))
            x2 (+ x2 (1- (random 3)))
            y2 (+ y2 (1- (random 3)))))))

相似 move-two-chars.

一个带有辅助功能来确定交错方向的版本:

(defun delta ()
  (1- (random 3)))

(defun two-drunks2 ()
  (setq cursor-type nil)
  (make-grid)
  (let ((x1 (/ width 2)) (y1 (/ height 2))
        (x2 (/ width 2)) (y2 (/ height 2)))
    (while t
      (draw-char x1 y1 ?*)
      (draw-char x2 y2 ?#)
      (sit-for 0.2)
      (make-grid)
      (setq x1 (+ x1 (delta))
            y1 (+ y1 (delta))
            x2 (+ x2 (delta))
            y2 (+ y2 (delta))))))

注意 delta 是一个函数,它返回一个值,这个值是函数中最后一个(在本例中也仅有这一个)表达式的结果。这个值可以是- 1,0,也可以是1,每次调用它的时候都会变化,因为它调用了 random.

一个永远流的醉鬼

(defun circular-drunkard ()
  (setq cursor-type nil)
  (make-grid)
  (let ((x (/ width 2))
        (y (/ height 2)))
    (while t
      (draw-char x y ?*)
      (sit-for 0.05)
      (clear-char x y)
      (setq x (+ x (1- (random 3)))
            y (+ y (1- (random 3))))
      ;; like pac-man, edges of screen connect
      (if (< x 1) (setq x width))
      (if (> x width) (setq x 1))
      (if (< y 1) (setq y height))
      (if (> y height) (setq y 1)))))

最后的 if 从句处理离开网格的字符。

受键盘控制的行者

(defun walker ()
  (setq cursor-type nil)
  (make-grid)
  (let ((x 1) (y 1))
    (while t
      (draw-char x y ?*)
      (let ((key (read-event)))
        (clear-char x y)
        (cond
         ((eq key 'left)
          (setq x (1- x)))
         ((eq key 'right)
          (setq x (1+ x)))
         ((eq key 'up)
          (setq y (1- y)))
         ((eq key 'down)
          (setq y (1+ y)))))
      ;; stop at edges of screen
      (setq x (min x width)
            y (min y height)
            x (max x 1)
            y (max y 1)))))

就像一个醉汉走路一样,只是现在能用键盘控制角色走路的方向. (read-event) 等待一个按键,然后返回它. cond (condition的缩写)函数是一种执行多个 if 而不需要每次都写 if 的方法。 通过 let 命令将 (read-event) 的结果临时存储在变量 key 中,让 cond 将相同的按键与所有四个箭头键进行比较。 在 left, right, up, down 之前的撇号(')是必须的,否则Lisp会认为我们指的是一个名为 left (或其他)的变量。

minmax 函数分别返回它们的参数中的最小值和最大i值。 这是一种确保x总是小于等于 width, y总是小于等于 height,并且x和y都大于等于1简洁的方法。

按一次键,角色就会一直移动

(defun runner ()
  (setq cursor-type nil)
  (make-grid)
  (let ((x 1) (y 1)
        (dx 0) (dy 0))
    (while (and (>= x 1) (<= x width))
      (draw-char x y ?*)
      (let ((key (read-event nil nil 0.1)))
        (cond
         ((eq key 'left)
          (setq dx -1))
         ((eq key 'right)
          (setq dx 1))
         ((eq key 'up)
          (setq dy -1))
         ((eq key 'down)
          (setq dy 1))))
      (clear-char x y)
      (setq x (+ x dx)
            y (+ y dy))
      ;; bounce off edges
      (if (or (< x 1) (> x width))
          (setq dx (- dx)))
      (if (or (< y 1) (> y height))
          (setq dy (- dy)))
      ;; stop at edges of screen
      (setq x (min x width)
            y (min y height)
            x (max x 1)
            y (max y 1)))))

变量 dxdy 代表x和y的偏差,描述x和y的变化率。

(read-event nil nil 0.1) 等待击键十分之一秒,并返回击键(如果有的话),或者 nil 如果当时没有击键。 这是检查输入的一种好方法,但是会在没有输入的情况下,继续执行操作。因为它需要等待一段时间,所以我们不再需要 (sit-for 0.1),在 read-event 中的等待做同样的事情。

(setq dx (- dx)) 设置 dx 为负的 dx,以反转字符的左右移动。 类似地, (setq dy (- dy)) 反转字符的上下运动方向。 这(在 if 测试中)使角色走的太远的情况下在屏幕边缘反弹。这是一大段代码让字符在屏幕边缘弹回,也受到其约束…

字符会从网格的顶部/底部弹起,但会从两侧掉下去

(defun runner-with-boundaries ()
  (setq cursor-type nil)
  (make-grid)
  (let ((x 1) (y 1)
        (dx 0) (dy 0))
    (while (and (>= x 1) (<= x width))
      (draw-char x y ?*)
      (let ((key (read-event nil nil 0.1)))
        (cond
         ((eq key 'left)
          (setq dx -1))
         ((eq key 'right)
          (setq dx 1))
         ((eq key 'up)
          (setq dy -1))
         ((eq key 'down)
          (setq dy 1))))
      (clear-char x y)
      (setq x (+ x dx)
            y (+ y dy))
      ;; bounce off top/bottom edges
      (if (or (< y 1) (> y height))
          (setq dy (- dy)))
      ;; stop at edges of screen
      (setq y (min y height)
            y (max y 1)))
    (insert "Game over")))

现在 while 循环不会一直循环下去,除非字符保持在左/右网格边界内。最后的“Game over”是有点粗糙,但这开始让我们更像Pong游戏了!