[comp.emacs] getris.el -- clone of a famous Russian game program.

mad@math.keio.JUNET (MAEDA Atusi) (09/14/89)

;;; Getris -- clone of a famous Russian game program.
;; Copyright (C) 1989 by MAEDA Atusi
;; Originally written by MAEDA Atusi
;; Modified by Hideto Sazuka Thu Jun 29 12:09:36 1989
;; Modified by MAEDA Atusi Thu Jun 29 20:50:16 1989
;; Modified by MAEDA Atusi Wed Jul  5 20:21:31 1989

;; This file is part of GNU Emacs.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY.  No author or distributor
;; accepts responsibility to anyone for the consequences of using it
;; or for whether it serves any particular purpose or works at all,
;; unless he says so in writing.  Refer to the GNU Emacs General Public
;; License for full details.

;; Everyone is granted permission to copy, modify and redistribute
;; GNU Emacs, but only under the conditions described in the
;; GNU Emacs General Public License.   A copy of this license is
;; supposed to have been given to you along with GNU Emacs so you
;; can know your rights and responsibilities.  It should be in a
;; file named COPYING.  Among other things, the copyright notice
;; and this notice must be preserved on all copies.

(provide 'getris)
;(require 'boss)

;;; User customizable variables.

(defvar getris-initial-delay 200
  "*Delay count to control the speed of getris game.  Bigger means slower.
You should substitute this value according to your system's performance.")

(defvar getris-min-delay 2
  "*Minimum delay count to control the maximum speed of getris game.
Smaller means faster.  The default value, 2, means `as fast as possible.'")

(defvar getris-acceleration 200
  "*Acceleration rate of getris game.
Smaller value means quicker speed-up.  This value is performance independent.")

(defvar getris-high-score-file
  (or (getenv "GETRISFILE")
      "$HOME/.getris")
  "*File name where top ten scores of getris are recorded.
Initialized from GETRISFILE environment variable.
Nil means does not record scores.")

(defvar getris-block-string
  (if (and (boundp kanji-flag) kanji-flag) "\242\243" "[]")
  "*String for getris block.  Must be width of two column.")

(defvar getris-width 10
  "*Width of getris board (number of blocks).  Each block occupies two
column width on window.")

(defvar getris-use-full-window nil
  "*Non-nil means that starting Getris game deletes other windows.")

(defun getris ()
  "Clone of a famous Russian game program."
  (interactive)
  (setq getris-previous-window-configuration
	(current-window-configuration))
  (switch-to-buffer "*Getris*")
  (getris-mode)
  (getris-startup))

;;; Internal variables.

(defvar getris-command-vector nil
  "Vector of functions which maps character to getris command.")

(defvar getris-mode-map nil)

(defvar getris-piece-data nil
  "Vector of piece data.
Each element of this vector is vector of size four, which correspond
to four directions of piece.  And each element of four size vectors is
a list of form:
	(max-y-offset (x1 . y1) (x2 . y2) (x3 . y3) (x4 . y4))
where:
	(x1 . y1) ... (x4 . y4) are offsets of dot from imaginary `origin'
				at upper-left side of the piece,
	0 <= y[1-4] <= max-y-offset.")

(defvar getris-left-margin)
(defvar getris-height)
(defvar getris-previous-window-configuration nil)
(defvar getris-blank-line)
(defvar getris-complete-line)
(defvar getris-line-length)

(defun getris-startup ()
  (setq buffer-read-only nil)
  (erase-buffer)
  (goto-char (point-min))
  (insert (substitute-command-keys "

<<< G E T R I S >>>

Clone of a famous Russian game program.

Originally written by
MAEDA Atusi
mad@nakanishi.math.keio.junet


<Type \\[getris-mode-help] for help, \\[getris-start] to start game.>
"))
  (center-region (point-min) (point-max))
  (setq buffer-read-only t))

(defun getris-mode-help ()
  (interactive)
  (message (concat
	    (substitute-command-keys "\\[getris-mode-help]:Print this  ")
	    (substitute-command-keys "\\[getris-start]:Start new game  ")
	    (substitute-command-keys "\\[getris-help]:List action keys  ")
	    (substitute-command-keys "\\[boss-has-come]:Boss has come!  ")
	    (substitute-command-keys "\\[getris-exit]:Exit"))))

(or getris-mode-map
    (progn
      (setq getris-mode-map (make-sparse-keymap))
      (define-key getris-mode-map "?" 'getris-mode-help)
      (define-key getris-mode-map "\C-m" 'getris-start)
      (define-key getris-mode-map "h" 'getris-help)
      (define-key getris-mode-map "\e" 'boss-has-come)
      (define-key getris-mode-map "q" 'getris-exit)))

(defun getris-help ()
  (interactive)
  (message "j:Left  k:Rotate  l:Right  Space:Drop  ESC:Escape  q:Exit"))

(or getris-command-vector
    (progn
      (setq getris-command-vector (make-vector 256 'getris-help))
      (aset getris-command-vector ?j 'getris-move-left)
      (aset getris-command-vector ?k 'getris-rotate)
      (aset getris-command-vector ?l 'getris-move-right)
      (aset getris-command-vector ?  'getris-drop)
      (aset getris-command-vector ?q 'getris-quit)
      (aset getris-command-vector ?\e 'getris-boss-has-come)))

(defun getris-mode ()
  "Major mode for playing getris game.
\\{getris-mode-map}
Type \\[getris-help] for key action in the game.
Entry to this mode calls the value of getris-mode-hook
if that value is non-nil."
  (interactive)
  (kill-all-local-variables)
  (make-local-variable 'global-mode-string)
  (setq major-mode 'getris-mode)
  (setq mode-name "Getris")
  (use-local-map getris-mode-map)
  (buffer-flush-undo (current-buffer))
  (setq buffer-read-only t)
  (getris-mode-help)
  (run-hooks 'getris-mode-hook))

(defun getris-start ()
  (interactive)
  (switch-to-buffer "*Getris*")
  (if getris-use-full-window
      (delete-other-windows)
    ;; Enlarge window size if necessary.
    (progn
      (getris-get-window-size)
      (if (< getris-left-margin 5)
	  (enlarge-window (1+ (* 2 (- 5 getris-left-margin))) t))
      (if (< getris-height 20)
	  (enlarge-window (- 20 getris-height)))))
  (getris-get-window-size)		;again
  (if (or (< getris-height 20)
	  (< getris-left-margin 5))
      (error "Window size too small for getris."))
  (let ((left-margin-space (make-string (1- getris-left-margin) ? )))
    (setq getris-blank-line
	  (concat left-margin-space "||"
		  (make-string (* 2 getris-width) ? ) "||\n"))
    (setq getris-complete-line
	  (regexp-quote (concat left-margin-space "||"
				(getris-repeat-string getris-block-string
						      getris-width)
				"||")))
    (setq getris-line-length (length getris-blank-line))
    (setq buffer-read-only nil)
    (erase-buffer)
    (let ((i 0))
      (while (< i getris-height)
	(insert getris-blank-line)
	(setq i (1+ i))))
    (insert (concat left-margin-space
		    (make-string (+ 4 (* 2 getris-width)) ?=))))
  (random t)				;randomize by current time
  (catch 'getris-quit-tag
    (getris-main-loop)
    (getris-mode-help)))

(defun getris-get-window-size ()
  (setq getris-height (- (window-height) 2))
  (setq getris-left-margin
	(/ (- (window-width)
	      (* 2 getris-width)
	      4)
	   2)))

(defun getris-repeat-string (string times)
  (let ((result ""))
    (while (> times 0)
      (setq result (concat string result))
      (setq times (1- times)))
    result))

(defun getris-exit ()
  (interactive)
  (set-window-configuration getris-previous-window-configuration))

(defun abs (number)
  (if (< number 0)
      (- number)
    number))

(defun getris-main-loop ()
  (let ((delay getris-initial-delay)
	(score 0)
	(loop-count 0)
	(center (+ getris-left-margin (logior getris-width 1) -2))
	(disp (/ getris-width 4))
	delay-count
	x y direction kind)
    (while (progn (setq x (+ center (ash (mod (random) disp) 1))
			y -1
			direction (mod (abs (random)) 4)
			piece-num (mod (abs (random)) 7)
			piece-vector (aref getris-piece-data piece-num)
			piece-data (aref piece-vector direction))
		  (getris-puttable-p x 0 piece-data))
      (while (getris-puttable-p x (1+ y) piece-data)
	(getris-set-piece x (setq y (1+ y)) piece-data)
	(setq delay (max getris-min-delay
			 2
			 (- getris-initial-delay
			    (/ loop-count
			       getris-acceleration))))
	(setq delay-count delay)
	(while (> (setq delay-count (1- delay-count))
		  0)
	  (setq loop-count (1+ loop-count))
	  (if (input-pending-p)
	      ;; Execute a command.
	      ;; Variable values may be modified.
	      (funcall (aref getris-command-vector (read-char)))))
	(getris-unset-piece x y piece-data))
      (getris-set-piece x y piece-data)
      (setq score (+ score (car piece-data)
		     (getris-test-delete-line y piece-data)))
      (getris-show-score))
    (end-of-line 1)
    (insert "*** GAME OVER ***")
    (setq buffer-read-only t)
    (if getris-high-score-file
	(getris-show-high-score))))

(defmacro getris-goto-x-y (x y)
  (`(goto-char (+ (* (, y) getris-line-length)
		  (, x)
		  1))))

(defmacro sit-for-getris (n)
  (`(progn (goto-char (point-min))
	   (sit-for (, n)))))

(defun getris-puttable-p (x y piece-data)
  (let ((result t))
    (while (and (setq piece-data (cdr piece-data)) result)
      (getris-goto-x-y (+ x (car (car piece-data)))
		       (+ y (cdr (car piece-data))))
      (if (not (= (following-char) ? ))
	  (setq result nil)))
    result))

(defun getris-set-piece (x y piece-data)
  (while (setq piece-data (cdr piece-data))
    (getris-goto-x-y (+ x (car (car piece-data)))
		     (+ y (cdr (car piece-data))))
    (delete-char 2)
    (insert getris-block-string)
  (sit-for-getris 0)))

(defun getris-unset-piece (x y piece-data)
  (while (setq piece-data (cdr piece-data))
    (getris-goto-x-y (+ x (car (car piece-data)))
		     (+ y (cdr (car piece-data))))
    (delete-char 2)
    (insert "  ")))

(defun getris-test-delete-line (y piece-data)
  (let ((max-y (+ y (car piece-data)))
	(lines-deleted 0))
    (while (<= y max-y)
      (getris-goto-x-y 0 y)
      (if (looking-at getris-complete-line)
	  (progn (setq lines-deleted (1+ lines-deleted))
		 (ding)
		 (delete-region (point)
				(progn (next-line 1) (point)))
		 (insert getris-blank-line)
		 (sit-for 1)
		 (delete-region (point)
				(progn (previous-line 1) (point)))
		 (goto-char (point-min))
		 (insert getris-blank-line)
		 (sit-for-getris 0)))
      (setq y (1+ y)))
    (* lines-deleted lines-deleted lines-deleted)))

(defun getris-show-score ()
  (setq global-mode-string (format "Score: %d" score))
  (save-excursion (set-buffer (other-buffer)))
  (set-buffer-modified-p (buffer-modified-p))
  (sit-for 0))

(defun getris-show-high-score ()
  (let ((file (substitute-in-file-name getris-high-score-file)))
    (find-file-other-window file)
    (goto-char (point-max))
    (insert (format "  %08d %20s at %s on %s\n"
		    score
		    (user-full-name)
		    (current-time-string)
		    (system-name)))
    (sort-fields -1 (point-min) (point-max))
    (goto-line 11)
    (move-to-column 0)
    (delete-region (point) (point-max))
    (write-file file)
    (goto-char (point-min))
    (pop-to-buffer "*Getris*")))

(defun getris-move-left ()
  (getris-unset-piece x y piece-data)
  (getris-set-piece
   (if (getris-puttable-p (- x 2) y piece-data)
       (setq x (- x 2))
     x)
   y piece-data))

(defun getris-move-right ()
  (getris-unset-piece x y piece-data)
  (getris-set-piece
   (if (getris-puttable-p (+ x 2) y piece-data)
       (setq x (+ x 2))
     x)
   y piece-data))

(defun getris-rotate ()
  (let ((new-direction (if (= direction 3)
			   0
			 (1+ direction))))
    (getris-unset-piece x y piece-data)
    (getris-set-piece
     x y
     (if (getris-puttable-p x y (aref piece-vector new-direction))
	 (setq piece-data
	       (aref piece-vector (setq direction new-direction)))
       piece-data))))

(defun getris-drop ()
  (getris-unset-piece x y piece-data)
  (while (getris-puttable-p x (1+ y) piece-data)
    (setq y (1+ y)))
  (setq delay-count delay))

(defun getris-quit ()
  (if (y-or-n-p "Are you sure to quit Getris? ")
      (progn
	(setq buffer-read-only t)
	(throw 'getris-quit-tag (getris-exit)))))

(defun getris-boss-has-come ()
  ;; Need improvement.
  (save-window-excursion
    (boss-has-come)
    (local-set-key "\C-c\C-c" 'getris-boss-goes-away)
    (recursive-edit)))

(defun getris-boss-goes-away ()
  (interactive)
  (boss-goes-away)
  (exit-recursive-edit))

(defun getris-make-piece-data (raw-piece-data)
  (setq getris-piece-data (make-vector (length raw-piece-data) nil))
  (let ((kind 0))
    (while raw-piece-data
      (let ((direction 0)
	    (four-list (car raw-piece-data))
	    (four-vector (make-vector 4 nil)))
	(while four-list
	  (let ((y 0)
		(piece-data nil)
		(max-y 0)
		(lines (car four-list)))
	    (while lines
	      (let ((x 0)
		    (line (car lines))
		    (len (length (car lines))))
		(while (< x len)
		  (if (= (aref line x) ?#)
		      (progn
			(setq piece-data
			      (cons (cons (+ x x) y) piece-data))
			(if (> y max-y)
			    (setq max-y y))))
		  (setq x (1+ x)))
		(setq lines (cdr lines)
		      y (1+ y))))
	    (aset four-vector direction (cons max-y piece-data)))
	  (setq four-list (cdr four-list)
		direction (1+ direction)))
	(aset getris-piece-data kind four-vector))
      (setq raw-piece-data (cdr raw-piece-data)
	    kind (1+ kind)))))

(or getris-piece-data
    (getris-make-piece-data
     '(
       ;; ####
       ( (""
	  ""
	  "####"
	  "")
	 (" #"
	  " #"
	  " #"
	  " #")
	 (""
	  "####"
	  ""
	  "")
	 ("  #"
	  "  #"
	  "  #"
	  "  #"))
       ;; ##
       ;; ##
       ( ("##"
	  "##")
	 ("##"
	  "##")
	 ("##"
	  "##")
	 ("##"
	  "##"))
       ;; ##
       ;;  ##
       ( ("##"
	  " ##")
	 (" #"
	  "##"
	  "#")
	 ("##"
	  " ##")
	 (" #"
	  "##"
	  "#"))
       ;;  ##
       ;; ##
       ( (" ##"
	  "##")
	 ("#"
	  "##"
	  " #")
	 (" ##"
	  "##")
	 ("#"
	  "##"
	  " #"))
       ;;  #
       ;; ###
       ( (" #"
	  "###")
	 (" #"
	  "##"
	  " #")
	 (""
	  "###"
	  " #")
	 (" #"
	  " ##"
	  " #"))
       ;; ###
       ;; #
       ( (""
	  "###"
	  "#")
	 ("#"
	  "#"
	  "##")
	 ("  #"
	  "###")
	 (" ##"
	  "  #"
	  "  #"))
       ;; #
       ;; ###
       ( (""
	  "###"
	  "  #")
	 ("##"
	  "#"
	  "#")
	 ("#"
	  "###")
	 ("  #"
	  "  #"
	  " ##"))
       )))

kayvan@mrspoc.transact.COM (Kayvan Sylvan) (09/16/89)

Herb Barad writes:
> The recently posted getris.el is a great game, but the value
> kanji-flag is not defined.  Below is a diff to fix this for
> non-Japanese systems.  Good luck.

Actually, the "correct" fix is not to set kanji-flag for non-japanese
versions, but to do this:

*** teris-orig	Fri Sep 15 16:09:10 1989
--- src/elisp/getris.el	Fri Sep 15 11:28:04 1989
***************
*** 48,52
  
  (defvar getris-block-string
!   (if (and (boundp kanji-flag) kanji-flag) "\242\243" "[]")
    "*String for getris block.  Must be width of two column.")
  

--- 48,52 -----
  
  (defvar getris-block-string
!   (if (and (boundp 'kanji-flag) kanji-flag) "\242\243" "[]")
    "*String for getris block.  Must be width of two column.")
  

The boundp function expects a symbol name, not the value of the symbol.

Also, where is the missing boss-* functions? (I assume they're in the
commented out '(require boss) module)...

			---Kayvan
---
Kayvan Sylvan @ Transact Software, Inc. -*-  Los Altos, CA (415) 961-6112
Internet: kayvan@Transact.COM -*- UUCP: ...!{apple,pyramid,mips}!mrspoc!kayvan