]> code.delx.au - gnu-emacs/blob - lisp/play/tetris.el
Nuke arch-tags.
[gnu-emacs] / lisp / play / tetris.el
1 ;;; tetris.el --- implementation of Tetris for Emacs
2
3 ;; Copyright (C) 1997, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008,
4 ;; 2009, 2010, 2011 Free Software Foundation, Inc.
5
6 ;; Author: Glynn Clements <glynn@sensei.co.uk>
7 ;; Version: 2.01
8 ;; Created: 1997-08-13
9 ;; Keywords: games
10
11 ;; This file is part of GNU Emacs.
12
13 ;; GNU Emacs is free software: you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation, either version 3 of the License, or
16 ;; (at your option) any later version.
17
18 ;; GNU Emacs is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 ;; GNU General Public License for more details.
22
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
25
26 ;;; Commentary:
27
28 ;;; Code:
29
30 (eval-when-compile
31 (require 'cl))
32
33 (require 'gamegrid)
34
35 ;; ;;;;;;;;;;;;; customization variables ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
36
37 (defgroup tetris nil
38 "Play a game of Tetris."
39 :prefix "tetris-"
40 :group 'games)
41
42 (defcustom tetris-use-glyphs t
43 "Non-nil means use glyphs when available."
44 :group 'tetris
45 :type 'boolean)
46
47 (defcustom tetris-use-color t
48 "Non-nil means use color when available."
49 :group 'tetris
50 :type 'boolean)
51
52 (defcustom tetris-draw-border-with-glyphs t
53 "Non-nil means draw a border even when using glyphs."
54 :group 'tetris
55 :type 'boolean)
56
57 (defcustom tetris-default-tick-period 0.3
58 "The default time taken for a shape to drop one row."
59 :group 'tetris
60 :type 'number)
61
62 (defcustom tetris-update-speed-function
63 'tetris-default-update-speed-function
64 "Function run whenever the Tetris score changes.
65 Called with two arguments: (SHAPES ROWS)
66 SHAPES is the number of shapes which have been dropped.
67 ROWS is the number of rows which have been completed.
68
69 If the return value is a number, it is used as the timer period."
70 :group 'tetris
71 :type 'function)
72
73 (defcustom tetris-mode-hook nil
74 "Hook run upon starting Tetris."
75 :group 'tetris
76 :type 'hook)
77
78 (defcustom tetris-tty-colors
79 ["blue" "white" "yellow" "magenta" "cyan" "green" "red"]
80 "Vector of colors of the various shapes in text mode."
81 :group 'tetris
82 :type (let ((names `("Shape 1" "Shape 2" "Shape 3"
83 "Shape 4" "Shape 5" "Shape 6" "Shape 7"))
84 (result nil))
85 (while names
86 (add-to-list 'result
87 (cons 'choice
88 (cons :tag
89 (cons (car names)
90 (mapcar (lambda (color)
91 (list 'const color))
92 (defined-colors)))))
93 t)
94 (setq names (cdr names)))
95 result))
96
97 (defcustom tetris-x-colors
98 [[0 0 1] [0.7 0 1] [1 1 0] [1 0 1] [0 1 1] [0 1 0] [1 0 0]]
99 "Vector of colors of the various shapes."
100 :group 'tetris
101 :type 'sexp)
102
103 (defcustom tetris-buffer-name "*Tetris*"
104 "Name used for Tetris buffer."
105 :group 'tetris
106 :type 'string)
107
108 (defcustom tetris-buffer-width 30
109 "Width of used portion of buffer."
110 :group 'tetris
111 :type 'number)
112
113 (defcustom tetris-buffer-height 22
114 "Height of used portion of buffer."
115 :group 'tetris
116 :type 'number)
117
118 (defcustom tetris-width 10
119 "Width of playing area."
120 :group 'tetris
121 :type 'number)
122
123 (defcustom tetris-height 20
124 "Height of playing area."
125 :group 'tetris
126 :type 'number)
127
128 (defcustom tetris-top-left-x 3
129 "X position of top left of playing area."
130 :group 'tetris
131 :type 'number)
132
133 (defcustom tetris-top-left-y 1
134 "Y position of top left of playing area."
135 :group 'tetris
136 :type 'number)
137
138 (defvar tetris-next-x (+ (* 2 tetris-top-left-x) tetris-width)
139 "X position of next shape.")
140
141 (defvar tetris-next-y tetris-top-left-y
142 "Y position of next shape.")
143
144 (defvar tetris-score-x tetris-next-x
145 "X position of score.")
146
147 (defvar tetris-score-y (+ tetris-next-y 6)
148 "Y position of score.")
149
150 ;; It is not safe to put this in /tmp.
151 ;; Someone could make a symlink in /tmp
152 ;; pointing to a file you don't want to clobber.
153 (defvar tetris-score-file "tetris-scores"
154 ;; anybody with a well-connected server want to host this?
155 ;(defvar tetris-score-file "/anonymous@ftp.pgt.com:/pub/cgw/tetris-scores"
156 "File for holding high scores.")
157
158 ;; ;;;;;;;;;;;;; display options ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
159
160 (defvar tetris-blank-options
161 '(((glyph colorize)
162 (t ?\040))
163 ((color-x color-x)
164 (mono-x grid-x)
165 (color-tty color-tty))
166 (((glyph color-x) [0 0 0])
167 (color-tty "black"))))
168
169 (defvar tetris-cell-options
170 '(((glyph colorize)
171 (emacs-tty ?O)
172 (t ?\040))
173 ((color-x color-x)
174 (mono-x mono-x)
175 (color-tty color-tty)
176 (mono-tty mono-tty))
177 ;; color information is taken from tetris-x-colors and tetris-tty-colors
178 ))
179
180 (defvar tetris-border-options
181 '(((glyph colorize)
182 (t ?\+))
183 ((color-x color-x)
184 (mono-x grid-x)
185 (color-tty color-tty))
186 (((glyph color-x) [0.5 0.5 0.5])
187 (color-tty "white"))))
188
189 (defvar tetris-space-options
190 '(((t ?\040))
191 nil
192 nil))
193
194 ;; ;;;;;;;;;;;;; constants ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
195
196 (defconst tetris-shapes
197 [[[[0 0] [1 0] [0 1] [1 1]]]
198
199 [[[0 0] [1 0] [2 0] [2 1]]
200 [[1 -1] [1 0] [1 1] [0 1]]
201 [[0 -1] [0 0] [1 0] [2 0]]
202 [[1 -1] [2 -1] [1 0] [1 1]]]
203
204 [[[0 0] [1 0] [2 0] [0 1]]
205 [[0 -1] [1 -1] [1 0] [1 1]]
206 [[2 -1] [0 0] [1 0] [2 0]]
207 [[1 -1] [1 0] [1 1] [2 1]]]
208
209 [[[0 0] [1 0] [1 1] [2 1]]
210 [[1 0] [0 1] [1 1] [0 2]]]
211
212 [[[1 0] [2 0] [0 1] [1 1]]
213 [[0 0] [0 1] [1 1] [1 2]]]
214
215 [[[1 0] [0 1] [1 1] [2 1]]
216 [[1 0] [1 1] [2 1] [1 2]]
217 [[0 1] [1 1] [2 1] [1 2]]
218 [[1 0] [0 1] [1 1] [1 2]]]
219
220 [[[0 0] [1 0] [2 0] [3 0]]
221 [[1 -1] [1 0] [1 1] [1 2]]]]
222 "Each shape is described by a vector that contains the coordinates of
223 each one of its four blocks.")
224
225 ;;the scoring rules were taken from "xtetris". Blocks score differently
226 ;;depending on their rotation
227
228 (defconst tetris-shape-scores
229 [[6] [6 7 6 7] [6 7 6 7] [6 7] [6 7] [5 5 6 5] [5 8]] )
230
231 (defconst tetris-shape-dimensions
232 [[2 2] [3 2] [3 2] [3 2] [3 2] [3 2] [4 1]])
233
234 (defconst tetris-blank 7)
235
236 (defconst tetris-border 8)
237
238 (defconst tetris-space 9)
239
240 (defun tetris-default-update-speed-function (shapes rows)
241 (/ 20.0 (+ 50.0 rows)))
242
243 ;; ;;;;;;;;;;;;; variables ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
244
245 (defvar tetris-shape 0)
246 (defvar tetris-rot 0)
247 (defvar tetris-next-shape 0)
248 (defvar tetris-n-shapes 0)
249 (defvar tetris-n-rows 0)
250 (defvar tetris-score 0)
251 (defvar tetris-pos-x 0)
252 (defvar tetris-pos-y 0)
253 (defvar tetris-paused nil)
254
255 (make-variable-buffer-local 'tetris-shape)
256 (make-variable-buffer-local 'tetris-rot)
257 (make-variable-buffer-local 'tetris-next-shape)
258 (make-variable-buffer-local 'tetris-n-shapes)
259 (make-variable-buffer-local 'tetris-n-rows)
260 (make-variable-buffer-local 'tetris-score)
261 (make-variable-buffer-local 'tetris-pos-x)
262 (make-variable-buffer-local 'tetris-pos-y)
263 (make-variable-buffer-local 'tetris-paused)
264
265 ;; ;;;;;;;;;;;;; keymaps ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
266
267 (defvar tetris-mode-map
268 (let ((map (make-sparse-keymap 'tetris-mode-map)))
269 (define-key map "n" 'tetris-start-game)
270 (define-key map "q" 'tetris-end-game)
271 (define-key map "p" 'tetris-pause-game)
272
273 (define-key map " " 'tetris-move-bottom)
274 (define-key map [left] 'tetris-move-left)
275 (define-key map [right] 'tetris-move-right)
276 (define-key map [up] 'tetris-rotate-prev)
277 (define-key map [down] 'tetris-rotate-next)
278 map))
279
280 (defvar tetris-null-map
281 (let ((map (make-sparse-keymap 'tetris-null-map)))
282 (define-key map "n" 'tetris-start-game)
283 map))
284
285 ;; ;;;;;;;;;;;;;;;; game functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
286
287 (defun tetris-display-options ()
288 (let ((options (make-vector 256 nil)))
289 (loop for c from 0 to 255 do
290 (aset options c
291 (cond ((= c tetris-blank)
292 tetris-blank-options)
293 ((and (>= c 0) (<= c 6))
294 (append
295 tetris-cell-options
296 `((((glyph color-x) ,(aref tetris-x-colors c))
297 (color-tty ,(aref tetris-tty-colors c))
298 (t nil)))))
299 ((= c tetris-border)
300 tetris-border-options)
301 ((= c tetris-space)
302 tetris-space-options)
303 (t
304 '(nil nil nil)))))
305 options))
306
307 (defun tetris-get-tick-period ()
308 (if (boundp 'tetris-update-speed-function)
309 (let ((period (apply tetris-update-speed-function
310 tetris-n-shapes
311 tetris-n-rows nil)))
312 (and (numberp period) period))))
313
314 (defun tetris-get-shape-cell (block)
315 (aref (aref (aref tetris-shapes
316 tetris-shape) tetris-rot)
317 block))
318
319 (defun tetris-shape-width ()
320 (aref (aref tetris-shape-dimensions tetris-shape) 0))
321
322 (defun tetris-shape-rotations ()
323 (length (aref tetris-shapes tetris-shape)))
324
325 (defun tetris-draw-score ()
326 (let ((strings (vector (format "Shapes: %05d" tetris-n-shapes)
327 (format "Rows: %05d" tetris-n-rows)
328 (format "Score: %05d" tetris-score))))
329 (loop for y from 0 to 2 do
330 (let* ((string (aref strings y))
331 (len (length string)))
332 (loop for x from 0 to (1- len) do
333 (gamegrid-set-cell (+ tetris-score-x x)
334 (+ tetris-score-y y)
335 (aref string x)))))))
336
337 (defun tetris-update-score ()
338 (tetris-draw-score)
339 (let ((period (tetris-get-tick-period)))
340 (if period (gamegrid-set-timer period))))
341
342 (defun tetris-new-shape ()
343 (setq tetris-shape tetris-next-shape)
344 (setq tetris-rot 0)
345 (setq tetris-next-shape (random 7))
346 (setq tetris-pos-x (/ (- tetris-width (tetris-shape-width)) 2))
347 (setq tetris-pos-y 0)
348 (if (tetris-test-shape)
349 (tetris-end-game)
350 (tetris-draw-shape)
351 (tetris-draw-next-shape)
352 (tetris-update-score)))
353
354 (defun tetris-draw-next-shape ()
355 (loop for x from 0 to 3 do
356 (loop for y from 0 to 3 do
357 (gamegrid-set-cell (+ tetris-next-x x)
358 (+ tetris-next-y y)
359 tetris-blank)))
360 (loop for i from 0 to 3 do
361 (let ((tetris-shape tetris-next-shape)
362 (tetris-rot 0))
363 (gamegrid-set-cell (+ tetris-next-x
364 (aref (tetris-get-shape-cell i) 0))
365 (+ tetris-next-y
366 (aref (tetris-get-shape-cell i) 1))
367 tetris-shape))))
368
369 (defun tetris-draw-shape ()
370 (loop for i from 0 to 3 do
371 (let ((c (tetris-get-shape-cell i)))
372 (gamegrid-set-cell (+ tetris-top-left-x
373 tetris-pos-x
374 (aref c 0))
375 (+ tetris-top-left-y
376 tetris-pos-y
377 (aref c 1))
378 tetris-shape))))
379
380 (defun tetris-erase-shape ()
381 (loop for i from 0 to 3 do
382 (let ((c (tetris-get-shape-cell i)))
383 (gamegrid-set-cell (+ tetris-top-left-x
384 tetris-pos-x
385 (aref c 0))
386 (+ tetris-top-left-y
387 tetris-pos-y
388 (aref c 1))
389 tetris-blank))))
390
391 (defun tetris-test-shape ()
392 (let ((hit nil))
393 (loop for i from 0 to 3 do
394 (unless hit
395 (setq hit
396 (let* ((c (tetris-get-shape-cell i))
397 (xx (+ tetris-pos-x
398 (aref c 0)))
399 (yy (+ tetris-pos-y
400 (aref c 1))))
401 (or (>= xx tetris-width)
402 (>= yy tetris-height)
403 (/= (gamegrid-get-cell
404 (+ xx tetris-top-left-x)
405 (+ yy tetris-top-left-y))
406 tetris-blank))))))
407 hit))
408
409 (defun tetris-full-row (y)
410 (let ((full t))
411 (loop for x from 0 to (1- tetris-width) do
412 (if (= (gamegrid-get-cell (+ tetris-top-left-x x)
413 (+ tetris-top-left-y y))
414 tetris-blank)
415 (setq full nil)))
416 full))
417
418 (defun tetris-shift-row (y)
419 (if (= y 0)
420 (loop for x from 0 to (1- tetris-width) do
421 (gamegrid-set-cell (+ tetris-top-left-x x)
422 (+ tetris-top-left-y y)
423 tetris-blank))
424 (loop for x from 0 to (1- tetris-width) do
425 (let ((c (gamegrid-get-cell (+ tetris-top-left-x x)
426 (+ tetris-top-left-y y -1))))
427 (gamegrid-set-cell (+ tetris-top-left-x x)
428 (+ tetris-top-left-y y)
429 c)))))
430
431 (defun tetris-shift-down ()
432 (loop for y0 from 0 to (1- tetris-height) do
433 (if (tetris-full-row y0)
434 (progn (setq tetris-n-rows (1+ tetris-n-rows))
435 (loop for y from y0 downto 0 do
436 (tetris-shift-row y))))))
437
438 (defun tetris-draw-border-p ()
439 (or (not (eq gamegrid-display-mode 'glyph))
440 tetris-draw-border-with-glyphs))
441
442 (defun tetris-init-buffer ()
443 (gamegrid-init-buffer tetris-buffer-width
444 tetris-buffer-height
445 tetris-space)
446 (let ((buffer-read-only nil))
447 (if (tetris-draw-border-p)
448 (loop for y from -1 to tetris-height do
449 (loop for x from -1 to tetris-width do
450 (gamegrid-set-cell (+ tetris-top-left-x x)
451 (+ tetris-top-left-y y)
452 tetris-border))))
453 (loop for y from 0 to (1- tetris-height) do
454 (loop for x from 0 to (1- tetris-width) do
455 (gamegrid-set-cell (+ tetris-top-left-x x)
456 (+ tetris-top-left-y y)
457 tetris-blank)))
458 (if (tetris-draw-border-p)
459 (loop for y from -1 to 4 do
460 (loop for x from -1 to 4 do
461 (gamegrid-set-cell (+ tetris-next-x x)
462 (+ tetris-next-y y)
463 tetris-border))))))
464
465 (defun tetris-reset-game ()
466 (gamegrid-kill-timer)
467 (tetris-init-buffer)
468 (setq tetris-next-shape (random 7))
469 (setq tetris-shape 0
470 tetris-rot 0
471 tetris-pos-x 0
472 tetris-pos-y 0
473 tetris-n-shapes 0
474 tetris-n-rows 0
475 tetris-score 0
476 tetris-paused nil)
477 (tetris-new-shape))
478
479 (defun tetris-shape-done ()
480 (tetris-shift-down)
481 (setq tetris-n-shapes (1+ tetris-n-shapes))
482 (setq tetris-score
483 (+ tetris-score
484 (aref (aref tetris-shape-scores tetris-shape) tetris-rot)))
485 (tetris-update-score)
486 (tetris-new-shape))
487
488 (defun tetris-update-game (tetris-buffer)
489 "Called on each clock tick.
490 Drops the shape one square, testing for collision."
491 (if (and (not tetris-paused)
492 (eq (current-buffer) tetris-buffer))
493 (let (hit)
494 (tetris-erase-shape)
495 (setq tetris-pos-y (1+ tetris-pos-y))
496 (setq hit (tetris-test-shape))
497 (if hit
498 (setq tetris-pos-y (1- tetris-pos-y)))
499 (tetris-draw-shape)
500 (if hit
501 (tetris-shape-done)))))
502
503 (defun tetris-move-bottom ()
504 "Drop the shape to the bottom of the playing area."
505 (interactive)
506 (unless tetris-paused
507 (let ((hit nil))
508 (tetris-erase-shape)
509 (while (not hit)
510 (setq tetris-pos-y (1+ tetris-pos-y))
511 (setq hit (tetris-test-shape)))
512 (setq tetris-pos-y (1- tetris-pos-y))
513 (tetris-draw-shape)
514 (tetris-shape-done))))
515
516 (defun tetris-move-left ()
517 "Move the shape one square to the left."
518 (interactive)
519 (unless tetris-paused
520 (tetris-erase-shape)
521 (setq tetris-pos-x (1- tetris-pos-x))
522 (if (tetris-test-shape)
523 (setq tetris-pos-x (1+ tetris-pos-x)))
524 (tetris-draw-shape)))
525
526 (defun tetris-move-right ()
527 "Move the shape one square to the right."
528 (interactive)
529 (unless tetris-paused
530 (tetris-erase-shape)
531 (setq tetris-pos-x (1+ tetris-pos-x))
532 (if (tetris-test-shape)
533 (setq tetris-pos-x (1- tetris-pos-x)))
534 (tetris-draw-shape)))
535
536 (defun tetris-rotate-prev ()
537 "Rotate the shape clockwise."
538 (interactive)
539 (unless tetris-paused
540 (tetris-erase-shape)
541 (setq tetris-rot (% (+ 1 tetris-rot)
542 (tetris-shape-rotations)))
543 (if (tetris-test-shape)
544 (setq tetris-rot (% (+ 3 tetris-rot)
545 (tetris-shape-rotations))))
546 (tetris-draw-shape)))
547
548 (defun tetris-rotate-next ()
549 "Rotate the shape anticlockwise."
550 (interactive)
551 (unless tetris-paused
552 (tetris-erase-shape)
553 (setq tetris-rot (% (+ 3 tetris-rot)
554 (tetris-shape-rotations)))
555 (if (tetris-test-shape)
556 (setq tetris-rot (% (+ 1 tetris-rot)
557 (tetris-shape-rotations))))
558 (tetris-draw-shape)))
559
560 (defun tetris-end-game ()
561 "Terminate the current game."
562 (interactive)
563 (gamegrid-kill-timer)
564 (use-local-map tetris-null-map)
565 (gamegrid-add-score tetris-score-file tetris-score))
566
567 (defun tetris-start-game ()
568 "Start a new game of Tetris."
569 (interactive)
570 (tetris-reset-game)
571 (use-local-map tetris-mode-map)
572 (let ((period (or (tetris-get-tick-period)
573 tetris-default-tick-period)))
574 (gamegrid-start-timer period 'tetris-update-game)))
575
576 (defun tetris-pause-game ()
577 "Pause (or resume) the current game."
578 (interactive)
579 (setq tetris-paused (not tetris-paused))
580 (message (and tetris-paused "Game paused (press p to resume)")))
581
582 (defun tetris-active-p ()
583 (eq (current-local-map) tetris-mode-map))
584
585 (put 'tetris-mode 'mode-class 'special)
586
587 (define-derived-mode tetris-mode nil "Tetris"
588 "A mode for playing Tetris."
589
590 (add-hook 'kill-buffer-hook 'gamegrid-kill-timer nil t)
591
592 (use-local-map tetris-null-map)
593
594 (unless (featurep 'emacs)
595 (setq mode-popup-menu
596 '("Tetris Commands"
597 ["Start new game" tetris-start-game]
598 ["End game" tetris-end-game
599 (tetris-active-p)]
600 ["Pause" tetris-pause-game
601 (and (tetris-active-p) (not tetris-paused))]
602 ["Resume" tetris-pause-game
603 (and (tetris-active-p) tetris-paused)])))
604
605 (setq show-trailing-whitespace nil)
606
607 (setq gamegrid-use-glyphs tetris-use-glyphs)
608 (setq gamegrid-use-color tetris-use-color)
609
610 (gamegrid-init (tetris-display-options)))
611
612 ;;;###autoload
613 (defun tetris ()
614 "Play the Tetris game.
615 Shapes drop from the top of the screen, and the user has to move and
616 rotate the shape to fit in with those at the bottom of the screen so
617 as to form complete rows.
618
619 tetris-mode keybindings:
620 \\<tetris-mode-map>
621 \\[tetris-start-game] Starts a new game of Tetris
622 \\[tetris-end-game] Terminates the current game
623 \\[tetris-pause-game] Pauses (or resumes) the current game
624 \\[tetris-move-left] Moves the shape one square to the left
625 \\[tetris-move-right] Moves the shape one square to the right
626 \\[tetris-rotate-prev] Rotates the shape clockwise
627 \\[tetris-rotate-next] Rotates the shape anticlockwise
628 \\[tetris-move-bottom] Drops the shape to the bottom of the playing area
629
630 "
631 (interactive)
632
633 (select-window (or (get-buffer-window tetris-buffer-name)
634 (selected-window)))
635 (switch-to-buffer tetris-buffer-name)
636 (gamegrid-kill-timer)
637 (tetris-mode)
638 (tetris-start-game))
639
640 (random t)
641
642 (provide 'tetris)
643
644 ;;; tetris.el ends here