comparison lisp/play/blackbox.el @ 473:999d0b38694e

Initial revision
author Jim Blandy <jimb@redhat.com>
date Sat, 21 Dec 1991 08:23:15 +0000
parents
children 8a533acedb77
comparison
equal deleted inserted replaced
472:e6b49c51a9bb 473:999d0b38694e
1 ; Blackbox game in Emacs Lisp
2 ;; Copyright (C) 1985, 1986, 1987 Free Software Foundation, Inc.
3
4 ;; This file is part of GNU Emacs.
5
6 ;; GNU Emacs is free software; you can redistribute it and/or modify
7 ;; it under the terms of the GNU General Public License as published by
8 ;; the Free Software Foundation; either version 1, or (at your option)
9 ;; any later version.
10
11 ;; GNU Emacs is distributed in the hope that it will be useful,
12 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ;; GNU General Public License for more details.
15
16 ;; You should have received a copy of the GNU General Public License
17 ;; along with GNU Emacs; see the file COPYING. If not, write to
18 ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
19
20 ; by F. Thomas May <uw-nsr!uw-warp!tom@beaver.cs.washington.edu>
21 ; doc comment by Root Boy Jim <rbj@dsys.icst.nbs.gov>, 27 Apr 89
22 ; interface improvements by Eric Raymond <eric@snark.thyrsus.com>, Dec 5 1991.
23
24 ; The object of the game is to find four hidden balls by shooting rays
25 ; into the black box. There are four possibilities: 1) the ray will
26 ; pass thru the box undisturbed, 2) it will hit a ball and be absorbed,
27 ; 3) it will be deflected and exit the box, or 4) be deflected immediately,
28 ; not even being allowed entry into the box.
29 ;
30 ; The strange part is the method of deflection. It seems that rays will
31 ; not pass next to a ball, and change direction at right angles to avoid it.
32 ;
33 ; R 3
34 ; 1 - - - - - - - - 1
35 ; - - - - - - - -
36 ; - O - - - - - - 3
37 ; 2 - - - - O - O -
38 ; 4 - - - - - - - -
39 ; 5 - - - - - - - - 5
40 ; - - - - - - - - R
41 ; H - - - - - - - O
42 ; 2 H 4 H
43 ;
44 ; Rays which enter and exit are numbered. You can see that rays 1 & 5 pass
45 ; thru the box undisturbed. Ray 2 is deflected by the northwesternmost
46 ; ball. Likewise rays 3 and 4. Rays which hit balls and are absorbed are
47 ; marked with H. The bottom of the left and the right of the bottom hit
48 ; the southeastern ball directly. Rays may also hit balls after being
49 ; reflected. Consider the H on the bottom next to the 4. It bounces off
50 ; the NW-ern most ball and hits the central ball. A ray shot from above
51 ; the right side 5 would hit the SE-ern most ball. The R beneath the 5
52 ; is because the ball is returned instantly. It is not allowed into
53 ; the box if it would reflect immediately. The R on the top is a more
54 ; leisurely return. Both central balls would tend to deflect it east
55 ; or west, but it cannot go either way, so it just retreats.
56 ;
57 ; At the end of the game, if you've placed guesses for as many balls as
58 ; there are in the box, the true board position will be revealed. Each
59 ; `x' is an incorrect guess of yours; `o' is the true location of a ball.
60
61 (defvar blackbox-mode-map nil "")
62
63 (if blackbox-mode-map
64 ()
65 (setq blackbox-mode-map (make-keymap))
66 (suppress-keymap blackbox-mode-map t)
67 (define-key blackbox-mode-map "\C-f" 'bb-right)
68 (define-key blackbox-mode-map "\C-b" 'bb-left)
69 (define-key blackbox-mode-map "\C-p" 'bb-up)
70 (define-key blackbox-mode-map "\C-n" 'bb-down)
71 (define-key blackbox-mode-map "\C-e" 'bb-eol)
72 (define-key blackbox-mode-map "\C-a" 'bb-bol)
73 (define-key blackbox-mode-map " " 'bb-romp)
74 (define-key blackbox-mode-map "\C-m" 'bb-done)
75
76 ;; This is a kluge. What we really want is a general
77 ;; feature for reminding terminal keys to the functions
78 ;; corresponding to them in local maps
79 (if (featurep 'keypad)
80 (let (keys)
81 (if (setq keys (function-key-sequence ?u)) ; Up Arrow
82 (define-key blackbox-mode-map keys 'bb-up))
83 (if (setq keys (function-key-sequence ?d)) ; Down Arrow
84 (define-key blackbox-mode-map keys 'bb-down))
85 (if (setq keys (function-key-sequence ?l)) ; Left Arrow
86 (define-key blackbox-mode-map keys 'bb-left))
87 (if (setq keys (function-key-sequence ?r)) ; Right Arrow
88 (define-key blackbox-mode-map keys 'bb-right))
89 (if (setq keys (function-key-sequence ?e)) ; Enter
90 (define-key blackbox-mode-map keys 'bb-done))
91 (if (setq keys (function-key-sequence ?I)) ; Insert
92 (define-key blackbox-mode-map keys 'bb-romp))
93 )))
94
95
96 ;; Blackbox mode is suitable only for specially formatted data.
97 (put 'blackbox-mode 'mode-class 'special)
98
99 (defun blackbox-mode ()
100 "Major mode for playing blackbox. To learn how to play blackbox,
101 see the documentation for function `blackbox'.
102
103 The usual mnemonic keys move the cursor around the box.
104 \\<blackbox-mode-map>\\[bb-bol] and \\[bb-eol] move to the beginning and end of line, respectively.
105
106 \\[bb-romp] -- send in a ray from point, or toggle a ball at point
107 \\[bb-done] -- end game and get score
108 "
109 (interactive)
110 (kill-all-local-variables)
111 (use-local-map blackbox-mode-map)
112 (setq truncate-lines t)
113 (setq major-mode 'blackbox-mode)
114 (setq mode-name "Blackbox"))
115
116 (defun blackbox (num)
117 "Play blackbox. Optional prefix argument is the number of balls;
118 the default is 4.
119
120 What is blackbox?
121
122 Blackbox is a game of hide and seek played on an 8 by 8 grid (the
123 Blackbox). Your opponent (Emacs, in this case) has hidden several
124 balls (usually 4) within this box. By shooting rays into the box and
125 observing where they emerge it is possible to deduce the positions of
126 the hidden balls. The fewer rays you use to find the balls, the lower
127 your score.
128
129 Overview of play:
130
131 To play blackbox, call the function `blackbox'. An optional prefix
132 argument specifies the number of balls to be hidden in the box; the
133 default is four.
134
135 The cursor can be moved around the box with the standard cursor
136 movement keys.
137
138 To shoot a ray, move the cursor to the edge of the box and press SPC.
139 The result will be determined and the playfield updated.
140
141 You may place or remove balls in the box by moving the cursor into the
142 box and pressing \\<bb-romp>.
143
144 When you think the configuration of balls you have placed is correct,
145 press \\<bb-done>. You will be informed whether you are correct or not, and
146 be given your score. Your score is the number of letters and numbers
147 around the outside of the box plus five for each incorrectly placed
148 ball. If you placed any balls incorrectly, they will be indicated
149 with `x', and their actual positions indicated with `o'.
150
151 Details:
152
153 There are three possible outcomes for each ray you send into the box:
154
155 Detour: the ray is deflected and emerges somewhere other than
156 where you sent it in. On the playfield, detours are
157 denoted by matching pairs of numbers -- one where the
158 ray went in, and the other where it came out.
159
160 Reflection: the ray is reflected and emerges in the same place
161 it was sent in. On the playfield, reflections are
162 denoted by the letter `R'.
163
164 Hit: the ray strikes a ball directly and is absorbed. It does
165 not emerge from the box. On the playfield, hits are
166 denoted by the letter `H'.
167
168 The rules for how balls deflect rays are simple and are best shown by
169 example.
170
171 As a ray approaches a ball it is deflected ninety degrees. Rays can
172 be deflected multiple times. In the diagrams below, the dashes
173 represent empty box locations and the letter `O' represents a ball.
174 The entrance and exit points of each ray are marked with numbers as
175 described under \"Detour\" above. Note that the entrance and exit
176 points are always interchangeable. `*' denotes the path taken by the
177 ray.
178
179 Note carefully the relative positions of the ball and the ninety
180 degree deflection it causes.
181
182 1
183 - * - - - - - - - - - - - - - - - - - - - - - -
184 - * - - - - - - - - - - - - - - - - - - - - - -
185 1 * * - - - - - - - - - - - - - - - O - - - - O -
186 - - O - - - - - - - O - - - - - - - * * * * - -
187 - - - - - - - - - - - * * * * * 2 3 * * * - - * - -
188 - - - - - - - - - - - * - - - - - - - O - * - -
189 - - - - - - - - - - - * - - - - - - - - * * - -
190 - - - - - - - - - - - * - - - - - - - - * - O -
191 2 3
192
193 As mentioned above, a reflection occurs when a ray emerges from the same point
194 it was sent in. This can happen in several ways:
195
196
197 - - - - - - - - - - - - - - - - - - - - - - - -
198 - - - - O - - - - - O - O - - - - - - - - - - -
199 R * * * * - - - - - - - * - - - - O - - - - - - -
200 - - - - O - - - - - - * - - - - R - - - - - - - -
201 - - - - - - - - - - - * - - - - - - - - - - - -
202 - - - - - - - - - - - * - - - - - - - - - - - -
203 - - - - - - - - R * * * * - - - - - - - - - - - -
204 - - - - - - - - - - - - O - - - - - - - - - - -
205
206 In the first example, the ray is deflected downwards by the upper
207 ball, then left by the lower ball, and finally retraces its path to
208 its point of origin. The second example is similar. The third
209 example is a bit anomalous but can be rationalized by realizing the
210 ray never gets a chance to get into the box. Alternatively, the ray
211 can be thought of as being deflected downwards and immediately
212 emerging from the box.
213
214 A hit occurs when a ray runs straight into a ball:
215
216 - - - - - - - - - - - - - - - - - - - - - - - -
217 - - - - - - - - - - - - - - - - - - - - O - - -
218 - - - - - - - - - - - - O - - - H * * * * - - - -
219 - - - - - - - - H * * * * O - - - - - - * - - - -
220 - - - - - - - - - - - - O - - - - - - O - - - -
221 H * * * O - - - - - - - - - - - - - - - - - - - -
222 - - - - - - - - - - - - - - - - - - - - - - - -
223 - - - - - - - - - - - - - - - - - - - - - - - -
224
225 Be sure to compare the second example of a hit with the first example of
226 a reflection."
227 (interactive "P")
228 (switch-to-buffer "*Blackbox*")
229 (blackbox-mode)
230 (setq buffer-read-only t)
231 (buffer-disable-undo (current-buffer))
232 (setq bb-board (bb-init-board (or num 4)))
233 (setq bb-balls-placed nil)
234 (setq bb-x -1)
235 (setq bb-y -1)
236 (setq bb-score 0)
237 (setq bb-detour-count 0)
238 (bb-insert-board)
239 (bb-goto (cons bb-x bb-y)))
240
241 (defun bb-init-board (num-balls)
242 (random t)
243 (let (board pos)
244 (while (>= (setq num-balls (1- num-balls)) 0)
245 (while
246 (progn
247 (setq pos (cons (random 8) (random 8)))
248 (bb-member pos board)))
249 (setq board (cons pos board)))
250 board))
251
252 (defun bb-insert-board ()
253 (let (i (buffer-read-only nil))
254 (erase-buffer)
255 (insert " \n")
256 (setq i 8)
257 (while (>= (setq i (1- i)) 0)
258 (insert " - - - - - - - - \n"))
259 (insert " \n")
260 (insert (format "\nThere are %d balls in the box" (length bb-board)))
261 ))
262
263 (defun bb-right ()
264 (interactive)
265 (if (= bb-x 8)
266 ()
267 (forward-char 2)
268 (setq bb-x (1+ bb-x))))
269
270 (defun bb-left ()
271 (interactive)
272 (if (= bb-x -1)
273 ()
274 (backward-char 2)
275 (setq bb-x (1- bb-x))))
276
277 (defun bb-up ()
278 (interactive)
279 (if (= bb-y -1)
280 ()
281 (previous-line 1)
282 (setq bb-y (1- bb-y))))
283
284 (defun bb-down ()
285 (interactive)
286 (if (= bb-y 8)
287 ()
288 (next-line 1)
289 (setq bb-y (1+ bb-y))))
290
291 (defun bb-eol ()
292 (interactive)
293 (setq bb-x 8)
294 (bb-goto (cons bb-x bb-y)))
295
296 (defun bb-bol ()
297 (interactive)
298 (setq bb-x -1)
299 (bb-goto (cons bb-x bb-y)))
300
301 (defun bb-romp ()
302 (interactive)
303 (cond
304 ((and
305 (or (= bb-x -1) (= bb-x 8))
306 (or (= bb-y -1) (= bb-y 8))))
307 ((bb-outside-box bb-x bb-y)
308 (bb-trace-ray bb-x bb-y))
309 (t
310 (bb-place-ball bb-x bb-y))))
311
312 (defun bb-place-ball (x y)
313 (let ((coord (cons x y)))
314 (cond
315 ((bb-member coord bb-balls-placed)
316 (setq bb-balls-placed (bb-delete coord bb-balls-placed))
317 (bb-update-board "-"))
318 (t
319 (setq bb-balls-placed (cons coord bb-balls-placed))
320 (bb-update-board "O")))))
321
322 (defun bb-trace-ray (x y)
323 (let ((result (bb-trace-ray-2
324 t
325 x
326 (cond
327 ((= x -1) 1)
328 ((= x 8) -1)
329 (t 0))
330 y
331 (cond
332 ((= y -1) 1)
333 ((= y 8) -1)
334 (t 0)))))
335 (cond
336 ((eq result 'hit)
337 (bb-update-board "H")
338 (setq bb-score (1+ bb-score)))
339 ((equal result (cons x y))
340 (bb-update-board "R")
341 (setq bb-score (1+ bb-score)))
342 (t
343 (setq bb-detour-count (1+ bb-detour-count))
344 (bb-update-board (format "%d" bb-detour-count))
345 (save-excursion
346 (bb-goto result)
347 (bb-update-board (format "%d" bb-detour-count)))
348 (setq bb-score (+ bb-score 2))))))
349
350 (defun bb-trace-ray-2 (first x dx y dy)
351 (cond
352 ((and (not first)
353 (bb-outside-box x y))
354 (cons x y))
355 ((bb-member (cons (+ x dx) (+ y dy)) bb-board)
356 'hit)
357 ((bb-member (cons (+ x dx dy) (+ y dy dx)) bb-board)
358 (bb-trace-ray-2 nil x (- dy) y (- dx)))
359 ((bb-member (cons (+ x dx (- dy)) (+ y dy (- dx))) bb-board)
360 (bb-trace-ray-2 nil x dy y dx))
361 (t
362 (bb-trace-ray-2 nil (+ x dx) dx (+ y dy) dy))))
363
364 (defun bb-done ()
365 "Finish the game and report score."
366 (interactive)
367 (let (bogus-balls)
368 (cond
369 ((not (= (length bb-balls-placed) (length bb-board)))
370 (message "There %s %d hidden ball%s; you have placed %d."
371 (if (= (length bb-board) 1) "is" "are")
372 (length bb-board)
373 (if (= (length bb-board) 1) "" "s")
374 (length bb-balls-placed)))
375 (t
376 (setq bogus-balls (bb-show-bogus-balls bb-balls-placed bb-board))
377 (if (= bogus-balls 0)
378 (message "Right! Your score is %d." bb-score)
379 (message "Oops! You missed %d ball%s. Your score is %d."
380 bogus-balls
381 (if (= bogus-balls 1) "" "s")
382 (+ bb-score (* 5 bogus-balls))))
383 (bb-goto '(-1 . -1))))))
384
385 (defun bb-show-bogus-balls (balls-placed board)
386 (bb-show-bogus-balls-2 balls-placed board "x")
387 (bb-show-bogus-balls-2 board balls-placed "o"))
388
389 (defun bb-show-bogus-balls-2 (list-1 list-2 c)
390 (cond
391 ((null list-1)
392 0)
393 ((bb-member (car list-1) list-2)
394 (bb-show-bogus-balls-2 (cdr list-1) list-2 c))
395 (t
396 (bb-goto (car list-1))
397 (bb-update-board c)
398 (1+ (bb-show-bogus-balls-2 (cdr list-1) list-2 c)))))
399
400 ;; blackbox.el ends here
401
402 (defun bb-goto (pos)
403 (goto-char (+ (* (car pos) 2) (* (cdr pos) 22) 26)))
404
405 (defun bb-update-board (c)
406 (let ((buffer-read-only nil))
407 (backward-char (1- (length c)))
408 (delete-char (length c))
409 (insert c)
410 (backward-char 1)))
411
412 (defun bb-member (elt list)
413 "Returns non-nil if ELT is an element of LIST."
414 (eval (cons 'or (mapcar (function (lambda (x) (equal x elt))) list))))
415
416 (defun bb-delete (item list)
417 "Deletes ITEM from LIST and returns a copy."
418 (cond
419 ((equal item (car list)) (cdr list))
420 (t (cons (car list) (bb-delete item (cdr list))))))