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